当前位置:   article > 正文

MyBatis缓存之一级缓存(SqlSession级别)二级缓存(Mapper级别)原理_mybaits-plus 一级缓存原理

mybaits-plus 一级缓存原理

        MyBatis的缓存分为一级缓存和二级缓存(全局缓存) ,缓存示意图如下图所示。默认情况下,一级缓存是开启的,且不能被关闭。

Mybatis缓存设计

  MyBatis的一级缓存(SqlSession级别)

在同一个SqlSession中执行相同的SQL语句时,将查询结果集缓存,第二次查询时直接取缓存,不在操作数据库。注意:一级缓存最多缓存1024条SQL语句。

原理: 当客户端第一次发出SQL查询语句时,MyBatis执行SQL的查询并将查询结果写入SqlSession的一级缓存中,当第二次相同的SQL的查询语句过来时,则直接从缓存中获取数据,具体过程如上图所示。缓存使用的数据结构是Map,其中key是 MapperId+Offset+Limit+SQL+所有的入参。

同一个SqlSession操作相同的SQL语句,如中间有修改操作时,需不要手动flush?

不需要!!!

当同一个SqlSession多次发出相同的SQL语句查询时,MyBatis会直接从缓存中取数据。如果前后两次中间出现过commit操作(修改、添加、删除),则认为数据发生了变化,MyBatis会把SqlSession中的一级缓存区域全部清空,则下一次的SQL语句查询时,需从数据库中查询数据并将查询的结果写入缓存。

1、MyBatis的一级缓存是SqlSession级别的,但是它并不定义在SqlSessio接口的实现类DefaultSqlSession中,而是定义在DefaultSqlSession的成员变量Executor中,Executor是在openSession的时候被实例化出来的,它的默认实现为SimpleExecutor

2、MyBatis中的一级缓存,与有没有配置无关,只要SqlSession存在,MyBastis一级缓存就存在,localCache的类型是PerpetualCache,它其实很简单,一个id属性+一个HashMap属性而已,id是一个名为"localCache"的字符串,HashMap用于存储数据,Key为CacheKey,Value为查询结果

3、MyBatis的一级缓存查询的时候默认都是会先尝试从一级缓存中获取数据的,但是我们看第6行的代码做了一个判断,ms.isFlushCacheRequired(),即想每次查询都走DB也行,将<select>标签中的flushCache属性设置为true即可,这意味着每次查询的时候都会清理一遍PerpetualCache,PerpetualCache中没数据,自然只能走DB

从MyBatis一级缓存来看,它以单纯的HashMap做缓存,没有容量控制,而一次SqlSession中通常来说并不会有大量的查询操作,因此只适用于一次SqlSession,如果用到二级缓存的Mapper级别的场景,有可能缓存数据不断碰到而导致内存溢出。

<insert>、<delete>、<update>最终都会转换为update方法,看一下BaseExecutor的update方法:

  1. public int update(MappedStatement ms, Object parameter) throws SQLException {
  2. ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
  3. if (closed) {
  4. throw new ExecutorException("Executor was closed.");
  5. }
  6. clearLocalCache();
  7. return doUpdate(ms, parameter);
  8. }

clearLocalCache()方法 =====> 所有的增、删、改都会清空缓存,这和是否配置了flushCache=true是无关的。 

namespace+id 其中namespace为Mapper的全量名!

MyBatis的二级缓存(Mapper级别,全局缓存)

跨SqlSession的缓存,Mapper级别的缓存。在Mapper级别的缓存内,不同的SQLSession缓存可以共享。

原理: Mapper级别,Mapper以命名空间为单位创建缓存数据结构,缓存使用的数据结构是Map,其中key是 MapperId+Offset+Limit+SQL+所有的入参。MyBatis的二级缓存是CacheExecutor实现的。CacheExecutor是Executor的代理对象。当MyBatis接收到用户的查询请求时首先会根据Map的Key在CacheExecute缓存中查询数据是否存在,存在则从缓存中取,否则将执行数据库查询操作。

开启二级缓存需要进行相关配置:

  1. MyBatis全局配置中启用二级缓存配置 CacheEnable=true;
  2. 在对应的Mapper文件中配置Cache节点;
  3. 在对应的Select标签中添加useCache=true;

