本节内容
- MyBatis动态SQL的基本使用
- MyBatis动态SQL的基本元素:if、set、where、bind、foreach等元素
- MyBatis的动态SQL的条件判断方法
前言
如果使用JDBC或者类似于Hibernate的其他框架,很多时候要根据需要去拼装SQL,这是一个麻烦的事情。因为某些查询需要许多条件,比如查询角色,可以根据角色名称或者备注等信息查询,当不输入名称时使用名称作条件就不合适了。通常使用其他框架需要大量的Java代码进行判断,可读性比较差,而MyBatis提供对SQL语句动态的组装能力,使用XML的几个简单的元素,便能完成动态SQL的功能。大量的判断都可以在MyBatis的映射XML里面配置,以达到许多需要大量代码才能实现的功能,大大减少了代码量,这体了MyBatis的灵活、高度可配置性和可维护性。
1、概述
元素 | 作用 | 备注 |
if | 判断语句 | 单条件分支判断 |
choose(when、otherwise) | 相当于java中的switch和case语句 | 多条件分支判断 |
tirm(where、set) | 辅助元素,用于处理特定的SQL拼接问题,比如去掉多余的and、or等 | 用于处理SQL拼接问题 |
foreach | 循环语句 | 在in语句中列举条件常用 |
动态SQL实际使用的元素并不多,它们带来了灵活性,减少了更多的工作量的同时,也在一定程度上提高了程序的可读性和可维护性。
2、if元素
if元素是常用的判断语句,相当于Java中的if语句,常与test属性联合使用。
举个栗子:根据用户名name或者用户ID查询一个角色信息,但是用户名是一个选填条件,不填写的时候,就不用它作为条件进行查询。
<select id="selectById" parameterType="String">
SELECT * FROM sys_user A
WHERE
<if test="id!= null and id!= ''">
A.ID= #{id}
</if>
<if test="userName != null and userName != ''">
AND A.USER_NAME like concat('%', #{userName}, '%')
</if>
</select>
当入参userName传递进mapper映射器时,如果参数不为空,则采取构造对userName的模糊查询,否则就不去构造这个条件。通常MyBatis的if元素省去了许多拼接SQL的工作,集中在XML中维护。
3、choose、when、otherwise元素
如果说if元素相当于if语句,即不是这个就是那个,那么有时候还需要另一种选择,甚至更多选择,也就是需要类似switch...case...default...功能的语句。
在映射器的动态语句中choose、when、otherwise这3个元素承担了这个功能。
举个栗子:
- 如果用户名不为空,则只用用户编号ID作为条件查询;
- 如果用户名为空,而昵称不为空,则用昵称作为条件进行模糊查询;
- 如果用户名和昵称都为空,则要求用户联系电话不能为空。
使用choose、when、otherwise元素:
<select id="selectUsers" parameterType="sysUser" resultMap="baseResultMap">
SELECT USER_NAME,NICK_NAME,USER_TEL
FROM sys_user A
WHERE 1=1
<choose>
<when test="userName != null and userName != ''">
AND A.USER_NAME = #{userName}
</when>
<when test="nickName != null and nickName != ''">
AND A.NICK_NAME like concat('%', #{nickName}, '%')
</when>
<otherwise>
AND A.USER_TEL IS NOT NULL
</otherwise>
</choose>
</select>
4、tirm、where、set元素
这里,我们会发现上边的SQL语句中有一个条件where 1=1,如果没有这个条件,则SQL语句将变为:
SELECT
USER_NAME,NICK_NAME,USER_TEL
FROM
sys_user A
WHERE
AND A.USER_NAME = #{userName}
AND A.NICK_NAME like concat('%', #{nickName}, '%')
AND A.USER_TEL IS NOT NULL
显然,上边的SQL将抛语法异常。 where也可以通过<where>元素加入。
而有的时候要去掉的是一些特殊的SQL语法,比如常见的and、or,而使用的tirm元素可以达到预期。
<select id="selectById" parameterType="String">
SELECT ID, USER_NAME, NICK_NAME FROM sys_user A
<tirm prefix="where" prefixOverride="and">
<if test="id!= null and id!= ''">
A.ID= #{id}
</if>
<if test="userName != null and userName != ''">
AND A.USER_NAME like concat('%', #{userName}, '%')
</if>
</tirm>
</select>
分析:tirm元素表示要去掉一些特殊的字符串,当时prefix表示语句的前缀,而prefixOverride表示要去掉哪种字符串。当时prefix代表的是语句的前缀,而prefixOverrides代表的是需要去掉哪种字符串。上面的写法基本与where是等效的。
在MyBatis中,set元素使用:
<update id="updateRole" parameterType="role">
UPDATE sys_role
<set>
<if test="roleName != null and roleName !=''">
ROLE_NAME = #{roleName},
</if>
<if test="note != null and note !=''">
NOTE = #{note}
</if>
</set>
WHERE ROLE_NO = #{roleNo}
</update>
set元素遇到逗号,它会把对应的逗号去掉,如果我们自己编写那将是多少次的判断?当我们只想更新备注时,只需要传递备注信息和角色编号即可,而不需要再传递角色名称。MyBatis会根据参数的规则进行动态SQL组装,便能满足需求,从而避免全部字段更新的问题。
5、foreach元素
foreach元素是一个循环语句,它的作用是遍历集合,能够很好地支持数组、List、Set接口的集合,对此提供遍历功能。它往往用于SQL中的in关键字。
比如批量删除一张表中的记录:
<!--[7]批量删除数据-->
<delete id="deleteByIdList" parameterType="String">
delete from tb_goods where ID IN
<foreach collection="list" item="id" index="index" open="(" separator="," close=")" >
#{id}
</foreach>
</delete>
分析:
- collection:配置的list是传进来的参数名称(以list集合作为入参传递),它可以是一个数组、List、Set等集合;
- item:配置的是循环中当前的元素;
- index:配置的是当前元素在集合的位置下标;
- open和close配置的是以什么符号将这些集合元素包装起来;
- separator:表示入参的元素的间隔符。
在SQL中常常用到in语句,但是对于大量数据的in语句要特别注意,因为它会消耗大量的性能。此外,还有一些数据库的SQL对执行的SQL长度有限制,所以使用它时要预估一下collection对象的长度,避免出现类似问题。
6、用test的属性判断字符串
test元素相当于条件判断语句,用于判断真假。大部分场景下,它都是用以判断空和非空的。有时需要判断字符串、数字和枚举等。
举个栗子:
<update id="updateInfo" parameterType="com.hl.study.model.SysUser">
update sys_user A
<set>
<if test="userName != null and userName != ''">
A.USER_NAME = #{userName},
</if>
<if test="nickName != null and nickName != ''">
A.NICK_NAME = #{nickName},
</if>
<if test="headImgUrl != null and headImgUrl != ''">
A.HEAD_IMG_URL = #{headImgUrl},
</if>
<if test="password != null and password != ''">
A.PASSWORD = #{password},
</if>
<if test="phone != null and phone != ''">
A.PHONE = #{phone},
</if>
<if test="telephone != null and telephone != ''">
A.TELPHONE = #{telephone},
</if>
<if test="email != null and email != ''">
A.EMAIL = #{email},
</if>
<if test="birthday != null and birthday != ''">
A.BIRTHDAY = #{birthday},
</if>
<if test="sex != null and sex != ''">
A.SEX = #{sex},
</if>
<if test="status != null and status != ''">
A.STATUS = #{status},
</if>
</set>
where A.ID = #{id}
</update>
如上所述,我们可以很简单的对字段是否为空进行判断,当然,对于字符串的判断,可以通过toString()的方法进行比较。它可以判断数值型的参数。对于枚举类而言,取决于使用何种typeHandler。
7、bind元素
bing元素的作用是通过OGNL表达式去自定义一个上下文变量,这样更方便使用。在进行模糊查询时,如果是MySQL数据库,常用到的是一个concat,它用%和参数相连。然而在Oracle数据库则没有,Oracle数据库用连接符号||,这样SQL就需要提供两种形式去实现。有了bind元素,就不必使用数据库语言,而是使用MyBatis的动态SQL即可完成。
比如,按照角色名称进行模糊查询,可以使用bind元素把映射文件写成如下:
<select id = "selectByame" parameterType="String" resultTpe="sysRole">
<bind name="pattern" value="'%' + roleName + '%'" />
SELECT ID, ROLE_NAME AS roleName, NICK_NAME AS nickName FROM sys_role A
WHERE A.ROLE_NAME like #{roleName}
</select>
这里bind元素中入参roleName和通配符(%)连接后赋给了pattern,然后就可以在select语句中使用这个变量进行模糊查询了。无论是MySQL还是Oracle都可以使用这样的语句,提高了代码的可移植性。
MyBatis支持多个参数使用bind元素的用法,所以传递多个参数也没有问题。
举个栗子:
/**
* 根据编号和用户名查询
* @param id 用户编号
* @param name 用户名
* @return
*/
T selectByIdAndName(@Param("id") String id, @Param("userName") String name);
这个时候只需在映射文件中传递两个变量作为查询条件即可:
<select id = "selectByIdAndNme" resultTpe="sysRole">
<bind name="pattern_id" value="'%' + id+ '%'" />
<bind name="pattern_roleName" value="'%' + roleName + '%'" />
SELECT ID, ROLE_NAME AS roleName, NICK_NAME AS nickName
FROM sys_role A
WHERE A.ID like #{id}
AND A.ROLE_NAME like #{roleName}
</select>
这里绑定了两个新的变量pattern_id和pattern_roleName,就可以在SQL的其他地方使用了。