MyBatis⑤动态SQL
动态 SQL
在业务逻辑复杂的情况下,要求 SQL 根据需求而动态变化。
- 传统 SQL:需要在 service 层对不同逻辑进行判断,代码复杂。
- 动态 SQL:可根据不同条件自动拼接 SQL 语句。
- 动态 SQL = SQL 语句 + 条件拼接子句
- 注意 SQL 语句之间的空格、逗号等。
主要功能
- if:匹配条件,追加满足条件的所有子句。
- choose:匹配条件,追加满足条件的一个子句(类似 Java 的 switch-case)
- trim
- foreach
- SQL 片段
if(❗)
1.1、说明
匹配条件,追加满足条件的所有子句。
根据 if 标签的 test
条件表达式,匹配条件。
- 若没有满足条件的
if
子句,不追加任何子句。 - 若匹配到任意满足条件的
if
子句,按声明的先后顺序追加子句。
1.2、示例
示例:查询所有用户
- 若有传入
userId
,则拼接userId
查询条件。 - 若有传入
name
,则拼接name
模糊查询条件。
1.2.1、Mapper
-
Mapper 接口
List<User> listUsersByUserIdAndName(@Param("userId") String userId, @Param("name") String name);
-
Mapper.xml
-
使用
WHERE 1=1
,则 if 子句都以AND
开头(无需考虑是否添加 AND) -
使用
<if>
子句,判断参数是否为空<select id="listUsersByUserIdAndName" resultType="user"> SELECT user_id, name, password FROM study_mysql.t_user WHERE 1 = 1 <if test="userId != null"> AND user_id = #{userId} </if> <if test="name != null"> AND name LIKE CONCAT('%',#{name},'%') </if> </select>
-
1.2.2、测试
修改 userId 和 name 的值,查看 SQL 语句(null 相当于没有传参)
String userId;
String name;
SqlSession sqlSession = MyBatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = mapper.listUsersByUserIdAndName(userId, name);
System.out.println(userList);
sqlSession.close();
-
没传参:不拼接任何子句
-
传参:追加相应的子句
-
传入 userId
-
传入 name
-
传入 userId 和 name
-
choose
2.1、说明
匹配条件,追加满足条件的一个子句(类似 Java 的 switch-case)
根据 choose 标签 中 when
标签的 test
条件表达式,匹配条件。
- 若匹配到满足条件的
when
子句,追加首个满足条件的when
子句。 - 若没有满足条件的
when
子句,有otherwise
子句则追加,否则不追加任何子句。
2.2、示例
示例:查询所有用户
- 若匹配到满足条件的
when
子句,追加首个满足条件的when
子句。- 只要有 userId,追加 userId 子句
- 只有 name,追加 userId 子句
- 若没有满足条件的
when
子句,追加otherwise
子句。
2.2.1、Mapper
Mapper 接口不变,修改 mapper.xml 中的 SQL 语句。
Mapper.xml
-
使用
WHERE 1=1
,则 when 子句都以AND
开头(无需考虑是否添加 AND) -
<when>
标签使用 test 属性,判断参数是否为空。<select id="listUsersByUserIdAndName" resultType="user"> SELECT user_id, name, password FROM study_mysql.t_user WHERE 1 = 1 <choose> <when test="userId != null"> AND user_id = #{userId} </when> <when test="name != null"> AND name LIKE CONCAT('%',#{name},'%') </when> <otherwise> AND user_id = 'admin' </otherwise> </choose> </select>
2.2.2、测试
-
没传参:拼接 otherwise 子句
-
传参:追加相应的子句
-
只传入 userId、传入 userId 和 name
-
只传入 name
-
trim(❗)
3.1、where
动态 SQL 拼接前需要添加
WHERE 1=1
条件,以便追加AND
子句。
使用 where
标签改进。
- 匹配到至少一个条件时,才会添加 WHERE 关键字。
- 自动将子句开头多余的 AND 或 OR 去除。
示例:查询用户
<select id="listUsersByUserIdAndName" resultType="user">
SELECT user_id, name, password
FROM study_mysql.t_user
<where>
<if test="userId != null">
AND user_id = #{userId}
</if>
<if test="name != null">
AND name LIKE CONCAT('%',#{name},'%')
</if>
</where>
</select>
3.2、set
适用于 UPDATE 语句
- 匹配到至少一个条件时,才会添加 SET 关键字。
- 自动将多余的逗号去除。
示例:更新用户
-
Mapper 接口
int updateUser(User user);
-
Mapper.xml
<update id="updateUser" parameterType="user"> UPDATE study_mysql.t_user <set> <if test="name != null"> name = #{name}, </if> <if test="password != null"> password = #{password}, </if> </set> WHERE user_id = #{userId} </update>
3.3、自定义 trim(*)
trim 标签的属性(了解即可)
- prefix:行首自动添加 prefix 值。
- prefixOverrides:若行首为 prefixOverrides 值,自动去除。
- suffix:行尾自动添加 suffix 值。
- suffixOverrides:若行尾为 suffixOverrides 值,自动去除。
示例:自定义 trim 元素,实现与 set 标签等价的功能。
-
set 标签
<set> ... </set>
-
自定义 trim 标签
<trim prefix="SET" suffixOverrides=","> ... </trim>
foreach
4.1、说明
用于遍历集合,通常用于 IN 条件语句。
属性
- collection:待遍历的集合对象。
- index:索引,可省略。
- 可迭代对象或数组:当前下标。
- Map:Key 值。
- item:集合项,封装集合遍历的每个元素。
- 可迭代对象或数组:下标为 index 的元素。
- Map:Key 对应的 Value 值。
- open:起始字符串。
- close:结束字符串。
- separator:集合项之间的分隔符。
4.2、示例
查询包含指定 name 的用户
-
Mapper 接口
List<User> listUsersNameIn(@Param("nameList") List<String> nameList);
-
Mapper.xml
-
collection 属性匹配
@Param
注解值。 -
占位符
#{}
参数为 item 值。<select id="listUsersNameIn" resultType="user"> SELECT user_id, name, password FROM study_mysql.t_user <where> <foreach collection="nameList" item="nameItem" open="name IN(" separator="," close=")"> #{nameItem} </foreach> </where> </select>
-
测试
向 nameList 中添加属性,查看不同情况的 SQL 语句。
ArrayList<String> nameList = new ArrayList<>();
SqlSession sqlSession = MyBatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
mapper.listUsersNameIn(nameList);
sqlSession.close();
-
没有属性:不追加任何条件
-
传属性:将所有属性拼接到 IN 语句中
SQL 片段
抽取公共的 SQL 语句,减少重复编码。
- 使用
sql
标签抽取,使用include
标签引用。 - 最好抽取单表查询的基本 SQL 语句。
- 多表查询具有动态性和不确定性(不同业务的查询列、连接条件不同)
- 不同业务的数据过滤条件也不同。
示例:根据用户名查询用户
-
抽取前
<select id="getUserByName" resultType="user"> SELECT user_id, name, password FROM study_mysql.t_user WHERE name = #{name} </select>
-
抽取后
<sql id="selectUser"> SELECT user_id, name, password FROM study_mysql.t_user </sql> <select id="getUserByName" resultType="user"> <include refid="selectUser"/> WHERE name = #{name} </select>