MybatisPlus默认最大查询数据量

前言

背景

在持久层使用 MyBatisPlus 的项目开发过程中,开发人员会使用 listselectListMyBatisPlus 自带函数根据一定条件查询全表数据,由于代码的健壮性不强,部分查询条件不生效时 listselectList 等函数的查询结果集合过大。

目的

限制 MyBatisPlus 非分页查询情况下查询的数据量,提高项目稳定性。

思路

  1. MyBatisPlus 中执行的非分页查询 SQL 进行拦截,添加默认最大查询数据量。
  2. 提供符合 MyBatisPlus规范的 BigPage(允许自定义分页查询的最大数据量),供大数据量查询的业务。
  3. MyBatisPlus 分页查询,设置全局单页最大数据量。
  4. 查询结果集合等于设置的最大数据量时,打印 warn 级别的日志提醒或抛出异常(可配)。

限制

支持版本

  1. MyBatisPlus 3.4.x
  2. MyBatisPlus 3.5.x

开发限制

  1. 必须使用 MyBatisPlus 分页插件。
  2. 只能对 MyBatisPlus 语法下的 SQL 进行拦截,比如Service函数、Mapper函数、动态SQL。(自定义实现的 Mapper 函数,暂不考虑兼容测试
  3. 自定义 SQL 中的分页数据量不能控制(业务相关,不方便兼容)。
  4. 如果因为“程序错误”导致查询数据量过大的问题,比较难以排查。

自定义 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)

未兼容(不考虑实现)

  • SQLServer2005
  • Sybase

实现

1、全表查询SQL限制最大查询数据量

配置最大查询数量

1
2
3
4
5
6
7
8
9
10
maxzhao:
mybatis-plus:
# 最大查询条数限制
limit:
# 开启限制(默认开启)
enabled: true
# 分页查询最大条数限制(默认 1w)
maxPageLimit: 10000
# 全表查询最大条数限制(默认 10w)
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
/**
* 添加 MyBatisPlus 最大查询数据量限制 <br>
* 默认开启
*
* @author zhaoliansheng
* @since 2022-06-29 9:55
*/
@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);
/**
* 分页查询最大条数限制 <br>
* 默认1w
*/
@Value(value = "${maxzhao.mybatis-plus.limit.maxPageLimit:0}")
private Long maxPageLimit;
/**
* 全表查询最大条数限制 <br>
* 默认10w
*/
@Value(value = "${maxzhao.mybatis-plus.limit.maxListLimit:0}")
private Long maxListLimit;

/**
* 配置 MyBatisPlus 分页插件
*/
@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;

/**
* MyBatisPlus 大数据量的分页查询
*
* @author zhaoliansheng
* @since 2022-06-29 10:38
*/
public class BigPage<T> extends Page<T> {
/**
* 默认 100W 数据量
*/
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
# 分页查询最大条数限制(默认 1w)
maxPageLimit: 10000
# 全表查询最大条数限制(默认 10w)
maxListLimit: 100000

MybatisPlusInterceptor 注入分页拦截器时,设置分页拦截器的单页查询最大数据量。

4、查询数据量等于最大限制时的处理

目前有两种推荐的处理方式:

  1. 打印 warn 级别的日志提醒。
  2. 抛出异常。

这两种实现方式可以动态配置。

需要重写 com.baomidou.mybatisplus.core.override.MybatisMapperMethod暂时不做处理

本文地址: https://github.com/maxzhao-it/blog/post/4e95394/