参考链接: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.x | 3.x |
---|---|
com.baomidou.mybatisplus.activerecord.Model | com.baomidou.mybatisplus.extension.activerecord.Model; |
com.baomidou.mybatisplus.annotations.TableField | com.baomidou.mybatisplus.annotation.TableField |
com.baomidou.mybatisplus.annotations.TableId | com.baomidou.mybatisplus.annotation.TableId |
com.baomidou.mybatisplus.enums.IdType | com.baomidou.mybatisplus.annotation.IdType |
com.baomidou.mybatisplus.service.impl.ServiceImpl | com.baomidou.mybatisplus.extension.service.impl.ServiceImpl |
com.baomidou.mybatisplus.mapper.BaseMapper | com.baomidou.mybatisplus.core.mapper.BaseMapper |
com.baomidou.mybatisplus.plugins.Page | com.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方法,但是这并不是简单的名字变更,在返回值的判断逻辑上也有变更,具体可参考以下:
上图中参数 “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对象,且是在有限条件下才会使用,所以可以改为懒加载:
- Model中的Log默认为空,在使用到log的地方才调用getLog()方法获取log的实例,在getLog()方法中对log懒加载。
具体变更: https://github.com/baomidou/mybatis-plus/commit/c0a9ed1c5e22008d50fc2f2a055fdfba3e94c789 - 修复效果:效率及耗时几乎一致
PS:此变更也会在v3.4.3中发布