- Executor是一个大管家,核心功能包括:缓存维护、获取动态SQL、获取连接、以及最终的JDBC调用等。
Executor接口及其层级如下:
BaseExecutor是基础执行器,包括一级缓存逻辑也在此实现,BaseExecutor是Excutor接口的抽象实现类,主要提供了缓存管理和事务管理的基本功能,其实现了接口Executor的部分方法,SimpleExecutor继承了BaseExecutor抽象类,是Mybatis提供的最简单的Executor接口实现;ReuseExecutor也继承了BaseExecutor抽象类,提供了对Statement重用的功能;BatchExecutor同样继承了BaseExecutor抽象类,实现了批处理多条 SQL 语句的功能;ClosedExecutor也继承了BaseExecutor抽象类,且是ResultLoaderMap类中的一个内部类,用于实现懒加载相关逻辑。CachingExecutor类直接实现了Excutor接口,是装饰器类,主要增强缓存相关功能
我们今天先阅读BaseExecutor的源码
BaseExecutor抽象类的源码及详解注释如下,源码较多,但注释也多,有需要理解的请耐心看或跳着看所需要的那一部分,此源码注释只为了记录个人学习笔记,方便自查
/**
* @author Clinton Begin
*/
public abstract class BaseExecutor implements Executor {
//日志
private static final Log log = LogFactory.getLog(BaseExecutor.class);
//Transaction对象,实现事务的提交、回滚和关闭操作
//protected对于类中来说,就是public的,可以自由使用,没有任何限制,而对于其他的外部class,protected就变成private
protected Transaction transaction;
//封装了真正的 Executor对象
protected Executor wrapper;
//定义线程安全队列,此类继承和实现如下
/*public class ConcurrentLinkedQueue<E> extends AbstractQueue<E>
implements Queue<E>, java.io.Serializable {
延迟加载队列。一个基于链接节点的无界线程安全队列。此队列按照 FIFO(先进先出)原则对元素进行排序。
当多个线程共享访问一个公共 collection 时,ConcurrentLinkedQueue 是一个恰当的选择。此队列不允许使用 null元素。该变量主要是存储一些可以延时加载的变量对象,且可供多线程使用*/
protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;
//一级缓存,用于缓存该 Executor对象查询结果集映射得到的结果对象,此类中成员如下
/*public class PerpetualCache implements Cache {
private final String id;
private Map<Object, Object> cache = new HashMap<>();
*/
protected PerpetualCache localCache;
//一级缓存,用于缓存输出类型的参数
protected PerpetualCache localOutputParameterCache;
//mybatis的配置信息,全局唯一配置对象
protected Configuration configuration;
//查询的深度,用来记录嵌套查询的层数,分析 DefaultResultSetHandler时介绍过的嵌套查询
protected int queryStack = 0;
//标识该执行器是否已经关闭
private boolean closed;
//BaseExecutor类构造函数,初始化变量,主要实现了对上述的属性字段进行初始化工作。其中,
//locaCache、localOutputParameterCache都使用了PerpetualCache类型的缓存。
protected BaseExecutor(Configuration configuration, Transaction transaction) {
//事务对象
this.transaction = transaction;
this.deferredLoads = new ConcurrentLinkedQueue<>();
this.localCache = new PerpetualCache("LocalCache");
this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");
//表明exector的状态
this.closed = false;
//主文件属性,主要获取MappedStatement对象
this.configuration = configuration;
this.wrapper = this;
}
//获取执行器中的事务对象
@Override
public Transaction getTransaction() {
if (closed) {
throw new ExecutorException("Executor was closed.");
}
return transaction;
}
/**
* 关闭当前执行器,如果需要回滚,就执行回滚操作
* 需要把相应的资源(比如事务transaction等),同步关闭
* 把其他变量设置成NULL
*/
@Override
public void close(boolean forceRollback) {
try {
try {
rollback(forceRollback);
} finally {
if (transaction != null) {
transaction.close();
}
}
} catch (SQLException e) {
// Ignore. There's nothing that can be done at this point.
log.warn("Unexpected exception on closing transaction. Cause: " + e);
} finally {
transaction = null;
deferredLoads = null;
localCache = null;
localOutputParameterCache = null;
closed = true;
}
}
//获取当前执行器是否关闭的标识
@Override
public boolean isClosed() {
return closed;
}
/*update/insert/delete请求到达SqlSession都会调用此方法
从语义的角度,insert、update、delete都是属于对数据库的行进行更新操作
从实现的角度,我们熟悉的PreparedStatement里面提供了两种execute方法,一种是executeUpdate(),
一种是executeQuery(),前者对应的是insert、update与delete,后者对应的是select,因此对于MyBatis来说只有update与select
该方法处理了通用的情况,真正实现由子类通过实现doUpdate方法完成。该方法实现了:
判断该执行器是否已经关闭,如果关闭,就直接抛出异常
因为该操作是更新操作,所以该操作会让其中的缓存就全部失效,即清理缓存
调用抽象方法doUpdate实现数据更新,由子类完成。
*/
@Override
public int update(MappedStatement ms, Object parameter) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
//判断该执行器是否已经关闭,如果关闭,就直接抛出异常
if (closed) {
throw new ExecutorException("Executor was closed.");
}
//由于默认情况下mybatis开启一级缓存,所以如果你需要每次查询都从数据库查询
//清空一级缓存,基于SqlSession范围作用,再更新,如何更新由子类实现,这里利用模板方法模式
clearLocalCache();
//调用抽象方法doUpdate实现数据更新,由子类完成
return doUpdate(ms, parameter);
}
//刷新Statement。真正的实现由具体的子类完成,且在不同的实现类中的逻辑不一样
@Override
public List<BatchResult> flushStatements() throws SQLException {
return flushStatements(false);
}
//调用批处理执行器中方法,刷新Statement,记录执行次数
public List<BatchResult> flushStatements(boolean isRollBack) throws SQLException {
if (closed) {
throw new ExecutorException("Executor was closed.");
}
//调用抽象方法doFlushStatements,真正实现由子类完成
return doFlushStatements(isRollBack);
}
/*SqlSession.selectList会调用此方法
在该方法中,首先判断当前执行器是否已经关闭;然后再根据queryStack查询层数和flushCache属性判断,
是否需要清除一级缓存;然后再判断结果处理器resultHandler是否为空,为空的话,尝试从缓存中查询数据,
否则直接为空,后续从数据库查询数据;然后再根据缓存中是否有数据,如果存在数据,
且是存储过程或函数类型则执行handleLocallyCachedOutputParameters()方法,如果存在数据,
不是存储过程或函数类型就直接返回,如果缓存中没有数据就直接通过queryFromDatabase()方法从数据库查询数据,
其中又通过doQuery()方法实现查询逻辑;后续在通过DeferredLoad类实现嵌套查询的延时加载功能*/
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
//得到绑定sql,并将参数对象与sql语句的#{}一一对应
BoundSql boundSql = ms.getBoundSql(parameter);
//创建获取cacheKey供缓存,包含完整的语句、参数等,确保CacheKey的唯一性
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
//比原先多传入CacheKey和BoundSql参数,执行查询然后返回,具体处理逻辑见下一个方法
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
//告诉编译器忽略指定的警告,不用在编译完成后出现警告信息
@SuppressWarnings("unchecked")
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
//如果已经关闭就抛出异常
if (closed) {
throw new ExecutorException("Executor was closed.");
}
//判断是否清除本地缓存,但仅仅查询堆栈为0才清,为了处理递归调用
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
//本地缓存记录自增,这样递归调用到上面的时候就不会再清局部缓存了
queryStack++;
//如果查询的语句已存在本地缓存中,则直接从本地获取,反之从数据库中读取内容
list = resultHandler == null ? (List) localCache.getObject(key) : null;
if (list != null) {
/*针对存储过程调用的处理 其功能是 在一级缓存命中时,获取缓存中保存的输出类型参数,
并设到用户传入的实参( parameter )对象中。
*/
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
//从数据库中获取并进行缓存处理,其也会调用子类需复写的doQuery()方法
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
//清空堆栈操作
queryStack--;
}
if (queryStack == 0) {
//延迟加载队列中所有元素
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
//清空延迟加载队列
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
//如果是statement,清本地缓存
clearLocalCache();
}
}
return list;
}
//查询,返回Cursor类型。该方法主要完成了根据参数生成BoundSql实例的工作,真正的查询工作还是由子类实现doQueryCursor方法来完成。
@Override
public <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameter);
return doQueryCursor(ms, parameter, rowBounds, boundSql);
}
//延迟加载一级缓存中的数据
@Override
public void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType) {
if (closed) {
throw new ExecutorException("Executor was closed.");
}
//创建DeferredLoad对象
DeferredLoad deferredLoad = new DeferredLoad(resultObject, property, key, localCache, configuration, targetType);
//如果能加载则立即加载,否则加入到延迟加载队列中
if (deferredLoad.canLoad()) {
deferredLoad.load();
} else {
deferredLoads.add(new DeferredLoad(resultObject, property, key, localCache, configuration, targetType));
}
}
/*
createCacheKey()方法
创建缓存中使用的key,即CacheKey实例对象。创建的CacheKey实例对象,由下列参数决定其唯一性,
即相同的查询,相关的参数,生成的CacheKey实例唯一且不变。依据的参数如下:
1、MappedStatement实例的ID
2、RowBounds实例的offset和limit属性
3、BoundSql实例的sql属性
4、查询条件的参数值
5、连接数据库的环境ID
MappedStatement(一般为xml中定义)、parameterObject(传递给xml的参数)、
rowBounds(分页参数)、boundSql(最终sql,由MappedStatement和parameterObject决定)
*/
//还有如果一个查询的 id、分页组件中的 offset 和 limit、sql 语句、参数 都保持不变,
//那么这个查询产生的 CacheKey一定是不变的。在一个 SqlSession 的生命周期内,二次同样的查询 CacheKey 是一样的
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
if (closed) throw new ExecutorException("Executor was closed.");
CacheKey cacheKey = new CacheKey();
/*MyBatis对于其Key的生成采取规则为:
//[mappedStementId + offset + limit + SQL + queryParams + environment]生成一个哈希码
createCacheKey方法中调用CacheKey的update方法时传入的元素,就是Mybatis用来确定缓存唯一性的元素
*/
//方法名
cacheKey.update(ms.getId());
//逻辑分页偏移量
cacheKey.update(rowBounds.getOffset());
//逻辑分页起始值
cacheKey.update(rowBounds.getLimit());
//BoundSql实例的sql属性
cacheKey.update(boundSql.getSql());
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
// mimic DefaultParameterHandler logic
for (ParameterMapping parameterMapping : parameterMappings) {
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) {
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
cacheKey.update(value);
}
}
if (configuration.getEnvironment() != null) {
// issue #176
cacheKey.update(configuration.getEnvironment().getId());
}
return cacheKey;
}
//判断该查询结果是否被缓存。
@Override
public boolean isCached(MappedStatement ms, CacheKey key) {
return localCache.getObject(key) != null;
}
/**
* 事务提交,真正提交操作是通过事务实例transaction来完成
* 该方法实现了:
* 1、判断执行器是否被关闭,如果关闭就直接抛出异常
* 2、清理缓存和执行预操作
* 3、根据参数判断是否执行提交操作
*/
@Override
public void commit(boolean required) throws SQLException {
//判断执行器是否被关闭,如果关闭就直接抛出异常
if (closed) {
throw new ExecutorException("Cannot commit, transaction is already closed");
}
clearLocalCache();
flushStatements();
//根据参数判断是否执行提交操作
if (required) {
transaction.commit();
}
}
//回滚操作
@Override
public void rollback(boolean required) throws SQLException {
//如果该执行器没有关闭才执行回滚操作,否则就忽略,不做任何操作
if (!closed) {
try {
//清除缓存内容
clearLocalCache();
//刷新,预处理
flushStatements(true);
} finally {
//需要回滚,就执行事务的回滚操作
if (required) {
transaction.rollback();
}
}
}
}
//清空本地缓存,一个map结构
@Override
public void clearLocalCache() {
if (!closed) {
localCache.clear();
localOutputParameterCache.clear();
}
}
//调用抽象方法doFlushStatements,真正实现由子类完成
protected abstract List<BatchResult> doFlushStatements(boolean isRollback)
throws SQLException;
protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
throws SQLException;
//根据参数生成BoundSql实例的工作,真正的查询工作还是由子类实现doQueryCursor方法来完成
protected abstract <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql)
throws SQLException;
//关闭java的Statement实例
protected void closeStatement(Statement statement) {
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
// ignore
}
}
}
* Apply a transaction timeout.
* @param statement a current statement
* @throws SQLException if a database access error occurs, this method is called on a closed <code>Statement</code>
* @since 3.4.0
* @see StatementUtil#applyTransactionTimeout(Statement, Integer, Integer)
*/
protected void applyTransactionTimeout(Statement statement) throws SQLException {
//第一优先级是事务中设置的超时时间
StatementUtil.applyTransactionTimeout(statement, statement.getQueryTimeout(), transaction.getTimeout());
}
//针对存储过程调用的处理 其功能是 在一级缓存命中时,获取缓存中保存的输出类型参数,并设豆到用户传入的实参( parameter )对象中。
* @param ms
private void handleLocallyCachedOutputParameters(MappedStatement ms, CacheKey key, Object parameter, BoundSql boundSql) {
CALLABLE类型,即存储过程、函数类型,否则不做任何处理
if (ms.getStatementType() == StatementType.CALLABLE) {
/*从localOutputParameterCache取出缓存对象,localOutputParameterCache就是一个PerpetualCache
// PerpetualCache维护了个map,就是session的缓存本质了,类成员如下:
public class PerpetualCache implements Cache {
private final String id;
private Map<Object, Object> cache = new HashMap<>();
*/
final Object cachedParameter = localOutputParameterCache.getObject(key);
if (cachedParameter != null && parameter != null) {
//元数据对象(MetaObject)实际上就是提供 类|集合|Map 的一种自动识别的访问形式
final MetaObject metaCachedParameter = configuration.newMetaObject(cachedParameter);
final MetaObject metaParameter = configuration.newMetaObject(parameter);
//通过boundSql去获取parameterMappings这个集合,循环使用parameterMapping 接收
for (ParameterMapping parameterMapping : boundSql.getParameterMappings()) {
/*
当OUT, INOUT 才继续处理
public enum ParameterMode {
IN, OUT, INOUT
}
*/
if (parameterMapping.getMode() != ParameterMode.IN) {
//获取参数名称,通过参数名称获取参数值,再存入
final String parameterName = parameterMapping.getProperty();
final Object cachedValue = metaCachedParameter.getValue(parameterName);
metaParameter.setValue(parameterName, cachedValue);
}
}
}
}
}
//从数据库中查出数据
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
//向缓存中放入占位符
/*
public enum ExecutionPlaceholder {
EXECUTION_PLACEHOLDER
}
*/
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
//通过调用需要子类实现的抽象方法doQuery()实现真正的查询操作逻辑
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
//执行完查询后清除占位符
localCache.removeObject(key);
}
//把查询数据加入缓存
localCache.putObject(key, list);
//如果是存储过程,OUT参数也加入缓存
/*public enum StatementType {
STATEMENT, PREPARED, CALLABLE
声明 准备好的 可调用的
}
*/
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
//mybatis在获取连接的时候,会根据日志的打印级别来判断是否会创建一个代理类。到这里就基本可以猜到,
//在代理类中,mybatis会去打印这个sql的语句
protected Connection getConnection(Log statementLog) throws SQLException {
//获取连接
Connection connection = transaction.getConnection();
//如果是Debug级别,创建一个代理类
if (statementLog.isDebugEnabled()) {
return ConnectionLogger.newInstance(connection, statementLog, queryStack);
} else {
return connection;
}
}
@Override
public void setExecutorWrapper(Executor wrapper) {
this.wrapper = wrapper;
}
/*内部类DeferredLoad
负责从一级缓存中延迟加载结果对象,并赋给外层对象
*/
private static class DeferredLoad {
//结果对象(外层对象)对应的MetaObject对象
private final MetaObject resultObject;
//延迟加载的属性名称
private final String property;
//延迟加载的属性类型
private final Class<?> targetType;
//结果对象在缓存中的key
private final CacheKey key;
//一级缓存
private final PerpetualCache localCache;
//默认对象工厂
private final ObjectFactory objectFactory;
//将结果从集合类型转为对象类型的辅助类
private final ResultExtractor resultExtractor;
// issue #781 构造函数
public DeferredLoad(MetaObject resultObject,
String property,
CacheKey key,
PerpetualCache localCache,
Configuration configuration,
Class<?> targetType) {
this.resultObject = resultObject;
this.property = property;
this.key = key;
this.localCache = localCache;
this.objectFactory = configuration.getObjectFactory();
this.resultExtractor = new ResultExtractor(configuration, objectFactory);
this.targetType = targetType;
}
//用于检测缓存对象是否已经加载到缓存中,在缓存中找到,不空且不为占位符,代表可以加载
public boolean canLoad() {
return localCache.getObject(key) != null && localCache.getObject(key) != EXECUTION_PLACEHOLDER;
}
//负责从缓存中获取对象,并将其设置到外层对象中
public void load() {
@SuppressWarnings("unchecked")
// we suppose we get back a List
List<Object> list = (List<Object>) localCache.getObject(key);
Object value = resultExtractor.extractObjectFromList(list, targetType);
resultObject.setValue(property, value);
}
}
}