假装读源码Mybatis(一):缓存原理

     Mybatis作为一个强大的ORM框架,也提供了数据的缓存功能,缓存的架构分为两层:一级缓存和二级缓存。

     一级缓存是会话级别的缓存,当SqlSessionFactory创建一个SqlSession对象时,就表示开启了一个数据库会话,同时也开启了这个会话的一级缓存。那么为什么要需要会话级别的缓存呢。在一个sqlSession中会存在多次的查询。相同的sql查询在应用一级缓存中有较高的效率。所以当sqlSession关闭时,一级缓存就会清空。

    二级缓存是MapperStatement级别的缓存,也就是一个namespace一个二级缓存,而且二级缓存是通过CachingExecutor实现的。它的生命周期与应用程序的生命周期相同。

    如果一级和二级的缓存都开启,那么sql先查询二级缓存,如果二级缓存没有命中,那么会去查询一级缓存。

假装读源码Mybatis(一):缓存原理_第1张图片

 

按照sql的执行顺序来分析缓存:

 

二级缓存

   二级缓存的执行是由CachingExecutor来执行的,那么CachingExecutor是怎么来的呢?

  在DefaultSqlSessionFactory构建DefaultSqlSession时

  public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    if (ExecutorType.BATCH == executorType) {
      executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
      executor = new ReuseExecutor(this, transaction);
    } else {
      executor = new SimpleExecutor(this, transaction);
    }
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

当判断二级缓存cacheEnable为true,创建CachingExecutor来注入SqlSession中。

CachingExecutor类介绍:

private final Executor delegate;
//事务缓存管理器
private final TransactionalCacheManager tcm = new TransactionalCacheManager();
TransactionalCacheManager类是用来维护Cache和TransactionalCache之间的映射关系的。在其内部便是维护了
private final Map transactionalCaches = new HashMap();

这样的关系Map。

//分析一个query的
public  List query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
    throws SQLException {
  //从MappedStatement中获取缓存
  Cache cache = ms.getCache();
  if (cache != null) {
  	// 如果配置文件中没有配置 ,则 cache 为空
    flushCacheIfRequired(ms);
    if (ms.isUseCache() && resultHandler == null) {
      ensureNoOutParams(ms, boundSql);
      @SuppressWarnings("unchecked")
      //查找二级缓存
      List list = (List) tcm.getObject(cache, key);
      if (list == null) {
      	//委托给BaseExcetor的子类来执行
        list = delegate. query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
        tcm.putObject(cache, key, list); // issue #578 and #116
      }
      return list;
    }
  }
  return delegate. query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

一个经过二级缓存的查询大致的逻辑。当Mapper配置了并且开启了cacheEnable==true才会开启二级缓存逻辑。二级缓存是从MapperStatement中获取的,而这个对象是全局的,就会存在线程安全问题。如果多个事务共用一个缓存实例,会出现脏读的问题。

TransactionalCache类是缓存SqlSession的某个事务需要向某个二级缓存添加的缓存数据。

//属性
//底层封装的二级缓存所对应的Cache对象
private final Cache delegate; 
//表示当前TransactionalCache不可查询,且提交事务会清空Cache.
private boolean clearOnCommit;
//暂时保存事务中的数据,事务提交后会将数据添加至二级缓存。
private final Map entriesToAddOnCommit;
//记录缓存未命中的CacheKey对象。
private final Set entriesMissedInCache;
 
  

添加/获取缓存记录:

public Object getObject(Object key) {
  //查询二级缓存是否有指定的key
  Object object = delegate.getObject(key);
  if (object == null) {
    //不存在,记录这个未命中key
    entriesMissedInCache.add(key);
  }
  
  if (clearOnCommit) {
    return null;
  } else {
    return object;
  }
}

//缓存在entriesToAddOnCommit等待事务提交
public void putObject(Object key, Object object) {
  entriesToAddOnCommit.put(key, object);
}

//提交
public void commit() {
  //事务提交前清空二级缓存
  if (clearOnCommit) {
      delegate.clear();
  }
  //将entriesToAddOnCommit添加至二级缓存中
  flushPendingEntries();
  //重置clearOnCommit=false,清空entriesToAddOnCommit、entriesMissedInCache
  reset();
}


private void flushPendingEntries() {  
  //将entriesToAddOnCommit添加至二级缓存中                                   
  for (Map.Entry entry : entriesToAddOnCommit.entrySet()) { 
    delegate.putObject(entry.getKey(), entry.getValue());                   
  }
  //将未命中的缓存且不存在的置为null, 自己觉得是为了防止缓存穿透                                                                         
  for (Object entry : entriesMissedInCache) {                               
    if (!entriesToAddOnCommit.containsKey(entry)) {                         
      delegate.putObject(entry, null);                                      
    }                                                                       
  }                                                                         
}                                                                           

那么为什么要当事务提交之后才去将数据缓存在二级缓存中。这是防止出现脏读的现象。

当有两个事务T1,T2。当T1添加A,之后查询A的记录最后提交事务。 T2查询记录A。 如果查询后直接提交给二级缓存,那么在T1未提交事务之前,T2边获取到了A,出现了脏读。所以最好的方式当事务提交之后,再将数据提交至二级缓存中。

假装读源码Mybatis(一):缓存原理_第2张图片

 

你可能感兴趣的:(mybatis)