自定义注解与拦截器实现不规范sql拦截(拦截器实现篇)

news2025/1/12 7:57:26

最近考虑myBatis中sql语句使用规范的问题,如果漏下条件或者写一些不规范语句会对程序性能造成很大影响。最好的方法就是利用代码进行限制,通过拦截器进行sql格式的判断在自测环节就能找到问题。写了个简单情景下的demo,并通过idea插件来将myBatis的mapper方法都打上拦截器判断的注解,多少自动化一点。

需求简介

使用myBatis拦截器对Mapper的sql进行判断,对增加了自定义注解修饰的方法进行where条件的判断,存在where子句再执行,否则抛出异常。

具体实现

自定义注解

// 标志注解,用来表示mapper方法需要经过where条件存在与否的判断
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface WhereConditionCheck {
}

限定method修饰,同时指定runtime,以在运行过程中判断是否被该注解修饰。

拦截器实现类

WhereConditionInterceptor拦截器判断类

@Intercepts({
        // 拦截query方法
        @Signature(type = Executor.class, method = "query",
                args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
        @Signature(type = Executor.class,method = "update",args = {MappedStatement.class, Object.class})
})
@Component
@Slf4j
// 利用myBatis拦截器去获取对应的语句,where条件放行,无where阻止
public class WhereConditionInterceptor implements Interceptor {

首先利用@Intercepters注解,定义拦截器拦截的方法。这些方法来自Mybatis执行时调用的Executor接口的实现类,并通过args参数的设置来唯一指定同名的重载方法。

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 获取sql信息
        MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
        Object parameter = invocation.getArgs()[1]; // 参数
        BoundSql sql = mappedStatement.getBoundSql(parameter);

        // 获取原始调用方法的注解信息
        String id = mappedStatement.getId();
        String className = id.substring(0, id.lastIndexOf("."));
        String methodName = id.substring(id.lastIndexOf(".") + 1);
        Method[] methods = Class.forName(className).getMethods();
        // 不考虑方法重载
        for(Method method:methods){
            if(method.getName().equals(methodName) && method.isAnnotationPresent(WhereConditionCheck.class)){
                // 执行拦截
                boolean checkResult = checkIfWhereExist(sql);
                if(!checkResult){
                    // 根据sql类型判断抛出异常的语句
                    SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
                    if(sqlCommandType.equals(SqlCommandType.SELECT)) {
                        log.error("query error, sql='" + sql.getSql() + "''");
                        throw new RuntimeException("Query methods must contain where condition. method:" + id);
                    } else if(sqlCommandType.equals(SqlCommandType.DELETE)) {
                        log.error("delete error, sql='" + sql.getSql()  + "''");
                        throw new RuntimeException("Delete methods must contain where condition. method:" + id);
                    }
                }
            }
        }

        return invocation.proceed();
    }

重写intercept方法,利用invocation来获取之前定义的拦截方法的具体参数对象,利用第一个参数MappedStatement对象来获取具体的sql信息。需要注意的是,为了获取原Mapper接口中定义方法的注解信息,我们需要利用getId获取完整的接口名和类名,并利用反射获取对应的方法对象,以判断对应方法是否存在@WhereConditionCheck注解。

    private boolean checkIfWhereExist(BoundSql sql){
        String sqlStr = sql.getSql();
        sqlStr = sqlStr.toUpperCase();
        return sqlStr.contains("WHERE");
    }

具体的判断就直接将字符串转成大写后进行匹配即可。

简单使用

Mapper接口

@Mapper
public interface UserMapper {
    @Select("select * from user where code = #{code}")
    @WhereConditionCheck
    public User findUserByCode(String code);

    @Select("select * from user")
    // 全量查询,需要拦截
    @WhereConditionCheck
    public List<User> findUserListWhole();

简单设置两个方法,where子句和全量查询。

controller

@Controller
@RequestMapping("/user")
public class WhereConditionCheckController {

    @Autowired
    UserMapper userMapper;

    @ResponseBody
    @RequestMapping("/queryWhole")
    // 全量查询
    public String getWholeUser(){
        try {
            List<User> userList = userMapper.findUserListWhole();
            return JSONObject.toJSONString(userList);
        }catch(Exception ex){
            // 获取异常栈最底层以显示自定义信息
            Throwable cause = ex;
            Throwable result = null;
            while(cause != null){
                result = cause;
                cause = cause.getCause();
            }
            return result.getMessage();
        }
    }

    @ResponseBody
    @RequestMapping("/queryByCode")
    // where查询
    public String getUser(String code){
        try{
            User user = userMapper.findUserByCode(code);
            return JSONObject.toJSONString(user);
        }catch(Exception ex){
            /// 获取异常栈最底层以显示自定义信息
            Throwable cause = ex;
            Throwable result = null;
            while(cause != null){
                result = cause;
                cause = cause.getCause();
            }
            return result.getMessage();
        }
    }
}

对两个方法进行使用访问。需要注意的是,为了返回拦截器中抛出方法设定的message,需要捕获到异常栈中底层的exception进行输出。

效果展示

postman进行调用:
在这里插入图片描述
全量查询被拒绝。
在这里插入图片描述
这边有做一个脱敏处理,不用管。可以看出能正常查询出来。

总结

简单展示了使用Mybatis拦截器进行where子句判断的方式,用完整的类名和方法名去定位自定义注解,比较麻烦的其实是如何显示最原始的异常信息。开头说的使用插件自动添加自定义注解的实现,放在自定义注解与拦截器实现不规范sql拦截(自定义注解填充插件篇)中来讲。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1401658.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

JavaScript进阶:WebAPIs重点知识整理1

目录 1 DOM修改元素内容 2 DOM修改元素常见属性 3 修改元素样式属性 3.1 通过style修改元素样式 3.2 通过类名className修改元素样式 3.3 通过classList修改元素样式 4 操作表单元素属性 5 自定义属性 6 定时器 7 事件监听 7.1 点击事件 click 7.2 鼠mouseenter和移…

【数据结构】 双链表的基本操作 (C语言版)

目录 一、双链表 1、双链表的定义&#xff1a; 2、双链表表的优缺点&#xff1a; 二、双链表的基本操作算法&#xff08;C语言&#xff09; 1、宏定义 2、创建结构体 3、双链表的初始化 4、双链表表插入 5、双链表的查找 6、双链表的取值 7、求双链表长度 8、双链表…

华南理工大学数字信号处理实验实验一(薛y老师版本)matlab源码

一、实验目的 1、加深对离散信号频谱分析的理解&#xff1b; 2、分析不同加窗长度对信号频谱的影响&#xff1b; 3、理解频率分辨率的概念&#xff0c;并分析其对频谱的 影响&#xff1b; 4、窗长和补零对DFT的影响 实验源码&#xff1a; 第一题&#xff1a; % 定义离散信…

C++函数对象-函数包装器-调用空的 std::function 时抛出的异常(std::bad_function_call)

任何定义了函数调用操作符的对象都是函数对象。C 支持创建、操作新的函数对象&#xff0c;同时也提供了许多内置的函数对象。 函数包装器 std::function 提供存储任意类型函数对象的支持。 用空的 std::function 时抛出的异常 std::bad_function_call class bad_function_cal…

【C++】命名空间(namespace)

文章目录 1. 为什么要有命名空间?2. 命名空间介绍3.命名空间三种使用方式4. 注意 1. 为什么要有命名空间? 在C语言中&#xff0c;局部变量和全局变量如果同名&#xff0c;在使用时可能会造成冲突。这并不是想避免就能避免的&#xff0c;在程序中&#xff0c;不仅仅是变量&…

《WebKit 技术内幕》之八(2):硬件加速机制

2 Chromium的硬件加速机制 2.1 GraphicsLayer的支持 GraphicsLayer对象是对一个渲染后端存储中某一层的抽象&#xff0c;同众多其他WebKit所定义的抽象类一样&#xff0c;在WebKit移植中&#xff0c;它还需要具体的实现类来支持该类所要提供的功能。为了完成这一功能&#x…

系统架构设计师教程(十三)层次式架构设计理论与实践

层次式架构设计理论与实践 13.1 层次式体系结构概述13.2 表现层框架设计13.2.1 表现层设计模式13.2.2 使用XML设计表现层&#xff0c;统一Web Form与Windows Form的外观13.2.3表现层中UIP设计思想13.2.4 表现层动态生成设计思想 13.3 中间层架构设计13.3.1 业务逻辑层组件设计1…

【Elasticsearch】索引恢复(recovery)流程梳理之副本分片数据恢复

replica shard重启具体流程 replica shard node &#xff08;generic threadpool&#xff09; 也是因为应用新的集群状态触发recovery&#xff0c;进入index阶段进入translog 阶段。先尝试重放本地的translog到global checkpoint向primary shard发起start recovery的请求&…

JVM对象创建与内存回收机制

对象的创建过程有如下步骤&#xff1a; 1.类加载检查&#xff1a; 虚拟机遇到一个new指令时&#xff0c;首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用&#xff0c;并且检查这个符号引用代表的类是否已被加载、解析和初始化过&#xff0c;如果没…

【golang】Context超时控制与原理

Context 在Go语言圈子中流行着一句话&#xff1a; Never start a goroutine without knowing how it will stop。 翻译&#xff1a;如果你不知道协程如何退出&#xff0c;就不要使用它。 在创建协程时&#xff0c;我们可能还会再创建一些别的子协程&#xff0c;那么这些协程的…

【书籍】强化学习第二版(英文版电子版下载、github源码)-附copilot翻译的中英文目录...

Python代码&#xff1a;https://github.com/ShangtongZhang/reinforcement-learning-an-introduction 英文原版书籍下载&#xff1a;http://incompleteideas.net/book/the-book-2nd.html 作者&#xff1a; 理查德S萨顿是阿尔伯塔大学计算机科学教授和强化学习与人工智能 AITF 主…

1.6万字全面掌握 BERT:自然语言处理(NLP)从初学到高级的全面指南

BERT&#xff08;双向编码器表示来自Transformer的模型&#xff09;是由Google开发的一种革命性的自然语言处理&#xff08;NLP&#xff09;模型。它改变了语言理解任务的格局&#xff0c;使机器能够理解语言中的上下文和细微差异。 在本博客中&#xff0c;我们将带您从 BERT …

信息安全概述

信息安全&#xff1a;防止任何对数据进行未授权访问的措施&#xff0c;或者防止造成信息有意无意泄漏、破坏、丢失等问题的发生&#xff0c;让数据处于远离危险、免于威胁的状态或特性。 网络安全&#xff1a;计算机网络环境下的信息安全。 信息安全现状及挑战 数字化时代威…

什么是VUE 创建第一个VUE实例

一、什么是Vue 概念&#xff1a;Vue (读音 /vjuː/&#xff0c;类似于 view) 是一套 构建用户界面 的 渐进式 框架 Vue2官网&#xff1a;Vue.js 1.什么是构建用户界面 基于数据渲染出用户可以看到的界面 2.什么是渐进式 所谓渐进式就是循序渐进&#xff0c;不一定非得把Vu…

Docker编译多平台文件、构建多平台镜像并运行

参考官方文档&#xff1a; Multi-stageMulti-platformMulti-platform images 文章目录 方法Buildx 设置使用QEMU仿真编译文件构建镜像 使用交叉编译&#xff08;cross-compilation&#xff09;编译文件构建镜像 在x86_64上运行arm64容器 方法 如果在 x86_64/amd64 的平台上&am…

Docker 操作之数据卷挂载【云原生】

文章目录 1. 镜像1.1 基本命令1.2 案例练习 2. 容器2.1 基本命令2.2 Nginx 案例2.3 Redis 案例 3. 数据卷3.1 基本命令3.2 Nginx 案例3.3 MySQL 案例 1. 镜像 镜像命令一般分两部分组成&#xff1a;repository:tag。比如 mysql:5.7&#xff0c;表示名称加版本。 在没有指定 t…

一文掌握Python所有常见的循环用法

更多资料获取 &#x1f4da; 个人网站&#xff1a;ipengtao.com 循环是编程中的重要概念&#xff0c;它允许我们重复执行一组操作&#xff0c;直到满足某个条件。Python提供了多种类型的循环&#xff0c;以适应不同的需求和场景。本文将详细介绍Python中所有常见的循环用法&am…

MySQL下对[表]的操作数据类型

目录 表的操作 创建表&#xff1a; 查看表结构&#xff1a; 修改表&#xff1a; 删除表&#xff1a; 数据类型 数据类型分类&#xff1a; 数值类型&#xff1a; tinyint类型越界测试&#xff1a; bit类型&#xff1a; 小数类型&#xff1a; 字符串类型&#xff1a; …

JAVA中 Lambda 方法引用 算法

算法和数据结构 一、Arrays类 接下来学习的类叫做Arrays&#xff0c;其实Arrays并不是重点&#xff0c;但是我们通过Arrays这个类的学习有助于我们理解下一个知识点Lambda的学习。所以我们这里先学习Arrays&#xff0c;再通过Arrays来学习Lamdba这样学习会更丝滑一些_. 1.1 …

【Qt Quick 项目(第一集Qt Quick UI 项目项目创建)】

# Qt Quick 项目 到底什么是Qt Qml、什么是Qt Quick、QtQuick应用程序与Qt Widget程序有何区别,为了让读者在学习QML之前有一个整体认识,这里先介绍几个Quick项目。 01 Qt Quick UI 项目