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


Posted in Java/Android onOctober 24, 2021

前言

本文主要是讲通过 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拦截器内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Java/Android 相关文章推荐
SpringBoot集成Redis,并自定义对象序列化操作
Jun 22 Java/Android
如何解决springcloud feign 首次调用100%失败的问题
Jun 23 Java/Android
Java中PriorityQueue实现最小堆和最大堆的用法
Jun 27 Java/Android
利用Java设置Word文本框中的文字旋转方向的实现方法
Jun 28 Java/Android
springboot 启动如何排除某些bean的注入
Aug 02 Java/Android
引用计数法和root搜索算法以及JVM中判定对象需要回收的方法
Apr 19 Java/Android
Elasticsearch 配置详解
Apr 19 Java/Android
Android开发之底部导航栏的快速实现
Apr 28 Java/Android
java版 联机五子棋游戏
May 04 Java/Android
Spring Security动态权限的实现方法详解
Jun 16 Java/Android
springboot集成redis存对象乱码的问题及解决
Jun 16 Java/Android
java多态注意项小结
Spring Security中用JWT退出登录时遇到的坑
Java实现房屋出租系统详解
Oct 05 #Java/Android
Java Spring 控制反转(IOC)容器详解
Java spring定时任务详解
JAVA API 实用类 String详解
Oct 05 #Java/Android
SpringCloud之@FeignClient()注解的使用方式
Sep 25 #Java/Android
You might like
查找mysql字段中固定字符串并替换的几个方法
2012/09/23 PHP
PHP插入排序实现代码
2013/04/04 PHP
封装ThinkPHP的一个文件上传方法实例
2014/10/31 PHP
thinkphp的静态缓存用法分析
2014/11/29 PHP
PHP大文件分块上传功能实例详解
2019/07/22 PHP
jQuery get和post 方法传值注意事项
2009/11/03 Javascript
JS图片浏览组件PhotoLook的公开属性方法介绍和进阶实例代码
2010/11/09 Javascript
使用原生javascript创建通用表单验证——更锋利的使用dom对象
2011/09/13 Javascript
jquery插件之信息弹出框showInfoDialog(成功/错误/警告/通知/背景遮罩)
2013/01/09 Javascript
jQuery之尺寸调整组件的深入解析
2013/06/19 Javascript
JQuery控制div外点击隐藏而div内点击不会隐藏的方法
2015/01/13 Javascript
chrome不支持form.submit的解决方案
2015/04/28 Javascript
jQuery实现为控件添加水印文字效果(附源码)
2015/12/02 Javascript
js实现根据身份证号自动生成出生日期
2015/12/15 Javascript
JS实现随机颜色的3种方法与颜色格式的转化
2017/01/05 Javascript
JS实现复选框的全选和批量删除功能
2017/04/05 Javascript
浅谈微信小程序之官方UI框架we-ui使用教程
2018/08/20 Javascript
详解小程序云开发攻略(解决最棘手的问题)
2019/09/30 Javascript
Vue发布订阅模式实现过程图解
2020/04/30 Javascript
微信小程序动态评分展示/五角星展示/半颗星展示/自定义长度展示功能的实现
2020/07/22 Javascript
python变量不能以数字打头详解
2016/07/06 Python
python3.4用循环往mysql5.7中写数据并输出的实现方法
2017/06/20 Python
python 在指定范围内随机生成不重复的n个数实例
2019/01/28 Python
Python爬取豆瓣视频信息代码实例
2019/11/16 Python
python GUI库图形界面开发之PyQt5滑块条控件QSlider详细使用方法与实例
2020/02/28 Python
杭州-飞时达软件有限公司.net笔面试
2012/04/28 面试题
总经理助理的八要求
2013/11/12 职场文书
中专毕业生的自我鉴定
2013/12/01 职场文书
搬家公司的创业计划书
2014/01/01 职场文书
互联网创业计划书的书写步骤
2014/01/28 职场文书
八一建军节活动方案
2014/02/10 职场文书
党员干部承诺书
2014/03/25 职场文书
幼儿教师暑期培训方案
2014/08/27 职场文书
新郎新娘致辞
2015/07/31 职场文书
员工担保书范本
2015/09/22 职场文书
Mysql InnoDB 的内存逻辑架构
2022/05/06 MySQL