MyBatis自定义SQL拦截器示例详解

 更新时间:2021年10月24日 12:01:16   作者:老郑_  
Mybatis支持对Executor、StatementHandler、PameterHandler和ResultSetHandler 接口进行拦截,也就是说会对这4种对象进行代理,下面这篇文章主要给大家介绍了关于MyBatis自定义SQL拦截器的相关资料,需要的朋友可以参考下

前言

本文主要是讲通过 MyBaits 的 Interceptor 的拓展点进行对 MyBatis 执行 SQL 之前做一个逻辑拦截实现自定义逻辑的插入执行。

适合场景:1. 比如限制数据库查询最大访问条数;2. 限制登录用户只能访问当前机构数据。

定义是否开启注解

定义是否开启注解, 主要做的一件事情就是是否添加 SQL 拦截器。

// 全局开启
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MyBatisSqlInterceptorConfiguration.class)
public @interface EnableSqlInterceptor {

}

// 自定义注解
@Target({ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface DataScope {

}

注册SQL 拦截器

注册一个 SQL 拦截器,会对符合条件的 SQL 查询操作进行拦截。

public class MyBatisSqlInterceptorConfiguration implements ApplicationContextAware {

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        SqlSessionFactory sqlSessionFactory = applicationContext.getBean(SqlSessionFactory.class);
        sqlSessionFactory.getConfiguration().addInterceptor(new MyBatisInterceptor());
    }
}


处理逻辑

在处理逻辑中,我主要是做一个简单的 limit 1 案例,如果是自己需要做其他的逻辑需要修改

@Slf4j
@Intercepts(
        {
                @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
                @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
        })
public class MyBatisInterceptor implements Interceptor {

    private static final Logger LOGGER = LoggerFactory.getLogger(MyBatisInterceptor.class);

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // TODO Auto-generated method stub
        Object[] args = invocation.getArgs();
        MappedStatement ms = (MappedStatement) args[0];
        Object parameter = args[1];
        RowBounds rowBounds = (RowBounds) args[2];
        ResultHandler resultHandler = (ResultHandler) args[3];
        Executor executor = (Executor) invocation.getTarget();
        CacheKey cacheKey;
        BoundSql boundSql;
        //由于逻辑关系,只会进入一次
        if (args.length == 4) {
            //4 个参数时
            boundSql = ms.getBoundSql(parameter);
            cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql);
        } else {
            //6 个参数时
            cacheKey = (CacheKey) args[4];
            boundSql = (BoundSql) args[5];
        }
        DataScope dataScope = getDataScope(ms);
        if (Objects.nonNull(dataScope)) {
            String origSql = boundSql.getSql();
            log.info("origSql : {}", origSql);
            // 组装新的 sql
            // todo you weaving business
            String newSql = origSql + " limit 1";

            // 重新new一个查询语句对象
            BoundSql newBoundSql = new BoundSql(ms.getConfiguration(), newSql,
                    boundSql.getParameterMappings(), boundSql.getParameterObject());

            // 把新的查询放到statement里
            MappedStatement newMs = newMappedStatement(ms, new BoundSqlSource(newBoundSql));
            for (ParameterMapping mapping : boundSql.getParameterMappings()) {
                String prop = mapping.getProperty();
                if (boundSql.hasAdditionalParameter(prop)) {
                    newBoundSql.setAdditionalParameter(prop, boundSql.getAdditionalParameter(prop));
                }
            }

            args[0] = newMs;
            if (args.length == 6) {
                args[5] = newMs.getBoundSql(parameter);
            }
        }
        LOGGER.info("mybatis intercept sql:{},Mapper方法是:{}", boundSql.getSql(), ms.getId());


        return invocation.proceed();
    }

    private MappedStatement newMappedStatement(MappedStatement ms, SqlSource newSqlSource) {
        MappedStatement.Builder builder = new
                MappedStatement.Builder(ms.getConfiguration(), ms.getId(), newSqlSource, ms.getSqlCommandType());
        builder.resource(ms.getResource());
        builder.fetchSize(ms.getFetchSize());
        builder.statementType(ms.getStatementType());
        builder.keyGenerator(ms.getKeyGenerator());
        if (ms.getKeyProperties() != null && ms.getKeyProperties().length > 0) {
            builder.keyProperty(ms.getKeyProperties()[0]);
        }
        builder.timeout(ms.getTimeout());
        builder.parameterMap(ms.getParameterMap());
        builder.resultMaps(ms.getResultMaps());
        builder.resultSetType(ms.getResultSetType());
        builder.cache(ms.getCache());
        builder.flushCacheRequired(ms.isFlushCacheRequired());
        builder.useCache(ms.isUseCache());
        return builder.build();
    }


    private DataScope getDataScope(MappedStatement mappedStatement) {
        String id = mappedStatement.getId();
        // 获取 Class Method
        String clazzName = id.substring(0, id.lastIndexOf('.'));
        String mapperMethod = id.substring(id.lastIndexOf('.') + 1);

        Class<?> clazz;
        try {
            clazz = Class.forName(clazzName);
        } catch (ClassNotFoundException e) {
            return null;
        }
        Method[] methods = clazz.getMethods();

        DataScope dataScope = null;
        for (Method method : methods) {
            if (method.getName().equals(mapperMethod)) {
                dataScope = method.getAnnotation(DataScope.class);
                break;
            }
        }
        return dataScope;
    }

    @Override
    public Object plugin(Object target) {
        // TODO Auto-generated method stub
        LOGGER.info("MysqlInterCeptor plugin>>>>>>>{}", target);
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        // TODO Auto-generated method stub
        String dialect = properties.getProperty("dialect");
        LOGGER.info("mybatis intercept dialect:>>>>>>>{}", dialect);
    }

    /**
     * 定义一个内部辅助类,作用是包装 SQL
     */
    class BoundSqlSource implements SqlSource {
        private BoundSql boundSql;

        public BoundSqlSource(BoundSql boundSql) {
            this.boundSql = boundSql;
        }

        public BoundSql getBoundSql(Object parameterObject) {
            return boundSql;
        }

    }

}

如何使用

我们在 XXXMapper 中对应的数据操作方法只要加入 @DataScope 注解即可。

@Mapper
public interface OrderMapper {

   @Select("select 1 ")
   @DataScope
   Intger selectOne();

}

参考资料

mybatis.org/mybatis-3/z

总结

到此这篇关于MyBatis自定义SQL拦截器的文章就介绍到这了,更多相关MyBatis自定义SQL拦截器内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • MyBatis里映射文件sql语句爆红问题及解决方案

    MyBatis里映射文件sql语句爆红问题及解决方案

    文章介绍了在MyBatis映射文件中SQL语句爆红的问题及其解决方法,解决方法是在设置中将SQL的全局方言设为None,然后应用并确认设置即可
    2025-03-03
  • Java/Android引用类型及其使用全面分析

    Java/Android引用类型及其使用全面分析

    下面小编就为大家带来一篇Java/Android引用类型及其使用全面分析。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2016-09-09
  • Java Handler同步屏障浅析讲解

    Java Handler同步屏障浅析讲解

    同步屏障机制是什么?Handler发送的消息分为普通消息、屏障消息、异步消息,一旦Looper在处理消息时遇到屏障消息,那么就不再处理普通的消息,而仅仅处理异步的消息。不再使用屏障后,需要撤销屏障,不然就再也执行不到普通消息了
    2022-08-08
  • SpringBoot 如何读取classpath下的文件

    SpringBoot 如何读取classpath下的文件

    这篇文章主要介绍了SpringBoot 读取classpath下的文件方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-06-06
  • Spring高级注解@PropertySource详细解读

    Spring高级注解@PropertySource详细解读

    这篇文章主要介绍了Spring高级注解@PropertySource详细解读,@PropertySource注解用于指定资源文件读取的位置,它不仅能读取properties文件,也能读取xml文件,并且通过YAML解析器,配合自定义PropertySourceFactory实现解析yaml文件,需要的朋友可以参考下
    2023-11-11
  • Log4j_配置方法(全面讲解)

    Log4j_配置方法(全面讲解)

    下面小编就为大家带来一篇Log4j_配置方法(全面讲解)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-09-09
  • springBoot无法解析yml问题

    springBoot无法解析yml问题

    这篇文章主要介绍了springBoot无法解析yml问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-06-06
  • 深入了解Java核心类库--String类

    深入了解Java核心类库--String类

    这篇文章主要为大家详细介绍了java String类定义与使用的相关资料,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能给你带来帮助
    2021-07-07
  • IDEA插件Statistic统计代码快速分辨烂项目

    IDEA插件Statistic统计代码快速分辨烂项目

    这篇文章主要为大家介绍了使用IDEA插件Statistic来统计项目代码,帮助大家快速识别出烂项目,有需要的朋友可以借鉴参考下,希望能够有所帮助
    2022-01-01
  • Springcloud Stream消息驱动工具使用介绍

    Springcloud Stream消息驱动工具使用介绍

    SpringCloud Stream由一个中间件中立的核组成,应用通过SpringCloud Stream插入的input(相当于消费者consumer,它是从队列中接收消息的)和output(相当于生产者producer,它是发送消息到队列中的)通道与外界交流
    2022-09-09

最新评论