这是我参与11月更文挑战的第7天,活动详情查看:2021最后一次更文挑战
1. 拦截器注解
1.1 MyBatis自定义拦截器
- 实现
org.apache.ibatis.plugin.Interceptor
接口。 - 添加拦截器注解
org.apache.ibatis.plugin.Intercepts
- 配置文件中添加拦截器
1.2 在MyBatis中可被拦截的类型有四种(按照拦截顺序)
- Executor:拦截执行器的方法。
- ParameterHandler:拦截参数的处理。
- ResultHandler:拦截结果集的处理。
- StatementHandler:拦截Sql语法构建的处理,绝大部分我们是在这里设置我们的拦截器
(1)不同拦截类型执行顺序:
(2)多个插件拦截的顺序?
需要注意的是,因为拦截器Aa和拦截器Bb均是拦截的StatementHandler对象,所以拦截器B在此获取StatementHandler的时候,获取的是代理对象。
(3)多个插件plugin()和intercept()方法的执行顺序
先执行每个插件的plugin方法,若是@Intercepts
注解标明需要拦截该对象,那么生成类型对象的代理对象。(即使该插件需要拦截该类型对象,但是依旧会执行下一个插件的plugin方法)。知道执行完毕所有的plugin方法。在执行每个Intercept方法。
1.3 拦截器注解的作用:
自定义拦截器必须使用MyBatis提供的注解来声明我们要拦截的类型对象。
Mybatis插件都要有Intercepts注解来指定要拦截哪个对象哪个方法。我们知道,Plugin.wrap方法会返回四大接口对象的代理对象,会拦截所有的方法。在代理对象执行对应方法的时候,会调用InvocationHandler处理器的invoke方法。
1.4 拦截器注解的规则:
具体规则如下:
@Intercepts({
@Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class}),
@Signature(type = StatementHandler.class, method = "update", args = {Statement.class}),
@Signature(type = StatementHandler.class, method = "batch", args = {Statement.class})
})
- @Intercepts:标识该类是一个拦截器
- @Signature:指明自定义拦截器需要拦截哪一个类型,哪一个方法;
- type:对应四种类型中的一种;
- method:对应接口中的哪类方法(因为可能存在重载方法);
- args:对应哪一个方法;
1.5. 拦截器可拦截的方法:**
拦截的类 | 拦截的方法 |
---|---|
Executor | update, query, flushStatements, commit, rollback,getTransaction, close, isClosed |
ParameterHandler | getParameterObject, setParameters |
StatementHandler | prepare, parameterize, batch, update, query |
ResultSetHandler | handleResultSets, handleOutputParameters |
2.实验自定义拦截器案例
谈到自定义拦截器实践部分,主要按照以下三步:
(1) 实现org.apache.ibatis.plugin.Interceptor
接口,重写以下方法:
public interface Interceptor {
Object intercept(Invocation var1) throws Throwable;
Object plugin(Object var1);
void setProperties(Properties var1);
}
(2) 添加拦截器注解@Intercepts{...}
。具体值遵循上述规则设置。
(3) 配置文件中添加拦截器。
intercept(Invocation invocation)
从上面我们了解到interceptor能够拦截的四种类型对象,此处入参invocation
便是指拦截到的对象。
举例说明:拦截**StatementHandler#query(Statement st,ResultHandler rh)**方法,那么Invocation就是该对象。
plugin(Object target)
这个方法的作用是就是让mybatis判断,是否要进行拦截,然后做出决定是否生成一个代理。
@Override
public Object plugin(Object target) {
//判断是否拦截这个类型对象(根据@Intercepts注解决定),然后决定是返回一个代理对象还是返回原对象。
//故我们在实现plugin方法时,要判断一下目标类型,如果是插件要拦截的对象时才执行Plugin.wrap方法,否则的话,直接返回目标本身。
if (target instanceof StatementHandler) {
return Plugin.wrap(target, this);
}
return target;
}
注意:每经过一个拦截器对象都会调用插件的plugin方法,也就是说,该方法会调用4次。根据@Intercepts注解来决定是否进行拦截处理。
setProperties(Properties properties)
拦截器需要一些变量对象,而且这个对象是支持可配置的。
实战:自定义拦截器
在设计数据库字段时,往往会有表示创建时间或者更新时间的字段,假如名字分别叫create_time
和update_time
。如果在每个表的insert或update时,都要手动设置这些字段,则会很麻烦。
通过MyBatis拦截器,就可省去这些重复的步骤。拦截器能帮我们自动设置时间。
@Intercepts({
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
})
public class CustomInterceptor implements Interceptor {
private static final Logger LOGGER = LoggerFactory.getLogger(EnvInterceptor.class);
@Override
public Object intercept(Invocation invocation) throws Throwable {
MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
Object object = invocation.getArgs()[1];
//sql类型
SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
if (SqlCommandType.INSERT.equals(sqlCommandType)) {
//插入操作时,自动插入env
Field fieldCreate = object.getClass().getDeclaredField("create_time");
fieldCreate.setAccessible(true);
fieldCreate.set(object, new Date());
}else{
if (SqlCommandType.UPDATE.equals(sqlCommandType)) {
//update时,自动更新updated_at
Field fieldUpdate = object.getClass().getDeclaredField("updated_time");
fieldUpdate.setAccessible(true);
fieldUpdate.set(object, new Date());
}
}
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
}
注册
<plugins>
<plugin interceptor="xxxx.CustomInterceptor"></plugin>
</plugins>