*************************************优雅的分割线 **********************************
分享一波:程序员赚外快-必看的巅峰干货
如果以上内容对你觉得有用,并想获取更多的赚钱方式和免费的技术教程
请关注微信公众号: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添加到CacheKeycacheKey.update(ms.getId());// 将offset添加到CacheKeycacheKey.update(rowBounds.getOffset());// 将limit添加到CacheKeycacheKey.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);}// 将实参参数值添加到CacheKeycacheKey.update(value);}}// 环境不为空if (configuration.getEnvironment() != null) {// 将当前环境添加到CacheKeycacheKey.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节点配置了flushCacheif (queryStack == 0 && ms.isFlushCacheRequired()) {// 先清空缓存clearLocalCache();}List<E> list;try {// 查询层数+1queryStack++;// 先查询 一级缓存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();}// 加载完成后清除deferredLoadsdeferredLoads.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();// 处理缓存的SQLflushStatements();if (required) {// 提交事务transaction.commit();}
}
*************************************优雅的分割线 **********************************
分享一波:程序员赚外快-必看的巅峰干货
如果以上内容对你觉得有用,并想获取更多的赚钱方式和免费的技术教程
请关注微信公众号:HB荷包
一个能让你学习技术和赚钱方法的公众号,持续更新
*************************************优雅的分割线 **********************************