MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括。
这些类中方法的细节可以通过查看每个方法的签名来发现,或者直接查看 MyBatis 发行包中的源代码。 如果你想做的不仅仅是监控方法的调用,那么你最好相当了解要重写的方法的行为。 因为如果在试图修改或重写已有方法的行为的时候,你很可能在破坏 MyBatis 的核心模块。 这些都是更低层的类和方法,所以使用插件的时候要特别当心。
功能描述:编写一个MyBatis插件,在执行删除动作的时候,做日志记录
编写类DeleteWarningPlugin,实现Interceptor 接口
@Intercepts({@Signature(
type = Executor.class,
method = "update",
args = {MappedStatement.class, Object.class})})
public class DeleteWarningPlugin implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
MappedStatement ms = (MappedStatement) invocation.getArgs()[0];
System.out.println();
if ("DELETE".equals(ms.getSqlCommandType().name())) {
SqlSource sqlSource = ms.getSqlSource();
RawSqlSource rawSqlSource = (RawSqlSource) sqlSource;
MetaObject metaObject = SystemMetaObject.forObject(rawSqlSource);
SqlSource thisSqlSource = (StaticSqlSource) metaObject.getValue("sqlSource");
MetaObject metaObjectSql = SystemMetaObject.forObject(thisSqlSource);
String sql = (String) metaObjectSql.getValue("sql");
System.out.println("系统发生删除操作:" + sql);
}
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
for (String key : properties.stringPropertyNames()) {
System.out.println(key + " " + properties.get(key));
}
}
}
在mybatis-config.xml配置中加入插件
运行调试:
public class Demo6PluginExample {
public static void main(String[] args) throws IOException {
String resource = "mybatis/conf/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
//从 XML 中构建 SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession session = sqlSessionFactory.openSession(true);
try {
BlogMapper mapper = session.getMapper(BlogMapper.class);
int result = mapper.delete(25L);
System.out.println("result: " + result);
} finally {
session.close();
}
}
}
调试结果:
someProperty 100
系统发生删除操作:delete from blog where blog_id = ?
result: 1
可以看到,插件已经起了效果。
我们拿Executor 来说,
打开源码org.apache.ibatis.session.Configuration->newExecutor()
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
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 = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
打开InterceptorChain源码
public class InterceptorChain {
private final List interceptors = new ArrayList();
public Object pluginAll(Object target) {
//迭代包装所有插件
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
public void addInterceptor(Interceptor interceptor) {
interceptors.add(interceptor);
}
public List getInterceptors() {
return Collections.unmodifiableList(interceptors);
}
}
org.apache.ibatis.session.Configuration->newExecutor() 何时调用?顺序如下
SqlSession session = sqlSessionFactory.openSession();
DefaultSqlSessionFactory->openSessionFromDataSource()
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
打开Plugin这个类我们可以看到,这其实是一个动态代理
public static Object wrap(Object target, Interceptor interceptor) {
Map, Set> signatureMap = getSignatureMap(interceptor);
Class> type = target.getClass();
Class>[] interfaces = getAllInterfaces(type, signatureMap);
if (interfaces.length > 0) {
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
return target;
}