Mybatis源码阅读(四):核心接口4.2——Executor(上)
*************************************优雅的分割线 **********************************
分享一波:程序员赚外快-必看的巅峰干货
如果以上内容对你觉得有用,并想获取更多的赚钱方式和免费的技术教程
请关注微信公众号:HB荷包
一个能让你学习技术和赚钱方法的公众号,持续更新
*************************************优雅的分割线 **********************************
Executor
Executor是Mybatis的核心接口之一,其中定义了数据库操作的基本方法。在实际应用中涉及的SqlSession的操作都是基于Executor实现的。Executor代码如下。
/**
-
Mybatis的核心接口,定义了操作数据库的方法
-
SqlSession接口的功能都是基于Executor实现的
-
@author Clinton Begin
*/
public interface Executor {ResultHandler NO_RESULT_HANDLER = null;
/**
- 执行update、insert、delete语句
- @param ms
- @param parameter
- @return
- @throws SQLException
*/
int update(MappedStatement ms, Object parameter) throws SQLException;
/**
- 执行select
- @param ms
- @param parameter
- @param rowBounds
- @param resultHandler
- @param cacheKey
- @param boundSql
- @param
- @return
- @throws SQLException
*/
List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;
/**
- 执行select
- @param ms
- @param parameter
- @param rowBounds
- @param resultHandler
- @param
- @return
- @throws SQLException
*/
List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;
/**
- 执行select,返回游标
- @param ms
- @param parameter
- @param rowBounds
- @param
- @return
- @throws SQLException
*/
Cursor queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException;
/**
- 批量执行SQL语句
- @return
- @throws SQLException
*/
List flushStatements() throws SQLException;
/**
- 提交事务
- @param required
- @throws SQLException
*/
void commit(boolean required) throws SQLException;
/**
- 回滚事务
- @param required
- @throws SQLException
*/
void rollback(boolean required) throws SQLException;
/**
- 创建缓存中的CacheKey对象
- @param ms
- @param parameterObject
- @param rowBounds
- @param boundSql
- @return
*/
CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql);
/**
- 根据CacheKey查找缓存是否出在
- @param ms
- @param key
- @return
*/
boolean isCached(MappedStatement ms, CacheKey key);
/**
- 清除一级缓存
*/
void clearLocalCache();
/**
- 延迟加载一级缓存中的数据
- @param ms
- @param resultObject
- @param property
- @param key
- @param targetType
*/
void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType);
/**
- 获取事务对象
- @return
*/
Transaction getTransaction();
/**
- 关闭Executor对象
- @param forceRollback
*/
void close(boolean forceRollback);
/**
- 检测Executor是否关闭
- @return
*/
boolean isClosed();
/**
- 设置包装的Executor
- @param executor
*/
void setExecutorWrapper(Executor executor);
}
[点击并拖拽以移动]
Executor接口的实现中使用到了装饰器模式和模板方法模式,关于设计模式的内容可以查看我之前的文章,这里就不贴出文章链接了。Executor的实现如图所示。
BaseExecutor
BaseExecutor是个抽象类,实现了Executor大部分的方法。BaseExecutor中主要提供了缓存管理和事务管理的基本功能,继承BaseExecutor的子类只需要实现四个基本的方法来完成数据库的相关操作即可,分别是doUpdate、doQuery、doQueryCursor、doFlushStatement。其余的方法在BaseExecutor中都有了实现。BaseExecutor的字段如下
/**
* 事务对象
*/
protected Transaction transaction;
/**
* 封装的Executor对象
*/
protected Executor wrapper;
/**
* 延迟加载队列
*/
protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;
/**
* 一级缓存,用于缓存该Executor对象查询结果集映射得到的结果对象
*/
protected PerpetualCache localCache;
/**
* 一级缓存,用来缓存输出类型的参数
*/
protected PerpetualCache localOutputParameterCache;
protected Configuration configuration;
/**
* 记录嵌套查询的层数
*/
protected int queryStack;
/**
* 标识Executor是否关闭
*/
private boolean closed;
一级缓存
常见的系统中,数据库资源是比较珍贵的,在web系统中的性能瓶颈主要也就是数据库。在设计系统时,会使用多种优化手段去减少数据库的直接访问,比如使用缓存。使用缓存可以减少系统与数据库的网络交互、减少数据库访问次数、降低数据库负担、降低重复创建和销毁对象等一系列的开销,从而提升系统的性能。同时,当数据库意外宕机时,缓存中保存的数据可以继续支持系统部分功能的正常展示,提高系统的可用性。Mybatis提供了一级缓存和二级缓存,我们这里先讨论一级缓存。
一级缓存是会话级别的缓存,在Mybatis中每创建一个SqlSession对象,就表示开启一次数据库会话。在一次会话中,系统可能回反复的执行相同的查询语句,如果不对数据库进行缓存,那么短时间内执行多次完全相同的SQL语句,查询到的结果集也可能完全相同,就造成了数据库资源的浪费。
为了避免这种问题,Executor对象中会建立一个简单的缓存,也就是一级缓存。它会将每次查询结果缓存起来,再执行查询操作时,会先查询一级缓存,如果存在完全一样的查询语句,则直接从一级缓存中取出相应的结果对象返回给用户,从而减少数据库压力。
一级缓存的生命周期与SqlSession相同,也就与SqlSession封装的Executor对象的生命周期相同,当调用了Executor的close方法时,该Executor中的一级缓存将会不可用。同时,一级缓存中对象的存活时间也会受其他因素影响,比如在执行update方法时,也会先清空一级缓存。
query
BaseExecutor方法会首先创建CacheKey对象,并根据CacheKey对象查找一级缓存,如果缓存命中则直接返回缓存中记录的结果对象。如果没有命中则查询数据库得到结果集,之后将结果集映射成对象保存到一级缓存中,同时返回结果对象。query方法如下所示。
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
// 获取BoundSql对象
BoundSql boundSql = ms.getBoundSql(parameter);
// 创建CacheKey对象
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
在query方法中会先获取到boundSql对象,并且去创建CacheKey对象,再调用query的一个重载方法。
这里的CacheKey由MappedStatement的id、对应的offset和limit、包含问号的sql语句、用户传递的实参、Environment的id五部分构成,代码如下。
/**
* 创建CacheKey对象
* CacheKey由Sql节点的id、offset、limit、sql、实参、环境组成
*
* @param ms
* @param parameterObject
* @param rowBounds
* @param boundSql
* @return
*/
@Override
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
if (closed) {
throw new ExecutorException("Executor was closed.");
}
CacheKey cacheKey = new CacheKey();
// 将sql节点的id添加到CacheKey
cacheKey.update(ms.getId());
// 将offset添加到CacheKey
cacheKey.update(rowBounds.getOffset());
// 将limit添加到CacheKey
cacheKey.update(rowBounds.getLimit());
// 将SQL添加到CacheKey(包含?的sql)
cacheKey.update(boundSql.getSql());
// 获取参数映射
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
// 获取类型处理器
TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
// 遍历参数映射
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
cacheKey.update(value);
}
}
// 环境不为空
if (configuration.getEnvironment() != null) {
// 将当前环境添加到CacheKey
cacheKey.update(configuration.getEnvironment().getId());
}
return cacheKey;
}
而query的重载方法会根据创建的CacheKey对象查询一级缓存。如果缓存命中则将缓存中记录的结果对象返回,如果未命中,则调用doQuery方法查询数据库,并存到一级缓存。代码如下。
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.");
}
// 非嵌套查询并且当前select节点配置了flushCache
if (queryStack == 0 && ms.isFlushCacheRequired()) {
// 先清空缓存
clearLocalCache();
}
List<E> list;
try {
// 查询层数+1
queryStack++;
// 先查询 一级缓存
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
// 针对存储过程调用的处理。在一级缓存 命中时,获取缓存中保存的输出类型参数,设置到用户传入的实参中
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
// 数据库查询,并得到映射后的结果对象
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
// 当前查询完成,查询层数减少
queryStack--;
}
// 延迟加载相关
if (queryStack == 0) {
// 触发DeferredLoad加载一级缓存中记录的嵌套查询的结果对象
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// 加载完成后清除deferredLoads
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// 根据localCacheScope配置决定是否清空一级缓存
clearLocalCache();
}
}
return list;
}
BaseExecutor中缓存除了缓存结果集以外,在分析嵌套查询时,如果一级缓存中缓存了嵌套查询的结果对象,则可以从一级缓存中直接加载该结果对象。如果一级缓存中记录的嵌套查询的结果对象并未完全加载,则可以通过DeferredLoad实现类实现延迟加载的功能。与这个流程相关的方法有两个,isCached方法负责检测是否缓存了指定查询的结果对象,deferLoad方法负责创建DeferredLoad对象并添加到deferredLoad集合中。代码如下。
/**
* 检测是否缓存了指定查询的结果对象
*
* @param ms
* @param key
* @return
*/
@Override
public boolean isCached(MappedStatement ms, CacheKey key) {
// 检测缓存中是否花奴才能了CacheKey对象
return localCache.getObject(key) != null;
}
/**
* 负责创建DeferredLoad对象并将其添加到deferredLoads集合中
*
* @param ms
* @param resultObject
* @param property
* @param key
* @param targetType
*/
@Override
public void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType) {
if (closed) {
throw new ExecutorException("Executor was closed.");
}
DeferredLoad deferredLoad = new DeferredLoad(resultObject, property, key, localCache, configuration, targetType);
if (deferredLoad.canLoad()) {
// 一级缓存中已经记录了指定查询结果的对象,直接从缓存中加载对象,并设置到外层对象
deferredLoad.load();
} else {
// 将deferredLoad对象添加到deferredLoads队列中,待整个外层查询结束后再加载结果对象
deferredLoads.add(new DeferredLoad(resultObject, property, key, localCache, configuration, targetType));
}
}
DeferredLoad是定义在BaseExecutor中的内部类,它负责从loadCache缓存中延迟加载结果对象,含义如下。
/**
* 外层对象对应的MetaObject
*/
private final MetaObject resultObject;
/**
* 延迟加载的属性名称
*/
private final String property;
/**
* 延迟加载的属性类型
*/
private final Class<?> targetType;
/**
* 延迟加载的结果对象在一级缓存中的CacheKey
*/
private final CacheKey key;
/**
* 一级缓存
*/
private final PerpetualCache localCache;
private final ObjectFactory objectFactory;
/**
* 负责结果对象的类型转换
*/
private final ResultExtractor resultExtractor;
DeferredLoad的canLoad方法负责检测缓存项是否已经完全加载到缓存中。BaseExecutor的queryFromDatabase方法中,开始调用doQuery查询数据库之前,会先在localCache中放一个占位符,待查询完毕后会将key替换成真实的数据,此时缓存就完全加载了。queryFromDatabase方法的实现如下。
/**
* 从数据库中查询
*
* @param ms
* @param parameter
* @param rowBounds
* @param resultHandler
* @param key
* @param boundSql
* @param <E>
* @return
* @throws SQLException
*/
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
// 先添加一个占位符,查询完毕后才将真正的结果对象放入缓存,此时算完全家在
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
// 删除占位符
localCache.removeObject(key);
}
// 将真正的结果对象添加到一级缓存中
localCache.putObject(key, list);
// 如果是存储过程
if (ms.getStatementType() == StatementType.CALLABLE) {
// 缓存输出类型的参数
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
canLoad和load方法实现如下。
/**
* 判断是否是完全加载
*
* @return
*/
public boolean canLoad() {
return localCache.getObject(key) != null && localCache.getObject(key) != EXECUTION_PLACEHOLDER;
}
/**
* 负责从缓存中加载结果对象,设置到外层对象 的属性中
*/
@SuppressWarnings("unchecked")
public void load() {
// 从缓存中查询指定的结果对象
List<Object> list = (List<Object>) localCache.getObject(key);
// 将缓存的结果对象转换成指定的类型
Object value = resultExtractor.extractObjectFromList(list, targetType);
// 设置到外层对象的对应属性
resultObject.setValue(property, value);
}
clearLocalCache方法用于清空缓存。query方法会根据flushCache属性和localCacheScope配置决定是否清空一级缓存。update方法在执行insert、update、delete三类SQL语句之前,会清空缓存。代码比较简单这里就不贴了。
事务操作
在BatchExecutor中可以缓存多条SQL,等待合适的时机将缓存的多条SQL一起发送给数据库执行。Executor.flushStatements方法主要是针对批处理多条SQL语句的,会调用doFlushStatements方法处理Executor中缓存的多条SQL语句,在BaseExecutor的commit、rollback方法中会首先调用flushStatement方法,再执行相关事务操作,方法具体的实现如下。
public List<BatchResult> flushStatements(boolean isRollBack) throws SQLException {
if (closed) {
throw new ExecutorException("Executor was closed.");
}
return doFlushStatements(isRollBack);
}
BaseExecutor.commit方法首先会清空一级缓存,调用flushStatements,最后才根据参数决定是否真正提交事务。代码如下,
/**
* 提交事务
* @param required
* @throws SQLException
*/
@Override
public void commit(boolean required) throws SQLException {
if (closed) {
throw new ExecutorException("Cannot commit, transaction is already closed");
}
// 清除缓存
clearLocalCache();
// 处理缓存的SQL
flushStatements();
if (required) {
// 提交事务
transaction.commit();
}
}
*************************************优雅的分割线 **********************************
分享一波:程序员赚外快-必看的巅峰干货
如果以上内容对你觉得有用,并想获取更多的赚钱方式和免费的技术教程
请关注微信公众号:HB荷包
一个能让你学习技术和赚钱方法的公众号,持续更新
*************************************优雅的分割线 **********************************