专栏首页Java技术大杂烩Mybatis 缓存系统源码解析

Mybatis 缓存系统源码解析

本文首发于个人公众号 Java 技术大杂烩,欢迎关注

Mybatis 解析 SQL 源码分析二

Mybatis Mapper.xml 配置文件中 resultMap 节点的源码解析

Mybatis 解析 SQL 源码分析一

Mybatis Mapper 接口源码解析

Mybatis 数据库连接池源码解析

Mybatis 类型转换源码分析

Mybatis 解析配置文件的源码解析

前言

在使用诸如 Mybatis 这种 ORM 框架的时候,一般都会提供缓存功能,用来缓存从数据库查询到的结果,当下一次查询条件相同的时候,只需从缓存中进行查找返回即可,如果缓存中没有,再去查库;一方面是提高查询速度,另一方面是减少数据库压力;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 Map<Object, Object> cache = new HashMap<Object, 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 List<Object> 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    List<ParameterMapping> 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> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;
10
11  // 查询,带分页 
12  <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;
13
14  // 查询存储过程
15  <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException;
16
17  //刷新批处理语句
18  List<BatchResult> 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 ConcurrentLinkedQueue<DeferredLoad> 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 ConcurrentLinkedQueue<DeferredLoad>();
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 List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException;
30  protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)throws SQLException;
31  protected abstract <E> Cursor<E> 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 List<BatchResult> flushStatements() throws SQLException {
45    return flushStatements(false);
46  }
47  public List<BatchResult> flushStatements(boolean isRollBack) throws SQLException {
48    // ...
49    // doFlushStatements 的 isRollBack 参数表示是否执行缓存中的SQL语句,false表示执行,true表示不执行
50    return doFlushStatements(isRollBack);
51  }
52
53  // 查询存储过程
54  @Override
55  public <E> Cursor<E> 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> List<E> 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> List<E> 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    List<E> list;
19    try {
20      // 嵌套查询层数加1
21      queryStack++;
22      // 首先从一级缓存中进行查询
23      list = resultHandler == null ? (List<E>) 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> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
40    List<E> 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 个:

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

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

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

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 Map<Object, Object> entriesToAddOnCommit;
 8  // 存放为命中缓存的 CacheKey 对象
 9  private Set<Object> entriesMissedInCache;
10
11  public TransactionalCache(Cache delegate) {
12    this.delegate = delegate;
13    this.clearOnCommit = false;
14    this.entriesToAddOnCommit = new HashMap<Object, Object>();
15    this.entriesMissedInCache = new HashSet<Object>();
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.Entry<Object, 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 Map<Cache, TransactionalCache> transactionalCaches = new HashMap<Cache, 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> List<E> 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> List<E> 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        List<E> list = (List<E>) 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 的二级缓存的主要实现过程,CachingExecutorTransactionalCacheManagerTransactionalCache 的关系如下所示,主要是通过 TransactionalCache 来操作二级缓存的。

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

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

Cache 装饰器

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

来看看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技术大杂烩(tsmyk0715),作者:TSMYK

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-03-18

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 从零搭建 Vue 开发环境

    由于最近公司需要做H5页面,然后嵌入到微信公众号中去,从公众号菜单点击进入H5页面进行操作,需要使用 Vue 来做。之前由于部门中没有使用 Vue 做过任何下项...

    Java技术大杂烩
  • Spring AOP 功能使用详解

    AOP 既熟悉又陌生,了解过 Spring 人的都知道 AOP 的概念,即面向切面编程,可以用来管理一些和主业务无关的周边业务,如日志记录,事务管理等;陌生是因...

    Java技术大杂烩
  • Ribbon 负载均衡器 LoadBalancer 源码解析

    什么是负载均衡?简单来说一个应用,后台有多个服务来支撑,即利用多台服务器提供单一服务,当某个服务挂了或者负载过高的时候,负载均衡器能够选择其他的服务来处理请求,...

    Java技术大杂烩
  • 「性能提升」扩展 Spring Cache 支持多级缓存

    综合所述:我们需要构建 L1 Caffeine JVM 级别缓存 , L2 Redis 缓存。

    冷冷
  • Guava -- 集合类 和 Guava Cache

    Guava 是 google 推出的一个第三方 java 库,用来代替 jdk 的一些公共操作,给我印象特别深的就是 Collection 的扩展和本地缓存的扩...

    希希里之海
  • 聊聊MyBatis缓存机制

    前言 MyBatis是常见的Java数据库访问层框架。在日常工作中,开发人员多数情况下是使用MyBatis的默认缓存配置,但是MyBatis缓存机制有一些不足之...

    美团技术团队
  • HTTP之缓存控制

    http://www.nirsoft.net/utils/chrome_cache_view.html

    Ashen
  • 分布式之数据库和缓存双写一致性方案解析!

    首先,缓存由于其高并发和高性能的特性,已经在项目中被广泛使用。在读取缓存方面,大家没啥疑问,都是按照下图的流程来进行业务操作:

    Java后端技术
  • 分布式之数据库和缓存双写一致性方案解析

    Tanyboye
  • 缓存的正确使用方式,你都会了吗?

    首先,缓存由于其适应高并发和高性能的特性,已经在项目中被广泛使用。在读取缓存方面,大家没啥疑问,都是按照下图的流程来进行业务操作。

    黄泽杰

扫码关注云+社区

领取腾讯云代金券