
在 Java 持久层框架的演进历程中,MyBatis-Plus(简称 MP)以其 "为简化开发而生" 的设计理念,成为了众多开发者的首选工具。其中,QueryWrapper 和 LambdaQueryWrapper 作为 MP 查询体系的两大核心组件,承载着将 Java 代码转换为 SQL 语句的重要职责。但你是否真正掌握了它们的精髓?为什么阿里规约强烈推荐使用 Lambda 形式?看似相似的 API 背后隐藏着怎样的设计哲学差异?本文将从底层实现到架构设计,全方位剖析这两个查询封装类的本质区别,助你写出既安全又高效的数据库操作代码。
在深入探讨区别之前,我们首先需要明确 QueryWrapper 和 LambdaQueryWrapper 的基本定义和设计定位。这是理解它们差异的基础,也是正确使用的前提。
QueryWrapper 是 MyBatis-Plus 提供的基础查询条件构造器,它通过字符串形式指定数据库字段名来构建查询条件。其设计初衷是为开发者提供一种灵活的、接近 SQL 语法的条件拼接方式。
/**
* QueryWrapper的核心定义(简化版)
* 基于字符串字段名构建查询条件
*/
public class QueryWrapper<T> extends AbstractWrapper<T, String, QueryWrapper<T>> {
// 构造方法
public QueryWrapper() {
super();
}
public QueryWrapper(T entity) {
super(entity);
}
// 字段名以字符串形式传入
public QueryWrapper<T> eq(String column, Object val) {
return super.eq(column, val);
}
// 更多条件方法...
}
LambdaQueryWrapper 是 MP 在后期版本中引入的增强型查询构造器,它利用 Java 8 的 Lambda 表达式特性,通过方法引用来指定实体类的属性,从而避免了直接使用字符串字段名可能带来的问题。
/**
* LambdaQueryWrapper的核心定义(简化版)
* 基于Lambda表达式构建类型安全的查询条件
*/
public class LambdaQueryWrapper<T> extends AbstractLambdaWrapper<T, LambdaQueryWrapper<T>> {
// 构造方法
public LambdaQueryWrapper() {
super();
}
public LambdaQueryWrapper(T entity) {
super(entity);
}
// 字段名以Lambda表达式形式传入
public <R> LambdaQueryWrapper<T> eq(SFunction<T, R> column, Object val) {
return super.eq(column, val);
}
// 更多条件方法...
}
两者的核心差异从设计定位上就已显现:
下面的架构图清晰展示了它们在 MP 体系中的位置:

语法是开发者接触最多的层面,也是两者最直观的区别。一个小小的语法差异,可能在项目维护阶段带来巨大的影响。
QueryWrapper 使用字符串指定数据库字段名,而 LambdaQueryWrapper 使用实体类的方法引用(Lambda 表达式)指定属性,这是两者最核心的语法差异。
@Slf4j
@Service
@RequiredArgsConstructor
public class UserServiceImpl implements UserService {
private final UserMapper userMapper;
/**
* 使用QueryWrapper查询年龄大于18的用户
* @return 符合条件的用户列表
*/
@Override
public List<User> getAdultUsers() {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
// 使用字符串指定字段名
queryWrapper.gt("age", 18);
List<User> users = userMapper.selectList(queryWrapper);
log.info("查询到成年用户数量:{}", users.size());
return users;
}
}
@Slf4j
@Service
@RequiredArgsConstructor
public class UserServiceImpl implements UserService {
private final UserMapper userMapper;
/**
* 使用LambdaQueryWrapper查询年龄大于18的用户
* @return 符合条件的用户列表
*/
@Override
public List<User> getAdultUsers() {
LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
// 使用Lambda表达式指定字段(方法引用)
lambdaQueryWrapper.gt(User::getAge, 18);
List<User> users = userMapper.selectList(lambdaQueryWrapper);
log.info("查询到成年用户数量:{}", users.size());
return users;
}
}
在处理多条件组合、嵌套查询等复杂场景时,两者的语法差异会带来不同的开发体验。
QueryWrapper 实现:
/**
* 使用QueryWrapper查询特定条件的用户
* @param userName 用户名(模糊匹配)
* @param minAge 最小年龄
* @param maxAge 最大年龄
* @return 符合条件的用户列表
*/
@Override
public List<User> queryUsers(String userName, Integer minAge, Integer maxAge) {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
// 字符串拼接容易出错,尤其是字段名较长时
if (StringUtils.hasText(userName)) {
queryWrapper.like("user_name", userName);
}
if (ObjectUtils.isNotEmpty(minAge)) {
queryWrapper.ge("age", minAge);
}
if (ObjectUtils.isNotEmpty(maxAge)) {
queryWrapper.le("age", maxAge);
}
// 按创建时间降序排列
queryWrapper.orderByDesc("create_time");
return userMapper.selectList(queryWrapper);
}
LambdaQueryWrapper 实现:
/**
* 使用LambdaQueryWrapper查询特定条件的用户
* @param userName 用户名(模糊匹配)
* @param minAge 最小年龄
* @param maxAge 最大年龄
* @return 符合条件的用户列表
*/
@Override
public List<User> queryUsers(String userName, Integer minAge, Integer maxAge) {
LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
// Lambda表达式避免了字符串拼写错误
if (StringUtils.hasText(userName)) {
lambdaQueryWrapper.like(User::getUserName, userName);
}
if (ObjectUtils.isNotEmpty(minAge)) {
lambdaQueryWrapper.ge(User::getAge, minAge);
}
if (ObjectUtils.isNotEmpty(maxAge)) {
lambdaQueryWrapper.le(User::getAge, maxAge);
}
// 按创建时间降序排列
lambdaQueryWrapper.orderByDesc(User::getCreateTime);
return userMapper.selectList(lambdaQueryWrapper);
}
QueryWrapper 实现:
/**
* 使用QueryWrapper实现嵌套查询
* 查询:(age > 18 AND status = 1) OR (email LIKE '%@example.com')
*/
@Override
public List<User> getComplexUsers() {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
// 嵌套条件需要手动管理括号,字符串字段名容易混淆
queryWrapper.and(wq -> wq.gt("age", 18).eq("status", 1))
.or(wq -> wq.like("email", "@example.com"));
return userMapper.selectList(queryWrapper);
}
LambdaQueryWrapper 实现:
/**
* 使用LambdaQueryWrapper实现嵌套查询
* 查询:(age > 18 AND status = 1) OR (email LIKE '%@example.com')
*/
@Override
public List<User> getComplexUsers() {
LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
// Lambda表达式使嵌套条件更清晰,字段关系明确
lambdaQueryWrapper.and(wq -> wq.gt(User::getAge, 18).eq(User::getStatus, 1))
.or(wq -> wq.like(User::getEmail, "@example.com"));
return userMapper.selectList(lambdaQueryWrapper);
}
影响维度 | QueryWrapper | LambdaQueryWrapper |
|---|---|---|
编译时检查 | 无,字段错误只能在运行时发现 | 有,字段错误在编译时即可发现 |
重构支持 | 差,修改实体类属性名后无法自动更新 | 好,IDE 能自动更新所有引用 |
代码可读性 | 低,需要对照实体类才能确定字段含义 | 高,直接关联实体类方法 |
学习成本 | 低,接近 SQL 语法 | 中,需要了解 Lambda 表达式 |
表面的语法差异背后,是截然不同的底层实现逻辑。理解这些原理,能帮助我们更好地把握两者的适用场景。
QueryWrapper 的实现相对直接,它本质上是将开发者传入的字符串字段名、条件运算符和值组装成一个条件表达式集合,最终在执行查询时转换为 SQL 语句的 WHERE 子句。

关键实现代码片段(简化版):
// AbstractWrapper中的eq方法实现
public Children eq(String column, Object val) {
// 校验字段名非空
if (StringUtils.isEmpty(column)) {
throw new IllegalArgumentException("column cannot be empty");
}
// 添加到条件集合
this.conditionList.add(new Condition(column, Operator.EQ, val));
return (Children) this;
}
// 转换为SQL片段的过程
public String getSqlSegment() {
StringBuilder sql = new StringBuilder();
for (Condition condition : conditionList) {
sql.append(condition.getColumn())
.append(condition.getOperator().getSql())
.append("?")
.append(" AND ");
}
// 移除最后一个AND
if (sql.length() > 0) {
sql.setLength(sql.length() - 5);
}
return sql.toString();
}
LambdaQueryWrapper 的实现则复杂得多,它需要解析 Lambda 表达式,提取出实体类的属性信息,再映射到数据库字段名。这一过程涉及到 Java 的 MethodHandle、反射等高级特性。

