自定义Mybatis-plus插件(限制最大查询数量)

需求背景

​ 一次查询如果结果返回太多(1万或更多),往往会导致系统性能下降,有时更会内存不足,影响系统稳定性,故需要做限制。

解决思路

1.经分析最后决定,应限制一次查询返回的最大结果数量不应该超出1万,对于一次返回结果大于限制的时候应该抛出异常,而不应该截取(limit 10000)最大结果(结果需求不匹配)。

2.利用mybatis拦截器技术,统一拦截sql,并真对大结果的查询先做一次count查询。

步骤一

1.1 定义拦截器PreCheckBigQueryInnerInterceptor

  1. public class PreCheckBigQueryInnerInterceptor implements InnerInterceptor {}
1.2 重写willDoQuery方法
  1. public boolean willDoQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
  2. // 解析sql
  3. Statement stmt = CCJSqlParserUtil.parse(boundSql.getSql());
  4. if (stmt instanceof Select) {
  5. PlainSelect selectStmt = (PlainSelect) ((Select) stmt).getSelectBody();
  6. if (Objects.nonNull(selectStmt.getLimit())) {
  7. //包含limit查询
  8. return true;
  9. }
  10. for (SelectItem selectItem : selectStmt.getSelectItems()) {
  11. //计数查询 count();
  12. SelectExpressionItem selectExpressionItem = (SelectExpressionItem) selectItem;
  13. if (selectExpressionItem.getExpression() instanceof Function) {
  14. //包含function查询
  15. return true;
  16. }
  17. }
  18. Long aLong = doQueryCount(executor, ms, parameter, rowBounds, resultHandler, boundSql);
  19. if (aLong == 0L) {
  20. return false;
  21. }
  22. if (aLong > 20) {
  23. throw new RuntimeException("单个查询结果大于20条!!!");
  24. }
  25. }
  26. return true;
  27. }
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方法
  1. private Long doQueryCount(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
  2. MappedStatement countMs = buildAutoCountMappedStatement(ms);
  3. String countSqlStr = autoCountSql(true, boundSql.getSql());
  4. PluginUtils.MPBoundSql mpBoundSql = PluginUtils.mpBoundSql(boundSql);
  5. BoundSql countSql = new BoundSql(countMs.getConfiguration(), countSqlStr, mpBoundSql.parameterMappings(), parameter);
  6. PluginUtils.setAdditionalParameter(countSql, mpBoundSql.additionalParameters());
  7. CacheKey cacheKey = executor.createCacheKey(countMs, parameter, rowBounds, countSql);
  8. Object result = executor.query(countMs, parameter, rowBounds, resultHandler, cacheKey, countSql).get(0);
  9. System.out.println(result);
  10. return (result == null ? 0L : Long.parseLong(result.toString()));
  11. }
代码解读:参考PaginationInnerInterceptor(mybatis-plus)分页插件
1.4.1:构造MappedStatement对象buildAutoCountMappedStatement(ms),MappedStatement相当于一个存储 SQL 语句、输入参数和输出结果映射等信息的封装体,它对应一条 SQL 语句,并包含了该 SQL 语句执行所需的所有信息。如下代码
  1. <mapper namespace="com.example.UserMapper">
  2. <select id="selectAllUsers" resultType="com.example.User">
  3. SELECT * FROM user
  4. </select>
  5. </mapper>

注意:必须重新构造,不能直接使用入参中的ms

1.4.2:autoCountSql(true, boundSql.getSql()) 定义并优化计数查询语句
  1. String.format("SELECT COUNT(1) FROM (%s) TOTAL", originalSql);
1.4.3: 执行查询executor.query

步骤二

1.1 注册拦截器PreCheckBigQueryInnerInterceptor

  1. @Bean
  2. public MybatisPlusInterceptor mybatisPlusInterceptor() {
  3. MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
  4. interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));//分页插件(Mybatis-plus)
  5. interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());//防止全表更新(Mybatis-plus)
  6. interceptor.addInnerInterceptor(new PreCheckBigQueryInnerInterceptor());//防止全表查询(自定义插件)
  7. return interceptor;
  8. }

知识小结:

  1. MybatisPlusInterceptor
  1. public class MybatisPlusInterceptor implements Interceptor {
  2. @Setter
  3. private List<InnerInterceptor> interceptors = new ArrayList<>();
  4. }

​ 他是基于mybatis的Interceptor接口做的拦截器,上文中我们 注册拦截器PreCheckBigQueryInnerInterceptor的拦截器其实添加到MybatisPlusInterceptor.interceptors集合中。

  1. 为啥重写willDoQuery见代码而不是beforeQuery
  1. public Object intercept(Invocation invocation) throws Throwable {
  2. ......
  3. for (InnerInterceptor query : interceptors) {
  4. if (!query.willDoQuery(executor, ms, parameter, rowBounds, resultHandler, boundSql)) {
  5. return Collections.emptyList();
  6. }
  7. query.beforeQuery(executor, ms, parameter, rowBounds, resultHandler, boundSql);
  8. }
  9. ......
  10. return invocation.proceed();
  11. }

2.1 willDoQuery先于beforeQuery方法,且一定会执行

