MyBatis-plus 2.x -> 3.x 版本升级笔记

参考链接:https://github.com/baomidou/mybatis-plus/issues/3262

1.官方更新日志

  • 升级 JDK 8 + 优化性能 Wrapper 支持 lambda 语法
  • 模块化 MP 合理的分配各个包结构
  • 移除 com.baomidou.mybatisplus.extension.injector.methods.additional 包下的过时类
  • fix: 初始化 TableInfo 中遇到多个字段有 @TableId 注解时未能抛出异常的问题
  • 改造 Wrapper 更改为 QueryWrapper UpdateWrapper
  • 重构 分页插件 消灭固定分页模型,支持 Mapper 直接返回 IPage 接口
  • feat: 所有 count 从 count(1) 变更为 count(*)
  • 相关 BUG 修复

2.各项细致变更

2.1 导入包的结构修改
2.x3.x
com.baomidou.mybatisplus.activerecord.Modelcom.baomidou.mybatisplus.extension.activerecord.Model;
com.baomidou.mybatisplus.annotations.TableFieldcom.baomidou.mybatisplus.annotation.TableField
com.baomidou.mybatisplus.annotations.TableIdcom.baomidou.mybatisplus.annotation.TableId
com.baomidou.mybatisplus.enums.IdTypecom.baomidou.mybatisplus.annotation.IdType
com.baomidou.mybatisplus.service.impl.ServiceImplcom.baomidou.mybatisplus.extension.service.impl.ServiceImpl
com.baomidou.mybatisplus.mapper.BaseMappercom.baomidou.mybatisplus.core.mapper.BaseMapper
com.baomidou.mybatisplus.plugins.Pagecom.baomidou.mybatisplus.extension.plugins.pagination.Page
2.2 CURD接口 方法名字变更
Mapper
  • 去除了insertAllColumn(T entity),updateAllColumn(T entity)方法
  • 新增update(T entity, Wrapper updateWrapper)方法
Service
  • insert和insertXXX方法改成了save和saveXXX方法
  • selectXXX方法改成了getXXX方法
  • deleteXXX改成了removeXXX方法
    在这里插入图片描述
2.3 条件构造器Wrapper
  • 删除了EntityWrapper,新增了QueryWrapper、UpdateWrapper、LambdaQueryWrapper、LambdaUpdateWrapper。
  • EntityWrapper可用QueryWrapper来做替换。
  • 2.x
// 查询条件构造器
EntityWrapper<BannerItem> wrapper = new EntityWrapper<>();
wrapper.eq("banner_id", id);
// 查询操作
List<BannerItem> bannerItems = bannerItemMapper.selectList(wrapper);
  • 3.x Version 1
// 查询条件构造器
QueryWrapper<BannerItem> wrapper = new QueryWrapper<>();
wrapper.eq("banner_id", id);
// 查询操作
List<BannerItem> bannerItems = bannerItemMapper.selectList(wrapper);
  • 3.x Version 2
// 查询条件构造器
LambdaQueryWrapper<BannerItem> wrapper = new LambdaQueryWrapper<>();
//引入lambda,避免我们在代码中写类似的于"banner_id"的硬编码
wrapper.eq(BannerItem::getBannerId, id);//面向对象思想
// 查询操作
List<BannerItem> bannerItems = bannerItemMapper.selectList(wrapper);
  • 3.x Version 3
//链式查询
List<BannerItem> bannerItems = new LambdaQueryChainWrapper<>(bannerItemMapper)
                        .eq(BannerItem::getBannerId, id)
                        .isNotNull(BannerItem::getName)
                        .list();

3.隐藏的变更

之前提到Service的CRUD接口中,deleteXXX改成了removeXXX方法,但是这并不是简单的名字变更,在返回值的判断逻辑上也有变更,具体可参考以下:

  • 2.x 版本中的delete方法

在这里插入图片描述

  • 3.x 版本中的remove方法

在这里插入图片描述
上图中参数 “Integer result” 为最终DB成功删除的条数

  • 可以看到旧版本 2.x 中只要DB成功删除的条数大于等于0即返回 true
  • 而新版 3.x 中则需要DB成功删除的条数大于等于1才返回 true

简单来说,即如果现在要删除一条DB中不存在的记录,在旧版 2.x 中会返回 true, 而在新版 3.x 中则会返回 false,所以此处需要注意自己的业务逻辑中是否有根据 remove 的返回值而作出的其他判断。

