MyBatis(七):MyBatis快取詳解(一級快取/二級快取)
阿新 • • 發佈:2021-03-11
1. 一級快取
MyBatis一級快取上SqlSession快取,即在統一SqlSession中,在不執行增刪改操作提交事務的前提下,對同一條資料進行多次查詢時,第一次查詢從資料庫中查詢,完成後會存入快取,其餘從快取中直接讀取。MyBatis一級快取預設開啟。
![](https://img2020.cnblogs.com/blog/1142548/202103/1142548-20210311160322069-812017910.png)
2. 二級快取
MyBatis二級快取是名稱空間NameSpace快取,也可理解為二級快取被多個SqlSession共享,是一個全域性變數。
二級快取預設是關閉的,需要手動配置進行開啟。開啟二級快取後,資料查詢流程為:二級快取->一級快取->資料庫。
- 二級快取開啟
1. 實體類需要實現Serializable介面
2. 核心配置檔案增加標籤
```xml
```
3. 在需要開啟的mapper中新增標籤
```xml
```
> - `eviction`屬性可以設定快取回收策略,預設LRU策略
>
> - `LRU` - 最近最少回收,移除最長時間不被使用的物件
> - `FIFO` - 先進先出,按照快取進入的順序來移除它們
> - `SOFT` - 軟引用,移除基於垃圾回收器狀態和軟引用規則的物件
> - `WEAK` - 弱引用,更積極的移除基於垃圾收集器和弱引用規則的物件
>
> - `flushinterval` 快取重新整理間隔,快取多長時間重新整理一次,預設不清空,設定一個毫秒值
>
> - `readOnly`: 是否只讀;
>
> false讀寫(預設):MyBatis 覺得資料可能會被修改
>
> true 只讀,MyBatis 認為所有從快取中獲取資料的操作都是隻讀操作,不會修改資料。MyBatis 為了加快獲取資料,直接就會將資料在快取中的引用交給使用者。不安全,速度快。
>
> - `size` : 快取存放多少個元素
>
> - `type`: 指定自定義快取的全類名(實現Cache 介面即可)
>
> - `blocking`: 若快取中找不到對應的key,是否會一直blocking,直到有對應的資料進入快取。
>
> 例如:
>
> ```xml
>
> ```
- 二級快取測試,多個SqlSession
```java
InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
User param = new User();
param.setId(1);
SqlSession sqlSession1 = sqlSessionFactory.openSession();
IUserDao userDao1 = sqlSession1.getMapper(IUserDao.class);
User user1 = userDao1.findOne(param);
System.out.println(user1);
sqlSession1.close();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
IUserDao userDao2 = sqlSession2.getMapper(IUserDao.class);
User user2 = userDao2.findOne(param);
System.out.println(user2);
sqlSession2.close();
```
控制檯檢視輸出日誌,發現快取已命中—Cache Hit Ratio [com.rangers.dao.IUserDao]: 0.5
```xml
14:26:48,608 DEBUG IUserDao:62 - Cache Hit Ratio [com.rangers.dao.IUserDao]: 0.0
14:26:48,611 DEBUG JdbcTransaction:137 - Opening JDBC Connection
14:26:48,952 DEBUG PooledDataSource:406 - Created connection 543846639.
14:26:48,952 DEBUG JdbcTransaction:101 - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@206a70ef]
14:26:48,957 DEBUG findOne:159 - ==> Preparing: select * from user where id=?
14:26:48,980 DEBUG findOne:159 - ==> Parameters: 1(Integer)
14:26:49,004 DEBUG findOne:159 - <== Total: 1
com.rangers.entity.User{id=1, name='rangers', address='杭州'}
14:26:49,007 DEBUG JdbcTransaction:123 - Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@206a70ef]
14:26:49,010 DEBUG JdbcTransaction:91 - Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@206a70ef]
14:26:49,011 DEBUG PooledDataSource:363 - Returned connection 543846639 to pool.
14:26:49,014 DEBUG IUserDao:62 - Cache Hit Ratio [com.rangers.dao.IUserDao]: 0.5
com.rangers.entity.User{id=1, name='rangers', address='杭州'}
```
- 二級快取失效條件
- 首次SqlSession未提交事務時,二級快取無法命中
SqlSession未提交時,執行結果未放入二級快取中,這時候第二個SqlSession在查詢時候是無法命中的
例如:調整sqlSession1.close();在sqlSession2執行之後,就不會命中快取
```java
InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
User param = new User();
param.setId(1);
SqlSession sqlSession1 = sqlSessionFactory.openSession();
IUserDao userDao1 = sqlSession1.getMapper(IUserDao.class);
User user1 = userDao1.findOne(param);
System.out.println(user1);
SqlSession sqlSession2 = sqlSessionFactory.openSession();
IUserDao userDao2 = sqlSession2.getMapper(IUserDao.class);
User user2 = userDao2.findOne(param);
System.out.println(user2);
sqlSession1.close();
sqlSession2.close();
```
檢視控制檯輸出,發現兩次快取命中都是0.0,Cache Hit Ratio [com.rangers.dao.IUserDao]: 0.0
```xml
14:49:39,073 DEBUG IUserDao:62 - Cache Hit Ratio [com.rangers.dao.IUserDao]: 0.0
14:49:39,075 DEBUG JdbcTransaction:137 - Opening JDBC Connection
14:49:39,385 DEBUG PooledDataSource:406 - Created connection 543846639.
14:49:39,385 DEBUG JdbcTransaction:101 - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@206a70ef]
14:49:39,389 DEBUG findOne:159 - ==> Preparing: select * from user where id=?
14:49:39,408 DEBUG findOne:159 - ==> Parameters: 1(Integer)
14:49:39,433 DEBUG findOne:159 - <== Total: 1
com.rangers.entity.User{id=1, name='rangers', address='杭州'}
14:49:39,434 DEBUG IUserDao:62 - Cache Hit Ratio [com.rangers.dao.IUserDao]: 0.0
14:49:39,434 DEBUG JdbcTransaction:137 - Opening JDBC Connection
14:49:39,489 DEBUG PooledDataSource:406 - Created connection 2079179914.
14:49:39,489 DEBUG JdbcTransaction:101 - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@7bedc48a]
14:49:39,491 DEBUG findOne:159 - ==> Preparing: select * from user where id=?
14:49:39,492 DEBUG findOne:159 - ==> Parameters: 1(Integer)
14:49:39,495 DEBUG findOne:159 - <== Total: 1
com.rangers.entity.User{id=1, name='rangers', address='杭州'}
14:49:39,497 DEBUG JdbcTransaction:123 - Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@206a70ef]
14:49:39,499 DEBUG JdbcTransaction:91 - Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@206a70ef]
14:49:39,500 DEBUG PooledDataSource:363 - Returned connection 543846639 to pool.
14:49:39,500 DEBUG JdbcTransaction:123 - Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@7bedc48a]
14:49:39,502 DEBUG JdbcTransaction:91 - Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@7bedc48a]
14:49:39,502 DEBUG PooledDataSource:363 - Returned connection 2079179914 to pool.
```
- 更新提交事務,與一級快取一樣,增刪改操作提交事務會清空快取
```java
InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
User param = new User();
param.setId(1);
SqlSession sqlSession1 = sqlSessionFactory.openSession();
IUserDao userDao1 = sqlSession1.getMapper(IUserDao.class);
User user1 = userDao1.findOne(param);
System.out.println("sqlSession1查詢結果:"+user1);
sqlSession1.close();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
IUserDao userDao2 = sqlSession2.getMapper(IUserDao.class);
param.setName("遊騎兵");
param.setAddress("西安");
Boolean flag = userDao2.updateUser(param) > 0;
System.out.println("執行更新操作結果:"+flag);
sqlSession2.commit();
sqlSession2.close();
SqlSession sqlSession3 = sqlSessionFactory.openSession();
IUserDao userDao3 = sqlSession3.getMapper(IUserDao.class);
User user3 = userDao3.findOne(param);
sqlSession3.close();
System.out.println("sqlSession3查詢結果:"+user3);
```
檢視控制檯輸出,經過sqlSession2提交事務後,sqlSession3的查詢快取命中率為0.0
```xml
15:00:33,901 DEBUG IUserDao:62 - Cache Hit Ratio [com.rangers.dao.IUserDao]: 0.0
15:00:33,904 DEBUG JdbcTransaction:137 - Opening JDBC Connection
15:00:34,263 DEBUG PooledDataSource:406 - Created connection 543846639.
15:00:34,263 DEBUG JdbcTransaction:101 - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@206a70ef]
15:00:34,267 DEBUG findOne:159 - ==> Preparing: select * from user where id=?
15:00:34,290 DEBUG findOne:159 - ==> Parameters: 1(Integer)
15:00:34,314 DEBUG findOne:159 - <== Total: 1
sqlSession1查詢結果:com.rangers.entity.User{id=1, name='rangers', address='杭州'}
15:00:34,317 DEBUG JdbcTransaction:123 - Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@206a70ef]
15:00:34,321 DEBUG JdbcTransaction:91 - Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@206a70ef]
15:00:34,321 DEBUG PooledDataSource:363 - Returned connection 543846639 to pool.
15:00:34,321 DEBUG JdbcTransaction:137 - Opening JDBC Connection
15:00:34,322 DEBUG PooledDataSource:398 - Checked out connection 543846639 from pool.
15:00:34,322 DEBUG JdbcTransaction:101 - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@206a70ef]
15:00:34,325 DEBUG updateUser:159 - ==> Preparing: update user set name=?,address=? where id=?
15:00:34,325 DEBUG updateUser:159 - ==> Parameters: 遊騎兵(String), 西安(String), 1(Integer)
15:00:34,334 DEBUG updateUser:159 - <== Updates: 1
執行更新操作結果:true
15:00:34,335 DEBUG JdbcTransaction:70 - Committing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@206a70ef]
15:00:34,445 DEBUG JdbcTransaction:123 - Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@206a70ef]
15:00:34,448 DEBUG JdbcTransaction:91 - Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@206a70ef]
15:00:34,450 DEBUG PooledDataSource:363 - Returned connection 543846639 to pool.
15:00:34,450 DEBUG IUserDao:62 - Cache Hit Ratio [com.rangers.dao.IUserDao]: 0.0
15:00:34,450 DEBUG JdbcTransaction:137 - Opening JDBC Connection
15:00:34,451 DEBUG PooledDataSource:398 - Checked out connection 543846639 from pool.
15:00:34,451 DEBUG JdbcTransaction:101 - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@206a70ef]
15:00:34,453 DEBUG findOne:159 - ==> Preparing: select * from user where id=?
15:00:34,454 DEBUG findOne:159 - ==> Parameters: 1(Integer)
15:00:34,458 DEBUG findOne:159 - <== Total: 1
15:00:34,458 DEBUG JdbcTransaction:123 - Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@206a70ef]
15:00:34,461 DEBUG JdbcTransaction:91 - Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@206a70ef]
15:00:34,462 DEBUG PooledDataSource:363 - Returned connection 543846639 to pool.
sqlSession3查詢結果:com.rangers.entity.User{id=1, name='遊騎兵', address='西安'}
```
- 多表操作時,對MyBatis二級快取的影響
1. 問題:未同一namespace下進行增刪改提交操作時,其他namespace的快取是無法感知的
例如:兩個Mapper檔案,AMapper.xml和BMapper.xml,B修改了user表中的內容,A是感知不到的,那麼再從A裡查詢如果用到了快取,就是舊的資料。
新增UserMapper1.xml ,修改名稱空間為com.rangers.dao.IUserDao1
```java
InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
User param = new User();
param.setId(1);
SqlSession sqlSession1 = sqlSessionFactory.openSession();
IUserDao userDao1 = sqlSession1.getMapper(IUserDao.class);
User user1 = userDao1.findOne(param);
System.out.println("sqlSession1查詢結果:"+user1);
sqlSession1.close();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
param.setName("遊騎兵");
param.setAddress("西安");
Boolean flag = sqlSession2.update("com.rangers.dao.IUserDao1.updateUser",param) > 0;
System.out.println("執行更新操作結果:"+flag);
sqlSession2.commit();
sqlSession2.close();
SqlSession sqlSession3 = sqlSessionFactory.openSession();
IUserDao userDao3 = sqlSession3.getMapper(IUserDao.class);
User user3 = userDao3.findOne(param);
sqlSession3.close();
System.out.println("sqlSession3查詢結果:"+user3);
```
檢視控制檯輸出,發現sqlSession2成功更新使用者資訊後,sqlSession3進行查詢時二級快取命中,依舊是sqlSession1中的結果,查詢結果錯誤
```xml
15:33:54,792 DEBUG JdbcTransaction:137 - Opening JDBC Connection
15:33:55,164 DEBUG PooledDataSource:406 - Created connection 1165303897.
15:33:55,165 DEBUG JdbcTransaction:101 - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@45752059]
15:33:55,168 DEBUG findOne:159 - ==> Preparing: select * from user where id=?
15:33:55,186 DEBUG findOne:159 - ==> Parameters: 1(Integer)
15:33:55,212 DEBUG findOne:159 - <== Total: 1
sqlSession1查詢結果:com.rangers.entity.User{id=1, name='Rangers', address='杭州'}
15:33:55,215 DEBUG JdbcTransaction:123 - Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@45752059]
15:33:55,218 DEBUG JdbcTransaction:91 - Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@45752059]
15:33:55,218 DEBUG PooledDataSource:363 - Returned connection 1165303897 to pool.
15:33:55,219 DEBUG JdbcTransaction:137 - Opening JDBC Connection
15:33:55,220 DEBUG PooledDataSource:398 - Checked out connection 1165303897 from pool.
15:33:55,220 DEBUG JdbcTransaction:101 - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@45752059]
15:33:55,224 DEBUG updateUser:159 - ==> Preparing: update user set name=?,address=? where id=?
15:33:55,225 DEBUG updateUser:159 - ==> Parameters: 遊騎兵(String), 西安(String), 1(Integer)
15:33:55,233 DEBUG updateUser:159 - <== Updates: 1
執行更新操作結果:true
15:33:55,233 DEBUG JdbcTransaction:70 - Committing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@45752059]
15:33:55,351 DEBUG JdbcTransaction:123 - Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@45752059]
15:33:55,354 DEBUG JdbcTransaction:91 - Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@45752059]
15:33:55,355 DEBUG PooledDataSource:363 - Returned connection 1165303897 to pool.
15:33:55,357 DEBUG IUserDao:62 - Cache Hit Ratio [com.rangers.dao.IUserDao]: 0.5
sqlSession3查詢結果:com.rangers.entity.User{id=1, name='Rangers', address='杭州'}
```
2. 解決
在查詢的Mapper檔案中引入cache-ref標籤,指向執行增刪改操作表的名稱空間,例如在UserMapper.xml中增加一下標籤
```xml
```
在此執行,檢視控制檯輸出,發現第三次查詢已直接從資料庫中進行查詢,結果正確
```xml
15:46:05,486 DEBUG IUserDao1:62 - Cache Hit Ratio [com.rangers.dao.IUserDao1]: 0.0
15:46:05,489 DEBUG JdbcTransaction:137 - Opening JDBC Connection
15:46:05,788 DEBUG PooledDataSource:406 - Created connection 1165303897.
15:46:05,789 DEBUG JdbcTransaction:101 - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@45752059]
15:46:05,793 DEBUG findOne:159 - ==> Preparing: select * from user where id=?
15:46:05,822 DEBUG findOne:159 - ==> Parameters: 1(Integer)
15:46:05,842 DEBUG findOne:159 - <== Total: 1
sqlSession1查詢結果:com.rangers.entity.User{id=1, name='rangers', address='杭州'}
15:46:05,845 DEBUG JdbcTransaction:123 - Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@45752059]
15:46:05,848 DEBUG JdbcTransaction:91 - Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@45752059]
15:46:05,848 DEBUG PooledDataSource:363 - Returned connection 1165303897 to pool.
15:46:05,849 DEBUG JdbcTransaction:137 - Opening JDBC Connection
15:46:05,849 DEBUG PooledDataSource:398 - Checked out connection 1165303897 from pool.
15:46:05,849 DEBUG JdbcTransaction:101 - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@45752059]
15:46:05,852 DEBUG updateUser:159 - ==> Preparing: update user set name=?,address=? where id=?
15:46:05,852 DEBUG updateUser:159 - ==> Parameters: 遊騎兵(String), 西安(String), 1(Integer)
15:46:05,865 DEBUG updateUser:159 - <== Updates: 1
執行更新操作結果:true
15:46:05,865 DEBUG JdbcTransaction:70 - Committing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@45752059]
15:46:05,970 DEBUG JdbcTransaction:123 - Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@45752059]
15:46:05,974 DEBUG JdbcTransaction:91 - Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@45752059]
15:46:05,976 DEBUG PooledDataSource:363 - Returned connection 1165303897 to pool.
15:46:05,976 DEBUG IUserDao1:62 - Cache Hit Ratio [com.rangers.dao.IUserDao1]: 0.0
15:46:05,976 DEBUG JdbcTransaction:137 - Opening JDBC Connection
15:46:05,976 DEBUG PooledDataSource:398 - Checked out connection 1165303897 from pool.
15:46:05,976 DEBUG JdbcTransaction:101 - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@45752059]
15:46:05,979 DEBUG findOne:159 - ==> Preparing: select * from user where id=?
15:46:05,979 DEBUG findOne:159 - ==> Parameters: 1(Integer)
15:46:05,985 DEBUG findOne:159 - <== Total: 1
15:46:05,985 DEBUG JdbcTransaction:123 - Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@45752059]
15:46:05,989 DEBUG JdbcTransaction:91 - Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@45752059]
15:46:05,990 DEBUG PooledDataSource:363 - Returned connection 1165303897 to pool.
sqlSession3查詢結果:com.rangers.entity.User{id=1, name='遊騎兵', address='西安'}
```
- 是否應該使用二級快取
> 二級快取的注意事項:
>
> 1. 快取是以`namespace`為單位的,不同`namespace`下的操作互不影響。
>
> 2. insert,update,delete操作會清空所在`namespace`下的全部快取。
>
> 3. 通常使用MyBatis Generator生成的程式碼中,都是各個表獨立的,每個表都有自己的`namespace`。
>
> 4. 多表操作一定不要使用二級快取,因為多表操作進行更新操作,一定會產生髒資料。
>
> 如果你遵守二級快取的注意事項,那麼你就可以使用二級快取。
>
> 如果不能使用多表操作,二級快取不就可以用一級快取來替換掉嗎?而且二級快取是表級快取,開銷大,沒有一級快取直接使用 HashMap 來儲存的效率更高,所以二級快取並不推薦使用。
引用內容參考:https://www.cnblogs.com/cxuanBlog/p/11333021.html