MyBatis中的Cache接口

  1. public interface Cache {
  2. /**
  3. * @return The identifier of this cache
  4. */
  5. String getId();
  6. /**
  7. * @param key Can be any object but usually it is a {@link CacheKey}
  8. * @param value The result of a select.
  9. */
  10. void putObject(Object key, Object value);
  11. /**
  12. * @param key The key
  13. * @return The object stored in the cache.
  14. */
  15. Object getObject(Object key);
  16. /**
  17. * Optional. It is not called by the core.
  18. *
  19. * @param key The key
  20. * @return The object that was removed
  21. */
  22. Object removeObject(Object key);
  23. /**
  24. * Clears this cache instance
  25. */
  26. void clear();
  27. /**
  28. * Optional. This method is not called by the core.
  29. *
  30. * @return The number of elements stored in the cache (not its capacity).
  31. */
  32. int getSize();
  33. /**
  34. * Optional. As of 3.2.6 this method is no longer called by the core.
  35. *
  36. * Any locking needed by the cache must be provided internally by the cache provider.
  37. *
  38. * @return A ReadWriteLock
  39. */
  40. ReadWriteLock getReadWriteLock();
  41. }
  1. public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
  2. if (closed) throw new ExecutorException("Executor was closed.");
  3. CacheKey cacheKey = new CacheKey();
  4. cacheKey.update(ms.getId());
  5. cacheKey.update(rowBounds.getOffset());
  6. cacheKey.update(rowBounds.getLimit());
  7. cacheKey.update(boundSql.getSql());
  8. List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
  9. TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
  10. for (int i = 0; i < parameterMappings.size(); i++) { // mimic DefaultParameterHandler logic
  11. ParameterMapping parameterMapping = parameterMappings.get(i);
  12. if (parameterMapping.getMode() != ParameterMode.OUT) {
  13. Object value;
  14. String propertyName = parameterMapping.getProperty();
  15. if (boundSql.hasAdditionalParameter(propertyName)) {
  16. value = boundSql.getAdditionalParameter(propertyName);
  17. } else if (parameterObject == null) {
  18. value = null;
  19. } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
  20. value = parameterObject;
  21. } else {
  22. MetaObject metaObject = configuration.newMetaObject(parameterObject);
  23. value = metaObject.getValue(propertyName);
  24. }
  25. cacheKey.update(value);
  26. }
  27. }
  28. return cacheKey;
  29. }

1、<select>标签所在的Mapper的Namespace+<select>标签的id属性

2、RowBounds的offset和limit属性,RowBounds是MyBatis用于处理分页的一个类,offset默认为0,limit默认为Integer.MAX_VALUE

3、<select>标签中定义的sql语句

