Mybatis源碼分析之二級緩存

最近一週都在研究mybatis源碼,其實就mybatis源碼相對於Spring框架源碼來講,mybatis仍是簡單的,主要就是封裝jdbc,而後應用各類設計模式優化總體架構:如 在mybatis中用到了如下的設計模式,

構建者(建立sqlSession對象用到了)

責任鏈(mybatis中主要特色之一就是大量的handler,他們就是經過責任鏈來增長,執行)

裝飾者(mybatis中主要就是 Executor   主要這些:單例執行器SimpleExecutor,批量執行器BatchExecutor,以及一個緩存執行器CachingExecutor   )面試若是問執行器,能夠簡單來講 兩類 即BaseExecutor CachingExecutor,具體在看狀況回答   下圖是idea查看源碼,建議你們看這文章的時候,使用idea跟蹤源碼一步一步分析,看看類圖,比較記憶深入,本人學習源碼也是一個一個斷點跟蹤。

 

下面說下官方的一個mybatis緩存的介紹

MyBatis支持聲明式數據緩存(declarative data caching)。當一條SQL語句被標記爲「可緩存」後,首次執行它時從數據庫獲取的全部數據會被存儲在一段高速緩存中,從此執行這條語句時就會從高速緩存中讀取結果,而不是再次命中數據庫。MyBatis提供了默認下基於Java HashMap的緩存實現,以及用於與OSCache、Ehcache、Hazelcast和Memcached鏈接的默認鏈接器。MyBatis還提供API供其餘緩存實現使用。面試

重點的那句話就是:MyBatis執行SQL語句以後,這條語句就是被緩存,之後再執行這條語句的時候,會直接從緩存中拿結果,而不是再次執行SQLsql

這也就是你們常說的MyBatis一級緩存,一級緩存的做用域scope是SqlSession。數據庫

MyBatis同時還提供了一種全局做用域global scope的緩存,這也叫作二級緩存,也稱做全局緩存。設計模式

 

下面給你們演示 一級緩存

同個session進行兩次相同查詢:緩存

同個session進行兩次相同查詢:

@Test
public void test() {
    SqlSession sqlSession = sqlSessionFactory.openSession();
    try {
        User user = (User)sqlSession.selectOne("org.format.mybatis.cache.UserMapper.getById", 1);
        log.debug(user);
        User user2 = (User)sqlSession.selectOne("org.format.mybatis.cache.UserMapper.getById", 1);
        log.debug(user2);
    } finally {
        sqlSession.close();
    }
}


MyBatis只進行1次數據庫查詢:
==>  Preparing: select * from USERS WHERE ID = ? 
==> Parameters: 1(Integer)
<==      Total: 1
User{id=1, name='format', age=23, birthday=Sun Oct 12 23:20:13 CST 2014}
User{id=1, name='format', age=23, birthday=Sun Oct 12 23:20:13 CST 2014}


同個session進行兩次不一樣的查詢:

@Test
public void test() {
    SqlSession sqlSession = sqlSessionFactory.openSession();
    try {
        User user = (User)sqlSession.selectOne("org.format.mybatis.cache.UserMapper.getById", 1);
        log.debug(user);
        User user2 = (User)sqlSession.selectOne("org.format.mybatis.cache.UserMapper.getById", 2);
        log.debug(user2);
    } finally {
        sqlSession.close();
    }
}

MyBatis進行兩次數據庫查詢:

==>  Preparing: select * from USERS WHERE ID = ? 
==> Parameters: 1(Integer)
<==      Total: 1
User{id=1, name='format', age=23, birthday=Sun Oct 12 23:20:13 CST 2014}
==>  Preparing: select * from USERS WHERE ID = ? 
==> Parameters: 2(Integer)
<==      Total: 1
User{id=2, name='FFF', age=50, birthday=Sat Dec 06 17:12:01 CST 2014}

不一樣session,進行相同查詢:

@Test
public void test() {
    SqlSession sqlSession = sqlSessionFactory.openSession();
    SqlSession sqlSession2 = sqlSessionFactory.openSession();
    try {
        User user = (User)sqlSession.selectOne("org.format.mybatis.cache.UserMapper.getById", 1);
        log.debug(user);
        User user2 = (User)sqlSession2.selectOne("org.format.mybatis.cache.UserMapper.getById", 1);
        log.debug(user2);
    } finally {
        sqlSession.close();
        sqlSession2.close();
    }
}

MyBatis進行了兩次數據庫查詢:



==>  Preparing: select * from USERS WHERE ID = ? 
==> Parameters: 1(Integer)

同個session,查詢以後更新數據,再次查詢相同的語句:

@Test
public void test() {
    SqlSession sqlSession = sqlSessionFactory.openSession();
    try {
        User user = (User)sqlSession.selectOne("org.format.mybatis.cache.UserMapper.getById", 1);
        log.debug(user);
        user.setAge(100);
        sqlSession.update("org.format.mybatis.cache.UserMapper.update", user);
        User user2 = (User)sqlSession.selectOne("org.format.mybatis.cache.UserMapper.getById", 1);
        log.debug(user2);
        sqlSession.commit();
    } finally {
        sqlSession.close();
    }
}


更新操做以後緩存會被清除:

==>  Preparing: select * from USERS WHERE ID = ? 
==> Parameters: 1(Integer)
<==      Total: 1
User{id=1, name='format', age=23, birthday=Sun Oct 12 23:20:13 CST 2014}
==>  Preparing: update USERS SET NAME = ? , AGE = ? , BIRTHDAY = ? where ID = ? 
==> Parameters: format(String), 23(Integer), 2014-10-12 23:20:13.0(Timestamp), 1(Integer)
<==    Updates: 1
==>  Preparing: select * from USERS WHERE ID = ? 
==> Parameters: 1(Integer)
<==      Total: 1
User{id=1, name='format', age=23, birthday=Sun Oct 12 23:20:13 CST 2014}

很明顯,結果驗證了一級緩存的概念,在同個SqlSession中,查詢語句相同的sql會被緩存,可是一旦執行新增或更新或刪除操做,緩存就會被清除session

 

一級二級緩存結論已經展現,下面帶你們看下源碼結構如何實現的,其實就看了好多源碼狀況,通常底層的結構存儲緩存就是 map數據結構,具體什麼緩存策略 無非就是  FIFO  LRU  LFU   定時,懶加載 什麼的。

在分析MyBatis的一級緩存以前,咱們先簡單看下MyBatis中幾個重要的類和接口:

Configuration類:主要MyBatis全局配置信息類

看這個圖片是否是你們就豁然開朗了,就是 讀取xml  解析各個節點信息,最終將這個解析到的全局信息存放到 configuration對象中數據結構

SqlSessionFactory:操做SqlSession的工廠接口,具體的實現類是DefaultSqlSessionFactory

SqlSession接口:執行sql,管理事務的接口,具體的實現類是DefaultSqlSession

Executor接口:sql執行器,SqlSession執行sql最終是經過該接口實現的,經常使用的實現類有SimpleExecutor和CachingExecutor,這些實現類都使用了裝飾者設計模式(io  其實也用到了裝飾者,裝飾者就是解決了方法的加強)

-------------------------------------------------------------分割線---------------------------------------------------------------------------------------------------mybatis

 

基本分析mybatis須要瞭解的一本概念若是上面你們都清楚了,那麼就能夠往下面走了,相信我,下面so easy,若是上面還有模糊,請百度~

一級緩存的做用域是SqlSession,那麼咱們就先看一下SqlSession的select過程:

認真看了上面文章就知道,每一個sql的執行其實就是經過 session對象來調用方法,那麼session 是怎麼來的呢?  1.讀取配置文件,生成enviroment對象放入全局對象  configuration對象,  2.由sessionFactory openSession 生成sqlSession對象(構建者模式生成的,經過configuration對象中存儲的屬性配置)

 

這是DefaultSqlSession(SqlSession接口實現類,MyBatis默認使用這個類)的selectList源碼(咱們例子上使用的是selectOne方法,調用selectOne方法最終會執行selectList方法):

 看這裏能夠先看下  下面的第二張圖片  介紹 MappedStatement 這個就是 mybatis對sql解析後 存放的對象

接下來咱們看下DefaultSqlSession中的executor接口屬性具體是哪一個實現類。

DefaultSqlSession的構造過程(DefaultSqlSessionFactory內部)由於sqlSession 是由factory生成的,咱們就須要看factory的類實現:

Executor根據ExecutorType的不一樣而建立,最經常使用的是SimpleExecutor,本文的例子也是建立這個實現類。 最後咱們發現若是cacheEnabled這個屬性爲true的話,那麼executor會被包一層裝飾器,這個裝飾器是CachingExecutor。其中cacheEnabled這個屬性是mybatis總配置文件中settings節點中cacheEnabled子節點的值,默認就是true,也就是說咱們在mybatis總配置文件中不配cacheEnabled的話,它也是默認爲打開的。架構


executor = (Executor) interceptorChain.pluginAll(executor);  這行代碼就是 應用了責任鏈設計模式,不在這次解說返回,讀者自行百度

 

Executor根據ExecutorType的不一樣而建立,最經常使用的是SimpleExecutor,本文的例子也是建立這個實現類。 最後咱們發現若是cacheEnabled這個屬性爲true的話,那麼executor會被包一層裝飾器,這個裝飾器是CachingExecutor。其中cacheEnabled這個屬性是mybatis總配置文件中settings節點中cacheEnabled子節點的值,默認就是true,也就是說咱們在mybatis總配置文件中不配cacheEnabled的話,它也是默認爲打開的。

你們看到這其實 已經看得差很少了,就剩下下面一小點了,總結上面就是  默認給咱們生成了個 單例的執行器,而後因爲默認配置 cacheEanbled,全部採用裝飾者,最終先由cachingExecutor 執行器用了 執行sqlapp

------------------------------------------------------分割線----------------------------------------------------------------------------------------------------------

如今,問題就剩下一個了,CachingExecutor執行sql的時候到底作了什麼?

回到sql 執行query方法咱們繼續跟蹤

此時應該看得就是  CachingExecutor 代碼中的query方法了

帶着這個問題,咱們繼續走下去(CachingExecutor的query方法):

public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    Cache cache = ms.getCache();
    if (cache != null) {
      flushCacheIfRequired(ms);
      if (ms.isUseCache() && resultHandler == null) { 
        ensureNoOutParams(ms, parameterObject, boundSql);
        if (!dirty) {
          cache.getReadWriteLock().readLock().lock();
          try {
            @SuppressWarnings("unchecked")
            List<E> cachedList = (List<E>) cache.getObject(key);
            if (cachedList != null) return cachedList;
          } finally {
            cache.getReadWriteLock().readLock().unlock();
          }
        }
        List<E> list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
        tcm.putObject(cache, key, list); // issue #578. Query must be not synchronized to prevent deadlocks
        return list;
      }
    }
    return delegate.<E>query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

其中Cache cache = ms.getCache();這句代碼中,這個cache實際上就是個二級緩存,因爲咱們沒有開啓二級緩存(二級緩存的內容下面會分析),所以這裏執行了最後一句話。這裏的delegate也就是SimpleExecutor,SimpleExecutor沒有Override父類的query方法,所以最終執行了SimpleExecutor的父類BaseExecutor的query方法。

 

BaseExecutor的屬性localCache是個PerpetualCache類型的實例,PerpetualCache類是實現了MyBatis的Cache緩存接口的實現類之一,內部有個Map<Object, Object>類型的屬性用來存儲緩存數據。 這個localCache的類型在BaseExecutor內部是寫死的。 這個localCache就是一級緩存!

 

未完待續。。。。。。要和小姐姐吃飯去了。

相關文章
相關標籤/搜索