自定义Mybatis-plus插件(限制最大查询数量)
自定义Mybatis-plus插件(限制最大查询数量)
需求背景
一次查询如果结果返回太多(1万或更多),往往会导致系统性能下降,有时更会内存不足,影响系统稳定性,故需要做限制。
解决思路
1.经分析最后决定,应限制一次查询返回的最大结果数量不应该超出1万,对于一次返回结果大于限制的时候应该抛出异常,而不应该截取(limit 10000)最大结果(结果需求不匹配)。
2.利用mybatis拦截器技术,统一拦截sql,并真对大结果的查询先做一次count查询。
步骤一
1.1 定义拦截器PreCheckBigQueryInnerInterceptor
public class PreCheckBigQueryInnerInterceptor implements InnerInterceptor {}
1.2 重写willDoQuery方法
public boolean willDoQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
// 解析sql
Statement stmt = CCJSqlParserUtil.parse(boundSql.getSql());
if (stmt instanceof Select) {
PlainSelect selectStmt = (PlainSelect) ((Select) stmt).getSelectBody();
if (Objects.nonNull(selectStmt.getLimit())) {
//包含limit查询
return true;
}
for (SelectItem selectItem : selectStmt.getSelectItems()) {
//计数查询 count();
SelectExpressionItem selectExpressionItem = (SelectExpressionItem) selectItem;
if (selectExpressionItem.getExpression() instanceof Function) {
//包含function查询
return true;
}
}
Long aLong = doQueryCount(executor, ms, parameter, rowBounds, resultHandler, boundSql);
if (aLong == 0L) {
return false;
}
if (aLong > 20) {
throw new RuntimeException("单个查询结果大于20条!!!");
}
}
return true;
}
1.3 代码解析
1.3.1 利用CCJSqlParserUtil解析sql,并判断sql类型,只对Select的SQL拦击.
1.3.2 对于已有limit的sql查询,直接放行.
1.3.3 对于包含function查询(例如count(1)计算,max()...),直接放行.
1.3.4 否则判断为大结果查询,执行(doQueryCount)与查询数量.
1.3.5 对于大于指定数量的结果,抛出异常.
1.4 定义doQueryCount方法
private Long doQueryCount(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
MappedStatement countMs = buildAutoCountMappedStatement(ms);
String countSqlStr = autoCountSql(true, boundSql.getSql());
PluginUtils.MPBoundSql mpBoundSql = PluginUtils.mpBoundSql(boundSql);
BoundSql countSql = new BoundSql(countMs.getConfiguration(), countSqlStr, mpBoundSql.parameterMappings(), parameter);
PluginUtils.setAdditionalParameter(countSql, mpBoundSql.additionalParameters());
CacheKey cacheKey = executor.createCacheKey(countMs, parameter, rowBounds, countSql);
Object result = executor.query(countMs, parameter, rowBounds, resultHandler, cacheKey, countSql).get(0);
System.out.println(result);
return (result == null ? 0L : Long.parseLong(result.toString()));
}
代码解读:参考PaginationInnerInterceptor(mybatis-plus)分页插件
1.4.1:构造MappedStatement对象buildAutoCountMappedStatement(ms),MappedStatement相当于一个存储 SQL 语句、输入参数和输出结果映射等信息的封装体,它对应一条 SQL 语句,并包含了该 SQL 语句执行所需的所有信息。如下代码
<mapper namespace="com.example.UserMapper">
<select id="selectAllUsers" resultType="com.example.User">
SELECT * FROM user
</select>
</mapper>
注意:必须重新构造,不能直接使用入参中的ms
1.4.2:autoCountSql(true, boundSql.getSql()) 定义并优化计数查询语句
String.format("SELECT COUNT(1) FROM (%s) TOTAL", originalSql);
1.4.3: 执行查询executor.query
步骤二
1.1 注册拦截器PreCheckBigQueryInnerInterceptor
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));//分页插件(Mybatis-plus)
interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());//防止全表更新(Mybatis-plus)
interceptor.addInnerInterceptor(new PreCheckBigQueryInnerInterceptor());//防止全表查询(自定义插件)
return interceptor;
}
知识小结:
- MybatisPlusInterceptor
public class MybatisPlusInterceptor implements Interceptor {
@Setter
private List<InnerInterceptor> interceptors = new ArrayList<>();
}
他是基于mybatis的Interceptor接口做的拦截器,上文中我们 注册拦截器PreCheckBigQueryInnerInterceptor的拦截器其实添加到MybatisPlusInterceptor.interceptors集合中。
- 为啥重写willDoQuery见代码而不是beforeQuery
public Object intercept(Invocation invocation) throws Throwable {
......
for (InnerInterceptor query : interceptors) {
if (!query.willDoQuery(executor, ms, parameter, rowBounds, resultHandler, boundSql)) {
return Collections.emptyList();
}
query.beforeQuery(executor, ms, parameter, rowBounds, resultHandler, boundSql);
}
......
return invocation.proceed();
}
2.1 willDoQuery先于beforeQuery方法,且一定会执行
自定义Mybatis-plus插件(限制最大查询数量)的更多相关文章
- springboot多数据源动态切换和自定义mybatis分页插件
1.配置多数据源 增加druid依赖 完整pom文件 数据源配置文件 route.datasource.driver-class-name= com.mysql.jdbc.Driver route.d ...
- Mybatis的插件 PageHelper 分页查询使用方法
参考:https://blog.csdn.net/ckc_666/article/details/79257028 Mybatis的一个插件,PageHelper,非常方便mybatis分页查询,国内 ...
- Spring Boot入门系列(十七)整合Mybatis,创建自定义mapper 实现多表关联查询!
之前讲了Springboot整合Mybatis,介绍了如何自动生成pojo实体类.mapper类和对应的mapper.xml 文件,并实现最基本的增删改查功能.mybatis 插件自动生成的mappe ...
- springboot结合mybatis使用pageHelper插件进行分页查询
1.pom相关依赖引入 <dependencies> <dependency> <groupId>org.springframework.boot</grou ...
- SSM 使用 mybatis 分页插件 pagehepler 实现分页
使用分页插件的原因,简化了sql代码的写法,实现较好的物理分页,比写一段完整的分页sql代码,也能减少了误差性. Mybatis分页插件 demo 项目地址:https://gitee.com/fre ...
- mybatis分页插件PageHelp的使用
1.简介 PageHelper 是国内非常优秀的一款开源的 mybatis 分页插件,它支持基本主流与常用的数据库,例如 mysql.oracle.mariaDB.DB2.SQLite.Hsqld ...
- Mybatis分页插件的使用流程
如果你也在用Mybatis,建议尝试该分页插件,这一定是最方便使用的分页插件.该插件支持任何复杂的单表.多表分页. 1.引入PageHelper的jar包 在pom.xml中添加如下依赖: 12345 ...
- Mybatis框架插件PageHelper的使用
在web开发过程中涉及到表格时,例如dataTable,就会产生分页的需求,通常我们将分页方式分为两种:前端分页和后端分页. 前端分页 一次性请求数据表格中的所有记录(ajax),然后在前端缓存并且计 ...
- 基于Mybatis分页插件PageHelper
基于Mybatis分页插件PageHelper 1.分页插件使用 1.POM依赖 PageHelper的依赖如下.需要新的版本可以去maven上自行选择 <!-- PageHelper 插件分页 ...
- 2017.12.25 Mybatis物理分页插件PageHelper的使用(二)
参考来自: 官方文档的说明:https://github.com/pagehelper/Mybatis-PageHelper/blob/master/wikis/zh/HowToUse.md 上篇博客 ...
随机推荐
- winIO介绍
WinIO程序库允许在32位的Windows应用程序中直接对I/O端口和物理内存进行存取操作.通过使用一种内核模式的设备驱动器和其它几种底层编程技巧,它绕过了Windows系统的保护机制. 因为需要加 ...
- TCP 为什么是 三次 握手 不是两次 不是四次
为什么不是两次 (1) 防止 历史 旧数据 连接 客户端连续发送多次 SYN 建⽴连接的报⽂,在⽹络拥堵等情况下: ● ⼀个「旧 SYN 报⽂」⽐「最新的 SYN 」 报⽂早到达了服务端: ● 那 ...
- Spring校验:@Validated和@Valid区别
结论: Spring validation验证框架对入参实体进行嵌套验证必须在相应属性(字段)加上@Valid而不是@Validated Spring Validation验证框架对参数的验证机制提供 ...
- Angular Material TreeTable Component 使用教程
一. 安装 npm i ng-material-treetable --save npm i @angular/material @angular/cdk @angular/animations -- ...
- 使用Kong网关API接口配置
一.Upstream1.创建Upstream: curl -i -X POST IPAddress:8001/upstreams -d 'name=upstream-test' -d 'slots=1 ...
- 使用Navicat查询后 , 在结果处更改数据
参考资料: https://blog.csdn.net/weixin_43786801/article/details/125364995 问题: 在使用Navicat查询是,往往想直接对查询结果进行 ...
- spring mvc加载顺序
contextLoaderLister extends ContextLoader 实现 ServletContextListener接口 contextLoaderLister 是一个观察查模式 由 ...
- linux 常用的查找命令
linux 常用的查找命令 查找文件内容 grep grep -nr "str" path -nr: n是line number行号,r是recursive,可以理解为遍历文件文件 ...
- python轮流监听多台服务器资源情况
在主动持续监听某台服务器基础上,优化为同时监听多台服务器资源占用情况: 优点:较初版,设备监听范围有了明显提升: 缺点:主动式,轮询方式,实时性较差. #-*- coding: utf-8 -*- # ...
- 53.cin、cin.get()、cin.getline()、getline()、gets()等函数的用法
1.cin 用法1:最基本,也是最常用的用法,输入一个数字: #pragma warning(disable:4996) #define _CRT_SECURE_NO_WARNINGS 1 #incl ...