很长一段时间没更新博客了,真的是差点儿就放弃了(都怪自己太懒散了)!!!
继续我们的 MyBatis 之旅,上一篇讲到MyBatis的SQL映射,今天学习一下动态SQL,看看如何用MyBatis写出灵活的SQL。
参考文档MyBatis官方文档
if
大家在开发中是否碰到过这种情况:查询一个表时,页面中有多个查询条件,但是用户并不是必须选择查询条件的,每次选择查询条件的也不一定相同。比如,有一个User表的查询,页面中的查询条件为 用户名(username)、真是姓名(realname)、昵称(nickname),这个时候应该怎么去写SQL呢?总不能在 Java 代码中一行行地写 if (username != null)if (realname != null) 。。。这些判断吧,这样看着是不是有点儿蠢???
这时候 if 就有了用武之地,在我们的mapper.xml文件中可以这么写:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
...
<mapper namespace="org.gaipianer.mybatis.demo.UserMapper">
<select id="findUser" resultType="User">
SELECT * FROM user
WHERE 1 = 1
<if test="username != null">
AND username like #{username}
</if>
<if test="realname != null">
AND realname like #{realname}
</if>
<if test="nickname != null">
AND nickname like #{nickname}
</if>
</select>
</mapper>
这时不管用户选择什么样的查询条件,都被我们轻松地搞定了,MyBatis在创建SQL语句时就会帮我们执行 <if> 标签里的判断逻辑,如果username不为空,AND username like #{username}就会假如到我们的SQL语句中,如果realname为空,realname的查询条件就不会出现在SQL语句中。
大家可能会对上面的 SQL中 WHERE 1 = 1 这行比较疑惑,为什么要这么写呢?那是因为在我们举例的场景中,用户是可以不选择查询条件的,而我们的 <if> 条件语句中,我们并不确定用户到底选择了哪些查询条件,如果不在需要判断条件的SQL语句中加上 AND ,那么我们的SQL拼接出来就会是这样的:
SELECT * FROM user WHERE username like 'zhang' realname like ‘张三’
这明显是错误的,为了保证我们的拼接条件在有AND 前缀时也能保证正确性,所以在WHERE后面加了一个永远都成立的条件 WHERE 1 = 1,Mybatis提供了一个 <where>标签,可以帮助我们更优雅地解决这个问题,我们在文章的下面会讲到。
choose、when、otherwise
将上面的业务场景改为如果用户传入了 username、realname等条件,就按照传入的条件进行查询,如果用户什么都没有传,我们就添加另外一个查询条件,查询没有被逻辑删除的用户,即:delete = 0。
显然,这个时候 <if> 标签已经不能满足我们需求了,这时候轮到我们的 choose、when、otherwise闪亮登场了:
<select id="findUser">
SELECT * FROM user WHERE 1 = 1
<choose>
<when test="username != null">
AND username like #{username}
</when>
<when test="realname != null and nickname != null">
AND realname like #{realname} AND nickname like #{nickname}
</when>
<otherwise>
AND delete = 0
</otherwise>
</choose>
</select>
从执行逻辑来看,上面的代码是不是很像我们的 if () else () 语句。因为MyBatis中并没有提供 if else的写反,所以个人感觉choose、when、otherwise更像是 if else 的替代实现方式。
where、trim、set
在 if 的例子中,为了能够让SQL正确地拼接,我们有一个 WHERE 1=1 <if>...</if>的写法,这种方式虽然能够解决问题,但是根本没有凸显出我们 MyBatis 优雅的气质。而 where 标签能够帮助我们很好地解决这个问题:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
...
<mapper namespace="org.gaipianer.mybatis.demo.UserMapper">
<select id="findUser" resultType="User">
SELECT * FROM user
<where>
<if test="username != null">
username like #{username}
</if>
<if test="realname != null">
AND realname like #{realname}
</if>
<if test="nickname != null">
AND nickname like #{nickname}
</if>
</where>
</select>
</mapper>
加上 <where> 标签后,只有在有条件成立时,才会将 WHERE 拼接到我们的 SQL 语句中,假如上述例子中仅有 <if test="realname != null">AND realname like #{realname}</if> 这一个条件成立,MyBatis 也会帮助我们去掉 AND 前缀,保证我们查询语句的正确性。<where> 标签能够去除的前缀有 AND 和 OR 两个关键字。
where 标签实现的功能,也可以使用 trim 标签实现,<where>...</where>等价于以下的代码:
<trim prefix="WHERE" prefixOverrides="AND |OR ">
...
</trim>
注意:上述代码 prefixOverrides 属性中的空格是必填的!
MyBatis 会移除所有 prefixOverrides 属性中指定的内容,并且插入 prefix 属性中指定的内容。
同样的,MyBatis也为我们提供了 set 标签,更好地实现动态 update 操作,代码如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
...
<mapper namespace="org.gaipianer.mybatis.demo.UserMapper">
<update id="updateUser">
UPDATE user
<set>
<if test="realname != null">
realname = #{realname},
</if>
<if test="nickname != null">
nickname = #{nickname}
</if>
</set>
WHERE username = #{username}
</update>
</mapper>
和 where 标签一样,set 标签也会动态地在行首插入 SET 关键字,并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的)。
set 标签实现的功能,同样也可以使用 trim 标签实现,<set>...</set>等价于以下的代码:
<trim prefix="SET" suffixOverrides=",">
...
</trim>
在 where 标签的例子中,我们使用的是 prefixOverrides 覆盖多余的前缀,在 set 的例子中,使用的是 suffixOverrides 覆盖多月的后缀。
foreach
有的时候我们会遇到这样一种业务场景,查询 username 在 zhangsan、luolaoshi、lisi、wangmou 中的用户信息。自然而然地,我们想到了 SQL 中的 IN 关键字,foreach 标签能够让我们更加方便的拼接带有 IN 操作的 SQL 语句,代码如下:
<select id="selectUsernameIn" parameterType="java.util.List" resultType="User">
SELECT * FROM user
WHERE username IN
<foreach collection="list" item="item" index="index" open="(" separator="," close=")">
#{item}
</foreach>
</select>
foreach 标签中的 open 属性表示动态 SQL 拼接的起始字符, separator 属性表示在循环中每个元素之间的分隔符,在本例中我们的 IN 使用 , 分隔,close 属性表示拼接结束的字符。一顿操作之后,MyBatis会帮我们拼接出如下 SQL:
SELECT * FROM user
WHERE username IN (zhangsan,luolaoshi,lisi,wangmou)
foreach 不仅能够遍历 List 对象,他还能够遍历任何可迭代对象,比如 数组、Set等,foreach 甚至能够遍历 Map 类型的对象!!!当遍历对象为数组、List、Set 对象时,item 属性为集合中的元素,index 为元素在集合中的索引位置,当遍历对象为 Map 对象时,item 为 Map 中的 value,index 为 key 值。
script
以上的例子,我们都是在 Mapper.xml 中完成的。在之前的文章中介绍过,MyBatis 同样支持在Mapper.java 文件中以注解的方式拼接 SQL 语句,但是注解方式不能直接在 SQL 中使用 if set foreach where 等动态 SQL 标签,而是需要我们在 SQL 中使用 script 标签之后才可以:
@Select("<script>" +
"SELECT * FROM user " +
"<where>" +
"<if test='username != null'>username = #{username},</if>" +
"<if test='nickname != null'>nickname = #{nickname},</if>" +
"<if test='realname != null'>realname = #{realname}</if>" +
"</where>" +
"</script>")
List<User> listUser(User user);
今天的【从入门的放弃系列】就先到这里,以我最近的状态,下次更新说不定又是什么时候了。。。