MyBatis 學習記錄4 MyBatis的一級緩存

主題

  分享記錄一下MyBatis的一級緩存相關的學習.mysql

 

Demo

 1     public static void firstLevelCache() {
 2         init("mybatis-config.xml");
 3 
 4         SqlSession session = sqlSessionFactory.openSession();
 5 
 6         UserMapper mapper = session.getMapper(UserMapper.class);
 7         User u = mapper.selectByPrimaryKey(1);
 8         System.out.println(u);
 9 
10         User u2 = mapper.selectByPrimaryKey(1);
11         System.out.println(u2);
12         session.close();
13 
14         System.out.println("==============session2==============");
15 
16         SqlSession session2 = sqlSessionFactory.openSession();
17         UserMapper mapper2 = session2.getMapper(UserMapper.class);
18         User u3 = mapper2.selectByPrimaryKey(1);
19         System.out.println(u3);
20         session2.close();
21 
22     }

從1個Demo來學習.sql

上面這段代碼首先經過session獲取了mapper而後執行了2次一樣的查詢,查詢用戶ID爲1的數據, 而後打印=======session2======,而後新開了一個session2. 而後獲取了第二個mapper並執行了相同的查詢.apache

 

輸出結果:設計模式

2018-09-29 19:06:26,813 DEBUG [main] logging.LogFactory : Logging initialized using 'class org.apache.ibatis.logging.slf4j.Slf4jImpl' adapter.
2018-09-29 19:06:26,961 DEBUG [main] pooled.PooledDataSource : PooledDataSource forcefully closed/removed all connections.
2018-09-29 19:06:26,962 DEBUG [main] pooled.PooledDataSource : PooledDataSource forcefully closed/removed all connections.
2018-09-29 19:06:26,962 DEBUG [main] pooled.PooledDataSource : PooledDataSource forcefully closed/removed all connections.
2018-09-29 19:06:26,962 DEBUG [main] pooled.PooledDataSource : PooledDataSource forcefully closed/removed all connections.
2018-09-29 19:06:27,102 DEBUG [main] jdbc.JdbcTransaction : Opening JDBC Connection
Sat Sep 29 19:06:27 CST 2018 WARN: Establishing SSL connection without server's identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn't set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to 'false'. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification.
2018-09-29 19:06:28,095 DEBUG [main] pooled.PooledDataSource : Created connection 1682463303.
2018-09-29 19:06:28,095 DEBUG [main] jdbc.JdbcTransaction : Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@64485a47]
2018-09-29 19:06:28,108 DEBUG [main] UserMapper.selectByPrimaryKey : ==>  Preparing: select id, user_id, user_name, real_name, email, creator_uid, modifier_uid, created_at, updated_at, del from user where id = ? 
2018-09-29 19:06:28,145 DEBUG [main] UserMapper.selectByPrimaryKey : ==> Parameters: 1(Integer)
2018-09-29 19:06:28,172 DEBUG [main] UserMapper.selectByPrimaryKey : <==      Total: 1
User{id=1, userId=1, userName='test', realName='realName', email='jyzjyz12@163.com', creatorUid=1, modifierUid=1, createdAt=Mon Sep 24 10:10:43 CST 2018, updatedAt=Mon Sep 24 10:10:46 CST 2018, del=false}
User{id=1, userId=1, userName='test', realName='realName', email='jyzjyz12@163.com', creatorUid=1, modifierUid=1, createdAt=Mon Sep 24 10:10:43 CST 2018, updatedAt=Mon Sep 24 10:10:46 CST 2018, del=false}
2018-09-29 19:06:28,172 DEBUG [main] jdbc.JdbcTransaction : Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@64485a47]
2018-09-29 19:06:28,183 DEBUG [main] jdbc.JdbcTransaction : Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@64485a47]
2018-09-29 19:06:28,183 DEBUG [main] pooled.PooledDataSource : Returned connection 1682463303 to pool.
==============session2==============
2018-09-29 19:06:28,183 DEBUG [main] jdbc.JdbcTransaction : Opening JDBC Connection
2018-09-29 19:06:28,183 DEBUG [main] pooled.PooledDataSource : Checked out connection 1682463303 from pool.
2018-09-29 19:06:28,183 DEBUG [main] jdbc.JdbcTransaction : Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@64485a47]
2018-09-29 19:06:28,193 DEBUG [main] UserMapper.selectByPrimaryKey : ==>  Preparing: select id, user_id, user_name, real_name, email, creator_uid, modifier_uid, created_at, updated_at, del from user where id = ? 
2018-09-29 19:06:28,194 DEBUG [main] UserMapper.selectByPrimaryKey : ==> Parameters: 1(Integer)
2018-09-29 19:06:28,210 DEBUG [main] UserMapper.selectByPrimaryKey : <==      Total: 1
User{id=1, userId=1, userName='test', realName='realName', email='jyzjyz12@163.com', creatorUid=1, modifierUid=1, createdAt=Mon Sep 24 10:10:43 CST 2018, updatedAt=Mon Sep 24 10:10:46 CST 2018, del=false}
2018-09-29 19:06:28,210 DEBUG [main] jdbc.JdbcTransaction : Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@64485a47]
2018-09-29 19:06:28,221 DEBUG [main] jdbc.JdbcTransaction : Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@64485a47]
2018-09-29 19:06:28,221 DEBUG [main] pooled.PooledDataSource : Returned connection 1682463303 to pool.

