侧边栏壁纸
博主头像
colo

欲买桂花同载酒

  • 累计撰写 1823 篇文章
  • 累计收到 0 条评论

MyBatis 中如何设计可复用的动态 SQL 片段以解决复杂条件查询的代码冗余问题?

2025-12-13 / 0 评论 / 4 阅读

题目

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 与硬编码方式完全一致

适用场景

  • 多条件组合查询(如高级搜索)
  • 多租户数据隔离
  • 权限控制条件注入
  • 审计字段自动填充