MybatisPlus常用方法
简介
记录一下常用的一些使用方法。
测试环境
表SQL
1 | DROP TABLE IF EXISTS `user`; |
实体类
1 | package com.yww.demo.entity; |
执行SQL分析打印
使用p6spy
来打印SQL。
1 | <dependency> |
配置文件
1 | # 数据库配置(p6spy) |
假数据的生成
这里使用了datafaker
这个库来生成假数据。
1 | <dependency> |
随机生成600条数据。
1 |
|
代码结构
环境使用MybatisPlus
代码生成器的结构。
- service继承IService
- serviceImpl继承ServiceImpl,实现service
- mapper继承BaseMapper
补充说明
关于服务实现类的API
根据上述的代码结构,在服务的实现类中,会有两套API。
- Service CRUD接口
- Mapper CRUD接口
前者算是对Mapper接口的封装,所以前者会有Mapper接口里基本所有方法,也会添加很多封装后的方法。后者是基础的Mapper使用。
所以以下大多数使用都是基于前者,当有用到Mapper里的方法才会使用后者。
关于条件构造器
这里以查询的条件构造器为例子。
QueryWrapper
1 | QueryWrapper<User> queryWrapper = new QueryWrapper<>(); |
LambdaQueryWrapper
这个条件构造器使用了Lambda
语法,使用起来更方便,而且使用这个方法不用构造条件,不用填写表的字段,因为字段这种不能修改的对开发来说这是十分不友好的,LambdaQueryWrapper
可以直接使用实体类属性来构造条件。
使用
QueryWrapper
的lambda
方法获取1
2
3
4QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("id", 1);
LambdaQueryWrapper<User> lambda = queryWrapper.lambda();
this.getOne(lambda);使用
Wrappers.lambdaQuery()
获取(推荐)1
2LambdaQueryWrapper<User> lambda = Wrappers.lambdaQuery(User.builder().id("1").build());
this.getOne(lambda);以下的条件构造器,都是基于第二种方法生成。
防全表更新与删除插件
这个插件我感觉还是很有必要的,要是开发出现一点错误,这个插件能多一份保障。当出现全表更新或者是全表删除的SQL时,MybatisPlus
能帮我们拦截,并抛出异常。
1 | /** |
分页插件
分页时需要用到的插件。
1 | /** |
多个插件使用的情况,请将分页插件放到
插件执行链
最后面。
添加
添加单条数据
1 |
|
- 添加的数据尽量自己先提取在添加,避免添加了不该添加的数据,比如说ID,不建议传入ID,更新用户这些字段,尽量避免这些脏数据。
- 考虑添加数据的条件,比如
username
是唯一的,某些数据是不为空的,或者某些数据是有什么格式的,都可以先将提取的元素进行判断后在添加。- 只通过前端校验参数是不合理的。
批量添加
这里就不从请求上输入用户列表了,通过faker
模拟生成用户列表。
1 | /** |
for循环添加
这是不推荐的方法,但也算是可行的一种方法,这种方法效率很低。
1 |
|
通过p6spy
的打印,可以看到执行的其中一条插入语句。
1 | Consume Time:1 ms 2023-02-22 15:30:30 |
saveBatch的伪批量插入
使用saveBatch最好先通过@EnableTransactionManagement
注解开启mybatis-plus
的事务管理,不然可能会出现警告。(虽然可能不会影响操作)
1 |
|
通过p6spy
的打印,可以看到执行的其中一条插入语句。
1 | Consume Time:0 ms 2023-02-22 15:32:05 |
这里可以发现saveBatch的插入语句和for循环的插入语句是一样的,都是有10条INSERT语句,而不是真正的批量插入,只不过saveBatch是10条SQL语句一起提交,而for循环是10条语句10次提交,这里的效率差距还是很明显的,但由于这样也不算是批量插入,所以saveBatch被称为伪批量插入。
由于是一次提交,如果一次性插入成千上万条数据,也会导致数据库响应缓慢,所以当插入的数据量很大时,还是建议先分片,在提交。
1 |
|
批量插入
这种方案效率是最高的,但是相应的需要去配置一下。
创建批量插入SQL注入器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18/**
* <p>
* 批量插入SQL注入器
* </p>
*
* @author yww
* @since 2023/2/22 15:53
*/
public class InsertBatchSqlInjector extends DefaultSqlInjector {
public List<AbstractMethod> getMethodList(Class<?> mapperClass, TableInfo tableInfo) {
// 获取MybatisPlus的自带方法
List<AbstractMethod> methodList = super.getMethodList(mapperClass, tableInfo);
// 添加自定义批量插入方法,名称为insertBatchSomeColumn
methodList.add(new InsertBatchSomeColumn());
return methodList;
}
}将SQL注入器添加到
mybatis-plus
配置中1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MybatisPlusConfig {
/**
* 自定义批量插入 SQL 注入器
*/
public InsertBatchSqlInjector insertBatchSqlInjector() {
return new InsertBatchSqlInjector();
}
}在Mapper类中添加方法
1
2
3
4
5
6
7
8
9
10
11
12
public interface UserMapper extends BaseMapper<User> {
/**
* 批量插入 仅适用于mysql
* @param batchList 实体列表
* @return 影响行数
*/
int insertBatchSomeColumn( List<User> batchList);
}注意,insertBatchSomeColumn为内部指定的方法名字,不用在xml中写具体的SQL语句。IDE的警告可以忽略,上述添加的SQL注入器会帮我们注入具体的SQL语句,当然不用这么麻烦的配置,自己去xml中拼接SQL也是可以的。
可以看看官方的一个方法解释。
1 | /** |
具体的使用。
1 |
|
SQL打印为
1 | Consume Time:3 ms 2023-02-22 16:04:37 |
可以看到这个SQL的格式为
INSERT INTO user (..) VALUES (…),(…),(…),(…)
这种格式才算是批量插入,而且效率会高上很多。
同理,一次性不建议插入太多条数据,所以还是需要分片。insertBatchSomeColumn
这个mapper方法我找了一下源码,好像没见到自带的分片方法,所以需要手动分片然后进行插入。操作如下。
1 |
|
总结
使用第三种发放是效率最高的,所以建议使用第三种,当然嫌麻烦配置,直接用第二种也可以。毕竟数据量不是特别大,差距可以忽略。
尽量不要使用for循环的操作进行数据批量操作,消耗的时间是很多的。
所以之后的批量操作,就不记录使用for循环的方案了。
删除
根据ID删除单条数据
直接调用API即可。
1 |
|
根据其他条件删除
1 |
|
这个方法,这里只是用来记录一下,我感觉这个方法十分的危险,实际开发建议不要这样写,当user为空,或者是里面的字段全是空值,就会导致语句变成
DELETE FROM user
这样的SQL会导致全表数据删除,所以尽量使用ID进行删除。
实在需要用到这种方法,请先判定传入的实体对象条件不为空,而且里面的属性不能全部为空,避免出现全表删除的情况。
或者可以参考补充说明,添加防全表更新与删除插件的插件。
根据ID列表批量删除
这里有两个方法,对应的SQL语句不太一样。
removeByIds方法
直接调用API即可。
1 |
|
对应的SQL语句如下。
1 | Consume Time:0 ms 2023-02-23 17:07:41 |
同样,尽量对ID列表分片,不能一次性删除太多数据。
1 |
|
removeBatchByIds方法
直接调用API即可。
1 |
|
对应的SQL语句如下。
1 | Consume Time:1 ms 2023-02-23 17:11:09 |
这个有点类似与saveBatch
的操作,多次删除一次提交。
总结
这两种方法我进行了简单的测试。测试结果打印如下。
1 | com.yww.demo.util.TestController : 此处测试的两种方法,测试删除的数据数量为10600 |
这两个差距还算很明显的了。测试中每次删除1000条数据,要是按更大的长度进行分片,我觉得这两种的方法差距会更加的大,所以尽量使用
removeByIds
这种方法。
根据其他条件批量删除
这里以用户名举例,即传入用户名列表,批量进行删除。
由于MybatisPlus
没有对应的API,所以需要手动去编写Mapper。
1 |
|
deleteBatchByUsernames
方法的mapper可以参考批量删除ID的方法进行编写,这里就模仿removeByIds
对应的SQL进行编写。
1 | <delete id="deleteBatchByUsernames" parameterType="java.util.List"> |
更新
根据ID更新数据
直接调用API即可。
1 |
|
根据ID批量更新数据
直接调用API即可。
1 |
|
使用UpdateWrapper更新
使用UpdateWrapper
的条件去定位数据,更新条件需要设置sqlset。
以下是个简单的例子。
1 |
|
使用Wrapper更新
以下是个简单的例子。
1 |
|
saveOrUpdate
这个方法是MybatisPlus
里的一个方法,顾名思义,保存或者是更新,先去数据库根据ID查询是否存在该条数据,没有就插入数据,有的话就更新数据。大致用法和update或者是save差不多。
1 | // TableId 注解存在更新记录,否插入一条记录 |
这方法确实也挺好用的,不过是更新和插入的混合方法,有点违和就是了,所以这里就不怎么探讨了。
查询
根据ID查询数据
1 |
|
根据条件查询单条数据
1 |
|
这个方法只会返回一条数据,如果查询出多条数据会抛出异常,若是希望查出多条数据不抛出异常,可以自行设置。
getOne(queryWrapper, false)
根据条件查询数据
假设根据用户名和昵称进行查询。
1 |
|
根据ID批量查询
1 |
|
根据其他条件批量查询
这里需要自己手写SQL实现才行了,SQL语句和根据其他条件批量删除的差不多,也是通过IN
关键字拼接实现。
1 | /** |
1 | <select id="listByUserNames" resultType="com.yww.demo.entity.User"> |
简单分页查询
MybatisPlus
提供了一个分页插件,需要先去配置,配置参考上述的补充说明。
MybatisPlus
的分页方法需要IPage
这分页模型,可以自己去实现这个分页模型,也可以使用官方提供的Page
类(Page
继承了IPage
,实现了一个简单的分页模型)。Page
的对象创建可以直接使用其Page.of
方法。
1 |
|
注意,
MybatisPlus
的分页方法是会先查询所有数据的数量,然后才会进行LIMIT
分页,也就是说会有两条SQL语句,从打印的SQL就可以看到。Consume Time:19 ms
Execute SQL:SELECT COUNT(*) AS total FROM userConsume Time:3 ms
Execute SQL:SELECT id,username,password,nickname,avatar,email,sex,phone,status,create_time,create_by,update_time,update_by FROM user LIMIT 10如果用不到总记录数,不想执行
count
查询,可以通过Page.of(current, size, false)
方法去关掉。
根据条件分页查询
根据条件分页查询,其实只需要添加一个条件构造器即可。
这里拿性别举例,分页查询性别为男的数据。
1 |
|
篇末
这篇文章主要是介绍一些MybatisPlus
的API吧,其实刚刚开始写的时候,是想记录一些常用的用法的,但是后面写着写着,发现其实都是在写官方的API如何使用(确实这个项目考虑的很周全)。后来又怕别人看得更明白些,又写了很多介绍类和使用的注意事项。emm,怎么说的,是写的有些乱了,本来就是给自己看到,写写感觉就偏题了hh,不过还可以吧,将就看一下。