if用法
if 标签通常用于 WHERE 语句中,通过判断参数值来决定是否使用某个查询条件,它也经常用于 UPDATE 语句中判断是否更新某一个字段,还可以在 INSERT 语句中用来判断是否插入某个字段的值。
在WHERE条件中使用if
假设现在有一个新的需求:实现一个用户管理高级查询功能,根据输入的条件去检索用户信息。这个功能还需要支持以下三种情况:当只输入用户名时,需要根据用户名进行模糊查询;当只输入邮箱时,根据邮箱进行完全匹配;当同时输入用户名和邮箱时,用这两个条件去查询匹配的用户。
如果仍然按照前面章节中介绍的方法去编写代码,可能会写出如下方法。
<select id="selectByUser" resultType="tk.mybatis.simple.model.SysUser">
select id,
user_name userName,
user_password userPassword,
user_email userEmail,
user_info userInfo,
head_img headImg,
create_time createTime
from sys_user
where user_name like concat('%', #{userName}, '%')
and user_email = #{userEmail}
</select>
当同时输入 userName 和 userEmail 这两个条件时,能查出正确的结果,但是当只提供 userName 的参数值时,userEmail 默认是 null,这就会导致 user_email=null 也成为查询条件,因而查不出正确的结果。这时就可以使用 if 标签来解决这个问题了,代码如下。
<select id="selectByUser" resultType="tk.mybatis.simple.model.SysUser">
select id,
user_name userName,
user_password userPassword,
user_email userEmail,
user_info userInfo,
head_img headImg,
create_time createTime
from sys_user
where 1 = 1
<if test="userName != null and userName != ''">
and user_name like concat('%', #{userName}, '%')
</if>
<if test="userEmail != null and userEmail != ''">
and user_email = #{userEmail}
</if>
</select>
if 标签有一个必填的属性 test,test 的属性值是一个符合 OGNL 要求的判断表达式,表达式的结果可以是 true 或 false,除此之外所有的非 0 值都为 true,只有 0 为 false。为了方便理解,在表达式中,建议只用 true 或 false 作为结果。OGNL 的详细用法在 4.7 节中会有比较完整的介绍,这里暂且只关注以下内容。
-
判断条件 property != null 或 property == null:适用于任何类型的字段,用于判断属性值是否为空。
-
判断条件 property != '' 或 property == '':仅适用于 String 类型的字段,用于判断是否为空字符串。
-
and 和 or:当有多个判断条件时,使用 and 或 or 进行连接,嵌套的判断可以使用小括号分组,and 相当于 Java 中的与(&&),or 相当于 Java 中的或(||)。
上面两个条件的属性类型都是 String,对字符串的判断和 Java 中的判断类似,首先需要判断字段是否为 null,然后再去判断是否为空(在 OGNL 表达式中,这两个判断的顺序不会影响判断的结果,也不会有空指针异常)。在本章所有例子中,字符串的判断几乎都包含 null 和空的判断,这两个条件不是必须写在一起,可以根据实际业务决定是否需要空值判断。
有了 XML 中的方法后,还需要在 UserMapper 接口中增加对应的接口方法,代码如下。
/**
* 根据动态条件查询用户信息
*
* @param sysUser
* @return
*/
List<SysUser> selectByUser(SysUser sysUser);
测试方法如下。
@Test
public void testSelectByUser() {
SqlSession sqlSession = getSqlSession();
try {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
// 只查询用户名时
SysUser user = new SysUser();
user.setUserName("ad");
List<SysUser> userList = userMapper.selectByUser(user);
Assert.assertTrue(userList.size() > 0);
// 只查询用户邮箱时
user = new SysUser();
user.setUserEmail("test@zccoder.com");
userList = userMapper.selectByUser(user);
Assert.assertTrue(userList.size() > 0);
// 同时查询用户名和邮箱时
user = new SysUser();
user.setUserName("ad");
user.setUserEmail("test@zccoder.com");
userList = userMapper.selectByUser(user);
// 由于没有同时符合这两个条件的用户,因此查询结果为0
Assert.assertTrue(userList.size() == 0);
} finally {
// 不要忘记关闭 sqlSession
sqlSession.close();
}
}
上面的代码针对需求中的 3 种情况分别进行测试,输出的日志内容如下。
由于 SQL 过长,上面的日志仅保留了 from 之后的 SQL,并且查询结果只保留了条数。从日志中可以看到,查询条件的不同组合最终执行的 SQL 和预期一样,这样就实现了动态条件查询。
虽然实现了需求,但是在 XML 的方法中仍然有两个地方需要注意。
-
注意 SQL 中 where 关键字后面的条件
where 1=1
由于两个条件都是动态的,所以如果没有 1=1 这个默认条件,当两个 if 判断都不满足时,最后生成的 SQL 就会以 where 结束,这样不符合 SQL 规范,因此会报错。加上 1=1 这个条件就可以避免 SQL 语法错误导致的异常。这种写法并不美观,在 4.3 节中会介绍 where 标签的用法,可以替代这种写法。
-
注意条件中的 and(或 or)
and user_name like concat('%',#{userName},'%')
这里的 and(或 or)需要手动添加,当这部分条件拼接到 where 1=1 后面时仍然是合法的 SQL。因为有默认的 1=1 这个条件,我们才不需要判断第一个动态条件是否需要加上 and(或 or),因为这种情况下 and(或 or)是必须有的。
在UPDATE更新列中使用if
现在要实现这样一个需求:只更新有变化的字段。需要注意,更新的时候不能将原来有值但没有发生变化的字段更新为空或 null。通过 if 标签可以实现这种动态列更新。
先在 UserMapper 中增加接口,代码如下。
/**
* 根据主键动态更新
* @param sysUser 用户
*/
int updateByIdSelective(SysUser sysUser);
一般情况下,MyBatis 中选择性更新的方法名会以 Selective 作为后缀。
接下来在 UserMapper.xml 中添加对应的 SQL 语句,代码如下。
<update id="updateByIdSelective">
update sys_user
set
<if test="userName != null and userName != ''">
user_name = #{userName},
</if>
<if test="userPassword != null and userPassword != ''">
user_password = #{userPassword},
</if>
<if test="userEmail != null and userEmail != ''">
user_email = #{userEmail},
</if>
<if test="userInfo != null and userInfo != ''">
user_info = #{userInfo},
</if>
<if test="headImg != null">
head_img = #{headImg,jdbcType=BLOB},
</if>
<if test="createTime != null">
create_time = #{createTime, jdbcType=TIMESTAMP},
</if>
id = #{id}
where id = #{id}
</update>
这里要结合业务层的逻辑判断,确保最终产生的 SQL 语句没有语法错误。需要注意的有两点:第一点是每个 if 元素里面 SQL 语句后面的逗号;第二点就是 where 关键字前面的 id=#{id}
这个条件。以下两种情况可以帮助大家理解为什么需要关注这两点。
-
全部的查询条件都是 null 或者空。
如果有 id=#{id} 这个条件,最终的 SQL 如下。
update sys_user set id=#{id} where id=#{id}
如果没有这个条件,最终的 SQL 如下。
update sys_user set where id=#{id}
这个 SQL 很明显是错误的,set 关键字后面没有内容,直接是 where 关键字,不符合 SQL 语句规范。
-
查询条件只有一个不是 null 也不是空(假设是 userName)。
如果有 id=#{id} 这个条件,最终的 SQL 如下。
update sys_user set user_name=#{userName},id=#{id} where id=#{id}
如果没有这个条件,最终的 SQL 如下。
update sys_user set user_name=#{userName},where id=#{id}
where 关键字前面直接是一个逗号,这个 SQL 语句也是错的。
从上面两种情况来看,id=#{id}
这个条件可以最大限度保证方法不出错。除了使用这种方式外,还可以结合业务层的逻辑判断调整 XML 文件中的 SQL 来确保最终的 SQL 语句的正确性,也可以通过 where 和 set 标签来解决这些问题。
下面是以上方法的测试代码。
@Test
public void testUpdateByIdSelective() throws Exception{
SqlSession sqlSession = getSqlSession();
try {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
// 创建一个新的 user 对象
SysUser user = new SysUser();
// 更新 id = 1 的用户
user.setId(1L);
// 修改邮箱
user.setUserEmail("test@zccoder.com");
// 更新邮箱,特别注意,这里的返回值 result 执行的是 SQL 影响的行数
int result = userMapper.updateByIdSelective(user);
// 只更新1条数据
Assert.assertEquals(1, result);
// 根据当前 id 查询修改后的数据
user = userMapper.selectById(1L);
// 修改后的名字保存不变,但是邮箱变成了新的
Assert.assertEquals("admin",user.getUserName());
Assert.assertEquals("test@zccoder.com",user.getUserEmail());
} finally {
// 为了不影响其它测试,这里选择回滚
sqlSession.rollback();
// 不要忘记关闭 sqlSession
sqlSession.close();
}
}
该测试输出的日志代码如下。
在INSERT动态插入列中使用if
在数据库表中插入数据的时候,如果某一列的参数值不为空,就使用传入的值,如果传入参数为空,就使用数据库中的默认值(通常是空),而不使用传入的空值。使用 if 就可以实现这种动态插入列的功能。
先修改 sys_user 表,在数据库中执行如下的 SQL 语句给 user_email 列增加默认值 test@mybatis.tk。
ALTER TABLE `sys_user`
MODIFY COLUMN `user_email` varchar(50) NULL DEFAULT 'test@mybatis.tk'
COMMENT '邮箱'
AFTER `user_password`;
下面直接修改 SysUserMapper.xml 中的 insert2 方法。
<insert id="insert2" useGeneratedKeys="true" keyProperty="id">
insert into sys_user(
user_name,user_password,
<if test="userEmail != null and userEamil != ''">
user_email,
</if>
user_info,head_img,create_time)
values(
#{userName},#{userPassword},
<if test="userEmail != null and userEamil != ''">
#{userEmail},
</if>
#{userInfo},#{headImg,jdbcType=BLOB},#{createTime,jdbcType=TIMESTAMP})
</insert>
在 INSERT 中使用时要注意,若在列的部分增加 if 条件,则 values 的部分也要增加相同的 if 条件,必须保证上下可以互相对应,完全匹配。
该方法的测试代码如下。
@Test
public void testInsert2Seclective(){
SqlSession sqlSession = getSqlSession();
try {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
// 创建一个user对象
SysUser user = new SysUser();
user.setUserName("test-selective");
user.setUserPassword("123456");
user.setUserInfo("test info");
user.setCreateTime(new Date());
// 插入数据库
userMapper.insert2(user);
// 获取插入的这条数据
user = userMapper.selectById(user.getId());
Assert.assertEquals("test@zccoder.com",user.getUserEmail());
} finally {
sqlSession.rollback();
// 不要忘记关闭 sqlSession
sqlSession.close();
}
}
在新增的 user 中,我们并没有给 userEmail 属性赋值,这样就会使用数据库的默认值,执行测试后,输出日志如下。
观察日志输出的 SQL 语句和查询结果,INSERT 语句中并没有插入 user_email 列,查询的结果中 user_email 的值就是我们设置的默认值 test@mybatis.tk。