动态SQL
动态 SQL 是 MyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦。
- if
- choose (when, otherwise)
- trim (where, set)
- foreach
搭建环境
创建新表
1
2
3
4
5
6
7
8
9
10CREATE TABLE `mybatis`.`blog`
(
`id` VARCHAR(50) NOT NULL COMMENT '博客id',
`title` VARCHAR(100) NOT NULL COMMENT '博客标题',
`author` VARCHAR(30) NOT NULL COMMENT '博客作者',
`create_time` DATETIME NOT NULL COMMENT '创建时间',
`views` INT(30) NOT NULL COMMENT '浏览量'
) ENGINE = INNODB
CHARSET = utf8
COLLATE = utf8_general_ci;创建一个基础工程
编写实体类
1
2
3
4
5
6public class Blog {
private int id;
private String title;
private String author;
private Date createTime;
private int views;属性名和字段不一致的问题
一个是
createTime
, 一个是create_time
方法是使用官方文档中的设置选项:
往数据库中添加数据
1
2
3
4<insert id="addBlog" parameterType="blog">
insert into mybatis.blog (id, title, author, create_time, views)
values (#{id}, #{title}, #{author}, #{createTime}, #{views});
</insert>
IF
使用动态 SQL 最常见情景是根据条件包含 where 子句的一部分。比如:
1 | <select id="findActiveBlogWithTitleLike" |
这条语句提供了可选的查找文本功能。如果不传入 “title”,那么所有处于 “ACTIVE” 状态的 BLOG 都会返回;如果传入了 “title” 参数,那么就会对 “title” 一列进行模糊查找并返回对应的 BLOG 结果(细心的读者可能会发现,“title” 的参数值需要包含查找掩码或通配符字符)。
如果希望通过 “title” 和 “author” 两个参数进行可选搜索该怎么办呢?首先,我想先将语句名称修改成更名副其实的名称;接下来,只需要加入另一个条件即可。
1 | <select id="findActiveBlogLike" |
应用到我们的项目中就是:
1 | <select id="queryBlogIF" parameterType="map" resultType="blog"> |
测试:
1 |
|
输出为:
Mapper语句中的where 1=1
太牵强, 改换为where
标签, 如下:
1 | <select id="queryBlogIF" parameterType="map" resultType="blog"> |
查看日志内容可以看到这个标签自动把and
去掉了, 所以可以得到正确的查询结果:
choose、when、otherwise
有时候,我们不想使用所有的条件,而只是想从多个条件中选择一个使用。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。
还是上面的例子,但是策略变为:传入了 “title” 就按 “title” 查找,传入了 “author” 就按 “author” 查找的情形。若两者都没有传入,就返回标记为 featured 的 BLOG(这可能是管理员认为,与其返回大量的无意义随机 Blog,还不如返回一些由管理员挑选的 Blog)。
1 | <select id="findActiveBlogLike" |
套用在我们的项目中, 如下代码:
1 | <select id="queryBlogChoose" parameterType="map" resultType="blog"> |
后面的otherwise
根据具体的逻辑是可以不写的.
SET
用于动态更新语句的类似解决方案叫做 set。set 元素可以用于动态包含需要更新的列,忽略其它不更新的列。比如:
1 | <update id="updateAuthorIfNecessary"> |
这个例子中,set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的)。
因为update
语句修改多个字段时, where之前的字段不需要逗号, 而其他的字段都要以逗号结尾, 否则会出现格式错误
在我们的项目中测试:
1 | <update id="updateBlog" parameterType="map"> |
需要注意的是, set
标签只能做到删除逗号, 不能添加逗号, 所以写的时候不能省
trim
如果 where 元素与你期望的不太一样,你也可以通过自定义 trim 元素来定制 where 元素的功能。比如,和 where 元素等价的自定义 trim 元素为:
1 | <trim prefix="WHERE" prefixOverrides="AND |OR "> |
prefixOverrides 属性会忽略通过管道符分隔的文本序列(注意此例中的空格是必要的)。上述例子会移除所有 prefixOverrides 属性中指定的内容,并且插入 prefix 属性中指定的内容。
来看看与 set 元素等价的自定义 trim 元素吧:
1 | <trim prefix="SET" suffixOverrides=","> |
注意,我们覆盖了后缀值设置,并且自定义了前缀值。
SQL片段
有的时候, 我们可能会将一些功能的部分抽取出来, 方便复用
- 使用
标签抽取公共的部分 - 在使用的时候使用
标签引用即可
foreach
动态 SQL 的另一个常见使用场景是对集合进行遍历(尤其是在构建 IN 条件语句的时候)。比如:
1 | <select id="selectPostIn" resultType="domain.blog.Post"> |
foreach 元素的功能非常强大,它允许你指定一个集合,声明可以在元素体内使用的集合项(item)和索引(index)变量。它也允许你指定开头与结尾的字符串以及集合项迭代之间的分隔符。这个元素也不会错误地添加多余的分隔符
想办法拼出这样的SQL语句:
1 | select * from mybatis.blog where 1=1 and (id=1 or id=2 or id=3); |
Mapper映射:
1
2
3
4
5
6
7
8<select id="queryBlogForeach" parameterType="map" resultType="blog">
select * from mybatis.blog
<where>
<foreach collection="ids" item="id" open="and (" close=")" separator="or">
id=#{id}
</foreach>
</where>
</select>测试:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public void tes5(){
SqlSession sqlSession = MybtisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
HashMap map = new HashMap();
ArrayList<Integer> ids = new ArrayList<>();
ids.add(1);
map.put("ids", ids);
List<Blog> blogList = mapper.queryBlogForeach(map);
for (Blog blog : blogList) {
System.out.println(blog);
}
sqlSession.close();
}结果
1
2
3
4
5
6
7
8
9
10
11
12Opening JDBC Connection
Created connection 1806431167.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@6babf3bf]
==> Preparing: select * from mybatis.blog WHERE ( id=? )
==> Parameters: 1(Integer)
<== Columns: id, title, author, create_time, views
<== Row: 1, Spring教程, 刘潇博, 2020-07-24 09:58:00.0, 7412831
<== Total: 1
Blog{id=1, title='Spring教程', author='刘潇博', createTime=Fri Jul 24 09:58:00 CST 2020, views=7412831}
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@6babf3bf]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@6babf3bf]
Returned connection 1806431167 to pool.
总结
动态SQL就是在拼接SQL语句, 我们只要保证SQL的正确性, 按照SQL的格式, 去排列组合就可以了
建议:
- 先在Mysql中写出完整的SQL, 再对应地去修改称为我们的动态SQL实现