MyBatis---动态 SQL
一、动态 SQL 的核心作用
动态 SQL 主要解决以下问题:
-
灵活性:根据不同的输入参数生成不同的 SQL 语句(如条件查询、批量操作)。
-
可维护性:减少重复代码,通过标签化逻辑提高 SQL 可读性。
-
安全性:自动处理参数绑定,防止 SQL 注入。
二、常用动态 SQL 标签
MyBatis 提供了以下标签来实现动态 SQL:
1. <if>
:条件判断
- 根据参数值是否满足条件,决定是否包含 SQL 片段。
- 示例:
xml
<select id="findUser" resultType="User">
SELECT * FROM users
<where>
<if test="name != null and name != ''">
AND name = #{name}
</if>
<if test="age != null">
AND age = #{age}
</if>
</where>
</select>
- 说明 :如果
name
或age
为空,则对应的条件不会被包含。
2. <choose>/<when>/<otherwise>
:多条件选择
-
类似于 Java 的
switch-case
,按顺序判断条件。 -
示例:
xml<select id="findUserByCondition" resultType="User"> SELECT * FROM users <where> <choose> <when test="name != null"> AND name = #{name} </when> <when test="email != null"> AND email = #{email} </when> <otherwise> AND status = 'active' </otherwise> </choose> </where> </select>
-
说明 :按顺序判断
name
、email
,若都不满足则执行默认条件。
3. <where>
:智能处理 WHERE 子句
-
自动去除多余的
AND
或OR
,并自动添加WHERE
关键字。 -
示例:
xml<select id="findUser" resultType="User"> SELECT * FROM users <where> <if test="name != null"> AND name = #{name} </if> <if test="age != null"> AND age = #{age} </if> </where> </select>
- 结果 :若
name
和age
都为空,则生成SELECT * FROM users
(无WHERE
子句);若name
不为空,则生成WHERE name = ?
。
- 结果 :若
4. <set>
:动态更新字段
-
用于
UPDATE
语句,自动去除末尾逗号。 -
示例:
xml<update id="updateUser"> UPDATE users <set> <if test="name != null"> name = #{name}, </if> <if test="email != null"> email = #{email}, </if> </set> WHERE id = #{id} </update>
- 结果 :若
name
和email
都不为空,生成SET name = ?, email = ?
;若只有一个字段不为空,自动去除末尾逗号。
- 结果 :若
5. <foreach>
:遍历集合
-
用于批量操作(如
IN
子句、批量插入)。 -
示例:
xml<select id="findUsersByIds" resultType="User"> SELECT * FROM users WHERE id IN <foreach item="id" collection="list" open="(" separator="," close=")"> #{id} </foreach> </select>
- 结果 :若传入的
list
为[1, 2, 3]
,生成WHERE id IN (1, 2, 3)
。
- 结果 :若传入的
6. <trim>
:灵活拼接 SQL
-
手动控制 SQL 片段的前缀和后缀,常用于复杂逻辑。
-
示例:
xml<trim prefix="WHERE" prefixOverrides="AND |OR "> <if test="name != null"> AND name = #{name} </if> </trim>
- 说明 :若
name
为空,则不生成WHERE
;若name
不为空,生成WHERE name = ?
。
- 说明 :若
三、动态 SQL 的实现原理
- XML 解析:MyBatis 启动时加载 Mapper XML 文件,解析动态 SQL 标签。
- SQL 拼接:运行时根据传入参数动态生成 SQL 片段。
- 参数绑定 :使用
#{}
绑定参数,防止 SQL 注入。 - 预编译 :最终生成的 SQL 被发送给数据库驱动,创建
PreparedStatement
。
四、动态 SQL 的应用场景
- 条件查询:根据用户输入动态过滤条件。
- 批量操作:批量插入、更新、删除。
- 多表关联:根据业务需求动态关联不同表。
- 复杂业务逻辑:如动态排序、分页等。
五、最佳实践
- 避免复杂嵌套:过多嵌套会降低可读性,建议拆分逻辑。
- 合理使用
<where>
和<set>
:简化 SQL 片段。 - 测试动态 SQL:通过日志查看生成的 SQL,确保逻辑正确。
- 参数校验:在业务层校验参数,避免无效条件。
六、示例:综合使用动态 SQL(实战)
比如我们已经写完了controller层,entity层,mapper层,service层等Impl。
动态SQL实现:修改mapper层:(注释掉SQL注解)
java
@Mapper
public interface UsersMapper {
@Select("insert into users(account,password,uname,gender,age,phone,email,address,avatar,regtime,uflag) values(#{account},#{password},#{uname},#{gender},#{age},#{phone},#{email},#{address},#{avatar},#{regtime},#{uflag})")
void addUser(Users users);
@Select("select * from users where account=#{account}")
Users getUserByAccount(String account);
// @Select("select * from users where uflag = #{uflag} order by regtime desc")
List<Users> queryUsersByUflag(Users users);
//根据id查询用户
@Select("select * from users where account = #{id}")
Users queryUsersById(String id);
//审核通过
// @Update("update users set uflag = #{uflag} where account = #{account}")
void updateUser(Users users);
//删除家长
@Delete("delete from users where account = #{id}")
void delUsers(String id);
}
这些SQL注解我们写进resources/mapper/UsersMapper.xml这里。
例如:
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.bkty.turtorsystem.mapper.UsersMapper">
<!--根据动态的条件查询用户-->
<select id="queryUsersByUflag" resultType="Users"
parameterType="Users">
select * from users
<where>
<if test="uflag != null and uflag != ''">
uflag = #{uflag}
</if>
<if test="account != null and account != ''">
and account = #{account}
</if>
<if test="uname != null and uname != ''">
and uname = #{uname}
</if>
<if test="password != null and password != ''">
and password = #{password}
</if>
<if test="email != null and email != ''">
email = #{email}
</if>
<if test="phone != null and phone != ''">
and phone like concat('%',#{phone},'%')
</if>
<if test="gender != null and gender != ''">
and gender = #{gender}
</if>
<if test="age != null and age != ''">
and age = #{age}
</if>
<if test="address != null and address != ''">
address = #{address}
</if>
<if test="condition != null and condition != ''">
${condition}
</if>
</where>
order by regtime desc
</select>
<update id="updateUser" parameterType="Users">
update users
<set>
<if test="account != null and account != ''">
account = #{account},
</if>
<if test="password != null and password != ''">
password = #{password},
</if>
<if test="uname != null and uname != ''">
uname = #{uname},
</if>
<if test="gender != null and gender != ''">
gender = #{gender},
</if>
<if test="age != null and age != ''">
age = #{age},
</if>
<if test="phone != null and phone != ''">
phone = #{phone},
</if>
<if test="email != null and email != ''">
email = #{email},
</if>
<if test="address != null and address != ''">
address = #{address},
</if>
<if test="avatar != null and avatar != ''">
avatar = #{avatar},
</if>
<if test="regtime != null and regtime != ''">
regtime = #{regtime},
</if>
<if test="uflag != null and uflag != ''">
uflag =#{uflag},
</if>
</set>
where account = #{account}
</update>
</mapper>
java
<mapper namespace="com.bkty.turtorsystem.mapper.UsersMapper">
这个namespace=指向我的mapper,相当于注解SQL,把注解改成了xml。
1. 查询语句:queryUsersByUflag
xml
<select id="queryUsersByUflag" resultType="Users" parameterType="Users">
select * from users
<where>
<if test="uflag != null and uflag != ''">
uflag = #{uflag}
</if>
<if test="account != null and account != ''">
and account = #{account}
</if>
<!-- 其他字段的 <if> 条件 -->
<if test="condition != null and condition != ''">
${condition}
</if>
</where>
order by regtime desc
</select>
功能说明
- 作用 :根据传入的
Users
对象中的字段动态生成 SQL 查询条件,查询users
表中的记录。 - 动态条件:
- 使用
<where>
标签包裹所有条件,MyBatis 会自动处理AND
或OR
的冗余问题(例如,如果第一个条件不成立,WHERE
关键字不会被输出)。 - 每个
<if>
标签检查字段是否非空,若非空则添加对应的查询条件。 - 特殊字段
condition
使用${condition}
直接拼接 SQL(需注意 SQL 注入风险)。
- 使用
关键点
- 字段条件:
- 所有字段(如
uflag
,account
,uname
等)都通过<if>
动态判断是否添加到查询条件中。 - 注意:第一个条件(
uflag
)没有加AND
,但<where>
标签会自动处理这种情况,避免语法错误。
- 所有字段(如
condition
字段:- 使用
${condition}
直接拼接原始 SQL 片段(例如1=1
或status='active'
)。 - 风险 :
${}
不会进行参数绑定,存在 SQL 注入风险,需确保传入值的安全性。
- 使用
- 排序:
- 固定按
regtime
降序排列。
- 固定按
示例
假设传入的 Users
对象包含 uflag="1"
和 account="test123"
,生成的 SQL 为:
sql
SELECT * FROM users
WHERE uflag = '1' AND account = 'test123'
ORDER BY regtime DESC;
2. 更新语句:updateUser
xml
<update id="updateUser" parameterType="Users">
update users
<set>
<if test="account != null and account != ''">
account = #{account},
</if>
<!-- 其他字段的 <if> 条件 -->
<if test="uflag != null and uflag != ''">
uflag =#{uflag},
</if>
</set>
where account = #{account}
</update>
功能说明
- 作用 :根据传入的
Users
对象中的字段动态更新users
表中的记录。 - 动态更新字段:
- 使用
<set>
标签包裹所有字段更新逻辑,MyBatis 会自动去除末尾多余的逗号。 - 每个
<if>
标签判断字段是否非空,若非空则更新对应字段。
- 使用
- 更新条件:
- 使用
account = #{account}
作为更新条件(需确保account
是唯一标识字段)。
- 使用
关键点
- 字段更新:
- 所有字段(如
account
,password
,uname
等)都通过<if>
动态判断是否更新。 - 注意:每个字段条件后都有逗号
,
,但<set>
会自动去除最后一个字段的逗号。
- 所有字段(如
- 更新条件:
- 使用
account = #{account}
作为更新条件,需确保account
是唯一值(否则可能更新多条记录)。
- 使用
- 潜在问题:
- 如果
account
不唯一,可能会导致意外更新多条记录。 - 更推荐使用主键(如
id
)作为更新条件。
- 如果
示例
假设传入的 Users
对象包含 account="test123"
和 uname="NewName"
,生成的 SQL 为:
sql
UPDATE users
SET account = 'test123', uname = 'NewName'
WHERE account = 'test123';
七、总结
MyBatis 的动态 SQL 通过标签化逻辑,解决了传统 SQL 硬编码的问题,使代码更简洁、安全且灵活。合理使用 <if>
、<where>
、<foreach>
等标签,可以大幅提升开发效率和代码质量。