【MyBatis-Plus | Wrapper 条件构造器 】

MyBatis-Plus 的 Wrapper 条件构造器是其最核心、最强大的功能之一。下面我深入讲解 Wrapper 的设计原理、使用技巧、高级用法和源码分析。

一、Wrapper 体系结构

1.1 核心类层次图

text

Wrapper (接口)
├── AbstractWrapper (抽象类)
│   ├── QueryWrapper (查询条件构造器)
│   ├── UpdateWrapper (更新条件构造器)
│   └── LambdaQueryWrapper (Lambda表达式查询)
└── LambdaUpdateWrapper (Lambda表达式更新)

1.2 核心接口设计

java

// 核心接口方法
public interface Wrapper<T> {
    // 获取 SQL 片段
    String getSqlSegment();
    // 获取参数映射
    Map<String, Object> getParamNameValuePairs();
}

二、QueryWrapper 深度解析

2.1 基础条件方法原理

java

QueryWrapper<User> wrapper = new QueryWrapper<>();

// eq 方法底层原理分析
wrapper.eq("name", "张三");
// 等价于:name = '张三'

// 源码模拟:
public Children eq(String column, Object val) {
    // 1. 将条件添加到 SQL 片段列表
    return addCondition(String.format("%s = {0}", column), val);
}

2.2 链式调用与条件组合

java

// 复杂条件组合
wrapper.select("id", "name", "age")
       .eq("deleted", 0)
       .and(w -> w.gt("age", 18).lt("age", 60))
       .or()
       .like("name", "张")
       .orderByDesc("create_time")
       .last("LIMIT 10");

// 生成的 SQL:
// SELECT id, name, age FROM user 
// WHERE deleted = 0 AND (age > 18 AND age < 60) 
// OR name LIKE '%张%' 
// ORDER BY create_time DESC LIMIT 10

2.3 嵌套查询与子查询

java

// 子查询示例
QueryWrapper<User> subWrapper = new QueryWrapper<>();
subWrapper.select("dept_id").eq("status", 1);

QueryWrapper<User> mainWrapper = new QueryWrapper<>();
mainWrapper.inSql("dept_id", subWrapper.getSqlSegment())
          .eq("type", 1);

// 生成的 SQL:
// SELECT * FROM user 
// WHERE dept_id IN (SELECT dept_id FROM user WHERE status = 1) 
// AND type = 1

三、LambdaWrapper 类型安全编程

3.1 Lambda 表达式原理

java

// 传统方式 - 字符串,容易出错
QueryWrapper<User> wrapper1 = new QueryWrapper<>();
wrapper1.eq("name", "张三");  // 如果列名写错,运行时才会报错

// Lambda 方式 - 类型安全
LambdaQueryWrapper<User> lambdaWrapper = new LambdaQueryWrapper<>();
lambdaWrapper.eq(User::getName, "张三");  // 编译时检查

// Lambda 原理:通过方法引用获取属性名
// User::getName 会被解析为 "name"

3.2 复杂 Lambda 条件

java

LambdaQueryWrapper<User> wrapper = Wrappers.<User>lambdaQuery()
    // 基础条件
    .eq(User::getStatus, 1)
    // 嵌套条件
    .nested(i -> i.gt(User::getAge, 18).lt(User::getAge, 65))
    // 函数条件
    .apply("DATE(create_time) = {0}", "2024-01-01")
    // 存在性检查
    .exists("SELECT 1 FROM dept WHERE id = user.dept_id AND status = 1")
    // 分组统计
    .groupBy(User::getDeptId)
    .having("COUNT(*) > {0}", 10);

// 生成的 SQL 非常复杂且类型安全

四、UpdateWrapper 更新操作

4.1 条件更新与字段设置

java

UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
updateWrapper.eq("status", 0)
            .set("name", "新名称")
            .setSql("balance = balance + 100")  // 原生SQL更新
            .set("update_time", new Date());

// 生成的 SQL:
// UPDATE user SET name = '新名称', balance = balance + 100, update_time = NOW() 
// WHERE status = 0

4.2 Lambda 更新表达式

java

LambdaUpdateWrapper<User> lambdaUpdate = Wrappers.<User>lambdaUpdate()
    .eq(User::getId, 1)
    .set(User::getName, "新名称")
    .set(User::getAge, 25)
    // 支持函数式设置
    .setSql(user -> "version = version + 1");

userMapper.update(null, lambdaUpdate);

五、Wrapper 源码深度分析

5.1 条件存储结构

java

// AbstractWrapper 中的核心数据结构
public abstract class AbstractWrapper<T, R, Children> 
    implements Wrapper<T> {
    
    // 存储所有 SQL 片段
    protected List<SegmentList> expression = new ArrayList<>();
    
    // 存储参数值
    protected Map<String, Object> paramNameValuePairs;
    
    // 条件构建入口
    protected Children addCondition(String sqlSegment, Object val) {
        // 1. 将条件添加到 expression
        // 2. 将参数值存入 paramNameValuePairs
        // 3. 返回 this 实现链式调用
    }
}

5.2 SQL 生成过程

java

// SQL 生成核心流程
public String getSqlSegment() {
    // 1. 合并所有 expression 片段
    String sqlSegment = mergeSegments();
    
    // 2. 替换参数占位符 {0}, {1}...
    sqlSegment = formatSql(sqlSegment);
    
    // 3. 返回最终的 WHERE 条件
    return StringUtils.isNotEmpty(sqlSegment) 
        ? "WHERE " + sqlSegment : "";
}

