还不了解MySQL的可以看我这篇文章:
动态 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 {
@Autowired
UserInfoXMLMapper userInfoXMLMapper;
@Test
void insertUserByCondition() {
UserInfo userInfo = new UserInfo();
userInfo.setUsername("王麻子");
userInfo.setPassword("123");
userInfo.setAge(22);
userInfo.setGender(0);
userInfoXMLMapper.insertUserByCondition(userInfo);
}
}
结果:
测试不存在gender的情况:
@SpringBootTest
class UserInfoXMLMapperTest {
@Autowired
UserInfoXMLMapper userInfoXMLMapper;
@Test
void 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 {
@Autowired
UserInfoXMLMapper userInfoXMLMapper;
@Test
void 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 {
@Autowired
UserInfoXMLMapper userInfoXMLMapper;
@Test
void 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 {
@Autowired
UserInfoXMLMapper userInfoXMLMapper;
@Test
void 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 id
IN
<foreach collection="ids" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</delete>
</mapper>
相当于通过sql定义字符串,可以通过include标签通过id指定对应的字符串
上面的内容也标签也可以用,通过<script></script> 标签括起来就可以,但是不推荐,因为看着非常乱,就容易出错