条件构造器
在BaseMapper中,有一些Wrapper参数的方法,这就是复杂一点的where条件查询。复杂的条件查询会用到Wrapper类,以及它的各种子类,我们常用的就是QueryWrapper、UpdateWrapper和LambdaQueryWrapper。
QueryWrapper
无论是修改、删除、查询,都可以使用QueryWrapper来构建查询条件。接下来看一些例子:
查询:查询出名字中带o的,存款大于等于1000元的人。代码如下:
1 2 3 4 5 6 7 8 9 10 11
| @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); }
|
更新:更新用户名为jack的用户的余额为2000,代码如下:
1 2 3 4 5 6 7 8 9
| @Test void testUpdateByQueryWrapper() { QueryWrapper<User> wrapper = new QueryWrapper<User>().eq("username", "Jack"); User user = new User(); user.setBalance(2000); userMapper.update(user, wrapper); }
|
UpdateWrapper
基于BaseMapper中的update方法更新时只能直接赋值,对于一些复杂的需求就难以实现。
例如:更新id为1,2,4的用户的余额,扣200,对应的SQL应该是:
1
| UPDATE user SET balance = balance - 200 WHERE id in (1, 2, 4)
|
SET的赋值结果是基于字段现有值的,这个时候就要利用UpdateWrapper中的setSql功能了:
1 2 3 4 5 6 7 8 9 10 11
| @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); }
|
LambdaQuery/UpdateWrapper
无论是QueryWrapper还是UpdateWrapper在构造条件的时候都需要写死字段名称,会出现字符串魔法值。这在编程规范中显然是不推荐的。
在软件工程中,“魔法值”(Magic Value) 指的是在代码中直接写死的、具有特定含义的常量数值或字符串,但没有任何常量定义或注释来解释它的用途。
那怎么样才能不写字段名,又能知道字段名呢?
其中一种办法是基于变量的gettter方法结合反射技术。因此我们只要将条件对应的字段的getter方法传递给MybatisPlus,它就能计算出对应的变量名了。而传递方法可以使用JDK8中的方法引用和Lambda表达式。
因此MybatisPlus又提供了一套基于Lambda的Wrapper,包含两个:
- LambdaQueryWrapper
- LambdaUpdateWrapper
分别对应QueryWrapper和UpdateWrapper:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| @Test void testLambdaQueryWrapper() { LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>(); wrapper.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); }
@Test void testLambdaUpdateWrapper() { LambdaUpdateWrapper<User> wrapper = new LambdaUpdateWrapper<>(); wrapper.set(User::getBalance, User::getBalance - 200) .like(User::getUsername, "o") .ge(User::getBalance, 1000); userMapper.update(null, wrapper); }
|
此外,还有其他方式如下:
1 2 3 4 5 6 7 8 9 10 11 12
| @Test void testLambdaQueryWrapper() { QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper.lambda() .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); }
|
自定义SQL
在UpdateWrapper的案例中,我们在代码中编写了更新的SQL语句:🚀 UpdateWrapper
这种写法在某些企业也是不允许的,因为SQL语句最好都维护在持久层,而不是业务层。就这个案例来说,由于条件是in语句,只能将SQL写在Mapper.xml文件,利用foreach来生成动态SQL。
这实在是太麻烦了。假如查询条件更复杂,动态SQL的编写也会更加复杂。
所以,MybatisPlus提供了自定义SQL功能,可以让我们利用Wrapper生成查询条件,再结合Mapper.xml编写SQL
基本用法
以当前案例来说,我们可以这样写:
1 2 3 4 5 6 7 8 9
| @Test void testCustomWrapper() { List<Long> ids = List.of(1L, 2L, 4L); QueryWrapper<User> wrapper = new QueryWrapper<User>().in("id", ids);
userMapper.deductBalanceByIds(200, wrapper); }
|
然后在UserMapper中自定义SQL:
1 2 3 4
| public interface UserMapper extends BaseMapper<User> { @Select("UPDATE user SET balance = balance - #{money} ${ew.customSqlSegment}") void deductBalanceByIds(@Param("money") int money, @Param("ew") QueryWrapper<User> wrapper); }
|
customSqlSegment 是 MyBatis-Plus 为 Wrapper 对象自动生成的 WHERE 条件片段(字符串)
MybatisPlusInterceptor
一般和springboot的配置类一起使用:
1 2 3 4 5 6 7 8 9 10 11 12
| @Configuration public class MybatisPlusConfig {
@Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }
|
这段代码的作用是:告诉 MyBatis-Plus 启用物理分页功能,并且分页语法要适配 MySQL 数据库。
当你在代码中调用 mapper.selectPage(page, wrapper) 时,这个插件就会自动将原本的普通查询 SQL,改写成带有 LIMIT 分页子句的 SQL,并同时执行一次 COUNT(*) 查询来获取总记录数。
关于逻辑分页和物理分页:
- 物理分页(本行代码的作用):插件会拦截即将执行的 SQL,在它发送到数据库之前进行改写。比如,你写的是 select * from user,插件会将其改写为 select * from user LIMIT 0, 10,数据库只返回 10 条数据,内存压力极小。
- 逻辑分页(内存分页):如果不加这个插件,selectPage 会退化为 select * from user,一次性把全表几百万数据全部加载到 JVM 内存中,再用 Java 代码截取 10 条,这在高并发下会直接导致内存溢出(OOM)。
更多的详情可以查看官方文档:https://baomidou.com/plugins/pagination/
参考
https://my.feishu.cn/wiki/PsyawI04ei2FQykqfcPcmd7Dnsc