// 示例:
// expression: ["name = {0}", "AND age > {1}"]
// paramNameValuePairs: {0: "张三", 1: 18}
// 最终SQL: "WHERE name = '张三' AND age > 18"

六、高级特性与性能优化

6.1 动态条件构建

java

public QueryWrapper<User> buildUserQuery(UserQueryDTO query) {
    return Wrappers.<User>query()
        .eq(query.getId() != null, "id", query.getId())
        .like(StringUtils.isNotBlank(query.getName()), "name", query.getName())
        .ge(query.getStartTime() != null, "create_time", query.getStartTime())
        .le(query.getEndTime() != null, "create_time", query.getEndTime())
        .in(!CollectionUtils.isEmpty(query.getStatusList()), "status", query.getStatusList());
}

// 使用方法:
UserQueryDTO query = new UserQueryDTO();
query.setName("张");
query.setStatusList(Arrays.asList(1, 2));
QueryWrapper<User> wrapper = buildUserQuery(query);

6.2 性能优化技巧

java

// 1. 避免 SELECT *,指定需要的字段
wrapper.select("id", "name", "email");

// 2. 合理使用索引 - 最左前缀原则
wrapper.eq("dept_id", 1).eq("status", 1); 
// 如果索引是 (dept_id, status),这个查询会走索引

// 3. 避免在索引列上使用函数
wrapper.apply("YEAR(create_time) = 2024"); // ❌ 不会走索引
wrapper.ge("create_time", "2024-01-01").lt("create_time", "2025-01-01"); // ✅ 会走索引

// 4. 分页优化
wrapper.select("id")  // 只查询ID
      .eq("status", 1)
      .last("LIMIT 10000, 20");

七、实战中的复杂场景

7.1 多表关联查询

java

// 虽然MP主要处理单表,但可以通过Wrapper实现复杂查询
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.select("u.*", "d.dept_name")
      .eq("u.status", 1)
      .like("u.name", "张")
      .in("u.dept_id", 
          "SELECT id FROM dept WHERE status = 1 AND level > 2")
      .exists("SELECT 1 FROM user_role ur WHERE ur.user_id = u.id AND ur.role_id = 1");

// 配合自定义XML使用
List<UserVO> users = userMapper.selectUserWithDept(wrapper);

7.2 批量操作优化

java

// 批量更新场景
List<Long> ids = Arrays.asList(1L, 2L, 3L);
UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
updateWrapper.in("id", ids)
            .set("status", 2)
            .set("update_time", new Date());

// 只生成一条SQL,性能更好
userMapper.update(null, updateWrapper);

八、常见陷阱与最佳实践

8.1 NULL 值处理陷阱

java

String name = null;
Integer age = null;

// ❌ 错误做法:会生成 WHERE name = null
wrapper.eq("name", name).eq("age", age);

// ✅ 正确做法1:条件判断
if (name != null) {
    wrapper.eq("name", name);
}
if (age != null) {
    wrapper.eq("age", age);
}

// ✅ 正确做法2:使用条件方法(推荐)
wrapper.eq(name != null, "name", name)
       .eq(age != null, "age", age);

8.2 SQL 注入防护

java

// ❌ 危险!SQL注入风险
String userInput = "张三'; DROP TABLE user; --";
wrapper.apply("name = '" + userInput + "'");

// ✅ 安全做法
wrapper.eq("name", userInput);  // 使用预编译
// 或者使用参数化 apply
wrapper.apply("name = {0}", userInput);

九、面试深度问题

9.1 源码设计思想

Q: Wrapper 是如何实现链式调用的?

java

// 答案:通过泛型和返回 this 实现
public abstract class AbstractWrapper<T, R, Children extends AbstractWrapper<T, R, Children>> {
    
    // 关键:返回 Children 类型,实现链式调用
    public Children eq(String column, Object val) {
        addCondition(column, EQ, val);
        return (Children) this;  // 类型转换,返回具体子类
    }
}

9.2 性能优化相关

Q: 在大数据量下使用 Wrapper 要注意什么?

  1. 避免深分页:使用 last("LIMIT 100000, 10") 性能极差

  2. 字段选择:避免 select("*"),只查询需要的字段

  3. 索引优化:确保查询条件能命中合适索引

  4. 连接查询:复杂关联查询建议使用原生 SQL

9.3 扩展设计

Q: 如何基于 Wrapper 设计一个数据权限框架?

java

// 自定义数据权限拦截器
@Component
public class DataPermissionInterceptor implements InnerInterceptor {
    
    @Override
    public void beforeQuery(Executor executor, MappedStatement ms, 
                          Object parameter, RowBounds rowBounds, 
                          ResultHandler resultHandler, BoundSql boundSql) {
        
        // 解析原始SQL和参数
        // 根据当前用户权限,动态添加数据权限条件
        String dataPermissionSql = buildDataPermissionSql();
        
        // 修改最终的SQL
        String newSql = boundSql.getSql() + " AND " + dataPermissionSql;
        // 通过反射修改boundSql的sql字段
    }
}

总结

Wrapper 的设计体现了几个重要思想:

  1. 链式编程:通过泛型设计实现流畅的API

  2. 组合模式:通过 Segment 组合复杂SQL条件

  3. 模板方法:AbstractWrapper 定义算法骨架,子类实现具体逻辑

  4. 类型安全:LambdaWrapper 在编译期检查字段名正确性

深入理解 Wrapper 不仅有助于更好地使用 MyBatis-Plus,也能学习到优秀框架的设计思想和编程模式。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值