1. 使用SQL片段(SQL Fragments)
MyBatis示例:
<!-- 定义可重用的SQL片段 -->
<sql id="baseColumns">
id, name, email, create_time
</sql>
<sql id="userWhereCondition">
WHERE status = 'ACTIVE'
<if test="name != null">
AND name LIKE CONCAT('%', #{name}, '%')
</if>
</sql>
<!-- 引用SQL片段 -->
<select id="selectUsers" resultType="User">
SELECT
<include refid="baseColumns"/>
FROM users
<include refid="userWhereCondition"/>
</select>
<select id="selectUserCount" resultType="int">
SELECT COUNT(*)
FROM users
<include refid="userWhereCondition"/>
</select>
2. 使用动态SQL构建通用查询
<sql id="dynamicWhere">
<where>
<if test="id != null">AND id = #{id}</if>
<if test="name != null">AND name = #{name}</if>
<if test="status != null">AND status = #{status}</if>
<if test="startDate != null">AND create_time >= #{startDate}</if>
<if test="endDate != null">AND create_time <= #{endDate}</if>
</where>
</sql>
<!-- 多个查询复用同一个WHERE条件 -->
<select id="findByCondition" resultType="User">
SELECT * FROM users
<include refid="dynamicWhere"/>
ORDER BY create_time DESC
</select>
<select id="countByCondition" resultType="int">
SELECT COUNT(*) FROM users
<include refid="dynamicWhere"/>
</select>
3. 使用继承/模板模式
<!-- 基础查询模板 -->
<sql id="baseSelect">
SELECT
u.id,
u.username,
u.email,
d.department_name
FROM users u
LEFT JOIN department d ON u.dept_id = d.id
</sql>
<!-- 具体查询扩展基础模板 -->
<select id="getUserList" resultType="UserDTO">
<include refid="baseSelect"/>
WHERE u.status = 'ACTIVE'
ORDER BY u.create_time DESC
</select>
<select id="getUserById" resultType="UserDTO">
<include refid="baseSelect"/>
WHERE u.id = #{id}
</select>
4. 使用参数化排序和分页
<sql id="pagination">
<if test="orderBy != null">
ORDER BY ${orderBy}
<if test="orderDirection != null">
${orderDirection}
</if>
</if>
<if test="offset != null and limit != null">
LIMIT #{offset}, #{limit}
</if>
</sql>
<select id="findWithPagination" resultType="User">
SELECT * FROM users
WHERE status = 'ACTIVE'
<include refid="pagination"/>
</select>
5. 创建通用查询构建器(高级)
<!-- 定义查询组件 -->
<sql id="commonConditions">
<trim prefix="WHERE" prefixOverrides="AND |OR ">
<if test="filters != null">
<foreach collection="filters" item="filter" separator=" AND ">
${filter.column} ${filter.operator} #{filter.value}
</foreach>
</if>
</trim>
</sql>
<select id="dynamicQuery" resultType="map">
SELECT ${columns} FROM ${tableName}
<include refid="commonConditions"/>
</select>
6. 最佳实践建议
优点:
- ✅ 减少代码重复,提高维护性
- ✅ 统一修改,一处修改全局生效
- ✅ 提高可读性,复杂的SQL逻辑被抽象
注意事项:
- ⚠️ 不要过度抽象,保持SQL的可读性
- ⚠️ 避免在片段中使用过于复杂的逻辑
- ⚠️ 注意SQL片件的顺序依赖问题
- ⚠️ 考虑数据库兼容性(如分页语法差异)
推荐结构:
<!-- 1. 列定义片段 -->
<sql id="baseColumns">...</sql>
<!-- 2. 关联表片段 -->
<sql id="baseJoins">...</sql>
<!-- 3. 通用条件片段 -->
<sql id="baseConditions">...</sql>
<!-- 4. 排序分页片段 -->
<sql id="baseOrderAndPage">...</sql>
<!-- 具体查询组合使用 -->
<select id="query" resultType="...">
SELECT <include refid="baseColumns"/>
FROM main_table
<include refid="baseJoins"/>
<include refid="baseConditions"/>
<include refid="baseOrderAndPage"/>
</select>
7. 替代方案:注解方式
如果XML中仍然有大量重复,可以考虑:
// 使用MyBatis注解
@SelectProvider(type = UserSqlBuilder.class, method = "buildSelectSql")
List<User> selectUsers(UserQuery query);
// 构建通用SQL
public class UserSqlBuilder {
public static String buildSelectSql(UserQuery query) {
return new SQL()
.SELECT("*")
.FROM("users")
.WHERE("status = 'ACTIVE'")
.WHERE(buildConditions(query))
.toString();
}
private static String buildConditions(UserQuery query) {
// 动态构建条件
}
}
选择哪种方法取决于:
SQL的复杂程度
团队的熟悉程度
项目的具体需求
维护的便利性
建议:对于中等复杂度的项目,使用SQL片段是最平衡的选择;对于复杂查询逻辑,可以考虑使用查询构建器模式。