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. 性能优化要点
- 避免过度动态:保持执行计划稳定性
- 优先使用WHERE:替代复杂的trim配置
- 批量操作优化: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表达式引擎解析
- 参数访问优先级:
- @Param注解命名参数
- 参数索引(arg0, arg1)
- 参数别名(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. 最佳实践建议
- 复杂查询优先使用XML配置
- 超过3个参数建议封装DTO
- 集合查询使用@Param明确命名
- 动态字段过滤使用优化
- 分页查询结合PageHelper插件
五、性能优化策略
-
批量操作使用BatchExecutor
SqlSession batchSession = sqlSessionFactory.openSession(ExecutorType.BATCH); batchSession.insert("insertProduct", productList); batchSession.commit(); -
二级缓存配置
<cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/> -
结果集映射优化
<resultMap id="productMap" type="Product"> <id property="id" column="product_id"/> <result property="name" column="product_name"/> </resultMap>