MyBatis动态SQL
还不了解MySQL的可以看我这篇文章:
MyBatis入门:快速搭建数据库操作框架 + 两种增删改查的方法(CRUD)-CSDN博客
动态 SQL 是Mybatis的强⼤特性之⼀,能够完成不同条件下不同的 sql 拼接
官方文档:动态 SQL_MyBatis中文网
创建相关数据库
-- 创建数据库
DROP DATABASE IF EXISTS mybatis_test;CREATE DATABASE mybatis_test DEFAULT CHARACTER SET utf8mb4;-- 使用数据数据
USE mybatis_test;-- 创建表[用户表]
DROP TABLE IF EXISTS user_info;
CREATE TABLE `user_info` (`id` INT ( 11 ) NOT NULL AUTO_INCREMENT,`username` VARCHAR ( 127 ) NOT NULL,`password` VARCHAR ( 127 ) NOT NULL,`age` TINYINT ( 4 ) NOT NULL,`gender` TINYINT ( 4 ) DEFAULT '0' COMMENT '1-男 2-女 0-默认',`phone` VARCHAR ( 15 ) DEFAULT NULL,`delete_flag` TINYINT ( 4 ) DEFAULT 0 COMMENT '0-正常, 1-删除',`create_time` DATETIME DEFAULT now(),`update_time` DATETIME DEFAULT now() ON UPDATE now(),PRIMARY KEY ( `id` )
) ENGINE = INNODB DEFAULT CHARSET = utf8mb4; -- 添加用户信息
INSERT INTO mybatis_test.user_info( username, `password`, age, gender, phone )
VALUES ( 'admin', 'admin', 18, 1, '18612340001' );
INSERT INTO mybatis_test.user_info( username, `password`, age, gender, phone )
VALUES ( 'zhangsan', 'zhangsan', 18, 1, '18612340002' );
INSERT INTO mybatis_test.user_info( username, `password`, age, gender, phone )
VALUES ( 'lisi', 'lisi', 18, 1, '18612340003' );
INSERT INTO mybatis_test.user_info( username, `password`, age, gender, phone )
VALUES ( 'wangwu', 'wangwu', 18, 1, '18612340004' );
一、<if>标签
在注册⽤⼾的时候,可能会有这样⼀个问题,如下图所⽰:
注册分为两种字段:必填字段和⾮必填字段,那如果在添加⽤⼾的时候有不确定的字段传⼊,程序应
该如何实现呢?
这个时候就需要使⽤动态标签来判断了,⽐如添加的时候性别 gender 为⾮必填字段,具体实现如
下:
@Mapper
public interface UserInfoXMLMapper {Integer insertUserByCondition(UserInfo userInfo);}
<?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="com.Li.mybatis.demo.mapper.UserInfoXMLMapper"><insert id="insertUserByCondition">insert into user_info (username,password,age,<if test="gender != null">gender</if>)values (#{username},#{password},#{age},<if test="gender != null">#{gender}</if>)</insert></mapper>
单元测试:
测试纯在gender的情况:
@SpringBootTest
class UserInfoXMLMapperTest {@AutowiredUserInfoXMLMapper userInfoXMLMapper;@Testvoid insertUserByCondition() {UserInfo userInfo = new UserInfo();userInfo.setUsername("王麻子");userInfo.setPassword("123");userInfo.setAge(22);userInfo.setGender(0);userInfoXMLMapper.insertUserByCondition(userInfo);}
}
结果:
测试不存在gender的情况:
@SpringBootTest
class UserInfoXMLMapperTest {@AutowiredUserInfoXMLMapper userInfoXMLMapper;@Testvoid insertUserByCondition() {UserInfo userInfo = new UserInfo();userInfo.setUsername("王麻子");userInfo.setPassword("123");userInfo.setAge(22);//userInfo.setGender(0);userInfoXMLMapper.insertUserByCondition(userInfo);}
}
需要处理一下sql中的“,”
再次执行:
这样就可以了
那如果所有的都是非必传字段呢?
<?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="com.Li.mybatis.demo.mapper.UserInfoXMLMapper"><insert id="insertUserByCondition">insert into user_info (<if test="username != null">username,</if><if test="password != null">password,</if><if test="age != null">age,</if><if test="gender != null">gender</if>)values (<if test="username != null">#{username},</if><if test="password != null">#{password},</if><if test="age != null">#{age},</if><if test="gender != null">#{gender}</if>)</insert>
</mapper>
测试所有字段都传的情况下:
这个时候我又不穿gender:
那我们将sql中的“,”全部放在前面:
<?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="com.Li.mybatis.demo.mapper.UserInfoXMLMapper"><insert id="insertUserByCondition">insert into user_info (<if test="username != null">username</if><if test="password != null">,password</if><if test="age != null">,age</if><if test="gender != null">,gender</if>)values (<if test="username != null">#{username}</if><if test="password != null">,#{password}</if><if test="age != null">,#{age}</if><if test="gender != null">,#{gender}</if>)</insert>
</mapper>
那我再不传username呢?
测试
这个时候就需要用另一个注解了:
二、<trim>标签
之前的插⼊⽤⼾功能,只是有⼀个 gender 字段可能是选填项,如果有多个字段,⼀般考虑使⽤标签结
合标签,对多个字段都采取动态⽣成的⽅式。
标签中有如下属性:
- prefix:表⽰整个语句块,以prefix的值作为前缀
- suffix:表⽰整个语句块,以suffix的值作为后缀
- prefixOverrides:表⽰整个语句块要去除掉的前缀
- suffixOverrides:表⽰整个语句块要去除掉的后缀
那么我将“,”移到字段末尾:
<mapper namespace="com.Li.mybatis.demo.mapper.UserInfoXMLMapper"><insert id="insertUserByCondition">insert into user_info<trim prefix="(" suffix=")" suffixOverrides=","><if test="username != null">username,</if><if test="password != null">password,</if><if test="age != null">age,</if><if test="gender != null">gender</if></trim>values<trim prefix="(" suffix=")" suffixOverrides=","><if test="username != null">#{username},</if><if test="password != null">#{password},</if><if test="age != null">#{age},</if><if test="gender != null">#{gender}</if></trim></insert>
</mapper>
测试不加gender的情况:
@SpringBootTest
class UserInfoXMLMapperTest {@AutowiredUserInfoXMLMapper userInfoXMLMapper;@Testvoid insertUserByCondition() {UserInfo userInfo = new UserInfo();userInfo.setUsername("王麻子");userInfo.setPassword("123");userInfo.setAge(22);
// userInfo.setGender(0);userInfoXMLMapper.insertUserByCondition(userInfo);}
}
结果:
trim就帮我们处理掉了末尾的","
在以上 sql 动态解析时,会将第⼀个部分做如下处理:
- 基于 prefix 配置,开始部分加上 (
- 基于 suffix 配置,结束部分加上 )
- 多个 组织的语句都以 , 结尾,在最后拼接好的字符串还会以 , 结尾,会基于suffixOverrides 配置去掉最后⼀个 ,
- 注意 <if test="username !=null"> 中的 username 是传⼊对象的属性
三、<where>标签
看下⾯这个场景, 系统会根据我们的筛选条件, 动态组装where 条件
这种如何实现呢?
接下来我们看代码实现:
需求: 传⼊的⽤⼾对象,根据属性做where条件查询,⽤⼾对象中属性不为 null 的,都为查询条件. 如username 为 "a",则查询条件为 where username="a"
原有sql:
接口定义:
@Mapper
public interface UserInfoXMLMapper {List<UserInfo> queryByCondition(UserInfo userInfo);
}
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="com.Li.mybatis.demo.mapper.UserInfoXMLMapper"><select id="queryByCondition" resultType="com.Li.mybatis.demo.model.UserInfo">SELECT * FROM user_info<where><if test="age != null">age = #{age} and</if><if test="deleteFlag != null">delete_flag = #{deleteFlag} and</if></where></select>
</mapper>
测试用例:
@SpringBootTest
class UserInfoXMLMapperTest {@AutowiredUserInfoXMLMapper userInfoXMLMapper;@Testvoid queryByCondition() {UserInfo userInfo = new UserInfo();userInfo.setAge(18);userInfo.setDeleteFlag(0);userInfoXMLMapper.queryByCondition(userInfo);}
}
当age和deleteFlag都有值:
如果我们不写deleteFlag:
where不会帮我们取出末尾的and 或者 or , 但是最 前面 的or 和 and 可以,测试:
<?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="com.Li.mybatis.demo.mapper.UserInfoXMLMapper"><select id="queryByCondition" resultType="com.Li.mybatis.demo.model.UserInfo">SELECT * FROM user_info<where><if test="age != null">and age = #{age}</if><if test="deleteFlag != null">and delete_flag = #{deleteFlag}</if></where></select>
</mapper>
结果:
所以where 是可以帮我们取出前面的 and 或者 or的
那么如果我一个都不写:
💡
<where> 只会在⼦元素有内容的情况下才插⼊where⼦句,⽽且会⾃动去除⼦句的开头的AND或OR
以上标签也可以使⽤ <trim prefix="where" prefixOverrides="and"> 替换, 但是此种
情况下, 当⼦元素都没有内容时, where关键字也会保留
四、<set>标签
需求: 根据传⼊的⽤⼾对象属性来更新⽤⼾数据,可以使⽤标签来指定动态内容.
接⼝定义: 根据传⼊的⽤⼾ id 属性,修改其他不为 null 的属性
源SQL:
UPDATE user_info set username = 'zzz', age = 20, delete_flag = 1 WHERE id = 13
定义接口:
@Mapper
public interface UserInfoXMLMapper {Integer updateUserByCondition(UserInfo userInfo);
}
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="com.Li.mybatis.demo.mapper.UserInfoXMLMapper"><update id="updateUserByCondition">UPDATE user_info<set><if test="username != null">username = #{username},</if><if test="age != null">age = #{age},</if><if test="deleteFlag != null">delete_flag = #{deleteFlag}</if></set><where>id = 13</where></update>
</mapper>
单元测试:
如果不写deleteFlag(末尾选项):
结果:
注意age后面是有“,”的
如果一个元素都没有:
set就会不存在,但是sql肯定就错误了,所以set也做不了什么,过多的东西
💡
<set> :动态的在SQL语句中插⼊set关键字,并会删掉额外的逗号. (⽤于update语句中)
五、<foreach>标签
对集合进⾏遍历时可以使⽤该标签。标签有如下属性:
- collection:绑定⽅法参数中的集合,如 List,Set,Map或数组对象
- item:遍历时的每⼀个对象
- open:语句块开头的字符串
- close:语句块结束的字符串
- separator:每次遍历之间间隔的字符串
需求: 根据多个userid, 删除⽤⼾数据
DELETE FROM user_info WHERE id IN (21,22,23)
写接口:
@Mapper
public interface UserInfoXMLMapper {Integer batchDelete(List<Integer> ids);
}
xml:
<?xml version="1.0" encoding="UTF-8"?><?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="com.Li.mybatis.demo.mapper.UserInfoXMLMapper"><delete id="batchDelete">DELETE FROM user_info WHERE id IN <foreach collection="ids" item="id" separator="," open="(" close=")">#{id}</foreach></delete></mapper>
测试用例:
@SpringBootTest
class UserInfoXMLMapperTest {@AutowiredUserInfoXMLMapper userInfoXMLMapper;@Testvoid batchDelete() {List<Integer> ids = new ArrayList<>();ids.add(19);ids.add(24);ids.add(25);userInfoXMLMapper.batchDelete(ids);}
}
结果:
六、<include>标签
在xml映射⽂件中配置的SQL,有时可能会存在很多重复的⽚段,此时就会存在很多冗余的代码
我们可以对重复的代码⽚段进⾏抽取,将其通过 <sql> 标签封装到⼀个SQL⽚段,然后再通过<include> 标签进⾏引⽤。
- <sql> :定义可重⽤的SQL⽚段
- <include> :通过属性refid,指定包含的SQL⽚段
<?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="com.Li.mybatis.demo.mapper.UserInfoXMLMapper">
<!-- 假设这是一串冗余的代码--><sql id="deleteUser">DELETE FROM user_info</sql><delete id="batchDelete"><include refid="deleteUser"></include>WHERE idIN <foreach collection="ids" item="id" separator="," open="(" close=")">#{id}</foreach></delete></mapper>
相当于通过sql定义字符串,可以通过include标签通过id指定对应的字符串
上面的内容也标签也可以用,通过<script></script> 标签括起来就可以,但是不推荐,因为看着非常乱,就容易出错