题目
MyBatis 中如何设计可复用的动态 SQL 片段以解决复杂条件查询的代码冗余问题?
信息
- 类型:问答
- 难度:⭐⭐⭐
考点
动态SQL复用, MyBatis高级特性, 代码优化
快速回答
核心解决方案是使用 <sql> 片段和 <include> 标签实现动态 SQL 的模块化设计:
- 定义可复用的
<sql>片段封装公共条件逻辑 - 通过
<include>引入片段并传递动态参数 - 结合 OGNL 表达式实现片段内部逻辑控制
- 使用
@Param注解处理多参数传递 - 通过继承机制构建片段层级体系
需警惕 SQL 注入风险和过度设计导致的维护复杂度上升。
解析
问题背景与痛点
在复杂业务系统中,多个查询往往包含相同的条件组合(如时间范围、状态过滤等)。若在每个 Mapper 中重复编写相同动态 SQL 逻辑,会导致:
- 代码冗余:相同逻辑在多处重复
- 维护困难:修改需同步所有副本
- 可读性下降:SQL 臃肿难以理解
解决方案:动态 SQL 复用技术
1. 基础实现 - SQL 片段
<!-- 定义可复用片段 -->
<sql id="timeRangeCondition">
<if test="startTime != null">
AND create_time >= #{startTime}
</if>
<if test="endTime != null">
AND create_time <= #{endTime}
</if>
</sql>
<!-- 在查询中引用 -->
<select id="selectOrders" resultType="Order">
SELECT * FROM orders
WHERE 1=1
<include refid="timeRangeCondition"/>
</select>
2. 进阶技巧 - 参数化片段
通过 <include> 的 property 传递参数:
<sql id="statusCondition">
<if test="${statusParam} != null">
AND status = #{${statusParam}}
</if>
</sql>
<select id="selectUsers" resultType="User">
SELECT * FROM users
<where>
<include refid="statusCondition">
<property name="statusParam" value="userStatus"/>
</include>
</where>
</select>
3. 复杂场景 - 多参数 OGNL 表达式
<sql id="permissionCheck">
<if test="@com.util.SecurityUtils@hasPermission(#role, 'VIEW')">
AND department_id = #{deptId}
</if>
</sql>
最佳实践
- 分层设计:建立基础片段库(BaseSqlFragments.xml)被其他 Mapper 继承
- 命名规范:使用
模块名.功能名(如order.timeRange)避免冲突 - 参数校验:在片段内增加空值检查,如
test="param != null and param != ''" - 性能优化:避免在循环内包含大型 SQL 片段
常见错误与规避
| 错误类型 | 示例 | 解决方案 |
|---|---|---|
| 参数覆盖 | 父子片段同名参数冲突 | 使用命名空间前缀(如 @vars.param) |
| SQL 注入 | test="${rawSql}" 直接拼接 |
禁止在 ${} 中使用用户输入参数 |
| 过度复用 | 将不相关逻辑强行合并 | 遵循单一职责原则,按功能拆分片段 |
扩展知识:继承体系设计
<!-- 基础片段文件 BaseFragments.xml -->
<mapper namespace="BaseFragments">
<sql id="coreTimeRange">...</sql>
</mapper>
<!-- 业务Mapper继承 -->
<mapper namespace="OrderMapper">
<!-- 重写扩展片段 -->
<sql id="timeRange">
<include refid="BaseFragments.coreTimeRange"/>
<if test="includeExtended"> ... </if>
</sql>
</mapper>
性能对比
复用方案 vs 传统方案:
- 代码量减少:平均减少 40% 重复 SQL 代码
- 解析效率:片段在启动时预编译,运行时无额外开销
- 执行计划:最终生成的 SQL 与硬编码方式完全一致
适用场景
- 多条件组合查询(如高级搜索)
- 多租户数据隔离
- 权限控制条件注入
- 审计字段自动填充