即只要两次查询满足以上三个条件且没有定义flushCache="true",那么第二次查询会直接从MyBatis一级缓存PerpetualCache中返回数据,而不会走DB。

  1. /**
  2. * @author Clinton Begin
  3. */
  4. public final class MappedStatement {
  5. private String resource;
  6. private Configuration configuration;
  7. private String id;
  8. private Integer fetchSize;
  9. private Integer timeout;
  10. private StatementType statementType;
  11. private ResultSetType resultSetType;
  12. private SqlSource sqlSource;
  13. private Cache cache;
  14. private ParameterMap parameterMap;
  15. private List<ResultMap> resultMaps;
  16. private boolean flushCacheRequired;
  17. private boolean useCache;
  18. private boolean resultOrdered;
  19. private SqlCommandType sqlCommandType;
  20. private KeyGenerator keyGenerator;
  21. private String[] keyProperties;
  22. private String[] keyColumns;
  23. private boolean hasNestedResultMaps;
  24. private String databaseId;
  25. private Log statementLog;
  26. private LanguageDriver lang;
  27. private String[] resultSets;
  28. private MappedStatement() {
  29. // constructor disabled
  30. }
  31. public static class Builder {
  32. private MappedStatement mappedStatement = new MappedStatement();
  33. public Builder(Configuration configuration, String id, SqlSource sqlSource, SqlCommandType sqlCommandType) {
  34. mappedStatement.configuration = configuration;
  35. mappedStatement.id = id;
  36. mappedStatement.sqlSource = sqlSource;
  37. mappedStatement.statementType = StatementType.PREPARED;
  38. mappedStatement.parameterMap = new ParameterMap.Builder(configuration, "defaultParameterMap", null, new ArrayList<ParameterMapping>()).build();
  39. mappedStatement.resultMaps = new ArrayList<ResultMap>();
  40. mappedStatement.timeout = configuration.getDefaultStatementTimeout();
  41. mappedStatement.sqlCommandType = sqlCommandType;
  42. mappedStatement.keyGenerator = configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType) ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
  43. String logId = id;
  44. if (configuration.getLogPrefix() != null) logId = configuration.getLogPrefix() + id;
  45. mappedStatement.statementLog = LogFactory.getLog(logId);
  46. mappedStatement.lang = configuration.getDefaultScriptingLanuageInstance();
  47. }
  48. public Builder resource(String resource) {
  49. mappedStatement.resource = resource;
  50. return this;
  51. }
  52. public String id() {
  53. return mappedStatement.id;
  54. }
  55. public Builder parameterMap(ParameterMap parameterMap) {
  56. mappedStatement.parameterMap = parameterMap;
  57. return this;
  58. }
  59. public Builder resultMaps(List<ResultMap> resultMaps) {
  60. mappedStatement.resultMaps = resultMaps;
  61. for (ResultMap resultMap : resultMaps) {
  62. mappedStatement.hasNestedResultMaps = mappedStatement.hasNestedResultMaps || resultMap.hasNestedResultMaps();
  63. }
  64. return this;
  65. }
  66. public Builder fetchSize(Integer fetchSize) {
  67. mappedStatement.fetchSize = fetchSize;
  68. return this;
  69. }
  70. public Builder timeout(Integer timeout) {
  71. mappedStatement.timeout = timeout;
  72. return this;
  73. }
  74. public Builder statementType(StatementType statementType) {
  75. mappedStatement.statementType = statementType;
  76. return this;
  77. }
  78. public Builder resultSetType(ResultSetType resultSetType) {
  79. mappedStatement.resultSetType = resultSetType;
  80. return this;
  81. }
  82. public Builder cache(Cache cache) {
  83. mappedStatement.cache = cache;
  84. return this;
  85. }
  86. public Builder flushCacheRequired(boolean flushCacheRequired) {
  87. mappedStatement.flushCacheRequired = flushCacheRequired;
  88. return this;
  89. }
  90. public Builder useCache(boolean useCache) {
  91. mappedStatement.useCache = useCache;
  92. return this;
  93. }
  94. public Builder resultOrdered(boolean resultOrdered) {
  95. mappedStatement.resultOrdered = resultOrdered;
  96. return this;
  97. }
  98. public Builder keyGenerator(KeyGenerator keyGenerator) {
  99. mappedStatement.keyGenerator = keyGenerator;
  100. return this;
  101. }
  102. public Builder keyProperty(String keyProperty) {
  103. mappedStatement.keyProperties = delimitedStringtoArray(keyProperty);
  104. return this;
  105. }
  106. public Builder keyColumn(String keyColumn) {
  107. mappedStatement.keyColumns = delimitedStringtoArray(keyColumn);
  108. return this;
  109. }
  110. public Builder databaseId(String databaseId) {
  111. mappedStatement.databaseId = databaseId;
  112. return this;
  113. }
  114. public Builder lang(LanguageDriver driver) {
  115. mappedStatement.lang = driver;
  116. return this;
  117. }
  118. public Builder resulSets(String resultSet) {
  119. mappedStatement.resultSets = delimitedStringtoArray(resultSet);
  120. return this;
  121. }
  122. public MappedStatement build() {
  123. assert mappedStatement.configuration != null;
  124. assert mappedStatement.id != null;
  125. assert mappedStatement.sqlSource != null;
  126. assert mappedStatement.lang != null;
  127. mappedStatement.resultMaps = Collections.unmodifiableList(mappedStatement.resultMaps);
  128. return mappedStatement;
  129. }
  130. }
  131. public boolean hasNestedResultMaps() {
  132. return hasNestedResultMaps;
  133. }
  134. public BoundSql getBoundSql(Object parameterObject) {
  135. BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
  136. List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
  137. if (parameterMappings == null || parameterMappings.size() <= 0) {
  138. boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
  139. }
  140. // check for nested result maps in parameter mappings (issue #30)
  141. for (ParameterMapping pm : boundSql.getParameterMappings()) {
  142. String rmId = pm.getResultMapId();
  143. if (rmId != null) {
  144. ResultMap rm = configuration.getResultMap(rmId);
  145. if (rm != null) {
  146. hasNestedResultMaps |= rm.hasNestedResultMaps();
  147. }
  148. }
  149. }
  150. return boundSql;
  151. }
  152. private static String[] delimitedStringtoArray(String in) {
  153. if (in == null || in.trim().length() == 0) {
  154. return null;
  155. } else {
  156. String[] answer = in.split(",");
  157. return answer;
  158. }
  159. }
  160. }

一二级缓存之间的关系 

  1. public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
  2. executorType = executorType == null ? defaultExecutorType : executorType;
  3. executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
  4. Executor executor;
  5. if (ExecutorType.BATCH == executorType) {
  6. executor = new BatchExecutor(this, transaction);
  7. } else if (ExecutorType.REUSE == executorType) {
  8. executor = new ReuseExecutor(this, transaction);
  9. } else {
  10. executor = new SimpleExecutor(this, transaction);
  11. }
  12. // 如果开启二级缓存会使用CachingExecutor对原Executor进行包装
  13. if (cacheEnabled) {
  14. executor = new CachingExecutor(executor);
  15. }
  16. // Mybatis插件功能对Executor的增强
  17. executor = (Executor) interceptorChain.pluginAll(executor);
  18. return executor;
  19. }

MyBatis缓存

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/盐析白兔/article/detail/159831
推荐阅读
相关标签
  

闽ICP备14008679号