how to directly add ENUM in Mybatis IN query ,with no params - mybatis

i directly have to check ENUM in where condition ,
<if test="params.fileTypes != null and !params.fileTypes.isEmpty()">
AND
RTRIM(INTH.IF_FILETYPE) IN
<foreach item="item" index="index"
collection="params.fileTypes" open="(" separator="," close=")">
#{item}
</foreach>
</if>
<if test="params.fileTypes.isEmpty()">
AND
RTRIM(IF_FILETYPE) IN (#{#com.xyz.wealth.appconfiguration.domain.FileType})
</if>

Assuming that com.xyz.wealth.appconfiguration.domain.FileType is the enum you are referring to and it has two values. e.g.
public enum FileType {
DOCUMENT,
IMAGE
}
The following MyBatis statement ...
RTRIM(IF_FILETYPE) IN (
<foreach item="x" separator=","
collection="#om.xyz.wealth.appconfiguration.domain.FileType#values()">
#{x}
</foreach>
)
... is translated into the SQL below and the enum values DOCUMENT and IMAGE are bound to the placeholders as string respectively.
RTRIM(IF_FILETYPE) IN (?, ?)

Related

How to properly use the #Param annotation of Mybatis

I didn't use #Param annotation at first, this is my mapper.java
public void changeUserAuth(Integer userId,int identity);
, and this is my mapper.xml
<update id="changeUserAuth">
update user
<set>
<if test="identity != 0">identity = #{identity}</if>
</set>
<where>
<if test="userId != 0">userId = #{userId}</if>
</where>
</update>
then it works correctly!I continue to write like this, as follows:
//this's mapper.java
public void updateUserStatus(Integer userId);
<!--this is mapper.xml>
<update id="changeUserAuth">
update user
set deleteFlag= true
<where>
<if test="userId != 0">userId = #{userId}</if>
</where>
</update>
however,it gave an error,the message is
There is no getter for property named 'userId' in 'class.java.lang.Integer'
I can understand that mybatis cannot parse the Integer, but why it is not an error like my first use, just because I have an int type Parameter? In the second method, I have to use #Param annotation
Here is how you reference parameters in MyBatis statements.
I'll use this POJO in the following explanation.
public class User {
private Integer id;
private String name;
//...
}
When #Param is used
If you add #Param annotation on a parameter, you can use the specified name to reference the parameter. This is the simplest case.
A few examples:
List<USer> select(#Param("id") Integer userId, #Param("name") String userName);
void insert(#Param("record") User user);
<select id="select" resultType="User">
select * from users
<where>
<if test="id != null">and id = #{id}</if>
<if test="name != null">and name = #{name}</if>
</where>
</select>
<insert id="insert">
insert into users (id, name) values
(#{record.id}, #{record.name})
</insert>
Without #Param
If there is no #Param, it depends on several conditions.
When the mapper method takes only one parameter [1] and ...
... the sole parameter is assignable to java.util.List, you can reference the parameter as list.
List<User> selectByIds(List<Integer> ids);
<select id="select" resultType="User">
select * from users
where id in (
<foreach item="x" collection="list" separator=",">
#{x}
</foreach>
)
</select>
... the sole parameter is assignable to java.util.Collection, you can reference the parameter as collection.
List<User> selectByIds(Set<Integer> ids);
<select id="select" resultType="User">
select * from users
where id in (
<foreach item="x" collection="collection" separator=",">
#{x}
</foreach>
)
</select>
... there is a type handler mapped to the sole parameter (i.e. the parameter is String, Integer, etc.).
With MyBatis 3.5.2 and later, you can reference the parameter using any name (you should use sensible names for obvious reasons, though). e.g.
List<User> select(Integer id);
<select id="select" resultType="User">
select * from users
<where>
<if test="x != null">and id = #{y}</if>
</where>
</select>
With MyBatis 3.5.1
you can reference the parameter with any name in #{}.
you must reference the parameter as _parameter in ${}, test attribute of <if /> and <when /> and value attribute of <bind />. This is why your second example throws exception.
List<User> select(Integer id);
<select id="select" resultType="User">
select * from users
<where>
<if test="_parameter != null">and id = #{z}</if>
</where>
</select>
... there is no type handler mapped to the sole parameter (i.e. the parameter is POJO or Map<String, ?>), you can reference the parameter properties directly with their names (or the keys if the parameter is a Map).
void insert(User user);
<insert id="insert">
insert into users (id, name) values
(#{id}, #{name})
</insert>
When the mapper method takes multiple parameters
If the project is compiled with '-parameters' compiler option, you can reference the parameters using their names declared in the method signature. This is your first example.
List<USer> select(Integer userId, String userName);
<select id="select" resultType="User">
select * from users
<where>
<if test="userId != null">and id = #{userId}</if>
<if test="userName != null">and name = #{userName}</if>
</where>
</select>
Otherwise, you can reference the parameters using the names implicitly assigned by MyBatis i.e. arg0, arg1, ... (I would not recommend this as it's fragile and error-prone).
List<USer> select(Integer userId, String userName);
<select id="select" resultType="User">
select * from users
<where>
<if test="arg0 != null">and id = #{arg0}</if>
<if test="arg1 != null">and name = #{arg1}</if>
</where>
</select>
[1] RowBounds and ResultHandler does not count.

How to iterate List in MyBatis

iBatis to MyBatis Migration:
Need Help for MyBatis foreach logic, because the Map contains Value as ArrayList.
The below java code is the logic:
employeeRequest.put("ID", employeeId);
Map <String,ArrayList<String> employeeRequest = new HashMap<String, ArrayList<String>>();
Set<String> employeeSet = new HashSet<String>();
for(Employee employee: employeeList) {
String name = employee.getName();
String value = employee.getValue();
if("EMPLOYEE".equalsIgnoreCase(name) {
employeeSet.add(value)
}
}
if(!employeeSet.isEmpty()) {
employeeRequest.put("EMPLOYEE", new ArrayList<String>(employeeSet))
}
iBatis SQL:
My Previous code I am using iBatis which has the following query
<select id="getEmployeeName" resultclass="java.util.HashMap" parameterClass="java.util.Map">
SELECT EMP.EMPNAM NAME FROM EMPLOYEE EMP
WHERE EMP.ID = #ID#
<isNotEmpty property="EMPLOYEE" prepend="AND">
<iterate property="EMPLOYEE" conjunction="AND">
EMP.EMPNAM != #EMPLOYEE[]#
<iterate>
</isNotEmpty>
</select>
MyBatis SQL:
Now I am migrating to MyBatis, so formatted the query as below
<select id="getEmployeeName" resultclass="java.util.HashMap" parameterClass="java.util.Map">
SELECT EMP.EMPNAM NAME FROM EMPLOYEE EMP
WHERE EMP.ID = #{ID}#
<if test="EMPLOYEE !=null and EMPLOYEE>0">
<foreach collection="EMPLOYEE" index="index" item="item" separator="AND">
EMP.EMP_ID != ${item}
</foreach>
</if>
</select>
Could any one of you please help me with the correct query for the above java code logic.
Missing spaces around separator value: AND instead of just AND.
For parameters use #{param} to bind parameter instead of ${param} that just concatenates values to the SQL string. That does not prevent from working but that is very bad.
!= is not standard SQL and will not work for every DB vendor (although it might do with the one you are using), unlike NOT column = value,
<foreach collection="EMPLOYEE" index="index" item="item" separator=" AND ">
NOT EMP.EMP_ID = #{item}
</foreach>
furthermore better use IN:
EMP.EMP_ID NOT IN (<foreach collection="EMPLOYEE" index="index" item="item" separator=", ">
#{item}
</foreach>)

how to parameterize tablename in mybatis

I want to parameterize table_name:t_user_address_book(uid/500000).
for example: when uid = 1000, table_name = t_user_address_book0;
when uid = 500001, table_name = t_user_address_book1;
How to write?
public interface UserAddressBookMapper {
#Insert("insert into t_user_address_book? values(...)")
int upsert(Long uid, UserAddressBookMsg userAddressBookMsg);
}
You can choose the table with Mybatis XML code:
<choose>
<when test="uid gt 1000000">
<bind name="tableName" value="t_user_address_book2" />
</when>
<when test="uid gt 500000">
<bind name="tableName" value="t_user_address_book1" />
</when>
<otherwise>
<bind name="tableName" value="t_user_address_book0" />
</otherwise>
</choose>
Or you can compute the table name in the java and pass it as parameter.
Whatever your choice, the table name parameter in the query must be referenced with the $ notation instead of # since the value must replace the place holder as is to be part of the query and not being interpreted/bound/escaped as parameters are:
INSERT INTO ${tableName} ...
Despite use of XML, you can stick with annotations surrounding the query with <script> tags:
#Insert({"<script>",
"<choose> ...",
"INSERT ..."
"</script>"
})
Also when using Mapper interface with annotations, you need to name the parameters with there are more than 1:
#Insert("INSERT INTO table VALUES(#{uid}, #{userAddressBookMsg.propertyName1})")
int upsert(upsert(#Param("uid")Long uid, #Param("userAddressBookMsg") UserAddressBookMsg userAddressBookMsg);
However, it seems you want to split into multiple tables for volume issues, this is much complexity to handle while it would better be to keep a single table and look around on DB side about indexing and partitioning.
Quick response will be "no". It is not possible to give table name as parameter, because mybatis uses prepared statements.
I would suggest using table name as variable, and giving it to the statement string.
For example:
public interface UserAddressBookMapper {
static String tableName;
static void setTableName(String name) {
tableName = name;
}
#Insert({"insert into", tableName, "values(...)"})
int upsert(UserAddressBookMsg userAddressBookMsg);
You will have to set tableName before calling the method.

MyBatis Performance of bulk operator

I have used MyBatis-spring + Java. I need to insert >10000 records into table in one transaction. To do it, I have used a mapper:
<insert id="saveBulk" parameterType="List">
INSERT INTO "quote" ("id", "mi_id", "timestamp", "open", "close", "low", "high", "volume", "period")
VALUES
<foreach collection="list" item="item" separator=",">
( #{item.key}, #{item.marketInstrumentKey}, #{item.timestamp}, #{item.open}, #{item.close}, #{item.low},
#{item.high}, #{item.volume}, #{item.period}::quote_period)
</foreach>
</insert>
And pass a List to this statement. It is worked extremely slowly for 2000-3000 records, but the 10000 records are inserted more than 4 minutes (I should increase the timeout interval)! The same 10000 records are inserted to the same DB via PgAdmin for less than 10 second. I tried to trace the processing of this operation and found a bottleneck
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.update(stmt);
} finally {
closeStatement(stmt);
}
}
The StatementHandler is calculated few minutes, and few minutes for prepareStatement.
I understand, why it happens: 10000 records with 9 field for each record. All these 100k fields should be inserted as parameters into statement.
And how can I accelerate this process?
UPDATE:
I implements a batch save with using of "BATCH" mode of sqlFactory and #Transactional.
This is configuration of mybatis-spring XML config:
<bean id="sqlBatchTemplate" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="sqlSessionFactory"/>
<constructor-arg index="1" value="BATCH"/>
</bean>
<bean id="quoteBatchMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="mapperInterface" value="tafm.dataaccess.mybatis.mapper.QuoteMapper"/>
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
<property name="sqlSessionTemplate" ref="sqlBatchTemplate"/>
</bean>
<bean id="dataAccessBatch" class="tafm.dataaccess.DataAccess">
<property name="quoteMapper" ref="quoteBatchMapper"/>
</bean>
Then, I have implemented a "batch" method:
#Transactional
public void saveBulk(List<Quote> quotes) {
for(Quote q:quotes) {
mapper.save(q);
}
}
mapper - is XML mapper for entity Quote:
<insert id="saveBulk" parameterType="List">
INSERT INTO "quote" ("id", "mi_id", "timestamp", "open", "close", "low", "high", "volume", "period")
VALUES
<foreach collection="list" item="item" index="index" separator=",">
( #{item.key}, #{item.marketInstrumentKey}, #{item.timestamp}, #{item.open}, #{item.close}, #{item.low},
#{item.high}, #{item.volume}, #{item.period}::quote_period)
</foreach>
</insert>
It is working fast
you can do something like this.
Mapper.xml
<insert id="saveBulk" parameterType="List">
INSERT INTO "quote" ("id", "mi_id", "timestamp", "open", "close", "low", "high", "volume", "period")
VALUES
<foreach collection="list" item="item" separator=",">
( #{item.key}, #{item.marketInstrumentKey}, #{item.timestamp}, #{item.open}, #{item.close}, #{item.low},
#{item.high}, #{item.volume}, #{item.period}::quote_period)
</foreach>
</insert>
In Java File
public void insertBulkData(List<Item> list)
{
int total = list.size();
int interval = 1000;
int from = 0;
int to = 0;
while (to <= total)
{
from = to == 0 ? 0 : to;
to = (to + interval) <= total ? (to + interval) : total;
saveBulk(list.subList(from, to));
if (to == total)
break;
}
}
above code using interval at 1000.so 1000 records will insert at a time.you can modify this interval also.
you have to call insertBulkData(list).
I hope this will helpful to you.

Skip items in Mybatis3 mapper foreach loop

I have a mapper for creating a table from a object.
The thing is I can't figure out how to skip an item depending on it's "pos" property
<update id="createTable">
CREATE TABLE ${param1}_${param2}_${param3.id}
<foreach collection="param3.field" item="field" separator="," open="(" close=")">
<!-- Skip logic here..-->
${field.name} varchar(${field.size})
</foreach>
</update>
I've tried some different stuff like:
...
<if test="${field.pos != 1}">
${field.name} varchar(${field.size})
</if>
...
But with no luck.
According to docs, in <if> tags' conditions ${} notation shouldn't be used.
Try <if test="field.pos != 1"> instead of <if test="${field.pos != 1}">.