Mybatis 中的动态 SQL
# 150.Mybatis 中的动态 SQL
Mybatis 的映射文件中,前面我们的 SQL 都是比较简单的,有些时候业务逻辑复杂时,我们的 SQL 是动态变化的,此时在前面的学习中我们的 SQL 就不能满足要求了。
# 什么是动态 SQL
MyBatis 官网说明 | 动态 SQL (opens new window):动态 SQL 是 MyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦。
使用动态 SQL 并非一件易事,但借助可用于任何 SQL 映射语句中的强大的动态 SQL 语言,MyBatis 显著地提升了这一特性的易用性。
如果你之前用过 JSTL 或任何基于类 XML 语言的文本处理器,你对动态 SQL 元素可能会感觉似曾相识。在 MyBatis 之前的版本中,需要花时间了解大量的元素。借助功能强大的基于 OGNL 的表达式,MyBatis 3 替换了之前的大部分元素,大大精简了元素种类,现在要学习的元素种类比原来的一半还要少。
举个例子,我们查询的时候,可以允许用户选择多个条件进行查询,也可以不选。例如某购物网站上可以根据订单类型、成交时间等条件,查询订单的数据:
那么在查询的时候,有些参数是有值的,有些又是没有的。此时我们就可以用到动态 SQL,它提供了很多标签,例如 <if>
,当有参数则传入,没有则不传入。
# 实践 if
我们在 IUserDao.java,接口里加一个方法:
List<User> findUserByCondition(User user);
user 作为查询的条件,该对象里有可能有用户名,有可能有性别,有可能有地址,有可能都有,也可能都没有
燃煤我们在 IUserDao.xml 里,配置我们的 SQL 了:
<!-- 根据可有可无的多个参数进行查询-->
<select id="findUserByCondition" resultType="USER" resultMap="userMap">
select * from user where 1=1
<if test="userName != null">
and username = #{userName}
</if>
</select>
2
3
4
5
6
7
注意 if 标签里,不能写 && 符号,这是 Java 语言提供的运算符;而我们现在是写 SQL,SQL 里有 and 这个关键字,但是没有&&。
此外,test 属性里的内容,以及 #{}
里的内容,是 Java 代码里的内容,因此区分大小写,得和 User 类中的 userName 成员变量对应起来。
我们写个测试方法:
@Test
public void testFindUserByCondition(){
User user = new User();
user.setUserName("王五");
List<User> users = userDao.findUserByCondition(user);
for(User u : users){
System.out.println(u);
}
}
2
3
4
5
6
7
8
9
10
运行结果:
Preparing: select * from user where 1=1 and username = ?
- ==> Parameters: 王五(String)
- <== Total: 1
User{userId=43, userName='王五', userBirthday=Sun Mar 04 11:34:34 CST 2018, userSex='女', userAddress='北京'}
2
3
4
# 多个 if
也可以使用多个条件:
<!-- 根据可有可无的多个参数进行查询-->
<select id="findUserByCondition" resultType="USER" resultMap="userMap">
select * from user where 1=1
<if test="userName != null">
and username = #{userName}
</if>
<if test="userSex != null">
and sex = #{userSex}
</if>
</select>
2
3
4
5
6
7
8
9
10
此时我们先不修改测试类,试下运行:
Preparing: select * from user where 1=1 and username = ?
- ==> Parameters: 王五(String)
- <== Total: 1
User{userId=43, userName='王五', userBirthday=Sun Mar 04 11:34:34 CST 2018, userSex='女', userAddress='北京'}
2
3
4
由于我们的 userSex 是 null,因此查询的 SQL 里也不会有该条件
而如果我们设置了性别:
@Test
public void testFindUserByCondition(){
User user = new User();
user.setUserName("王五");
user.setUserSex("女");
List<User> users = userDao.findUserByCondition(user);
for(User u : users){
System.out.println(u);
}
}
2
3
4
5
6
7
8
9
10
那么运行结果里,就可以看到,SQL 里也加上了性别:
- ==> Preparing: select * from user where 1=1 and username = ? and sex = ?
- ==> Parameters: 王五(String), 女(String)
- <== Total: 1
User{userId=43, userName='王五', userBirthday=Sun Mar 04 11:34:34 CST 2018, userSex='女', userAddress='北京'}
2
3
4
# where 标签
刚刚我们使用了 where 1 = 1 的条件,看起来不够简洁明了;而如果使用 where 标签,可以更简化配置:
<select id="findUserByCondition" resultType="USER" resultMap="userMap">
select * from user
<where>
<if test="userName != null">
and username = #{userName}
</if>
<if test="userSex != null">
and sex = #{userSex}
</if>
</where>
</select>
2
3
4
5
6
7
8
9
10
11
我们重新运行测试方法,可以看到运行结果没有变化。
# foreach 标签
使用 if,我们可以解决传入一个参数的情况;但有时候,我们需要用到传入多个参数,例如:
select * from user where id in (41, 42, 43);
那这种情况该怎么传参呢?
我们这里使用 QueryVo 为例,添加一个 List:
public class QueryVo {
private User user;
private List<Integer> ids;
}
2
3
4
请读者自行添加 getter 和 setter
我们在接口 IUserDao.java 里添加一个方法:
/**
* 根据QueryVo提供的id集合,查询用户信息
* @param vo
* @return
*/
List<User> findUserInIds(QueryVo vo );
2
3
4
5
6
然后,我们就可以使用 Mybatis 提供的 foreach 标签了:该标签能遍历一个集合
<!-- 根据QueryVo中的id集合,实现查询用户列表 -->
<select id="findUserInIds" resultType="USER" resultMap="userMap" parameterType="QueryVo">
select * from user
<where>
<if test="ids != null and ids.size() > 0">
<foreach collection="ids" open="and id in (" close=")" item="uid" separator=",">
#{uid}
</foreach>
</if>
</where>
</select>
2
3
4
5
6
7
8
9
10
11
collection 属性表示这是一个集合,open 表示开始,close 表示结束,items 就是要填充的内容,分隔符是逗号
然后我们写一个测试方法:
@Test
public void testFindUserInIds(){
List<Integer> list = new ArrayList<>();
list.add(41);
list.add(42);
list.add(43);
QueryVo vo = new QueryVo();
vo.setIds(list);
List<User> users = userDao.findUserInIds(vo);
for(User u : users){
System.out.println(u);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
运行结果:
- ==> Preparing: select * from user WHERE id in ( ? , ? , ? )
- ==> Parameters: 41(Integer), 42(Integer), 43(Integer)
- <== Total: 3
2
3
可以看到能正常查询
# sql 标签
纵观我们的 IUserDao.xml 文件,几乎每个 select 标签都是以 select * from user
开头的,此时我们可以抽取重复的部分:在 IUserDao.xml 下新建一个 sql 标签,内容写我们希望抽取的内容
<sql id="defaultUser">
select * from user
</sql>
2
3
注意:抽取了代码片段后,里面不能写分号,不然拼接会出错的
然后,我们就可以在 select 标签里引用了:
<!-- 配置查询所有用户,id要写方法名称-->
<select id="findAll" resultMap="userMap">
<include refid="defaultUser"/>
</select>
<!-- 根据QueryVo中的id集合,实现查询用户列表 -->
<select id="findUserInIds" resultType="USER" resultMap="userMap" parameterType="QueryVo">
<include refid="defaultUser"/>
<where>
<if test="ids != null and ids.size() > 0">
<foreach collection="ids" open="and id in (" close=")" item="uid" separator=",">
#{uid}
</foreach>
</if>
</where>
</select>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
该标签了解即可,用的较少。
# 源码
本文所有代码已上传到了 GitHub (opens new window) 和 Gitee (opens new window) 上,并且创建了分支 demo13,读者可以通过切换分支来查看本文的示例代码。