当前位置:   article > 正文

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

mybatisplus限制条数
需求背景

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

解决思路

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

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

步骤一
1.1 定义拦截器PreCheckBigQueryInnerInterceptor
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()) 定义并优化计数查询语句
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方法,且一定会执行 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/盐析白兔/article/detail/282231
推荐阅读
相关标签
  

闽ICP备14008679号