Mybatis 缓存系统源码解析

本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

原文链接:blog.ouyangsihai.cn >> Mybatis 缓存系统源码解析

  1. 前言
  2. 缓存的相关接口
  3. 一级缓存的实现过程
  4. 二级缓存的实现过程
  5. 如何保证缓存的线程安全
  6. 缓存的装饰器

二级缓存的实现过程

前言

在使用诸如 Mybatis 这种 ORM 框架的时候,一般都会提供缓存功能,用来缓存从数据库查询到的结果,当下一次查询条件相同的时候,只需从缓存中进行查找返回即可,如果缓存中没有,再去查库;一方面是提高查询速度,另一方面是减少数据库压力; Mybatis 也提供了缓存,它分为 一级缓存 二级缓存,接下来就来看看它的缓存系统是如何实现的。

缓存系统的实现使用了   模板方法模式**** 和 装饰器模式

接下来先来看下和缓存相关的接口

Mybatis 缓存系统源码解析

Cache

Mybatis 使用 Cache 来表示缓存,它是一个接口,定义了缓存需要的一些方法,如下所示:


 1public interface Cache {
 2  //获取缓存的id,即 namespace
 3  String getId();
 4  // 添加缓存
 5  void putObject(Object key, Object value);
 6  //根据key来获取缓存对应的值
 7  Object getObject(Object key);
 8  // 删除key对应的缓存
 9  Object removeObject(Object key);
10  // 清空缓存  
11  void clear();
12  // 获取缓存中数据的大小
13  int getSize();
14  //取得读写锁, 从3.2.6开始没用了
15  ReadWriteLock getReadWriteLock();
16}

对于每一个 namespace 都会创建一个缓存的实例, Cache 实现类的构造方法都必须传入一个 String 类型的 ID,Mybatis自身的实现类都使用 namespace 作为 ID

PerpetualCache

Mybatis 为 Cache 接口提供的唯一一个实现类就是 PerpetualCache,这个唯一并不是说 Cache 只有一个实现类,只是缓存的处理逻辑,Cache 还有其他的实现类,但是只是作为装饰器存在,只是对 Cache 进行包装而已。

PerpetualCache 的实现比较简单,就是把对应的 key-value 缓存数据存入到 map 中,如下所示:


 1public class PerpetualCache implements Cache {
 2  // id,一般对应mapper.xml 的namespace 的值
 3  private String id;
 4
 5  // 用来存放数据,即缓存底层就是使用 map 来实现的
 6  private MapObject, Object cache = new HashMapObject, Object();
 7
 8  public PerpetualCache(String id) {
 9    this.id = id;
10  }
11  //......其他的getter方法.....
12  // 添加缓存
13  @Override
14  public void putObject(Object key, Object value) {
15    cache.put(key, value);
16  }
17  // 获取缓存
18  @Override
19  public Object getObject(Object key) {
20    return cache.get(key);
21  }
22  // 删除缓存
23  @Override
24  public Object removeObject(Object key) {
25    return cache.remove(key);
26  }
27  // 清空缓存
28  @Override
29  public void clear() {
30    cache.clear();
31  }
32}

从上面的代码逻辑可以看到,mybatis 提供的缓存底层就是使用一个 HashMap 来实现的,但是我们知道, HashMap 不是线程安全的,它是如何来保证缓存中的线程安全问题呢? 在后面讲到 Cache 的包装类就知道,它提供了一个 SynchronizedCache 的装饰器类,就是用来包装线程安全的,在该类中所有方法都加上了 synchronized 关键字。

CacheKey

