在上一篇文章中(原生Mybatis源码简析(上)),我们介绍了原生Mybatis的初始化,以及Mapper接口的运行原理。在介绍到Executor
类具体执行SQL时,就没有继续下去了,现在我们继续将剩下的流程梳理一下。
1、概述
在具体介绍代码流程之前,我们得先介绍下如下四个比较重要的类,他在接下来的代码分析中占有重要的地位。
- Executor:Executor创建StatementHandler对象;
- StatementHandler: 设置sql语句,预编译,设置参数等相关工作,以及执行增删改查方法;
- ParameterHandler: 设置预编译参数;
- ResultHandler: 处理查询结果集;
其中在设置参数和处理查询结果时,都是依赖TypeHandler,进行数据库类型和javaBean类型的映射
2、流程介绍
从上篇文章中,我们知道,在获取SqlSession的同时,其实已经生成了Executor对象了,并且是作为属性设置给了SqlSession对象。
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();
}
}
我们看下Executor对象的实例化过程
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);
}
// 插件相关的代码,先记住这里,后面分析Mybatis插件机制时,再重点分析
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
这里executorType默认是simple,而Mybatis的二级缓存机制一般很少启用(一般都是自己在业务层通过redis等实现缓存机制),所以这里默认是返回SimpleExecutor类的实例。
现在再回到上篇文章最后的代码部分,DefaultSqlSession的selectList方法中,调用了executor的query方法。该方法会调用到BaseExecutor的query方法,不考虑一级缓存,最后会来到SimpleExecutor的doQuery方法
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
在该方法中,具体SQL语句的执行会有SimpleExecutor对象委托给StatementHandler对象处理。我们先看下该对象的实例化过程
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;
}
RoutingStatementHandler对象会也会根据statementType属性实例化对应的StatementHandler对象
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
switch (ms.getStatementType()) {
case STATEMENT:
delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case PREPARED:
delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case CALLABLE:
delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
default:
throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
}
}
MappedStatement的StatementType属性默认是PREPARED(可以在MappedStatement实例化方法中看到),这里自然是实例化PreparedStatementHandler对象
protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
this.configuration = mappedStatement.getConfiguration();
this.executor = executor;
this.mappedStatement = mappedStatement;
this.rowBounds = rowBounds;
this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
this.objectFactory = configuration.getObjectFactory();
if (boundSql == null) { // issue #435, get the key before calculating the statement
generateKeys(parameterObject);
boundSql = mappedStatement.getBoundSql(parameterObject);
}
this.boundSql = boundSql;
this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
}
这里可以看出,在初始化StatementHandler的时候,就会同时实例化parameterHandler和resultSetHandler,并设置为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;
}
我们再回到SimpleExecutor的doQuery方法中,先调用了prepareStatement方法,完成SQL参数化配置
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);
return stmt;
}
然后调用了StatementHandler的query方法
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler.handleResultSets(ps);
}
之后就是jdbc的套路了,就不再继续分析了。下面再看下Mybatis的插件机制
3、插件机制
在上一篇文章中,我们知道在初始化Configuration对象时,会初始化InterceptorChain对象,该对象中的interceptors属性中保存了所有的插件对象。在上面的代码介绍了,我们一共发现了四处,使用interceptorChain属性的pluginAll方法,这也就是为什么Mybatis支持Executor、StatementHandler、ParamaterHandler、ResultsetHandler四个对象的插件拦截机制的原因了。我们看下plugAll方法
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
很简单,就是循环遍历执行Interceptor的plugin方法,而我们在实现一个插件时,通过继承Interceptor接口,实现plugin方法都是如下实现方式。
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
该Plugin类时Mybatis提供的一个工具类
public static Object wrap(Object target, Interceptor interceptor) {
Map<Class<?>, Set<Method>> 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;
}
private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
// issue #251
if (interceptsAnnotation == null) {
throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());
}
Signature[] sigs = interceptsAnnotation.value();
Map<Class<?>, Set<Method>> signatureMap = new HashMap<>();
for (Signature sig : sigs) {
Set<Method> methods = signatureMap.computeIfAbsent(sig.type(), k -> new HashSet<>());
try {
Method method = sig.type().getMethod(sig.method(), sig.args());
methods.add(method);
} catch (NoSuchMethodException e) {
throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e);
}
}
return signatureMap;
}
private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
Set<Class<?>> interfaces = new HashSet<>();
while (type != null) {
for (Class<?> c : type.getInterfaces()) {
if (signatureMap.containsKey(c)) {
interfaces.add(c);
}
}
type = type.getSuperclass();
}
return interfaces.toArray(new Class<?>[interfaces.size()]);
}
可见,Mybatis的插件机制是通过动态代理实现的,且多个插件会层层代理,代理的顺序就是在xml中配置插件的顺序。