缓存的使用前面已经说过,在同时开启一级缓存和二级缓存的情况下,会先查询二级缓存,再查询一级缓存,最后查询数据库,并且在操作过程中,只有一次sqlSession查询,commit或者close之后,查询结果才会放入二级缓存,这点前面在mybatis的使用的时候也说过,接下来看下源码具体分析下。

标签解析

还是看加载配置解析配置的代码:

  1. // 还是看解析mappers标签的部分
  2. private void mapperElement(XNode parent) throws Exception {
  3. if (parent != null) {
  4. for (XNode child : parent.getChildren()) {
  5. if ("package".equals(child.getName())) {
  6. String mapperPackage = child.getStringAttribute("name");
  7. configuration.addMappers(mapperPackage);
  8. } else {
  9. String resource = child.getStringAttribute("resource");
  10. String url = child.getStringAttribute("url");
  11. String mapperClass = child.getStringAttribute("class");
  12. if (resource != null && url == null && mapperClass == null) {
  13. ErrorContext.instance().resource(resource);
  14. InputStream inputStream = Resources.getResourceAsStream(resource);
  15. XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
  16. mapperParser.parse();
  17. } else if (resource == null && url != null && mapperClass == null) {
  18. ErrorContext.instance().resource(url);
  19. InputStream inputStream = Resources.getUrlAsStream(url);
  20. XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
  21. mapperParser.parse();
  22. } else if (resource == null && url == null && mapperClass != null) {
  23. Class<?> mapperInterface = Resources.classForName(mapperClass);
  24. configuration.addMapper(mapperInterface);
  25. } else {
  26. throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
  27. }
  28. }
  29. }
  30. }
  31. }
  32. // 看parse方法
  33. public void parse() {
  34. if (!configuration.isResourceLoaded(resource)) {
  35. configurationElement(parser.evalNode("/mapper"));
  36. configuration.addLoadedResource(resource);
  37. bindMapperForNamespace();
  38. }
  39. parsePendingResultMaps();
  40. parsePendingCacheRefs();
  41. parsePendingStatements();
  42. }
  43. // 看configurationElement
  44. private void configurationElement(XNode context) {
  45. try {
  46. String namespace = context.getStringAttribute("namespace");
  47. if (namespace == null || namespace.isEmpty()) {
  48. throw new BuilderException("Mapper's namespace cannot be empty");
  49. }
  50. builderAssistant.setCurrentNamespace(namespace);
  51. cacheRefElement(context.evalNode("cache-ref"));
  52. cacheElement(context.evalNode("cache"));
  53. parameterMapElement(context.evalNodes("/mapper/parameterMap"));
  54. resultMapElements(context.evalNodes("/mapper/resultMap"));
  55. sqlElement(context.evalNodes("/mapper/sql"));
  56. buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
  57. } catch (Exception e) {
  58. throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
  59. }
  60. }
  61. // 关键在于解析cache部分
  62. private void cacheElement(XNode context) {
  63. if (context != null) {
  64. // 设置默认的缓存处理类,默认是 PerpetualCache,也可以自定义缓存实现类
  65. String type = context.getStringAttribute("type", "PERPETUAL");
  66. Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
  67. // 负责过期的Cache实现类
  68. String eviction = context.getStringAttribute("eviction", "LRU");
  69. Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
  70. // 清空缓存的频率 0表示不清空
  71. Long flushInterval = context.getLongAttribute("flushInterval");
  72. // 缓存大小
  73. Integer size = context.getIntAttribute("size");
  74. // 是否序列化
  75. boolean readWrite = !context.getBooleanAttribute("readOnly", false);
  76. // 是否阻塞
  77. boolean blocking = context.getBooleanAttribute("blocking", false);
  78. Properties props = context.getChildrenAsProperties();
  79. builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
  80. }
  81. }
  82. // 创建Cache对象
  83. public Cache useNewCache(Class<? extends Cache> typeClass,
  84. Class<? extends Cache> evictionClass,
  85. Long flushInterval,
  86. Integer size,
  87. boolean readWrite,
  88. boolean blocking,
  89. Properties props) {
  90. // 根据配置的参数创建Cache对象
  91. Cache cache = new CacheBuilder(currentNamespace)
  92. .implementation(valueOrDefault(typeClass, PerpetualCache.class))
  93. .addDecorator(valueOrDefault(evictionClass, LruCache.class))
  94. .clearInterval(flushInterval)
  95. .size(size)
  96. .readWrite(readWrite)
  97. .blocking(blocking)
  98. .properties(props)
  99. .build();
  100. // 将Cache对象添加到configuration对象中去
  101. configuration.addCache(cache);
  102. // 将当前 MapperBuilderAssistant 的cache设置,这里有大用
  103. currentCache = cache;
  104. return cache;
  105. }
  106. // 在 configurationElement中,我们解析玩Cache标签之后,看看最后 buildStatementFromContext 方法,证实一个Mapper一个Cache,准确说
  107. // 一个namespace一个cache
  108. // 下面这个类解析了每一个MappedStatement的参数
  109. public void parseStatementNode() {
  110. String id = context.getStringAttribute("id");
  111. String databaseId = context.getStringAttribute("databaseId");
  112. if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
  113. return;
  114. }
  115. String nodeName = context.getNode().getNodeName();
  116. SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
  117. boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
  118. boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
  119. boolean useCache = context.getBooleanAttribute("useCache", isSelect);
  120. boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
  121. // Include Fragments before parsing
  122. XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
  123. includeParser.applyIncludes(context.getNode());
  124. String parameterType = context.getStringAttribute("parameterType");
  125. Class<?> parameterTypeClass = resolveClass(parameterType);
  126. String lang = context.getStringAttribute("lang");
  127. LanguageDriver langDriver = getLanguageDriver(lang);
  128. // Parse selectKey after includes and remove them.
  129. processSelectKeyNodes(id, parameterTypeClass, langDriver);
  130. // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
  131. KeyGenerator keyGenerator;
  132. String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
  133. keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
  134. if (configuration.hasKeyGenerator(keyStatementId)) {
  135. keyGenerator = configuration.getKeyGenerator(keyStatementId);
  136. } else {
  137. keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
  138. configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
  139. ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
  140. }
  141. SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
  142. StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
  143. Integer fetchSize = context.getIntAttribute("fetchSize");
  144. Integer timeout = context.getIntAttribute("timeout");
  145. String parameterMap = context.getStringAttribute("parameterMap");
  146. String resultType = context.getStringAttribute("resultType");
  147. Class<?> resultTypeClass = resolveClass(resultType);
  148. String resultMap = context.getStringAttribute("resultMap");
  149. String resultSetType = context.getStringAttribute("resultSetType");
  150. ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
  151. if (resultSetTypeEnum == null) {
  152. resultSetTypeEnum = configuration.getDefaultResultSetType();
  153. }
  154. String keyProperty = context.getStringAttribute("keyProperty");
  155. String keyColumn = context.getStringAttribute("keyColumn");
  156. String resultSets = context.getStringAttribute("resultSets");
  157. builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
  158. fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
  159. resultSetTypeEnum, flushCache, useCache, resultOrdered,
  160. keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
  161. }
  162. // 下面方法是我们要关注的点
  163. public MappedStatement addMappedStatement(
  164. String id,
  165. SqlSource sqlSource,
  166. StatementType statementType,
  167. SqlCommandType sqlCommandType,
  168. Integer fetchSize,
  169. Integer timeout,
  170. String parameterMap,
  171. Class<?> parameterType,
  172. String resultMap,
  173. Class<?> resultType,
  174. ResultSetType resultSetType,
  175. boolean flushCache,
  176. boolean useCache,
  177. boolean resultOrdered,
  178. KeyGenerator keyGenerator,
  179. String keyProperty,
  180. String keyColumn,
  181. String databaseId,
  182. LanguageDriver lang,
  183. String resultSets) {
  184. if (unresolvedCacheRef) {
  185. throw new IncompleteElementException("Cache-ref not yet resolved");
  186. }
  187. id = applyCurrentNamespace(id, false);
  188. boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
  189. // 创建MapperStatement
  190. MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
  191. .resource(resource)
  192. .fetchSize(fetchSize)
  193. .timeout(timeout)
  194. .statementType(statementType)
  195. .keyGenerator(keyGenerator)
  196. .keyProperty(keyProperty)
  197. .keyColumn(keyColumn)
  198. .databaseId(databaseId)
  199. .lang(lang)
  200. .resultOrdered(resultOrdered)
  201. .resultSets(resultSets)
  202. .resultMaps(getStatementResultMaps(resultMap, resultType, id))
  203. .resultSetType(resultSetType)
  204. .flushCacheRequired(valueOrDefault(flushCache, !isSelect))
  205. .useCache(valueOrDefault(useCache, isSelect))
  206. .cache(currentCache);
  207. ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
  208. if (statementParameterMap != null) {
  209. statementBuilder.parameterMap(statementParameterMap);
  210. }
  211. MappedStatement statement = statementBuilder.build();
  212. configuration.addMappedStatement(statement);
  213. return statement;
  214. }
  215. // 在创建 MappedStatement 的时候,使用了 MapperBuilderAssistant的currentCache属性,那就证实了一个namespace一个cache

