MyBatis插件介绍和理解
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 提供的强大机制,使用插件是非常简单的,只需实现 Interceptor 接口,并指定想要拦截的方法签名即可。
/**
* 在@Signature中
* type: 拦截的处理对象类型, 可以是Executor、ParameterHandler、ResultSetHandler、StatementHandler
* method: 拦截处理对象方法
* args: 拦截处理对象方法参数类型
*
* type指定了拦截对象类型,method和args描述拦截的对象方法签名
*
* Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
* ParameterHandler (getParameterObject, setParameters)
* ResultSetHandler (handleResultSets, handleOutputParameters)
* StatementHandler (prepare, parameterize, batch, update, query)
*/
@Intercepts({
@Signature(
type = Executor.class,
method = "update", // {@link Executor#query(MappedStatement,Object)}
args = {MappedStatement.class, Object.class}
),
@Signature(
type = Executor.class,
method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}
),
@Signature(
type = ParameterHandler.class,
method = "setParameters",
args = {PreparedStatement.class}
),
@Signature(
type = ResultSetHandler.class,
method = "handleResultSets",
args = {Statement.class}
)
})
public class ExamplePlugin implements Interceptor {
private Properties properties = new Properties();
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 拦截的目标对象,对象类型为@Signature#type定义的类型
// 如果定义了多个类型,需要进行类型判断后,分情况处理
Object target = invocation.getTarget();
if (target instanceof Executor) {
} else if (target instanceof ParameterHandler) {
} else if (target instanceof ResultSetHandler) {
} else {
}
// 拦截的方法参数,参数类型和顺序同@Signature#args中定义的签名参数
Object[] args = invocation.getArgs();
// 一般需要获取到MappedStatement、BoundSql以及入参对象
// 如果target中无法直接获取,则需要通过反射获取相应对象
// CASE1: 可直接获取
if (target instanceof Executor) {
MappedStatement mappedStatement = (MappedStatement) args[0];
Object parameterObject = args[1];
BoundSql boundSql = mappedStatement.getBoundSql(parameterObject);
Configuration configuration = mappedStatement.getConfiguration();
TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
}
// CASE2: 不可直接获取,通过反射获取
if (target instanceof ParameterHandler) {
DefaultParameterHandler parameterHandler = (DefaultParameterHandler) target;
PreparedStatement ps = (PreparedStatement) invocation.getArgs()[0];
// 反射获取 BoundSql 对象,此对象包含生成的sql和sql的参数map映射
Field boundSqlField = parameterHandler.getClass().getDeclaredField("boundSql");
boundSqlField.setAccessible(true);
BoundSql boundSql = (BoundSql) boundSqlField.get(parameterHandler);
// 反射获取 参数对像
Field parameterField = parameterHandler.getClass().getDeclaredField("parameterObject");
parameterField.setAccessible(true);
Object parameterObject = parameterField.get(parameterHandler);
Field mappedStatementField = parameterHandler.getClass().getDeclaredField("mappedStatement");
mappedStatementField.setAccessible(true);
MappedStatement mappedStatement = (MappedStatement) mappedStatementField.get(parameterHandler);
Configuration configuration = mappedStatement.getConfiguration();
TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
}
// 一般情况下
// 在invocation.proceed()之前处理入参映射
// 调用invocation.proceed(),意味着继续执行后续的拦截处理逻辑
// 可能的情况是,参数被重复添加到Statement中,或某些操作被重复执行或覆盖等等情况
// 所以应该按照实际的情况决定是否应该调用invocation.proceed()返回结果还是调用自己的逻辑处理后直接返回结果
Object returnObject = invocation.proceed();
// 在invocation.proceed()返回结果之后对结果进行处理
// returnObject是ArrayList<Object>对象
return returnObject;
}
@Override
public Object plugin(Object target) {
// 此处增加逻辑判断是否需要进入当前拦截插件进行处理
// 我们可能预期在某些条件满足的情况下才需要进入插件拦截处理逻辑,
// 即 intercept(Invocation invocation)方法
if (target instanceof MappedStatement ||
target instanceof ParameterHandler ||
target instanceof ResultSetHandler
) {
return Plugin.wrap(target, this);
}
return target;
}
/**
* 可在配置插件时向插件传递属性参数
*
* @param properties 插件属性
*/
@Override
public void setProperties(Properties properties) {
this.properties = properties;
}
}
要使插件生效,需要在mybatis配置中注册插件
<!-- mybatis-config.xml -->
<plugins>
<plugin interceptor="org.mybatis.example.ExamplePlugin">
<property name="someProperty" value="100"/>
</plugin>
</plugins>
除了用插件来修改 MyBatis 核心行为以外,还可以通过完全覆盖配置类来达到目的。只需继承配置类后覆盖其中的某个方法,再把它传递到 SqlSessionFactoryBuilder.build(myConfig) 方法即可。再次重申,这可能会极大影响 MyBatis 的行为,务请慎之又慎。