一、Mybatis插件介绍:
Mybatis插件又称拦截器,Mybatis采用责任链模式,通过动态代理组织多个插件(拦截器),通过这些插件可以改变Mybatis的默认行为(诸如SQL重写之类的)。Mybatis的插件是采用对四大接口的对象生成动态代理对象的方法来实现的。那么现在我们看下Mybatis是怎么创建这四大接口对象的。
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
//确保ExecutorType不为空(defaultExecutorType有可能为空)
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
} if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
return parameterHandler;
}
public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler, ResultHandler resultHandler, BoundSql boundSql) {
ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
return resultSetHandler;
}
查看源码可以发现, Mybatis框架在创建好这四大接口对象的实例后,都会调用InterceptorChain.pluginAll()方法。InterceptorChain对象是插件执行链对象,看源码就知道里面维护了Mybatis配置的所有插件(Interceptor)对象。
// target --> Executor/ParameterHandler/ResultSetHander/StatementHandler
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
其实就是按顺序执行我们插件的plugin方法,一层一层返回我们原对象(Executor/ParameterHandler/ResultSetHander/StatementHandler)的代理对象。当我们调用四大接口的方法的时候,实际上是调用代理对象的相应方法,代理对象又会调用四大接口的实例。
二、自定义Mybatis插件:拦截增、删、改时的操作,进行事务校验
mybatis插件接口类:
package org.apache.ibatis.plugin;
import java.util.Properties;
/**
* @author Clinton Begin
*/
public interface Interceptor {
Object intercept(Invocation invocation) throws Throwable;
Object plugin(Object target);
void setProperties(Properties properties);
}
自定义的插件抽象类(这个可以不要)
/**
* MyBatis插件抽象类
*
* @author xx
* @CreateDate xxx
*/
public class AbstractInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
}
自定义的插件类
/**
* 拦截增、删、改时的操作 <br>
*
* @author xxx <br>
* @CreateDate xxx <br>
*/
@Intercepts({
@Signature(type = Executor.class, method = "update", args = {
MappedStatement.class, Object.class
})
})
public class TransactionVerifyInterceptor extends AbstractInterceptor {
/**
* 事务验证
*/
private boolean transactionVerify;
@Override
public Object intercept(Invocation invocation) throws Throwable {
if (transactionVerify && !TransactionSynchronizationManager.isSynchronizationActive() && needVerify(invocation)) {
// 1、开启了事务验证,如果没有开启事务就抛出异常
throw new NoTransactionException("You are modifying database data. Please open the transaction.");
}
return super.intercept(invocation);
}
@Override
public void setProperties(Properties properties) {
String verify = properties.getProperty(Settings.TRANSACTION_VERIFY);
if (StringUtils.isNotEmpty(verify)) {
transactionVerify = BooleanUtils.toBoolean(verify);
}
}
private boolean needVerify(Invocation invocation) throws ClassNotFoundException {
Object[] args = invocation.getArgs();
MappedStatement mappedStatement = (MappedStatement) args[0];
MapperMethodMapping mapperMethodMapping = MapperHelper.getMapperMethodMapping(mappedStatement);
Method method = mapperMethodMapping.getMethod();
IgnoreTransactionVerify ignore = AnnotationUtils.getAnnotation(method, IgnoreTransactionVerify.class);
return ignore == null;
}
}
类继承关系:
类似的插件还有分页插件
参考文章:https://database.51cto.com/art/201912/608017.htm#topx