前言

Mybatis拦截器注解的基本写法格式为:

  1. @Intercepts({
  2. @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
  3. })

说明: @Intercepts:标识该类是一个拦截器 @Signature:拦截器相关属性设置

type

拦截器的类型,总共有4种,按照执行的先后顺序为:

  1. Executor:拦截执行器的方法。
  2. ParameterHandler:拦截参数的处理。
  3. ResultHandler:拦截结果集的处理。
  4. StatementHandler:拦截Sql语法构建的处理,绝大部分我们是在这里设置我们的拦截器。

image.png

method

可被拦截的方法,按照拦截器的不同类型,总共有下面这些方法:

拦截的类型 拦截的方法
Executor update, query, flushStatements, commit, rollback,getTransaction, close, isClosed
ParameterHandler getParameterObject, setParameters
StatementHandler prepare, parameterize, batch, update, query
ResultSetHandler handleResultSets, handleOutputParameters

args

设置对应接口中的哪一个方法,比如 Executorquery方法因为重载原因,方法有多个,args 就是指明参数类型,从而确定是哪一个方法。

这里的方法和上面的方法的要怎么理解呢?可以这样理解,上面的那个方法(method)是大类的方法,比如update方法,下面的这个方法(args)呢,是具体的方法,用来指明你到底的哪里的update方法。

注册拦截器

使用配置文件

  1. <?xml version="1.0" encoding="UTF-8" ?>
  2. <!DOCTYPE configuration
  3. PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  4. "http://mybatis.org/dtd/mybatis-3-config.dtd">
  5. <!--其它配置项都在著配置文件配置了,所以这里只需要配置configuration项即可-->
  6. <configuration>
  7. <settings>
  8. <!--是否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN 到经典 Java 属性名 aColumn 的类似映射。 -->
  9. <setting name="mapUnderscoreToCamelCase" value="true"/>
  10. </settings>
  11. <plugins>
  12. <plugin interceptor="com.example.IbatisInterceptor"/>
  13. </plugins>
  14. </configuration>

然后在主配置文件中,将mybatis配置文件引入:

  1. # Mybatis配置
  2. mybatis:
  3. config-location: classpath:mybatis/config/mybatis-config.xml # mybatis配置文件,如果使用configuration的话就不需要mybatis配置文件了

todo:
mybatis的配置项中,有一个interceptors配置,其参数是一个列表(java.util.List)。通过查阅相关信息发现,不能在配置文件中引用自定义的拦截器。总之是暂时没找到 具体使用方法,先放着吧。

  1. # Mybatis配置
  2. mybatis:
  3. # config-location: classpath:mybatis/config/mybatis-config.xml # mybatis配置文件,如果使用configuration的话就不需要mybatis配置文件了
  4. mapper-locations: classpath:mybatis/*.xml # mybatis映射文件
  5. configuration: # mybatis全局配置
  6. map-underscore-to-camel-case: true #开启驼峰命名规则
  7. interceptors:
  8. - xxx
  9. - xxxxx

使用配置类

  1. import com.example.IbatisInterceptor;
  2. import org.apache.ibatis.session.SqlSessionFactory;
  3. import org.springframework.context.annotation.Bean;
  4. import org.springframework.context.annotation.Configuration;
  5. import java.util.Properties;
  6. @Configuration
  7. public class IbatisInterceptorConfig {
  8. @Bean
  9. public String MyBatisInterceptor(SqlSessionFactory sqlSessionFactory) {
  10. IbatisInterceptor ibatisInterceptor = new IbatisInterceptor();
  11. Properties properties = new Properties();
  12. properties.setProperty("driver", "mysql");
  13. properties.setProperty("username", "root");
  14. properties.setProperty("password", "123456");
  15. // 给拦截器添加自定义参数
  16. ibatisInterceptor.setProperties(properties);
  17. sqlSessionFactory.getConfiguration().addInterceptor(ibatisInterceptor);
  18. return "interceptor";
  19. }
  20. }

使用注解

直接在拦截器类上面使用@Component注解即可(本文中就是直接使用的注解)

使用拦截器

使用mybatis拦截器,需要实现Interceptor接口的三个方法:

  1. intercept():当方法被拦截时调用,用于设置拦截后需要执行的业务逻辑,自定义拦截器时,此方法是必须实现的。
  2. plugin():在mybatis加载配置时被调用。设置是否需要对对象进行拦截
  • 拦截对象:调用wrap()方法,返回一个代理对象,并执行intercept()方法
  • 不拦截对象:直接返回目标原本对象,不会执行intercept()方法
  1. setProperties():在mybatis加载配置时被调用。获取并设置mybatis配置文件或配置类中的property属性的值,配置类见上面注册拦截器时的演示

mybatis配置文件演示:

  1. <environments default="mysql">
  2. <environment id="mysql">
  3. <transactionManager type="JDBC"/>
  4. <dataSource type="POOLED">
  5. <!--mybatis配置项,上面的setProperties()方法中获取的propertys就是这里的-->
  6. <property name="driver" value="com.mysql.jdbc.Driver"/>
  7. <property name="url" value="jdbc:mysql://localhost:3306/test"/>
  8. <property name="username" value="root"/>
  9. <property name="password" value="123456"/>
  10. </dataSource>
  11. </environment>
  12. </environments>

原理图:
image.png

简单使用

以拦截执行器方法为例。
示例:拦截更新操作,在更新操作时统一添加 “更新时间 ”字段,同时打印当前sql语句。

从语义角度分析: insert、delete、update都是属于更新(update)操作; 而select属于查询(query)操作。

  1. import com.example.bean.User;
  2. import org.apache.ibatis.executor.Executor;
  3. import org.apache.ibatis.mapping.BoundSql;
  4. import org.apache.ibatis.mapping.MappedStatement;
  5. import org.apache.ibatis.mapping.SqlCommandType;
  6. import org.apache.ibatis.plugin.*;
  7. import org.slf4j.Logger;
  8. import org.slf4j.LoggerFactory;
  9. import org.springframework.stereotype.Component;
  10. import java.lang.reflect.Field;
  11. import java.sql.Date;
  12. import java.time.LocalDateTime;
  13. import java.util.Properties;
  14. @Component
  15. @Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})})
  16. public class IbatisInterceptor implements Interceptor {
  17. private Logger logger = LoggerFactory.getLogger(IbatisInterceptor.class);
  18. @Override
  19. public Object intercept(Invocation invocation) throws Throwable {
  20. Object[] args = invocation.getArgs();
  21. SqlCommandType sqlCommandType = null;
  22. for (Object object : args) {
  23. // 从MappedStatement中获取到操作类型
  24. if (object instanceof MappedStatement) {
  25. MappedStatement mappedStatement = (MappedStatement) object;
  26. sqlCommandType = mappedStatement.getSqlCommandType();
  27. // 获取当前擦操作的sql语句
  28. BoundSql boundSql = mappedStatement.getBoundSql(object);
  29. String sql = boundSql.getSql();
  30. // 打印当前操作类型及当前操作的sql语句
  31. logger.info("操作类型: {},操作语句:{}", sqlCommandType, sql);
  32. }
  33. // 判断参数的类型是否和实体类一致(这里只有一个参数,所以直接用if判断)
  34. if (object instanceof User) {
  35. if (SqlCommandType.UPDATE == sqlCommandType) {
  36. // 由于没有写额外的获取实体类属性的方法,所以这里只能利用反射机制,获取需要修改的字段对应的实体类属性
  37. Field updateTime = object.getClass().getDeclaredField("updateTime");
  38. updateTime.setAccessible(true);
  39. // 对数据进行修改
  40. updateTime.set(object, Date.valueOf(LocalDateTime.now().toLocalDate()));
  41. }
  42. }
  43. }
  44. return invocation.proceed();
  45. }
  46. @Override
  47. public Object plugin(Object target) {
  48. // 拦截属于Executor的对象
  49. if (target instanceof Executor) {
  50. // 返回代理
  51. return Plugin.wrap(target, this);
  52. }
  53. // 否则返回对象本身
  54. return target;
  55. }
  56. @Override
  57. public void setProperties(Properties properties) {
  58. }
  59. }

拦截多个请求

示例:只做简单示例,不涉及业务逻辑

  1. import com.example.bean.User;
  2. import org.apache.ibatis.executor.Executor;
  3. import org.apache.ibatis.executor.parameter.ParameterHandler;
  4. import org.apache.ibatis.mapping.BoundSql;
  5. import org.apache.ibatis.mapping.MappedStatement;
  6. import org.apache.ibatis.mapping.SqlCommandType;
  7. import org.apache.ibatis.plugin.*;
  8. import org.apache.ibatis.session.RowBounds;
  9. import org.slf4j.Logger;
  10. import org.slf4j.LoggerFactory;
  11. import org.springframework.stereotype.Component;
  12. import java.sql.PreparedStatement;
  13. import java.util.Properties;
  14. @Component
  15. @Intercepts({@Signature(type = Executor.class,method = "query", args = {MappedStatement.class, Object.class}),
  16. @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
  17. @Signature(type = Executor.class, method = "createCacheKey", args = {MappedStatement.class, Object.class, RowBounds.class, BoundSql.class}),
  18. @Signature(type = ParameterHandler.class, method = "setParameters", args = PreparedStatement.class),
  19. })
  20. public class IbatisInterceptor implements Interceptor {
  21. private Logger logger = LoggerFactory.getLogger(IbatisInterceptor.class);
  22. @Override
  23. public Object intercept(Invocation invocation) throws Throwable {
  24. Object[] args = invocation.getArgs();
  25. SqlCommandType sqlCommandType = null;
  26. for (Object object : args) {
  27. // 从MappedStatement中获取到操作类型
  28. if (object instanceof MappedStatement) {
  29. MappedStatement mappedStatement = (MappedStatement) object;
  30. sqlCommandType = mappedStatement.getSqlCommandType();
  31. // 判断参数的类型是否和实体类一致(这里只有一个参数,所以直接用if判断)
  32. if (object instanceof User) {
  33. if (SqlCommandType.UPDATE == sqlCommandType) {
  34. // 执行更新操作修改
  35. } else if (SqlCommandType.SELECT == sqlCommandType) {
  36. // 执行查询操作修改
  37. } else if (SqlCommandType.DELETE == sqlCommandType) {
  38. // 执行删除操作修改
  39. } else if (SqlCommandType.INSERT == sqlCommandType) {
  40. // 执行插入操作修改
  41. }
  42. }
  43. }else if(object instanceof PreparedStatement){
  44. // 从PreparedStatement中获取到操作类型
  45. PreparedStatement preparedStatement = (PreparedStatement) object;
  46. // doSomething
  47. }
  48. // 判断参数的类型是否和实体类一致(这里只有一个参数,所以直接用if判断)
  49. if (object instanceof User) {
  50. if (SqlCommandType.UPDATE == sqlCommandType) {
  51. // 执行更新操作修改
  52. } else if (SqlCommandType.SELECT == sqlCommandType) {
  53. // 执行查询操作修改
  54. } else if (SqlCommandType.DELETE == sqlCommandType) {
  55. // 执行删除操作修改
  56. } else if (SqlCommandType.INSERT == sqlCommandType) {
  57. // 执行插入操作修改
  58. }
  59. }
  60. }
  61. return invocation.proceed();
  62. }
  63. @Override
  64. public Object plugin(Object target) {
  65. if (target instanceof Executor) {
  66. return Plugin.wrap(target, this);
  67. }
  68. return target;
  69. }
  70. @Override
  71. public void setProperties(Properties properties) {
  72. // String username = properties.getProperty("username");
  73. // String password = properties.getProperty("password");
  74. }
  75. }

拦截并修改sql语句

从上面的示例中我们能够发现,是可以在拦截器中获取到sql语句的,那么我们就可以通过反射修改sql语句。
将通过id删除数据的sql语句改为通过name删除的sql语句:
有两种方法:
第一种是通过Statement修改

  1. import org.apache.ibatis.executor.Executor;
  2. import org.apache.ibatis.mapping.*;
  3. import org.apache.ibatis.plugin.*;
  4. import org.slf4j.Logger;
  5. import org.slf4j.LoggerFactory;
  6. import org.springframework.stereotype.Component;
  7. import java.util.Properties;
  8. @Component
  9. @Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})})
  10. public class IbatisInterceptor implements Interceptor {
  11. private Logger logger = LoggerFactory.getLogger(IbatisInterceptor.class);
  12. @Override
  13. public Object intercept(Invocation invocation) throws Throwable {
  14. Object[] args = invocation.getArgs();
  15. SqlCommandType sqlCommandType = null;
  16. for (Object object : args) {
  17. // 从MappedStatement中获取到操作类型
  18. if (object instanceof MappedStatement) {
  19. MappedStatement mappedStatement = (MappedStatement) object;
  20. sqlCommandType = mappedStatement.getSqlCommandType();
  21. // 获取当前擦操作的sql语句
  22. BoundSql boundSql = mappedStatement.getBoundSql(object);
  23. String sql = boundSql.getSql();
  24. // 打印当前操作类型及当前操作的sql语句
  25. logger.info("操作类型: {},操作语句:{}", sqlCommandType, sql);
  26. // 设置新的sql语句
  27. String newSql = "delete from users where u_name=?";
  28. /*
  29. * 通过statement修改sql语句
  30. */
  31. // 重新new一个查询语句对像
  32. BoundSql newBoundSql = new BoundSql(mappedStatement.getConfiguration(), newSql, boundSql.getParameterMappings(), boundSql.getParameterObject());
  33. // 把新的查询放到statement里
  34. MappedStatement neMappedStatement = copyFromMappedStatement(mappedStatement, new BoundSqlSqlSource(newBoundSql));
  35. for (ParameterMapping mapping : boundSql.getParameterMappings()) {
  36. String prop = mapping.getProperty();
  37. if (boundSql.hasAdditionalParameter(prop)) {
  38. newBoundSql.setAdditionalParameter(prop, boundSql.getAdditionalParameter(prop));
  39. }
  40. }
  41. final Object[] queryArgs = invocation.getArgs();
  42. // 这个i就是设置你需要修改哪个拦截的请求,对应上面的@Intercepts注解中的数组中的元素(@Signature注解)对应的索引
  43. // 因为我上面只有一个(@Signature注解的)元素,对应的索引自然也就是0
  44. int i = 0;
  45. queryArgs[i] = neMappedStatement;
  46. }
  47. }
  48. return invocation.proceed();
  49. }
  50. @Override
  51. public Object plugin(Object target) {
  52. // 拦截属于Executor的对象
  53. if (target instanceof Executor) {
  54. // 返回代理
  55. return Plugin.wrap(target, this);
  56. }
  57. // 否则返回对象本身
  58. return target;
  59. }
  60. @Override
  61. public void setProperties(Properties properties) {
  62. }
  63. private MappedStatement copyFromMappedStatement(MappedStatement ms, SqlSource newSqlSource) {
  64. MappedStatement.Builder builder = new MappedStatement.Builder(ms.getConfiguration(), ms.getId(), newSqlSource, ms.getSqlCommandType());
  65. builder.resource(ms.getResource());
  66. builder.fetchSize(ms.getFetchSize());
  67. builder.statementType(ms.getStatementType());
  68. builder.keyGenerator(ms.getKeyGenerator());
  69. if (ms.getKeyProperties() != null && ms.getKeyProperties().length > 0) {
  70. builder.keyProperty(ms.getKeyProperties()[0]);
  71. }
  72. builder.timeout(ms.getTimeout());
  73. builder.parameterMap(ms.getParameterMap());
  74. builder.resultMaps(ms.getResultMaps());
  75. builder.resultSetType(ms.getResultSetType());
  76. builder.cache(ms.getCache());
  77. builder.flushCacheRequired(ms.isFlushCacheRequired());
  78. builder.useCache(ms.isUseCache());
  79. return builder.build();
  80. }
  81. public static class BoundSqlSqlSource implements SqlSource {
  82. private BoundSql boundSql;
  83. public BoundSqlSqlSource(BoundSql boundSql) {
  84. this.boundSql = boundSql;
  85. }
  86. public BoundSql getBoundSql(Object parameterObject) {
  87. return boundSql;
  88. }
  89. }
  90. }

第二种是直接通过反射修改:

  1. import org.apache.ibatis.executor.Executor;
  2. import org.apache.ibatis.mapping.BoundSql;
  3. import org.apache.ibatis.mapping.MappedStatement;
  4. import org.apache.ibatis.mapping.SqlCommandType;
  5. import org.apache.ibatis.plugin.*;
  6. import org.slf4j.Logger;
  7. import org.slf4j.LoggerFactory;
  8. import org.springframework.stereotype.Component;
  9. import java.lang.reflect.Field;
  10. import java.util.Properties;
  11. @Component
  12. @Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})})
  13. public class IbatisInterceptor implements Interceptor {
  14. private Logger logger = LoggerFactory.getLogger(IbatisInterceptor.class);
  15. @Override
  16. public Object intercept(Invocation invocation) throws Throwable {
  17. Object[] args = invocation.getArgs();
  18. SqlCommandType sqlCommandType = null;
  19. for (Object object : args) {
  20. // 从MappedStatement中获取到操作类型
  21. if (object instanceof MappedStatement) {
  22. MappedStatement mappedStatement = (MappedStatement) object;
  23. sqlCommandType = mappedStatement.getSqlCommandType();
  24. // 获取当前擦操作的sql语句
  25. BoundSql boundSql = mappedStatement.getBoundSql(object);
  26. String sql = boundSql.getSql();
  27. // 打印当前操作类型及当前操作的sql语句
  28. logger.info("操作类型: {},操作语句:{}", sqlCommandType, sql);
  29. // 设置新的sql语句
  30. String newSql = "delete from users where u_name=?";
  31. // 通过反射修改sql语句
  32. Field field = boundSql.getClass().getDeclaredField("sql");
  33. field.setAccessible(true);
  34. field.set(boundSql, newSql);
  35. }
  36. }
  37. return invocation.proceed();
  38. }
  39. @Override
  40. public Object plugin(Object target) {
  41. // 拦截属于Executor的对象
  42. if (target instanceof Executor) {
  43. // 返回代理
  44. return Plugin.wrap(target, this);
  45. }
  46. // 否则返回对象本身
  47. return target;
  48. }
  49. @Override
  50. public void setProperties(Properties properties) {
  51. }
  52. }

最后来个图吧:
image.png