mybatis插件之自定义避免全表扫描插件实现

前言

  1. 上一篇知道IllegalSQLInnerInterceptor插件可以拦截没带条件的全表扫描,但在项目中经常有1=1的条件,这种情况在IllegalSQLInnerInterceptor是不被拦截的
  2. 关于JSQLParser的官网学习参考https://github.com/JSQLParser/JSqlParser/wiki
  3. 下面主要参考IllegalSQLInnerInterceptor插件实现一个符合项目需求的插件SelectCheckInterceptor
    3.1 全表查询排除1=1的条件
    3.2 支持某些全表查询

Jsqlparser条件表达式

  1. 调式示例
    	@Test
        public void testParse() throws Exception {
    	   Statement stmt = CCJSqlParserUtil.parse("SELECT * FROM tab1 where 1 = 1");
    	   Select select = (Select) stmt;
    	   PlainSelect plainSelect = (PlainSelect) select.getSelectBody();
    	   Expression where = plainSelect.getWhere();
        }
    
  2. Expression where有多种实现,当1=1时,where为EqualsTo
    在这里插入图片描述
    2.1 当有多个条件时,where为AndExpression,并且可以通过leftExpression属性进行嵌套
    在这里插入图片描述

恒等式 1 = 1 或 ‘1’ = ‘1’

  1. 当条件为where 1 = 1时,EqualsTo的leftExpression和rightExpression为LongValue
    1.1 当条件为where ‘1’ = ‘1’ 时,EqualsTo的leftExpression和rightExpression为StringValue
    在这里插入图片描述
  2. LongValue/StringValue 可以通过getValue方法获取其值
    在这里插入图片描述

自定义查询防全表查询的插件

参考IllegalSQLInnerInterceptor的实现

  1. 仿照IllegalSQLInnerInterceptor实现自定义查询校验的插件
    1.1 IllegalSQLInnerInterceptor继承JsqlParserSupport实现InnerInterceptor
    1.2 查询时插件执行哪些方法,在MybatisPlusInterceptor#intercept可知
    在这里插入图片描述
    在这里插入图片描述
    通过上面两副截图可以发现,在执行SQL时会调用两次Plugin#invoke。第一次是executor的形式,第二次是handler的形式

  2. InnerInterceptor#willDoQuery 判断是否执行,默认为true
    在这里插入图片描述

  3. InnerInterceptor#beforeQuery 操作前置处理,默认为空

  4. InnerInterceptor#beforePrepare 操作前置处理,在IllegalSQLInnerInterceptor中对sql做缓存处理
    4.1 重点关注一下InterceptorIgnoreHelper#willIgnoreIllegalSql(因为有些小表可以全表扫描)
    (1)通过下图发现,INTERCEPTOR_IGNORE_CACHE的key 可能是 mappedStatement 的 ID,也可能是 class 的 name
    在这里插入图片描述

  5. JsqlParserSupport#processSelect 处理查询,IllegalSQLInnerInterceptor对查询做分析校验

拦截忽略注解 @InterceptorIgnore

  1. 用法:在方法和类中加上@InterceptorIgnore注解
    在这里插入图片描述
  2. 注解的初始化
    2.1 mapper方法注解初始化:InterceptorIgnoreHelper#initSqlParserInfoCache(InterceptorIgnoreCache mapperAnnotation, String mapperClassName, Method method)
    2.2 mapper类注解初始化:InterceptorIgnoreHelper#initSqlParserInfoCache(Class<?> mapperClass)
  3. 构建拦截忽略缓存:InterceptorIgnoreHelper#buildInterceptorIgnoreCache
    在这里插入图片描述
  4. 在 InnerInterceptor#beforePrepare判断是否忽略:InterceptorIgnoreHelper#willIgnoreIllegalSql
    4.1 判断@InterceptorIgnore的illegalSql属性值,为true则忽略
    在这里插入图片描述
    在这里插入图片描述
    4.2 如果直接用BaseMapper的getAll,但有些想支持全表查询,可以重写加上注解@InterceptorIgnore
    在这里插入图片描述

实现防全表查询的插件

public class SelectCheckInterceptor extends JsqlParserSupport implements InnerInterceptor {

    /**
     * 缓存验证结果,提高性能
     */
    private static final Set<String> cacheValidResult = new HashSet<>();

    @Override
    public void beforePrepare(StatementHandler sh, Connection connection, Integer transactionTimeout) {
        PluginUtils.MPStatementHandler mpStatementHandler = PluginUtils.mpStatementHandler(sh);
        MappedStatement ms = mpStatementHandler.mappedStatement();
        SqlCommandType sct = ms.getSqlCommandType();
        //@InterceptorIgnore(illegalSql = "true") mapper类或方法上配此注解忽略校验
        if (sct == SqlCommandType.INSERT || InterceptorIgnoreHelper.willIgnoreIllegalSql(ms.getId())) return;
        BoundSql boundSql = mpStatementHandler.boundSql();
        String originalSql = boundSql.getSql();
        logger.debug("检查SQL是否合规,SQL:" + originalSql);
        String md5Base64 = EncryptUtils.md5Base64(originalSql);
        if (cacheValidResult.contains(md5Base64)) {
            logger.debug("该SQL已验证,无需再次验证,,SQL:" + originalSql);
            return;
        }
        parserSingle(originalSql, connection);
        //缓存验证结果
        cacheValidResult.add(md5Base64);
    }

    @Override
    protected void processSelect(Select select, int index, String sql, Object obj) {
        PlainSelect plainSelect = (PlainSelect) select.getSelectBody();
        Expression where = plainSelect.getWhere();
        Assert.notNull(where, "非法SQL,必须要有where条件");
        if(where instanceof EqualsTo){
            Expression left  =  ((EqualsTo) where).getLeftExpression();
            Expression right = ((EqualsTo) where).getRightExpression();
            if(left.getClass().isAssignableFrom(right.getClass())){
                if(left instanceof StringValue &&
                        ((StringValue) left).getValue().equals(((StringValue) right).getValue())){
                    throw new MybatisPlusException("非法SQL,必须要有where条件");
                }
                if(left instanceof LongValue &&
                        ((LongValue) left).getValue() == ((LongValue) right).getValue() ){
                    throw new MybatisPlusException("非法SQL,必须要有where条件");
                }
            }
        }
    }
}

小知识补充

Maven 父pom中dependencyManagement版本优先级高于传递依赖版本

  1. 父pom文件

    <dependencyManagement>
    	<dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>1.3.0</version>
      </dependency>
    </dependencyManagement>
    
  2. 子模块的pom文件

    	 <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-boot-starter</artifactId>
                <version>3.4.3.4</version>
            </dependency>
    
  3. 此时该工程中mybatis-spring的版本将是1.3.0,即父pom dependencyManagement中的版本

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值