MyBatis-Plus¶
约 2099 个字 173 行代码 预计阅读时间 9 分钟
依赖引入¶
| XML | |
|---|---|
1 2 3 4 5 | |
数据准备¶
| SQL | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | |
自动填空字段¶
假设数据库已经配置了下面两个字段:
| SQL | |
|---|---|
1 2 | |
要使用MyBatis-Plus的自动填充字段功能,首先需要在实体类上使用@TableField注解并填入需要的枚举值:
| Java | |
|---|---|
1 2 3 4 | |
接着创建一个配置类实现MetaObjectHandler,在该配置类中重写insertFill和updateFill方法:
| Java | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
此时再插入/更新数据时可以不用显式指定时间,MyBatis-Plus会自动处理时间
需要注意的是,如果在更新数据时使用的是update(wrapper updateWrapper/LambdaUpdateWrapper)的,自动填充字段将不会触发,更推荐使用下面两种方案:
- 使用带实体参数和
UpdateWrapper的重载版本:update(T entity, updateWrapper/LambdaUpdateWrapper),其中entity可以传一个临时对象,例如new User() - 手动构建携带新值对象+使用
updateById方法
为什么自动填充在使用update(wrapper)版本会失效,但是update(T entity, wrapper)版本又可以?
MyBatis-Plus的自动填充逻辑在底层需要拿到实体类对应的TableInfo,从而判断哪些字段标注了@TableField(fill = ...)。核心源码逻辑大致如下(com.baomidou.mybatisplus.core.toolkit.ReflectionKit / TableInfoHelper):
| Java | |
|---|---|
1 2 3 4 5 6 | |
其中的parameterObject它是Mapper方法的第一个参数对象,不同的调用方式,parameterObject指向如下表:
| 调用方式 | parameterObject实际指向 |
|---|---|
insert(sysUser) | sysUser这个实体对象 |
updateById(sysUser) | sysUser这个实体对象 |
update(sysUser, wrapper) | sysUser这个实体对象 |
update(null, wrapper) | 第一个参数为null,MyBatis-Plus会把wrapper当作 parameterObject |
update(wrapper) | wrapper |
对于parameterObject.getClass()来说,拿到parameterObject的运行时类型,对应表如下:
| 调用方式 | getClass() 结果 |
|---|---|
insert(sysUser) | SysUser.class |
updateById(sysUser) | SysUser.class |
update(sysUser, wrapper) | SysUser.class |
update(null, wrapper) | LambdaUpdateWrapper.class / UpdateWrapper.class |
update(wrapper) | LambdaUpdateWrapper.class / UpdateWrapper.class |
而TableInfoHelper是MyBatis-Plus的工具类,内部维护了一张类 → 表信息的映射缓存,调用getTableInfo(User.class)会返回User对应的TableInfo,里面包含:
- 表名(如
tb_sys_user) - 主键字段
- 所有加了
@TableField(fill = ...)的字段 - 逻辑删除字段
- 其他元数据
但getTableInfo(LambdaUpdateWrapper.class)会返回null,因为LambdaUpdateWrapper不是实体类,没有 @TableName 注解,MyBatis-Plus不知道它对应哪张表,也就意味着tableInfo为null,即哪张表都不知道,自然也无法判断哪些字段需要自动填充。此时直接返回原始参数,跳过updateFill,当不为null时,就会执行:
- 从
tableInfo里找出所有fill = FieldFill.INSERT/INSERT_UPDATE/UPDATE的字段; - 把这些字段信息封装成
MetaObject - 调用自定义的
MybatisPlusMetaObjectHandler.updateFill(metaObject) - 在方法里通过
strictInsertFill/strictUpdateFill设置的值,最终会被写回实体对象
MyBatis-Plus插件¶
分页插件¶
如果MyBatisPlus的版本是3.5.9以上的,那么需要引入下面的jsqlparser依赖
| XML | |
|---|---|
1 2 3 4 5 6 | |
先创建配置类:
| Java | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
MyBatis-Plus要实现分页查询需要指定两个参数:
- 当前页码
- 每一页的条目个数
在查询前需要先创建一个Page类对象,将上面两个参数传入构造函数中。接着再调用selectPage方法传入创建好的Page类对象
但是selectPage默认是需要传递两个参数的,如果是无条件查询,那么第二个参数传递null即可
创建测试类如下:
| Java | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | |
可以看到输出结果如下:
| Text Only | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | |
如果传递的当前页参数为2,那么就是从第11条开始到第20条数据,可以得到下面的结果:
| Text Only | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | |
根据官方文档的描述,Page类介绍如下:
Page 类继承了 IPage 类,实现了简单分页模型。如果你需要实现自己的分页模型,可以继承 Page 类或实现 IPage 类。Page类中有以下的属性:
| 属性名 | 类型 | 默认值 | 描述 |
|---|---|---|---|
records | List<T> | emptyList | 查询数据列表 |
total | Long | 0 | 查询列表总记录数 |
size | Long | 10 | 每页显示条数,默认 10 |
current | Long | 1 | 当前页 |
orders | List<OrderItem> | emptyList | 排序字段信息 |
optimizeCountSql | boolean | true | 自动优化 COUNT SQL |
optimizeJoinOfCountSql | boolean | true | 自动优化 COUNT SQL 是否把 join 查询部分移除 |
searchCount | boolean | true | 是否进行 count 查询 |
maxLimit | Long | 单页分页条数限制 | |
countId | String | XML 自定义 count 查询的 statementId |
如果想根据具体条件查询,则可以使用MyBatis-Plus的条件构造器,例如根据出版社名称不为“出版社1”的结果进行分页查询,每页三条,则可以写为:
| Java | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
输出结果如下:
| Text Only | |
|---|---|
1 2 3 4 5 6 7 8 9 | |
防止全表更新/删除插件¶
BlockAttackInnerInterceptor是MyBatis-Plus提供的一个安全防御型插件,用于防止因误操作或恶意攻击导致的全表更新/删除。当执行的UPDATE或DELETE语句没有WHERE条件时,该插件会抛出异常,阻止 SQL 执行
BlockAttackInnerInterceptor 基于 JSqlParser 解析 SQL,因此需要保证项目中已经引入 jsqlparser 依赖。MyBatis-Plus 3.5.9 及以上版本建议显式引入 mybatis-plus-jsqlparser。
在已有的MybatisPlusConfig配置类中添加BlockAttackInnerInterceptor:
| Java | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
创建测试类,分别执行不带条件的update和delete操作:
| Java | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | |
执行上述测试方法后,控制台会抛出如下异常:
| Text Only | |
|---|---|
1 | |
如果是全表更新,抛出的异常信息为:
| Text Only | |
|---|---|
1 | |
如果某个 Mapper 方法确实需要执行全表操作,可以在对应方法上添加@InterceptorIgnore(blockAttack = "true")注解临时绕过拦截