最近一週都在研究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就是一級緩存!
未完待續。。。。。。要和小姐姐吃飯去了。