这是Mybatis由浅入深的第3节,前两节传送门:
【Mybatis由浅入深 -01入门】
【Mybatis由浅入深 - 02增删改查CRUD】
前言
动态SQL是Mybatis的亮点功能之一,解决了根据不同条件拼接SQL的繁琐痛苦问题,避免了java代码和sql混在一起,是非常常用的功能,也是本人非常喜欢的功能,点赞!
通过本文您将掌握xml映射文件的常用元素:
- if
- choose (when, otherwise)
- trim(where, set)
- foreach
使用环境
以下为基础前置条件:请参考其它博文自行安装。
- JDK:1.8
- Maven: 3.6.3
- Mysql: 5.7 安装教程
创建Mysql测试表
CREATE TABLE `user` (
`id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,
`name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`age` tinyint(1) UNSIGNED NOT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
INSERT INTO `user`(`id`, `name`, `age`) VALUES (1, '天罡gg', 18);
INSERT INTO `user`(`id`, `name`, `age`) VALUES (2, '天罡666', 28);
INSERT INTO `user`(`id`, `name`, `age`) VALUES (3, '666天罡', 38);
INSERT INTO `user`(`id`, `name`, `age`) VALUES (4, '天罡666', 48);
1、if
和java代码的if非常类似, 如果if test成立时执行if标签内的sql,用于各种条件判断:像拼接字段名、拼接where条件等等,最常见情景是根据条件包含 where 子句的一部分
// 查询user,如果name不为空则按name查询(全模糊)
List<User> selectListIfName(String name);
<select id="selectListIfName" resultType="com.tiangang.dao.po.User">
select * from user
<if test="name != null and name != ''">
where name like concat ('%', #{name},'%')
</if>
</select>
当传入的name=null时,最终执行的sql为:
select * from user
当传入的name="666"时,最终执行的sql为:
select * from user where name like '%666%'
单元测试如下:
@Test
public void selectListIfNameTest() {
MyBatisManager.executeMapperMethod(true, UserDynamicSqlMapper.class, (mapper -> {
List<User> userList = mapper.selectListIfName("666");
System.out.println("1.查询name like '%666%':\n" + userList);
userList = mapper.selectListIfName(null);
System.out.println("2.查询全部:\n" + userList);
}));
}
执行结果如下图:
2、choose (when, otherwise)
类似java的swicth case。choose标签下,任意when的test成立时执行when标签内的sql,choose结束; 当都不成立时,执行otherwise标签内的sql。
// 查询user,如果name不为空则按name查询(通过symbol选择是)
List<User> selectListChooseName(@Param("name") String name, @Param("symbol") Integer symbol);
<select id="selectListChooseName" resultType="com.tiangang.dao.po.User">
select * from user
<if test="name != null and name != ''">
where
<choose>
<when test="symbol == null">
name = #{name}
</when>
<when test="symbol==1">
name like concat ('%', #{name})
</when>
<when test="symbol==2">
name like concat (#{name},'%')
</when>
<otherwise>
name like concat ('%', #{name},'%')
</otherwise>
</choose>
</if>
</select>
单元测试如下:
@Test
public void selectListChooseNameTest() {
MyBatisManager.executeMapperMethod(true, UserDynamicSqlMapper.class, (mapper -> {
List<User> userList = mapper.selectListChooseName("天罡", 1);
System.out.println("1.查询name like '%天罡': \n" + userList);
userList = mapper.selectListChooseName("天罡", 2);
System.out.println("2.查询name like '天罡%': \n" + userList);
userList = mapper.selectListChooseName("天罡", 3);
System.out.println("3.查询name like '%天罡%': \n" + userList);
userList = mapper.selectListChooseName("天罡666", null);
System.out.println("4.查询name='天罡666': \n" + userList);
userList = mapper.selectListChooseName("", null);
System.out.println("5.查询全部: \n" + userList);
}));
}
执行结果如下图:
3、trim(where, set)
前面几个例子已经让动态sql变的方便了,但是还不够!现在回到之前的 “if” 示例,这次我们同时name和age两个条件,看看会发生什么。
<select id="selectListWhereNameAgeError" resultType="com.tiangang.dao.po.User">
select * from user
where
<if test="name != null and name != ''">
name like concat ('%', #{name},'%')
</if>
<if test="gleAge!= null">
and age >= #{gleAge}
</if>
</select>
如果没有匹配的条件会怎么样?最终这条 SQL 会变成这样:
select * from user
where
这会导致查询失败。如果匹配的只是第二个条件又会怎样?这条 SQL 会是这样:
select * from user
where
and age >= 18
这个查询也会失败。所以这里mybatis提供了非常好用的where元素, 示例:
<select id="selectListWhereNameAge" resultType="com.tiangang.dao.po.User">
select * from user
<where>
<if test="name != null and name != ''">
and name like concat ('%', #{name},'%')
</if>
<if test="gleAge!= null">
and age >= #{gleAge}
</if>
</where>
</select>
完美解决了这两个问题:
1. 当子元素不返回时, 不会插入 “WHERE” 子句
2. 若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。
如果 where 元素与你期望的不太一样,你也可以通过自定义 trim 元素来定制 where 元素的功能。比如,和 where 元素等价的自定义 trim 元素为:
<trim prefix="WHERE" prefixOverrides="AND |OR ">
...
</trim>
prefixOverrides 属性会忽略通过管道符分隔的文本序列(注意此例中的空格是必要的)。上述例子会移除所有 prefixOverrides 属性中指定的内容,并且插入 prefix 属性中指定的内容。
另一个与where类似的元素是set,set 元素可以用于动态包含需要更新的列,忽略其它不更新的列。比如:
<update id="updateSelectiveById" parameterType="com.tiangang.dao.po.User">
update user
<set>
<if test="name != null"> name = #{name}, </if>
<if test="age != null"> age = #{age} </if>
</set>
where id = #{id}
</update>
这个例子中,set元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号。
或者,你可以通过使用trim元素来达到同样的效果:
<trim prefix="SET" suffixOverrides=",">
...
</trim>
4、foreach
动态 SQL 的另一个常见使用场景是对集合进行遍历(尤其是在构建 IN 条件语句的时候)。比如:
// 查询user,根据idList
List<User> selectByIdList(@Param("idList") List<Integer> idList);
<select id="selectByIdList" resultType="com.tiangang.dao.po.User">
select * from user
<if test="idList !=null and idList.size >0">
where id in
<foreach collection="idList" item="id" separator="," open="(" close=")" >
#{id}
</foreach>
</if>
</select>
foreach 元素的功能非常强大,它允许你指定一个集合,声明可以在元素体内使用的集合项(item)和索引(index)变量。它也允许你指定开头与结尾的字符串以及集合项迭代之间的分隔符。这个元素也不会错误地添加多余的分隔符,看它多智能!
你可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象作为集合参数传递给 foreach。当使用可迭代对象或者数组时,index 是当前迭代的序号,item 的值是本次迭代获取到的元素。当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。