Cache标签的解析说明了一个namespace虽然有多个MappedStatement,但是它们都使用的是一个缓存,接下来继续深入执行流程。

二级缓存执行流程

这段源码的查看就可以清楚为什么必须要前一个sqlSession执行commit或者close操作之后,一级缓存的内容才可以刷新到二级缓存中去。

  1. // 执行方法 Executor.selectList 来进行查看,这个时候查询的时候就要找 CacheExecutor,如下
  2. @Override
  3. public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
  4. throws SQLException {
  5. // 上面cache标签的解析来说,每一个MappedStatement中都有一个Cache对象
  6. Cache cache = ms.getCache();
  7. if (cache != null) {
  8. // 判断是否清除缓存,根据设置的标签 flushCache 来判断
  9. flushCacheIfRequired(ms);
  10. // 判断是否使用缓存, useCache 标签
  11. if (ms.isUseCache() && resultHandler == null) {
  12. ensureNoOutParams(ms, boundSql);
  13. // TransactionalCacheManager 对象中查询二级缓存,这个下面有战士
  14. @SuppressWarnings("unchecked")
  15. List<E> list = (List<E>) tcm.getObject(cache, key);
  16. if (list == null) {
  17. // 查询一级缓存,这里执行的就是 BaseExecutor
  18. list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  19. // 保存数据到map中
  20. tcm.putObject(cache, key, list); // issue #578 and #116
  21. }
  22. return list;
  23. }
  24. }
  25. return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  26. }
  27. // 一级缓存中查询,没有查询数据库,也是上面的 delegate.query
  28. @Override
  29. public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
  30. ......
  31. // 前面的不用看,直接看查询缓存和查询数据库的操作
  32. try {
  33. queryStack++;
  34. // 查询一级缓存
  35. list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
  36. if (list != null) {
  37. handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
  38. } else {
  39. // 查询数据库
  40. list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
  41. }
  42. } finally {
  43. queryStack--;
  44. }
  45. ......
  46. return list;
  47. }
  48. // 查询数据库
  49. private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
  50. List<E> list;
  51. localCache.putObject(key, EXECUTION_PLACEHOLDER);
  52. try {
  53. // 查询数据库,简历连接执行sql
  54. list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
  55. } finally {
  56. localCache.removeObject(key);
  57. }
  58. // 结果存入一级缓存
  59. localCache.putObject(key, list);
  60. if (ms.getStatementType() == StatementType.CALLABLE) {
  61. localOutputParameterCache.putObject(key, parameter);
  62. }
  63. return list;
  64. }
  65. // 结果查询完成之后就要返回到我们的query方法,接下来执行 tcm.putObject(cache, key, list);
  66. // 目标类是 TransactionalCacheManager
  67. private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<>();
  68. public void putObject(Cache cache, CacheKey key, Object value) {
  69. // 根据二级缓存的 cache对象来从 transactionalCaches 中获取 TransactionalCache
  70. getTransactionalCache(cache).putObject(key, value);
  71. }
  72. // 接下来就要看 TransactionalCache 的put方法 以及 get方法了,因为get方法也是从 TransactionalCache对象中获取
  73. private final Cache delegate;
  74. private final Map<Object, Object> entriesToAddOnCommit;
  75. @Override
  76. public void putObject(Object key, Object object) {
  77. // 可以看到 put的时候是放在 entriesToAddOnCommit中
  78. entriesToAddOnCommit.put(key, object);
  79. }
  80. @Override
  81. public Object getObject(Object key) {
  82. // issue #116
  83. // 从Cache获取的时候取的是 Cache 对象 delegate中的
  84. Object object = delegate.getObject(key);
  85. if (object == null) {
  86. entriesMissedInCache.add(key);
  87. }
  88. // issue #146
  89. if (clearOnCommit) {
  90. return null;
  91. } else {
  92. return object;
  93. }
  94. }

