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、自定义拦截器
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、注册拦截器
拦截器注册有两种方式,一种是在配置文件中配置,第二种是通过代码的形式。
这里演示使用代码进行拦截器注册。
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
参考博客: