主頁 > 軟體設計 > 精通Mybatis之Jdbc處理器StatementHandler

精通Mybatis之Jdbc處理器StatementHandler

2021-04-24 19:27:13 軟體設計

前言

Mybatis小編已經陸續出了兩篇博客了,之前我們講解了Executor處理器,快取體系,卻沒有講解與jdbc互動的相關操作,這其實就是Mybatis的StatementHandler做的事情,一個SQL請求會經過會話,然后是執行器, 由StatementHandler執行jdbc最終到達資料庫,今天小編帶大家詳細認識一下StatementHandler他是怎樣的結構以及如何發揮作用的,

StatementHandler

定義

JDBC處理器,基于JDBC構建JDBC Statement,并設定引數,然后執行Sql,每呼叫會話當中一次sql,都會有與之相對應的且唯一的Statement實體(命中快取除外),

結構

StatementHandler 介面原始碼:

public interface StatementHandler {
  //基于JDBC宣告statement
  Statement prepare(Connection connection, Integer transactionTimeout)
      throws SQLException;
  //為statement設定方法
  void parameterize(Statement statement)
      throws SQLException;
  //添加批處理
  void batch(Statement statement)
      throws SQLException;
  //執行update 方法
  int update(Statement statement)
      throws SQLException;
  //執行query 方法
  <E> List<E> query(Statement statement, ResultHandler resultHandler)
      throws SQLException;
  //查詢游標
  <E> Cursor<E> queryCursor(Statement statement)
      throws SQLException;
  //獲取動態sql
  BoundSql getBoundSql();
 //獲取引數處理器
  ParameterHandler getParameterHandler();

}

StatementHandler 有三個子類

  1. SimpleStatementHandler:對應JDBC中的Statement
  2. PreparedStatementHandler:對應JDBC中的PreparedStatement
  3. CallableStatementHandler:對應JDBC中的CallableStatement,

下面是結構圖:
在這里插入圖片描述
大部分情況下都是前處理器,所以接下小編就針對PreparedStatementHandler來講解其流程,(其他的大家自行研究)

PreparedStatementHandler處理流程

首先看一下呼叫的時序圖:

在這里插入圖片描述
總共執行程序分為三個階段:

  1. 預處理:這里預處理不僅僅是通過Connection創建Statement,還包括設定引數,
  2. 執行:包含執行SQL和處理結果映射兩部分,
  3. 關閉:直接關閉Statement,

原始碼閱讀
假設使用SimpleExecutor來呼叫的

@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最終呼叫的地方RoutingStatementHandler
      //為什么使用configuration創建,這里第一可以理解為簡單工廠,統一創建,第二是對插件做攔截
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      //創建statement然后是引數處理
      stmt = prepareStatement(handler, ms.getStatementLog());
      //執行查詢操作
      return handler.query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

使用RoutingStatementHandler來創建的根據StatementType來new一個相應的statementHandler(小編覺得這個好像沒什么特別大的作用,可能當初作者還想再這個Handler里面做其他操作吧),里面statementType默認為PREPARED,可以通過@Options(statementType = StatementType.PREPARED)來配置,@Options放在對應介面的方法上,

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

  }

創建statement并且引數處理

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    //獲取連接
    Connection connection = getConnection(statementLog);
    //獲取具體的StatementHandler這里就是PreparedStatementHandler的instantiateStatement方法,
    //然后再base里面做了超時時間以及設定回傳行數,設定的引數可以做mappedStatement配置
    stmt = handler.prepare(connection, transaction.getTimeout());
    //引數處理,具體參加后續引數部分怎么進行引數映射
    handler.parameterize(stmt);
    return stmt;
  }

PreparedStatementHandler的instantiateStatement方法

 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() == ResultSetType.DEFAULT) {
      return connection.prepareStatement(sql);
    } else {
      return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
    }
  }

具體執行方法:

@Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    //處理結果集,后續詳解
    return resultSetHandler.handleResultSets(ps);
  }

上圖涉及到了引數處理以及結果集封裝以及原始碼閱讀中小編沒有具體講,這是由于涉及資料庫欄位和JavaBean之間的相互映射,相對復雜,所以分別使用ParameterHandler與ResultSetHandler兩個專門的組件實作,接下來就一起了解一下引數處理與結果集封裝的處理流程,

引數處理

引數處理包括了引數轉換,引數映射以及引數的賦值,小編先來說一下引數的轉換:

引數轉換

所有引數轉換我們運用的到了一個類:ParamNameResolver,分為兩種情況

  1. 單個引數:假如說沒有加@Param注解則不做轉換直接交給執行器做對應的查詢,如果有注解,就轉換成一個map
  2. 多個引數:按照順序轉換,并且轉換成一個map,key按照順序就是param1,param2,如果假如了@Param,key就為對應引數的名稱,jdk8之后基于反射可以用到對應引數的名稱(需要打開引數編譯),jdk8之前則為arg0,arg1,

ParamNameResolver引數轉換類原始碼閱讀

public Object getNamedParams(Object[] args) {
    final int paramCount = names.size();
    //沒有引數,或引數數量為0回傳null
    if (args == null || paramCount == 0) {
      return null;
      //如果沒有@param注解并且引數數量為1,則直接回傳args[0]也就是引數本身
    } else if (!hasParamAnnotation && paramCount == 1) {
      return args[names.firstKey()];
    } else {
     //其他都封裝成一個map,ParamMap為mybatis自定義的其實就是繼承了hashMap,對get方法重寫了一下
      final Map<String, Object> param = new ParamMap<>();
      int i = 0;
      for (Map.Entry<Integer, String> entry : names.entrySet()) {
        param.put(entry.getValue(), args[entry.getKey()]);
        // add generic param names (param1, param2, ...)
        final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1);
        // ensure not to overwrite parameter named with @Param
        if (!names.containsValue(genericParamName)) {
          param.put(genericParamName, args[entry.getKey()]);
        }
        i++;
      }
      return param;
    }
  }

多個引數情況下運行的結果的
在這里插入圖片描述
所以在我們介面類上可以這樣寫:

	//這兩種都可以,不過得注意順序同時注意arg是從0開始的param是從1開始的(小編覺得還是加上@Param注解比較好)
    @Update("update  users set name=#{arg1} where id=#{arg0}")
    @Update("update  users set name=#{param2} where id=#{param1}")
    int setName(Integer id, String name);

對了小編說了可以加入編譯引數然后不寫注解也可以決議到引數名稱,那怎么加呢?請看下圖:
在這里插入圖片描述
或者在pom的maven配置中 加入

<plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <compilerArgs>
                        <arg>-parameters</arg>
                    </compilerArgs>
                </configuration>
            </plugin>
        </plugins>

然后記得maven clean一下重新編譯,當然不建議這么做,
結果如下圖
在這里插入圖片描述

引數映射與賦值

上面說的一個和多個引數由ParamNameResolver轉換,但是無論是一個或多個引數的情況下都會出現引數是JavaBean物件或者是原始型別,這里交由ParameterHandler處理,如果只有一個原始型別的話那引數占用符基本可以隨便寫,多參的情況下根據map的key映射,如果是JavaBean物件會根據屬性的名稱進行映射比方說#{user.id},
ParameterHandler原始碼閱讀實作為DefaultParameterHandler

