前言
背景
在持久层使用 MyBatisPlus
的项目开发过程中,开发人员会使用 list
、selectList
等 MyBatisPlus
自带函数根据一定条件查询全表数据,由于代码的健壮性不强,部分查询条件不生效时 list
、selectList
等函数的查询结果集合过大。
目的
限制 MyBatisPlus
非分页查询情况下查询的数据量,提高项目稳定性。
思路
- 对
MyBatisPlus
中执行的非分页查询 SQL
进行拦截,添加默认最大查询数据量。
- 提供符合
MyBatisPlus
规范的 BigPage
(允许自定义分页查询的最大数据量),供大数据量查询的业务。
MyBatisPlus
分页查询,设置全局单页最大数据量。
- 查询结果集合等于设置的最大数据量时,打印
warn
级别的日志提醒或抛出异常(可配)。
限制
支持版本
MyBatisPlus 3.4.x
MyBatisPlus 3.5.x
开发限制
- 必须使用
MyBatisPlus
分页插件。
- 只能对
MyBatisPlus
语法下的 SQL
进行拦截,比如Service函数、Mapper函数、动态SQL
。(自定义实现的 Mapper
函数,暂不考虑兼容测试)
- 自定义
SQL
中的分页数据量不能控制(业务相关,不方便兼容)。
- 如果因为“程序错误”导致查询数据量过大的问题,比较难以排查。
自定义 SQL
中的分页 与 IPage
一同使用会报错,MyBatisPlus
认定为代码逻辑上的错误,这里不做处理。
数据库
兼容
DB2
Dm
MySQL
(已测试)
Oracle12c
Oracle
Postgre
SQLServer
理论上兼容(未测试)
Gauss(Oracle)
GBase(MySQL)
H2(Postgre)
HSQL(Postgre)
Kingbase(Postgre)
MariaDB(MySQL)
Oscar(MySQL)
Phoenixt(Postgre)
SQLite(Postgre)
XuGu(MySQL)
未兼容(不考虑实现)
实现
1、全表查询SQL
限制最大查询数据量
配置最大查询数量
1 2 3 4 5 6 7 8 9 10
| maxzhao: mybatis-plus: limit: enabled: true maxPageLimit: 10000 maxListLimit: 100000
|
自定义配置分页拦截器
自定义MyBatisPlus
分页拦截器。
分页拦截器需要做的是在查询之前拦截并处理SQL
:
1 2 3 4 5 6 7 8 9 10 11 12 13
| start=>start: 查询之前 end=>end: 开始查询 nodo=>end: 不做处理 是否包含分页参数=>condition: 是否包含分页参数 SQL中是否包含限制属性=>condition: SQL中是否包含限制属性 构建默认分页参数=>operation: 构建默认分页参数 构建分页SQL=>operation: 构建分页SQL start->是否包含分页参数 是否包含分页参数(yes)->构建分页SQL 是否包含分页参数(no)->SQL中是否包含限制属性 SQL中是否包含限制属性(yes)->nodo SQL中是否包含限制属性(no)->构建默认分页参数->构建分页SQL 构建分页SQL->end
|
分页参数:指的是主动设置的 IPage
。
SQL
中是否包含限制属性:指的是动态SQL
中是否包含用于限制查询条数的关键字。
MyBatisPlus
分页拦截器:是对 MyBatis
拦截器的一层封装。
注入分页
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
|
@Configuration @ConditionalOnProperty(value = "maxzhao.mybatis-plus.limit.enabled", matchIfMissing = true) @ConditionalOnClass(value = MybatisPlusInterceptor.class) public class MybatisPlusConfig { private static final Logger log = LoggerFactory.getLogger(MybatisPlusConfig.class);
@Value(value = "${maxzhao.mybatis-plus.limit.maxPageLimit:0}") private Long maxPageLimit;
@Value(value = "${maxzhao.mybatis-plus.limit.maxListLimit:0}") private Long maxListLimit;
@Order(9) @Bean @ConditionalOnMissingBean(MybatisPlusInterceptor.class) public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); PaginationForListInnerInterceptor paginationForListInnerInterceptor = new PaginationForListInnerInterceptor(); paginationForListInnerInterceptor.setMaxLimit(maxPageLimit); paginationForListInnerInterceptor.setMaxListLimit(maxListLimit); interceptor.addInnerInterceptor(paginationForListInnerInterceptor); log.info("已开启最大条数限制:分页查询单页数据量限制:{} ;全表查询数据量限制:{}", maxPageLimit, maxListLimit); return interceptor; }
}
|
2、BigPage
实现
MyBatisPlus
原分页实现类 com.baomidou.mybatisplus.extension.plugins.pagination.Page
没有 maxLimit
属性的默认值,但是我们可以通过继承 Page
来实现 BigPage
,并且给与一个较大的默认值(100W
),如果超过这个默认值,我们认为这个业务很可能存在问题(业务上真的超过100W
数据量的限制,setMaxLimit
就可以了)。
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
| import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import java.util.Optional;
public class BigPage<T> extends Page<T> {
private static final long MAX_LIMIT = 1000000L;
@Override public Long maxLimit() { this.maxLimit = Optional.ofNullable(this.maxLimit).orElse(MAX_LIMIT); return this.maxLimit; }
@Override public Long getMaxLimit() { this.maxLimit = Optional.ofNullable(this.maxLimit).orElse(MAX_LIMIT); return this.maxLimit; } }
|
3、MyBatisPlus
分页查询,设置全局单页最大数据量。
配置单页最大数据量
1 2 3 4 5 6 7 8 9 10
| maxzhao: mybatis-plus: limit: enabled: true maxPageLimit: 10000 maxListLimit: 100000
|
在 MybatisPlusInterceptor
注入分页拦截器时,设置分页拦截器的单页查询最大数据量。
4、查询数据量等于最大限制时的处理
目前有两种推荐的处理方式:
- 打印
warn
级别的日志提醒。
- 抛出异常。
这两种实现方式可以动态配置。
需要重写 com.baomidou.mybatisplus.core.override.MybatisMapperMethod
,暂时不做处理。
本文地址: https://github.com/maxzhao-it/blog/post/4e95394/