Spring SQL 拦截器实现


1. 依赖引入

在当下绝多数的工程中通常采用 MyBatis 作为数据交互框架,同类此处在项目中导入下述依赖。

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
    </dependency>
</dependencies>

2. 注解介绍

以往的文章中已经详细介绍过拦截器的使用,这里仅简要概述一下的 @Intercepts 注解的参数配置。

@Intercepts 中提供了注解参数 @Signature 用于配置拦截目标信息,@Signature 注解的配置参数如下:

方法 作用
type 指定目标方法所在的接口或类。
method 指定要拦截的目标方法的名称。
args 指定拦截目标方法的参数类型。

假设存在目标类 TargetClass,并包含了两个方法,具体内容如下:

public void TargetClass {

    public void demo1(String s) {}

    public void demo2(String s1, String s2) {}
}

若想要编写一个拦截器作用于上述 demo1() 方法,则 @Intercepts 注解的定义格式如下:

@Component
@Intercepts(
        @Signature(
                type = TargetClass.class,
                method = "demo1",
                args = { String.class }
        )
)
public class TestInterceptor implements Interceptor { }

3. 拦截实现

基于上述描述,我们即可编辑拦截器实现 SQL 层面执行的跟踪编辑操作。

MyBatisJDBC 的执行依托于 org.apache.ibatis.executor.Executor 接口,其包含如下接口方法:

接口方法 方法作用
public Object plugin(Object target) 配置开启拦截器插件,默认开启。
Object intercept(Invocation var1) 声明具体的拦截器业务逻辑
void setProperties(Properties properties) 用于接收配置文件标签里的外部参数。

以最常见的数据库查询为例,下述定义了作用于 query() 方法,当执行 <select> 标签查询语句时将会触发 intercept() 方法逻辑,可在其中执行通用数据操作业务。

@Component
@Intercepts(
        @Signature(
                type = Executor.class,
                method = "query",
                args = {
                        MappedStatement.class,
                        Object.class,
                        RowBounds.class,
                        ResultHandler.class
                }
        )
)
@SuppressWarnings("rawtypes")
public class QueryInterceptor implements Interceptor {

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object[] args = invocation.getArgs();
        MappedStatement ms = (MappedStatement) args[0];
        Object params = args[1];
        RowBounds rowBounds = (RowBounds) args[2];
        ResultHandler resultHandler = (ResultHandler) args[3];

        // Get sql detail
        Executor executor = (Executor) invocation.getTarget();
        BoundSql boundSql = ms.getBoundSql(params); ;
        CacheKey cacheKey = executor.createCacheKey(ms, params, rowBounds, boundSql);

        // Handle target sql
        SqlCommandType sqlType = ms.getSqlCommandType();
        if (sqlType == SqlCommandType.SELECT) {
            System.out.println("=========> Query sql: " + boundSql.getSql());
            return executor.query(ms, params, rowBounds, resultHandler, cacheKey, boundSql);
        }
        return invocation.proceed();
    }
}

如上所述 plugin() 方法提供了开关的效果,默认为 Plugin.wrap(target, this) 即开启拦截器插件。

而在工具库设计应用中,可搭配 @Value 读取外部配置从而实现动态的插件开关,当直接返回 target 则表明不启用插件,即不会触发 intercept() 所定义的事件。

public class TestIntercept implements Interceptor {

    @Value("${switch.intercept:true}")
    private Boolean switch;

    @Override
    public Object plugin(Object target) {
        if (Boolean.FALSE.equals(switch)) {
            // disable interceptor
            return target;
        }

        return Plugin.wrap(target, this);
    }
}

文章作者: 烽火戏诸诸诸侯
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 烽火戏诸诸诸侯 !
  目录