前言
Mybatis拦截器注解的基本写法格式为:
@Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})})
说明:
@Intercepts:标识该类是一个拦截器@Signature:拦截器相关属性设置
type
拦截器的类型,总共有4种,按照执行的先后顺序为:
Executor:拦截执行器的方法。ParameterHandler:拦截参数的处理。ResultHandler:拦截结果集的处理。StatementHandler:拦截Sql语法构建的处理,绝大部分我们是在这里设置我们的拦截器。
method
可被拦截的方法,按照拦截器的不同类型,总共有下面这些方法:
| 拦截的类型 | 拦截的方法 |
|---|---|
| Executor | update, query, flushStatements, commit, rollback,getTransaction, close, isClosed |
| ParameterHandler | getParameterObject, setParameters |
| StatementHandler | prepare, parameterize, batch, update, query |
| ResultSetHandler | handleResultSets, handleOutputParameters |
args
设置对应接口中的哪一个方法,比如 Executor 中 query方法因为重载原因,方法有多个,args 就是指明参数类型,从而确定是哪一个方法。
这里的方法和上面的方法的要怎么理解呢?可以这样理解,上面的那个方法(method)是大类的方法,比如update方法,下面的这个方法(args)呢,是具体的方法,用来指明你到底的哪里的update方法。
注册拦截器
使用配置文件
<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd"><!--其它配置项都在著配置文件配置了,所以这里只需要配置configuration项即可--><configuration><settings><!--是否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN 到经典 Java 属性名 aColumn 的类似映射。 --><setting name="mapUnderscoreToCamelCase" value="true"/></settings><plugins><plugin interceptor="com.example.IbatisInterceptor"/></plugins></configuration>
然后在主配置文件中,将mybatis配置文件引入:
# Mybatis配置mybatis:config-location: classpath:mybatis/config/mybatis-config.xml # mybatis配置文件,如果使用configuration的话就不需要mybatis配置文件了
todo:
mybatis的配置项中,有一个interceptors配置,其参数是一个列表(java.util.List)。通过查阅相关信息发现,不能在配置文件中引用自定义的拦截器。总之是暂时没找到 具体使用方法,先放着吧。
# Mybatis配置mybatis:# config-location: classpath:mybatis/config/mybatis-config.xml # mybatis配置文件,如果使用configuration的话就不需要mybatis配置文件了mapper-locations: classpath:mybatis/*.xml # mybatis映射文件configuration: # mybatis全局配置map-underscore-to-camel-case: true #开启驼峰命名规则interceptors:- xxx- xxxxx
使用配置类
import com.example.IbatisInterceptor;import org.apache.ibatis.session.SqlSessionFactory;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import java.util.Properties;@Configurationpublic class IbatisInterceptorConfig {@Beanpublic String MyBatisInterceptor(SqlSessionFactory sqlSessionFactory) {IbatisInterceptor ibatisInterceptor = new IbatisInterceptor();Properties properties = new Properties();properties.setProperty("driver", "mysql");properties.setProperty("username", "root");properties.setProperty("password", "123456");// 给拦截器添加自定义参数ibatisInterceptor.setProperties(properties);sqlSessionFactory.getConfiguration().addInterceptor(ibatisInterceptor);return "interceptor";}}
使用注解
直接在拦截器类上面使用@Component注解即可(本文中就是直接使用的注解)
使用拦截器
使用mybatis拦截器,需要实现Interceptor接口的三个方法:
intercept():当方法被拦截时调用,用于设置拦截后需要执行的业务逻辑,自定义拦截器时,此方法是必须实现的。plugin():在mybatis加载配置时被调用。设置是否需要对对象进行拦截
- 拦截对象:调用wrap()方法,返回一个代理对象,并执行intercept()方法
- 不拦截对象:直接返回目标原本对象,不会执行intercept()方法
setProperties():在mybatis加载配置时被调用。获取并设置mybatis配置文件或配置类中的property属性的值,配置类见上面注册拦截器时的演示
mybatis配置文件演示:
<environments default="mysql"><environment id="mysql"><transactionManager type="JDBC"/><dataSource type="POOLED"><!--mybatis配置项,上面的setProperties()方法中获取的propertys就是这里的--><property name="driver" value="com.mysql.jdbc.Driver"/><property name="url" value="jdbc:mysql://localhost:3306/test"/><property name="username" value="root"/><property name="password" value="123456"/></dataSource></environment></environments>
简单使用
以拦截执行器方法为例。
示例:拦截更新操作,在更新操作时统一添加 “更新时间 ”字段,同时打印当前sql语句。
从语义角度分析: insert、delete、update都是属于更新(update)操作; 而select属于查询(query)操作。
import com.example.bean.User;import org.apache.ibatis.executor.Executor;import org.apache.ibatis.mapping.BoundSql;import org.apache.ibatis.mapping.MappedStatement;import org.apache.ibatis.mapping.SqlCommandType;import org.apache.ibatis.plugin.*;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.stereotype.Component;import java.lang.reflect.Field;import java.sql.Date;import java.time.LocalDateTime;import java.util.Properties;@Component@Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})})public class IbatisInterceptor implements Interceptor {private Logger logger = LoggerFactory.getLogger(IbatisInterceptor.class);@Overridepublic Object intercept(Invocation invocation) throws Throwable {Object[] args = invocation.getArgs();SqlCommandType sqlCommandType = null;for (Object object : args) {// 从MappedStatement中获取到操作类型if (object instanceof MappedStatement) {MappedStatement mappedStatement = (MappedStatement) object;sqlCommandType = mappedStatement.getSqlCommandType();// 获取当前擦操作的sql语句BoundSql boundSql = mappedStatement.getBoundSql(object);String sql = boundSql.getSql();// 打印当前操作类型及当前操作的sql语句logger.info("操作类型: {},操作语句:{}", sqlCommandType, sql);}// 判断参数的类型是否和实体类一致(这里只有一个参数,所以直接用if判断)if (object instanceof User) {if (SqlCommandType.UPDATE == sqlCommandType) {// 由于没有写额外的获取实体类属性的方法,所以这里只能利用反射机制,获取需要修改的字段对应的实体类属性Field updateTime = object.getClass().getDeclaredField("updateTime");updateTime.setAccessible(true);// 对数据进行修改updateTime.set(object, Date.valueOf(LocalDateTime.now().toLocalDate()));}}}return invocation.proceed();}@Overridepublic Object plugin(Object target) {// 拦截属于Executor的对象if (target instanceof Executor) {// 返回代理return Plugin.wrap(target, this);}// 否则返回对象本身return target;}@Overridepublic void setProperties(Properties properties) {}}
拦截多个请求
示例:只做简单示例,不涉及业务逻辑
import com.example.bean.User;import org.apache.ibatis.executor.Executor;import org.apache.ibatis.executor.parameter.ParameterHandler;import org.apache.ibatis.mapping.BoundSql;import org.apache.ibatis.mapping.MappedStatement;import org.apache.ibatis.mapping.SqlCommandType;import org.apache.ibatis.plugin.*;import org.apache.ibatis.session.RowBounds;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.stereotype.Component;import java.sql.PreparedStatement;import java.util.Properties;@Component@Intercepts({@Signature(type = Executor.class,method = "query", args = {MappedStatement.class, Object.class}),@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),@Signature(type = Executor.class, method = "createCacheKey", args = {MappedStatement.class, Object.class, RowBounds.class, BoundSql.class}),@Signature(type = ParameterHandler.class, method = "setParameters", args = PreparedStatement.class),})public class IbatisInterceptor implements Interceptor {private Logger logger = LoggerFactory.getLogger(IbatisInterceptor.class);@Overridepublic Object intercept(Invocation invocation) throws Throwable {Object[] args = invocation.getArgs();SqlCommandType sqlCommandType = null;for (Object object : args) {// 从MappedStatement中获取到操作类型if (object instanceof MappedStatement) {MappedStatement mappedStatement = (MappedStatement) object;sqlCommandType = mappedStatement.getSqlCommandType();// 判断参数的类型是否和实体类一致(这里只有一个参数,所以直接用if判断)if (object instanceof User) {if (SqlCommandType.UPDATE == sqlCommandType) {// 执行更新操作修改} else if (SqlCommandType.SELECT == sqlCommandType) {// 执行查询操作修改} else if (SqlCommandType.DELETE == sqlCommandType) {// 执行删除操作修改} else if (SqlCommandType.INSERT == sqlCommandType) {// 执行插入操作修改}}}else if(object instanceof PreparedStatement){// 从PreparedStatement中获取到操作类型PreparedStatement preparedStatement = (PreparedStatement) object;// doSomething}// 判断参数的类型是否和实体类一致(这里只有一个参数,所以直接用if判断)if (object instanceof User) {if (SqlCommandType.UPDATE == sqlCommandType) {// 执行更新操作修改} else if (SqlCommandType.SELECT == sqlCommandType) {// 执行查询操作修改} else if (SqlCommandType.DELETE == sqlCommandType) {// 执行删除操作修改} else if (SqlCommandType.INSERT == sqlCommandType) {// 执行插入操作修改}}}return invocation.proceed();}@Overridepublic Object plugin(Object target) {if (target instanceof Executor) {return Plugin.wrap(target, this);}return target;}@Overridepublic void setProperties(Properties properties) {// String username = properties.getProperty("username");// String password = properties.getProperty("password");}}
拦截并修改sql语句
从上面的示例中我们能够发现,是可以在拦截器中获取到sql语句的,那么我们就可以通过反射修改sql语句。
将通过id删除数据的sql语句改为通过name删除的sql语句:
有两种方法:
第一种是通过Statement修改
import org.apache.ibatis.executor.Executor;import org.apache.ibatis.mapping.*;import org.apache.ibatis.plugin.*;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.stereotype.Component;import java.util.Properties;@Component@Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})})public class IbatisInterceptor implements Interceptor {private Logger logger = LoggerFactory.getLogger(IbatisInterceptor.class);@Overridepublic Object intercept(Invocation invocation) throws Throwable {Object[] args = invocation.getArgs();SqlCommandType sqlCommandType = null;for (Object object : args) {// 从MappedStatement中获取到操作类型if (object instanceof MappedStatement) {MappedStatement mappedStatement = (MappedStatement) object;sqlCommandType = mappedStatement.getSqlCommandType();// 获取当前擦操作的sql语句BoundSql boundSql = mappedStatement.getBoundSql(object);String sql = boundSql.getSql();// 打印当前操作类型及当前操作的sql语句logger.info("操作类型: {},操作语句:{}", sqlCommandType, sql);// 设置新的sql语句String newSql = "delete from users where u_name=?";/** 通过statement修改sql语句*/// 重新new一个查询语句对像BoundSql newBoundSql = new BoundSql(mappedStatement.getConfiguration(), newSql, boundSql.getParameterMappings(), boundSql.getParameterObject());// 把新的查询放到statement里MappedStatement neMappedStatement = copyFromMappedStatement(mappedStatement, new BoundSqlSqlSource(newBoundSql));for (ParameterMapping mapping : boundSql.getParameterMappings()) {String prop = mapping.getProperty();if (boundSql.hasAdditionalParameter(prop)) {newBoundSql.setAdditionalParameter(prop, boundSql.getAdditionalParameter(prop));}}final Object[] queryArgs = invocation.getArgs();// 这个i就是设置你需要修改哪个拦截的请求,对应上面的@Intercepts注解中的数组中的元素(@Signature注解)对应的索引// 因为我上面只有一个(@Signature注解的)元素,对应的索引自然也就是0int i = 0;queryArgs[i] = neMappedStatement;}}return invocation.proceed();}@Overridepublic Object plugin(Object target) {// 拦截属于Executor的对象if (target instanceof Executor) {// 返回代理return Plugin.wrap(target, this);}// 否则返回对象本身return target;}@Overridepublic void setProperties(Properties properties) {}private MappedStatement copyFromMappedStatement(MappedStatement ms, SqlSource newSqlSource) {MappedStatement.Builder builder = new MappedStatement.Builder(ms.getConfiguration(), ms.getId(), newSqlSource, ms.getSqlCommandType());builder.resource(ms.getResource());builder.fetchSize(ms.getFetchSize());builder.statementType(ms.getStatementType());builder.keyGenerator(ms.getKeyGenerator());if (ms.getKeyProperties() != null && ms.getKeyProperties().length > 0) {builder.keyProperty(ms.getKeyProperties()[0]);}builder.timeout(ms.getTimeout());builder.parameterMap(ms.getParameterMap());builder.resultMaps(ms.getResultMaps());builder.resultSetType(ms.getResultSetType());builder.cache(ms.getCache());builder.flushCacheRequired(ms.isFlushCacheRequired());builder.useCache(ms.isUseCache());return builder.build();}public static class BoundSqlSqlSource implements SqlSource {private BoundSql boundSql;public BoundSqlSqlSource(BoundSql boundSql) {this.boundSql = boundSql;}public BoundSql getBoundSql(Object parameterObject) {return boundSql;}}}
第二种是直接通过反射修改:
import org.apache.ibatis.executor.Executor;import org.apache.ibatis.mapping.BoundSql;import org.apache.ibatis.mapping.MappedStatement;import org.apache.ibatis.mapping.SqlCommandType;import org.apache.ibatis.plugin.*;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.stereotype.Component;import java.lang.reflect.Field;import java.util.Properties;@Component@Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})})public class IbatisInterceptor implements Interceptor {private Logger logger = LoggerFactory.getLogger(IbatisInterceptor.class);@Overridepublic Object intercept(Invocation invocation) throws Throwable {Object[] args = invocation.getArgs();SqlCommandType sqlCommandType = null;for (Object object : args) {// 从MappedStatement中获取到操作类型if (object instanceof MappedStatement) {MappedStatement mappedStatement = (MappedStatement) object;sqlCommandType = mappedStatement.getSqlCommandType();// 获取当前擦操作的sql语句BoundSql boundSql = mappedStatement.getBoundSql(object);String sql = boundSql.getSql();// 打印当前操作类型及当前操作的sql语句logger.info("操作类型: {},操作语句:{}", sqlCommandType, sql);// 设置新的sql语句String newSql = "delete from users where u_name=?";// 通过反射修改sql语句Field field = boundSql.getClass().getDeclaredField("sql");field.setAccessible(true);field.set(boundSql, newSql);}}return invocation.proceed();}@Overridepublic Object plugin(Object target) {// 拦截属于Executor的对象if (target instanceof Executor) {// 返回代理return Plugin.wrap(target, this);}// 否则返回对象本身return target;}@Overridepublic void setProperties(Properties properties) {}}
最后来个图吧:
