MyBatis动态SQL深度解析与电商三表联查实战

232 阅读6分钟

MyBatis动态SQL全标签深度解析

一、动态SQL核心标签功能详解

1. <if> 条件分支

功能作用:根据表达式结果动态拼接SQL片段
核心价值:实现条件过滤,避免全表扫描
使用场景:商品搜索、订单筛选等条件查询

<if test="price != null">
    AND price > #{price}
</if>

拼接原理

  • test表达式为true时,拼接包含内容
  • 自动处理前后空格,无需手动添加空格
  • 常用于WHERE/UPDATE/SET等语句块内部

2. <choose> 多路选择

功能作用:实现类似Java的switch-case逻辑
核心价值:处理互斥条件,优化执行计划
使用场景:订单状态流转、优先级条件判断

<choose>
    <when test="status == 1">status = 1</when>
    <when test="status == 2">status = 2 AND is_paid = 1</when>
    <otherwise>status IS NOT NULL</otherwise>
</choose>

拼接特点

  • 按顺序执行第一个满足条件的<when>
  • <otherwise>作为默认分支
  • 保证只有一个分支生效

3. <foreach> 集合遍历

功能作用:迭代处理集合类型参数
核心价值:构建IN查询、批量操作语句
使用场景:批量删除商品、多ID查询订单

<foreach item="id" collection="ids" 
         open="AND id IN (" close=")" separator=",">
    #{id}
</foreach>

关键属性

  • collection:参数来源(List/Array/Map.key)
  • item:迭代元素别名
  • open/close:循环包装符
  • separator:元素分隔符

4. <where> 智能条件

功能作用:动态生成WHERE子句
核心价值:自动处理前缀AND/OR,避免语法错误
使用场景:动态组合多个查询条件

<where>
    <if test="name != null">name = #{name}</if>
    <if test="category != null">AND category = #{category}</if>
</where>

智能特性

  • 自动去除首个条件前的AND/OR
  • 当所有条件不满足时,不生成WHERE关键字
  • <if>配合使用率达90%

5. <set> 更新优化

功能作用:动态生成UPDATE语句的SET部分
核心价值:避免更新全字段,提升执行效率
使用场景:商品信息部分更新

<set>
    <if test="name != null">name = #{name},</if>
    <if test="price != null">price = #{price}</if>
</set>

特殊处理

  • 自动去除末尾多余的逗号
  • 至少有一个条件成立时才生成SET
  • 配合<if>实现精准字段更新

6. <trim> 自定义修剪

功能作用:精细化控制SQL片段格式
核心价值:处理复杂的前后缀问题
使用场景:特殊格式的WHERE/UPDATE语句

<trim prefix="WHERE" prefixOverrides="AND|OR">
    <if test="stock != null">AND stock > #{stock}</if>
</trim>

核心参数

  • prefix:添加前缀
  • suffix:添加后缀
  • prefixOverrides:去除前缀
  • suffixOverrides:去除后缀

7. <bind> 变量绑定

功能作用:创建OGNL表达式变量
核心价值:解决不同数据库函数差异
使用场景:模糊查询兼容多数据库

<bind name="pattern" value="'%' + keyword + '%'" />
SELECT * FROM products 
WHERE name LIKE #{pattern}

优势特点

  • 避免SQL注入风险
  • 支持复杂表达式计算
  • 提升SQL可读性

二、标签对比决策矩阵

标签最佳场景参数类型典型示例
<if>简单条件判断任意标量类型价格过滤、状态筛选
<choose>互斥条件选择枚举值订单状态阶梯判断
<foreach>集合遍历操作List/Array/Map批量删除、IN查询
<where>动态组合多个条件多条件对象复杂查询表单
<set>动态字段更新部分更新对象用户信息部分修改
<trim>复杂格式控制需要精确控制特殊前缀处理
<bind>数据库兼容处理需要计算的值多数据库模糊查询兼容

三、实战技巧精要

1. 条件表达式优化

<!-- 安全写法:先判空再取值 -->
<if test="user != null and user.age != null">
    AND age = #{user.age}
</if>

<!-- 集合判空最佳实践 -->
<if test="ids != null and !ids.isEmpty()">
    AND id IN (...)
