Mybatis拦截器介绍及其应用

Mybatis拦截器介绍及其应用

1、介绍

Mybatis拦截器设计的初衷就是为了供用户在某些时候可以实现自己的逻辑而不必去动Mybatis固有的逻辑。通过Mybatis拦截器我们可以拦截某些方法的调用,我们可以选择在这些被拦截的方法执行前后加上某些逻辑,也可以在执行这些被拦截的方法时执行自己的逻辑而不再执行被拦截的方法。所以Mybatis拦截器的使用范围是非常广泛的。

2、Mybatis部分核心对象

Mybatis核心对象 解释
SqlSession 作为MyBatis工作的主要顶层API,表示和数据库交互的会话,完成必要数据库增删改查功能
Executor MyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护
StatementHandler 封装了JDBC Statement操作,负责对JDBC statement 的操作,如设置参数、将Statement结果集转换成List集合
ParameterHandler 负责对用户传递的参数转换成JDBC Statement 所需要的参数
ResultSetHandler 负责将JDBC返回的ResultSet结果集对象转换成List类型的集合
TypeHandler 负责java数据类型和jdbc数据类型之间的映射和转换
MappedStatement MappedStatement维护了一条mapper.xml文件里面 select 、update、delete、insert节点的封装
SqlSource 负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,并返回
BoundSql 表示动态生成的SQL语句以及相应的参数信息
Configuration MyBatis所有的配置信息都维持在Configuration对象之中

3、拦截器种类

3.1、ParameterHandler 参数拦截器:

ParameterHandler拦截器类型用于拦截MyBatis的参数处理过程。它在参数设置到PreparedStatement对象之前拦截并修改参数。你可以通过实现ParameterHandler接口来自定义参数处理逻辑,例如对参数进行加密、解密、校验或转换等操作。

3.2、ResultSetHandler 结果集拦截器:

ResultSetHandler拦截器类型用于拦截MyBatis的结果集处理过程。它在从JDBC结果集中获取数据并映射到Java对象或集合之前拦截并修改结果。你可以通过实现ResultSetHandler接口来自定义结果集处理逻辑,例如对结果进行加工、过滤、缓存或转换等操作。

3.3、StatementHandler 语句拦截器:

StatementHandler拦截器类型用于拦截MyBatis的SQL语句处理过程。它在SQL语句执行之前拦截并修改SQL语句、设置参数或进行其他操作。你可以通过实现StatementHandler接口来自定义SQL语句处理逻辑,例如动态修改SQL语句、添加分页逻辑、实现缓存等。

3.4、Executor执行拦截器:

拦截执行器的方法,主要负责SQL的执行,包括INSERT、UPDATE、DELETE等操作以及SELECT查询操作。通过拦截Executor接口的方法,可以实现对数据库操作前后的统一处理,比如开启事务、记录日志、分页处理、二级缓存控制等。

4、实际应用

4.1、自定义拦截器
java 复制代码
package com.springboot.cli;

import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import java.lang.reflect.Method;
import java.util.Properties;

/**
 * 类说明:@Intercepts注解用于标注一个类是一个MyBatis拦截器;@Signature注解用于指定一个方法的签名,其中包括方法所在的接口、方法名以及方法的参数列表
 * type参数指定了被拦截的接口类型,比如Executor.class表示要拦截Executor接口的方法。
 * method参数指定了要拦截的方法名。
 * args参数是一个Class数组,用来指定方法的参数类型列表,通过这些参数类型可以唯一确定要拦截的方法。
 * 如果需要拦截多个不同类型的方法,可以在@Intercepts注解中指定多个@Signature注解。
 *
 * @author liangtl
 * @Date 2024/7/14
 */
@Intercepts({
        @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})
})
public class MybatisInterceptor implements Interceptor {

    private Properties properties;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 代理对象
        Object target = invocation.getTarget();
        // 代理方法
        Method method = invocation.getMethod();
        // 方法参数
        Object[] args = invocation.getArgs();
        // 如果args长度大于1,说明有附带的其他参数
        Object parameter = null;
        if (args.length > 1) {
            // 查询参数
            parameter = args[1];
        }
        MappedStatement mappedStatement = (MappedStatement) args[0];
        // sqlId
        String sqlId = mappedStatement.getId();
        // 拼接完成的sql
        String sql = mappedStatement.getBoundSql(parameter).getSql();
        // 这里一定需要使用invocation.proceed()返回,以保证sql继续执行。
        // 如果想进行判断然后中断sql执行,可以返回emptyList。
        return invocation.proceed();
    }

    /**
     * 生成MyBatis拦截器代理对象(非必须)
     */
    @Override
    public Object plugin(Object target) {
        if(target instanceof StatementHandler){
            //调用插件
            return Plugin.wrap(target, this);
        }
        return target;
    }

    /**
     * 设置插件属性(直接通过Spring的方式获取属性,所以这个方法一般也用不到)
     * 项目启动的时候数据就会被加载
     */
    @Override
    public void setProperties(Properties properties) {
        //赋值成员变量,在其他方法使用。
        this.properties = properties;
    }
}
4.2、注册拦截器