從輸出中咱們能夠看到,紅色部分是執行的SQL,藍色部分是查詢到用戶信息並打印的數據...緩存

總共執行了3次輸出打印,可是SQL查詢命令只運行了2次.其中第1個session的第1次查詢和第2個session的第1次查詢的時候會觸發SQL的執行.而第1個session的第二次查詢沒有處罰SQL執行session

 

從中咱們能夠獲得結論:mybatis

一級緩存是發生在同一個SqlSession中的, 1個sqlSession 屢次執行相同的SQL查詢會有緩存app

 

如何緩存

而後咱們來學習下MyBatis是如何設計二級緩存的.ide

整體來講我以爲最核心的就是BaseExecutor這個類學習

 

看到BaseExecutor的成員域中有個wrapper字段,類型是Executor類,你們就應該能明白,Executor是一種裝飾者的設計模式.這種模式在mybatis裏用的真的挺多的.

咱們無論是自定義Mapper仍是使用SqlSession去執行SQL,最終都是委託Executor來執行的.

 

如上圖,咱們的selectByPrimaryKey方法會調用SqlSession的selectOne而後再轉到selectList,最後委託給executor來執行.

而默認的狀況下,咱們在從SqlSessionFactory裏拿到的SqlSession的時候new的是一個SimpleExecutor外層包裹着CachingExecutor.

其中CachingExecutor涉及二級緩存,而BaseExecutor(SimpleExecutor的父類)主要涉及一級緩存.

 

從以前的BaseExecutor的結構圖中咱們也能夠發現有1個叫作localCache的字段.這個字段類型是PerpetualCache

PerpetualCache能夠認爲就是hashmap的簡單封裝.

 

 1     @SuppressWarnings("unchecked")
 2     @Override
 3     public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
 4         ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
 5         if (closed) {
 6             throw new ExecutorException("Executor was closed.");
 7         }
 8         if (queryStack == 0 && ms.isFlushCacheRequired()) {
 9             clearLocalCache();
10         }
11         List<E> list;
12         try {
13             queryStack++;
14             list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
15             if (list != null) {
16                 handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
17             } else {
18                 list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
19             }
20         } finally {
21             queryStack--;
22         }
23         if (queryStack == 0) {
24             for (DeferredLoad deferredLoad : deferredLoads) {
25                 deferredLoad.load();
26             }
27             // issue #601
28             deferredLoads.clear();
29             if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
30                 // issue #482
31                 clearLocalCache();
32             }
33         }
34         return list;
35     }

當執行BaseExecutor的query方法的時候,如上代碼所示,會先在L14那行,試着從localCache中獲取,若是沒有就L18,queryFromDatabase.不然就會返回localCache.getObject(key)的結果.代碼仍是比較清楚明瞭的

 

那麼這裏就涉及到一個問題.怎麼樣纔算緩存命中? 或者換句話說CacheKey怎麼計算? 由於相同的cacheKey會取到相同的緩存結果.

 

-1233329154:3360697231:test.mapper.UserMapper.selectByPrimaryKey:0:2147483647:select
     
    id, user_id, user_name, real_name, email, creator_uid, modifier_uid, created_at, 
    updated_at, del
   
    from user
    where id = ?:1:development

上面這段字符串就是我debug中截取的一個CacheKey的字符串形式. CacheKey的equals和toString依賴的成員域差很少.從toString的結果上來看可能更直觀一點.

 

首先須要說明一下,CacheKey有一個doUpdate方法,這個方法容許你加入一些須要計算緩存key的成分.也就是說加入的對象會影響key的結果.

而後咱們來分析一下那個字符串,其中有不少個組成部分

-1233329154爲CacheKey自己的hashcode

3360697231爲doUpdate裏添加過的對象的hashcode和

後面的各個部分是doUpdate中的添加過的對象的各自的toString.那麼能夠添加哪些東西呢?

從字符串中其實也能夠看出來.

類.方法名 + 分頁初始位置(默認爲0) + 分頁終止位置(默認爲Integer.MAX_VALUE) + SQL + 調用Mapper方法的參數 + 環境名(configuration裏配置的,默認是development)

從代碼裏也能夠看出就是這麼個邏輯: 下圖爲BaseExecutor實現Executor接口生成cacheKey的方法

另外cacheKey的equals只是比toString多了一個count對象的校驗,也就是doUpdate添加過了幾個對象的校驗...其餘部分基本是一致的.

因此若是2次查詢CacheKey的hashcode和equals一致,那麼就會使用以前緩存的結果.

 

小結

MyBatis一級緩存主要是在BaseExecutor中實現的,CacheKey涉及到不少組成部分(hashcode+各部分hashcode+類.方法名+分頁界限+SQL+參數+環境等), 從直觀上的感覺來講.調用同一個Mapper方法,若是參數一致,那就會被取到緩存結果.

相關文章
相關標籤/搜索