
? 动态 SQL 核心标签深度解析
1. if
标签:条件判断的基石
if
标签是最基础的动态 SQL 工具,它能根据传入的参数动态决定是否包含某个 SQL 片段。比如在用户查询接口中,如果用户传入了姓名参数,就添加姓名过滤条件;否则跳过。<select id="findUser" resultType="User">
SELECT * FROM users
<where>
<if test="username != null">
AND username = #{username}
if>
<if test="age != null">
AND age = #{age}
if>
where>
select>
where
标签会自动处理第一个 AND
,避免生成 WHERE AND
这样的语法错误。2. choose
/when
/otherwise
:多条件分支选择
choose
标签就派上用场了。它类似 Java 中的 switch
语句,只会选择一个符合条件的分支执行。假设一个商品搜索接口,优先按商品名称搜索,其次按分类搜索,都不满足时返回热门商品。
<select id="searchProducts" resultType="Product">
SELECT * FROM products
<where>
<choose>
<when test="name != null">
name LIKE CONCAT('%', #{name}, '%')
when>
<when test="categoryId != null">
category_id = #{categoryId}
when>
<otherwise>
is_hot = 1
otherwise>
choose>
where>
select>
3. foreach
:批量操作的利器
foreach
标签能遍历集合或数组,生成对应的 SQL 片段。比如批量删除用户时:<delete id="deleteUsers">
DELETE FROM users
WHERE id IN
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
foreach>
delete>
collection
指定集合参数名,item
是当前遍历的元素,open
和 close
定义包裹的符号。4. trim
/where
/set
:SQL 语句的美容师
trim
:自定义前缀、后缀,移除多余的关键字。
场景:动态更新用户信息时,只更新有值的字段。xml<update id="updateUser"> UPDATE users <trim prefix="SET" suffixOverrides=","> <if test="username != null">username = #{username},if> <if test="email != null">email = #{email},if> trim> WHERE id = #{id} update>
prefix="SET"
会在开头添加SET
,suffixOverrides=","
移除最后一个逗号。where
:自动处理WHERE
子句,移除开头的AND
或OR
。
set
:类似trim
,专门用于更新语句,自动添加SET
并处理逗号。
5. sql
与 include
:代码复用的魔法
标签定义,再通过
引用。比如多个查询都需要用户基本字段:<sql id="Base_Columns">
id, username, email, create_time
sql>
<select id="getUser" resultType="User">
SELECT <include refid="Base_Columns"/> FROM users WHERE id = #{id}
select>
? 2025 最新实战案例解析
案例 1:电商平台复杂搜索优化
<select id="searchGoods" resultType="Goods">
SELECT * FROM goods
<where>
<if test="name != null">
AND name LIKE CONCAT('%', #{name}, '%')
if>
<if test="minPrice != null and maxPrice != null">
AND price BETWEEN #{minPrice} AND #{maxPrice}
if>
<if test="brandIds != null">
AND brand_id IN
<foreach collection="brandIds" item="id" open="(" separator="," close=")">
#{id}
foreach>
if>
where>
<if test="orderBy != null">
ORDER BY ${orderBy} ${sort}
if>
LIMIT #{offset}, #{pageSize}
select>
- 使用
CONCAT
避免 SQL 注入,同时保证索引有效。 - 分页参数
offset
和pageSize
用#{}
预编译,提升性能。 - 排序字段
orderBy
用${}
直接拼接,但需在代码层严格校验允许的值,防止注入。
案例 2:批量插入性能优化
foreach
结合 JDBC 批处理能大幅提升效率。<insert id="batchInsertLogs">
INSERT INTO logs (event_type, content, operator)
VALUES
<foreach collection="logs" item="log" separator=",">
(#{log.eventType}, #{log.content}, #{log.operator})
foreach>
insert>
try (SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH)) {
LogMapper mapper = session.getMapper(LogMapper.class);
for (Log log : logs) {
mapper.batchInsertLogs(log);
}
session.commit();
}
- 单条插入:耗时约 100ms / 条。
- 批量插入(1000 条):耗时约 200ms,提升 500 倍!
? 常见问题与解决方案
问题 1:动态 SQL 导致的 SQL 注入风险
${}
直接拼接参数,未经过预编译。解决方案:
- 永远用
#{}
代替${}
,Mybatis 会自动转义参数。 - 对于排序字段等必须动态拼接的场景,在代码层校验允许的字段值。
ORDER BY ${orderBy}
ORDER BY
<if test="orderBy == 'price' or orderBy == 'create_time'">
${orderBy}
if>
问题 2:N+1
查询问题
解决方案:
- 使用
join
替代嵌套查询。 - 启用 Mybatis 的延迟加载(
lazyLoadingEnabled=true
),按需加载关联数据。
<resultMap id="OrderWithUserMap" type="Order">
<association property="user" column="user_id" select="getUser" fetchType="lazy"/>
resultMap>
问题 3:动态 SQL 可读性差
解决方案:
- 将复杂逻辑拆分成多个
sql
片段,通过include
组合。 - 使用注释说明每个标签的作用。
- 优先在 Java 层处理部分逻辑,减少 XML 中的条件判断。
问题 4:批量操作超时
解决方案:
- 分批处理数据,每次插入 1000 - 5000 条。
- 调整数据库连接参数(如
socketTimeout
)。 - 使用数据库批量插入语法(如 MySQL 的
LOAD DATA
)。
?️ 性能优化技巧
- 预编译 SQL:
Mybatis 会对#{}
参数进行预编译,避免重复解析 SQL,提升执行效率。
- 合理使用缓存:
- 一级缓存(默认开启):在同一个
SqlSession
内缓存查询结果。 - 二级缓存:跨
SqlSession
缓存,适合读多写少的场景。
xml<cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/>
- 一级缓存(默认开启):在同一个
- 批量操作优化:
- 启用 JDBC 批处理:在数据库 URL 中添加
rewriteBatchedStatements=true
。 - 配置 Mybatis 全局批量执行器:xml
<settings> <setting name="defaultExecutorType" value="BATCH"/> settings>
- 启用 JDBC 批处理:在数据库 URL 中添加
- 避免全表扫描:
- 确保动态条件字段有索引。
- 避免使用
%${value}%
这样的模糊查询,改用#{value}%
并创建前缀索引。
? 2025 最新工具与插件推荐
- MyBatis-Flex:
一个轻量级的 Mybatis 增强框架,提供更简洁的 API 和性能优化。支持动态 SQL、多表查询、分页等,代码生成器还能自动生成实体类和 Mapper。
官网:https://mybatis-flex.com/
- PageHelper:
分页插件,支持多种数据库,配置简单。2025 年新版本优化了与 Mybatis-Plus 的兼容性。
使用方式:javaPageHelper.startPage(pageNum, pageSize); List<User> list = userMapper.selectAll();
- SQL 染色插件:
京东云开发的插件,通过在 SQL 注释中添加标记(如statementId
),快速定位 SQL 来源,方便排查慢查询。
GitHub 地址:https://github.com/xxx/sql-dye-plugin
? 总结
- 多用
#{}
防注入,少用if
嵌套保可读性。 - 批量操作分批处理,复杂逻辑拆分成片段。
- 监控 SQL 执行,按需调整优化策略。