自定义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 上篇博客 ...
随机推荐
- CF 1020B Badge
之前写过DFS的题,但是或多或少有参考别人的思路,最近开始专攻DFS,下面这道题就是DFS练习第一题CF 1020B,之后训练完后抽时间写一篇DFS专项. B. Badge time limit ...
- C++ vector的emplace_back函数
C++ STL的vector相信大家一定都知道,它是一个一般用来当做可变长度列表的类.在C++11之前,一般给vector插入新元素用得都是push_back函数,比如下面这样: std::vecto ...
- VSCode-关于自动格式化问题
Code ->首选项 ->设置 ,在搜索框上输入"格式化" 设置格式化后挂号会自动补齐,关键字会自动添加空格,
- 用for打印九九乘法表
package com.jiemo.struct;public class ForShabi4 { public static void main(String[] args) { //1.先打印第一 ...
- 三种遍历的方法(map和forEach的区别)
一. for循环 arr[index]可以改变原数组 二. forEach方法 forEach方法的返回值是一个undefined: 2. 在循环体内改变item的值不会影响原数组,而是只在循环体内生 ...
- HTML笔记(一) HTML相关概念
一 HTML概述 1.HTML文档基本结构 先来看一个关于HTML的例子,里面包含了几个主要的HTML标签: <!DOCTYPE html> <html> <head&g ...
- 关于Windows10纯净启动的相关修改流程
关于纯净启动的相关流程 自动登录 打开命令提示符运行control userpasswords2后回车 在弹出的用户账户窗口中,取消勾选"要使用本计算机,用户必须输入用户名和密码" ...
- 后端008_配置Security登录授权过滤器
------------恢复内容开始------------ 现在我们就可以去进行springscurity的配置了.首先我们新建一个配置类.然后该类需要添加@Configuration注解,然后还要 ...
- ChatGPT的那些事 -1- 背景资料
ChatGPT的那些事 -1- 背景资料 多处搬运,学无止境 目 录 1 关键词 1 1.1. AIGC(百度百科) 1 1.2. AlphaGo(百度百科) 1 1.3. ChatG ...
- For循环用法-打印乘法表
for循环可以遍历某一对象(遍历:通俗点说,就是把这个循环中的第一个元素到最后一个元素依次访问一次).for循环的结构如下 具体例子打印乘法表: #打印乘法表: for i in range(1, ...