缓存的使用前面已经说过,在同时开启一级缓存和二级缓存的情况下,会先查询二级缓存,再查询一级缓存,最后查询数据库,并且在操作过程中,只有一次sqlSession查询,commit或者close之后,查询结果才会放入二级缓存,这点前面在mybatis的使用的时候也说过,接下来看下源码具体分析下。
标签解析
还是看加载配置解析配置的代码:
// 还是看解析mappers标签的部分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 (resource != null && url == null && mapperClass == null) {ErrorContext.instance().resource(resource);InputStream inputStream = Resources.getResourceAsStream(resource);XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());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.");}}}}}// 看parse方法public void parse() {if (!configuration.isResourceLoaded(resource)) {configurationElement(parser.evalNode("/mapper"));configuration.addLoadedResource(resource);bindMapperForNamespace();}parsePendingResultMaps();parsePendingCacheRefs();parsePendingStatements();}// 看configurationElementprivate void configurationElement(XNode context) {try {String namespace = context.getStringAttribute("namespace");if (namespace == null || namespace.isEmpty()) {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. The XML location is '" + resource + "'. Cause: " + e, e);}}// 关键在于解析cache部分private void cacheElement(XNode context) {if (context != null) {// 设置默认的缓存处理类,默认是 PerpetualCache,也可以自定义缓存实现类String type = context.getStringAttribute("type", "PERPETUAL");Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);// 负责过期的Cache实现类String eviction = context.getStringAttribute("eviction", "LRU");Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);// 清空缓存的频率 0表示不清空Long flushInterval = context.getLongAttribute("flushInterval");// 缓存大小Integer size = context.getIntAttribute("size");// 是否序列化boolean readWrite = !context.getBooleanAttribute("readOnly", false);// 是否阻塞boolean blocking = context.getBooleanAttribute("blocking", false);Properties props = context.getChildrenAsProperties();builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);}}// 创建Cache对象public Cache useNewCache(Class<? extends Cache> typeClass,Class<? extends Cache> evictionClass,Long flushInterval,Integer size,boolean readWrite,boolean blocking,Properties props) {// 根据配置的参数创建Cache对象Cache cache = new CacheBuilder(currentNamespace).implementation(valueOrDefault(typeClass, PerpetualCache.class)).addDecorator(valueOrDefault(evictionClass, LruCache.class)).clearInterval(flushInterval).size(size).readWrite(readWrite).blocking(blocking).properties(props).build();// 将Cache对象添加到configuration对象中去configuration.addCache(cache);// 将当前 MapperBuilderAssistant 的cache设置,这里有大用currentCache = cache;return cache;}// 在 configurationElement中,我们解析玩Cache标签之后,看看最后 buildStatementFromContext 方法,证实一个Mapper一个Cache,准确说// 一个namespace一个cache// 下面这个类解析了每一个MappedStatement的参数public void parseStatementNode() {String id = context.getStringAttribute("id");String databaseId = context.getStringAttribute("databaseId");if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {return;}String nodeName = context.getNode().getNodeName();SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));boolean isSelect = sqlCommandType == SqlCommandType.SELECT;boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);boolean useCache = context.getBooleanAttribute("useCache", isSelect);boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);// Include Fragments before parsingXMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);includeParser.applyIncludes(context.getNode());String parameterType = context.getStringAttribute("parameterType");Class<?> parameterTypeClass = resolveClass(parameterType);String lang = context.getStringAttribute("lang");LanguageDriver langDriver = getLanguageDriver(lang);// Parse selectKey after includes and remove them.processSelectKeyNodes(id, parameterTypeClass, langDriver);// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)KeyGenerator keyGenerator;String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);if (configuration.hasKeyGenerator(keyStatementId)) {keyGenerator = configuration.getKeyGenerator(keyStatementId);} else {keyGenerator = context.getBooleanAttribute("useGeneratedKeys",configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;}SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));Integer fetchSize = context.getIntAttribute("fetchSize");Integer timeout = context.getIntAttribute("timeout");String parameterMap = context.getStringAttribute("parameterMap");String resultType = context.getStringAttribute("resultType");Class<?> resultTypeClass = resolveClass(resultType);String resultMap = context.getStringAttribute("resultMap");String resultSetType = context.getStringAttribute("resultSetType");ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);if (resultSetTypeEnum == null) {resultSetTypeEnum = configuration.getDefaultResultSetType();}String keyProperty = context.getStringAttribute("keyProperty");String keyColumn = context.getStringAttribute("keyColumn");String resultSets = context.getStringAttribute("resultSets");builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,resultSetTypeEnum, flushCache, useCache, resultOrdered,keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);}// 下面方法是我们要关注的点public MappedStatement addMappedStatement(String id,SqlSource sqlSource,StatementType statementType,SqlCommandType sqlCommandType,Integer fetchSize,Integer timeout,String parameterMap,Class<?> parameterType,String resultMap,Class<?> resultType,ResultSetType resultSetType,boolean flushCache,boolean useCache,boolean resultOrdered,KeyGenerator keyGenerator,String keyProperty,String keyColumn,String databaseId,LanguageDriver lang,String resultSets) {if (unresolvedCacheRef) {throw new IncompleteElementException("Cache-ref not yet resolved");}id = applyCurrentNamespace(id, false);boolean isSelect = sqlCommandType == SqlCommandType.SELECT;// 创建MapperStatementMappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType).resource(resource).fetchSize(fetchSize).timeout(timeout).statementType(statementType).keyGenerator(keyGenerator).keyProperty(keyProperty).keyColumn(keyColumn).databaseId(databaseId).lang(lang).resultOrdered(resultOrdered).resultSets(resultSets).resultMaps(getStatementResultMaps(resultMap, resultType, id)).resultSetType(resultSetType).flushCacheRequired(valueOrDefault(flushCache, !isSelect)).useCache(valueOrDefault(useCache, isSelect)).cache(currentCache);ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);if (statementParameterMap != null) {statementBuilder.parameterMap(statementParameterMap);}MappedStatement statement = statementBuilder.build();configuration.addMappedStatement(statement);return statement;}// 在创建 MappedStatement 的时候,使用了 MapperBuilderAssistant的currentCache属性,那就证实了一个namespace一个cache
Cache标签的解析说明了一个namespace虽然有多个MappedStatement,但是它们都使用的是一个缓存,接下来继续深入执行流程。
二级缓存执行流程
这段源码的查看就可以清楚为什么必须要前一个sqlSession执行commit或者close操作之后,一级缓存的内容才可以刷新到二级缓存中去。
// 执行方法 Executor.selectList 来进行查看,这个时候查询的时候就要找 CacheExecutor,如下@Overridepublic <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)throws SQLException {// 上面cache标签的解析来说,每一个MappedStatement中都有一个Cache对象Cache cache = ms.getCache();if (cache != null) {// 判断是否清除缓存,根据设置的标签 flushCache 来判断flushCacheIfRequired(ms);// 判断是否使用缓存, useCache 标签if (ms.isUseCache() && resultHandler == null) {ensureNoOutParams(ms, boundSql);// TransactionalCacheManager 对象中查询二级缓存,这个下面有战士@SuppressWarnings("unchecked")List<E> list = (List<E>) tcm.getObject(cache, key);if (list == null) {// 查询一级缓存,这里执行的就是 BaseExecutorlist = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);// 保存数据到map中tcm.putObject(cache, key, list); // issue #578 and #116}return list;}}return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);}// 一级缓存中查询,没有查询数据库,也是上面的 delegate.query@Overridepublic <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {......// 前面的不用看,直接看查询缓存和查询数据库的操作try {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--;}......return list;}// 查询数据库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 {// 查询数据库,简历连接执行sqllist = 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;}// 结果查询完成之后就要返回到我们的query方法,接下来执行 tcm.putObject(cache, key, list);// 目标类是 TransactionalCacheManagerprivate final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<>();public void putObject(Cache cache, CacheKey key, Object value) {// 根据二级缓存的 cache对象来从 transactionalCaches 中获取 TransactionalCachegetTransactionalCache(cache).putObject(key, value);}// 接下来就要看 TransactionalCache 的put方法 以及 get方法了,因为get方法也是从 TransactionalCache对象中获取private final Cache delegate;private final Map<Object, Object> entriesToAddOnCommit;@Overridepublic void putObject(Object key, Object object) {// 可以看到 put的时候是放在 entriesToAddOnCommit中entriesToAddOnCommit.put(key, object);}@Overridepublic Object getObject(Object key) {// issue #116// 从Cache获取的时候取的是 Cache 对象 delegate中的Object object = delegate.getObject(key);if (object == null) {entriesMissedInCache.add(key);}// issue #146if (clearOnCommit) {return null;} else {return object;}}
到这里就知道为什么查询的时候在没有 commit 和 close 的时候无法命中缓存。
二级缓存生效时机
上面说了只有 sqlSession 执行 commit 或 close 缓存才会生效,看看执行操作干了什么?
// 层层向下找,找到 CachingExecutor 的commit@Overridepublic void commit(boolean required) throws SQLException {// 一级缓存的commitdelegate.commit(required);tcm.commit();}// 下面的操作就是将保存在 delegate 中的缓存数据保存到 entriesToAddOnCommit 中public void commit() {for (TransactionalCache txCache : transactionalCaches.values()) {txCache.commit();}}public void commit() {if (clearOnCommit) {delegate.clear();}flushPendingEntries();reset();}private void flushPendingEntries() {for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {delegate.putObject(entry.getKey(), entry.getValue());}for (Object entry : entriesMissedInCache) {if (!entriesToAddOnCommit.containsKey(entry)) {delegate.putObject(entry, null);}}}// 上面是 commit 流程,下面看close@Overridepublic void close(boolean forceRollback) {try {// issues #499, #524 and #573if (forceRollback) {tcm.rollback();} else {// close也会执行 TransactionalCacheManager 的commit 方法tcm.commit();}} finally {delegate.close(forceRollback);}}
综上所述,只有commit或者close之后二级缓存才会生效,原因就是一级缓存查询之后并没有保存到二级缓存,而是保存到了同一个对象TransactionalCache下的Cache对象中,而不是保存二级缓存Map对象 entriesToAddOnCommit 中。
二级缓存的刷新
其实二级缓存的刷新也叫二级缓存的清空,也就是在执行update方法的时候会进行清空,这里的update包括了增删改操作
@Overridepublic int update(String statement, Object parameter) {try {dirty = true;MappedStatement ms = configuration.getMappedStatement(statement);return executor.update(ms, wrapCollection(parameter));} catch (Exception e) {throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e);} finally {ErrorContext.instance().reset();}}@Overridepublic 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);}}
综上,基本就是二级缓存的所有流程了,包括了执行、刷新、生效。
二级缓存的设计使用了大量的装饰者模式:
- 二级缓存是基于namespace级别的,实现了sqlSession之间的缓存共享
- 二级缓存的工作是由一个缓存执行器CachingExecutor和一个事务型预缓存 TransactionalCache 完成
