Mybatis源碼分析之(七)Mybatis一級緩存和二級緩存的實現

對於一名程序員,緩存真的很重要,而且緩存真的是老生常談的一個話題拉。因爲它在我們的開發過程中真的是無處不在。今天LZ帶大家來看一下。Mybatis是怎麼實現一級緩存和二級緩存的。(自帶的緩存機制)

一級緩存

存在BaseExecutor中,是全局的緩存,每次查詢後將值存入BaseExecutor的localCache中。key是由ms,parameter,rowBounds和boundSql一起生成的一個值。value就是查詢出來的結果。一旦有任何更新變動,就刪除整個localCache。



  @Override
  //生成一級緩存的key的函數,有興趣的看看,LZ不詳細解釋了,不是重點。
  public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    CacheKey cacheKey = new CacheKey();
    cacheKey.update(ms.getId());
    cacheKey.update(rowBounds.getOffset());
    cacheKey.update(rowBounds.getLimit());
    cacheKey.update(boundSql.getSql());
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
    // mimic DefaultParameterHandler logic
    for (ParameterMapping parameterMapping : parameterMappings) {
      if (parameterMapping.getMode() != ParameterMode.OUT) {
        Object value;
        String propertyName = parameterMapping.getProperty();
        if (boundSql.hasAdditionalParameter(propertyName)) {
          value = boundSql.getAdditionalParameter(propertyName);
        } else if (parameterObject == null) {
          value = null;
        } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
          value = parameterObject;
        } else {
          MetaObject metaObject = configuration.newMetaObject(parameterObject);
          value = metaObject.getValue(propertyName);
        }
        cacheKey.update(value);
      }
    }
    if (configuration.getEnvironment() != null) {
      // issue #176
      cacheKey.update(configuration.getEnvironment().getId());
    }
    return cacheKey;
  }
//查詢數據庫並存入一級緩存的語句
  private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    //先放一個值佔位
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
    //查詢數據
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
    //刪除佔位的
      localCache.removeObject(key);
    }
    //將查詢出來的結果存入一級緩存
    localCache.putObject(key, list);
    if (ms.getStatementType() == StatementType.CALLABLE) {
    //如果是存儲過程把參數存入localOutputParameterCache
      localOutputParameterCache.putObject(key, parameter);
    }
    return list;
  }

  @Override
  //更新變動等語句刪除緩存
  public int update(MappedStatement ms, Object parameter) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    //刪除一級緩存
    clearLocalCache();
    //執行語句
    return doUpdate(ms, parameter);
  }

  @Override
  //刪除一級緩存
  public void clearLocalCache() {
    if (!closed) {
      localCache.clear();
      localOutputParameterCache.clear();
    }
  }

所以一級緩存的作用級別是SESSION級別的,因爲一個session中存放一個Executor。而一級緩存放在Executor。

二級緩存

如果開啓了二級緩存的話,你的Executor將會被裝飾成CachingExecutor,二級緩存是MapperStatement級的緩存,也就是一個namespace就會有一個緩存,緩存是通過CachingExecutor來操作的。查詢出來的結果會存在statement中的cache中,若有更新,刪除類的操作默認就會清空該MapperStatement的cache(也可以通過修改xml中的屬性,讓它不執行),不會影響其他的MapperStatement

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
     //獲得該MappedStatement的cache
    Cache cache = ms.getCache();
    if (cache != null) {
        //看是否需要清除cache(在xml中可以配置flushCache屬性決定何時清空cache)
      flushCacheIfRequired(ms);
      if (ms.isUseCache() && resultHandler == null) {
      //若開啓了cache且resultHandler 爲空
        ensureNoOutParams(ms, parameterObject, boundSql);
        @SuppressWarnings("unchecked")
        //從TransactionalCacheManager中取cache
        List<E> list = (List<E>) tcm.getObject(cache, key);
        //若沒值
        if (list == null) {
       //訪問數據庫
          list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          //存入cache
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        return list;
      }
    }
    return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

  @Override
  public int update(MappedStatement ms, Object parameterObject) throws SQLException {
  //看是否需要清除cache(在xml中可以配置flushCache屬性決定何時清空cache)
    flushCacheIfRequired(ms);
    return delegate.update(ms, parameterObject);
  }
    private void flushCacheIfRequired(MappedStatement ms) {
    //獲得cache
    Cache cache = ms.getCache();
    if (cache != null && ms.isFlushCacheRequired()) {      
    //若需要清除,則清除cache
      tcm.clear(cache);
    }
  }

因同一個namespace下的MappedStatement的cache是同一個,而TransactionalCacheManager中統一管理cache是裏面的屬性transactionalCaches,該屬性以MappedStatement中的Cache爲key,TransactionalCache對象爲Value。即一個namespace對應一個TransactionalCache。

public class TransactionalCacheManager {

  private Map<Cache, TransactionalCache> transactionalCaches = new HashMap<Cache, TransactionalCache>();
  ……
}
public class TransactionalCache implements Cache {

  private static final Log log = LogFactory.getLog(TransactionalCache.class);

//就是對應的namespace中的cache
  private Cache delegate;
  //提交的時候清除cache的標誌位
  private boolean clearOnCommit;
  //待提交的集合
  private Map<Object, Object> entriesToAddOnCommit;
  //未查到的key存放的集合
  private Set<Object> entriesMissedInCache;
}

總結

一級緩存是sqlSession級別的緩存,存放在BaseExecutor中的localCache中。查詢就將結果緩存進去,一旦有更新,刪除,插入類的操作就清空緩存。不同的sqlSession之間的緩存是互相不影響的。
二級緩存是namespace級別的,可以理解爲一個mapper.xml文件對應一個二級緩存(不同的sqlSession之間的緩存是共享的),然後對緩存的操作是在.xml文件中的標籤文件進行控制的。比如下面的代碼


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="mapper.TDemoMapper">
<!-- 一個namespace對應一個緩存 -->

    <resultMap id="baseMap" type="entity.TDemo">
        <result property="id" column="id" jdbcType="INTEGER"></result>
        <result property="name" column="name" jdbcType="VARCHAR"></result>

    </resultMap>

<!-- 執行此語句不清空緩存 -->
    <select id="getAll" resultType="entity.TDemo" useCache="true" flushCache="false" >
        select * from t_demo
    </select>


</mapper>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章