前言
MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括。
- Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
- ParameterHandler (getParameterObject, setParameters)
- ResultSetHandler (handleResultSets, handleOutputParameters)
- StatementHandler (prepare, parameterize, batch, update, query)
这些类中方法的细节可以通过查看每个方法的签名来发现,或者直接查看 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配置中加入插件
<plugins>
<plugin interceptor="top.yuyufeng.learn.mybatis.interceptor.DeleteWarningPlugin">
<property name="someProperty" value="100"/>
</plugin>
<plugin interceptor="com.github.pagehelper.PageHelper">
<property name="dialect" value="mysql"/>
</plugin>
</plugins>
运行调试:
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) interceptorChain.pluginAll(executor);
return executor;
}
打开InterceptorChain源码
public class InterceptorChain {
private final List<Interceptor> interceptors = new ArrayList<Interceptor>();
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<Interceptor> getInterceptors() {
return Collections.unmodifiableList(interceptors);
}
}