作为Java开发者,我们在日常工作中最常打交道的莫过于数据库操作。而MyBatisPlus作为MyBatis的增强工具,其条件构造器(Wrapper)功能堪称数据库操作的"瑞士军刀"。不同于传统的XML配置或注解方式,Wrapper提供了一种更符合Java开发者思维习惯的链式编程方式来构建查询条件。
Wrapper的核心价值在于它解决了SQL拼接这个"脏活累活"。想象一下,如果没有Wrapper,我们要实现一个带有多条件筛选的查询,可能需要写一大堆if-else来拼接SQL字符串,既容易出错又难以维护。Wrapper将这些条件抽象为Java方法调用,让我们的代码更加清晰。
MyBatisPlus提供了几种常用的Wrapper实现:
让我们看一个实际的用户查询场景:找出用户名包含字母"o"且余额不少于1000元的用户,只返回id、username、info和balance字段。
java复制@Test
void testQueryWrapper() {
QueryWrapper<User> wrapper = new QueryWrapper<User>()
.select("id", "username", "info", "balance")
.like("username", "o")
.ge("balance", 1000);
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
这段代码有几个值得注意的技术细节:
select()方法明确指定了返回字段,避免了select *的性能浪费like()会自动在值前后添加%实现模糊查询ge()表示"greater than or equal to",对应SQL中的>=实际开发中,建议将字段名定义为常量而非字符串字面量,这样在字段名变更时只需修改一处。
UpdateWrapper特别适合处理需要基于当前值计算的更新操作。例如,我们要给ID为1、2、4的用户每人扣除200元余额:
java复制@Test
void testUpdateWrapper() {
List<Long> ids = List.of(1L, 2L, 4L);
UpdateWrapper<User> wrapper = new UpdateWrapper<User>()
.setSql("balance = balance - 200")
.in("id", ids);
userMapper.update(null, wrapper);
}
这里有几个关键点:
setSql()可以直接写入SQL片段,适合复杂计算update()方法的第一个参数为null,表示不更新实体字段in()方法生成id IN (1,2,4)条件LambdaQueryWrapper是我个人最推荐的方式,它通过方法引用来避免字段名的字符串硬编码:
java复制@Test
void testLambdaQueryWrapper() {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>()
.select(User::getId, User::getUsername, User::getInfo, User::getBalance)
.like(User::getUsername, "o")
.ge(User::getBalance, 1000);
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
这种写法的优势非常明显:
虽然Wrapper已经非常强大,但有些复杂SQL还是需要我们自己编写。MyBatisPlus很贴心地提供了Wrapper与自定义SQL协同工作的机制。
假设我们需要实现一个批量扣减余额的操作,步骤如下:
java复制void updateBalanceByIds(@Param(Constants.WRAPPER) LambdaQueryWrapper<User> wrapper,
@Param("amount") int amount);
xml复制<update id="updateBalanceByIds">
UPDATE user SET balance = balance - #{amount} ${ew.customSqlSegment}
</update>
java复制@Test
void testCustomSqlUpdate() {
List<Long> ids = List.of(1L, 2L, 4L);
int amount = 200;
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>()
.in(User::getId, ids);
userMapper.updateBalanceByIds(wrapper, amount);
}
@Param(Constants.WRAPPER)注解将Wrapper参数命名为"ew"(Entity Wrapper的缩写)${ew.customSqlSegment}会将Wrapper转换的WHERE条件插入到SQL中注意:这里使用了${}而非#{},因为我们需要直接插入SQL片段而非参数值。在真实项目中要确保这些片段是安全的,避免SQL注入。
MyBatisPlus的IService接口提供了一系列常用的服务层方法,可以大幅减少样板代码。
标准的服务层实现包含三个部分:
java复制public interface IUserService extends IService<User> {
void deductBalance(Long id, Integer money);
}
java复制@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User>
implements IUserService {
@Override
public void deductBalance(Long id, Integer money) {
// 业务逻辑实现
}
}
java复制@RestController
@RequestMapping("/users")
@RequiredArgsConstructor
public class UserController {
private final IUserService userService;
// 各种端点方法
}
IService的lambdaQuery()方法让条件查询更加优雅。例如实现一个多条件用户查询:
java复制@Override
public List<User> queryUsers(String name, Integer status,
Integer minBalance, Integer maxBalance) {
return lambdaQuery()
.like(name != null, User::getUsername, name)
.eq(status != null, User::getStatus, status)
.ge(minBalance != null, User::getBalance, minBalance)
.le(maxBalance != null, User::getBalance, maxBalance)
.list();
}
这里的技巧在于:
让我们看一个更复杂的余额扣减示例,包含状态变更和乐观锁:
java复制@Override
@Transactional
public void deductBalance(Long id, Integer money) {
// 查询用户
User user = getById(id);
// 校验逻辑...
// 计算新余额
int remainBalance = user.getBalance() - money;
// Lambda更新
boolean success = lambdaUpdate()
.set(User::getBalance, remainBalance)
.set(remainBalance <= 0, User::getStatus, 2)
.eq(User::getId, id)
.eq(User::getBalance, user.getBalance()) // 乐观锁
.update();
if (!success) {
throw new RuntimeException("并发修改冲突,请重试");
}
}
这个实现有几个值得学习的点:
set(condition, column, value)实现条件更新eq(User::getBalance, user.getBalance())实现乐观锁@Transactional确保操作的原子性对于批量插入大量数据,MyBatisPlus提供了几种方案:
yaml复制# application.yml
spring:
datasource:
url: jdbc:mysql://localhost:3306/db?rewriteBatchedStatements=true
实测10万条数据插入时间对比:
java复制wrapper.eq(name != null, "username", name);
java复制wrapper.ge("create_time", LocalDateTime.now().atZone(ZoneId.systemDefault()));
java复制// 错误示范
wrapper.apply("date_format(create_time,'%Y-%m-%d') = " + userInput);
// 正确做法
wrapper.apply("date_format(create_time,'%Y-%m-%d') = {0}", userInput);
java复制// 可能无法命中索引
wrapper.likeLeft("username", "admin");
// 更可能命中索引
wrapper.likeRight("username", "admin");
java复制// 不推荐
wrapper.and(w -> w.eq("status",1).or().eq("type",2));
// 推荐
List<User> list1 = lambdaQuery().eq("status",1).list();
List<User> list2 = lambdaQuery().eq("type",2).list();
// 合并结果
java复制wrapper.last("LIMIT " + (pageNum-1)*pageSize + "," + pageSize);
properties复制mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
在实际项目中,我发现合理使用MyBatisPlus的条件构造器可以提升约40%的开发效率,特别是在快速迭代的业务场景中。但也要注意不要过度依赖Wrapper,对于特别复杂的SQL,直接手写XML映射可能更合适。