一、MyBatis 动态 SQL 标签详解
标签 | 作用 | 核心属性 | 使用场景 | 示例 |
---|---|---|---|---|
<if> |
条件判断 | test="表达式" |
非必填字段处理 | xml <if test="gender != null">gender=#{gender}</if> |
<trim> |
智能去除多余字符 | prefix/suffix prefixOverrides/suffixOverrides |
多字段动态插入/更新 | [见下方完整示例] |
<where> |
动态生成 WHERE 子句,自动去除开头 AND/OR | 无 | 条件查询 | xml <where><if test="age!=null">AND age=#{age}</if></where> |
<set> |
动态生成 SET 子句,自动去除尾部逗号 | 无 | 更新操作 | xml <set><if test="name!=null">name=#{name},</if></set> |
<foreach> |
遍历集合 | collection/item open/close/separator |
批量删除/IN 查询 | xml <foreach collection="ids" item="id" open="(" close=")" separator=",">#{id}</foreach> |
<include> |
引用公共 SQL 片段 | refid |
消除重复 SQL | xml <include refid="baseColumn"/> |
<sql> |
定义可重用 SQL 片段 | id |
抽取公共字段列表 | xml <sql id="baseColumn">id,name,age</sql> |
<trim>
完整示例(多字段插入)
<insert id="insertUser">
INSERT INTO user
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="username != null">username,</if>
<if test="password != null">password,</if>
</trim>
VALUES
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="username != null">#{username},</if>
<if test="password != null">#{password},</if>
</trim>
</insert>
二、XML vs 注解方式对比
特性 | XML 方式 | 注解方式 | 推荐场景 |
---|---|---|---|
动态SQL | 原生支持,结构清晰 | 需用<script> 包裹,可读性差 |
复杂业务首选 XML |
可维护性 | SQL 与 Java 代码分离 | SQL 嵌入代码中 | 中大型项目用 XML |
多表关联 | 支持强大的 ResultMap | 关联映射能力有限 | 复杂关联查询用 XML |
IDE 支持 | 语法高亮、自动提示 | 无 SQL 校验 | 所有场景 |
批量操作 | 完整支持 | 拼接复杂 | 批量操作强制 XML |
注解方式示例(不推荐)
@Update("<script>" +
"UPDATE user " +
"<set>" +
" <if test='name!=null'>name=#{name},</if>" +
"</set>" +
"WHERE id=#{id}" +
"</script>")
void updateUser(User user);
三、Mapper XML 实践
1. 基础结构规范
<?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.example.mapper.UserMapper">
<!-- 1. 公共SQL片段 -->
<sql id="baseColumn">id,username,email</sql>
<!-- 2. ResultMap映射 -->
<resultMap id="userMap" type="User">
<id column="id" property="id"/>
<result column="create_time" property="createTime"/>
</resultMap>
<!-- 3. SQL语句 -->
<select id="selectById" resultMap="userMap">
SELECT <include refid="baseColumn"/>
FROM user WHERE id = #{id}
</select>
</mapper>
2. 动态 SQL 技巧
避免空条件陷阱:用
<where>
替代WHERE 1=1
批量插入优化:
<insert id="batchInsert"> INSERT INTO user (name) VALUES <foreach collection="list" item="item" separator=","> (#{item.name}) </foreach> </insert>
安全更新:配合
<set>
防止全表更新<update id="update"> UPDATE user <set> <if test="name != null">name=#{name}</if> </set> WHERE id = #{id} AND status = 1 <!-- 双重保险 --> </update>
四、项目实战:图书管理系统
1. 假设他需要我这个分页的字段在xml中
<!-- BookInfoMapper.xml -->
<select id="queryBookListByPage" resultType="BookInfo">
SELECT id, book_name, author, count, price, publish, status
FROM book_info
WHERE status != 0
ORDER BY id DESC
LIMIT #{offset}, #{pageSize}
</select>
<select id="count" resultType="int">
SELECT COUNT(1) FROM book_info WHERE status != 0
</select>
2. 逻辑删除 vs 物理删除
类型 | 实现方式 | SQL 示例 | 优点 |
---|---|---|---|
逻辑删除 | 更新状态字段 | UPDATE book SET status=0 WHERE id=#{id} |
数据可恢复,安全 |
物理删除 | 真实删除数据 | DELETE FROM book WHERE id=#{id} |
节省存储空间 |
3. 条件更新(动态 SQL 典范)
<update id="updateBook">
UPDATE book_info
<set>
<if test="bookName != null">book_name = #{bookName},</if>
<if test="author != null">author = #{author},</if>
<if test="status != null">status = #{status},</if>
</set>
WHERE id = #{id} AND status != 0 <!-- 防止更新已删除数据 -->
</update>
五、MyBatis 高级特性
1. 结果集自动映射
# application.yml
mybatis:
configuration:
map-underscore-to-camel-case: true # 开启驼峰转换
auto-mapping-behavior: full # 自动映射未知字段
2. 枚举类型处理
// 枚举转换器
public enum BookStatus {
NORMAL(1, "可借阅"),
FORBIDDEN(2, "不可借阅");
private final int code;
private final String name;
// 通过code获取枚举
public static BookStatus fromCode(int code) { ... }
}
// Mapper中使用
<result column="status" property="status"
typeHandler="org.apache.ibatis.type.EnumOrdinalTypeHandler"/>
3. 二级缓存配置
<!-- Mapper.xml 开启缓存 -->
<cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/>
<!-- 特定语句禁用缓存 -->
<select id="selectLatest" useCache="false"> ... </select>
六、避坑指南
参数引用问题:
#{field}
防止 SQL 注入${field}
直接拼接 SQL(慎用!)
动态 SQL 陷阱:
<!-- 错误示例:逗号问题 --> UPDATE user <set> <if test="name!=null">name=#{name},</if> <!-- 多余逗号会导致语法错误 --> age=#{age} </set> <!-- 正确:用<set>智能处理 -->
批量操作优化:
// 分批提交(每1000条提交一次) @Transactional void batchInsert(List<User> users) { SqlSession batchSession = sqlSessionFactory.openSession(ExecutorType.BATCH); UserMapper mapper = batchSession.getMapper(UserMapper.class); for (int i = 0; i < users.size(); i++) { mapper.insert(users.get(i)); if (i % 1000 == 0) batchSession.flushStatements(); } batchSession.commit(); }
七、典型应用场景解决方案
场景 | 解决方案 |
---|---|
多条件模糊查询 | <where> + <if test="name!=null and name!=''">AND name LIKE CONCAT('%',#{name},'%')</if> |
批量逻辑删除 | <update> + <foreach> |
主从表关联查询 | 使用<resultMap> 的<collection> 标签 |
动态列排序 | ORDER BY ${sortField} ${sortOrder} (注意注入风险) |
唯一约束冲突处理 | ON DUPLICATE KEY UPDATE + 动态<set> |
总结
动态 SQL 优先 XML:复杂业务场景使用 XML 配置,保持代码清晰度
安全第一原则:
更新/删除操作必须带 WHERE 条件
批量操作使用
@Transactional
保证事务
性能优化关键:
分页查询先查总数再查数据
批量操作使用 BATCH 执行器
可维护性实践:
使用
<sql>
片段复用 SQL字段枚举值统一管理
通过图书管理系统实战案例,掌握 MyBatis 动态 SQL 在 CRUD 操作中的灵活应用,特别关注分页查询、条件更新、批量删除等高频场景的实现细节。