MyBatis 动态SQL学习

MyBatis 动态SQL详解

MyBatis 的动态SQL是其核心特性之一,允许基于条件动态构建SQL语句,避免了大量重复的SQL代码。

一、核心动态SQL标签

1. <if> 标签 - 条件判断

xml 复制代码
<select id="findUsers" resultType="User">
  SELECT * FROM users 
  WHERE 1=1
  <if test="name != null and name != ''">
    AND name = #{name}
  </if>
  <if test="age != null">
    AND age = #{age}
  </if>
  <if test="email != null">
    AND email LIKE CONCAT('%', #{email}, '%')
  </if>
</select>

2. <choose>, <when>, <otherwise> - 多条件选择

xml 复制代码
<select id="findActiveUsers" resultType="User">
  SELECT * FROM users 
  WHERE 
  <choose>
    <when test="status == 'active'">
      status = 'ACTIVE'
    </when>
    <when test="status == 'inactive'">
      status = 'INACTIVE'
    </when>
    <otherwise>
      status = 'PENDING'
    </otherwise>
  </choose>
</select>

3. <where> 标签 - 智能处理WHERE子句

xml 复制代码
<select id="searchUsers" resultType="User">
  SELECT * FROM users 
  <where>
    <if test="name != null">
      AND name = #{name}
    </if>
    <if test="age != null">
      AND age = #{age}
    </if>
    <if test="email != null">
      AND email LIKE #{email}
    </if>
  </where>
  ORDER BY id
</select>

特点:自动删除第一个多余的AND或OR,如果所有条件都不满足,则不生成WHERE子句。

4. <set> 标签 - 更新语句

xml 复制代码
<update id="updateUser">
  UPDATE users
  <set>
    <if test="name != null">name = #{name},</if>
    <if test="age != null">age = #{age},</if>
    <if test="email != null">email = #{email},</if>
    <if test="updateTime != null">update_time = #{updateTime},</if>
  </set>
  WHERE id = #{id}
</update>

特点:自动删除末尾多余的逗号。

5. <trim> 标签 - 自定义截取

xml 复制代码
<!-- 替代<where> -->
<select id="findUsers" resultType="User">
  SELECT * FROM users 
  <trim prefix="WHERE" prefixOverrides="AND |OR ">
    <if test="name != null">AND name = #{name}</if>
    <if test="age != null">OR age = #{age}</if>
  </trim>
</select>

<!-- 替代<set> -->
<update id="updateUser">
  UPDATE users
  <trim prefix="SET" suffixOverrides=",">
    <if test="name != null">name = #{name},</if>
    <if test="age != null">age = #{age},</if>
  </trim>
  WHERE id = #{id}
</update>

6. <foreach> 标签 - 循环遍历

xml 复制代码
<!-- IN查询 -->
<select id="findUsersByIds" resultType="User">
  SELECT * FROM users 
  WHERE id IN 
  <foreach item="id" index="index" collection="ids"
      open="(" separator="," close=")">
    #{id}
  </foreach>
</select>

<!-- 批量插入 -->
<insert id="batchInsertUsers">
  INSERT INTO users (name, age, email) VALUES 
  <foreach item="user" collection="list" separator=",">
    (#{user.name}, #{user.age}, #{user.email})
  </foreach>
</insert>

<!-- 批量更新(MySQL) -->
<update id="batchUpdateUsers">
  <foreach item="user" collection="list" separator=";">
    UPDATE users 
    SET name = #{user.name}, age = #{user.age}
    WHERE id = #{user.id}
  </foreach>
</update>

7. <bind> 标签 - 变量绑定

xml 复制代码
<select id="searchUsers" resultType="User">
  <!-- 创建变量并在OGNL表达式中使用 -->
  <bind name="pattern" value="'%' + name + '%'" />
  <bind name="emailPattern" value="email + '%'" />
  
  SELECT * FROM users 
  WHERE 
    name LIKE #{pattern}
    AND email LIKE #{emailPattern}
</select>

二、OGNL表达式

MyBatis动态SQL中使用的表达式语言:

xml 复制代码
<!-- 复杂条件判断 -->
<if test="(name != null and name != '') or (age != null and age > 0)">
  AND status = 'ACTIVE'
</if>

<!-- 调用Java方法 -->
<if test="@com.example.StringUtils@isNotEmpty(email)">
  AND email = #{email}
</if>

<!-- 集合判断 -->
<if test="ids != null and ids.size() > 0">
  AND id IN 
  <foreach collection="ids" item="id" open="(" close=")" separator=",">
    #{id}
  </foreach>
</if>

三、实战应用示例

1. 复杂搜索查询

xml 复制代码
<select id="searchWithPagination" resultType="User">
  SELECT * FROM users 
  <where>
    <!-- 姓名模糊搜索 -->
    <if test="name != null and name != ''">
      <bind name="namePattern" value="'%' + name + '%'" />
      AND (first_name LIKE #{namePattern} OR last_name LIKE #{namePattern})
    </if>
    
    <!-- 年龄范围 -->
    <if test="minAge != null">
      AND age >= #{minAge}
    </if>
    <if test="maxAge != null">
      AND age &lt;= #{maxAge}
    </if>
    
    <!-- 状态多选 -->
    <if test="statusList != null and statusList.size() > 0">
      AND status IN 
      <foreach collection="statusList" item="status" open="(" close=")" separator=",">
        #{status}
      </foreach>
    </if>
    
    <!-- 注册时间范围 -->
    <if test="startDate != null and endDate != null">
      AND register_time BETWEEN #{startDate} AND #{endDate}
    </if>
  </where>
  
  <!-- 动态排序 -->
  <choose>
    <when test="orderBy != null and orderBy != ''">
      ORDER BY ${orderBy}
      <if test="orderDirection != null and orderDirection != ''">
        ${orderDirection}
      </if>
    </when>
    <otherwise>
      ORDER BY id DESC
    </otherwise>
  </choose>
  
  <!-- 分页(MySQL) -->
  <if test="offset != null and limit != null">
    LIMIT #{offset}, #{limit}
  </if>
</select>

2. 批量操作优化

xml 复制代码
<!-- 批量插入(MySQL的ON DUPLICATE KEY UPDATE) -->
<insert id="batchInsertOrUpdate">
  INSERT INTO products (id, name, price, stock) VALUES 
  <foreach item="product" collection="list" separator=",">
    (#{product.id}, #{product.name}, #{product.price}, #{product.stock})
  </foreach>
  ON DUPLICATE KEY UPDATE
  name = VALUES(name),
  price = VALUES(price),
  stock = stock + VALUES(stock)
</insert>

<!-- 批量删除 -->
<delete id="batchDelete">
  DELETE FROM users 
  WHERE id IN 
  <foreach item="id" collection="ids" open="(" close=")" separator=",">
    #{id}
  </foreach>
  AND status = 'INACTIVE'
</delete>

3. 动态表名和列名

xml 复制代码
<sql id="dynamicTable">
  <!-- 根据年份分表 -->
  <choose>
    <when test="year == 2023">orders_2023</when>
    <when test="year == 2024">orders_2024</when>
    <otherwise>orders</otherwise>
  </choose>
</sql>

<select id="findOrdersByYear" resultType="Order">
  SELECT * FROM 
  <include refid="dynamicTable"/>
  WHERE user_id = #{userId}
</select>

四、注解方式动态SQL

java 复制代码
// 使用@SelectProvider注解
@SelectProvider(type = UserSqlProvider.class, method = "buildSearchSql")
List<User> searchUsers(@Param("name") String name, 
                       @Param("status") String status);

// SQL提供者类
public class UserSqlProvider {
    public String buildSearchSql(Map<String, Object> params) {
        return new SQL() {{
            SELECT("*");
            FROM("users");
            
            if (params.get("name") != null) {
                WHERE("name = #{name}");
            }
            
            if (params.get("status") != null) {
                WHERE("status = #{status}");
            }
            
            ORDER_BY("id DESC");
        }}.toString();
    }
}

// 或者使用字符串拼接方式
public class UserSqlProvider {
    public String buildDynamicSql(Map<String, Object> params) {
        String sql = "SELECT * FROM users WHERE 1=1";
        
        if (params.get("name") != null) {
            sql += " AND name = #{name}";
        }
        
        if (params.get("minAge") != null) {
            sql += " AND age >= #{minAge}";
        }
        
        return sql;
    }
}

五、高级技巧

1. 使用<script>包裹动态SQL

xml 复制代码
<!-- 在注解中使用动态SQL -->
@Update("<script>" +
        "UPDATE users " +
        "<set>" +
        "  <if test='name != null'>name = #{name},</if>" +
        "  <if test='age != null'>age = #{age},</if>" +
        "</set>" +
        "WHERE id = #{id}" +
        "</script>")
void updateUserSelective(User user);

2. 动态include SQL片段

xml 复制代码
<!-- 定义可重用的SQL片段 -->
<sql id="userColumns">
  id, name, age, email, 
  <if test="includeSensitive == true">
    phone, address
  </if>
</sql>

<!-- 使用 -->
<select id="getUser" resultType="map">
  SELECT 
  <include refid="userColumns">
    <property name="includeSensitive" value="true"/>
  </include>
  FROM users WHERE id = #{id}
</select>

3. 条件中的类型处理

xml 复制代码
<select id="findByConditions" resultType="User">
  SELECT * FROM users
  <where>
    <!-- 字符串判断 -->
    <if test='name != null and name != ""'>
      AND name = #{name}
    </if>
    
    <!-- 数字判断 -->
    <if test="age != null">
      AND age = #{age}
    </if>
    
    <!-- 布尔判断 -->
    <if test="active != null">
      AND is_active = #{active}
    </if>
    
    <!-- 集合/数组判断 -->
    <if test="ids != null and ids.length > 0">
      AND id IN 
      <foreach collection="ids" item="id" open="(" close=")" separator=",">
        #{id}
      </foreach>
    </if>
  </where>
</select>

六、性能优化建议

  1. 避免过度使用动态SQL:简单的条件判断可使用数据库函数
  2. 合理使用索引:确保动态生成的SQL能利用索引
  3. 分页优化:大数据量时使用延迟加载
  4. 缓存策略:合理配置MyBatis二级缓存
  5. 批量操作 :使用<foreach>进行批量操作,减少数据库交互

七、常见问题解决

1. 参数为0时判断失效

xml 复制代码
<!-- 错误 -->
<if test="status != null">
  <!-- 当status=0时,条件不生效 -->
</if>

<!-- 正确 -->
<if test="status != null or status == 0">
  AND status = #{status}
</if>

2. 特殊字符转义

xml 复制代码
<!-- 使用CDATA或转义 -->
<if test="type != null and type != ''">
  AND type = #{type}
  <!-- 小于号要转义 -->
  AND age &lt; 18
</if>

<!-- 或者使用CDATA -->
<if test="value != null">
  AND amount <![CDATA[ < ]]> #{maxValue}
</if>

3. 动态排序安全问题

xml 复制代码
<!-- 不安全:直接使用${} -->
ORDER BY ${orderBy}

<!-- 安全方案:白名单验证 -->
ORDER BY 
<choose>
  <when test="orderBy == 'name'">name</when>
  <when test="orderBy == 'age'">age</when>
  <when test="orderBy == 'create_time'">create_time</when>
  <otherwise>id</otherwise>
</choose>

总结

MyBatis动态SQL提供了强大而灵活的SQL构建能力,通过合理使用各种标签,可以:

  • 减少大量重复SQL代码
  • 提高代码可维护性
  • 实现复杂的业务查询逻辑
  • 保证SQL注入安全(正确使用时)

关键是要掌握各种标签的使用场景和组合方式,同时注意性能优化和安全问题。

相关推荐
子非鱼9211 小时前
SpringBoot快速上手
java·spring boot·后端
我爱娃哈哈2 小时前
SpringBoot + XXL-JOB + Quartz:任务调度双引擎选型与高可用调度平台搭建
java·spring boot·后端
JavaGuide2 小时前
Maven 4 终于快来了,新特性很香!
后端·maven
开心就好20252 小时前
全面解析iOS应用代码混淆和加密加固方法与实践注意事项
后端
Thomas游戏开发2 小时前
分享一个好玩的:一次提示词让AI同时开发双引擎框架
前端·javascript·后端
龙门吹雪2 小时前
GO 语言处理多个布尔选项的实现方案
开发语言·后端·golang·布尔选项·标识位
毕设源码-钟学长2 小时前
【开题答辩全过程】以 基于Springboot vue肢体残疾人就业服务网站的设计与实现为例,包含答辩的问题和答案
java·spring boot·后端
源代码•宸3 小时前
Golang原理剖析(map面试与分析)
开发语言·后端·算法·面试·职场和发展·golang·map