@Override
  public void setParameters(PreparedStatement ps) {
    ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
    //boundSql中包含了sql,然后引數使用?作為占位符
    //原先sql里面的#{param1}等封裝為parameterMappings為引數映射,有幾個引數就會映射幾個
    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;
          //獲得屬性名稱也就是param1或arg0或者是name等
          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 {
          	//將ParamNameResolver轉換出來的引數封裝為metaObject,然后就可以直接拿到值了
            MetaObject metaObject = configuration.newMetaObject(parameterObject);
            value = metaObject.getValue(propertyName);
          }
          TypeHandler typeHandler = parameterMapping.getTypeHandler();
          //獲取jdbcType
          JdbcType jdbcType = parameterMapping.getJdbcType();          
          if (value == null && jdbcType == null) {
            jdbcType = configuration.getJdbcTypeForNull();
          }
          try {
          	//如果沒設定jdbc型別則基于值型別做引數設定否則根據jdbc型別轉換
          	//這里需要TypeHandler來處理然后進行賦值
            typeHandler.setParameter(ps, i + 1, value, jdbcType);
          } catch (TypeException | SQLException e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          }
        }
      }
    }
  }

對于上面小編的注釋更直觀的圖如下:
在這里插入圖片描述
上面MetaObject 是很厲害的可以封裝javaBean物件,原始型別還有陣列List等等,這邊小編后續講解MetaObject ,因為這個很復雜,
引數的賦值通過TypeHandler 為PrepareStatement設定值,通常情況下一般的資料型別MyBatis都有與之相對應的TypeHandler,

結果集封裝

指讀取ResultSet資料,并將每一行轉換成相對應的物件,用戶可在轉換的程序當中可以通過ResultContext來控制是否要繼續轉換,轉換后的物件都會暫存在ResultHandler中最后統一封裝成list回傳給呼叫方,
在這里插入圖片描述
結果集封裝比較復雜下次講解,

總結

今天主要講了與jdbc打交道的statementHandler他有三個主要的子類,也分別封裝了jdbc的statement,然后我們根據最常用的PrepareStatement說了一下其主要的流程,分為三個階段:預處理,執行,關閉,其中有四個主要步驟:準備statement,設定引數,執行以及結果值處理,之后到設定引數的重要組件包括ParamNameResolver(引數轉換器),ParameterHandler(引數的映射)以及TypeHandler(引數賦值所需的型別處理),最后簡單說了一下結果集封裝,結果集封裝是最復雜的下次小編接著為大家詳細說明了一下,先到這兒,一起加油努力!

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/279688.html

標籤:其他

上一篇:常見的主流自動化測驗框架,這5種能幫到你很多

下一篇:SAAS前端組織特點

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 面試突擊第一季,第二季,第三季

    第一季必考 https://www.bilibili.com/video/BV1FE411y79Y?from=search&seid=15921726601957489746 第二季分布式 https://www.bilibili.com/video/BV13f4y127ee/?spm_id_fro ......

    uj5u.com 2020-09-10 05:35:24 more
  • 第三單元作業總結

    1.前言 這應該是本學期最后一次寫作業總結了吧。總體來說,對作業的節奏也差不多掌握了,作業做起來的效率也更高了。雖然和之前的作業一樣,作業中都要用到新的知識,但是相比之前,更加懂得了如何利用工具以及資料。雖然之間卡過殼,但總體而言,這幾次作業還算完成的比較好。 2.作業程序總結 相比前兩個單元,此單 ......

    uj5u.com 2020-09-10 05:35:41 more
  • 北航OO(2020)第四單元博客作業暨課程總結博客

    北航OO(2020)第四單元博客作業暨課程總結博客 本單元作業的架構設計 在本單元中,由于UML圖具有比較清晰的樹形結構,因此我對其中需要進行查詢操作的元素進行了包裝,在樹的父節點中存盤所有孩子的參考。考慮到性能問題,我采用了快取機制,一次查詢后盡可能快取已經遍歷過的資訊,以減少遍歷次數。 本單元我 ......

    uj5u.com 2020-09-10 05:35:48 more
  • BUAA_OO_第四單元

    一、UML決議器設計 ? 先看下題目:第四單元實作一個基于JDK 8帶有效性檢查的UML(Unified Modeling Language)類圖,順序圖,狀態圖分析器 MyUmlInteraction,實際上我們要建立一個有向圖模型,UML中的物件(元素)可能與同級元素連接,也可與低級元素相連形成 ......

    uj5u.com 2020-09-10 05:35:54 more
  • 6.1邏輯運算子

    邏輯運算子 1. && 短路與 運算式1 && 運算式2 01.運算式1為true并且運算式2也為true 整體回傳為true 02.運算式1為false,將不會執行運算式2 整體回傳為false 03.只要有一個運算式為false 整體回傳為false 2. || 短路或 運算式1 || 運算式2 ......

    uj5u.com 2020-09-10 05:35:56 more
  • BUAAOO 第四單元 & 課程總結

    1. 第四單元:StarUml檔案決議 本單元采用了圖模型決議UML。 UML檔案可以抽象為圖、子圖、邊的邏輯結構。 在實作中,圖的節點包括類、介面、屬性,子圖包括狀態圖、順序圖等。 采用了三次遍歷UML元素的方法建圖,第一遍遍歷建點,第二、三次遍歷設定屬性、連邊,實作圖物件的初始化。這里借鑒了一些 ......

    uj5u.com 2020-09-10 05:36:06 more
  • 談談我對C# 多型的理解

    面向物件三要素:封裝、繼承、多型。 封裝和繼承,這兩個比較好理解,但要理解多型的話,可就稍微有點難度了。今天,我們就來講講多型的理解。 我們應該經常會看到面試題目:請談談對多型的理解。 其實呢,多型非常簡單,就一句話:呼叫同一種方法產生了不同的結果。 具體實作方式有三種。 一、多載 多載很簡單。 p ......

    uj5u.com 2020-09-10 05:36:09 more
  • Python 資料驅動工具:DDT

    背景 python 的unittest 沒有自帶資料驅動功能。 所以如果使用unittest,同時又想使用資料驅動,那么就可以使用DDT來完成。 DDT是 “Data-Driven Tests”的縮寫。 資料:http://ddt.readthedocs.io/en/latest/ 使用方法 dd. ......

    uj5u.com 2020-09-10 05:36:13 more
  • Python里面的xlrd模塊詳解

    那我就一下面積個問題對xlrd模塊進行學習一下: 1.什么是xlrd模塊? 2.為什么使用xlrd模塊? 3.怎樣使用xlrd模塊? 1.什么是xlrd模塊? ?python操作excel主要用到xlrd和xlwt這兩個庫,即xlrd是讀excel,xlwt是寫excel的庫。 今天就先來說一下xl ......

    uj5u.com 2020-09-10 05:36:28 more
  • 當我們創建HashMap時,底層到底做了什么?

    jdk1.7中的底層實作程序(底層基于陣列+鏈表) 在我們new HashMap()時,底層創建了默認長度為16的一維陣列Entry[ ] table。當我們呼叫map.put(key1,value1)方法向HashMap里添加資料的時候: 首先,呼叫key1所在類的hashCode()計算key1 ......

    uj5u.com 2020-09-10 05:36:38 more
最新发布
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:20:47 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:20:25 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:20:17 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:20:10 more
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:19:44 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:19:07 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:18:57 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:18:49 more
  • 05單件模式

    #經典的單件模式 public class Singleton { private static Singleton uniqueInstance; //一個靜態變數持有Singleton類的唯一實體。 // 其他有用的實體變數寫在這里 //構造器宣告為私有,只有Singleton可以實體化這個類! ......

    uj5u.com 2023-04-19 08:42:51 more
  • 【架構與設計】常見微服務分層架構的區別和落地實踐

    軟體工程的方方面面都遵循一個最基本的道理:沒有銀彈,架構分層模型更是如此,每一種都有各自優缺點,所以請根據不同的業務場景,并遵循簡單、可演進這兩個重要的架構原則選擇合適的架構分層模型即可。 ......

    uj5u.com 2023-04-19 08:42:41 more