解析基于Mybatis 3.4.4 Release,近几个版本应该都适用。
在上一篇博客中我们详细说明了< cache>元素的解析过程和Cache对象的实例化过程,这篇博客接着往下讲。
在实例化Cache对象后,还需要弄明白其与MappedStatement的关联过程。
public class XMLMapperBuilder extends BaseBuilder {
private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
resultMapElements(context.evalNodes("/mapper/resultMap"));
sqlElement(context.evalNodes("/mapper/sql"));
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
}
}
}
Mybatis在解析Mapper.xml时,先解析< cache>元素,实例化Cache,且将其赋值给 “currentCache” 属性后,再解析 “select|insert|update|delete” 元素。
在解析 “select|insert|update|delete” 的过程中,就能通过 “currentCache” 引用到实例化的Cache对象:
public class XMLStatementBuilder extends BaseBuilder {
private MapperBuilderAssistant builderAssistant;
public void parseStatementNode() {
......
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
// 是否刷新缓存,当不是 SELECT 类型的SQL节点时刷新
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
// 是否可使用缓存, 当是SELECT 类型的SQL节点时可使用
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
......
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
}
public class MapperBuilderAssistant extends BaseBuilder {
private Cache currentCache;
public MappedStatement addMappedStatement(String id,SqlSource sqlSource,......) {
MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
......
.flushCacheRequired(valueOrDefault(flushCache, !isSelect)) // 当SQL节点类型非SELECT时, flushCacheRequired = true
.useCache(valueOrDefault(useCache, isSelect)) // 当SQL节点类型为SELECT时, useCache = true
.cache(currentCache); // MappedStatement关联实例化好的Cache对象
......
MappedStatement statement = statementBuilder.build();
configuration.addMappedStatement(statement);
return statement;
}
}
MappedStatement和Cache的关联流程也讲完了,我们接着上篇博客的CachingExecutor继续讲。
public class CachingExecutor implements Executor {
private Executor delegate;
private TransactionalCacheManager tcm = new TransactionalCacheManager();
@Override
public List query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
// 获取MappedStatement 上的缓存对象, 如果Mapper.xml里没有< cache>元素,那么返回的就是null,就不能使用二级缓存了
Cache cache = ms.getCache();
if (cache != null) {
flushCacheIfRequired(ms);
// 如果MappedStatement的SQL元素类型是SELECT, 那么useCache == true
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, parameterObject, boundSql);
// 通过CacheKey 作为key去缓存里搜索
List list = (List) tcm.getObject(cache, key);
if (list == null) {
// 如果缓存value不存在,那么借助delegate执行查询,然后存入tcm,这样相同的查询下次就能复用此结果了
list = delegate. query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
return delegate. query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
// 是否要刷新缓存
private void flushCacheIfRequired(MappedStatement ms) {
Cache cache = ms.getCache();
// 如果当前MappedStatement的SQL元素类型是insert,update,delete,那么会触发clear操作
if (cache != null && ms.isFlushCacheRequired()) {
tcm.clear(cache);
}
}
}
下面我们看tcm.getObject(cache, key);和tcm.putObject(cache, key, list)的内容,也就是看TransactionalCacheManager的代码:
public class TransactionalCacheManager {
private Map transactionalCaches = new HashMap();
public void clear(Cache cache) {
getTransactionalCache(cache).clear();
}
public Object getObject(Cache cache, CacheKey key) {
return getTransactionalCache(cache).getObject(key);
}
public void putObject(Cache cache, CacheKey key, Object value) {
getTransactionalCache(cache).putObject(key, value);
}
public void commit() {
for (TransactionalCache txCache : transactionalCaches.values()) {
txCache.commit();
}
}
public void rollback() {
for (TransactionalCache txCache : transactionalCaches.values()) {
txCache.rollback();
}
}
private TransactionalCache getTransactionalCache(Cache cache) {
TransactionalCache txCache = transactionalCaches.get(cache);
// 获取Cache对应的TransactionalCache ,如果不存在那么新建一个,然后存起来以备下次复用
if (txCache == null) {
txCache = new TransactionalCache(cache);
transactionalCaches.put(cache, txCache);
}
return txCache;
}
}
每个DefaultSqlSession持有一个CachingExecutor,每个CachingExecutor关联一个TransactionalCacheManager ,其内部含有Cache >> TransactionalCache 的映射集合。
一个Cache代表着一个Mapper.xml内解析成的缓存对象。当使用到Cache时,如果其不存在相应的TransactionalCache,那么新建一个,然后缓存起来以备下次复用。
在一个事务内的多次数据库操作共用一个SqlSession,也就是共用一个TransactionalCacheManager ,也就是事务内使用到的Mapper的Cache都会被缓存进映射集合中。
我们先将TransactionalCacheManager 的内部逻辑讲清楚,然后接着讲起内部的 Cache >> TransactionalCache 映射:
public class CachingExecutor implements Executor {
private Executor delegate;
private TransactionalCacheManager tcm = new TransactionalCacheManager();
public void commit(boolean required) throws SQLException {
delegate.commit(required);
tcm.commit();
}
public void rollback(boolean required) throws SQLException {
try {
delegate.rollback(required);
} finally {
if (required) {
tcm.rollback();
}
}
}
public int update(MappedStatement ms, Object parameterObject) throws SQLException {
flushCacheIfRequired(ms);
return delegate.update(ms, parameterObject);
}
private void flushCacheIfRequired(MappedStatement ms) {
Cache cache = ms.getCache();
if (cache != null && ms.isFlushCacheRequired()) {
tcm.clear(cache);
}
}
}
SqlSession在执行Commit和Rollback时,都会调用Executor的Commit和Rollback操作。
SqlSession在执行Insert,Delete,Update时会执行Executor的update方法,其内部调用flushCacheIfRequired方法,当此方法对应的Mapper.xml内有 < cache……/>节点,也就是实例化过Cache对象,且当前操作是Insert,Delete,Update 操作时,TransactionalCacheManager 清空指定的Cache。
结合上面TransactionalCacheManager 的源码,我们接着看TransactionalCache的代码:
public class TransactionalCache implements Cache {
private Cache delegate;
private boolean clearOnCommit;
private Map
TransactionalCache 对 Cache对象做了一层装饰,在通过CacheKey获取Value时,调用Cache的getObject( ) 方法返回缓存的值。
如果缓存没有值,那么通过装饰过的Executor去执行查询方法,然后调用putObject( ) 方法,存入entriesToAddOnCommit属性中。
entriesToAddOnCommit,从字面理解,在Commit时将会被添加的Entries,也就是在事务Commit时,这些Entries会被加入Cache对象中。
看commit( ) 方法,其调用flushPendingEntries( ),将entriesToAddOnCommit里的键值对合并入装饰过的Cache中,Cache调用putObject( )最终都会合并入PerpetualCache的cache属性中。
而在rollback( ) 方法中则不会将entriesToAddOnCommit合并入Cache。
也就是说只有在事务提交后,当前事务内的查询结果才能生效;如果事务回滚,那么当前事务内的查询结果会清空。
当执行某个Mapper.xml 的Insert,Delete,Update时,会触发其Cache相应的TransactionalCache 的clear( ) 操作,将当前事务内的查询结果中间缓存清空,也就是事务内之前的查询结果都失效了。
到这里源码上的流程基本都讲完了,做个总结吧: