《Mybatis从入门到精通》读书笔记(二)
第三章. Mybatis 注解方式的基本用法
表字段和Java属性字段映射的方式:
1. SQL语句中列取别名
2. 设置mapUnderscoreToCamelCase = true(下划线转驼峰)
3. resultMap映射
注解方式应用场景不多,不做过多介绍,具体可以参考原书或mybatis官方文档。
第四章. Mybatis 动态SQL
以下是Mybatis动态SQL在XML中支持的几种标签:
- if
- choose(when、otherwise)
- trim(where、set)
- foreach
- bind
4.1. if
判断条件property != null或property == null:适用于任何类型的字段,用于判断属性值是否为空。
判断条件property != ' '或property == ' ':仅适用于String类型的字段,用于判断属性值是否为空字符串。
// 在OGNL表达式中,这两个判断的顺序不会影响判断的结果,也不会有空指针异常
<if test="userName != null and userName != ''">
4.2. choose
用来实现 if .. .else if ... else ... 的逻辑
<select id="selectByIdOrUserName" resultType="tk.mybatis.simple.model.SysUser">
select id,
user_name userName,
user_password userPassword,
user_email userEmail,
from sys_user
where 1 = 1
<choose>
<when test="id != null">
and id = #{id}
</when>
<when test="userName != null and userName != ''">
and user_name = #{userName}
</when>
<otherwise>
limit 0
</otherwise>
</choose>
</select>
4.3. where、set、trim
where标签的作用:如果该标签包含的元素中有返回值,就插入一个where;如果where后面的字符串是以AND或OR开头的,就将它们剔除。
<select id="selectByUser" resultType="tk.mybatis.simple.model.SysUser">
select
id,
user_name userName,
user_password userPassword,
user_email userEmail,
from sys_user
<where>
<if test="userName != '' and userName != null">
and user_name like concat('%', #{userName}, '%')
</if>
<if test="userEmail != '' and userEmail != null">
and user_email = #{userEmail}
</if>
</where>
</select>
set标签的作用:如果该标签包含的元素中有返回值,就插入一个set;如果set后面的字符串是以逗号结尾的,就将这个逗号剔除。
<update id="updateByIdSelective">
update sys_user
<set>
<if test="userName != null and userName != ''">
user_name = #{userName},
</if>
<if test="userPassword != null and userPassword != ''">
user_password = #{userPassword},
</if>
<if test="userEmail != null and userEmail != ''">
user_email = #{userEmail},
</if>
<if test="userInfo != null and userInfo != ''">
user_info = #{userInfo},
</if>
<if test="headImg != null">
head_img = #{headImg, jdbcType=BLOB},
</if>
<if test="createTime != null">
create_time = #{createTime, jdbcType=TIMESTAMP},
</if>
id = #{id},
</set>
where id = #{id}
</update>
where和set标签的功能都可以用trim标签来实现,并且在底层就是通过TrimSqlNode实现的。
where标签对应trim的实现如下:
<trim prefix="WHERE" prefixOverrides="AND |OR ">
...
</trim>
这里的AND和OR后面的空格不能省略,为了避免匹配到andes、orders等单词。
实际的prefixOverrides包含“AND”、“OR”、“AND\n”、“OR\n”、“AND\r”、“OR\r”、“AND\t”、“OR\t”,不仅仅是上面提到的两个带空格的前缀。
set标签对应trim的实现如下:
<trim prefix="SET" suffixOverrides=",">
...
</trim>
trim标签有如下属性:
prefix:当trim元素内包含内容时,会给内容增加prefix指定的前缀。 prefixOverrides:当trim元素内包含内容时,会把内容中匹配的前缀字符串去掉。 suffix:当trim元素内包含内容时,会给内容增加suffix指定的后缀。 suffixOverrides:当trim元素内包含内容时,会把内容中匹配的后缀字符串去掉。
4.4. foreach
SQL语句中有时会使用IN关键字,例如id in (1, 2, 3)。可以使用${id}方式直接获取值,但这种写法不能防止SQL注入,想避免SQL注入就需要用#{}的写法,这时就要配合foreach标签来满足需求。
foreach可以对数组、Map或实现了Iterable接口(如List、Set)的对象进行遍历。数组在处理时会转换为List对象,因此foreach遍历的对象可以分为两大类:Iterable类型和Map类型。
4.4.1. foreach实现in集合
List<SysUser> selectByIdList(List<Long> idList);
<select id="selectByIdList" resultType="tk.mybatis.simple.model.SysUser">
select id,
user_name userName,
user_password userPassword,
user_email userEmail,
user_info userInfo,
head_img headImg,
create_time createTime
from sys_user
where id in
<foreach collection="list" open="(" close=")" separator="," item="id" index="i">
#{id}
</foreach>
</select>
foreach包含以下属性:
collection:必填,值为要迭代循环的属性名。这个属性值的情况有很多。 index:索引的属性名,在集合、数组情况下值为当前索引值,当迭代循环的对象是Map类型时,这个值为Map的key(键值) item:变量名,值为从迭代对象中取出的每一个值。 open:整个循环内容开头的字符串。 close:整个循环内容结尾的字符串。 separator:每次循环的分隔符。
collection的属性设置分为以下几种情况:
1. 只有一个数组参数或集合参数
以下代码是DefaultSQLSession中的方法,也是默认情况下的处理逻辑。
private Object wrapCollection(Object object) {
DefaultSqlSession.StrictMap map;
if (object instanceof Collection) {
map = new DefaultSqlSession.StrictMap();
map.put("collection", object);
if (object instanceof List) {
map.put("list", object);
}
return map;
} else if (object != null && object.getClass().isArray()) {
map = new DefaultSqlSession.StrictMap();
map.put("array", object);
return map;
} else {
return object;
}
}
总结下来就是:
当传入参数为集合collection的子类(Set、List):collection = "collection"
当传入参数为List:collection = "collection" 或 collection = "list"
当传入参数为Array:collection = "array"
这里是传入数组或集合类型参数的默认名字。推荐使用@Param来指定参数的名字,这时collection就设置为通过@Param注解指定的名字。
2. 有多个参数
当有多个参数的时候,要使用@Param注解给每个参数指定一个名字,否则在SQL中使用参数时就会不方便,因此将collection设置为@Param注解指定的名字即可。
3. 参数是Map类型
使用Map和使用@Param注解方式类似,将collection指定为对应Map中的key即可。
如果要循环所传入的Map,推荐使用@Param注解指定名字,此时可将collection设置为指定的名字,如果不想指定名字,就使用默认值_parameter。
4. 参数是一个对象
这种情况下指定为对象的属性名即可。当使用对象内多层嵌套的对象时,使用属性.属性(集合和数组使用下标取值)的方式可以指定深层的属性值。
4.4.2. foreach实现批量插入
如果数据库支持批量插入,就可以通过foreach来实现。批量插入是SQL-92新增的特性,目前支持的数据库有DB2、SQL Server 2008及以上版本、PostgreSQL 8.2及以上版本、MySQL、SQLite 3.7.11及以上版本、H2。批量插入的语法如下:
INSERT INTO tablename (column-a, [column-b, ...])
VALUES ('values-1a',['value-1b',... ]),
('values-2a',['value-2b',... ]),
...
<insert id="insertList">
insert into sys_user(
user_name, user_password,user_email,
user_info, head_img, create_time)
values
<foreach collection="list" item="user" separator=",">
(
#{user.userName}, #{user.userPassword},#{user.userEmail},
#{user.userInfo}, #{user.headImg, jdbcType=BLOB}, #{user.createTime, jdbcType=TIMESTAMP})
</foreach>
</insert>
从Mybatis 3.3.1版本开始,Mybatis开始支持批量新增回写主键值的功能,到目前为止,可以完美支持该功能的仅有MySQL数据库。要在MySQL中实现批量插入返回自增主键值,只需要在原来基础上进行如下修改:
<insert id="insertList" useGeneratedKeys="true" keyProperty="id">
4.4.3. foreach实现动态UPDATE
<update id="updateByMap">
update sys_user
set
<foreach collection="_parameter" item="val" index="key" separator=",">
${key} = #{val}
</foreach>
where id = #{id}
</update>
@Test
public void testUpdateByMap(){
SqlSession sqlSession = getSqlSession();
try {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
//从数据库查询 1 个 user 对象
Map<String, Object> map = new HashMap<String, Object>();
map.put("id", 1L);
map.put("user_email", "test@mybatis.tk");
map.put("user_password", "12345678");
//更新数据
userMapper.updateByMap(map);
//根据当前 id 查询修改后的数据
SysUser user = userMapper.selectById(1L);
Assert.assertEquals("test@mybatis.tk", user.getUserEmail());
} finally {
//为了不影响数据库中的数据导致其他测试失败,这里选择回滚
sqlSession.rollback();
//不要忘记关闭 sqlSession
sqlSession.close();
}
}
4.4.4. OGNL用法
- e1 or e2
- e1 and e2
- e1 == e2,e1 eq e2
- e1 != e2,e1 neq e2
- e1 lt e2:小于
- e1 lte e2:小于等于,其他gt(大于),gte(大于等于)
- e1 in e2
- e1 not in e2
- e1 + e2,e1 * e2,e1/e2,e1 - e2,e1%e2
- !e,not e:非,求反
- e.method(args)调用对象方法
- e.property对象属性值
- e1[ e2 ]按索引取值,List,数组和Map
- @class@method(args)调用类的静态方法
- @class@field调用类的静态字段值
表达式1~4是最常用的4中情况。另外有时候需要判断一个集合是否为空时,可能会出现如下判断。
<if test="list != null and list.size() > 0">
<!--其他-->
</if>
在这种用法中,list.size()是调用对象的方法,> 0是和数字进行比较。
表达式10、11两种情况也特别常见,而且可以多层嵌套使用。假设User类型的属性user中有一个Address类型的属性名为addr,在Address中还有一个属性zipcode,可以通过user.addr.zipcode直接使用zipcode的值。假设Map类型的属性为map,我们可以通过map['userName']或map.userName来获取map中的key为userName的值,这里一定要注意,不管userName的值是不是null,必须保证userName这个key存在,否则就会报错。
表达式12通常用于简化一些校验,或者进行更特殊的校验,例如if中常见的判断可以写成如下这样。
<if test="@tk.mybatis.util.StringUtil@isNotEmpty(userName)">
and user_name like concat('%', #{userName}, '%')
</if>
假设只是在测试的时候想知道映射XML中方法执行的参数,可以在XML方法标签中添加如下方法:
<select id="selectByUser" resultType="tk.mybatis.simple.model.SysUser">
<bind name="print" value="@tk.mybatis.util.StringUtil@print(_parameter)"/>
select id,
user_name userName,
user_password userPassword,
user_email userEmail,
user_info userInfo,
head_img headImg,
create_time createTime
from sys_user
<where>
<if test="@tk.mybatis.util.StringUtil@isNotEmpty(userName)">
and user_name like concat('%', #{userName}, '%')
</if>
<if test="userEmail != '' and userEmail != null">
and user_email = #{userEmail}
</if>
</where>
</select>
其中StringUtil类如下:
package tk.mybatis.util;
public class StringUtil {
public static boolean isEmpty(String str){
return str == null || str.length() == 0;
}
public static boolean isNotEmpty(String str){
return !isEmpty(str);
}
public static void print(Object parameter){
System.out.println(parameter);
}
}