Mybatis源碼學(xué)習(xí)記錄(ParameterHandler篇)

前言

在上一篇文章中我們分析了StatementHandler的源碼,本文我們會分析Mybatis中四大接口之一的ParameterHandler的源碼部分

源碼

ParameterHandler.java

參數(shù)處理器,用來給PreparedStatement設(shè)置參數(shù)的

接口定義

public interface ParameterHandler {
  // 獲取當(dāng)前執(zhí)行實際傳入的參數(shù)
  Object getParameterObject();
  
  // 給PreparedStatement設(shè)置參數(shù)
  void setParameters(PreparedStatement ps)
      throws SQLException;
}

接口定義好了,活就需要有人來做了,下面看看ParameterHandler接口有哪些實現(xiàn)

DefaultParameterHandler.java

Mybatis僅僅提供了一個實現(xiàn)類,我們直接看其源碼

DefaultParameterHandler.png

看其實現(xiàn)類的源碼之前,我們先設(shè)想一下,如果是讓你自己來實現(xiàn)這個接口,你的實現(xiàn)中應(yīng)該有哪些屬性呢? 確定實現(xiàn)有哪些屬性之前我們先仔細分析一下接口中定義的方法,接口方法只有兩個,一個獲取當(dāng)前實際執(zhí)行的參數(shù)對象,注意是這種形式的({{"phone", "15800000000"}, {"param1", "15800000000"}}

所以實現(xiàn)中必然需要有一個屬性,類似下面所示

// 當(dāng)前實際執(zhí)行前的參數(shù)對象
private final Object parameterObject;

除此之外的另一個接口方法是為PreparedStatement對象設(shè)置動態(tài)運行參數(shù),既然是設(shè)置參數(shù),我就需要拿到整個MappedStatement對象以及BoundSql對象,通過BoundSql我就可以得到帶有?號的sql語句,當(dāng)前傳遞的參數(shù)以及整個sql語句的參數(shù)映射關(guān)系List<ParameterMapping>,(這個映射關(guān)系尤為重要,可以說是ParameterHandler實現(xiàn)的關(guān)鍵),通過參數(shù)映射關(guān)系我就可以準確的找到哪個參數(shù)對應(yīng)哪個TypeHandler,以及該參數(shù)是入?yún)?/code>還是出參類型,其javaType是什么,jdbcType是什么等等信息

ok,分析到這里基本上差不多了,我們還是直接看源碼吧

public class DefaultParameterHandler implements ParameterHandler {
  // 屬性
  // 持有typeHandler注冊器
  private final TypeHandlerRegistry typeHandlerRegistry;

  // 持有MappedStatement實例,這是一個靜態(tài)的xml的一個數(shù)據(jù)庫操作節(jié)點的靜態(tài)信息而已
  private final MappedStatement mappedStatement;
  
  // 持有當(dāng)前操作傳入的實際參數(shù)
  private final Object parameterObject;

  // 動態(tài)語言被執(zhí)行后的結(jié)果sql
  private final BoundSql boundSql;
  private final Configuration configuration;
  
  // 構(gòu)造函數(shù)
  public DefaultParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
    this.mappedStatement = mappedStatement;
    this.configuration = mappedStatement.getConfiguration();
    this.typeHandlerRegistry = mappedStatement.getConfiguration().getTypeHandlerRegistry();
    this.parameterObject = parameterObject;
    this.boundSql = boundSql;
  }
  
  // 實現(xiàn)方法
  @Override
  public Object getParameterObject() {
    return parameterObject;
  }
  
  @Override
  public void setParameters(PreparedStatement ps) {
    ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
    // 1. 獲取boundSql中的參數(shù)映射信息列表
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings != null) {
      // 1.1. 遍歷參數(shù)映射列表,這個列表信息就是我們xml文件中定義的某個查詢語句的所有參數(shù)映射信息,注意這個List中的參數(shù)映射元素的順序是和真實xml中sql的參數(shù)順序?qū)?yīng)的
      for (int i = 0; i < parameterMappings.size(); i++) {
        ParameterMapping parameterMapping = parameterMappings.get(i);
        // 1.2. 只有入?yún)㈩愋筒艜O(shè)置PreparedStatement
        if (parameterMapping.getMode() != ParameterMode.OUT) {
          Object value;
          // 取出參數(shù)名,這里比如說是'phone'
          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 {
            // 1.3. 這一步的工作就是從當(dāng)前實際傳入的參數(shù)中獲取到指定key('phone')的value值,比如是'15800000000'
            MetaObject metaObject = configuration.newMetaObject(parameterObject);
            value = metaObject.getValue(propertyName);
          }
          
          // 2. 獲取該參數(shù)對應(yīng)的typeHandler
          TypeHandler typeHandler = parameterMapping.getTypeHandler();
          
          // 2.1. 獲取該參數(shù)對應(yīng)的jdbcType
          JdbcType jdbcType = parameterMapping.getJdbcType();
          if (value == null && jdbcType == null) {
            jdbcType = configuration.getJdbcTypeForNull();
          }
          try {
            // 3. 重點是調(diào)用每個參數(shù)對應(yīng)的typeHandler的setParameter方法為該ps設(shè)置正確的參數(shù)值
            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);
          }
        }
      }
    }
  }
}

