Mybatis的SqlSession運行原理

来源:https://www.cnblogs.com/jian0110/archive/2018/08/10/9452592.html
-Advertisement-
Play Games

前言 SqlSession是Mybatis最重要的構建之一,可以簡單的認為Mybatis一系列的配置目的是生成類似 JDBC生成的Connection對象的SqlSession對象,這樣才能與資料庫開啟“溝通”,通過SqlSession可以實現增刪改查(當然現在更加推薦是使用Mapper介面形式), ...


前言

  SqlSession是Mybatis最重要的構建之一,可以簡單的認為Mybatis一系列的配置目的是生成類似 JDBC生成的Connection對象的SqlSession對象,這樣才能與資料庫開啟“溝通”,通過SqlSession可以實現增刪改查(當然現在更加推薦是使用Mapper介面形式),那麼它是如何執行實現的,這就是本篇博文所介紹的東西,其中會涉及到簡單的源碼講解。

  瞭解SqlSession的運作原理是學習Mybatis插件的必經之路,因為Mybatis的插件會在SqlSession運行過程中“插入”運行,如果沒有很好理解的話,Mybatis插件可能會覆蓋相應的源碼造成嚴重的問題。鑒於此,本篇博文儘量詳細介紹SqlSession運作原理!

  建議:在我之前的博文《Mybatis緩存(1)--------系統緩存及簡單配置介紹》中介紹到SqlSession的產生過程,可以先理解後再讀此博文可能會更加好理解!

  註:本篇博文也是我最近真正理解Mybatis才開始編寫的,可能有些地方不太準確,如果有錯誤之處敬請指出,另外創作不易,望轉載告之,謝謝!

  參數資料:《深入淺出Mybatis基礎原理與實踐》(我這裡只有電子版PDF,需要的朋友可以聯繫我)

 


 

1、SqlSession簡單介紹

  (1)SqlSession簡單原理介紹

  SqlSession提供select/insert/update/delete方法,在舊版本中使用使用SqlSession介面的這些方法,但是新版的Mybatis中就會建議使用Mapper介面的方法。

  映射器其實就是一個動態代理對象,進入到MapperMethod的execute方法就能簡單找到SqlSession的刪除、更新、查詢、選擇方法,從底層實現來說:通過動態代理技術,讓介面跑起來,之後採用命令模式,最後還是採用了SqlSession的介面方法(getMapper()方法等到Mapper)執行SQL查詢(也就是說Mapper介面方法的實現底層還是採用SqlSession介面方法實現的)。

  註:以上雖然只是簡單的描述,但實際上源碼相對複雜,下麵將結合源碼進行簡單的介紹!

  (2)SqlSession重要的四個對象

    1)Execute:調度執行StatementHandler、ParmmeterHandler、ResultHandler執行相應的SQL語句;

    2)StatementHandler:使用資料庫中Statement(PrepareStatement)執行操作,即底層是封裝好了的prepareStatement;

    3)ParammeterHandler:處理SQL參數;

    4)ResultHandler:結果集ResultSet封裝處理返回。

2、SqlSession四大對象

(1)Execute執行器:

  執行器起到至關重要的作用,它是真正執行Java與資料庫交互的東西,參與了整個SQL查詢執行過程中。

1)主要有三種執行器:簡易執行器SIMPLE(不配置就是預設執行器REUSE是一種重用預處理語句、BATCH批量更新、批量專用處理器

package org.apache.ibatis.session;

/**
 * @author Clinton Begin
 */
public enum ExecutorType {
  SIMPLE, REUSE, BATCH
}

2)執行器作用:Executor會先調用StatementHandlerprepare()方法預編譯SQL語句,同時設置一些基本的運行參數,然後調用StatementHandler的parameterize()方法(實際上是啟用了ParameterHandler設置參數)設置參數,resultHandler再組裝查詢結果返回調用者完成一次查詢完成預編譯,簡單總結起來就是即先預編譯SQL語句,之後設置參數(跟JDBC的prepareStatement過程類似)最後如果有查詢結果就會組裝返回。

首先,以SimpleExecutor為例,查看源碼我們得到如下幾點重要知識點:

第一:Executor通過Configuration對象中newExecutor()方法中選擇相應的執行器生成

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

(註:最後interceptorChain.pluginAll()中執行層層動態代理,最後在可以在調用真正的Executor前可以修改插件代碼,這也就是為什麼學會Mybatis的插件必須要知道SqlSession的運行過程)

第二:在執行器中StatementHandler是根據Configuration構建的

public SimpleExecutor(Configuration configuration, Transaction transaction) {
    super(configuration, transaction);
  }

  @Override
  public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.update(stmt);
    } finally {
      closeStatement(stmt);
    }
  }

第三:Executor會執行StatementHandler的prepare()方法進行預編譯---->填入connection對象等參數---->再調用parameterize()方法設置參數---->完成預編譯

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    Connection connection = getConnection(statementLog);
    stmt = handler.prepare(connection, transaction.getTimeout());
    handler.parameterize(stmt);
    return stmt;
  } 

  總結以上繪製簡單思維圖如下:

      

 

 

(2)StatementHanlder資料庫會話器

      1)作用:簡單來說就是專門處理資料庫會話。詳細來說就是進行預編譯並且調用ParameterHandler的setParameters()方法設置參數

      2)資料庫會話器主要有三種:SimpleStatementHandler、PrepareStatementHandler、CallableStatementHandler,分別對應Executor的三種執行器(SIMPLE、REUSE、BATCH)

      我們從上述Executor的prepareStatement()方法中調用了StatementHandler的parameterize()開始一步步地查看源碼,如下得到幾點重要的知識點:

      第一:StatementHandler的生成是由Configuration方法中newStatementHandler()方法生成的,但是正在創建的是實現了StatementHandler介面的RoutingStatementHandler對象

 public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject,
                               RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { StatementHandler statementHandler
= new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql); statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler); return statementHandler; }

       第二:RoutingStatementHandler的通過適配器模式找到對應(根據上下文)的StatementHandler執行的,並且有SimpleStatementHandler、PrepareStatementHandler、CallableStatementHandler,分別對應Executor的三種執行器(SIMPLE、REUSE、BATCH)

  public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {

    switch (ms.getStatementType()) {
      case STATEMENT:
        delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case PREPARED:
        delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case CALLABLE:
        delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      default:
        throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
    }

  

       之後主要以PrepareStatementHandler為例,我們觀察到:它是實現BaseStatementHandler介面的,最後BaseStatementHandler又是實現StatementHandler介面的

public class PreparedStatementHandler extends BaseStatementHandler
......
public abstract class BaseStatementHandler implements StatementHandler

      它主要有三種方法:prepareparameterizequery我們查看源碼:

      第三:在BaseStatementHandler中重寫prepare()方法,instantiateStatement()方法完成預編譯,之後設置一些基礎配置(獲取最大行數,超時)

@Override
  public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
    ErrorContext.instance().sql(boundSql.getSql());
    Statement statement = null;
    try {
      statement = instantiateStatement(connection);
      setStatementTimeout(statement, transactionTimeout);
      setFetchSize(statement);
      return statement;
    } catch (SQLException e) {
      closeStatement(statement);
      throw e;
    } catch (Exception e) {
      closeStatement(statement);
      throw new ExecutorException("Error preparing statement.  Cause: " + e, e);
    }
  }

      第四:instantiateStatement()預編譯實際上也是使用了JDBC的prepareStatement()完成預編譯

  @Override
  protected Statement instantiateStatement(Connection connection) throws SQLException {
    String sql = boundSql.getSql();
    if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
      String[] keyColumnNames = mappedStatement.getKeyColumns();
      if (keyColumnNames == null) {
        return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
      } else {
        return connection.prepareStatement(sql, keyColumnNames);
      }
    } else if (mappedStatement.getResultSetType() != null) {
      return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
    } else {
      return connection.prepareStatement(sql);
    }
  }

      第五:在prepareStatement中重寫parameterize()方法。prepare()預編譯完成之後,Executor會調用parameterize()方法(在上面的Executor部分中已經做了介紹),實際上是調用ParameterHandler的setParameters()方法

 @Override
  public void parameterize(Statement statement) throws SQLException {
    parameterHandler.setParameters((PreparedStatement) statement);
  }

  (3)ParameterHandler參數處理器

      作用:對預編譯中參數進行設置,如果有配置typeHandler,自然會對註冊的typeHandler對參數進行處理

      查看並學習源碼,得到以下幾點重要知識點:

      第一:Mybatis提供了ParamterHandler的預設實現類DefalutParameterHandler

      

public interface ParameterHandler {

  Object getParameterObject();

  void setParameters(PreparedStatement ps)
      throws SQLException;

}

      (其中:getParameterObject是返回參數對象,setParameters()是設置預編譯參數)

      第二:從parameterObject中取到參數,然後使用typeHandler(註冊在Configuration中)進行參數處理:

  @Override
  public void setParameters(PreparedStatement ps) {
    ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings != null) {
      for (int i = 0; i < parameterMappings.size(); i++) {
        ParameterMapping parameterMapping = parameterMappings.get(i);
        if (parameterMapping.getMode() != ParameterMode.OUT) {
          Object value;
          String propertyName = parameterMapping.getProperty();
          if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
            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);
          }
          TypeHandler typeHandler = parameterMapping.getTypeHandler();
          JdbcType jdbcType = parameterMapping.getJdbcType();
          if (value == null && jdbcType == null) {
            jdbcType = configuration.getJdbcTypeForNull();
          }
          try {
            typeHandler.setParameter(ps, i + 1, value, jdbcType);
          } catch (TypeException e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          } catch (SQLException e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          }
        }
      }
    }
  }

  (4)ResultSetHandler結果集處理器

      作用:很簡單,就是組裝結果返回結果集

    第一:ResultSetHandler介面,handlerResultSets()是包裝並返回結果集的,handleOutputParameters()是處理存儲過程輸出參數的

public interface ResultSetHandler {

  <E> List<E> handleResultSets(Statement stmt) throws SQLException;

  <E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException;

  void handleOutputParameters(CallableStatement cs) throws SQLException;

    第二:Mybatis提供了預設的ResultSetHandler實現類DefaultResultSetHandler,其中重點是handlerResultSets()的實現,但是其實現過程比較複雜,這裡不過多介紹(emmmmm....個人目前能力還達理解,仍需努力)

    

    第三:在Executor中doQuery()方法返回了封裝的結果集

 @Override
  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

    第四:實際上是返回結果是調用了resultSetHandler的handleResultSets()方法

  @Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    return resultSetHandler.<E> handleResultSets(ps);
  }

3、SqlSession運行總結

(1)文字總結

 SqlSession的運行主要是依靠Executor執行器調用(調度)StatementHandler、parameterHanlder、ResultSetHandler,Executor首先通過創建StamentHandler執行預編譯並設置參數運行,而整個過程需要如下幾步才能完成:

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    Connection connection = getConnection(statementLog);
    stmt = handler.prepare(connection, transaction.getTimeout());
    handler.parameterize(stmt);
    return stmt;
  }

  1)prepare預編譯SQL

    由適配模式生成的RoutingStatementHandler根據上下文選擇生成三種相應的XXXStatementHandler;

    在生成的XXXStatementHandler內部instantiateStatement()方法執行底層JDBC的prepareStatement()方法完成預編譯

  2)parameterize設置參數

    預設是DefaultParameterHandler(實現了parameterHandler介面)中setParameter()方法完成參數配置,其中參數從ParameterObject中取出,交給typeHandler處理

  3)doUpdate/doQuery執行SQL

    返回的結果通過預設的DefaultResultSetHandler(實現了ResultSetHandler介面)封裝

(2)運行圖總結

       1)SqlSession內部總運行圖

                                       

       2)prepare()方法運行圖:                                  3)parameterize()方法運行圖                   

         

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 異常, 迭代器, 包裝類, StringBuffer(基本使用), 容器Collection, TreeSet容器中排序, ...
  • 導言: 我們有時候需要將做好的Python程式打包成為一個exe , 方便我們使用,查找了資料發現 pyinstaller 、py2exe,最後還是選擇的pyinstaller,用的時候踩過了挺多的坑,在這裡記錄一下具體的用法。安裝的時候直接在cmd pip install pyinstaller ...
  • 需求出現/使用場景: 因為公司需要將word辦的介面文檔線上化,看起來是個很好的事情,但是就是苦逼了我們這些幹活的,其中工程量最大的就是參數的錄入,要是參數少也罷,有的介面動輒三四十個參數,更甚八九十個,我手動複製了一個三四十個的就讓我懷疑人生,我覺的我的人生不能在賦值介面參數中浪費掉。以前也學過一 ...
  • 記憶體限制:256 MiB 時間限制:2000 ms 標準輸入輸出 題目類型:傳統 評測方式:文本比較 上傳者: 匿名 【題目描述】 這是一道模板題。 維護一個 nnn 點的無向圖,支持: 加入一條連接 uuu 和 vvv 的無向邊 查詢 uuu 和 vvv 的連通性 由於本題數據較大,因此輸出的時候 ...
  • 面向對象 面向過程的代表主要是 語言,面向對象是相對面向過程而言, 是面向對象的編程語言,面向過程是通過函數體現,面向過程主要是功能行為。 而對於面向對象而言,將功能封裝到對象,所以面向對象是基於面向過程的。以前是主要以面向過程為思想,現在是將功能裝進對象中,如果要用到功能時,就調用該對象即可。 面 ...
  • 數組的綜合應用 冒泡排序 為什麼這個排序要叫冒泡呢?為什麼不叫其他名詞呢? 其實這個取名是根據排序演算法的基本思路命名的,見名知意,冒泡排序,就是想泡泡在水裡一樣,在水裡大的泡泡先浮出水面,就是大的先排出來,最小的最慢排出。 冒泡排序,是對排序的各個元素從頭到尾依次進行相鄰的大小比較, 比如你是隊長, ...
  • 給定一個僅包含大小寫字母和空格 ' ' 的字元串,返回其最後一個單詞的長度。 如果不存在最後一個單詞,請返回 0 。 說明:一個單詞是指由字母組成,但不包含任何空格的字元串。 示例: 輸入: "Hello World" 輸出: 5 給定一個僅包含大小寫字母和空格 ' ' 的字元串,返回其最後一個單詞 ...
  • 給定一個整數數組 nums ,找到一個具有最大和的連續子數組(子數組最少包含一個元素),返回其最大和。 示例: 輸入: [-2,1,-3,4,-1,2,1,-5,4], 輸出: 6 解釋: 連續子數組 [4,-1,2,1] 的和最大,為 6。 class Solution: def maxSubAr ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...