关键实现代码片段(简化版):
// AbstractLambdaWrapper中的eq方法实现
public <R> Children eq(SFunction<T, R> column, Object val) {
// 解析Lambda表达式获取字段信息
LambdaMeta meta = LambdaUtils.extract(column);
String columnName = resolveColumnName(meta);
// 校验字段类型与值类型是否匹配
validateColumnType(meta, val);
// 添加到条件集合
this.conditionList.add(new Condition(columnName, Operator.EQ, val));
return (Children) this;
}
// 解析Lambda表达式获取字段信息
private String resolveColumnName(LambdaMeta meta) {
Method method = meta.getMethod();
String methodName = method.getName();
// 从getter方法推断字段名,如getAge() -> age
if (methodName.startsWith("get")) {
String fieldName = Introspector.decapitalize(methodName.substring(3));
// 检查实体类是否有该字段
Field field = ReflectionUtils.findField(meta.getInstantiatedType(), fieldName);
if (field == null) {
throw new IllegalArgumentException("No such field: " + fieldName);
}
// 检查是否有@TableField注解指定数据库字段名
TableField tableField = field.getAnnotation(TableField.class);
return tableField != null && StringUtils.hasText(tableField.value())
? tableField.value()
: fieldName;
}
throw new IllegalArgumentException("Invalid method: " + methodName);
}
由于 Lambda 表达式的解析过程涉及反射等操作,理论上 LambdaQueryWrapper 的性能会略低于 QueryWrapper。但在实际应用中,这种差异通常可以忽略不计,原因如下:
性能测试代码:
@Slf4j
public class WrapperPerformanceTest {
private static final int TEST_COUNT = 100000;
public static void main(String[] args) {
// 测试QueryWrapper性能
long queryWrapperTime = testQueryWrapper();
// 测试LambdaQueryWrapper性能
long lambdaTime = testLambdaQueryWrapper();
log.info("QueryWrapper {}次操作耗时:{}ms", TEST_COUNT, queryWrapperTime);
log.info("LambdaQueryWrapper {}次操作耗时:{}ms", TEST_COUNT, lambdaTime);
log.info("性能差异:{}%", (lambdaTime - queryWrapperTime) * 100.0 / queryWrapperTime);
}
private static long testQueryWrapper() {
long start = System.currentTimeMillis();
for (int i = 0; i < TEST_COUNT; i++) {
QueryWrapper<User> qw = new QueryWrapper<>();
qw.eq("id", i).like("user_name", "test").gt("age", 18);
}
return System.currentTimeMillis() - start;
}
private static long testLambdaQueryWrapper() {
long start = System.currentTimeMillis();
for (int i = 0; i < TEST_COUNT; i++) {
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();
lqw.eq(User::getId, i).like(User::getUserName, "test").gt(User::getAge, 18);
}
return System.currentTimeMillis() - start;
}
}
测试结果(仅供参考):
QueryWrapper 100000次操作耗时:42ms
LambdaQueryWrapper 100000次操作耗时:68ms
性能差异:61.904761904761905%
虽然 LambdaQueryWrapper 的耗时更长,但每次操作的额外开销仅为 0.26 微秒,在实际应用中几乎可以忽略不计。
理论需要结合实践,不同的业务场景和项目阶段适合不同的查询工具。以下是一些典型场景的对比分析。
对于简单的单表查询、插入、更新、删除操作,两者都能胜任,但 LambdaQueryWrapper 在类型安全方面更有优势。
场景:根据 ID 查询用户
QueryWrapper 实现:
@Override
public User getUserById(Long id) {
if (ObjectUtils.isEmpty(id)) {
throw new IllegalArgumentException("用户ID不能为空");
}
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("id", id);
return userMapper.selectOne(queryWrapper);
}
LambdaQueryWrapper 实现:
@Override
public User getUserById(Long id) {
if (ObjectUtils.isEmpty(id)) {
throw new IllegalArgumentException("用户ID不能为空");
}
LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.eq(User::getId, id);
return userMapper.selectOne(lambdaQueryWrapper);
}
结论:两者代码量相当,但 LambdaQueryWrapper 避免了 "id" 字符串的硬编码,更推荐使用。
在需要根据前端传入的参数动态构建查询条件的场景(如多条件搜索),LambdaQueryWrapper 的优势更加明显。
场景:用户高级搜索功能
@Slf4j
@RestController
@RequestMapping("/api/users")
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
/**
* 用户高级搜索接口
* @param userName 用户名(可选)
* @param email 邮箱(可选)
* @param minAge 最小年龄(可选)
* @param maxAge 最大年龄(可选)
* @param status 状态(可选)
* @param createTimeStart 创建开始时间(可选)
* @param createTimeEnd 创建结束时间(可选)
* @return 分页查询结果
*/
@GetMapping("/search")
@ApiOperation("用户高级搜索")
public PageResult<UserVO> searchUsers(
@RequestParam(required = false) String userName,
@RequestParam(required = false) String email,
@RequestParam(required = false) Integer minAge,
@RequestParam(required = false) Integer maxAge,
@RequestParam(required = false) Integer status,
@RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime createTimeStart,
@RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime createTimeEnd,
@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "10") Integer pageSize) {
Page<User> page = new Page<>(pageNum, pageSize);
// 使用LambdaQueryWrapper构建动态条件
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
// 用户名模糊查询
if (StringUtils.hasText(userName)) {
queryWrapper.like(User::getUserName, userName);
}
// 邮箱精确查询
if (StringUtils.hasText(email)) {
queryWrapper.eq(User::getEmail, email);
}
// 年龄范围查询
if (ObjectUtils.isNotEmpty(minAge)) {
queryWrapper.ge(User::getAge, minAge);
}
if (ObjectUtils.isNotEmpty(maxAge)) {
queryWrapper.le(User::getAge, maxAge);
}
// 状态精确查询
if (ObjectUtils.isNotEmpty(status)) {
queryWrapper.eq(User::getStatus, status);
}
// 创建时间范围查询
if (ObjectUtils.isNotEmpty(createTimeStart)) {
queryWrapper.ge(User::getCreateTime, createTimeStart);
}
if (ObjectUtils.isNotEmpty(createTimeEnd)) {
queryWrapper.le(User::getCreateTime, createTimeEnd);
}
// 按创建时间降序排列
queryWrapper.orderByDesc(User::getCreateTime);
// 执行分页查询
Page<User> userPage = userMapper.selectPage(page, queryWrapper);
// 转换为VO并返回
List<UserVO> records = userPage.getRecords().stream()
.map(this::convertToVO)
.collect(Collectors.toList());
return new PageResult<>(
records,
userPage.getTotal(),
userPage.getSize(),
userPage.getCurrent(),
userPage.getPages()
);
}
private UserVO convertToVO(User user) {
// 实体转换逻辑
UserVO vo = new UserVO();
BeanUtils.copyProperties(user, vo);
return vo;
}
}
结论:在动态条件较多的情况下,LambdaQueryWrapper 的类型安全特性可以有效避免因字段名拼写错误导致的运行时异常,大幅提高代码的可维护性。
MyBatis-Plus 的 Wrapper 主要用于单表查询,联表查询通常需要自定义 SQL。但在一些简单的联表场景中,仍可使用 Wrapper 配合 @TableField 注解或自定义方法。
场景:查询用户及其所属部门信息
用户实体类:
@Data
@TableName("sys_user")
@ApiModel(description = "用户实体类")
public class User {
@TableId(type = IdType.AUTO)
@ApiModelProperty(value = "用户ID")
private Long id;
@TableField("user_name")
@ApiModelProperty(value = "用户名")
private String userName;
@TableField("age")
@ApiModelProperty(value = "年龄")
private Integer age;
@TableField("dept_id")
@ApiModelProperty(value = "部门ID")
private Long deptId;
// 其他字段...
// 非数据库字段,用于联表查询结果封装
@TableField(exist = false)
@ApiModelProperty(value = "部门信息")
private Department dept;
}
部门实体类:
@Data
@TableName("sys_department")
@ApiModel(description = "部门实体类")
public class Department {
@TableId(type = IdType.AUTO)
@ApiModelProperty(value = "部门ID")
private Long id;
@TableField("dept_name")
@ApiModelProperty(value = "部门名称")
private String deptName;
// 其他字段...
}
Mapper 接口:
public interface UserMapper extends BaseMapper<User> {
/**
* 联表查询用户及其部门信息
* @param queryWrapper 查询条件
* @return 用户列表(包含部门信息)
*/
@Select("SELECT u.*, d.dept_name FROM sys_user u LEFT JOIN sys_department d ON u.dept_id = d.id ${ew.customSqlSegment}")
List<User> selectUserWithDept(@Param(Constants.WRAPPER) QueryWrapper<User> queryWrapper);
}
服务实现:
/**
* 联表查询用户及其部门信息
* @param deptName 部门名称(模糊查询)
* @return 用户列表
*/
@Override
public List<User> getUsersWithDept(String deptName) {
// 注意:联表查询的额外条件需要用QueryWrapper的字符串形式
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
if (StringUtils.hasText(deptName)) {
// 部门表的字段只能用字符串指定
queryWrapper.like("d.dept_name", deptName);
}
// 用户表的字段可以用LambdaQueryWrapper转义后传入
LambdaQueryWrapper<User> lambdaQuery = new LambdaQueryWrapper<>();
lambdaQuery.eq(User::getStatus, 1); // 只查询状态正常的用户
// 将Lambda条件合并到QueryWrapper
queryWrapper.and(i -> i.apply(lambdaQuery.getWrapper().getCustomSqlSegment()));
return userMapper.selectUserWithDept(queryWrapper);
}
结论:在联表查询中,主表的条件可以使用 LambdaQueryWrapper 保证类型安全,而关联表的条件则必须使用 QueryWrapper 的字符串形式。这种混合使用方式可以在一定程度上兼顾安全性和灵活性。
当需要在查询条件中使用数据库函数(如 DATE_FORMAT、CONCAT 等)时,QueryWrapper 更具灵活性,而 LambdaQueryWrapper 则需要通过 apply 方法配合。
场景:查询本月注册的用户
QueryWrapper 实现:
/**
* 查询本月注册的用户
*/
@Override
public List<User> getUsersRegisteredThisMonth() {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
// 直接使用MySQL函数
queryWrapper.apply("DATE_FORMAT(create_time, '%Y-%m') = DATE_FORMAT(CURDATE(), '%Y-%m')");
return userMapper.selectList(queryWrapper);
}
LambdaQueryWrapper 实现:
/**
* 查询本月注册的用户
*/
@Override
public List<User> getUsersRegisteredThisMonth() {
LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
// 通过apply方法使用数据库函数
lambdaQueryWrapper.apply("DATE_FORMAT({0}, '%Y-%m') = DATE_FORMAT(CURDATE(), '%Y-%m')",
User::getCreateTime);
return userMapper.selectList(lambdaQueryWrapper);
}
结论:两者都能实现数据库函数调用,但 LambdaQueryWrapper 通过占位符 {0} 与实体类属性绑定,比 QueryWrapper 的纯字符串拼接更安全,推荐使用这种方式。
对于包含子查询的复杂查询条件,QueryWrapper 和 LambdaQueryWrapper 各有优势,具体选择取决于子查询的复杂度。
场景:查询存在未完成订单的用户
QueryWrapper 实现:
/**
* 查询存在未完成订单的用户
*/
@Override
public List<User> getUsersWithUnfinishedOrders() {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.inSql("id", "SELECT user_id FROM `order` WHERE status != 3");
return userMapper.selectList(queryWrapper);
}
LambdaQueryWrapper 实现:
/**
* 查询存在未完成订单的用户
*/
@Override
public List<User> getUsersWithUnfinishedOrders() {
LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
// 子查询中的表和字段仍需用字符串
lambdaQueryWrapper.inSql(User::getId, "SELECT user_id FROM `order` WHERE status != 3");
return userMapper.selectList(lambdaQueryWrapper);
}
结论:子查询内部的表和字段无法使用 Lambda 表达式,必须用字符串指定。但主查询的条件仍可使用 LambdaQueryWrapper,以保证部分类型安全。
掌握了两者的区别和适用场景后,我们还需要了解一些最佳实践和常见问题的解决方案,以提高开发效率和代码质量。
在实际项目中,不必完全排斥其中任何一个,而应根据具体情况灵活选择:
当实体类属性名与数据库列名不一致时(未使用 @TableField 注解指定),QueryWrapper 需要使用数据库列名,而 LambdaQueryWrapper 会自动处理。
错误示例:
// 实体类属性为userName,数据库列名为user_name
// QueryWrapper错误用法(使用属性名而非列名)
QueryWrapper<User> qw = new QueryWrapper<>();
qw.eq("userName", "test"); // 错误:实际列名是user_name
// 正确用法
qw.eq("user_name", "test"); // 正确:使用数据库列名
LambdaQueryWrapper 则无需担心这个问题:
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();
lqw.eq(User::getUserName, "test"); // 正确:自动映射到user_name
在动态查询中,通常需要忽略 null 或空值条件,MP 提供了eq和eq(boolean condition, ...)两种方法。
推荐做法:
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();
// 当userName不为空时才添加条件
lqw.eq(StringUtils.hasText(userName), User::getUserName, userName);
// 等价于
if (StringUtils.hasText(userName)) {
lqw.eq(User::getUserName, userName);
}
使用 MP 的分页功能时,需要注意分页插件的配置,并正确使用 Page 对象。
配置类:
@Configuration
@MapperScan("com.example.mapper")
public class MyBatisPlusConfig {
/**
* 配置分页插件
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 添加分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
分页查询实现:
/**
* 分页查询用户
*/
@Override
public Page<User> getUserPage(Integer pageNum, Integer pageSize, String keyword) {
// 校验参数
if (ObjectUtils.isEmpty(pageNum) || pageNum < 1) {
pageNum = 1;
}
if (ObjectUtils.isEmpty(pageSize) || pageSize < 1 || pageSize > 100) {
pageSize = 10;
}
// 创建分页对象
Page<User> page = new Page<>(pageNum, pageSize);
// 构建查询条件
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();
if (StringUtils.hasText(keyword)) {
lqw.and(wrapper -> wrapper
.like(User::getUserName, keyword)
.or()
.like(User::getEmail, keyword)
);
}
lqw.orderByDesc(User::getCreateTime);
// 执行分页查询
return userMapper.selectPage(page, lqw);
}
对于经常使用的查询条件,可以封装为方法,提高代码复用性。
/**
* 封装通用查询条件
*/
private LambdaQueryWrapper<User> getCommonQueryWrapper() {
return new LambdaQueryWrapper<User>()
.eq(User::getStatus, 1) // 只查询状态正常的用户
.ge(User::getAge, 18); // 只查询成年人
}
/**
* 使用通用条件查询并附加额外条件
*/
@Override
public List<User> getActiveUsersInDept(Long deptId) {
LambdaQueryWrapper<User> lqw = getCommonQueryWrapper();
lqw.eq(User::getDeptId, deptId);
return userMapper.selectList(lqw);
}
在需要自定义 SQL 的场景下,可以通过${ew.customSqlSegment}变量引入 Wrapper 构建的条件。
Mapper 接口:
public interface UserMapper extends BaseMapper<User> {
/**
* 自定义查询,结合Wrapper条件
*/
@Select("SELECT u.id, u.user_name, COUNT(o.id) as order_count " +
"FROM sys_user u LEFT JOIN `order` o ON u.id = o.user_id " +
"${ew.customSqlSegment} " +
"GROUP BY u.id")
List<UserOrderCountVO> selectUserOrderCount(@Param(Constants.WRAPPER) LambdaQueryWrapper<User> queryWrapper);
}
使用示例:
@Override
public List<UserOrderCountVO> getUserOrderCount() {
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();
lqw.ge(User::getCreateTime, LocalDate.now().minusYears(1));
return userMapper.selectUserOrderCount(lqw);
}
通过本文的深入分析,我们可以清晰地看到 QueryWrapper 和 LambdaQueryWrapper 的本质区别:
在实际开发中,我们应该:
从技术演进的角度看,LambdaQueryWrapper 代表了查询构建器的发展方向 —— 更安全、更智能、更贴近对象编程的思想。未来,随着 Java 语言的不断发展(如 Valhalla 项目带来的值类型、Loom 项目带来的虚拟线程等),MyBatis-Plus 的查询封装机制可能会有进一步的优化和创新。
作为开发者,我们不仅要掌握现有工具的使用,更要理解其背后的设计思想和技术原理,这样才能在技术不断迭代的浪潮中保持竞争力。选择合适的工具,写出高质量的代码,是我们永恒的追求。
为确保示例代码能够正确运行,以下是推荐的 Maven 依赖配置:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>mybatis-plus-demo</artifactId>
<version>1.0.0</version>
<name>mybatis-plus-demo</name>
<description>Demo project for MyBatis-Plus</description>
<properties>
<java.version>17</java.version>
<mybatis-plus.version>3.5.5</mybatis-plus.version>
<lombok.version>1.18.30</lombok.version>
<fastjson2.version>2.0.32</fastjson2.version>
<guava.version>32.1.3-jre</guava.version>
<springdoc.version>2.1.0</springdoc.version>
</properties>
<dependencies>
<!-- Spring Boot Starter Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Boot Starter Data JDBC -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<!-- MyBatis-Plus Starter -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!-- MySQL Connector -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<optional>true</optional>
</dependency>
<!-- FastJSON2 -->
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>${fastjson2.version}</version>
</dependency>
<!-- Guava -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${guava.version}</version>
</dependency>
<!-- SpringDoc OpenAPI (Swagger3) -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>${springdoc.version}</version>
</dependency>
<!-- Spring Boot Starter Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
以上配置基于最新的稳定版本,确保了示例代码能够在 JDK 17 环境下正常编译和运行。