在 MyBatis 的动态 SQL 中处理复杂逻辑判断时,可以结合多种标签(如 <if>
、<choose>
、<when>
、<otherwise>
、<trim>
、<where>
、<set>
等)以及 OGNL 表达式来实现。以下是常见场景的解决方案:
1. 多条件组合查询(AND/OR)
场景:根据不同参数组合查询,例如用户可能只输入部分查询条件。
解决方案:使用 <where>
和 <if>
标签组合。
<select id="findUsers" parameterType="map" resultType="User">
SELECT * FROM users
<where>
<if test="username != null and username != ''">
AND username = #{username}
</if>
<if test="age != null">
AND age >= #{age}
</if>
<if test="role != null">
AND role = #{role}
</if>
</where>
</select>
<where>
标签会自动处理第一个条件前的AND
/OR
。- 使用 OGNL 表达式(如
test="username != null and username != ''"
)避免空值或空字符串。
2. 多条件分支判断(类似 switch-case)
场景:根据某个参数的值执行不同的 SQL 逻辑。
解决方案:使用 <choose>
、<when>
、<otherwise>
标签。
<select id="getUserByCondition" parameterType="map" resultType="User">
SELECT * FROM users
WHERE 1=1
<choose>
<when test="type == 'admin'">
AND role = 'ADMIN'
</when>
<when test="type == 'vip'">
AND vip_level >= 3
</when>
<otherwise>
AND status = 'ACTIVE'
</otherwise>
</choose>
</select>
<choose>
类似 Java 的switch
,按顺序匹配第一个满足条件的<when>
。<otherwise>
相当于default
分支。
3. 动态更新部分字段
场景:只更新实体中不为空的字段(类似前文的需求)。
解决方案:使用 <set>
和 <if>
标签。
<update id="updateUser" parameterType="User">
UPDATE users
<set>
<if test="username != null">username = #{username},</if>
<if test="age != null">age = #{age},</if>
<if test="email != null">email = #{email},</if>
<if test="status != null">status = #{status}</if>
</set>
WHERE id = #{id}
</update>
<set>
标签会自动处理SET
关键字和多余的逗号。
4. 集合遍历(IN 条件)
场景:根据多个值查询(如 WHERE id IN (1, 2, 3)
)。
解决方案:使用 <foreach>
标签。
<select id="findUsersByIds" parameterType="list" resultType="User">
SELECT * FROM users
WHERE id IN
<foreach item="id" collection="list" open="(" separator="," close=")">
#{id}
</foreach>
</select>
collection
:指定集合类型(如list
、array
、map
)。item
:遍历的元素别名。open
/close
/separator
:定义括号和分隔符。
5. 复杂条件组合(嵌套逻辑)
场景:多组条件之间需要 AND/OR 组合。
解决方案:嵌套使用动态标签,结合 <trim>
自定义前缀和后缀。
<select id="findComplexUsers" parameterType="map" resultType="User">
SELECT * FROM users
WHERE 1=1
<trim prefix="AND (" prefixOverrides="AND |OR " suffix=")">
<if test="status != null">
status = #{status}
</if>
<if test="age != null and age > 18">
AND age > 18
</if>
<if test="roles != null and roles.size() > 0">
AND role IN
<foreach item="role" collection="roles" open="(" separator="," close=")">
#{role}
</foreach>
</if>
</trim>
</select>
<trim>
标签用于自定义 SQL 片段的前缀、后缀和过滤规则。prefixOverrides
移除多余的AND
或OR
。
6. 自定义条件表达式(OGNL)
场景:需要更复杂的条件判断(如比较两个参数的值)。
解决方案:使用 OGNL 表达式。
<select id="findUsersByRange" parameterType="map" resultType="User">
SELECT * FROM users
WHERE 1=1
<if test="startAge != null and endAge != null and startAge <= endAge">
AND age BETWEEN #{startAge} AND #{endAge}
</if>
</select>
- OGNL 支持方法调用、属性访问、算术运算等(如
list.size() > 0
)。 - 注意转义特殊字符(如
<
需写成<
)。
7. 动态表名或列名
场景:根据参数选择不同的表或列。
解决方案:使用 <bind>
标签预处理参数(避免 SQL 注入)。
<select id="findByTable" parameterType="map" resultType="map">
<bind name="safeTable" value="@org.apache.ibatis.session.Configuration@
.getSafeTableName(param.tableName)" />
SELECT * FROM ${safeTable}
WHERE status = #{status}
</select>
- 注意:动态表名 / 列名必须通过代码严格校验,避免 SQL 注入!
最佳实践
- 保持 SQL 可读性:复杂逻辑可拆分为多个
<sql>
片段复用。 - 参数校验:在 Java 代码中先处理参数逻辑,避免 SQL 过于复杂。
- 使用
<sql>
标签:提取公共 SQL 片段。
<sql id="userColumns">
id, username, age, email, status
</sql>
<select id="findUser" parameterType="int" resultType="User">
SELECT <include refid="userColumns" /> FROM users
WHERE id = #{id}
</select>
避免过度动态化:如果逻辑过于复杂,考虑在 Java 代码中处理后再传入参数。
总结
MyBatis 的动态 SQL 通过组合标签和 OGNL 表达式,可以灵活处理各种复杂逻辑。关键是理解各标签的用途(如 <where>
、<set>
、<trim>
)和 OGNL 的语法,同时注意代码的可读性和安全性。