Mybatis 的缓存使用了 key-value 的形式存入到 HashMap 中,而 key 的话,Mybatis 使用了 CacheKey 来表示 key,它的生成规则为: mappedStementId + offset + limit + SQL + queryParams + environment 生成一个哈希码.


 1public class CacheKey implements Cloneable, Serializable {
 2
 3  private static final int DEFAULT_MULTIPLYER = 37;
 4  private static final int DEFAULT_HASHCODE = 17;
 5
 6  // 参与计算hashcode,默认值为37
 7  private int multiplier;
 8  // CacheKey 对象的 hashcode ,默认值 17
 9  private int hashcode;
10  // 检验和 
11  private long checksum;
12  // updateList 集合的个数
13  private int count;
14  // 由该集合中的所有对象来共同决定两个 CacheKey 是否相等
15  private ListObject updateList;
16
17  public int getUpdateCount() {
18    return updateList.size();
19  }
20  // 调用该方法,向 updateList 集合添加对应的对象
21  public void update(Object object) {
22    if (object != null && object.getClass().isArray()) {
23      // 如果是数组,则循环处理每一项
24      int length = Array.getLength(object);
25      for (int i = 0; i  length; i++) {
26        Object element = Array.get(object, i);
27        doUpdate(element);
28      }
29    } else {
30      doUpdate(object);
31    }
32  }
33  // 计算 count checksum hashcode 和把对象添加到 updateList 集合中
34  private void doUpdate(Object object) {
35    int baseHashCode = object == null ? 1 : object.hashCode();
36    count++;
37    checksum += baseHashCode;
38    baseHashCode *= count;
39    hashcode = multiplier * hashcode + baseHashCode;
40
41    updateList.add(object);
42  }
43
44  // 判断两个 CacheKey 是否相等
45  @Override
46  public boolean equals(Object object) {
47    if (this == object) {
48      return true;
49    }
50    if (!(object instanceof CacheKey)) {
51      return false;
52    }
53
54    final CacheKey cacheKey = (CacheKey) object;
55
56    if (hashcode != cacheKey.hashcode) {
57      return false;
58    }
59    if (checksum != cacheKey.checksum) {
60      return false;
61    }
62    if (count != cacheKey.count) {
63      return false;
64    }
65    // 如果前几项都不满足,则循环遍历 updateList 集合,判断每一项是否相等,如果有一项不相等则这两个CacheKey不相等
66    for (int i = 0; i  updateList.size(); i++) {
67      Object thisObject = updateList.get(i);
68      Object thatObject = cacheKey.updateList.get(i);
69      if (thisObject == null) {
70        if (thatObject != null) {
71          return false;
72        }
73      } else {
74        if (!thisObject.equals(thatObject)) {
75          return false;
76        }
77      }
78    }
79    return true;
80  }
81
82  @Override
83  public int hashCode() {
84    return hashcode;
85  }
86}

如果需要进行缓存,则如何创建 CacheKey 呢?下面这个就是创建 一个 CacheKey 的方法:


 1  public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
 2    //cacheKey 对象 
 3    CacheKey cacheKey = new CacheKey();
 4    // 向 updateList 存入id
 5    cacheKey.update(ms.getId());
 6    // 存入offset
 7    cacheKey.update(rowBounds.getOffset());
 8    // 存入limit
 9    cacheKey.update(rowBounds.getLimit());
10    // 存入sql
11    cacheKey.update(boundSql.getSql());
12    ListParameterMapping parameterMappings = boundSql.getParameterMappings();
13    TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
14    for (ParameterMapping parameterMapping : parameterMappings) {
15      if (parameterMapping.getMode() != ParameterMode.OUT) {
16          String propertyName = parameterMapping.getProperty();
17          MetaObject metaObject = configuration.newMetaObject(parameterObject);
18          Object  value = metaObject.getValue(propertyName);
19          // 存入每一个参数
20          cacheKey.update(value);
21      }
22    }
23    if (configuration.getEnvironment() != null) {
24      // 存入 environmentId
25      cacheKey.update(configuration.getEnvironment().getId());
26    }
27    return cacheKey;
28  }

从上面 CacheKey 和创建 CacheKey 的代码逻辑可以看出,Mybatis 的缓存使用了 mappedStementId + offset + limit + SQL + queryParams + environment 生成的hashcode作为 key。