拦截器注册有两种方式,一种是在配置文件中配置,第二种是通过代码的形式。

这里演示使用代码进行拦截器注册。

java 复制代码
package com.springboot.cli;

import org.apache.ibatis.datasource.pooled.PooledDataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

import javax.sql.DataSource;

/**
 * 数据源配置Demo
 *
 * @author liangtl
 * @Date 2024/7/14
 */

@Configuration
public class DataSourceConfig {
    @Value("${test.datasource.url}")
    private String url;

    @Value("${test.datasource.username}")
    private String user;

    @Value("${test.datasource.password}")
    private String password;

    @Value("${test.datasource.driverClassName}")
    private String driverClass;

    @Bean(name = "dataSource")
    public DataSource dataSource() {
        PooledDataSource dataSource = new PooledDataSource();
        dataSource.setDriver(driverClass);
        dataSource.setUrl(url);
        dataSource.setUsername(user);
        dataSource.setPassword(password);
        return dataSource;
    }

    @Bean(name = "transactionManager")
    public DataSourceTransactionManager dataSourceTransactionManager() {
        return new DataSourceTransactionManager(dataSource());
    }

    @Bean(name = "sqlSessionFactory")
    public SqlSessionFactory sqlSessionFactory(@Qualifier("dataSource") DataSource dataSource) throws Exception {
        final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        sessionFactory.setDataSource(dataSource);
        sessionFactory.setPlugins(new MybatisInterceptor());

        // 设置mapper文件位置
        sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver()
                .getResources("classpath:**/**/mapper/*.xml"));

        // 设置实体类映射规则: 下划线 -> 驼峰
        org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
        configuration.setMapUnderscoreToCamelCase(true);
        sessionFactory.setConfiguration(configuration);
        return sessionFactory.getObject();
    }
}

在我们定义的sqlSessionFactory这个bean中,sessionFactory.setPlugins(new MybatisInterceptor());这一行代码就是我们注册自定义拦截器的地方。

至此,Mybatis拦截器就已经从自定义到注册完成,当我们注入的是sqlSessionFactory这个Factory时,拦截器就会生效。

5、扩展

有些时候我们可能需要在拦截器中注入一些我们需要的类来完成一些特定的逻辑,于是我们默认使用@Autowired去将其注入,结果发生代码执行时报了NPE,因为注入的属性为null。

原因如下:

因为拦截器对象是由Servlet容器负责创建和管理的,而不是由Spring容器管理的。因此,Spring容器无法感知并注入拦截器中的属性。

现在已经知道原因了,那么就可以解决这个问题。

方法一:将拦截器作为一个Spring Bean进行配置,交由Spring容器进行管理

方法二:既然拦截器中无法注入,那就不注入了。新增一个有参构造使用构造器的方式注入属性

方法三:可以通过实现ApplicationContextAware接口或者直接在拦截器中获取ApplicationContext对象,然后从ApplicationContext中获取需要的Bean

参考博客:

Mybatis拦截器_@intercepts-CSDN博客

MyBatis拦截器四种类型和自定义拦截器的使用流程_mybatis 拦截器-CSDN博客

相关推荐
m0_5719575826 分钟前
Java | Leetcode Java题解之第543题二叉树的直径
java·leetcode·题解
魔道不误砍柴功3 小时前
Java 中如何巧妙应用 Function 让方法复用性更强
java·开发语言·python
NiNg_1_2343 小时前
SpringBoot整合SpringSecurity实现密码加密解密、登录认证退出功能
java·spring boot·后端
闲晨3 小时前
C++ 继承:代码传承的魔法棒,开启奇幻编程之旅
java·c语言·开发语言·c++·经验分享
测开小菜鸟4 小时前
使用python向钉钉群聊发送消息
java·python·钉钉
P.H. Infinity5 小时前
【RabbitMQ】04-发送者可靠性
java·rabbitmq·java-rabbitmq
生命几十年3万天5 小时前
java的threadlocal为何内存泄漏
java
caridle5 小时前
教程:使用 InterBase Express 访问数据库(五):TIBTransaction
java·数据库·express
^velpro^5 小时前
数据库连接池的创建
java·开发语言·数据库
苹果醋35 小时前
Java8->Java19的初步探索
java·运维·spring boot·mysql·nginx