MyBatisPlus从零到一:快速入门与核心功能详解(3)
2.3 Service 接口:
MybatisPlus 不仅提供了 BaseMapper,还提供了通用的 Service 接口及默认实现,封装了一些常用的 service 模板方法。
通用接口为IService,默认实现为ServiceImpl,其中封装的方法可以分为以下几类:
save:新增
remove:删除
update:更新
get:查询单个结果
list:查询集合结果
count:计数
page:分页查询
2.3.1 CRUD:
这里带着大家一起看看 Mybatis plus 提供的方法都有什么,并解释一些常用的方法。
新增:

save是新增单个元素saveBatch是批量新增saveOrUpdate是根据id判断,如果数据存在就更新,不存在则新增saveOrUpdateBatch是批量的新增或修改删除:

removeById:根据id删除
removeByIds:根据id批量删除
removeByMap:根据Map中的键值对为条件删除
remove(Wrapper<T>):根据Wrapper条件删除
removeBatchByIds:也是根据 id 批量进行删除,但是删除的实现方式不一样(removeByIds:使用一条 SQL 语句,配合 in 子句一次性执行删除操作,性能比较高。removeBatchByIds:会生成多条的 SQL 语句,每条 SQL 语句逐个执行,性能比 removeByIds 差,但是适用于数据量非常大的时候,可以有效避免一条 SQL 语句过长的情况)。
修改:

updateById:根据id修改
update(Wrapper<T>):根据UpdateWrapper修改,Wrapper中包含set和where部分
update(T,Wrapper<T>):按照T内的数据修改与Wrapper匹配到的数据
updateBatchById:根据id批量修改
Get:

getById:根据id查询1条数据getOne(Wrapper<T>):根据Wrapper查询1条数据getBaseMapper:获取Service内的BaseMapper的子类,某些时候需要直接调用Mapper内的自定义SQL时可以用这个方法获取到MapperlistByIds:根据id批量查询list(Wrapper<T>):根据Wrapper条件查询多条数据list():查询所有count():统计所有数量count(Wrapper<T>):统计符合Wrapper条件的数据数量

Count:

getBaseMapper:
当我们在 service 中要调用 Mapper 中自定义 SQL 时,就必须获取 service 对应的 Mapper,就可以通过这个方法:

2.3.2 基本用法:
由于Service中经常需要定义与业务有关的自定义方法,因此我们不能直接使用IService,而是自定义Service接口,然后继承IService的拓展方法,由于都是接口,继承了之后,根据 Java 语法,需要我们自己实现,这显然不符合 Mybatis plus 的创建初衷,于是 Mybatis plus 自己提供了一个 IService 的实现类 ServiceImpl,我们自己的实现类只要继承该实现类,就可以直接使用方法了。
上面的这段文字可以抽象成下面的这张图片:

// UserService 的接口
public interface IUserService extends IService<User> {
}
// UserService 的实现类
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService{
}由于这部分的代码调用演示和上面的都差不多,无非就是一个是在 Controller 层调用,一个是在 Service 层进行调用,所以这里就不再进行演示。
2.3.3 Lambda:
IService 中还提供了 Lambda 功能来简化我们的复杂查询及更新功能。
下面通过两个两个案例分别学习 lambdaQuery() 和 lambdaUpdate()。
案例一:
实现一个根据复杂条件查询用户的接口,查询条件如下:
name:用户名关键字,可以为空
status:用户状态,可以为空
minBalance:最小余额,可以为空
maxBalance:最大余额,可以为空
代码如下:
// UserQuery 实体
@Data
@ApiModel(description = "用户查询条件实体")
public class UserQuery {
@ApiModelProperty("用户名关键字")
private String name;
@ApiModelProperty("用户状态:1-正常,2-冻结")
private Integer status;
@ApiModelProperty("余额最小值")
private Integer minBalance;
@ApiModelProperty("余额最大值")
private Integer maxBalance;
}
// Controller 层的代码
@GetMapping("/list")
@ApiOperation("通过 UserQuery 查询用户")
public List<User> queryUser(UserQuery query) {
//1. 准备工作
String username = query.getName();
Integer status = query.getStatus();
Integer minBalance = query.getMinBalance();
Integer maxBalance = query.getMaxBalance();
//2. 构造条件并进行查询
List<User> users = userService.lambdaQuery()
.like(username != null, User::getUsername, username)
.eq(status != null, User::getStatus, status)
.ge(minBalance != null, User::getBalance, minBalance)
.le(maxBalance != null, User::getBalance, maxBalance)
.list();
//3. 返回结果
return users;
}可以发现 lambdaQuery 方法中除了构建条件,还需要在链式编程的最后添加一个list(),这是在告诉 MybatisPlus,我们的调用结果需要是一个 list 集合。这里不仅可以用list(),可选的方法有:
.one():最多1个结果
.list():返回集合结果
.count():返回计数结果
MybatisPlus 会根据链式编程的最后一个方法来判断最终的返回结果。
案例二:
根据 id 修改用户余额,如果扣减后余额为 0,则将用户 status 修改为冻结状态。
代码实现如下:
@Transactional
@Override
public void deductBalance(Long id, Integer money) {
//1. 检查用户是否存在
User user = this.getById(id);
if(user == null){
throw new RuntimeException("用户不存在");
}
//2. 检查用户状态
if(user.getStatus() == 2){
throw new RuntimeException("用户状态异常");
}
//3. 检查用户余额是否足够
if(user.getBalance() - money < 0){
throw new RuntimeException("用户余额不足");
}
// 剩余的金额
int newMoney = user.getBalance() - money;
//4. 扣减余额
this.lambdaUpdate()
.set(User::getBalance,newMoney)
.set(newMoney == 0, User::getStatus,2)// 动态判断是否要更新状态
.eq(User::getId,id)
.eq(User::getBalance,user.getBalance())// 乐观锁,防止在多线程的情况下出现异常。
.update();
}2.3.4 批量新增:
IService 中的批量新增功能使用起来非常方便,但有一点注意事项,我们先来测试一下。
MybatisPlus 的批处理(插入 10 万条数据):
@Test
void testSaveBatch() {
// 准备10万条数据
List<User> list = new ArrayList<>(1000);
long b = System.currentTimeMillis();
for (int i = 1; i <= 100000; i++) {
list.add(buildUser(i));
// 每1000条批量插入一次
if (i % 1000 == 0) {
userService.saveBatch(list);
list.clear();
}
}
long e = System.currentTimeMillis();
System.out.println("耗时:" + (e - b));
}
private User buildUser(int i) {
User user = new User();
user.setUsername("user_" + i);
user.setPassword("123");
user.setPhone("" + (18688190000L + i));
user.setBalance(2000);
user.setInfo("{\"age\": 24, \"intro\": \"英文老师\", \"gender\": \"female\"}");
user.setCreateTime(LocalDateTime.now());
user.setUpdateTime(user.getCreateTime());
return user;
}执行最终耗时如下:

MybatisPlus的批处理是基于PrepareStatement的预编译模式,然后批量提交,最终在数据库执行时还是会有多条 insert 语句,逐条插入数据。SQL 类似这样:
Preparing: INSERT INTO user ( username, password, phone, info, balance, create_time, update_time ) VALUES ( ?, ?, ?, ?, ?, ?, ? ) Parameters: user_1, 123, 18688190001, "", 2000, 2023-07-01, 2023-07-01 Parameters: user_2, 123, 18688190002, "", 2000, 2023-07-01, 2023-07-01 Parameters: user_3, 123, 18688190003, "", 2000, 2023-07-01, 2023-07-01
而如果想要得到最佳性能,最好是将多条 SQL 合并为一条,像这样:
INSERT INTO user ( username, password, phone, info, balance, create_time, update_time ) VALUES (user_1, 123, 18688190001, "", 2000, 2023-07-01, 2023-07-01), (user_2, 123, 18688190002, "", 2000, 2023-07-01, 2023-07-01), (user_3, 123, 18688190003, "", 2000, 2023-07-01, 2023-07-01), (user_4, 123, 18688190004, "", 2000, 2023-07-01, 2023-07-01);
这要怎么进行转化呢?
MySQL的客户端连接参数中有这样的一个参数:rewriteBatchedStatements。顾名思义,就是重写批处理的statement语句。
这个参数的默认值是 false,我们需要修改连接参数,将其配置为 true。
spring: datasource: url: jdbc:mysql://127.0.0.1:3306/mp?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true driver-class-name: com.mysql.cj.jdbc.Driver username: root password: root
再次测试插入10万条数据,可以发现速度有非常明显的提升:

本文系转载,版权归原作者所有,如若侵权请联系我们进行删除!
云掣基于多年在运维领域的丰富时间经验,编写了《云运维服务白皮书》,欢迎大家互相交流学习:
《云运维服务白皮书》下载地址:https://fs80.cn/v2kbbq
想了解更多大数据运维托管服务、数据库运维托管服务、应用系统运维托管服务的的客户,欢迎点击云掣官网沟通咨询:https://yunche.pro/?t=shequ





