一、插件的使用
2. 介绍
MyBatis 允许你在映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的对象方法调用包括:
Executor:MyBatis 执行器,负责 SQL 语句的生成和查询缓存的维护,继承关系如下:
StatementHandler:封装了JDBC Statement 操作,继承关系如下:
ParameterHandler:负责SQL参数的组装
ResultSetHandler:负责SQL结果的组装
2. 实现
(1)定义拦截器
StatementHandler拦截的实现:
@Intercepts({@Signature(type = Executor.class, method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class})})
@Slf4j
public class MybatisInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable{
log.info("拦截器内部处理");
return invocation.proceed();
}
@Override
public Object plugin(Object target){
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties){
}
}
Executor拦截的实现:
import com.example.demo.mybatis.domain.BasePO;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.util.Objects;
import java.util.Properties;
@Intercepts({@Signature(type = Executor.class, method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class})
, @Signature(type = Executor.class, method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
, @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
})
@Slf4j
@Component
//@Order(10)
public class MybatisInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable{
// 执行器
Executor executor = (Executor)invocation.getTarget();
// 方法参数
Object[] args = invocation.getArgs();
MappedStatement ms = (MappedStatement)args[0];
Object parameter = args[1];
switch (ms.getSqlCommandType()){
case SELECT:
RowBounds rowBounds = (RowBounds)args[2];
ResultHandler resultHandler = (ResultHandler)args[3];
CacheKey cacheKey;
BoundSql boundSql;
if (args.length == 4) {
boundSql = ms.getBoundSql(parameter);
cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql);
} else {
cacheKey = (CacheKey)args[4];
boundSql = (BoundSql)args[5];
}
String originSql = boundSql.getSql();
log.info("【MybatisInterceptor】原始SQL: {}", originSql);
String newSql = originSql + " order by id desc";
SqlSource newSqlSource = this.buildSqlSource(boundSql, newSql);
// MetaObject msObject = MetaObject.forObject(ms, new DefaultObjectFactory(), new DefaultObjectWrapperFactory(), new DefaultReflectorFactory());
// msObject.setValue("sqlSource", newSqlSource);
// log.info("【MybatisInterceptor】增强后SQL: {}", ms.getBoundSql(parameter).getSql());
MappedStatement newMs = this.buildMappedStatement(ms, newSqlSource);
log.info("【MybatisInterceptor】增强后SQL: {}", newMs.getBoundSql(parameter).getSql());
args[0] = newMs;
break;
case INSERT:
if(parameter instanceof BasePO){
ReflectUtil.setSuperValue("createTime", LocalDateTime.now(), parameter);
}
case UPDATE:
if(parameter instanceof BasePO){
ReflectUtil.setSuperValue("updateTime", LocalDateTime.now(), parameter);
}
}
return invocation.proceed();
}
/**
* 新建一个 MappedStatement
* 避免修改 Configuration中的 mappedStatements
* 导致从自定义的 SqlSource 中获取增强后的SQL
* getSqlBoundSql -> sqlSource.getBoundSql -> XML原始SQL
* @param ms
* @param sqlSource
* @return
*/
private MappedStatement buildMappedStatement(MappedStatement ms, SqlSource sqlSource) {
MappedStatement.Builder builder = new MappedStatement.Builder(ms.getConfiguration(), ms.getId(), sqlSource, ms.getSqlCommandType());
builder.resource(ms.getResource())
.parameterMap(ms.getParameterMap())
.resultMaps(ms.getResultMaps())
.fetchSize(ms.getFetchSize())
.timeout(ms.getTimeout())
.statementType(ms.getStatementType())
.resultSetType(ms.getResultSetType())
.cache(ms.getCache())
.flushCacheRequired(ms.isFlushCacheRequired())
.useCache(ms.isUseCache())
.resultOrdered(ms.isResultOrdered())
.keyGenerator(ms.getKeyGenerator())
.keyProperty(Objects.isNull(ms.getKeyProperties())?null:String.join(",", ms.getKeyProperties()));
return builder.build();
}
private BoundSqlSqlSource buildSqlSource(BoundSql boundSql, String sql){
// Mybatis 反射工具
MetaObject msObject = MetaObject.forObject(boundSql, new DefaultObjectFactory(), new DefaultObjectWrapperFactory(), new DefaultReflectorFactory());
msObject.setValue("sql", sql);
return new BoundSqlSqlSource(boundSql);
}
/**
* 自定义内部类,利用反射赋值boundSql
* boundSql 使用原MapperStatement不受影响
* getSqlBoundSql -> sqlSource.getBoundSql 会从 StaticSqlSource 中获取XML原始SQL
*/
static class BoundSqlSqlSource implements SqlSource {
private BoundSql boundSql;
BoundSqlSqlSource(BoundSql boundSql) {
this.boundSql = boundSql;
}
@Override
public BoundSql getBoundSql(Object parameterObject) {
return boundSql;
}
}
@Override
public Object plugin(Object target){
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties){
}
}
(2)配置拦截器
- Mybatis配置文件中增加插件配置
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<plugins>
<plugin interceptor="com.example.demo.mybatis.interceptor.MybatisInterceptor"/>
</plugins>
</configuration>
- Spring中添加Mybatis插件
@Configuration
@MapperScan({"com.example.demo.mybatis.mapper"})
public class MyBatisConfig {
@Resource
private DataSource dataSource;
@Resource
private MybatisInterceptor mybatisInterceptor;
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
sqlSessionFactoryBean.setMapperLocations(resolver.getResources("classpath:mapper/*Mapper.xml"));
sqlSessionFactoryBean.setPlugins(new Interceptor[] {mybatisInterceptor});
return sqlSessionFactoryBean.getObject();
}
二、原理
1. JDK动态代理
定义代理类实现InvocationHandler,持有被代理类的引用,重写invoke方法,获取代理类实例的方法:
Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
这里简单介绍jdk的动态代理,因为mybatis插件的实现原理基于此,只有在完全理解动态代理的基础上才能更好的理解mybatis插件的实现原理
2. 源码解析
(1)Interceptor
// 用于覆盖被拦截对象的原有方法(在调用代理对象 Plugin 的 invoke()方法时被调用)
Object intercept(Invocation invocation) throws Throwable;
// target 是被拦截对象,这个方法的作用是给被拦截对象生成一个代理对象,并返回
Object plugin(Object target);
// 设置参数
void setProperties(Properties properties);
(2)Plugin
// 基于动态代理实现
public class Plugin implements InvocationHandler{
// 生成代理类
public static Object wrap(Object target, Interceptor interceptor) {
......
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
// 重写invoke方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
......
return interceptor.intercept(new Invocation(target, method, args));
}
}
(3)Invocation
// 被代理对象
private final Object target;
private final Method method;
private final Object[] args;
// 反射执行被代理的方法
Public Object proceed() throws InvocationTargetException, IllegalAccessException {
return method.invoke(target, args);
}
3. 执行流程
(1)Mapper动态代理
public class MapperProxy<T> implements InvocationHandler, Serializable {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
......
return mapperMethod.execute(sqlSession, args);
}
}
这里是基于JDK动态代理实现调用mapper接口,最终实现由代理类执行XML中SQL.。承上启下,不做详细介绍,感兴趣的小伙伴可以另行查阅,或者留言有机会我来总结一下一起学习。如下执行流程Mapper -> MapperMethod -> SqlSession -> Executor,自此便到了我们拦截器可拦截器的对象
(2)四大代理对象
Open session时创建executor
Configuration
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
......
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
执行SQL时创建StatementHandler
SimpleExecutor
public <E> List<E> doQuery(......) throws SQLException {
......
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
}
Configuration
public StatementHandler newStatementHandler(......) {
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
statementHandler=(StatementHandler)interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
创建StatementHandler时创建ParameterHandler和ResultSetHandler
BaseStatementHandler
protected BaseStatementHandler(......) {
this.parameterHandler=configuration.newParameterHandler(mappedStatement,parameterObject,boundSql);
this.resultSetHandler=configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
}
Configuration
public ParameterHandler newParameterHandler(......) {
parameterHandler=(ParameterHandler)interceptorChain.pluginAll(parameterHandler);
return parameterHandler;
}
public ResultSetHandler newResultSetHandler(......) {
resultSetHandler=(ResultSetHandler)interceptorChain.pluginAll(resultSetHandler);
return resultSetHandler;
}
通过以上源码,解释了为什么拦截器会对该四大对象生效
(3)Executor拦截器
Executor拦截器的执行流程:
- 执行query时,由于动态代理,执行Plugin的invoke方法
- 继续执行Plugin invoke方法中的interceptor.intercept,进入自定义的拦截器,执行invocation.proceed()
- 执行invocation.proceed(),通过代理执行被代理类query方法
4. 设计模式总结
Executor:模板模式、装饰者模式
InterceptorChain:责任链模式
StatementHandler:委派模式、策略模式
Plugin:代理模式