MyBatis:动态SQL

介绍

动态 SQL 是 MyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦。
简而言之,动态SQL简化了SQL语句拼接

环境

1
2
3
4
5
6
7
8
create table 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 '浏览量'
);

已经提前加入几条数据。

if

接口

1
2
3
4
public interface BlogMapper {
// 根据条件查询博客
List<Blog> queryBlogIF(Map map);
}

BlogMapper.xml

1
2
3
4
5
6
7
8
9
10
<select id="queryBlogIF" parameterType="map" resultType="Blog">
select * from blog
where 1=1
<if test="title!=null">
and title =#{title}
</if>
<if test="author!=null">
and author =#{author}
</if>
</select>

官方文档上写where state='ACTIVATE',但是在我的环境中会报错,用where 1=1替代是一样的效果,表示恒成立,即使if后面的语句不执行,也能查询出东西。
完整标签为<if test="判断条件"></if>

choose(when,otherwise)

有时候,我们不想使用所有的条件,而只是想从多个条件中选择一个使用。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。

接口

1
2
3
4
public interface BlogMapper {
// 根据条件查询博客
List<Blog> queryBlogChoose(Map map);
}

BlogMapper.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<select id="queryBlogChoose" parameterType="map" resultType="Blog">
select * from blog
<where>
<choose>
<when test="title!=null">
title=#{title}
</when>

<when test="author!=null">
and author=#{author}
</when>

<otherwise>
and views=#{views}
</otherwise>
</choose>
</where>
</select>

注意:此处使用\标签替代了上一个例子中的where 1=1语句,相比后者,前者更优雅地实现了智能拼接。在此例中,如果”title!=null”不成立且”author!=null”成立,在最后拼接时\标签可以识别出where and author=#{author}这样的错误语句并去掉and。

set

接口

1
2
3
public interface BlogMapper {
int updateBlog(Map map);
}

BlogMapper.xml

1
2
3
4
5
6
7
8
9
10
11
12
<update id="updateBlog" parameterType="map">
update blog
<set>
<if test="title!=null">
title = #{title},
</if>
<if test="author!=null">
author =#{author},
</if>
</set>
where id = #{id}
</update>

在使用update语句时容易出现的拼接问题是逗号后面紧跟一个where,例如此例中可能会出现update blog set author=#{author}, where id=#{id}的情况,因此\标签也和\标签一样可以智能地识别多余的逗号并去除。

SQL片段

我们可以用\标签把上述三个例子中都出现的SQL片段抽取出来

1
2
3
4
5
6
7
8
<sql id="if-title-author">
<if test="title!=null">
title = #{title}
</if>
<if test="author!=null">
and author =#{author}
</if>
</sql>

需要使用的时候:

1
<include refid="if-title-author"/>

即可实现代码的复用。
注意事项:

  • 最好基于单表来定义SQL片段
  • 不要存在where标签

foreach

动态 SQL 的另一个常见使用场景是对集合进行遍历(尤其是在构建 IN 条件语句的时候)。
假设现在需要查询id为1,2,3的三条数据,SQL语句为select * from blog where 1=1 and (id=1 or id=2 or id=3)

使用foreach标签

接口

1
2
3
public interface BlogMapper {
List<Blog> queryBlogForeach(Map map);
}

BlogMapper.xml

1
2
3
4
5
6
7
8
<select id="queryBlogForeach" parameterType="map" resultType="blog">
select * from blog
<where>
<foreach collection="ids" item="id" open="and (" separator="or" close=")">
id=#{id}
</foreach>
</where>
</select>

foreach标签中collection是集合,item是集合中的对象(很类似于java中的for(:)语句),open是开头,seperator是分隔符,close是结尾。以这样的方式来拼接SQL语句。
此例中最终拼接成的语句为:select * from blog where 1=1 and (id=1 or id=2 or id=3)

Test.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Test
public void test(){
SqlSession sqlSession = MybatisUtil.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);

Map map = new HashMap();
ArrayList<Integer> ids = new ArrayList<Integer>();
ids.add(1);
ids.add(2);
ids.add(3);
map.put("ids",ids);


List<Blog> blogs = mapper.queryBlogForeach(map);
for (Blog blog : blogs) {
System.out.println(blog);
}
sqlSession.close();
}