MyBatis动态SQL标签用法

wen java案例 3

MyBatis动态SQL标签用法详解:从入门到实战

目录导读

  1. 什么是MyBatis动态SQL?
  2. 核心标签详解
    • <if>:条件判断
    • <choose><when><otherwise>:多分支选择
    • <where>:智能条件拼接
    • <set>:动态更新字段
    • <trim>:自定义前缀/后缀
    • <foreach>:集合遍历
  3. 常见问题与解答(Q&A)
  4. 性能优化与最佳实践
  5. 实战示例:电商订单查询

什么是MyBatis动态SQL?

MyBatis是一款优秀的持久层框架,其动态SQL特性允许开发者根据运行时条件动态拼接SQL语句,无需在Java代码中手动处理大量字符串拼接,这避免了SQL注入风险,提高了代码可维护性。

MyBatis动态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>

注意test属性中支持OGNL表达式,可进行逻辑运算(and、or、==、!=等)。先写WHERE 1=1是为了避免所有条件都不满足时SQL语法错误,但更推荐使用<where>


2 <choose><when><otherwise>:多分支

类似Java中的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>标签:智能条件管理

自动处理WHERE关键字,并过滤掉多余的ANDOR

<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 &lt;= #{maxPrice}
        </if>
    </where>
</select>

优点:无需手动写WHERE 1=1,当所有条件都不满足时,不会生成WHERE子句。


4 <set>标签:动态更新

用于UPDATE语句,自动处理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条件或批量操作,支持List、Array、Map。

<!-- 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)

Q1:<if>标签中test判断字符串空时,为什么用而不是?

A:OGNL语法中,字符串相等用,不等用,判断非空应写为:name != null and name != '',注意null和空字符串是不同的。

Q2:<foreach>遍历时,collection的值在什么情况下必须改?

A:当参数是Map时,collection必须指定为Map的key名;如果传入的是单个List,默认值为list;数组默认值为array,建议在接口参数上使用@Param注解显式指定名称。

Q3:动态SQL会导致SQL缓存失效吗?

A:是的!MyBatis的一级缓存(SqlSession级别)和二级缓存(Mapper级别)都基于SQL语句的完整字符串作为缓存key,动态SQL生成不同语句后,每次都会产生新key,导致缓存无法命中。建议对高频查询不使用动态SQL,或考虑使用其他缓存方案(如Redis)。

Q4:<where><trim>哪个性能更好?

A:解析性能几乎无差异。<where>更语义化,推荐用于条件查询;<trim>更底层,适合需自定义前后缀的场景,选择原则:优先<where><set>,不足时用<trim>补充


性能优化与最佳实践

1 避免过度动态化

动态SQL虽灵活,但应以可读性优先,当条件分支超过3层,建议拆分为多个独立查询方法。

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
  • 批量更新:建议使用逐条更新+事务,或使用CASE WHEN语法

实战示例:电商订单查询

假设需要根据用户输入灵活查询订单:

需求

  • 按订单号精确查询(可选)
  • 按状态(可选)
  • 按日期范围(可选)
  • 按金额范围(可选)
  • 翻页(必填)

接口定义

List<Order> searchOrders(@Param("condition") OrderSearchCondition condition);

XML实现

<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 = 1
  • 传入orderId=ORD001startDate → 生成:WHERE order_id = ? AND create_time >= ?

MyBatis动态SQL标签是连接Java代码与数据库的智能桥梁,通过<if><choose><where><set><foreach>等标签的组合,开发者可以构建高度灵活且安全的SQL,核心要点:

  • 使用防注入
  • 优先<where><set>简化代码
  • <foreach>注意collection命名
  • 动态SQL与缓存策略需权衡

掌握这些标签,能显著提升数据访问层的开发效率和代码质量,是每位Java开发者的必备技能。

抱歉,评论功能暂时关闭!