自定义Mybatis-plus插件(限制最大查询数量)的更多相关文章

  1. springboot多数据源动态切换和自定义mybatis分页插件

    1.配置多数据源 增加druid依赖 完整pom文件 数据源配置文件 route.datasource.driver-class-name= com.mysql.jdbc.Driver route.d ...

  2. Mybatis的插件 PageHelper 分页查询使用方法

    参考:https://blog.csdn.net/ckc_666/article/details/79257028 Mybatis的一个插件,PageHelper,非常方便mybatis分页查询,国内 ...

  3. Spring Boot入门系列(十七)整合Mybatis,创建自定义mapper 实现多表关联查询!

    之前讲了Springboot整合Mybatis,介绍了如何自动生成pojo实体类.mapper类和对应的mapper.xml 文件,并实现最基本的增删改查功能.mybatis 插件自动生成的mappe ...

  4. springboot结合mybatis使用pageHelper插件进行分页查询

    1.pom相关依赖引入 <dependencies> <dependency> <groupId>org.springframework.boot</grou ...

  5. SSM 使用 mybatis 分页插件 pagehepler 实现分页

    使用分页插件的原因,简化了sql代码的写法,实现较好的物理分页,比写一段完整的分页sql代码,也能减少了误差性. Mybatis分页插件 demo 项目地址:https://gitee.com/fre ...

  6. mybatis分页插件PageHelp的使用

    1.简介 ​ PageHelper 是国内非常优秀的一款开源的 mybatis 分页插件,它支持基本主流与常用的数据库,例如 mysql.oracle.mariaDB.DB2.SQLite.Hsqld ...

  7. Mybatis分页插件的使用流程

    如果你也在用Mybatis,建议尝试该分页插件,这一定是最方便使用的分页插件.该插件支持任何复杂的单表.多表分页. 1.引入PageHelper的jar包 在pom.xml中添加如下依赖: 12345 ...

  8. Mybatis框架插件PageHelper的使用

    在web开发过程中涉及到表格时,例如dataTable,就会产生分页的需求,通常我们将分页方式分为两种:前端分页和后端分页. 前端分页 一次性请求数据表格中的所有记录(ajax),然后在前端缓存并且计 ...

  9. 基于Mybatis分页插件PageHelper

    基于Mybatis分页插件PageHelper 1.分页插件使用 1.POM依赖 PageHelper的依赖如下.需要新的版本可以去maven上自行选择 <!-- PageHelper 插件分页 ...

  10. 2017.12.25 Mybatis物理分页插件PageHelper的使用(二)

    参考来自: 官方文档的说明:https://github.com/pagehelper/Mybatis-PageHelper/blob/master/wikis/zh/HowToUse.md 上篇博客 ...

随机推荐

  1. nuxtjs项目空白路由强跳到首页

    1.根目录下新建middleware文件夹并新建文件unknownRoute.js,代码如下 /** * 未知路由重定向 到首页 */ export default ({store, route, r ...

  2. [C++] Linux TCP Socket 实例- 阻塞

    Linux平台 TCP Socket通信实例,发现用代码注释记笔记也不错 TCP server 阻塞 1 // 两个进程通过socket进行通信,client需要知道server的,server却不需 ...

  3. (二).JavaScript的运算符和表达式,数据类型转化

    4. 运算符和表达式 4.3 赋值运算符和表达式 1.赋值运算符 = 作用:赋值运算符就是将右边的内容赋值给左边的变量或属性. var result = 1 + 2; 2.复合赋值运算符 +=,-=, ...

  4. k8s介绍和学习思路

    1. Kubernetes介绍 Kubernetes是一个完备的分布式系统支撑平台.具备完备的集群管理能力,包括多层次的安全防护和准入机制.多租户应用支撑能力.透明的服务注册和服务发现机制.内建的智能 ...

  5. 使用 Transformers 在你自己的数据集上训练文本分类模型

    最近实在是有点忙,没啥时间写博客了.趁着周末水一文,把最近用 huggingface transformers 训练文本分类模型时遇到的一个小问题说下. 背景 之前只闻 transformers 超厉 ...

  6. 2020/10/3笔记-网络概述、拓扑类型、OSI模型

    网络(network) 1.什么是网络 计算机网络(简称为网络)由若干节点(node)和连接的链路组成.网络中的节点可以是计算机.集线器.交换机或路由器等. 2.网络的作用是什么 网络最终为了解决的问 ...

  7. python中创建列表、元组、字符串、字典

    >>> a = ["aaa","bbb","ccc"] ## 列表,中括号,逗号 >>> type(a) ...

  8. 记一次yapi部署过程

    一.为什么用yapi yapi基于文档注释生成,没有代码的入侵. 同一个工程的接口文档可以导出多个项目中,分权限查看. 可以本地化部署,统一的接口文档,支持其他的文档接入. 有idea插件支持,自动导 ...

  9. [Unity框架]资源管理02:热更新

    这里可以分成资源打包.资源更新下载.资源加载卸载3个部分 一.资源打包 参考链接: https://blog.uwa4d.com/archives/TechSharing_59.html https: ...

  10. MySql分库分表以及相关问题

    为什么要分库分表? MySql是存在瓶颈的,数据量就是他最大的瓶颈,如果一张表或者一个数据库里面的数据量过大都会导致一些意料之外的问题,譬如查询过慢,难以维护等问题,这时候就要想出一个完美的解决办法. ...