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注入安全(正确使用时)

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

相关推荐
IT_陈寒40 分钟前
闭包陷阱让我加了两天班,JavaScript你真行
前端·人工智能·后端
易协同低代码2 小时前
通达OA核心类库TD类深度解析
后端
Gopher_HBo2 小时前
Go语言学习笔记(十八)Gin处理Session
后端
谭光志3 小时前
工具塞满上下文窗口怎么办?深度拆解 AI Agent Tool Search 按需加载实现原理
前端·后端·ai编程
她说..3 小时前
Java 默认值设置方式
java·开发语言·后端·springboot
foggyprojects3 小时前
从0开始,一句话启动AI DataAgent
后端·数据分析·ai编程
郡杰3 小时前
一些基础和问题解决
后端
陈随易3 小时前
前端项目部署只要30秒
前端·后端·程序员
YIAN3 小时前
从零手写文件读取 MCP 服务:一文吃透 Model Context Protocol 全链路通信原理
前端·后端·mcp
Imchendiana3 小时前
《狂人日记NO.9》— 前后端一把梭,我的全栈实录
前端·后端