首页 > 其他分享 >自定义Mybatis-plus插件(限制最大查询数量)

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

时间:2023-04-19 14:01:23浏览次数:37  
标签:插件 return 自定义 查询 boundSql ms executor plus parameter

自定义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;
}

知识小结:

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

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

  1. 为啥重写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方法,且一定会执行

标签:插件,return,自定义,查询,boundSql,ms,executor,plus,parameter
From: https://www.cnblogs.com/jinliang374003909/p/17333060.html

相关文章

  • pytest结合allure-pytest插件 生成allure测试报告
     注意:allure-pytest 从1.7之后已弃用,从2.0版本开始迁移至 allure-python 项目(即使用allure2),另外要运行allure命令行也需要Java的支持。1、安装allure-pytestpipinstall-U allure-pytest 这将安装allure-pytest和allure-python-commons程序包,以生成与allure2兼容的报告......
  • 【web 开发基础】PHP 自定义函数之函数的返回值-PHP 快速入门 (27)
    前言在定义函数时,函数名后面括号中的参数列表是用户在调用函数时用来将数据传递到函数内部的接口,而函数的返回值则将函数执行后的结果返回给调用者。如果函数没有返回值,就只能算一个执行过程。只依靠函数做一些事情还不够,有时更需要在程序脚本中使用函数执行后的结果。由于变量的作......
  • CentOS7添加自定义脚本服务
    在CentOS7下,已经不再使用chkconfig命令管理系统开机自启动服务和条件自定义脚本服务了,而是使用管理unit的方式来控制开机自启动服务和添加自定义脚本服务。在/usr/lib/systemd/system目录下包含了各种unit文件,有service后缀的服务unit,有target后缀的开机级别unit等。如果想把自定......
  • 6.自定义注解与设计模式
    自定义注解与设计模式课程目标熟悉注解底层实现原理完成ORM框架底层原理常用设计模式单例、工厂、代理一.自定义注解1.1什么是注解?Jdk1.5新增新技术,注解。很多框架为了简化代码,都会提供有些注解。可以理解为插件,是代码级别的插件,在类的方法上写:@XXX,就是在......
  • 11.Session与Cookie(自定义Session)
    Session与Cookie(自定义Session)课程目标:#熟悉Cookie、Session底层实现原理、自定义缓存、自定义Token、表单重复提交#解决方案、Servlet之Fileter解决XSS攻击。一、.会话管理入门1.1生活中会话我:小张,你会跳小苹果码?小张:会,怎么了?我:公司年会上要表演节目,你教教......
  • 动力节点2023版MyBatisPlus教程【进阶篇】
    来自B站动力节点最新版的MybatisPlus教程,整理了笔记——第四章高级篇4【高级篇】4.1主键策略4.1.1主键生成策略介绍首先大家先要知道什么是主键,主键的作用就是唯一标识,我们可以通过这个唯一标识来定位到这条数据。当然对于表数据中的主键,我们可以自己设计生成规则,生成主键。......
  • 小程序打印小票,复制功能,自定义导航栏
    //复制联系地址fnCopyAddress(){wx.setClipboardData({data:this.data.detailInfo.address,success:res=>{Util.errorShow('复制联系地址成功')}})},//打印小票fnPrintTicket(){let_this=this;......
  • 实现声明式锁,支持分布式锁自定义锁、SpEL和结合事务
    目录2.实现2.1定义注解2.2定义锁接口2.3锁的实现2.3.1什么是SPI2.3.2通过SPI实现锁的多个实现类2.3.3通过SPI自定义实现锁3.定义切面3.1切面实现3.2SpEL表达式获取动态key3.3锁与事务的结合4.测试4.1ReentrantLock测试4.2RedissonClient测试4.3自定义锁测试5.尾声5.1......
  • JDBC 调用自定义函数(常说的存储过程)的步骤
     平常说的存储过程(Procedure),严格意义上是自定义函数,所以这里以【自定义函数】为名,简称【函数(function)】。 packagecom.joyupx.jdbc;importlombok.extern.slf4j.Slf4j;importorg.junit.jupiter.api.Test;importjava.io.IOException;importjava.io.InputStream;im......
  • Shifu物联网开发框架成为MicroK8s官方认证的Kubernetes插件
    Shifu物联网开发框架已经成为Kubernetes生态下MicroK8s官方认证的插件,这将极大地简化基于K8s的物联网应用程序的开发,帮助企业高效搭建获得安全、可控的生产级物联中台。MicroK8s是一个轻量级的CNCF认证的Kubernetes发行版,适用于云、工作站、边缘和物联网设备。Shifu用作K......