到这里就知道为什么查询的时候在没有 commit 和 close 的时候无法命中缓存。

二级缓存生效时机

上面说了只有 sqlSession 执行 commit 或 close 缓存才会生效,看看执行操作干了什么?

  1. // 层层向下找,找到 CachingExecutor 的commit
  2. @Override
  3. public void commit(boolean required) throws SQLException {
  4. // 一级缓存的commit
  5. delegate.commit(required);
  6. tcm.commit();
  7. }
  8. // 下面的操作就是将保存在 delegate 中的缓存数据保存到 entriesToAddOnCommit 中
  9. public void commit() {
  10. for (TransactionalCache txCache : transactionalCaches.values()) {
  11. txCache.commit();
  12. }
  13. }
  14. public void commit() {
  15. if (clearOnCommit) {
  16. delegate.clear();
  17. }
  18. flushPendingEntries();
  19. reset();
  20. }
  21. private void flushPendingEntries() {
  22. for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {
  23. delegate.putObject(entry.getKey(), entry.getValue());
  24. }
  25. for (Object entry : entriesMissedInCache) {
  26. if (!entriesToAddOnCommit.containsKey(entry)) {
  27. delegate.putObject(entry, null);
  28. }
  29. }
  30. }
  31. // 上面是 commit 流程,下面看close
  32. @Override
  33. public void close(boolean forceRollback) {
  34. try {
  35. // issues #499, #524 and #573
  36. if (forceRollback) {
  37. tcm.rollback();
  38. } else {
  39. // close也会执行 TransactionalCacheManager 的commit 方法
  40. tcm.commit();
  41. }
  42. } finally {
  43. delegate.close(forceRollback);
  44. }
  45. }

