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
时可以用这个方法获取到Mapper
listByIds
:根据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