這里截取一個運行時的debug信息看一下就知道了


debug.png

至此我們可以知道,每一次的調(diào)用查詢(數(shù)據(jù)庫查詢,不走Mybatis緩存),Executor在執(zhí)行doQuery的時候都會創(chuàng)建一個StatementHandler實例,每個StatementHandler在實例化的時候,都會創(chuàng)建并持有兩個處理器即ParameterHandlerResultSetHandler

這樣statementHandler就可以利用ParameterHandler完成預(yù)處理語句的參數(shù)化設(shè)置,以及結(jié)果查詢出來以后再利用ResultSetHandler處理結(jié)果集

這里我們再次貼一下BaseStatementHandler的構(gòu)造函數(shù)部分源碼

// BaseStatementHandler.java
// ...

protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    // 省略其他屬性設(shè)置...
    
    // 1. parameterHandler
    this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
    
    // 2. resultSetHandler
    this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
  }

我們看下parameterHandler的創(chuàng)建過程,可以看到是利用了configuration

// Configuration.java
// ...

// new一個參數(shù)化處理器
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
    // 1. 內(nèi)部就是調(diào)用了DefaultParameterHandler的構(gòu)造方法,new了一個實例返回
    ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
    // 2. 看到這里,又是Mybatis的插件,提供給開發(fā)人員攔截ParameterHandler的調(diào)用邏輯
    parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
    return parameterHandler;
  }

代碼分析至此,我們簡單總結(jié)一下

  1. 當(dāng)真實的數(shù)據(jù)庫查詢被執(zhí)行時,SimpleExecutor執(zhí)行器會通過configuration對象new出一個StatementHandler出來,后續(xù)的所有操作都是利用這個statementHandler來完成,這個handler實際上是返回了經(jīng)過開發(fā)人員自定義插件的代理類,可以基于此處自定義插件

  2. 當(dāng)statementHandler實例有了以后,我們需要創(chuàng)建出真實的JDBC Statement實例,此處調(diào)用了statementHandler的prepare方法(此處可自定義插件攔截哦)實現(xiàn),根據(jù)handler的類型不同則會創(chuàng)建不同的JDBC Statement實例返回,這里簡單的提一下,如果是ReuseExecutor不會為同一個BoundSql中的sql語句創(chuàng)建多個prepareStatement返回,而是在內(nèi)部維護一個Map<String, Statement>來重用Statement實例

  3. 當(dāng)statement實例返回時,就會進行參數(shù)化設(shè)置(如果是一個Statement實例而非PrepareStatement實例,參數(shù)化設(shè)置方法的內(nèi)部就不作任何處理),調(diào)用statementHandler的parameterize方法完成,由于我們每個statementHandler都持有兩個處理器即ParameterHandlerResultSetHandler,這個時候就是ParameterHandler上場的時候了,PrepareStatementHandler類型的handler在參數(shù)化方法內(nèi)部就是委托給了ParameterHandler,調(diào)用了其setParameters方法進行參數(shù)化設(shè)置,而setParameters方法的核心實現(xiàn)就是拿到當(dāng)前調(diào)用的boundSql實例中的parameterMappings集合和parameterObject實例,通過遍歷這個集合,對所有入?yún)㈩愋?ParameterMode.IN)的參數(shù)進行逐個獲取其參數(shù)對應(yīng)TypeHandler,再利用TypeHandlersetParameter方法對當(dāng)前的preparedStatement實例進行參數(shù)化設(shè)置,而設(shè)置需要的參數(shù)值就是從parameterObject中獲取的

  4. 當(dāng)參數(shù)設(shè)置完成以后,statementHandler又拿到了調(diào)用權(quán),開始調(diào)用query/update(Statement)方法執(zhí)行數(shù)據(jù)庫操作,不同的handler則有不同的實現(xiàn),比如SimpleStatementHandler就是調(diào)用的Statement.execute(sql)執(zhí)行,而PreparedStatementHandler則是直接ps.execute()執(zhí)行即可

  5. 數(shù)據(jù)庫執(zhí)行完成后,就拿到了結(jié)果集,這個時候就是ResultSetHandler發(fā)揮的時候了,下文我們會繼續(xù)分析ResultSetHandler的源碼

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

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

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