综上所述,只有commit或者close之后二级缓存才会生效,原因就是一级缓存查询之后并没有保存到二级缓存,而是保存到了同一个对象TransactionalCache下的Cache对象中,而不是保存二级缓存Map对象 entriesToAddOnCommit 中。

二级缓存的刷新

其实二级缓存的刷新也叫二级缓存的清空,也就是在执行update方法的时候会进行清空,这里的update包括了增删改操作

  1. @Override
  2. public int update(String statement, Object parameter) {
  3. try {
  4. dirty = true;
  5. MappedStatement ms = configuration.getMappedStatement(statement);
  6. return executor.update(ms, wrapCollection(parameter));
  7. } catch (Exception e) {
  8. throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e);
  9. } finally {
  10. ErrorContext.instance().reset();
  11. }
  12. }
  13. @Override
  14. public int update(MappedStatement ms, Object parameterObject) throws SQLException {
  15. flushCacheIfRequired(ms);
  16. return delegate.update(ms, parameterObject);
  17. }
  18. // 清空二级缓存
  19. private void flushCacheIfRequired(MappedStatement ms) {
  20. Cache cache = ms.getCache();
  21. if (cache != null && ms.isFlushCacheRequired()) {
  22. tcm.clear(cache);
  23. }
  24. }

综上,基本就是二级缓存的所有流程了,包括了执行、刷新、生效。
二级缓存的设计使用了大量的装饰者模式:

  • 二级缓存是基于namespace级别的,实现了sqlSession之间的缓存共享
  • 二级缓存的工作是由一个缓存执行器CachingExecutor和一个事务型预缓存 TransactionalCache 完成