前言
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 configuration
PUBLIC "-//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;
@Configuration
public class IbatisInterceptorConfig {
@Bean
public 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);
@Override
public 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();
}
@Override
public Object plugin(Object target) {
// 拦截属于Executor的对象
if (target instanceof Executor) {
// 返回代理
return Plugin.wrap(target, this);
}
// 否则返回对象本身
return target;
}
@Override
public 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);
@Override
public 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();
}
@Override
public Object plugin(Object target) {
if (target instanceof Executor) {
return Plugin.wrap(target, this);
}
return target;
}
@Override
public 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);
@Override
public 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注解的)元素,对应的索引自然也就是0
int i = 0;
queryArgs[i] = neMappedStatement;
}
}
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
// 拦截属于Executor的对象
if (target instanceof Executor) {
// 返回代理
return Plugin.wrap(target, this);
}
// 否则返回对象本身
return target;
}
@Override
public 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);
@Override
public 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();
}
@Override
public Object plugin(Object target) {
// 拦截属于Executor的对象
if (target instanceof Executor) {
// 返回代理
return Plugin.wrap(target, this);
}
// 否则返回对象本身
return target;
}
@Override
public void setProperties(Properties properties) {
}
}
最后来个图吧: