MyBatis 核心配置綜述之 ParameterHandler


MyBatis 四大核心組件我們已經了解到了兩種,一個是 Executor ,它是MyBatis 解析SQL請求首先會經過的第一道關卡,它的主要作用在於創建緩存,管理 StatementHandler 的調用,為 StatementHandler 提供 Configuration 環境等。StatementHandler 組件最主要的作用在於創建 Statement 對象與數據庫進行交流,還會使用 ParameterHandler 進行參數配置,使用 ResultSetHandler 把查詢結果與實體類進行綁定。那么本篇就來了解一下第三個組件 ParameterHandler。

ParameterHandler 簡介

ParameterHandler 相比於其他的組件就簡單很多了,ParameterHandler 譯為參數處理器,負責為 PreparedStatement 的 sql 語句參數動態賦值,這個接口很簡單只有兩個方法

/**
 * A parameter handler sets the parameters of the {@code PreparedStatement}
 * 參數處理器為 PreparedStatement 設置參數
 */
public interface ParameterHandler {

  Object getParameterObject();

  void setParameters(PreparedStatement ps)
      throws SQLException;

}

ParameterHandler 只有一個實現類 DefaultParameterHandler , 它實現了這兩個方法。

  • getParameterObject: 用於讀取參數
  • setParameters: 用於對 PreparedStatement 的參數賦值

ParameterHandler 創建

參數處理器對象是在創建 StatementHandler 對象的同時被創建的,由 Configuration 對象負責創建

BaseStatementHandler.java

protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
  this.configuration = mappedStatement.getConfiguration();
  this.executor = executor;
  this.mappedStatement = mappedStatement;
  this.rowBounds = rowBounds;

  this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
  this.objectFactory = configuration.getObjectFactory();

  if (boundSql == null) { // issue #435, get the key before calculating the statement
    generateKeys(parameterObject);
    boundSql = mappedStatement.getBoundSql(parameterObject);
  }

  this.boundSql = boundSql;

  // 創建參數處理器
  this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
  // 創建結果映射器
  this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
}

在創建 ParameterHandler 時,需要傳入SQL的mappedStatement 對象,讀取的參數和SQL語句

注意:一個 BoundSql 對象,就代表了一次sql語句的實際執行,而 SqlSource 對象的責任,就是根據傳入的參數對象,動態計算這個 BoundSql, 也就是 Mapper 文件中節點的計算,是由 SqlSource 完成的,SqlSource 最常用的實現類是 DynamicSqlSource

Configuration.java

public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
  // 創建ParameterHandler
  ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
  parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
  return parameterHandler;
}

上面是 Configuration 創建 ParameterHandler 的過程,它實際上是交由 LanguageDriver 來創建具體的參數處理器,LanguageDriver 默認的實現類是 XMLLanguageDriver,由它調用 DefaultParameterHandler 中的構造方法完成 ParameterHandler 的創建工作

public ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
  return new DefaultParameterHandler(mappedStatement, parameterObject, boundSql);
}

public DefaultParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
  this.mappedStatement = mappedStatement;
  this.configuration = mappedStatement.getConfiguration();
  // 獲取 TypeHandlerRegistry 注冊
  this.typeHandlerRegistry = mappedStatement.getConfiguration().getTypeHandlerRegistry();
  this.parameterObject = parameterObject;
  this.boundSql = boundSql;
}

上面的流程是創建 ParameterHandler 的過程,創建完成之后,該進行具體的解析工作,那么 ParameterHandler 如何解析SQL中的參數呢?SQL中的參數從哪里來的?

ParameterHandler 中的參數從何而來

你可能知道 Parameter 中的參數是怎么來的,無非就是從 Mapper 配置文件中映射過去的啊,就比如如下例子

參數肯定就是圖中標紅的 1 ,然后再傳到XML對應的 SQL 語句中,用 #{} 或者 ${} 來進行賦值啊,

嗯,你講的沒錯,可是你知道這個參數是如何映射過來的嗎?或者說你知道 Parameter 的解析過程嗎?或許你不是很清晰了,我們下面就來探討一下 ParameterHandler 對參數的解析,這其中涉及到 MyBatis 中的動態代理模式

在MyBatis 中,當 deptDao.findByDeptNo(1) 將要執行的時候,會被 JVM 進行攔截,交給 MyBatis 中的代理實現類 MapperProxy 的 invoke 方法中,這也是執行 SQL 語句的主流程。

然后交給 Executor 、StatementHandler進行對應的參數解析和執行,因為是帶參數的 SQL 語句,最終會創建 PreparedStatement 對象並創建參數解析器進行參數解析

SimpleExecutor.java

handler.parameterize(stmt) 最終會調用到 DefaultParameterHandler 中的 setParameters 方法,我在源碼上做了注釋,為了方便拷貝,我沒有采用截圖的形式

public void setParameters(PreparedStatement ps) {
  ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
  // parameterMappings 就是對 #{} 或者 ${} 里面參數的封裝
  List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
  if (parameterMappings != null) {
    // 如果是參數化的SQL,便需要循環取出並設置參數的值
    for (int i = 0; i < parameterMappings.size(); i++) {
      ParameterMapping parameterMapping = parameterMappings.get(i);
      // 如果參數類型不是 OUT ,這個類型與 CallableStatementHandler 有關
      // 因為存儲過程不存在輸出參數,所以參數不是輸出參數的時候,就需要設置。
      if (parameterMapping.getMode() != ParameterMode.OUT) {
        Object value;
        // 得到#{}  中的屬性名
        String propertyName = parameterMapping.getProperty();
        // 如果 propertyName 是 Map 中的key
        if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
          // 通過key 來得到 additionalParameter 中的value值
          value = boundSql.getAdditionalParameter(propertyName);
        }
        // 如果不是 additionalParameters 中的key,而且傳入參數是 null, 則value 就是null
        else if (parameterObject == null) {
          value = null;
        }
        // 如果 typeHandlerRegistry 中已經注冊了這個參數的 Class對象,即它是Primitive 或者是String 的話
        else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
          value = parameterObject;
        } else {
          // 否則就是 Map
          MetaObject metaObject = configuration.newMetaObject(parameterObject);
          value = metaObject.getValue(propertyName);
        }
        // 在通過SqlSource 的parse 方法得到parameterMappings 的具體實現中,我們會得到parameterMappings的typeHandler
        TypeHandler typeHandler = parameterMapping.getTypeHandler();
        // 獲取typeHandler 的jdbc type
        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);
        }
      }
    }
  }
}

ParameterHandler 解析

我們在 MyBatis 核心配置綜述之 StatementHandler 一文中了解到 Executor 管理的是 StatementHandler 對象的創建以及參數賦值,那么我們的主要入口還是 Executor 執行器

下面用一個流程圖表示一下 ParameterHandler 的解析過程,以簡單執行器為例

像是 doQuery,doUpdate,doQueryCursor等方法都會先調用到

// 生成 preparedStatement 並調用 prepare 方法,並為參數賦值
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;
}

然后在生成 preparedStatement 調用DefaultParameterHandler進行參數賦值。

公眾號提供 優質Java資料 以及CSDN免費下載 權限,歡迎你關注我


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM