1. 程式人生 > >淺談Mybatis中session的一級快取的實現原理

淺談Mybatis中session的一級快取的實現原理

最近由於受工作中業務需要和現有工程中dao層非orm思想的影響,覺得在有些業務場景下,並不一定非要去使用ORM框架,畢竟寫大量的實體類也是一件麻煩的事,於是著手編寫一個非ORM框架。初步完成後,底層的session並沒能像mybatis那樣能支援session的一級快取(雖然在和Spring整合之後,Mybatis的session的一級快取並沒起什麼作用),so,通過看原始碼大致瞭解一哈Mybatis中session的一級快取實現。

Mybatis是一個很輕量也很強大的ORM框架(這並不影響我學習來開發非ORM框架),但她的一級快取的實現則不是那麼複雜。首先我們知道,Mybatis在每次開啟資料庫會話時,都會建立一個sqlsession物件,下面看一段預設sessionfactory建立預設session時的程式碼

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      final Executor executor = configuration.newExecutor(tx, execType);
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }
上述程式碼是DefaultSessionFactory通過資料來源建立session的方法,另一種通過Connection建立session的方式與此大同小異,我們可以看到,建立session需要executor這個物件,那Executor這個介面是幹嘛的呢?executor就是執行器,用於預處理語句,然後再呼叫底層的statementHandler去執行sql語句。executor下有兩個實現類BaseExecutor、CatchingExecutor,在建立session中,主要使用BaseExecutor的子類物件,BaseExecutor有四個子類:BatchExecutor、SimpleExecutor、RuseExecutor、ClosedExecutor,而一級快取的初始化工作是在BaseExecutor這個抽象父類中進行的,看一下BaseExecutor的構造方法:
protected BaseExecutor(Configuration configuration, Transaction transaction) {
    this.transaction = transaction;
    this.deferredLoads = new ConcurrentLinkedQueue<DeferredLoad>();
    this.localCache = new PerpetualCache("LocalCache");
    this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");
    this.closed = false;
    this.configuration = configuration;
    this.wrapper = this;
  }

在構造方法中localCache變數就是我們的主角:一級快取,Mybatis的session的一級快取實際上就是由PerpetualCache這個類來維護的,那PerpetualCache何許人也呢,我們來看看他的真面目:
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();
  }

}

是不是有點驚訝,就這點程式碼,內部其實就是一個HashMap來簡單實現的,回想一下,常說session的一級快取是執行緒不安全的,看到這裡就有點恍然了,HashMap可不就是執行緒不安全的嗎,類中的方法也基本上是對hashmap的操作,id屬性其實一個標識。。比如上面的程式碼中new PerpetualCache("LocalCache"),僅僅就是告訴你這是本地的一級快取。好,既然涉及到了hashmap,那麼就不得不想到如何保證鍵即key的唯一性,也由於查詢的一些東西存在key中,結果存在value裡,也進而要問,對啊,你一級快取不就是為了讓我重複查的時候直接從快取裡取唄,那你怎麼判斷兩次查詢是完全相同的查詢?帶著疑問,我們繼續看BaseExecutor,由於是隻有在查詢時才會去快取裡找,所以去找關於查詢的程式碼:

public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameter);
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
 }

我們看到有這樣一句話:CacheKey key = createCacheKey(..);顧名思義,這就是建立快取那個map的key的方法啊,再點進去看:
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
    if (closed) throw new ExecutorException("Executor was closed.");
    CacheKey cacheKey = new CacheKey();
    cacheKey.update(ms.getId());
    cacheKey.update(rowBounds.getOffset());
    cacheKey.update(rowBounds.getLimit());
    cacheKey.update(boundSql.getSql());
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
    for (int i = 0; i < parameterMappings.size(); i++) { // mimic DefaultParameterHandler logic
      ParameterMapping parameterMapping = parameterMappings.get(i);
      if (parameterMapping.getMode() != ParameterMode.OUT) {
        Object value;
        String propertyName = parameterMapping.getProperty();
        if (boundSql.hasAdditionalParameter(propertyName)) {
          value = boundSql.getAdditionalParameter(propertyName);
        } else if (parameterObject == null) {
          value = null;
        } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
          value = parameterObject;
        } else {
          MetaObject metaObject = configuration.newMetaObject(parameterObject);
          value = metaObject.getValue(propertyName);
        }
        cacheKey.update(value);
      }
    }
    return cacheKey;
  }  


我們看到定義了一個CacheKey物件,並呼叫update方法,再點進去看下CacheKey中的update方法:

public void update(Object object) {
    if (object != null && object.getClass().isArray()) {
      int length = Array.getLength(object);
      for (int i = 0; i < length; i++) {
        Object element = Array.get(object, i);
        doUpdate(element);
      }
    } else {
      doUpdate(object);
    }
  }

  private void doUpdate(Object object) {
    int baseHashCode = object == null ? 1 : object.hashCode();

    count++;
    checksum += baseHashCode;
    baseHashCode *= count;

    hashcode = multiplier * hashcode + baseHashCode;

    updateList.add(object);
  }

在這個類中,還有兩個屬性,就是hashcode和updateList,hashcode就是這個key的hashcode,而updateList則儲存影響hashcode計算的條件,從上面可以看到有ms.getId()、getOffSet()、getLimit()、getSql()、parameter,即mybatis的mapper對應的statement的id、分頁的範圍、sql語句、設定的引數,即判斷兩次查詢是相同的查詢的條件就是以上四個條件,通過這四個條件來生成key,對應查詢的結果,在查詢的時候通過list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;來獲取。

     以上基本上就是mybatis的session一級快取的基本實現原理,當然除了存取快取之外,mybatis還提供瞭如clearLocalCache()這樣的介面方法來手動清除快取。在瞭解了上面的原理之後,其實mybatis的一級快取還是有不少不完善的地方,比如這個在資料庫資料通過別的途徑發生更改時,快取不能做到更新,所以資料的操作最好只能在一個session中,還有前面說的session執行緒不安全的問題,不過這個在spring整合時得到了解決,spring注入了一個執行緒安全的session,這個以後有時間再仔細看看原始碼再寫篇部落格來討論。

相關推薦

Mybatissession一級快取實現原理

最近由於受工作中業務需要和現有工程中dao層非orm思想的影響,覺得在有些業務場景下,並不一定非要去使用ORM框架,畢竟寫大量的實體類也是一件麻煩的事,於是著手編寫一個非ORM框架。初步完成後,底層的session並沒能像mybatis那樣能支援session的一級快取

Java網絡編程和NIO詳解7: Linux NIO Selector 的實現原理

fdt 重要 文件描述 block tor create size 註冊 comm Java網絡編程和NIO詳解7:淺談 Linux 中NIO Selector 的實現原理 轉自:https://www.jianshu.com/p/2b71ea919d49 本系列文章首發於

Mybatis的 ${ } 和 #{ }的區別

mybatis sql註入 語句 nbsp 之前 com pre 預編譯 sql 語句 一、舉例說明 1 select * from user where name = "dato"; 2 3 select * from user where name = #

MyBatis一級快取和二級快取介紹

先說快取,合理使用快取是優化中最常見的,將從資料庫中查詢出來的資料放入快取中,下次使用時不必從資料庫查詢,而是直接從快取中讀取,避免頻繁操作資料庫,減輕資料庫的壓力,同時提高系統性能。 一級快取 一級快取是SqlSession級別的快取。在操作資料庫時需要

MyBatis學習12】MyBatis一級快取

  快取的作用是減輕資料庫的壓力,提高資料庫的效能的。mybatis中提供了一級快取和二級快取,先來看一下兩個快取的示意圖:   從圖中可以看出: 一級快取是SqlSession

深入理解MyBatis一級快取與二級快取

這篇文章主要給大家深入的介紹了關於MyBatis中一級快取與二級快取的相關資料,文中詳細介紹MyBatis中一級快取與二級快取的工作原理及使用,對大傢俱有一定的參考性學習價值,需要的朋友們下面來一起看看吧。 前言 先說快取,合理使用快取是優化中最常見的,將從資料庫

express 中介軟體機制及實現原理

中介軟體機制可以讓我們在一個給定的流程中新增一個處理步驟,從而對這個流程的輸入或者輸出產生影響,或者產生一些中作用、狀態,或者攔截這個流程。中介軟體機制和tomcat的過濾器類似,這兩者都屬於責任鏈模式的具體實現。 express 中介軟體使用案例 1 2

Vue v-model指令的實現原理

vue的v-model是一個十分強大的指令,它可以自動讓原生表單元件的值自動和你選擇的值繫結, 我們來看一下它的效果:  輸入框的值和一個數據是繫結的,輸入框的值變化,和他繫結的值也會發生變化 我們在手動輸入 hello的過程中 下面和他繫結的p標籤的值也是實時

java內置的觀察者模式與動態代理的實現

所有 代理 notify play ani effect 一個 indicate protected 一.關於觀察者模式 1.將觀察者與被觀察者分離開來,當被觀察者發生變化時,將通知所有觀察者,觀察者會根據這些變化做出對應的處理。 2.jdk裏已經提供對應的Observer

JS的!=、== 、!==、===的用法和區別 JSNull與Undefined的區別 讀取XML文件 獲取路徑的方式 C#Cookie,Session,Application的用法與區別? c#反射 抽象工廠

main 收集 data- 時間設置 oba ase pdo 簡單工廠模式 1.0 var num = 1; var str = ‘1‘; var test = 1; test == num //true 相同類型 相同值 te

最近在研究多線程,JAVA多線程的幾種實現方式

進行 數據 使用 導致 效率問題 多線程 方法 sta img 多線程的實現方式:   個人認為,要說多線程的實現方式,萬變不離其宗,最基本的就是兩種1.繼承Thread類;2.實現runnable接口,本質上來說就是用來啟動線程執行任務的過程,具體來說的話,通過這

Java併發(十九):final實現原理 Java的final關鍵字

final在Java中是一個保留的關鍵字,可以宣告成員變數、方法、類以及本地變數。 一旦你將引用宣告作final,你將不能改變這個引用了,編譯器會檢查程式碼,如果你試圖將變數再次初始化的話,編譯器會報編譯錯誤。 一、final變數   final成員變量表示常量,只能被賦值一次,賦值後值不再改變(fin

做面試的不倒翁: Vue computed 實現原理

編者按:我們會不時邀請工程師談談有意思的技術細節,希望知其所以然能讓大家在面試有更出色表現。也給面試官提供更多思路。 雖然目前的技術棧已由 Vue 轉到了 React,但從之前使用 Vue 開發的多個專案實際經歷來看還是非常愉悅的,Vue 文件清晰規範,api

Vue computed 實現原理

雖然目前的技術棧已由 Vue 轉到了 React,但從之前使用 Vue 開發的多個專案實際經歷來看還是非常愉悅的,Vue 文件清晰規範,api 設計簡潔高效,對前端開發人員友好,上手快,甚至個人認為在很多場景使用 Vue 比React 開發效率更高,之前也有斷斷

MyBatis一級快取實現詳解及使用注意事項

轉自:https://blog.csdn.net/chenyao1994/article/details/79233725 0.寫在前面 MyBatis是一個簡單,小巧但功能非常強大的ORM開源框架,它的功能強大也體現在它的快取機制上。MyBatis提供了一級快取、二級快取 這兩個快取機制,

《深入理解mybatis原理(三)》 MyBatis一級快取實現詳解 及使用注意事項

0.寫在前面         MyBatis是一個簡單,小巧但功能非常強大的ORM開源框架,它的功能強大也體現在它的快取機制上。MyBatis提供了一級快取、二級快取 這兩個快取機制,能夠很好地處理和維護快取,以提高系統的效能。本文的目的則是向讀者詳細介紹MyBatis的一級快取,深入原始碼,解析MyBa

thinkPHP利用快取處理高併發的思路

此內容如有問題,請多多指教 Thinkphp預設各類快取都是以檔案快取的 改的話在配置檔案裡改 'DATA_CACHE_TYPE'=>'File',// 資料快取型別,支援:File|Db|Apc|Memcache|Shmop|Sqlite|Xcache|Apa

android手機聯絡人字母索引表的實現

實際上字母索引表的效果,可以說在現在的眾多APP中使用的非常流行,比如支付寶,微信中的聯絡人,還有購物,買票的APP中選擇全國城市,切換城市的時候,這時候的城市也就是按照一個字母索引的順序來顯示,看起來是很方便的.其實這種字母索引表的效果最開始是出現在微信的聯絡人中.因為覺

WordPress實現Markdown編輯的終極解決方案

歡迎訪問Oldpan部落格,分享人工智慧有趣訊息,持續醞釀深度學習質量文。 既然我們的部落格主要的內容是人工智慧、機器學習、深度學習,許多理論推理和公示展示是必不可少的,不能因為公式編輯的不方便而減少對數學公式的展示和編寫,在文章中一個好的公式是很重要的,一個好的公式勝似

Android的非同步載入之ListView圖片的快取及優化三

     隔了很久沒寫部落格,現在必須快速脈動回來。今天我還是接著上一個多執行緒中的非同步載入系列中的最後一個使用非同步載入實現ListView中的圖片快取及其優化。具體來說這次是一個綜合Demo.但是個人覺得裡面還算有點價值的就是裡面的圖片的快取的實現。因為老實說它確實能