了解了上述和缓存相关的接口后,接下来就来看看 Mybatis 的缓存系统是如何实现的,Mybatis 的缓存分为 一级缓存 二级缓存 一级缓存是在 BaseExecutor 中实现的, 二级缓存是在 CachingExecutor 中实现的。

Executor

Executor 接口定义了操作数据库的基本方法,SqlSession 的相关方法就是基于 Executor 接口实现的,它定义了操作数据库的方法如下:


 1public interface Executor {
 2
 3  ResultHandler NO_RESULT_HANDLER = null;
 4
 5  // insert | update | delete 的操作方法
 6  int update(MappedStatement ms, Object parameter) throws SQLException;
 7
 8  // 查询,带分页,带缓存  
 9  E ListE query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;
10
11  // 查询,带分页 
12  E ListE query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;
13
14  // 查询存储过程
15  E CursorE queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException;
16
17  //刷新批处理语句
18  ListBatchResult flushStatements() throws SQLException;
19
20  // 事务提交
21  void commit(boolean required) throws SQLException;
22  // 事务回滚
23  void rollback(boolean required) throws SQLException;
24
25  // 创建缓存的key
26  CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql);
27  // 是否缓存
28  boolean isCached(MappedStatement ms, CacheKey key);
29  // 清空缓存
30  void clearLocalCache();
31  // 延迟加载
32  void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class? targetType);
33  // 获取事务
34  Transaction getTransaction();
35}

一级缓存

BaseExecutor

BaseExecutor 是一个抽象类,实现了 Executor 接口,并提供了大部分方法的实现,只有 4 个基本方法: doUpdate, doQuery,   doQueryCursor,   doFlushStatement 没有实现,还是一个抽象方法,由子类实现,这 4 个方法相当于模板方法中变化的那部分。

Mybatis 的一级缓存就是在该类中实现的。

Mybatis 的一级缓存是会话级别的缓存,Mybatis 每创建一个 SqlSession 对象,就表示打开一次数据库会话,在一次会话中,应用程序很可能在短时间内反复执行相同的查询语句,如果不对数据进行缓存,则每查询一次就要执行一次数据库查询,这就造成数据库资源的浪费。又因为通过 SqlSession 执行的操作,实际上由 Executor 来完成数据库操作的,所以在 Executor 中会建立一个简单的缓存,即一级缓存;将每次的查询结果缓存起来,再次执行查询的时候,会先查询一级缓存,如果命中,则直接返回,否则再去查询数据库并放入缓存中。

一级缓存的生命周期与 SqlSession 的生命周期相同,当调用 Executor.close 方法的时候,缓存变得不可用。一级缓存是默认开启的,一般情况下不需要特殊的配置,如果需要特殊配置,则可以通过插件的形式来实现


 1public abstract class BaseExecutor implements Executor {
 2  // 事务,提交,回滚,关闭事务
 3  protected Transaction transaction;
 4  // 底层的 Executor 对象
 5  protected Executor wrapper;
 6  // 延迟加载队列
 7  protected ConcurrentLinkedQueueDeferredLoad deferredLoads;
 8  // 一级缓存,用于缓存查询结果
 9  protected PerpetualCache localCache;
10  // 一级缓存,用于缓存输出类型参数(存储过程)
11  protected PerpetualCache localOutputParameterCache;
12  protected Configuration configuration;
13  // 用来记录嵌套查询的层数
14  protected int queryStack;
15  private boolean closed;
16
17  protected BaseExecutor(Configuration configuration, Transaction transaction) {
18    this.transaction = transaction;
19    this.deferredLoads = new ConcurrentLinkedQueueDeferredLoad();
20    this.localCache = new PerpetualCache("LocalCache");
21    this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");
22    this.closed = false;
23    this.configuration = configuration;
24    this.wrapper = this;
25  }
26
27// 4 个抽象方法,由子类实现,模板方法中可变部分
28  protected abstract int doUpdate(MappedStatement ms, Object parameter)throws SQLException;
29  protected abstract ListBatchResult doFlushStatements(boolean isRollback) throws SQLException;
30  protected abstract E ListE doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)throws SQLException;
31  protected abstract E CursorE doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql)throws SQLException;
32
33  // 执行 insert | update | delete 语句,调用 doUpdate 方法实现,在执行这些语句的时候,会清空缓存
34  public int update(MappedStatement ms, Object parameter) throws SQLException {
35    // ....
36    // 清空缓存
37    clearLocalCache();
38    // 执行SQL语句
39    return doUpdate(ms, parameter);
40  }
41
42  // 刷新批处理语句,且执行缓存中还没执行的SQL语句
43  @Override
44  public ListBatchResult flushStatements() throws SQLException {
45    return flushStatements(false);
46  }
47  public ListBatchResult flushStatements(boolean isRollBack) throws SQLException {
48    // ...
49    // doFlushStatements 的 isRollBack 参数表示是否执行缓存中的SQL语句,false表示执行,true表示不执行
50    return doFlushStatements(isRollBack);
51  }
52
53  // 查询存储过程
54  @Override
55  public E CursorE queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException {
56    BoundSql boundSql = ms.getBoundSql(parameter);
57    return doQueryCursor(ms, parameter, rowBounds, boundSql);
58  }
59
60  // 事务的提交和回滚
61  @Override
62  public void commit(boolean required) throws SQLException {
63    // 清空缓存
64    clearLocalCache();
65    // 刷新批处理语句,且执行缓存中的QL语句
66    flushStatements();
67    if (required) {
68      transaction.commit();
69    }
70  }
71  @Override
72  public void rollback(boolean required) throws SQLException {
73    if (!closed) {
74      try {
75        // 清空缓存
76        clearLocalCache();
77        // 刷新批处理语句,且不执行缓存中的SQL
78        flushStatements(true);
79      } finally {
80        if (required) {
81          transaction.rollback();
82        }
83      }
84    }
85  }

在上面的代码逻辑中,执行 update类型的语句会清空缓存,且执行结果不需要进行缓存,而在执行查询语句的时候,需要对数据进行缓存,如下所示:


 1  @Override
 2  public E ListE query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
 3    // 获取查询SQL
 4    BoundSql boundSql = ms.getBoundSql(parameter);
 5    // 创建缓存的key,创建逻辑在 CacheKey中已经分析过了
 6    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
 7    // 执行查询
 8    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
 9 }
10
11  // 执行查询逻辑
12  public E ListE query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
13    // ....
14    if (queryStack == 0 && ms.isFlushCacheRequired()) {
15      // 如果不是嵌套查询,且 select 的 flushCache=true 时才会清空缓存
16      clearLocalCache();
17    }
18    ListE list;
19    try {
20      // 嵌套查询层数加1
21      queryStack++;
22      // 首先从一级缓存中进行查询
23      list = resultHandler == null ? (ListE) localCache.getObject(key) : null;
24      if (list != null) {
25        // 如果命中缓存,则处理存储过程
26        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
27      } else {
28        // 如果缓存中没有对应的数据,则查数据库中查询数据
29        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
30      }
31    } finally {
32      queryStack--;
33    }
34    // ... 处理延迟加载的相关逻辑
35    return list;
36  }
37
38  // 从数据库查询数据
39  private E ListE queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
40    ListE list;
41    // 在缓存中添加占位符
42    localCache.putObject(key, EXECUTION_PLACEHOLDER);
43    try {
44      // 查库操作,由子类实现
45      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
46    } finally {
47      // 删除占位符
48      localCache.removeObject(key);
49    }
50    // 将从数据库查询的结果添加到一级缓存中
51    localCache.putObject(key, list);
52    // 处理存储过程
53    if (ms.getStatementType() == StatementType.CALLABLE) {
54      localOutputParameterCache.putObject(key, parameter);
55    }
56    return list;
57  }

二级缓存

