前言:
在上一篇文章Mybatis源码解析之一级缓存中分析了Mybatis一级缓存的使用及源码分析。
本篇博客就继续从源码角度来分析关于Mybatis的二级缓存。
1.二级缓存使用
项目配置均同一级缓存的配置相同,不同点就是
1)在blog.xml中假如标签
select * from blog where id = #{id}
update Blog set name = #{name},url = #{url} where id=#{id}
2.二级缓存的测试及结论 1)不同的session进行相同的查询
public static void main(String[] args) {
SqlSession session = sqlSessionFactory.openSession();
SqlSession session1 = sqlSessionFactory.openSession();
try {
Blog blog = (Blog)session.selectOne("queryById",17);
Blog blog2 = (Blog)session1.selectOne("queryById",17);
} finally {
session.close();
}
}
结论:执行两次DB查询
2)第一个session查询完成之后,手动提交,在执行第二个session查询
public static void main(String[] args) {
SqlSession session = sqlSessionFactory.openSession();
SqlSession session1 = sqlSessionFactory.openSession();
try {
Blog blog = (Blog)session.selectOne("queryById",17);
session.commit();
Blog blog2 = (Blog)session1.selectOne("queryById",17);
} finally {
session.close();
}
}
结论:执行一次DB查询
3)第一个session查询完成之后,手动关闭,在执行第二个session查询
public static void main(String[] args) {
SqlSession session = sqlSessionFactory.openSession();
SqlSession session1 = sqlSessionFactory.openSession();
try {
Blog blog = (Blog)session.selectOne("queryById",17);
session.close();
Blog blog2 = (Blog)session1.selectOne("queryById",17);
} finally {
session.close();
}
}
结论:执行一次DB查询
总结:二级缓存的生效必须在session提交或关闭之后才会生效
3.二级缓存源码分析
我们看下二级缓存与一级缓存的配置的异同,发现只是在blog.xml中添加了一个标签,就实现了二级缓存的功能,那么我们就从这个标签开始分析
1)的解析
按照之前的对Mybatis的分析,对blog.xml的解析工作主要交给XMLConfigBuilder.parse()方法来实现的
// XMLConfigBuilder.parse()
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));// 在这里
return configuration;
}
// parseConfiguration()
// 既然是在blog.xml中添加的,那么我们就直接看关于mappers标签的解析
private void parseConfiguration(XNode root) {
try {
Properties settings = settingsAsPropertiess(root.evalNode("settings"));
//issue #117 read properties first
propertiesElement(root.evalNode("properties"));
loadCustomVfs(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectionFactoryElement(root.evalNode("reflectionFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
// 就是这里
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
// mapperElement()
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
// 按照我们本例的配置,则直接走该if判断
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
// 生成XMLMapperBuilder,并执行其parse方法
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url == null && mapperClass != null) {
Class mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
2)XMLMapperBuilder.parse()解析对应的blog.xml配置文件
// XMLMapperBuilder.parse()
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
// 解析mapper属性
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingChacheRefs();
parsePendingStatements();
}
// configurationElement()
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"));
// 最终在这里看到了关于cache属性的处理
cacheElement(context.evalNode("cache"));
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
resultMapElements(context.evalNodes("/mapper/resultMap"));
sqlElement(context.evalNodes("/mapper/sql"));
// 这里会将生成的Cache包装到对应的MappedStatement,这个继续在4)中分析
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
}
}
// cacheElement()
private void cacheElement(XNode context) throws Exception {
if (context != null) {
String type = context.getStringAttribute("type", "PERPETUAL");
Class parameterTypeClass = resolveClass(parameterType);
String resultMap = context.getStringAttribute("resultMap");
String resultType = context.getStringAttribute("resultType");
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);
...
// 在这里添加MappedStatement
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
// builderAssistant.addMappedStatement()
public MappedStatement addMappedStatement(
String id,
...) {
if (unresolvedCacheRef) {
throw new IncompleteElementException("Cache-ref not yet resolved");
}
id = applyCurrentNamespace(id, false);
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
...
.flushCacheRequired(valueOrDefault(flushCache, !isSelect))
.useCache(valueOrDefault(useCache, isSelect))
.cache(currentCache);// 在这里将之前生成的Cache封装到MappedStatement
ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
if (statementParameterMap != null) {
statementBuilder.parameterMap(statementParameterMap);
}
MappedStatement statement = statementBuilder.build();
configuration.addMappedStatement(statement);
return statement;
}
总结:有关于标签的解析就到这了。我们再来回顾下,到底产生了什么对象,又放在哪里了?
从前到后依次是:
* XMLConfigureBuilder解析对应的mapper xml,为每一个mapper xml生成一个XMLMapperBuilder对象
* XMLMapperBuilder负责具体的解析mapper xml操作
* 关于其中的cache标签,每一个XMLMapperBuilder有对应的MapperBuilderAssistant对象进行cache标签解析
* MapperBuilderAssistant负责为cache标签创建Cache对象,并存在到Configuration一份,保存到当前属性currentCache一份
* XMLMapperBuilder解析mapper xml中的每一个执行语句,将其解析为一个MappedStatement,并将Cache对象封装其中
4.查询时如何使用这个一级缓存?
我们来看下上面的查询示例
Blog blog = (Blog)session.selectOne("queryById",17);
1)查询源码分析(根据上面的分析我们直接定位到CachingExecutor.query()方法)
// CachingExecutor.query()
@Override
public List query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
// 该Cache就是一级缓存,对应当前MappedStatement的Cache,
// 我们在上面解析blog.xml时分析过每一个MappedStatement都有一个Cache对象,就是这里
Cache cache = ms.getCache();
if (cache != null) {
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, parameterObject, boundSql);
@SuppressWarnings("unchecked")
// 先从缓存中获取该cache对应的值
List list = (List) tcm.getObject(cache, key);
if (list == null) {
// 如果没有值,则执行查询,这个查询实际也是先走一级缓存查询,一级缓存也没有的话,则进行DB查询
list = delegate. query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
// 然后将值放到tcm中,tcm就是TransactionalCacheManager
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
return delegate. query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
2)tcm.putObject(cache, key, list)如果保存值?
// TransactionalCacheManager.putObject(cache, key, list)
public void putObject(Cache cache, CacheKey key, Object value) {
getTransactionalCache(cache).putObject(key, value);
}
// getTransactionalCache()
private TransactionalCache getTransactionalCache(Cache cache) {
TransactionalCache txCache = transactionalCaches.get(cache);
if (txCache == null) {
txCache = new TransactionalCache(cache);
transactionalCaches.put(cache, txCache);
}
return txCache;
}
// TransactionalCache.putObject()
@Override
public void putObject(Object key, Object object) {
entriesToAddOnCommit.put(key, object);
}
最终将值放到TransactionCache中,下面我们看下TransactionCache的结构
public class TransactionalCache implements Cache {
private static final Log log = LogFactory.getLog(TransactionalCache.class);
private Cache delegate;
private boolean clearOnCommit;
private Map entriesToAddOnCommit;
private Set entriesMissedInCache;
可以知道,刚才的值只是存放到entriesToAddOnCommit这个属性中。
3)tcm.getObject(cache, key)如何查询二级缓存
// TransactionalCacheManager.getObject(cache, key)
public Object getObject(Cache cache, CacheKey key) {
return getTransactionalCache(cache).getObject(key);
}
// TransactionalCache.getObject()
@Override
public Object getObject(Object key) {
// 真正的问题在这
// 刚才存储的时候放到了entriesToAddOnCommit这个map中
// 但是查询的时候是从delegate中去查询的
Object object = delegate.getObject(key);
if (object == null) {
entriesMissedInCache.add(key);
}
// issue #146
if (clearOnCommit) {
return null;
} else {
return object;
}
}
总结:刚才存储二级缓存对象的时候放到了TransactionalCache.entriesToAddOnCommit这个map中
但是查询的时候是从TransactionalCache.delegate中去查询的,所以这个二级缓存查询完成之后是没有立刻生效的
按照我们刚才的示例,只有SqlSession提交或关闭后才提交,下面我们接着来看下提交或关闭操作做了什么
2)为何只有SqlSession提交或关闭之后二级缓存才会生效?
那我们来看下SqlSession.commit()方法做了什么
// DefaultSqlSession.commit()
@Override
public void commit(boolean force) {
try {
// 主要是这句
executor.commit(isCommitOrRollbackRequired(force));
dirty = false;
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error committing transaction. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
// CachingExecutor.commit()
@Override
public void commit(boolean required) throws SQLException {
delegate.commit(required);
tcm.commit();// 在这里
}
// TransactionalCacheManager.commit()
public void commit() {
for (TransactionalCache txCache : transactionalCaches.values()) {
txCache.commit();// 在这里
}
}
// TransactionalCache.commit()
public void commit() {
if (clearOnCommit) {
delegate.clear();
}
flushPendingEntries();//这一句
reset();
}
// TransactionalCache.flushPendingEntries()
private void flushPendingEntries() {
for (Map.Entry entry : entriesToAddOnCommit.entrySet()) {
// 在这里真正的将entriesToAddOnCommit的对象逐个添加到delegate中,只有这时,二级缓存才真正的生效
delegate.putObject(entry.getKey(), entry.getValue());
}
for (Object entry : entriesMissedInCache) {
if (!entriesToAddOnCommit.containsKey(entry)) {
delegate.putObject(entry, null);
}
}
}