MyBatis动态SQL标签用法详解:从入门到实战
目录导读
- 什么是MyBatis动态SQL?
- 核心标签详解
<if>:条件判断<choose>、<when>、<otherwise>:多分支选择<where>:智能条件拼接<set>:动态更新字段<trim>:自定义前缀/后缀<foreach>:集合遍历
- 常见问题与解答(Q&A)
- 性能优化与最佳实践
- 实战示例:电商订单查询
什么是MyBatis动态SQL?
MyBatis是一款优秀的持久层框架,其动态SQL特性允许开发者根据运行时条件动态拼接SQL语句,无需在Java代码中手动处理大量字符串拼接,这避免了SQL注入风险,提高了代码可维护性。

动态SQL通过XML映射文件中的标签实现,核心思想是:根据传入的参数值,决定SQL语句的组成部分。
核心标签详解
1 <if>标签:条件判断
用途:当某条件成立时,拼接对应的SQL片段。
<select id="findUsers" parameterType="map" resultType="User">
SELECT * FROM users
WHERE 1=1
<if test="name != null and name != ''">
AND name LIKE CONCAT('%', #{name}, '%')
</if>
<if test="status != null">
AND status = #{status}
</if>
</select>
注意: 类似Java中的 自动处理WHERE关键字,并过滤掉多余的 优点:无需手动写 用于UPDATE语句,自动处理SET关键字并去掉多余的逗号。 注意: 提供更灵活的前缀、后缀处理,可以替代 示例:模拟 用于IN条件或批量操作,支持List、Array、Map。 参数说明: Q1: A:OGNL语法中,字符串相等用,不等用,判断非空应写为: Q2: A:当参数是Map时, Q3:动态SQL会导致SQL缓存失效吗? A:是的!MyBatis的一级缓存(SqlSession级别)和二级缓存(Mapper级别)都基于SQL语句的完整字符串作为缓存key,动态SQL生成不同语句后,每次都会产生新key,导致缓存无法命中。建议对高频查询不使用动态SQL,或考虑使用其他缓存方案(如Redis)。 Q4: A:解析性能几乎无差异。 动态SQL虽灵活,但应以可读性优先,当条件分支超过3层,建议拆分为多个独立查询方法。 使用 假设需要根据用户输入灵活查询订单: 需求: 接口定义: XML实现: 测试用例: MyBatis动态SQL标签是连接Java代码与数据库的智能桥梁,通过 掌握这些标签,能显著提升数据访问层的开发效率和代码质量,是每位Java开发者的必备技能。
test属性中支持OGNL表达式,可进行逻辑运算(and、or、==、!=等)。先写WHERE 1=1是为了避免所有条件都不满足时SQL语法错误,但更推荐使用<where>
2
<choose>、<when>、<otherwise>:多分支switch-case,按顺序匹配第一个为真的<when>,否则执行<otherwise>。<select id="findByCondition" resultType="Order">
SELECT * FROM orders
<where>
<choose>
<when test="startDate != null and endDate != null">
AND create_time BETWEEN #{startDate} AND #{endDate}
</when>
<when test="startDate != null">
AND create_time >= #{startDate}
</when>
<otherwise>
AND create_time >= CURDATE()
</otherwise>
</choose>
</where>
</select>
3
<where>标签:智能条件管理AND或OR。<select id="searchProducts" resultType="Product">
SELECT * FROM products
<where>
<if test="category != null">
AND category_id = #{category}
</if>
<if test="minPrice != null">
AND price >= #{minPrice}
</if>
<if test="maxPrice != null">
AND price <= #{maxPrice}
</if>
</where>
</select>
WHERE 1=1,当所有条件都不满足时,不会生成WHERE子句。
4
<set>标签:动态更新<update id="updateUser" parameterType="User">
UPDATE users
<set>
<if test="name != null">name = #{name},</if>
<if test="email != null">email = #{email},</if>
<if test="status != null">status = #{status},</if>
</set>
WHERE id = #{id}
</update>
<set>会自动去除最后一个逗号,避免SQL语法错误。
5
<trim>标签:自定义拼接规则<where>和<set>。
属性
说明
prefix
在拼接的内容前添加前缀
suffix
在拼接的内容后添加后缀
prefixOverrides
忽略前部指定的字符
suffixOverrides
忽略后部指定的字符
<where>逻辑:<trim prefix="WHERE" prefixOverrides="AND |OR ">
<if test="name != null">AND name = #{name}</if>
<if test="age != null">AND age = #{age}</if>
</trim>
6
<foreach>标签:集合遍历<!-- IN查询 -->
<select id="findByIds" resultType="User">
SELECT * FROM users
WHERE id IN
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</select>
<!-- 批量插入 -->
<insert id="batchInsert">
INSERT INTO users (name, email)
VALUES
<foreach collection="list" item="user" separator=",">
(#{user.name}, #{user.email})
</foreach>
</insert>
collection:传入的集合/数组变量名(必填)item:循环中的当前元素别名open:起始符号(如)close:结束符号(如)separator:元素分隔符(如)index:可选,当前索引(Map时为key)
常见问题与解答(Q&A)
<if>标签中test判断字符串空时,为什么用而不是?name != null and name != '',注意null和空字符串是不同的。<foreach>遍历时,collection的值在什么情况下必须改?collection必须指定为Map的key名;如果传入的是单个List,默认值为list;数组默认值为array,建议在接口参数上使用@Param注解显式指定名称。<where>和<trim>哪个性能更好?<where>更语义化,推荐用于条件查询;<trim>更底层,适合需自定义前后缀的场景,选择原则:优先<where>和<set>,不足时用<trim>补充。
性能优化与最佳实践
1 避免过度动态化
2 使用
<sql>抽取代码片段<sql id="userColumns">id, name, email, status</sql>
<select id="getUser" resultType="User">
SELECT <include refid="userColumns"/> FROM users WHERE id=#{id}
</select>
3 预防SQL注入
#{value}参数占位符(预编译),避免使用${value}(直接拼接),除非是表名、字段名等元数据。4 批量操作建议
<foreach>配合MySQL批处理(url后加allowMultiQueries=true)
实战示例:电商订单查询
List<Order> searchOrders(@Param("condition") OrderSearchCondition condition);
<select id="searchOrders" resultType="Order">
SELECT * FROM orders
<where>
<if test="condition.orderId != null and condition.orderId != ''">
AND order_id = #{condition.orderId}
</if>
<if test="condition.status != null">
AND status = #{condition.status}
</if>
<if test="condition.startDate != null and condition.endDate != null">
AND create_time BETWEEN #{condition.startDate} AND #{condition.endDate}
</if>
<choose>
<when test="condition.minAmount != null and condition.maxAmount != null">
AND amount BETWEEN #{condition.minAmount} AND #{condition.maxAmount}
</when>
<when test="condition.minAmount != null">
AND amount >= #{condition.minAmount}
</when>
</choose>
</where>
ORDER BY create_time DESC
LIMIT #{condition.offset}, #{condition.limit}
</select>
status=1 → 生成:WHERE status = 1orderId=ORD001和startDate → 生成:WHERE order_id = ? AND create_time >= ?
<if>、<choose>、<where>、<set>、<foreach>等标签的组合,开发者可以构建高度灵活且安全的SQL,核心要点:
<where>和<set>简化代码<foreach>注意collection命名