MyBatis一級緩存原理解析

MyBatis是一個簡單,小巧但功能非常強大的ORM開源框架,它的功能強大也體現(xiàn)在它的緩存機制上。MyBatis提供了一級緩存、二級緩存 這兩個緩存機制,能夠很好地處理和維護緩存,以提高系統(tǒng)的性能。本文將介紹MyBatis的一級緩存,并深入源碼解析MyBatis一級緩存的實現(xiàn)原理。

什么是一級緩存?

每當我們使用MyBatis開啟一次和數(shù)據(jù)庫的會話,MyBatis會創(chuàng)建出一個SqlSession對象表示一次數(shù)據(jù)庫會話。

在對數(shù)據(jù)庫的一次會話中,我們有可能會反復地執(zhí)行完全相同的查詢語句,如果不采取一些措施的話,每一次查詢都會查詢一次數(shù)據(jù)庫,而我們在極短的時間內(nèi)做了完全相同的查詢,那么它們的結(jié)果極有可能完全相同,由于查詢一次數(shù)據(jù)庫的代價很大,這有可能造成很大的資源浪費。

為了解決這一問題,減少資源的浪費,MyBatis會在表示會話的SqlSession對象中建立一個簡單的緩存,將每次查詢到的結(jié)果結(jié)果緩存起來,當下次查詢的時候,如果判斷先前有個完全一樣的查詢,會直接從緩存中直接將結(jié)果取出,返回給用戶,不需要再進行一次數(shù)據(jù)庫查詢了。

如下圖所示,MyBatis會在一次會話的表示(一個SqlSession對象)中創(chuàng)建一個本地緩存(local cache),對于每一次查詢,都會嘗試根據(jù)查詢的條件去本地緩存中查找是否在緩存中,如果在緩存中,就直接從緩存中取出,然后返回給用戶;否則,從數(shù)據(jù)庫讀取數(shù)據(jù),將查詢結(jié)果存入緩存并返回給用戶。



對于會話(Session)級別的數(shù)據(jù)緩存,我們稱之為一級數(shù)據(jù)緩存,簡稱一級緩存。

一級緩存的實現(xiàn)原理

由于MyBatis使用SqlSession對象表示一次數(shù)據(jù)庫的會話,那么,對于會話級別的一級緩存也應該是在SqlSession中控制的。

實際上, MyBatis只是一個MyBatis對外的接口,SqlSession將它的工作交給了Executor執(zhí)行器這個角色來完成,負責完成對數(shù)據(jù)庫的各種操作。當創(chuàng)建了一個SqlSession對象時,MyBatis會為這個SqlSession對象創(chuàng)建一個新的Executor執(zhí)行器,而緩存信息就被維護在這個Executor執(zhí)行器中,MyBatis將緩存和對緩存相關(guān)的操作封裝成了Cache接口中。SqlSession、Executor、Cache之間的關(guān)系如下列類圖所示:



如上述的類圖所示,Executor接口的實現(xiàn)類BaseExecutor中擁有一個Cache接口的實現(xiàn)類PerpetualCache,則對于BaseExecutor對象而言,它將使用PerpetualCache對象維護緩存。

由于Session級別的一級緩存實際上就是使用PerpetualCache維護的,那么PerpetualCache是怎樣實現(xiàn)的呢?

PerpetualCache實現(xiàn)原理其實很簡單,其內(nèi)部就是通過一個簡單的HashMap<k,v> 來實現(xiàn)的,沒有其他的任何限制。如下是PerpetualCache的實現(xiàn)代碼:

public class PerpetualCache implements Cache {
 
  private String id;
 
  private Map<Object, Object> cache = new HashMap<Object, Object>();
 
  public PerpetualCache(String id) {
    this.id = id;
  }
 
  public String getId() {
    return id;
  }
 
  public int getSize() {
    return cache.size();
  }
 
  public void putObject(Object key, Object value) {
    cache.put(key, value);
  }
 
  public Object getObject(Object key) {
    return cache.get(key);
  }
 
  public Object removeObject(Object key) {
    return cache.remove(key);
  }
 
  public void clear() {
    cache.clear();
  }
 
  public ReadWriteLock getReadWriteLock() {
    return null;
  }
 
  public boolean equals(Object o) {
    if (getId() == null) throw new CacheException("Cache instances require an ID.");
    if (this == o) return true;
    if (!(o instanceof Cache)) return false;
 
    Cache otherCache = (Cache) o;
    return getId().equals(otherCache.getId());
  }
 
  public int hashCode() {
    if (getId() == null) throw new CacheException("Cache instances require an ID.");
    return getId().hashCode();
  }
 
}

一級緩存的生命周期有多長?

a. MyBatis在開啟一個數(shù)據(jù)庫會話時,會 創(chuàng)建一個新的SqlSession對象,SqlSession對象中會有一個新的Executor對象,Executor對象中持有一個新的PerpetualCache對象;當會話結(jié)束時,SqlSession對象及其內(nèi)部的Executor對象還有PerpetualCache對象也一并釋放掉。

b. 如果SqlSession調(diào)用了close()方法,會釋放掉一級緩存PerpetualCache對象,一級緩存將不可用;

c. 如果SqlSession調(diào)用了clearCache(),會清空PerpetualCache對象中的數(shù)據(jù),但是該對象仍可使用;

d.SqlSession中執(zhí)行了任何一個update操作(update()、delete()、insert()) ,都會清空PerpetualCache對象的數(shù)據(jù),但是該對象可以繼續(xù)使用;


CacheKey的定義

我們知道,Cache最核心的實現(xiàn)其實就是一個Map,將本次查詢使用的特征值作為key,將查詢結(jié)果作為value存儲到Map中。

現(xiàn)在最核心的問題出現(xiàn)了:怎樣來確定一次查詢的特征值?

換句話說就是:怎樣判斷某兩次查詢是完全相同的查詢?

也可以這樣說:如何確定Cache中的key值?

MyBatis認為,對于兩次查詢,如果以下條件都完全一樣,那么就認為它們是完全相同的兩次查詢:

  1. 傳入的 statementId

  2. 查詢時要求的結(jié)果集中的結(jié)果范圍 (結(jié)果的范圍通過rowBounds.offset和rowBounds.limit表示);

  3. 這次查詢所產(chǎn)生的最終要傳遞給JDBC java.sql.Preparedstatement的Sql語句字符串(boundSql.getSql() )

  4. 傳遞給java.sql.Statement要設(shè)置的參數(shù)值

綜上所述,CacheKey由以下條件決定:statementId + rowBounds + 傳遞給JDBC的SQL + 傳遞給JDBC的參數(shù)值

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容