4.更多

往往更新到新版本的目的是为了获得一些新的features或者得到一些bug的修复,但其实更新到最新版本也并不是一劳永逸,也需要更多关注这个版本会不会有什么隐藏的问题,好比当前最新版本v3.4.2也会有或多或少的问题,比如说Github上有用户提到的一个Issue:

Model类每次实例化时会创建log对象,在大量创建时对CPU和内存消耗严重 #3262

一句话描述

com.baomidou.mybatisplus.extension.activerecord.Model 每次被实例化时,都会创建log对象,而log在市面上的众多实现在创建时很消耗CPU和内存,因此在业务繁忙时,会有严重的性能问题。

当前使用版本

3.4.2
理论上此问题从3.1.2开始就存在。

该问题是如何引起的?

随便新建一个类,继承com.baomidou.mybatisplus.extension.activerecord.Model,当业务代码中频繁创建Model时,CPU使用率会飙升(相比3.1.2之前的版本)。
通过JProfiler、Arthas或jstack都可以看到CPU消耗在创建log对象上。
翻了下Commit记录,此问题应该是从3.1.2开始就存在。

重现步骤

该用户提供的重现Demo,见:https://github.com/jptx1234/mp-model-test

以下测试过程均通过此Demo使用JMH性能基准测试工具完成。此Demo的日志实现为Java自带的JUL。

Demo简介
  • 原com.baomidou.mybatisplus.extension.activerecord.Model类:
public abstract class Model<T extends Model<?>> implements Serializable {
	private static final long serialVersionUID = 1L;
    private final transient Log log = LogFactory.getLog(this.getClass());
    
    public Model() {
    }
   //省略后面其他方法
}
  • 魔改Model类(注释掉Log对象实例化的一行代码,其余全部复制):
public abstract class ModelWithoutLog<T extends ModelWithoutLog<?>> implements Serializable {
	 private static final long serialVersionUID = 1L;
	 // 去掉Model类中自带的log对象,下面用到log的地方改为实时创建log
	 // private final transient Log log = LogFactory.getLog(getClass());
	 public Model() {
    }
   //省略后面其他方法
}
  • 创建两个实体分别继承两个Model类:
@Data
public class H2StudentWithLog extends Model<H2StudentWithLog> {
}

@Data
public class H2StudentWithoutLog extends ModelWithoutLog<H2StudentWithoutLog> {
}
  • 创建两个方法分别实例化两个实体类:
  //分别实例化带log和不带log的对象,对比两个方法的性能耗时。
  /**
 * 测试 - 创建带有log的对象
   */
  @Benchmark
  public H2StudentWithLog testCreateModelWithLog() {
    return new H2StudentWithLog();
  }

  /**
 * 测试 - 创建不带log的对象
   */
  @Benchmark
  public H2StudentWithoutLog testCreateModelWithoutLog() {
    return new H2StudentWithoutLog();
  }
  • JMH运行结果:
Benchmark                            Mode  Cnt    Score    Error   Units
TestMain.testCreateModelWithLog     thrpt   10    3.972 ±  0.118  ops/us   -- 每微秒能创建3.9个对象(带log)
TestMain.testCreateModelWithoutLog  thrpt   10  248.170 ±  9.412  ops/us   -- 每微秒能创建248个对象(不带log)
TestMain.testCreateModelWithLog      avgt   10    0.253 ±  0.009   us/op   -- 每次创建对象消耗0.253微秒(带log)
TestMain.testCreateModelWithoutLog   avgt   10    0.004 ±  0.001   us/op   -- 每次创建对象消耗0.004微秒(不带log)
/*
Mode中 "thrpt" 指的是单位时间内该方法执行的次数,即 ops/us 每微秒能进行几次操作。
Mode中 "avgt" 指的是每执行一次该方法消耗的时间,即 us/op ,每次操作耗时。
Cnt为测试轮数(共10轮然后取平均值)
Error为平均值的误差
*/

可以看出,在此测试环境中,Model类中有log会比无log慢两个数量级,所以当业务代码中频繁创建Model时,CPU使用率和内存占用率会大量飙升,从而导致生产性能问题。所以各位需要及时留意看自己的生产环境中是否会有同样的场景发生,并针对性作出对策。

例如,提出这个Issue的用户也发现到关键原因并给出了解决方案:

该Model类中目前只有一个方法调用到Log对象,且是在有限条件下才会使用,所以可以改为懒加载:

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值