Mybatis 提供的二级缓存是应用级别的缓存,它的生命周期和应用程序的生命周期相同,且与二级缓存相关的配置有以下 3 个:

  • mybatis-config.xml 配置文件中的 cacheEnabled 配置,它是二级缓存的总开关,只有该配置为 true ,后面的缓存配置才会生效。默认为 true,即二级缓存默认是开启的。
  • Mapper.xml 配置文件中配置的 cache 和 cache-ref标签,如果 Mapper.xml 配置文件中配置了这两个标签中的任何一个,则表示开启了二级缓存的功能,在    文章中已经分析过,如果配置了 cache 标签,则在解析配置文件的时候,会为该配置文件指定的 namespace 创建相应的 Cache 对象作为其二级缓存(默认为 PerpetualCache 对象),如果配置了 cache-ref 节点,则通过 ref 属性的namespace值引用别的Cache对象作为其二级缓存。通过 cache 和 cache-ref 标签来管理其在 namespace 中二级缓存功能的开启和关闭
  • select 节点中的 useCache 属性也可以开启二级缓存,该属性表示查询的结果是否要存入到二级缓存中,该属性默认为 true,也就是说 select 标签默认会把查询结果放入到二级缓存中。
  • Mybatis 缓存系统源码解析

    Mybatis 的二级缓存是用 CachingExecutor 来实现的,它是 Executor 的一个装饰器类。为 Executor 对象添加了缓存的功能。

    在介绍 CachingExecutor 之前,先来看看 CachingExecutor 依赖的两个类, TransactionalCacheManager TransactionalCache

    TransactionalCache

    TransactionalCache 实现了 Cache 接口,主要用于保存在某个 SqlSession 的某个事务中需要向某个二级缓存中添加的数据,代码如下:

    
     1public class TransactionalCache implements Cache {
     2  // 底层封装的二级缓存对应的Cache对象
     3  private Cache delegate;
     4  // 为true时,表示当前的 TransactionalCache 不可查询,且提交事务时会清空缓存
     5  private boolean clearOnCommit;
     6  // 存放需要添加到二级缓存中的数据
     7  private MapObject, Object entriesToAddOnCommit;
     8  // 存放为命中缓存的 CacheKey 对象
     9  private SetObject entriesMissedInCache;
    10
    11  public TransactionalCache(Cache delegate) {
    12    this.delegate = delegate;
    13    this.clearOnCommit = false;
    14    this.entriesToAddOnCommit = new HashMapObject, Object();
    15    this.entriesMissedInCache = new HashSetObject();
    16  }
    17
    18  // 添加缓存数据的时候,先暂时放到 entriesToAddOnCommit 集合中,在事务提交的时候,再把数据放入到二级缓存中,避免脏数据
    19  @Override
    20  public void putObject(Object key, Object object) {
    21    entriesToAddOnCommit.put(key, object);
    22  }
    23  // 提交事务,
    24  public void commit() {
    25    if (clearOnCommit) {
    26      delegate.clear();
    27    }
    28    // 把 entriesToAddOnCommit  集合中的数据放入到二级缓存中
    29    flushPendingEntries();
    30    reset();
    31  }
    32 // 把 entriesToAddOnCommit  集合中的数据放入到二级缓存中
    33  private void flushPendingEntries() {
    34    for (Map.EntryObject, Object entry : entriesToAddOnCommit.entrySet()) {
    35      // 放入到二级缓存中
    36      delegate.putObject(entry.getKey(), entry.getValue());
    37    }
    38    for (Object entry : entriesMissedInCache) {
    39      if (!entriesToAddOnCommit.containsKey(entry)) {
    40        delegate.putObject(entry, null);
    41      }
    42    }
    43  }
    44 // 事务回滚
    45 public void rollback() {
    46    // 把未命中缓存的数据清除掉
    47    unlockMissedEntries();
    48    reset();
    49  }
    50  private void unlockMissedEntries() {
    51    for (Object entry : entriesMissedInCache) {
    52        delegate.removeObject(entry);
    53    }
    54  }
    

    TransactionalCacheManager

    TransactionalCacheManager 用于管理 CachingExecutor 使用的二级缓存:

    
     1public class TransactionalCacheManager {
     2
     3  //用来管理 CachingExecutor 使用的二级缓存
     4  // key 为对应的CachingExecutor 使用的二级缓存
     5  // value 为对应的 TransactionalCache 对象
     6  private MapCache, TransactionalCache transactionalCaches = new HashMapCache, TransactionalCache();
     7
     8  public void clear(Cache cache) {
     9    getTransactionalCache(cache).clear();
    10  }
    11  public Object getObject(Cache cache, CacheKey key) {
    12    return getTransactionalCache(cache).getObject(key);
    13  }  
    14  public void putObject(Cache cache, CacheKey key, Object value) {
    15    getTransactionalCache(cache).putObject(key, value);
    16  }
    17  public void commit() {
    18    for (TransactionalCache txCache : transactionalCaches.values()) {
    19      txCache.commit();
    20    }
    21  }
    22  public void rollback() {
    23    for (TransactionalCache txCache : transactionalCaches.values()) {
    24      txCache.rollback();
    25    }
    26  }
    27  // 所有的调用都会调用 TransactionalCache 的方法来实现
    28  private TransactionalCache getTransactionalCache(Cache cache) {
    29    TransactionalCache txCache = transactionalCaches.get(cache);
    30    if (txCache == null) {
    31      txCache = new TransactionalCache(cache);
    32      transactionalCaches.put(cache, txCache);
    33    }
    34    return txCache;
    35  }
    36}
    

    CachingExecutor

    接下来看下 二级缓存的实现 CachingExecutor

    
     1public class CachingExecutor implements Executor {
     2  // 底层的 Executor
     3  private Executor delegate;
     4  private TransactionalCacheManager tcm = new TransactionalCacheManager();
     5
     6  // 查询方法
     7  @Override
     8  public E ListE query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
     9    // 获取 SQL
    10    BoundSql boundSql = ms.getBoundSql(parameterObject);
    11    // 创建缓存key,在CacheKey中已经分析过创建过程
    12    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    13    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    14  }
    15
    16  // 查询
    17  public E ListE query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
    18      throws SQLException {
    19    // 获取查询语句所在namespace对应的二级缓存
    20    Cache cache = ms.getCache();
    21    // 是否开启了二级缓存
    22    if (cache != null) {
    23      // 根据 select 的属性 useCache 的配置,决定是否需要清空二级缓存
    24      flushCacheIfRequired(ms);
    25      if (ms.isUseCache() && resultHandler == null) {
    26        // 二级缓存不能保存输出参数,否则抛异常
    27        ensureNoOutParams(ms, parameterObject, boundSql);
    28        // 从二级缓存中查询对应的值
    29        ListE list = (ListE) tcm.getObject(cache, key);
    30        if (list == null) {
    31          // 如果二级缓存没有命中,则调用底层的 Executor 查询,其中会先查询一级缓存,一级缓存也未命中,才会去查询数据库
    32          list = delegate.E query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    33          // 查询到的数据放入到二级缓存中去
    34          tcm.putObject(cache, key, list); // issue #578 and #116
    35        }
    36        return list;
    37      }
    38    }
    39    // 如果没有开启二级缓存,则直接调用底层的 Executor 查询,还是会先查一级缓存
    40    return delegate.E query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    41  }
    

    以上就是 Mybatis 的二级缓存的主要实现过程, CachingExecutor TransactionalCacheManager TransactionalCache 的关系如下所示,主要是通过 TransactionalCache 来操作二级缓存的。

    Mybatis 缓存系统源码解析

    此外, CachingExecutor 还有其他的一些方法,主要是调用底层封装的 Executor 来实现的。

    以上就是 Mybatis 的一级缓存和二级缓存的实现过程。

    Cache 装饰器

    在介绍 Cache 接口的时候,说到,Cache 接口由很多的装饰器类,共 10 个,添加了不同的功能,如下所示:

    Mybatis 缓存系统源码解析

    来看看 SynchronizedCache装饰器类吧,在上面的缓存实现中介绍到了 Mybatis 其实就是使用 HashMap 来实现缓存的,即把数据放入到 HashMap中,但是 HashMap不是线安全的,Mybatis 是如何来保证缓存中的线程安全问题呢?就是使用了 SynchronizedCache 来保证的,它是一个装饰器类,其中的方法都加上了 synchronized关键字:

    
     1public class SynchronizedCache implements Cache {
     2
     3  private Cache delegate;
     4
     5  public SynchronizedCache(Cache delegate) {
     6    this.delegate = delegate;
     7  }
     8  @Override
     9  public synchronized int getSize() {
    10    return delegate.getSize();
    11  }
    12  @Override
    13  public synchronized void putObject(Object key, Object object) {
    14    delegate.putObject(key, object);
    15  }
    16  @Override
    17  public synchronized Object getObject(Object key) {
    18    return delegate.getObject(key);
    19  }
    20
    21  @Override
    22  public synchronized Object removeObject(Object key) {
    23    return delegate.removeObject(key);
    24  }
    25  // ............
    26}
    

    接下来看下添加 Cache 装饰器的方法,在 CacheBuilder.build() 方法中进行添加:

    
     1public class CacheBuilder {
     2  //...........
     3  // 创建缓存
     4  public Cache build() {
     5    // 设置缓存的实现类
     6    setDefaultImplementations();
     7    Cache cache = newBaseCacheInstance(implementation, id);
     8    setCacheProperties(cache);
     9    // 添加装饰器类
    10    if (PerpetualCache.class.equals(cache.getClass())) {
    11      for (Class? extends Cache decorator : decorators) {
    12        cache = newCacheDecoratorInstance(decorator, cache);
    13        setCacheProperties(cache);
    14      }
    15      // 为 Cache 添加装饰器
    16      cache = setStandardDecorators(cache);
    17    } else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
    18      cache = new LoggingCache(cache);
    19    }
    20    return cache;
    21  }
    22  // 设置 Cache 的默认实现类为 PerpetualCache
    23  private void setDefaultImplementations() {
    24    if (implementation == null) {
    25      implementation = PerpetualCache.class;
    26      if (decorators.isEmpty()) {
    27        decorators.add(LruCache.class);
    28      }
    29    }
    30  }
    31  // 添加装饰器
    32  private Cache setStandardDecorators(Cache cache) {
    33    try {
    34      // 添加 ScheduledCache 装饰器
    35      if (clearInterval != null) {
    36        cache = new ScheduledCache(cache);
    37        ((ScheduledCache) cache).setClearInterval(clearInterval);
    38      }
    39      // 添加SerializedCache装饰器
    40      if (readWrite) {
    41        cache = new SerializedCache(cache);
    42      }
    43      // 添加 LoggingCache 装饰器
    44      cache = new LoggingCache(cache);
    45      // 添加  SynchronizedCache 装饰器,保证线程安全
    46      cache = new SynchronizedCache(cache);
    47      if (blocking) {
    48        // 添加 BlockingCache 装饰器
    49        cache = new BlockingCache(cache);
    50      }
    51      return cache;
    52  }
    53}
    

    还有其他的装饰器,这里就不一一列出来了。

    到这里 Mybatis 的缓存系统模块就分析完毕了。

    原文始发于微信公众号(Java技术大杂烩):

    本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

    本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

    原文链接:blog.ouyangsihai.cn >> Mybatis 缓存系统源码解析


     上一篇
    Mybatis 解析 SQL 源码分析二 Mybatis 解析 SQL 源码分析二
    前言在上两篇文章   和    中分析了 Mybatis 是如何解析 Mapper.xml 配置文件的,配置文件中配置的 SQL 节点被解析成了一个个的 MappedStatement 对象放到了全局的配置对象 Configuration
    2021-04-05
    下一篇 
    MyBatis框架及原理分析 MyBatis框架及原理分析
    作者:luoxn28 cnblogs.com/luoxn28/p/6417892.html MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的持久层框架,其主要就完成2件事情: 封装JDBC操作
    2021-04-05