java架構師學習路線-Mybatis一級緩存源碼分析

圖靈學院java架構師學習路線java

一. 爲何要有一級緩存sql

    每當咱們使用Mybatis開啓一次和數據庫的會話, 就會建立一個SqlSession對象來表示這個會話。就在這一次會話中, 咱們有可能反覆執行徹底相同的查詢語句, 這些相同的查詢語句在沒有執行過更新的狀況下返回的結果也是一致的。相信機智的你已經想到, 若是每次都去和數據庫進行交互查詢的話, 就會形成資源浪費。 因此, mybatis加入了一級緩存, 用來在一次會話中緩存查詢結果。數據庫

    總結下一級緩存的存在起到的做用: 在同一個會話裏面,屢次執行相同的sql語句(statementId, 參數, rowbounds徹底相同),會直接從內存取到緩存的結果,不會再發送sql到數據庫與數據庫交互。可是不一樣的會話裏面,即便執行的sql如出一轍,也不能使用到一級緩存。設計模式

二. 一級緩存與會話的關係數組

一級緩存也叫本地緩存,MyBatis 的一級緩存是在會話層面(SqlSession)進行緩存的。默認開啓,不須要任何的配置。緩存

首先咱們先思考一個問題,在MyBatis 執行的流程裏面,涉及到這麼多的對象,那麼緩存Cache 應該放在哪一個對象裏面去維護?session

先來進行一下推斷, 咱們已經知道一級緩存的做用範圍是會話,那麼這個對象確定是在SqlSession 裏面建立的,做爲SqlSession 的一個屬性存在。SqlSession自己是一個接口, 它的實現類DefaultSqlSession 裏面只有兩個屬性---Configuration和Executor。Configuration 是全局的,與咱們知道的一級緩存的做用範圍不符, 因此緩存只可能放在Executor 裏面維護---而事實也正是如此, SimpleExecutor/ReuseExecutor/BatchExecutor 的父類BaseExecutor 的構造函數中就持有了Cache。mybatis

那究竟是不是這樣的呢....關門, 放源碼! 架構

(1)建立會話的源碼部分:app

首先是調用DefauldSqlSessionFactory的openSession()方法, 即:開啓會話

openSession()方法中調用了openSessionFromDataSource()方法, openSessionFromDataSource()方法中先是調用 configuration.newExecutor(tx, execType)建立了執行器(executor), 而後調用DefaultSqlSession的構造方法, 並傳入了建立好的執行器(executor), 這樣就建立出了DefaultSqlSession對象並讓其持有了executor屬性。

DefauldSqlSessionFactory:

//建立會話的方法

@Override

public SqlSession openSession(ExecutorType execType) {

  return openSessionFromDataSource(execType, null, false);

}

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);

    // 注意: 看這裏!!建立Executor執行器

    final Executor executor = configuration.newExecutor(tx, execType);

    // 注意: 看這裏!!建立DefaultSqlSession, executor做爲DefaultSqlSession構造方法的一個參數傳入

    //DefaultSqlSession持有了executor

    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();

  }

}

(2)建立執行部分源碼

而建立執行器的時候, 會根據具體傳入的執行器(executor)的類型, 來選擇一個合適的執行器(executor)建立出來。可是無論最終選擇哪一個執行器, 他們都是BaseExecutor的子類 (緩存執行器除外, 涉及二級緩存相關, 這裏暫且不提, 會專門寫二級緩存的文章), 而咱們的一級緩存, 正是BaseExecutor的一個屬性, 而建立好的執行器做爲BaseExecutor的子類也有着父類的屬性。因此SqlSession對象持有了executor屬性, 而executor持有了一級緩存。咱們以前的一級緩存與會話的關係也獲得了印證。

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {

   executorType = executorType == null ? defaultExecutorType : executorType;

   executorType = executorType == null ? ExecutorType.SIMPLE : executorType;

   Executor executor;

   if (ExecutorType.BATCH == executorType) {

      // 批處理執行器

      executor = new BatchExecutor(this, transaction);

   } else if (ExecutorType.REUSE == executorType) {

      // 可重用執行器

      executor = new ReuseExecutor(this, transaction);

   } else {

      // 簡單執行器

      executor = new SimpleExecutor(this, transaction);

   }

   // 若是開啓緩存,則使用緩存執行器(這裏涉及二級緩存部分, 暫時先不考慮, 有興趣請等待個人下一篇講二級緩存的文章 ^^)

   if (cacheEnabled) {

      executor = new CachingExecutor(executor);

   }

   // 插件執行

   executor = (Executor) interceptorChain.pluginAll(executor);

   return executor;

}

看下BaseExecutor的屬性, 它持有了PerpetualCache, 也就是一級緩存。

public abstract class BaseExecutor implements Executor:

  protected PerpetualCache localCache;

既然PerpetualCache就是一級緩存了, 咱們如今就來康康一級緩存究竟是個啥吧, 沒錯, 最終這些東西都存在了一個HashMap裏面。

public class PerpetualCache implements Cache {

  private final String id;

  //一級緩存最終存入容器

  private Map

  public PerpetualCache(String id) {

    this.id = id;

  }

  @Override

  public String getId() {

    return id;

  }

  @Override

  public int getSize() {

    return cache.size();

  }

  @Override

  public void putObject(Object key, Object value) {

    cache.put(key, value);

  }

  @Override

  public Object getObject(Object key) {

    return cache.get(key);

  }

  @Override

  public Object removeObject(Object key) {

    return cache.remove(key);

  }

  @Override

  public void clear() {

    cache.clear();

  }

}

三. 一級緩存的生命週期

當會話結束時,SqlSession對象及其內部的Executor對象還有Cache對象也一併釋放掉。

若是SqlSession調用了close()方法,會釋放掉一級緩存Cache對象,一級緩存將不可用;

若是SqlSession調用了clearCache(),會清空Cache對象中的數據,可是該對象仍可以使用;

SqlSession中執行了任何一個update操做(update()、delete()、insert()) ,都會清空Cache對象的數據,可是該對象能夠繼續使用;

四. 一級緩存的執行流程概要 

    緩存執行的大體思路與咱們熟知的緩存思想一致。

對於某個查詢,根據statementId,params,rowBounds來構建一個key值,根據這個key值去緩存Cache中取出對應的key值存儲的緩存結果

判斷從Cache中根據特定的key值取的數據是否爲空,便是否命中;

若是命中,則直接將緩存結果返回;

若是沒命中:

     去數據庫中查詢數據,獲得查詢結果;

將key和查詢到的結果分別做爲key,value對存儲到Cache中;

將查詢結果返回;

具體是怎麼實現的呢, 這裏又要放源碼了:

(1)查詢入口:

能夠看見, 查詢最終是調用了DefaultSqlSession持有的屬性executor的query()方法。

DefaultSqlSession: 

private final Configuration configuration;

private final Executor executor;

public

   try {

      //根據傳入的statementId,獲取MappedStatement對象

      MappedStatement ms = configuration.getMappedStatement(statement);

      //RowBounds是用來邏輯分頁(按照條件將數據從數據庫查詢到內存中,在內存中進行分頁)

      // wrapCollection(parameter)是用來裝飾集合或者數組參數

      // 注意:看這裏 !! 調用執行器的查詢方法

      return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);

   } catch (Exception e) {

      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);

   } finally {

      ErrorContext.instance().reset();

   }

}

executor的query()方法進行了一級緩存的邏輯, 會調用localCache.getObject(key)從緩存中獲取數據, 若是獲取不到, 又會調用queryFromDatabase()方法。見名知意, 這個方法就是用來來與數據庫進行交互取數據。

在queryFromDatabase()方法中, 調用doQuery()來執行查詢, 再把獲得的結果調用localCache.putObject(key, list)放入一級緩存。

若是咱們繼續查看doQuery()方法, 就會發現這個方法是抽象的, 這裏涉及到了一個經常使用的設計模式: 模板模式。真正的doQuery()方法的實現是在BaseExecutor的子類方法中去完成的, 完成從數據庫中查詢數據封裝數據的部分, 暫且不提。

模板模式(Template Pattern): 一個抽象類公開定義了執行它的方法的方式/模板。它的子類能夠按須要重寫方法實現,但調用將以抽象類中定義的方式進行。

BaseExcecutor:

protected PerpetualCache localCache;

Override

public

  ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());

  if (closed) {

    throw new ExecutorException("Executor was closed.");

  }

  if (queryStack == 0 && ms.isFlushCacheRequired()) {

    clearLocalCache();//清除緩存

  }

  List

  try {

    queryStack++;

    // 注意: 看這裏!!從一級緩存中獲取數據

    list = resultHandler == null ? (List

    if (list != null) {

      handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);

    } else {

   // 注意: 看這裏!!若是一級緩存沒有數據,則從數據庫查詢數據

      list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);

    }

  } finally {

    queryStack--;

  }

  if (queryStack == 0) {

    for (DeferredLoad deferredLoad : deferredLoads) {

      deferredLoad.load();

    }

    // issue #601

    deferredLoads.clear();

    if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {

      // issue #482

      clearLocalCache();

    }

  }

  return list;

}

private

  List

  localCache.putObject(key, EXECUTION_PLACEHOLDER);

  try {

    // 注意: 看這裏!!執行查詢

    list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);

  } finally {

    localCache.removeObject(key);

  }

  // 注意: 看這裏!! 放入緩存

  localCache.putObject(key, list);

  if (ms.getStatementType() == StatementType.CALLABLE) {

    localOutputParameterCache.putObject(key, parameter);

  }

  return list;

}

// 注意: 看這裏!! 這是一個抽象的方法, 等着子類去實現

protected abstract

    throws SQLException;

五. 結構與總計(不涉及二級緩存)

小結: sqlSession 持有 BaseExecutor , BaseExecutor持有了一級緩存, 查詢時調用BaseExecutor的query()方法, 並在query()方法中完成了一級緩存的功能。

緩存查到了就返回查詢結果, 查詢不到就調用queryFromDatabase()方法, 而後queryFromDatabase()方法中調用doQuery()方法從數據庫中查詢數據, 而後放入一級緩存, 其中doQuery()方法是抽象的 , 須要BaseExecutor的不一樣類型子類具體實現。

總體結構圖以下:

java高級架構師教程,java架構師培訓,圖靈學院

怎麼樣, 如今對mybatis一級緩存是如何實現的是否是有了大概的瞭解~圖靈學院

一. 爲何要有一級緩存

    每當咱們使用Mybatis開啓一次和數據庫的會話, 就會建立一個SqlSession對象來表示這個會話。就在這一次會話中, 咱們有可能反覆執行徹底相同的查詢語句, 這些相同的查詢語句在沒有執行過更新的狀況下返回的結果也是一致的。相信機智的你已經想到, 若是每次都去和數據庫進行交互查詢的話, 就會形成資源浪費。 因此, mybatis加入了一級緩存, 用來在一次會話中緩存查詢結果。

    總結下一級緩存的存在起到的做用: 在同一個會話裏面,屢次執行相同的sql語句(statementId, 參數, rowbounds徹底相同),會直接從內存取到緩存的結果,不會再發送sql到數據庫與數據庫交互。可是不一樣的會話裏面,即便執行的sql如出一轍,也不能使用到一級緩存。

二. 一級緩存與會話的關係

一級緩存也叫本地緩存,MyBatis 的一級緩存是在會話層面(SqlSession)進行緩存的。默認開啓,不須要任何的配置。

首先咱們先思考一個問題,在MyBatis 執行的流程裏面,涉及到這麼多的對象,那麼緩存Cache 應該放在哪一個對象裏面去維護?

先來進行一下推斷, 咱們已經知道一級緩存的做用範圍是會話,那麼這個對象確定是在SqlSession 裏面建立的,做爲SqlSession 的一個屬性存在。SqlSession自己是一個接口, 它的實現類DefaultSqlSession 裏面只有兩個屬性---Configuration和Executor。Configuration 是全局的,與咱們知道的一級緩存的做用範圍不符, 因此緩存只可能放在Executor 裏面維護---而事實也正是如此, SimpleExecutor/ReuseExecutor/BatchExecutor 的父類BaseExecutor 的構造函數中就持有了Cache。

那究竟是不是這樣的呢....關門, 放源碼! 

(1)建立會話的源碼部分:

首先是調用DefauldSqlSessionFactory的openSession()方法, 即:開啓會話

openSession()方法中調用了openSessionFromDataSource()方法, openSessionFromDataSource()方法中先是調用 configuration.newExecutor(tx, execType)建立了執行器(executor), 而後調用DefaultSqlSession的構造方法, 並傳入了建立好的執行器(executor), 這樣就建立出了DefaultSqlSession對象並讓其持有了executor屬性。

DefauldSqlSessionFactory:

//建立會話的方法

@Override

public SqlSession openSession(ExecutorType execType) {

  return openSessionFromDataSource(execType, null, false);

}

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);

    // 注意: 看這裏!!建立Executor執行器

    final Executor executor = configuration.newExecutor(tx, execType);

    // 注意: 看這裏!!建立DefaultSqlSession, executor做爲DefaultSqlSession構造方法的一個參數傳入

    //DefaultSqlSession持有了executor

    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();

  }

}

(2)建立執行部分源碼

而建立執行器的時候, 會根據具體傳入的執行器(executor)的類型, 來選擇一個合適的執行器(executor)建立出來。可是無論最終選擇哪一個執行器, 他們都是BaseExecutor的子類 (緩存執行器除外, 涉及二級緩存相關, 這裏暫且不提, 會專門寫二級緩存的文章), 而咱們的一級緩存, 正是BaseExecutor的一個屬性, 而建立好的執行器做爲BaseExecutor的子類也有着父類的屬性。因此SqlSession對象持有了executor屬性, 而executor持有了一級緩存。咱們以前的一級緩存與會話的關係也獲得了印證。

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {

   executorType = executorType == null ? defaultExecutorType : executorType;

   executorType = executorType == null ? ExecutorType.SIMPLE : executorType;

   Executor executor;

   if (ExecutorType.BATCH == executorType) {

      // 批處理執行器

      executor = new BatchExecutor(this, transaction);

   } else if (ExecutorType.REUSE == executorType) {

      // 可重用執行器

      executor = new ReuseExecutor(this, transaction);

   } else {

      // 簡單執行器

      executor = new SimpleExecutor(this, transaction);

   }

   // 若是開啓緩存,則使用緩存執行器(這裏涉及二級緩存部分, 暫時先不考慮, 有興趣請等待個人下一篇講二級緩存的文章 ^^)

   if (cacheEnabled) {

      executor = new CachingExecutor(executor);

   }

   // 插件執行

   executor = (Executor) interceptorChain.pluginAll(executor);

   return executor;

}

看下BaseExecutor的屬性, 它持有了PerpetualCache, 也就是一級緩存。

public abstract class BaseExecutor implements Executor:

  protected PerpetualCache localCache;

既然PerpetualCache就是一級緩存了, 咱們如今就來康康一級緩存究竟是個啥吧, 沒錯, 最終這些東西都存在了一個HashMap裏面。

public class PerpetualCache implements Cache {

  private final String id;

  //一級緩存最終存入容器

  private Map

  public PerpetualCache(String id) {

    this.id = id;

  }

  @Override

  public String getId() {

    return id;

  }

  @Override

  public int getSize() {

    return cache.size();

  }

  @Override

  public void putObject(Object key, Object value) {

    cache.put(key, value);

  }

  @Override

  public Object getObject(Object key) {

    return cache.get(key);

  }

  @Override

  public Object removeObject(Object key) {

    return cache.remove(key);

  }

  @Override

  public void clear() {

    cache.clear();

  }

}

三. 一級緩存的生命週期

當會話結束時,SqlSession對象及其內部的Executor對象還有Cache對象也一併釋放掉。

若是SqlSession調用了close()方法,會釋放掉一級緩存Cache對象,一級緩存將不可用;

若是SqlSession調用了clearCache(),會清空Cache對象中的數據,可是該對象仍可以使用;

SqlSession中執行了任何一個update操做(update()、delete()、insert()) ,都會清空Cache對象的數據,可是該對象能夠繼續使用;

四. 一級緩存的執行流程概要 

    緩存執行的大體思路與咱們熟知的緩存思想一致。

對於某個查詢,根據statementId,params,rowBounds來構建一個key值,根據這個key值去緩存Cache中取出對應的key值存儲的緩存結果

判斷從Cache中根據特定的key值取的數據是否爲空,便是否命中;

若是命中,則直接將緩存結果返回;

若是沒命中:

     去數據庫中查詢數據,獲得查詢結果;

將key和查詢到的結果分別做爲key,value對存儲到Cache中;

將查詢結果返回;

具體是怎麼實現的呢, 這裏又要放源碼了:

(1)查詢入口:

能夠看見, 查詢最終是調用了DefaultSqlSession持有的屬性executor的query()方法。

DefaultSqlSession: 

private final Configuration configuration;

private final Executor executor;

public

   try {

      //根據傳入的statementId,獲取MappedStatement對象

      MappedStatement ms = configuration.getMappedStatement(statement);

      //RowBounds是用來邏輯分頁(按照條件將數據從數據庫查詢到內存中,在內存中進行分頁)

      // wrapCollection(parameter)是用來裝飾集合或者數組參數

      // 注意:看這裏 !! 調用執行器的查詢方法

      return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);

   } catch (Exception e) {

      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);

   } finally {

      ErrorContext.instance().reset();

   }

}

executor的query()方法進行了一級緩存的邏輯, 會調用localCache.getObject(key)從緩存中獲取數據, 若是獲取不到, 又會調用queryFromDatabase()方法。見名知意, 這個方法就是用來來與數據庫進行交互取數據。

在queryFromDatabase()方法中, 調用doQuery()來執行查詢, 再把獲得的結果調用localCache.putObject(key, list)放入一級緩存。

若是咱們繼續查看doQuery()方法, 就會發現這個方法是抽象的, 這裏涉及到了一個經常使用的設計模式: 模板模式。真正的doQuery()方法的實現是在BaseExecutor的子類方法中去完成的, 完成從數據庫中查詢數據封裝數據的部分, 暫且不提。

模板模式(Template Pattern): 一個抽象類公開定義了執行它的方法的方式/模板。它的子類能夠按須要重寫方法實現,但調用將以抽象類中定義的方式進行。

BaseExcecutor:

protected PerpetualCache localCache;

Override

public

  ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());

  if (closed) {

    throw new ExecutorException("Executor was closed.");

  }

  if (queryStack == 0 && ms.isFlushCacheRequired()) {

    clearLocalCache();//清除緩存

  }

  List

  try {

    queryStack++;

    // 注意: 看這裏!!從一級緩存中獲取數據

    list = resultHandler == null ? (List

    if (list != null) {

      handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);

    } else {

   // 注意: 看這裏!!若是一級緩存沒有數據,則從數據庫查詢數據

      list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);

    }

  } finally {

    queryStack--;

  }

  if (queryStack == 0) {

    for (DeferredLoad deferredLoad : deferredLoads) {

      deferredLoad.load();

    }

    // issue #601

    deferredLoads.clear();

    if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {

      // issue #482

      clearLocalCache();

    }

  }

  return list;

}

private

  List

  localCache.putObject(key, EXECUTION_PLACEHOLDER);

  try {

    // 注意: 看這裏!!執行查詢

    list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);

  } finally {

    localCache.removeObject(key);

  }

  // 注意: 看這裏!! 放入緩存

  localCache.putObject(key, list);

  if (ms.getStatementType() == StatementType.CALLABLE) {

    localOutputParameterCache.putObject(key, parameter);

  }

  return list;

}

// 注意: 看這裏!! 這是一個抽象的方法, 等着子類去實現

protected abstract

    throws SQLException;

五. 結構與總計(不涉及二級緩存)

小結: sqlSession 持有 BaseExecutor , BaseExecutor持有了一級緩存, 查詢時調用BaseExecutor的query()方法, 並在query()方法中完成了一級緩存的功能。

緩存查到了就返回查詢結果, 查詢不到就調用queryFromDatabase()方法, 而後queryFromDatabase()方法中調用doQuery()方法從數據庫中查詢數據, 而後放入一級緩存, 其中doQuery()方法是抽象的 , 須要BaseExecutor的不一樣類型子類具體實現。

總體結構圖以下:

java高級架構師教程,java架構師培訓,圖靈學院

怎麼樣, 如今對mybatis一級緩存是如何實現的是否是有了大概的瞭解~圖靈學院

一. 爲何要有一級緩存

    每當咱們使用Mybatis開啓一次和數據庫的會話, 就會建立一個SqlSession對象來表示這個會話。就在這一次會話中, 咱們有可能反覆執行徹底相同的查詢語句, 這些相同的查詢語句在沒有執行過更新的狀況下返回的結果也是一致的。相信機智的你已經想到, 若是每次都去和數據庫進行交互查詢的話, 就會形成資源浪費。 因此, mybatis加入了一級緩存, 用來在一次會話中緩存查詢結果。

    總結下一級緩存的存在起到的做用: 在同一個會話裏面,屢次執行相同的sql語句(statementId, 參數, rowbounds徹底相同),會直接從內存取到緩存的結果,不會再發送sql到數據庫與數據庫交互。可是不一樣的會話裏面,即便執行的sql如出一轍,也不能使用到一級緩存。

二. 一級緩存與會話的關係

一級緩存也叫本地緩存,MyBatis 的一級緩存是在會話層面(SqlSession)進行緩存的。默認開啓,不須要任何的配置。

首先咱們先思考一個問題,在MyBatis 執行的流程裏面,涉及到這麼多的對象,那麼緩存Cache 應該放在哪一個對象裏面去維護?

先來進行一下推斷, 咱們已經知道一級緩存的做用範圍是會話,那麼這個對象確定是在SqlSession 裏面建立的,做爲SqlSession 的一個屬性存在。SqlSession自己是一個接口, 它的實現類DefaultSqlSession 裏面只有兩個屬性---Configuration和Executor。Configuration 是全局的,與咱們知道的一級緩存的做用範圍不符, 因此緩存只可能放在Executor 裏面維護---而事實也正是如此, SimpleExecutor/ReuseExecutor/BatchExecutor 的父類BaseExecutor 的構造函數中就持有了Cache。

那究竟是不是這樣的呢....關門, 放源碼! 

(1)建立會話的源碼部分:

首先是調用DefauldSqlSessionFactory的openSession()方法, 即:開啓會話

openSession()方法中調用了openSessionFromDataSource()方法, openSessionFromDataSource()方法中先是調用 configuration.newExecutor(tx, execType)建立了執行器(executor), 而後調用DefaultSqlSession的構造方法, 並傳入了建立好的執行器(executor), 這樣就建立出了DefaultSqlSession對象並讓其持有了executor屬性。

DefauldSqlSessionFactory:

//建立會話的方法

@Override

public SqlSession openSession(ExecutorType execType) {

  return openSessionFromDataSource(execType, null, false);

}

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);

    // 注意: 看這裏!!建立Executor執行器

    final Executor executor = configuration.newExecutor(tx, execType);

    // 注意: 看這裏!!建立DefaultSqlSession, executor做爲DefaultSqlSession構造方法的一個參數傳入

    //DefaultSqlSession持有了executor

    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();

  }

}

(2)建立執行部分源碼

而建立執行器的時候, 會根據具體傳入的執行器(executor)的類型, 來選擇一個合適的執行器(executor)建立出來。可是無論最終選擇哪一個執行器, 他們都是BaseExecutor的子類 (緩存執行器除外, 涉及二級緩存相關, 這裏暫且不提, 會專門寫二級緩存的文章), 而咱們的一級緩存, 正是BaseExecutor的一個屬性, 而建立好的執行器做爲BaseExecutor的子類也有着父類的屬性。因此SqlSession對象持有了executor屬性, 而executor持有了一級緩存。咱們以前的一級緩存與會話的關係也獲得了印證。

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {

   executorType = executorType == null ? defaultExecutorType : executorType;

   executorType = executorType == null ? ExecutorType.SIMPLE : executorType;

   Executor executor;

   if (ExecutorType.BATCH == executorType) {

      // 批處理執行器

      executor = new BatchExecutor(this, transaction);

   } else if (ExecutorType.REUSE == executorType) {

      // 可重用執行器

      executor = new ReuseExecutor(this, transaction);

   } else {

      // 簡單執行器

      executor = new SimpleExecutor(this, transaction);

   }

   // 若是開啓緩存,則使用緩存執行器(這裏涉及二級緩存部分, 暫時先不考慮, 有興趣請等待個人下一篇講二級緩存的文章 ^^)

   if (cacheEnabled) {

      executor = new CachingExecutor(executor);

   }

   // 插件執行

   executor = (Executor) interceptorChain.pluginAll(executor);

   return executor;

}

看下BaseExecutor的屬性, 它持有了PerpetualCache, 也就是一級緩存。

public abstract class BaseExecutor implements Executor:

  protected PerpetualCache localCache;

既然PerpetualCache就是一級緩存了, 咱們如今就來康康一級緩存究竟是個啥吧, 沒錯, 最終這些東西都存在了一個HashMap裏面。

public class PerpetualCache implements Cache {

  private final String id;

  //一級緩存最終存入容器

  private Map

  public PerpetualCache(String id) {

    this.id = id;

  }

  @Override

  public String getId() {

    return id;

  }

  @Override

  public int getSize() {

    return cache.size();

  }

  @Override

  public void putObject(Object key, Object value) {

    cache.put(key, value);

  }

  @Override

  public Object getObject(Object key) {

    return cache.get(key);

  }

  @Override

  public Object removeObject(Object key) {

    return cache.remove(key);

  }

  @Override

  public void clear() {

    cache.clear();

  }

}

三. 一級緩存的生命週期

當會話結束時,SqlSession對象及其內部的Executor對象還有Cache對象也一併釋放掉。

若是SqlSession調用了close()方法,會釋放掉一級緩存Cache對象,一級緩存將不可用;

若是SqlSession調用了clearCache(),會清空Cache對象中的數據,可是該對象仍可以使用;

SqlSession中執行了任何一個update操做(update()、delete()、insert()) ,都會清空Cache對象的數據,可是該對象能夠繼續使用;

四. 一級緩存的執行流程概要 

    緩存執行的大體思路與咱們熟知的緩存思想一致。

對於某個查詢,根據statementId,params,rowBounds來構建一個key值,根據這個key值去緩存Cache中取出對應的key值存儲的緩存結果

判斷從Cache中根據特定的key值取的數據是否爲空,便是否命中;

若是命中,則直接將緩存結果返回;

若是沒命中:

     去數據庫中查詢數據,獲得查詢結果;

將key和查詢到的結果分別做爲key,value對存儲到Cache中;

將查詢結果返回;

具體是怎麼實現的呢, 這裏又要放源碼了:

(1)查詢入口:

能夠看見, 查詢最終是調用了DefaultSqlSession持有的屬性executor的query()方法。

DefaultSqlSession: 

private final Configuration configuration;

private final Executor executor;

public

   try {

      //根據傳入的statementId,獲取MappedStatement對象

      MappedStatement ms = configuration.getMappedStatement(statement);

      //RowBounds是用來邏輯分頁(按照條件將數據從數據庫查詢到內存中,在內存中進行分頁)

      // wrapCollection(parameter)是用來裝飾集合或者數組參數

      // 注意:看這裏 !! 調用執行器的查詢方法

      return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);

   } catch (Exception e) {

      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);

   } finally {

      ErrorContext.instance().reset();

   }

}

executor的query()方法進行了一級緩存的邏輯, 會調用localCache.getObject(key)從緩存中獲取數據, 若是獲取不到, 又會調用queryFromDatabase()方法。見名知意, 這個方法就是用來來與數據庫進行交互取數據。

在queryFromDatabase()方法中, 調用doQuery()來執行查詢, 再把獲得的結果調用localCache.putObject(key, list)放入一級緩存。

若是咱們繼續查看doQuery()方法, 就會發現這個方法是抽象的, 這裏涉及到了一個經常使用的設計模式: 模板模式。真正的doQuery()方法的實現是在BaseExecutor的子類方法中去完成的, 完成從數據庫中查詢數據封裝數據的部分, 暫且不提。

模板模式(Template Pattern): 一個抽象類公開定義了執行它的方法的方式/模板。它的子類能夠按須要重寫方法實現,但調用將以抽象類中定義的方式進行。

BaseExcecutor:

protected PerpetualCache localCache;

Override

public

  ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());

  if (closed) {

    throw new ExecutorException("Executor was closed.");

  }

  if (queryStack == 0 && ms.isFlushCacheRequired()) {

    clearLocalCache();//清除緩存

  }

  List

  try {

    queryStack++;

    // 注意: 看這裏!!從一級緩存中獲取數據

    list = resultHandler == null ? (List

    if (list != null) {

      handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);

    } else {

   // 注意: 看這裏!!若是一級緩存沒有數據,則從數據庫查詢數據

      list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);

    }

  } finally {

    queryStack--;

  }

  if (queryStack == 0) {

    for (DeferredLoad deferredLoad : deferredLoads) {

      deferredLoad.load();

    }

    // issue #601

    deferredLoads.clear();

    if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {

      // issue #482

      clearLocalCache();

    }

  }

  return list;

}

private

  List

  localCache.putObject(key, EXECUTION_PLACEHOLDER);

  try {

    // 注意: 看這裏!!執行查詢

    list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);

  } finally {

    localCache.removeObject(key);

  }

  // 注意: 看這裏!! 放入緩存

  localCache.putObject(key, list);

  if (ms.getStatementType() == StatementType.CALLABLE) {

    localOutputParameterCache.putObject(key, parameter);

  }

  return list;

}

// 注意: 看這裏!! 這是一個抽象的方法, 等着子類去實現

protected abstract

    throws SQLException;

五. 結構與總計(不涉及二級緩存)

小結: sqlSession 持有 BaseExecutor , BaseExecutor持有了一級緩存, 查詢時調用BaseExecutor的query()方法, 並在query()方法中完成了一級緩存的功能。

緩存查到了就返回查詢結果, 查詢不到就調用queryFromDatabase()方法, 而後queryFromDatabase()方法中調用doQuery()方法從數據庫中查詢數據, 而後放入一級緩存, 其中doQuery()方法是抽象的 , 須要BaseExecutor的不一樣類型子類具體實現。

總體結構圖以下:

java高級架構師教程,java架構師培訓,圖靈學院

怎麼樣, 如今對mybatis一級緩存是如何實現的是否是有了大概的瞭解~

相關文章
相關標籤/搜索