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 要注意什么?
-
避免深分页:使用
last("LIMIT 100000, 10")性能极差 -
字段选择:避免
select("*"),只查询需要的字段 -
索引优化:确保查询条件能命中合适索引
-
连接查询:复杂关联查询建议使用原生 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 的设计体现了几个重要思想:
-
链式编程:通过泛型设计实现流畅的API
-
组合模式:通过 Segment 组合复杂SQL条件
-
模板方法:AbstractWrapper 定义算法骨架,子类实现具体逻辑
-
类型安全:LambdaWrapper 在编译期检查字段名正确性
深入理解 Wrapper 不仅有助于更好地使用 MyBatis-Plus,也能学习到优秀框架的设计思想和编程模式。
2029

被折叠的 条评论
为什么被折叠?



