MyBatis 动态 SQL 精讲:告别硬编码的智能拼接艺术
🔍 MyBatis 动态 SQL 精讲:告别硬编码的智能拼接艺术
文章目录
- 🔍 MyBatis 动态 SQL 精讲:告别硬编码的智能拼接艺术
- 🧠 一、为什么需要动态 SQL?
- 💡 JDBC 时代的痛点
- 🛠️ 二、核心标签深度解析
- 💡 动态 SQL 标签全景图
- 🔥 1. if 标签:条件分支
- 🔀 2. choose/when/otherwise:多路选择
- 🎯 3. where 标签:智能 WHERE 子句
- 🔄 4. set 标签:智能 UPDATE
- 🔁 5. foreach 标签:循环遍历
- 🎨 6. trim 标签:自定义修剪
- 💡 三、最佳实践指南
- 💡 可维护性设计原则
- 🛡️ 1. 避免冗余条件
- 📝 2. 保持 SQL 可读性
- ⚠️ 四、常见陷阱与优化
- 💡 性能与安全对比
- 🔧 1. 空条件处理
- ⚡ 2. foreach 性能优化
- 🛡️ 3. SQL 注入防护
- 🔚 总结与延伸
- 📚 核心要点回顾
🧠 一、为什么需要动态 SQL?
💡 JDBC 时代的痛点
在传统 JDBC 开发中,我们经常遇到这样的代码:
// JDBC 动态拼接SQL的噩梦
StringBuilder sql = new StringBuilder("SELECT * FROM users WHERE 1=1 ");
if (name != null) {sql.append("AND name = '").append(name).append("' ");
}
if (email != null) {sql.append("AND email = '").append(email).append("' ");
}
// SQL注入风险!字符串拼接地狱!
MyBatis 动态 SQL 的优势:
🛠️ 二、核心标签深度解析
💡 动态 SQL 标签全景图
🔥 1. if 标签:条件分支
应用场景:多条件查询
<select id="findUsers" resultType="User">SELECT * FROM users<where><if test="name != null and name != ''">AND name LIKE #{name}</if><if test="email != null">AND email = #{email}</if><if test="status != null">AND status = #{status}</if></where>
</select>
输入参数:{name: “张”, status: 1}
生成 SQL:
SELECT * FROM users
WHERE name LIKE ? AND status = ?
🔀 2. choose/when/otherwise:多路选择
应用场景:优先级条件查询
<select id="findUsers" resultType="User">SELECT * FROM users<where><choose><when test="id != null">id = #{id}</when><when test="email != null">email = #{email}</when><otherwise>status = 1</otherwise></choose></where>
</select>
输入参数:{email: “test@example.com”}
生成 SQL:
SELECT * FROM users WHERE email = ?
🎯 3. where 标签:智能 WHERE 子句
解决痛点:自动处理 AND/OR 和空条件
<!-- 传统方式需要写 WHERE 1=1 -->
<select id="findUsers" resultType="User">SELECT * FROM users<where><if test="name != null">name = #{name}</if><if test="email != null">AND email = #{email}</if></where>
</select>
输入参数:{name: null, email: “test@example.com”}
生成 SQL:
SELECT * FROM users WHERE email = ?
🔄 4. set 标签:智能 UPDATE
应用场景:动态更新字段
<update id="updateUser">UPDATE users<set><if test="name != null">name = #{name},</if><if test="email != null">email = #{email},</if><if test="status != null">status = #{status},</if></set>WHERE id = #{id}
</update>
输入参数:{id: 1, name: “张三”}
生成 SQL:
UPDATE users SET name = ? WHERE id = ?
🔁 5. foreach 标签:循环遍历
应用场景:批量操作和 IN 查询
<select id="findUsersByIds" resultType="User">SELECT * FROM usersWHERE id IN<foreach item="id" collection="ids" open="(" separator="," close=")">#{id}</foreach>
</select>
输入参数:{ids: [1, 2, 3, 5, 8]}
生成 SQL:
SELECT * FROM users WHERE id IN (?, ?, ?, ?, ?)
🎨 6. trim 标签:自定义修剪
应用场景:更精细的字符串处理
<update id="updateUser">UPDATE users<trim prefix="SET" suffixOverrides=","><if test="name != null">name = #{name},</if><if test="email != null">email = #{email},</if></trim>WHERE id = #{id}
</update>
💡 三、最佳实践指南
💡 可维护性设计原则
🛡️ 1. 避免冗余条件
不推荐:
<if test="name != null and name != ''">AND name = #{name}
</if>
<if test="name != null and name != ''">AND email = #{email}
</if>
推荐:
<where><if test="condition.name != null and condition.name != ''">name = #{condition.name}</if><if test="condition.email != null">email = #{condition.email}</if>
</where>
📝 2. 保持 SQL 可读性
复杂动态 SQL 示例:
<select id="searchUsers" resultType="User">/* 用户综合查询 */SELECT * FROM users<where><!-- 姓名条件 --><if test="name != null">AND (name LIKE #{name} OR nick_name LIKE #{name})</if><!-- 状态条件 --><if test="statusList != null and statusList.size() > 0">AND status IN<foreach item="status" collection="statusList" open="(" separator="," close=")">#{status}</foreach></if><!-- 时间范围 --><if test="startTime != null and endTime != null">AND create_time BETWEEN #{startTime} AND #{endTime}</if></where>ORDER BY id DESC
</select>
⚠️ 四、常见陷阱与优化
💡 性能与安全对比
问题类型 | 问题描述 | 解决方案 |
---|---|---|
条件缺失 | 所有if条件都不满足,SQL语法错误 | 使用<where> 标签 |
SQL注入 | 不当使用${} 导致注入风险 | 始终优先使用#{} |
性能问题 | foreach 批量过大导致SQL过长 | 分批处理,每批1000条 |
空集合 | 空集合在foreach 中导致SQL错误 | 先判断集合是否为空 |
🔧 1. 空条件处理
问题代码:
<select id="findUsers" resultType="User">SELECT * FROM usersWHERE<if test="name != null">name = #{name}</if><if test="email != null">AND email = #{email}</if>
</select>
当所有if都不满足时生成:
SELECT * FROM users WHERE
修复方案:
<select id="findUsers" resultType="User">SELECT * FROM users<where><if test="name != null">name = #{name}</if><if test="email != null">AND email = #{email}</if></where>
</select>
⚡ 2. foreach 性能优化
批量插入优化:
<insert id="batchInsertUsers">INSERT INTO users (name, email) VALUES<foreach item="user" collection="users" separator=",">(#{user.name}, #{user.email})</foreach>
</insert>
**风险:**一次插入10000条数据会导致SQL过长
解决方案:分批处理
// 服务层分批处理
public void batchInsert(List<User> users) {int batchSize = 1000;for (int i = 0; i < users.size(); i += batchSize) {List<User> batch = users.subList(i, Math.min(i + batchSize, users.size()));userMapper.batchInsertUsers(batch);}
}
🛡️ 3. SQL 注入防护
危险用法(绝对避免!):
<select id="findUsers">SELECT * FROM users ORDER BY ${orderBy} ${orderDirection}
</select>
安全用法:
<select id="findUsers">SELECT * FROM users ORDER BY <choose><when test="orderBy == 'name'">name</when><when test="orderBy == 'email'">email</when><otherwise>id</otherwise></choose><choose><when test="orderDirection == 'desc'">DESC</when><otherwise>ASC</otherwise></choose>
</select>
🔍 MyBatis 动态 SQL 精讲:告别硬编码的智能拼接艺术
作为拥有十年企业级开发经验的架构师,我将带您深入探索 MyBatis 动态 SQL 的强大功能。本文不仅解析核心标签,更通过实战案例展示如何构建灵活、高效且安全的数据库查询!
目录
•
🧠 一、为什么需要动态 SQL?
•
🛠️ 二、核心标签深度解析
•
💡 三、最佳实践指南
•
⚠️ 四、常见陷阱与优化
•
🚀 五、实战业务场景
🧠 一、为什么需要动态 SQL?
💡 JDBC 时代的痛点
在传统 JDBC 开发中,我们经常遇到这样的代码:
// JDBC 动态拼接SQL的噩梦
StringBuilder sql = new StringBuilder("SELECT * FROM users WHERE 1=1 “);
if (name != null) {
sql.append(“AND name = '”).append(name).append(”’ “);
}
if (email != null) {
sql.append(“AND email = '”).append(email).append(”’ ");
}
// SQL注入风险!字符串拼接地狱!
MyBatis 动态 SQL 的优势:
graph LR
A[JDBC手动拼接] --> B[SQL注入风险]
A --> C[代码冗长]
A --> D[难以维护]
E[MyBatis动态SQL] --> F[预编译安全]
E --> G[标签化简洁]
E --> H[易于维护]style B fill:#f99,stroke:#333
style C fill:#f99,stroke:#333
style D fill:#f99,stroke:#333
style F fill:#9f9,stroke:#333
style G fill:#9f9,stroke:#333
style H fill:#9f9,stroke:#333
🛠️ 二、核心标签深度解析
💡 动态 SQL 标签全景图
mindmap
root(MyBatis动态SQL)
条件控制
if
choose/when/otherwise
SQL结构
where
set
trim
循环遍历
foreach
其他
bind
🔥 1. if 标签:条件分支
应用场景:多条件查询
生成 SQL:
SELECT * FROM users
WHERE name LIKE ? AND status = ?
🔀 2. choose/when/otherwise:多路选择
应用场景:优先级条件查询
生成 SQL:
SELECT * FROM users WHERE email = ?
🎯 3. where 标签:智能 WHERE 子句
解决痛点:自动处理 AND/OR 和空条件
生成 SQL:
SELECT * FROM users WHERE email = ?
🔄 4. set 标签:智能 UPDATE
应用场景:动态更新字段
生成 SQL:
UPDATE users SET name = ? WHERE id = ?
🔁 5. foreach 标签:循环遍历
应用场景:批量操作和 IN 查询
生成 SQL:
SELECT * FROM users WHERE id IN (?, ?, ?, ?, ?)
🎨 6. trim 标签:自定义修剪
应用场景:更精细的字符串处理
style B fill:#9f9,stroke:#333
style C fill:#9f9,stroke:#333
🛡️ 1. 避免冗余条件
不推荐:
<!-- 状态条件 -->
<if test="statusList != null and statusList.size() > 0">AND status IN<foreach item="status" collection="statusList" open="(" separator="," close=")">#{status}</foreach>
</if><!-- 时间范围 -->
<if test="startTime != null and endTime != null">AND create_time BETWEEN #{startTime} AND #{endTime}
</if>
ORDER BY id DESC ⚠️ 四、常见陷阱与优化 💡 性能与安全对比 | 问题类型 | 问题描述 | 解决方案 | |---------|---------|---------| | **条件缺失** | 所有if条件都不满足,SQL语法错误 | 使用``标签 | | **SQL注入** | 不当使用`${}`导致注入风险 | 始终优先使用`#{}` | | **性能问题** | `foreach`批量过大导致SQL过长 | 分批处理,每批1000条 | | **空集合** | 空集合在`foreach`中导致SQL错误 | 先判断集合是否为空 | 🔧 1. 空条件处理 问题代码: SELECT * FROM users WHERE name = #{name} AND email = #{email} 当所有if都不满足时生成:
SELECT * FROM users WHERE
修复方案:
解决方案:分批处理
// 服务层分批处理
public void batchInsert(List users) {
int batchSize = 1000;
for (int i = 0; i < users.size(); i += batchSize) {
List batch = users.subList(i, Math.min(i + batchSize, users.size()));
userMapper.batchInsertUsers(batch);
}
}
🛡️ 3. SQL 注入防护
危险用法(绝对避免!):
<select id="searchProducts" resultType="Product">SELECT * FROM products<where><!-- 关键词搜索 --><if test="keyword != null">AND (name LIKE CONCAT('%', #{keyword}, '%') OR description LIKE CONCAT('%', #{keyword}, '%'))</if><!-- 价格范围 --><if test="minPrice != null">AND price >= #{minPrice}</if><if test="maxPrice != null">AND price <= #{maxPrice}</if><!-- 类目筛选 --><if test="categoryIds != null and categoryIds.size() > 0">AND category_id IN<foreach item="categoryId" collection="categoryIds" open="(" separator="," close=")">#{categoryId}</foreach></if><!-- 商品状态 --><if test="status != null">AND status = #{status}</if></where>ORDER BY<choose><when test="sortBy == 'price'">price</when><when test="sortBy == 'sales'">sales_count</when><otherwise>create_time</otherwise></choose><choose><when test="sortOrder == 'desc'">DESC</when><otherwise>ASC</otherwise></choose>
</select>
🔍 执行流程全景
🔚 总结与延伸
📚 核心要点回顾
1.if/choose:条件分支控制
2.where/set:智能SQL结构处理
3.foreach:循环遍历操作
安全第一:始终优先使用#{}
5.性能优化:批量操作分批处理