How to properly use the #Param annotation of Mybatis - annotations

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.

Related

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

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 (?, ?)

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.

Composite keys in MyBatis <collection> mappings

I am unable to pass a composite key to a MyBatis <collection> element (using version 3.2.7). The MyBatis documentation states:
Note: To deal with composite keys, you can specify multiple column names to pass to the nested select statement by using the syntax column="{prop1=col1,prop2=col2}". This will cause prop1 and prop2 to be set against the parameter object for the target nested select statement.
However, all my attempts to implement this produce the Exception
org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.reflection.ReflectionException: Error instantiating class java.lang.Integer with invalid types () or values (). Cause: java.lang.NoSuchMethodException: java.lang.Integer.<init>()
The collection (which resides in another ResultsMap) is:
<collection property="foos" ofType="FooObject"
column="{param1=user_id,param2=foo_id}" select="getFoosByUser" >
<id property="userId" column="user_id" />
<id property="foo" column="foo_id" />
<result property="fooName" column="foo_name" />
</collection>
It should return an ArrayList of Foo objects. The composite key is user_id and foo_id. The select query is:
<select id="getFoosByUser" parameterType="Integer" resultType="FooObject">
SELECT
user_id AS userId,
foo_id AS fooId,
foo_name AS fooName
FROM foo_table
WHERE user_id = #{param1}
AND foo_id = #{param2}
</select>
The query works correctly if I only use one parameter, e.g. removed foo_id=#{param2} and then use column=user_id in the collection, but I cannot work out how to structure the column attribute correctly for two keys. Any ideas?
MyBatis is confused by using parameterType when there are more than one parameter. Modify you query mapping like this:
<select id="getFoosByUser" resultType="FooObject">
SELECT
user_id AS userId,
foo_id AS fooId,
foo_name AS fooName
FROM foo_table
WHERE user_id = #{param1}
AND foo_id = #{param2}
</select>

How to make the myBatis select result(list) be set to object's property?

Generally, myBatis's select method returns single object or generic List types. for example, I want to fetch all students of a class:
<select id="fetchStudentsOfClass" parameterType="int" resultMap="resultMapStudent">
SELECT * FROM students WHERE class_id=#{id}
</select>
I can easily get a result like this: List<Student>.
Now, if I want to get the result like this rather than List<Student>:
class MyClass
{
List<Student> getStudents{return this.students;}
void setStudents(List<Student> students){this.students = students}
private List<Student> students;
}
how can I do?
This is what the <collection/> element is for. It's important that you properly mark both the container and the values with their <id/> so that MyBatis knows how to deal with collapsing multiple rows into one object.
<resultMap id="resultMapClass" type="some.package.MyClass" autoMapping="true">
<id property="classId" column="class_id" javaType="integer"/>
<collection property="students" ofType="some.package.Student" autoMapping="true">
<id property="studentId" column="student_id" javaType="integer"/>
</collection>
</resultMap>
<select id="fetchStudentsOfClass" parameterType="int" resultMap="resultMapClass">
SELECT *
FROM students
WHERE class_id = #{id}
</select>
See http://mybatis.github.io/mybatis-3/sqlmap-xml.html#Result_Maps for more details.