</if>

2. 复杂参数处理

// 混合参数示例
List<Product> searchProducts(
    @Param("query") ProductQuery query,
    @Param("ext") Map<String, Object> extParams
);

对应XML处理:

<if test="query.minPrice != null">
    AND price >= #{query.minPrice}
</if>

<if test="ext.containsKey('specialFlag')">
    AND is_special = #{ext.specialFlag}
</if>

3. 性能优化要点

  1. 避免过度动态:保持执行计划稳定性
  2. 优先使用WHERE:替代复杂的trim配置
  3. 批量操作优化:foreach的batch模式
    <insert id="batchInsert">
        INSERT INTO products(name, price) VALUES
        <foreach item="item" collection="list" separator=",">
            (#{item.name}, #{item.price})
        </foreach>
    </insert>
    

四、常见陷阱规避

1. 空值处理陷阱

错误示例

<if test="name != ''">  <!-- 无法处理name=null的情况 -->

正确写法

<if test="name != null and name != ''">

2. 类型转换问题

错误日志

There is no getter for property 'status' in 'java.lang.Integer'

解决方案

<if test="_parameter != null and _parameter.status != null">

3. 集合遍历异常

错误提示

Parameter 'ids' not found. Available parameters are [list]

正确用法

void deleteByIds(@Param("idList") List<Long> ids);
<foreach collection="idList" item="id">
    #{id}
</foreach>

五、高级组合应用

1. 动态排序实现

ORDER BY
<choose>
    <when test="sortType == 'price_asc'">price ASC</when>
    <when test="sortType == 'sales_desc'">sales_count DESC</when>
    <otherwise>create_time DESC</otherwise>
</choose>

2. 分页查询优化

<select id="search" resultType="Product">
    SELECT * FROM products
    <where>
        <!-- 动态条件 -->
    </where>
    LIMIT #{page.offset}, #{page.size}
</select>

3. 多租户隔离方案

<sql id="tenantFilter">
    WHERE tenant_id = #{tenantId}
</sql>

<select id="findAll" resultType="Product">
    SELECT * FROM products
    <include refid="tenantFilter"/>
</select>

1. 核心动态标签详解

<!-- if标签:条件判断 -->
<if test="title != null and title != ''">
    AND title LIKE CONCAT('%', #{title}, '%')
</if>

<!-- choose-when-otherwise:多条件选择 -->
<choose>
    <when test="status == 1">status = 1</when>
    <when test="status == 2">status = 2</when>
    <otherwise>status IS NOT NULL</otherwise>
</choose>

<!-- foreach:集合遍历 -->
<foreach item="id" collection="ids" open="AND id IN (" close=")" separator=",">
    #{id}
</foreach>

<!-- where:智能WHERE处理 -->
<where>
    <if test="categoryId != null">category_id = #{categoryId}</if>
</where>

<!-- set:动态更新 -->
<set>
    <if test="price != null">price = #{price},</if>
</set>

<!-- trim:自定义修剪 -->
<trim prefix="WHERE" prefixOverrides="AND|OR">
    <if test="stock > 0">AND stock > #{stock}</if>
</trim>

2. 属性参数来源解析

  • test表达式使用OGNL语法
  • 参数类型对应规则:
    // 单POJO参数:直接访问属性
    void searchProducts(ProductQuery query);
    
    // Map参数:通过key访问
    @MapKey("id")
    Map<String, Object> findByMap(Map<String, Object> params);
    
    // 多参数:@Param注解命名
    List<Product> findComplex(
        @Param("query") ProductQuery query,
        @Param("ext") Map<String, Object> extParams
    );
    

二、电商三表联查实战

1. 领域模型设计

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class OrderDetailDTO {
    private Long orderId;
    private LocalDateTime createTime;
    private BigDecimal totalAmount;
    private Integer productId;
    private String productName;
    private Integer quantity;
    private String username;
    private String email;
}

2. 核心业务实现

Controller层
@RestController
@RequestMapping("/orders")
@RequiredArgsConstructor
public class OrderController {
    private final OrderService orderService;

    @PostMapping("/search")
    public ResponseEntity<List<OrderDetailDTO>> searchOrders(
            @RequestBody OrderSearchVO searchVO) {
        return ResponseEntity.ok(orderService.searchOrders(searchVO));
    }
}
Service层
@Service
@RequiredArgsConstructor
public class OrderService {
    private final OrderMapper orderMapper;

    public List<OrderDetailDTO> searchOrders(OrderSearchVO searchVO) {
        Map<String, Object> params = new HashMap<>();
        params.put("startDate", searchVO.getStartDate());
        params.put("productIds", searchVO.getProductIds());
        return orderMapper.findOrderDetails(searchVO, params);
    }
}
Mapper接口
@Mapper
public interface OrderMapper {
    List<OrderDetailDTO> findOrderDetails(
            @Param("query") OrderSearchVO query,
            @Param("ext") Map<String, Object> extParams);
}

3. 复杂查询XML实现

<select id="findOrderDetails" resultType="OrderDetailDTO">
    SELECT 
        o.id AS orderId,
        o.create_time,
        o.total_amount,
        p.id AS productId,
        p.product_name,
        od.quantity,
        u.username,
        u.email
    FROM orders o
    JOIN order_detail od ON o.id = od.order_id
    JOIN products p ON od.product_id = p.id
    JOIN users u ON o.user_id = u.id
    <where>
        <!-- POJO参数访问 -->
        <if test="query.userId != null">
            AND o.user_id = #{query.userId}
        </if>
        
        <!-- Map参数访问 -->
        <if test="ext.startDate != null">
            AND o.create_time >= #{ext.startDate}
        </if>
        
        <!-- 集合遍历 -->
        <if test="ext.productIds != null and !ext.productIds.isEmpty()">
            AND p.id IN
            <foreach item="id" collection="ext.productIds" 
                     open="(" separator="," close=")">
                #{id}
            </foreach>
        </if>
        
        <!-- 多条件选择 -->
        <choose>
            <when test="query.status == 1">
                AND o.status = 1
            </when>
            <when test="query.status == 2">
                AND o.status = 2 AND o.payment_time IS NOT NULL
            </when>
            <otherwise>
                AND o.status IN (0,3)
            </otherwise>
        </choose>
    </where>
    ORDER BY o.create_time DESC
</select>

三、MyBatis配置详解

1. application.yml配置

mybatis:
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.example.ecommerce.domain
  configuration:
    map-underscore-to-camel-case: true
    default-fetch-size: 100
    default-statement-timeout: 30

2. mybatis-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <settings>
        <setting name="cacheEnabled" value="true"/>
        <setting name="lazyLoadingEnabled" value="false"/>
    </settings>
    <typeHandlers>
        <typeHandler handler="org.apache.ibatis.type.LocalDateTimeTypeHandler"/>
    </typeHandlers>
</configuration>

四、深度技术解析

1. test表达式原理

  • OGNL表达式引擎解析
  • 参数访问优先级:
    1. @Param注解命名参数
    2. 参数索引(arg0, arg1)
    3. 参数别名(param1, param2)

2. 复杂参数处理示例

// 构造参数示例
mapper.findByConstructor(
    new ProductQuery("手机", 2000),
    Arrays.asList(101, 102),
    Map.of("minStock", 10)
);

对应XML处理:

<if test="arg0.price gt 1000">  <!-- 访问第一个参数的price属性 -->
<if test="arg1.contains(102)">  <!-- 访问第二个集合参数 -->
<if test="arg2.minStock != null">  <!-- 访问第三个Map参数 -->

3. 最佳实践建议

  1. 复杂查询优先使用XML配置
  2. 超过3个参数建议封装DTO
  3. 集合查询使用@Param明确命名
  4. 动态字段过滤使用优化
  5. 分页查询结合PageHelper插件

五、性能优化策略

  1. 批量操作使用BatchExecutor

    SqlSession batchSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
    batchSession.insert("insertProduct", productList);
    batchSession.commit();
    
  2. 二级缓存配置

    <cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/>
    
  3. 结果集映射优化

    <resultMap id="productMap" type="Product">
        <id property="id" column="product_id"/>
        <result property="name" column="product_name"/>
    </resultMap>