本文首发于个人公众号 Java 技术大杂烩,欢迎关注
Mybatis 解析 SQL 源码分析二
Mybatis Mapper.xml 配置文件中 resultMap 节点的源码解析
Mybatis 解析 SQL 源码分析一
Mybatis Mapper 接口源码解析
Mybatis 数据库连接池源码解析
Mybatis 类型转换源码分析
Mybatis 解析配置文件的源码解析
前言在使用诸如 Mybatis 这种 ORM 框架的时候,一般都会提供缓存功能,用来缓存从数据库查询到的结果,当下一次查询条件相同的时候,只需从缓存中进行查找返回即可,如果缓存中没有,再去查库;一方面是提高查询速度,另一方面是减少数据库压力;Mybatis 也提供了缓存,它分为一级缓存和二级缓存,接下来就来看看它的缓存系统是如何实现的。
缓存系统的实现使用了 模板方法模式 和 装饰器模式
接下来先来看下和缓存相关的接口
CacheMybatis 使用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
PerpetualCacheMybatis 为 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 关键字。
CacheKeyMybatis 的缓存使用了 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 中实现的。
ExecutorExecutor 接口定义了操作数据库的基本方法,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}一级缓存BaseExecutorBaseExecutor 是一个抽象类,实现了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 个:
mybatis-config.xml 配置文件中的 cacheEnabled 配置,它是二级缓存的总开关,只有该配置为 true ,后面的缓存配置才会生效。默认为 true,即二级缓存默认是开启的。Mapper.xml 配置文件中配置的 <cache> 和 <cache-ref>标签,如果 Mapper.xml 配置文件中配置了这两个标签中的任何一个,则表示开启了二级缓存的功能,在 Mybatis 解析 SQL 源码分析一 文章中已经分析过,如果配置了 <cache> 标签,则在解析配置文件的时候,会为该配置文件指定的 namespace 创建相应的 Cache 对象作为其二级缓存(默认为 PerpetualCache 对象),如果配置了 <cache-ref> 节点,则通过 ref 属性的namespace值引用别的Cache对象作为其二级缓存。通过 <cache> 和 <cache-ref> 标签来管理其在 namespace 中二级缓存功能的开启和关闭<select> 节点中的 useCache 属性也可以开启二级缓存,该属性表示查询的结果是否要存入到二级缓存中,该属性默认为 true,也就是说 <select> 标签默认会把查询结果放入到二级缓存中。Mybatis 的二级缓存是用 CachingExecutor 来实现的,它是 Executor 的一个装饰器类。为 Executor 对象添加了缓存的功能。
在介绍 CachingExecutor 之前,先来看看 CachingExecutor 依赖的两个类,TransactionalCacheManager 和 TransactionalCache。
TransactionalCacheTransactionalCache 实现了 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 }TransactionalCacheManagerTransactionalCacheManager 用于管理 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 的二级缓存的主要实现过程,CachingExecutor , TransactionalCacheManager 和 TransactionalCache 的关系如下所示,主要是通过 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技术大杂烩
微信扫一扫打赏
支付宝扫一扫打赏