<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      第7篇:ParameterHandler參數處理機制

      1. 學習目標確認

      1.0 第6篇思考題解答

      在深入學習ParameterHandler參數處理機制之前,讓我們先回顧并解答第6篇中提出的思考題,這將幫助我們更好地理解ParameterHandler在整個執行流程中的關鍵作用。

      思考題1:為什么MyBatis要設計RoutingStatementHandler?直接使用具體的StatementHandler不行嗎?

      答案要點

      • 統一入口:RoutingStatementHandler提供統一的創建入口,屏蔽了具體實現的復雜性
      • 策略模式:根據StatementType動態選擇合適的處理器,實現了策略模式
      • 擴展性:便于添加新的StatementHandler類型,不影響現有代碼
      • 簡化調用:Executor只需要與RoutingStatementHandler交互,不需要關心具體類型
      • 插件支持:為插件攔截提供統一的攔截點

      與ParameterHandler的關系:每種StatementHandler都需要ParameterHandler來設置SQL參數,RoutingStatementHandler確保了參數處理的一致性。

      思考題2:PreparedStatementHandler相比SimpleStatementHandler有什么優勢?為什么是默認選擇?

      答案要點

      • 安全優勢:支持參數綁定,有效防止SQL注入攻擊
      • 性能優勢:SQL預編譯,相同SQL結構可以復用執行計劃,減少10-20%的SQL解析時間(視數據庫實現)
      • 類型安全:通過TypeHandler進行類型轉換,確保類型安全
      • 維護性:參數與SQL分離,代碼結構更清晰
      • 功能豐富:支持主鍵生成、批量操作等高級功能

      ParameterHandler的重要性:PreparedStatementHandler的核心優勢來自于ParameterHandler的參數綁定機制。

      思考題3:CallableStatementHandler如何處理存儲過程的輸出參數?與普通查詢有什么區別?

      答案要點

      • 參數注冊:需要預先注冊輸出參數的類型和位置
      • 參數模式:支持IN、OUT、INOUT三種參數模式
      • 結果獲取:通過CallableStatement.getXxx()方法獲取輸出參數值
      • 處理時機:在SQL執行完成后處理輸出參數
      • 類型轉換:輸出參數也需要進行類型轉換
      • 多結果集支持:復雜存儲過程可能返回多個結果集,需通過ResultSetHandler.handleMultipleResults()方法處理

      存儲過程多結果集處理示例

      // 調用存儲過程,處理多個結果集和輸出參數
      CallableStatement cs = connection.prepareCall("{call getUserOrders(?, ?)}");
      
      // 注冊輸出參數
      cs.setLong(1, userId);  // 輸入參數:用戶ID
      cs.registerOutParameter(2, Types.INTEGER);  // 輸出參數:訂單總數
      
      // 執行存儲過程
      cs.execute();
      
      // 處理第一個結果集(用戶信息)
      ResultSet rs1 = cs.getResultSet();
      while (rs1.next()) {
          System.out.println("用戶: " + rs1.getString("name"));
      }
      
      // 切換到下一個結果集(訂單列表)
      if (cs.getMoreResults()) {
          ResultSet rs2 = cs.getResultSet();
          while (rs2.next()) {
              System.out.println("訂單: " + rs2.getString("order_no"));
          }
      }
      
      // 獲取輸出參數
      int totalOrders = cs.getInt(2);
      System.out.println("訂單總數: " + totalOrders);
      

      ParameterHandler的擴展:CallableStatementHandler需要擴展ParameterHandler來處理輸出參數的注冊和獲取。

      思考題4:StatementHandler與ParameterHandler、ResultSetHandler是如何協作的?

      答案要點

      • 組合模式:StatementHandler通過組合包含ParameterHandler和ResultSetHandler
      • 職責分工:StatementHandler負責Statement管理,ParameterHandler負責參數設置,ResultSetHandler負責結果處理
      • 執行流程:prepare() → parameterize() → execute() → handleResults()
      • 統一管理:StatementHandler統一協調三者的執行時序

      核心協作流程

      StatementHandler.prepare()     // 創建Statement
      ↓
      ParameterHandler.setParameters() // 設置參數
      ↓
      Statement.execute()            // 執行SQL
      ↓
      ResultSetHandler.handleResultSets() // 處理結果
      

      1.1 本篇學習目標

      通過本文你將能夠:

      1. 深入理解ParameterHandler參數處理器的設計思想和核心職責
      2. 掌握DefaultParameterHandler的實現機制和參數設置流程
      3. 理解參數映射(ParameterMapping)的配置和使用方式
      4. 掌握TypeHandler類型處理器與參數處理的協作關系
      5. 了解不同參數類型(基本類型、對象、集合)的處理策略
      6. 理解額外參數(AdditionalParameter)的生成與使用機制
      7. 掌握參數驗證和性能優化策略
      8. 具備自定義ParameterHandler擴展開發的能力

      2. ParameterHandler參數處理器體系總覽

      還記得第6篇我們說StatementHandler是"SQL執行的總指揮"嗎?那ParameterHandler就是這位總指揮手下最得力的"后勤部長"——專門負責把我們Java代碼里的參數"翻譯"成數據庫能聽懂的話。

      想象一下,你要給外國朋友寄包裹,得先把中文地址翻譯成英文對吧?ParameterHandler做的就是這個活兒:把 User user = new User("張三", 18)這樣的Java對象,翻譯成 ps.setString(1, "張三"); ps.setInt(2, 18)這樣的JDBC調用。比如將 #{user.name}轉換為 ps.setString(1, "張三")這樣神奇的操作!

      2.1 參數處理器繼承關系圖

      classDiagram class ParameterHandler { <<interface>> +getParameterObject() Object +setParameters(PreparedStatement) void } class DefaultParameterHandler { -typeHandlerRegistry TypeHandlerRegistry -mappedStatement MappedStatement -parameterObject Object -boundSql BoundSql -configuration Configuration +DefaultParameterHandler(MappedStatement, Object, BoundSql) +getParameterObject() Object +setParameters(PreparedStatement) void } class ParameterMapping { -property String -mode ParameterMode -javaType Class -jdbcType JdbcType -typeHandler TypeHandler -numericScale Integer -resultMapId String -jdbcTypeName String -expression String } class TypeHandler { <<interface>> +setParameter(PreparedStatement, int, T, JdbcType) void +getResult(ResultSet, String) T +getResult(ResultSet, int) T +getResult(CallableStatement, int) T } class LanguageDriver { <<interface>> +createParameterHandler(MappedStatement, Object, BoundSql) ParameterHandler } class XMLLanguageDriver { +createParameterHandler(MappedStatement, Object, BoundSql) ParameterHandler } ParameterHandler <|.. DefaultParameterHandler LanguageDriver <|.. XMLLanguageDriver DefaultParameterHandler --> ParameterMapping : uses DefaultParameterHandler --> TypeHandler : delegates to XMLLanguageDriver --> DefaultParameterHandler : creates

      2.2 參數處理器職責分工

      組件 核心職責 主要功能 性能特點
      ParameterHandler 參數處理接口 定義參數設置規范 統一入口,零性能損耗
      DefaultParameterHandler 默認參數處理實現 參數值獲取、類型轉換、參數設置 內置優先級策略,高效穩定
      ParameterMapping 參數映射配置 參數屬性、類型、模式配置 建造者模式,減少對象創建開銷
      TypeHandler 類型轉換處理 Java類型與JDBC類型互轉 TypeHandler緩存可提升5-10%參數設置效率
      LanguageDriver 語言驅動器 創建ParameterHandler實例 動態選擇實現,支持多種SQL方言

      2.3 參數處理流程圖

      sequenceDiagram participant SH as StatementHandler participant PH as ParameterHandler participant PM as ParameterMapping participant TH as TypeHandler participant PS as PreparedStatement SH->>PH: setParameters(ps) Note over PH: MetaObject緩存反射結果<br/>優化復雜對象處理 loop 遍歷參數映射 PH->>PM: getProperty() PH->>PH: getPropertyValue() alt 額外參數 PH->>PH: boundSql.getAdditionalParameter() else 基本類型 PH->>PH: parameterObject直接使用 else 復雜對象 Note over PH: 使用MetaObject反射<br/>性能開銷較大 PH->>PH: metaObject.getValue() end PH->>TH: setParameter(ps, index, value, jdbcType) TH->>PS: setXxx(index, value) end PS-->>SH: 參數設置完成

      3. ParameterHandler接口定義

      3.1 接口源碼分析

      ParameterHandler接口非常簡潔,只定義了兩個核心方法:

      package org.apache.ibatis.executor.parameter;
      
      import java.sql.PreparedStatement;
      import java.sql.SQLException;
      
      /**
       * 參數處理器接口
       * 負責為PreparedStatement設置參數
       * 
       * @author Clinton Begin
       */
      public interface ParameterHandler {
      
          /**
           * 獲取參數對象
           * @return 參數對象,可能為null、基本類型(如Long、String)、復雜對象(如User)或集合(如List<User>)
           * 
           * 示例返回值:
           * - 基本類型:Long id = 123L
           * - 復雜對象:User user = new User()
           * - 集合類型:List<Long> ids = Arrays.asList(1L, 2L, 3L)
           * - 空值:null(無參數方法)
           */
          Object getParameterObject();
        
          // 使用示例
          // Object param = handler.getParameterObject();
          // 可能是 Long、User、List<User> 或 null
      
          /**
           * 為PreparedStatement設置參數
           * 這是ParameterHandler的核心方法,負責:
           * 1. 獲取參數值
           * 2. 進行類型轉換
           * 3. 調用PreparedStatement的setXxx方法設置參數
           * 
           * @param ps PreparedStatement對象
           * @throws SQLException 參數設置過程中的SQL異常
           */
          void setParameters(PreparedStatement ps) throws SQLException;
      }
      

      3.2 接口設計特點

      簡潔得讓人驚訝

      • 就兩個方法!getParameterObject()和setParameters(),簡單到你都懷疑"這就完了?"
      • 但別小看它,MyBatis的"單一職責原則"在這兒體現得淋漓盡致

      擴展性拉滿

      • 想加個參數加密?寫個實現類就行
      • 想做參數校驗?也是寫個實現類
      • 這就是面向接口編程的魅力啊!

      團隊協作能手

      • 跟TypeHandler配合:"兄弟,這個Date類型你來轉一下"
      • 跟StatementHandler配合:"老大,參數我都設置好了,可以執行了!"

      4. DefaultParameterHandler默認實現

      4.1 核心源碼分析

      DefaultParameterHandler是ParameterHandler的默認實現,承擔了參數處理的核心邏輯:

      package org.apache.ibatis.scripting.defaults;
      
      import org.apache.ibatis.executor.ErrorContext;
      import org.apache.ibatis.executor.parameter.ParameterHandler;
      import org.apache.ibatis.mapping.BoundSql;
      import org.apache.ibatis.mapping.MappedStatement;
      import org.apache.ibatis.mapping.ParameterMapping;
      import org.apache.ibatis.mapping.ParameterMode;
      import org.apache.ibatis.reflection.MetaObject;
      import org.apache.ibatis.session.Configuration;
      import org.apache.ibatis.type.JdbcType;
      import org.apache.ibatis.type.TypeException;
      import org.apache.ibatis.type.TypeHandler;
      import org.apache.ibatis.type.TypeHandlerRegistry;
      
      import java.sql.PreparedStatement;
      import java.sql.SQLException;
      import java.util.List;
      
      /**
       * 默認參數處理器實現
       * 負責將Java參數對象轉換為PreparedStatement的參數
       * 
       * @author Clinton Begin
       * @author Eduardo Macarron
       */
      public class DefaultParameterHandler implements ParameterHandler {
      
          // 類型處理器注冊表,用于獲取對應的TypeHandler
          private final TypeHandlerRegistry typeHandlerRegistry;
        
          // SQL映射語句,包含參數映射配置
          private final MappedStatement mappedStatement;
        
          // 參數對象,可能是null、基本類型、Map、POJO等
          private final Object parameterObject;
        
          // 綁定SQL對象,包含參數映射列表
          private final BoundSql boundSql;
        
          // MyBatis全局配置對象
          private final Configuration configuration;
      
          /**
           * 構造方法
           * 初始化參數處理器所需的核心組件
           * 
           * @param mappedStatement SQL映射語句,包含參數映射配置
           * @param parameterObject 參數對象,可能為null、基本類型、復雜對象或集合
           * @param boundSql 綁定SQL對象,包含參數映射列表和額外參數
           */
          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;
          }
      
          @Override
          public Object getParameterObject() {
              return parameterObject;
          }
      
          /**
           * 設置PreparedStatement參數的核心方法
           * 遍歷所有參數映射,為每個參數設置值
           * 
           * 核心處理流程(偽代碼):
           * for (ParameterMapping pm : boundSql.getParameterMappings()) {
           *     Object value = getPropertyValue(pm, metaObject);  // 優先級策略獲取值
           *     TypeHandler th = pm.getTypeHandler();           // 獲取類型處理器
           *     th.setParameter(ps, index++, value, pm.getJdbcType()); // 設置參數
           * }
           * 
           * MetaObject緩存優化: MetaObject內部使用Reflector緩存屬性元數據(getter/setter方法、字段類型等),
           * 避免重復反射解析,顯著減少復雜對象處理的反射開銷
           */
          @Override
          public void setParameters(PreparedStatement ps) throws SQLException {
              // 設置錯誤上下文,便于問題定位
              ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
            
              // 獲取參數映射列表
              List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
            
              if (parameterMappings != null) {
                  MetaObject metaObject = null;
                
                  // 遍歷每個參數映射
                  for (int i = 0; i < parameterMappings.size(); i++) {
                      ParameterMapping parameterMapping = parameterMappings.get(i);
                    
                      // 跳過輸出參數(OUT參數用于存儲過程)
                      if (parameterMapping.getMode() != ParameterMode.OUT) {
                          Object value;
                          String propertyName = parameterMapping.getProperty();
                        
                          // 參數值獲取的優先級策略
                          if (boundSql.hasAdditionalParameter(propertyName)) {
                              // 1. 優先從額外參數中獲取(如foreach生成的參數)
                              value = boundSql.getAdditionalParameter(propertyName);
                          } else if (parameterObject == null) {
                              // 2. 參數對象為null
                              value = null;
                          } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                              // 3. 參數對象是基本類型(有對應的TypeHandler)
                              value = parameterObject;
                          } else {
                              // 4. 參數對象是復雜類型,通過反射獲取屬性值
                              // MetaObject使用Reflector緩存屬性元數據,減少反射開銷
                              if (metaObject == null) {
                                  metaObject = configuration.newMetaObject(parameterObject);
                              }
                              value = metaObject.getValue(propertyName);
                          }
                        
                          // 獲取對應的類型處理器
                          TypeHandler typeHandler = parameterMapping.getTypeHandler();
                          JdbcType jdbcType = parameterMapping.getJdbcType();
                        
                          // 處理null值的JDBC類型
                          if (value == null && jdbcType == null) {
                              jdbcType = configuration.getJdbcTypeForNull();
                          }
                        
                          try {
                              // 使用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);
                          }
                      }
                  }
              }
          }
      }
      

      4.2 參數值獲取策略

      DefaultParameterHandler獲取參數值可不是瞎猜的,人家有一套嚴格的"優先級規則",就像你找東西一樣——先找最有可能的地方:

      // 第一優先級:額外參數(最高優先級)
      if (boundSql.hasAdditionalParameter(propertyName)) {
          // "哎,這個參數是foreach動態生成的,我先用這個!"
          value = boundSql.getAdditionalParameter(propertyName);
      } else if (parameterObject == null) {
          // 第二優先級:參數對象是null
          // "啊?你啥參數都沒給我?那我也沒辦法,只能是null了"
          value = null;
      } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
          // 第三優先級:基本類型
          // "哦,你就傳了個Long/String?那我直接用就行了"
          value = parameterObject;
      } else {
          // 第四優先級:復雜對象(最低優先級)
          // "嗯?User對象?我得反射一下拿到user.getName()這些屬性值"
          if (metaObject == null) {
              metaObject = configuration.newMetaObject(parameterObject);
          }
          value = metaObject.getValue(propertyName);
      }
      

      為什么要這樣設計?

      1. 額外參數優先:動態SQL(像foreach)會生成臨時參數,這些肯定是最新的,當然要先用
      2. null處理:空指針異常是程序員的噩夢,先判斷null能避免很多問題
      3. 基本類型直接用Long id = 123L這種,還反射個啥?直接用多省事
      4. 復雜對象才反射:User、Order這些對象,沒辦法,只能用反射了(雖然慢點,但準確啊)

      4.3 類型處理器協作

      DefaultParameterHandler跟TypeHandler的配合就像是"翻譯官"和"專業術語顧問"的關系:

      內置TypeHandler全家桶

      • StringTypeHandler:String ? VARCHAR,這個最常用
      • IntegerTypeHandler:Integer ? INTEGER,處理數字的
      • DateTypeHandler:Date ? TIMESTAMP,時間類型專用
      • EnumTypeHandler:枚舉 ? VARCHAR,枚舉值的好幫手

      它倆怎么配合的?

      // 獲取類型處理器
      TypeHandler typeHandler = parameterMapping.getTypeHandler();
      JdbcType jdbcType = parameterMapping.getJdbcType();
      
      // 處理null值的JDBC類型
      if (value == null && jdbcType == null) {
          jdbcType = configuration.getJdbcTypeForNull();
      }
      
      try {
          // 委托給TypeHandler進行具體的參數設置
          // TypeHandler會根據Java類型和JDBC類型進行轉換
          typeHandler.setParameter(ps, i + 1, value, jdbcType);
      } catch (TypeException | SQLException e) {
          throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
      }
      

      協作示例: 當參數類型為 String 時,StringTypeHandler 會調用 PreparedStatement.setString(index, value) 設置參數。

      內置TypeHandler使用示例:

      // StringTypeHandler 處理字符串參數
      TypeHandler<String> stringHandler = new StringTypeHandler();
      stringHandler.setParameter(ps, 1, "張三", JdbcType.VARCHAR);
      
      // IntegerTypeHandler 處理整數參數
      TypeHandler<Integer> intHandler = new IntegerTypeHandler();
      intHandler.setParameter(ps, 2, 25, JdbcType.INTEGER);
      
      // DateTypeHandler 處理日期參數
      TypeHandler<Date> dateHandler = new DateTypeHandler();
      dateHandler.setParameter(ps, 3, new Date(), JdbcType.TIMESTAMP);
      

      自定義TypeHandler示例

      /**
       * 自定義日期類型處理器
       * 將Java Date轉換為JDBC Timestamp
       */
      public class CustomDateTypeHandler implements TypeHandler<Date> {
        
          @Override
          public void setParameter(PreparedStatement ps, int i, Date parameter, JdbcType jdbcType) throws SQLException {
              if (parameter == null) {
                  ps.setNull(i, Types.TIMESTAMP);
              } else {
                  ps.setTimestamp(i, new Timestamp(parameter.getTime()));
              }
          }
        
          @Override
          public Date getResult(ResultSet rs, String columnName) throws SQLException {
              Timestamp timestamp = rs.getTimestamp(columnName);
              return timestamp != null ? new Date(timestamp.getTime()) : null;
          }
        
          @Override
          public Date getResult(ResultSet rs, int columnIndex) throws SQLException {
              Timestamp timestamp = rs.getTimestamp(columnIndex);
              return timestamp != null ? new Date(timestamp.getTime()) : null;
          }
        
          @Override
          public Date getResult(CallableStatement cs, int columnIndex) throws SQLException {
              Timestamp timestamp = cs.getTimestamp(columnIndex);
              return timestamp != null ? new Date(timestamp.getTime()) : null;
          }
      }
      
      // 注冊自定義TypeHandler
      configuration.getTypeHandlerRegistry().register(Date.class, JdbcType.TIMESTAMP, new CustomDateTypeHandler());
      

      5. ParameterMapping參數映射配置

      5.1 參數映射核心屬性

      ParameterMapping定義了參數的映射配置信息:

      package org.apache.ibatis.mapping;
      
      import org.apache.ibatis.type.JdbcType;
      import org.apache.ibatis.type.TypeHandler;
      
      /**
       * 參數映射配置類
       * 包含參數的詳細映射信息
       */
      public class ParameterMapping {
      
          private Configuration configuration;
        
          // 參數屬性名稱(在參數對象中的屬性名)
          private String property;
        
          // 參數模式:IN(輸入)、OUT(輸出)、INOUT(輸入輸出)
          private ParameterMode mode;
        
          // Java類型
          private Class<?> javaType = Object.class;
        
          // JDBC類型
          private JdbcType jdbcType;
        
          // 數字精度(用于NUMERIC和DECIMAL類型)
          // 例如:BigDecimal類型的小數位數,price字段精度為2表示保留兩位小數
          // 應用場景:處理金額、匯率等需要精確小數的數值
          private Integer numericScale;
        
          // 類型處理器
          private TypeHandler<?> typeHandler;
        
          // 結果映射ID(用于復雜類型)
          private String resultMapId;
        
          // JDBC類型名稱
          private String jdbcTypeName;
        
          // 表達式(用于動態參數)
          private String expression;
      
          // 構造方法和Getter/Setter略...
        
          /**
           * 參數映射建造器
           * 使用建造者模式構建ParameterMapping對象
           */
          public static class Builder {
              private ParameterMapping parameterMapping = new ParameterMapping();
      
              public Builder(Configuration configuration, String property, TypeHandler<?> typeHandler) {
                  parameterMapping.configuration = configuration;
                  parameterMapping.property = property;
                  parameterMapping.typeHandler = typeHandler;
              }
      
              public Builder mode(ParameterMode mode) {
                  parameterMapping.mode = mode;
                  return this;
              }
      
              public Builder javaType(Class<?> javaType) {
                  parameterMapping.javaType = javaType;
                  return this;
              }
      
              public Builder jdbcType(JdbcType jdbcType) {
                  parameterMapping.jdbcType = jdbcType;
                  return this;
              }
      
              public Builder numericScale(Integer numericScale) {
                  parameterMapping.numericScale = numericScale;
                  return this;
              }
      
              public Builder resultMapId(String resultMapId) {
                  parameterMapping.resultMapId = resultMapId;
                  return this;
              }
      
              public Builder jdbcTypeName(String jdbcTypeName) {
                  parameterMapping.jdbcTypeName = jdbcTypeName;
                  return this;
              }
      
              public Builder expression(String expression) {
                  parameterMapping.expression = expression;
                  return this;
              }
      
              public ParameterMapping build() {
                  resolveTypeHandler();
                  validate();
                  return parameterMapping;
              }
      
              private void resolveTypeHandler() {
                  if (parameterMapping.typeHandler == null && parameterMapping.javaType != null) {
                      Configuration configuration = parameterMapping.configuration;
                      TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
                      parameterMapping.typeHandler = typeHandlerRegistry.getTypeHandler(parameterMapping.javaType, parameterMapping.jdbcType);
                  }
              }
      
              private void validate() {
                  if (parameterMapping.typeHandler == null) {
                      throw new BuilderException("Type handler was null on parameter mapping for property '"
                          + parameterMapping.property + "'. It was either not specified and/or could not be found for the javaType ("
                          + parameterMapping.javaType.getName() + ") : jdbcType (" + parameterMapping.jdbcType + ") combination.");
                  }
              }
          }
      }
      

      5.2 參數映射創建流程

      參數映射通過SqlSource解析過程創建,支持在XML或注解中配置精度:

      XML配置示例(指定DECIMAL精度)

      <!-- 指定DECIMAL類型的精度 -->
      <select id="findByPrice" resultType="Product">
          SELECT * FROM product 
          WHERE price = #{price, javaType=BigDecimal, jdbcType=DECIMAL, numericScale=2}
      </select>
      
      <!-- 批量插入時指定精度 -->
      <insert id="batchInsert" parameterType="list">
          INSERT INTO product (name, price) VALUES
          <foreach collection="list" item="item" separator=",">
              (#{item.name}, #{item.price, javaType=BigDecimal, jdbcType=DECIMAL, numericScale=2})
          </foreach>
      </insert>
      

      參數映射構建代碼

      <!-- 指定DECIMAL類型的精度 -->
      <select id="findByPrice" resultType="Product">
          SELECT * FROM product 
          WHERE price = #{price, javaType=BigDecimal, jdbcType=DECIMAL, numericScale=2}
      </select>
      

      參數映射構建代碼

      // 在XMLScriptBuilder或AnnotationBuilder中創建ParameterMapping
      public ParameterMapping buildParameterMapping(Class<?> parameterType, String property, Class<?> javaType, JdbcType jdbcType) {
          // 解析參數類型
          if (javaType == null) {
              if (JdbcType.CURSOR.equals(jdbcType)) {
                  javaType = java.sql.ResultSet.class;
              } else if (Map.class.isAssignableFrom(parameterType)) {
                  javaType = Object.class;
              } else {
                  MetaClass metaClass = MetaClass.forClass(parameterType, configuration.getReflectorFactory());
                  javaType = metaClass.getGetterType(property);
              }
          }
        
          // 獲取類型處理器
          TypeHandler<?> typeHandler = configuration.getTypeHandlerRegistry().getTypeHandler(javaType, jdbcType);
        
          // 構建ParameterMapping
          return new ParameterMapping.Builder(configuration, property, typeHandler)
              .javaType(javaType)
              .jdbcType(jdbcType)
              .build();
      }
      

      6. 不同參數類型的處理策略

      6.1 基本類型參數處理

      /**
       * 基本類型參數處理示例
       * 當參數是基本類型時,直接使用parameterObject作為參數值
       */
      public class BasicParameterExample {
        
          /**
           * 單個基本類型參數
           * SQL: SELECT * FROM user WHERE id = ?
           */
          @Test
          public void testSingleBasicParameter() throws SQLException {
              // 參數對象就是基本類型值
              Object parameterObject = 123L;
            
              // MyBatis會檢測到Long類型有對應的TypeHandler
              boolean hasTypeHandler = typeHandlerRegistry.hasTypeHandler(parameterObject.getClass());
              System.out.println("Long類型有TypeHandler: " + hasTypeHandler);
            
              // 直接使用parameterObject作為參數值
              if (hasTypeHandler) {
                  Object value = parameterObject; // 123L
                  System.out.println("參數值: " + value);
              }
          }
        
          /**
           * 多個基本類型參數
           * SQL: SELECT * FROM user WHERE id = ? AND status = ?
           * MyBatis會自動包裝為Map
           */
          @Test
          public void testMultipleBasicParameters() {
              // MyBatis自動包裝:{arg0=123, arg1="ACTIVE", param1=123, param2="ACTIVE"}
              Map<String, Object> parameterObject = new HashMap<>();
              parameterObject.put("arg0", 123L);
              parameterObject.put("arg1", "ACTIVE");
              parameterObject.put("param1", 123L);
              parameterObject.put("param2", "ACTIVE");
            
              // 通過屬性名獲取值
              Object value1 = ((Map) parameterObject).get("arg0"); // 123L
              Object value2 = ((Map) parameterObject).get("arg1"); // "ACTIVE"
            
              System.out.println("第一個參數: " + value1);
              System.out.println("第二個參數: " + value2);
          }
      }
      

      6.2 復雜對象參數處理

      /**
       * 復雜對象參數處理示例
       * 當參數是POJO對象時,通過MetaObject反射獲取屬性值
       */
      public class ComplexParameterExample {
        
          /**
           * POJO對象參數
           * SQL: INSERT INTO user (name, email, age) VALUES (?, ?, ?)
           */
          @Test
          public void testPojoParameter() {
              // 參數對象是User POJO
              User parameterObject = new User();
              parameterObject.setName("張三");
              parameterObject.setEmail("zhangsan@example.com");
              parameterObject.setAge(25);
            
              // MyBatis檢測User類型沒有內置TypeHandler
              boolean hasTypeHandler = typeHandlerRegistry.hasTypeHandler(parameterObject.getClass());
              System.out.println("User類型有TypeHandler: " + hasTypeHandler); // false
            
              if (!hasTypeHandler) {
                  // 創建MetaObject進行反射操作
                  MetaObject metaObject = configuration.newMetaObject(parameterObject);
                
                  // 通過屬性名獲取值
                  Object nameValue = metaObject.getValue("name");       // "張三"
                  Object emailValue = metaObject.getValue("email");     // "zhangsan@example.com"
                  Object ageValue = metaObject.getValue("age");         // 25
                
                  System.out.println("name屬性值: " + nameValue);
                  System.out.println("email屬性值: " + emailValue);
                  System.out.println("age屬性值: " + ageValue);
              }
          }
        
          /**
           * 嵌套對象參數
           * SQL: SELECT * FROM order WHERE user.id = ? AND user.name = ?
           */
          @Test
          public void testNestedObjectParameter() {
              // 創建嵌套對象
              User user = new User();
              user.setId(123L);
              user.setName("張三");
            
              Order parameterObject = new Order();
              parameterObject.setUser(user);
            
              // 通過MetaObject訪問嵌套屬性
              MetaObject metaObject = configuration.newMetaObject(parameterObject);
            
              Object userId = metaObject.getValue("user.id");     // 123L
              Object userName = metaObject.getValue("user.name"); // "張三"
            
              System.out.println("嵌套屬性user.id: " + userId);
              System.out.println("嵌套屬性user.name: " + userName);
          }
      }
      

      6.3 集合類型參數處理

      /**
       * 集合類型參數處理示例
       * MyBatis通過動態SQL和額外參數處理集合
       */
      public class CollectionParameterExample {
        
          /**
           * List參數處理
           * SQL: SELECT * FROM user WHERE id IN <foreach collection="list" item="id" open="(" separator="," close=")">#{id}</foreach>
           */
          @Test
          public void testListParameter() {
              // 參數是List集合
              List<Long> parameterObject = Arrays.asList(1L, 2L, 3L);
            
              // MyBatis會將List包裝為Map: {"list": [1L, 2L, 3L]}
              Map<String, Object> wrappedParam = new HashMap<>();
              wrappedParam.put("list", parameterObject);
            
              // 在foreach處理過程中,會生成額外參數
              BoundSql boundSql = new BoundSql(configuration, "SELECT * FROM user WHERE id IN (?, ?, ?)", 
                  Arrays.asList(
                      createParameterMapping("__frch_id_0", Long.class),
                      createParameterMapping("__frch_id_1", Long.class),
                      createParameterMapping("__frch_id_2", Long.class)
                  ), wrappedParam);
                
              // 設置額外參數
              boundSql.setAdditionalParameter("__frch_id_0", 1L);
              boundSql.setAdditionalParameter("__frch_id_1", 2L);
              boundSql.setAdditionalParameter("__frch_id_2", 3L);
            
              // 獲取額外參數(最高優先級)
              Object value1 = boundSql.getAdditionalParameter("__frch_id_0"); // 1L
              Object value2 = boundSql.getAdditionalParameter("__frch_id_1"); // 2L
              Object value3 = boundSql.getAdditionalParameter("__frch_id_2"); // 3L
            
              System.out.println("foreach生成的參數1: " + value1);
              System.out.println("foreach生成的參數2: " + value2);
              System.out.println("foreach生成的參數3: " + value3);
          }
        
          /**
           * Map參數處理
           * SQL: SELECT * FROM user WHERE name = #{name} AND age = #{age}
           */
          @Test
          public void testMapParameter() {
              // 參數是Map
              Map<String, Object> parameterObject = new HashMap<>();
              parameterObject.put("name", "張三");
              parameterObject.put("age", 25);
            
              // Map類型沒有內置TypeHandler,但Map實現了特殊處理
              // Map參數可以直接從boundSql.getAdditionalParameter獲取
              // 或通過MetaObject的MapWrapper獲取
              MetaObject metaObject = configuration.newMetaObject(parameterObject);
            
              // 通過Map的key獲取值
              Object nameValue = metaObject.getValue("name"); // "張三"
              Object ageValue = metaObject.getValue("age");   // 25
            
              System.out.println("Map參數name: " + nameValue);
              System.out.println("Map參數age: " + ageValue);
            
              // Map作為參數的特殊處理
              // 當參數是Map時,MyBatis會將其包裝為ParamMap
              // SQL中的#{name}會直接從Map中通過"name"鍵獲取值
              System.out.println("Map參數直接映射: #{name} -> " + parameterObject.get("name"));
          }
        
          /**
           * Map參數實際應用示例
           */
          @Test
          public void testMapParameterInAction() {
              Map<String, Object> params = new HashMap<>();
              params.put("id", 1L);
              params.put("name", "張三");
              params.put("age", 25);
            
              // 對應的Mapper方法
              // List<User> findByMap(Map<String, Object> params);
              // SQL: SELECT * FROM user WHERE id = #{id} AND name = #{name} AND age = #{age}
            
              // 實際應用場景:Map參數適合動態條件查詢,無需創建專門的參數對象
              // MyBatis會直接從Map中獲取鍵對應的值(如id、name、age),
              // 并通過MetaObject包裝后按屬性名訪問
            
              System.out.println("使用Map參數查詢: " + params);
          }
      }
      

      7. 實踐案例:自定義參數處理器

      7.1 參數加密處理器

      讓我們創建一個參數加密處理器,自動對敏感參數進行加密:

      package com.example.parameter;
      
      import org.apache.ibatis.executor.parameter.ParameterHandler;
      import org.apache.ibatis.mapping.BoundSql;
      import org.apache.ibatis.mapping.MappedStatement;
      import org.apache.ibatis.mapping.ParameterMapping;
      import org.apache.ibatis.mapping.ParameterMode;
      import org.apache.ibatis.reflection.MetaObject;
      import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
      import org.apache.ibatis.session.Configuration;
      import org.apache.ibatis.type.JdbcType;
      import org.apache.ibatis.type.TypeHandler;
      import org.apache.ibatis.type.TypeHandlerRegistry;
      
      import javax.crypto.Cipher;
      import javax.crypto.spec.SecretKeySpec;
      import java.nio.charset.StandardCharsets;
      import java.sql.PreparedStatement;
      import java.sql.SQLException;
      import java.util.Base64;
      import java.util.HashSet;
      import java.util.List;
      import java.util.Set;
      
      /**
       * 參數加密處理器
       * 對指定的敏感參數進行自動加密
       */
      public class EncryptionParameterHandler implements ParameterHandler {
        
          private final DefaultParameterHandler delegate;
          private final Configuration configuration;
          private final TypeHandlerRegistry typeHandlerRegistry;
          private final MappedStatement mappedStatement;
          private final Object parameterObject;
          private final BoundSql boundSql;
        
          // 需要加密的字段名集合
          private static final Set<String> ENCRYPT_FIELDS = new HashSet<>();
        
          // AES加密密鑰(實際應用中應從配置文件讀取)
          private static final String ENCRYPT_KEY = "MyBatis123456789"; // 16位密鑰
        
          static {
              // 配置需要加密的字段
              ENCRYPT_FIELDS.add("password");
              ENCRYPT_FIELDS.add("idCard");
              ENCRYPT_FIELDS.add("phone");
              ENCRYPT_FIELDS.add("email");
          }
        
          public EncryptionParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
              this.delegate = new DefaultParameterHandler(mappedStatement, parameterObject, boundSql);
              this.configuration = mappedStatement.getConfiguration();
              this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
              this.mappedStatement = mappedStatement;
              this.parameterObject = parameterObject;
              this.boundSql = boundSql;
          }
        
          @Override
          public Object getParameterObject() {
              return delegate.getParameterObject();
          }
        
          @Override
          public void setParameters(PreparedStatement ps) throws SQLException {
              System.out.println("=== 使用加密參數處理器 ===");
            
              List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
            
              if (parameterMappings != null) {
                  MetaObject metaObject = 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();
                        
                          // 獲取原始參數值(使用與DefaultParameterHandler相同的邏輯)
                          if (boundSql.hasAdditionalParameter(propertyName)) {
                              value = boundSql.getAdditionalParameter(propertyName);
                          } else if (parameterObject == null) {
                              value = null;
                          } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                              value = parameterObject;
                          } else {
                              if (metaObject == null) {
                                  metaObject = configuration.newMetaObject(parameterObject);
                              }
                              value = metaObject.getValue(propertyName);
                          }
                        
                          // 對敏感字段進行加密
                          if (needEncryption(propertyName, value)) {
                              String originalValue = (String) value;
                              value = encryptValue(originalValue);
                              System.out.println(String.format("字段 [%s] 加密: %s -> %s", 
                                  propertyName, originalValue, value));
                          }
                        
                          // 設置參數
                          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 (Exception e) {
                              throw new SQLException("Could not set parameter: " + e.getMessage(), e);
                          }
                      }
                  }
              }
          }
        
          /**
           * 判斷是否需要加密
           */
          private boolean needEncryption(String propertyName, Object value) {
              return value instanceof String && 
                     ENCRYPT_FIELDS.contains(propertyName) && 
                     !((String) value).isEmpty();
          }
        
          /**
           * AES加密
           */
          private String encryptValue(String value) {
              try {
                  SecretKeySpec secretKey = new SecretKeySpec(ENCRYPT_KEY.getBytes(StandardCharsets.UTF_8), "AES");
                  Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
                  cipher.init(Cipher.ENCRYPT_MODE, secretKey);
                
                  byte[] encryptedBytes = cipher.doFinal(value.getBytes(StandardCharsets.UTF_8));
                  return Base64.getEncoder().encodeToString(encryptedBytes);
              } catch (Exception e) {
                  throw new RuntimeException("參數加密失敗: " + e.getMessage(), e);
              }
          }
        
          /**
           * 添加加密字段
           */
          public static void addEncryptField(String fieldName) {
              ENCRYPT_FIELDS.add(fieldName);
          }
        
          /**
           * 移除加密字段
           */
          public static void removeEncryptField(String fieldName) {
              ENCRYPT_FIELDS.remove(fieldName);
          }
      }
      

      7.2 自定義LanguageDriver

      創建自定義的LanguageDriver來使用我們的加密參數處理器:

      package com.example.parameter;
      
      import org.apache.ibatis.executor.parameter.ParameterHandler;
      import org.apache.ibatis.mapping.BoundSql;
      import org.apache.ibatis.mapping.MappedStatement;
      import org.apache.ibatis.mapping.SqlSource;
      import org.apache.ibatis.parsing.XNode;
      import org.apache.ibatis.scripting.LanguageDriver;
      import org.apache.ibatis.scripting.xmltags.XMLLanguageDriver;
      import org.apache.ibatis.session.Configuration;
      
      /**
       * 加密語言驅動器
       * 創建加密參數處理器
       */
      public class EncryptionLanguageDriver extends XMLLanguageDriver implements LanguageDriver {
        
          @Override
          public ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
              // 根據配置決定是否使用加密處理器
              Configuration configuration = mappedStatement.getConfiguration();
            
              // 檢查是否啟用加密(可以通過配置或注解控制)
              if (isEncryptionEnabled(mappedStatement)) {
                  System.out.println("創建加密參數處理器 for: " + mappedStatement.getId());
                  return new EncryptionParameterHandler(mappedStatement, parameterObject, boundSql);
              } else {
                  // 使用默認參數處理器
                  return super.createParameterHandler(mappedStatement, parameterObject, boundSql);
              }
          }
        
          /**
           * 判斷是否啟用加密
           * 可以根據MappedStatement的ID、配置等進行判斷
           */
          private boolean isEncryptionEnabled(MappedStatement mappedStatement) {
              String statementId = mappedStatement.getId();
            
              // 示例:對包含"User"的Mapper啟用加密
              return statementId.contains("User");
          }
      }
      

      7.3 配置使用

      插件注冊方式

      自定義ParameterHandler通常通過MyBatis插件機制注冊,在 mybatis-config.xml中配置:

      <plugins>
          <!-- 參數驗證攔截器 -->
          <plugin interceptor="com.example.ValidationParameterInterceptor"/>
          <!-- 參數加密攔截器 -->
          <plugin interceptor="com.example.EncryptionParameterInterceptor"/>
      </plugins>
      

      語言驅動器配置

      <configuration>
          <!-- 注冊自定義語言驅動器 -->
          <typeAliases>
              <typeAlias alias="encryptionLanguageDriver" type="com.example.parameter.EncryptionLanguageDriver"/>
          </typeAliases>
        
          <!-- 設置默認語言驅動器 -->
          <settings>
              <setting name="defaultScriptingLanguage" value="encryptionLanguageDriver"/>
          </settings>
        
          <!-- 或者在特定的Mapper方法上使用 -->
      </configuration>
      

      在Mapper接口中使用:

      package com.example.mapper;
      
      import org.apache.ibatis.annotations.*;
      import com.example.parameter.EncryptionLanguageDriver;
      
      public interface UserMapper {
        
          /**
           * 使用默認語言驅動器(會自動加密password字段)
           */
          @Insert("INSERT INTO user (username, password, email) VALUES (#{username}, #{password}, #{email})")
          int insertUser(User user);
        
          /**
           * 顯式指定語言驅動器
           */
          @Lang(EncryptionLanguageDriver.class)
          @Update("UPDATE user SET password = #{password} WHERE id = #{id}")
          int updatePassword(@Param("id") Long id, @Param("password") String password);
      }
      

      完整測試代碼

      package com.example;
      
      import org.apache.ibatis.io.Resources;
      import org.apache.ibatis.session.SqlSession;
      import org.apache.ibatis.session.SqlSessionFactory;
      import org.apache.ibatis.session.SqlSessionFactoryBuilder;
      import com.example.mapper.UserMapper;
      import com.example.entity.User;
      
      import java.io.InputStream;
      
      /**
       * 加密參數處理器測試
       * 演示自定義ParameterHandler的完整使用流程
       */
      public class EncryptionParameterTest {
        
          public static void main(String[] args) {
              SqlSessionFactory factory = MyBatisUtils.getSqlSessionFactory();
            
              testEncryptionParameterHandler(factory);
          }
        
          /**
           * 測試參數加密功能
           */
          private static void testEncryptionParameterHandler(SqlSessionFactory factory) {
              System.out.println("=== 測試參數加密功能 ===");
            
              try (SqlSession session = factory.openSession()) {
                  UserMapper mapper = session.getMapper(UserMapper.class);
                
                  // 創建用戶對象
                  User user = new User();
                  user.setUsername("testuser");
                  user.setPassword("mypassword123");      // 敏感信息,會被加密
                  user.setEmail("test@example.com");      // 敏感信息,會被加密
                
                  System.out.println(">>> 插入用戶前");
                  System.out.println("原始密碼: " + user.getPassword());
                  System.out.println("原始郵箱: " + user.getEmail());
                
                  // 插入用戶(password和email字段會自動加密)
                  int result = mapper.insertUser(user);
                
                  System.out.println(">>> 插入結果: " + result);
                
                  // 更新密碼測試
                  System.out.println(">>> 更新密碼測試");
                  String newPassword = "newpassword456";
                  System.out.println("新密碼: " + newPassword);
                
                  int updateResult = mapper.updatePassword(1L, newPassword);
                  System.out.println("更新結果: " + updateResult);
                
                  session.commit();
              }
          }
      }
      

      7.4 參數驗證處理器

      再創建一個參數驗證處理器:

      package com.example.parameter;
      
      import org.apache.ibatis.executor.parameter.ParameterHandler;
      import org.apache.ibatis.mapping.BoundSql;
      import org.apache.ibatis.mapping.MappedStatement;
      import org.apache.ibatis.mapping.ParameterMapping;
      import org.apache.ibatis.mapping.ParameterMode;
      import org.apache.ibatis.reflection.MetaObject;
      import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
      import org.apache.ibatis.session.Configuration;
      import org.apache.ibatis.type.TypeHandlerRegistry;
      
      import java.sql.PreparedStatement;
      import java.sql.SQLException;
      import java.util.HashMap;
      import java.util.List;
      import java.util.Map;
      import java.util.function.Predicate;
      import java.util.regex.Pattern;
      
      /**
       * 參數驗證處理器
       * 在設置參數前進行數據驗證
       */
      public class ValidationParameterHandler implements ParameterHandler {
        
          private final DefaultParameterHandler delegate;
          private final Configuration configuration;
          private final TypeHandlerRegistry typeHandlerRegistry;
          private final MappedStatement mappedStatement;
          private final Object parameterObject;
          private final BoundSql boundSql;
        
          // 驗證規則映射
          private static final Map<String, Predicate<Object>> VALIDATION_RULES = new HashMap<>();
        
          static {
              // 郵箱格式驗證
              VALIDATION_RULES.put("email", value -> {
                  if (value == null) return true;
                  String email = value.toString();
                  return Pattern.matches("^[A-Za-z0-9+_.-]+@([A-Za-z0-9.-]+\\.[A-Za-z]{2,})$", email);
              });
            
              // 手機號格式驗證
              VALIDATION_RULES.put("phone", value -> {
                  if (value == null) return true;
                  String phone = value.toString();
                  return Pattern.matches("^1[3-9]\\d{9}$", phone);
              });
            
              // 用戶名長度驗證
              VALIDATION_RULES.put("username", value -> {
                  if (value == null) return false;
                  String username = value.toString();
                  return username.length() >= 3 && username.length() <= 20;
              });
            
              // 密碼強度驗證
              VALIDATION_RULES.put("password", value -> {
                  if (value == null) return false;
                  String password = value.toString();
                  // 至少8位,包含字母和數字
                  return password.length() >= 8 && 
                         Pattern.matches(".*[A-Za-z].*", password) && 
                         Pattern.matches(".*\\d.*", password);
              });
          }
        
          public ValidationParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
              this.delegate = new DefaultParameterHandler(mappedStatement, parameterObject, boundSql);
              this.configuration = mappedStatement.getConfiguration();
              this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
              this.mappedStatement = mappedStatement;
              this.parameterObject = parameterObject;
              this.boundSql = boundSql;
          }
        
          @Override
          public Object getParameterObject() {
              return delegate.getParameterObject();
          }
        
          @Override
          public void setParameters(PreparedStatement ps) throws SQLException {
              System.out.println("=== 使用驗證參數處理器 ===");
            
              // 首先進行參數驗證
              validateParameters();
            
              // 驗證通過后,委托給默認處理器
              delegate.setParameters(ps);
          }
        
          /**
           * 驗證所有參數
           */
          private void validateParameters() {
              List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
            
              if (parameterMappings != null) {
                  MetaObject metaObject = null;
                
                  for (ParameterMapping parameterMapping : parameterMappings) {
                      if (parameterMapping.getMode() != ParameterMode.OUT) {
                          Object value;
                          String propertyName = parameterMapping.getProperty();
                        
                          // 獲取參數值
                          if (boundSql.hasAdditionalParameter(propertyName)) {
                              value = boundSql.getAdditionalParameter(propertyName);
                          } else if (parameterObject == null) {
                              value = null;
                          } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                              value = parameterObject;
                          } else {
                              if (metaObject == null) {
                                  metaObject = configuration.newMetaObject(parameterObject);
                              }
                              value = metaObject.getValue(propertyName);
                          }
                        
                          // 執行驗證
                          validateParameter(propertyName, value);
                      }
                  }
              }
          }
        
          /**
           * 驗證單個參數
           */
          private void validateParameter(String propertyName, Object value) {
              Predicate<Object> validator = VALIDATION_RULES.get(propertyName);
            
              if (validator != null) {
                  boolean isValid = validator.test(value);
                
                  if (!isValid) {
                      String message = String.format("參數驗證失敗: %s = %s", propertyName, value);
                      System.err.println(message);
                      throw new IllegalArgumentException(message);
                  } else {
                      System.out.println(String.format("參數驗證通過: %s = %s", propertyName, value));
                  }
              }
          }
        
          /**
           * 添加驗證規則
           */
          public static void addValidationRule(String propertyName, Predicate<Object> validator) {
              VALIDATION_RULES.put(propertyName, validator);
          }
        
          /**
           * 移除驗證規則
           */
          public static void removeValidationRule(String propertyName) {
              VALIDATION_RULES.remove(propertyName);
          }
      }
      

      8. 源碼調試指導

      8.1 關鍵斷點位置

      DefaultParameterHandler斷點

      1. DefaultParameterHandler.setParameters() - 參數設置入口
      2. 參數值獲取的優先級判斷邏輯
      3. typeHandler.setParameter() - 類型處理器設置參數

      ParameterMapping斷點

      1. ParameterMapping.Builder.build() - 參數映射構建
      2. resolveTypeHandler() - 類型處理器解析

      MetaObject斷點

      1. MetaObject.getValue() - 反射獲取屬性值
      2. 嵌套屬性訪問邏輯

      8.2 調試技巧

      工具建議

      • 使用IDEA的條件斷點,表達式:parameterObject.getClass().getSimpleName().equals("User")
      • 使用IDEA的字段監視(Field Watchpoint)觀察 parameterObject的變化
      • 啟用MyBatis日志:<setting name="logImpl" value="STDOUT_LOGGING"/>

      觀察參數類型判斷

      // 在setParameters方法中觀察類型判斷邏輯
      if (boundSql.hasAdditionalParameter(propertyName)) {
          System.out.println("使用額外參數: " + propertyName);
      } else if (parameterObject == null) {
          System.out.println("參數對象為null");
      } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
          System.out.println("參數是基本類型: " + parameterObject.getClass().getSimpleName());
      } else {
          System.out.println("參數是復雜對象: " + parameterObject.getClass().getSimpleName());
      }
      

      觀察參數映射

      // 觀察參數映射配置
      for (ParameterMapping pm : boundSql.getParameterMappings()) {
          System.out.println(String.format("參數映射: property=%s, javaType=%s, jdbcType=%s", 
              pm.getProperty(), pm.getJavaType().getSimpleName(), pm.getJdbcType()));
      }
      

      觀察類型處理器選擇邏輯

      // 在TypeHandlerRegistry.getTypeHandler方法設置斷點
      // 觀察類型處理器的匹配過程
      TypeHandler typeHandler = parameterMapping.getTypeHandler();
      System.out.println("使用的TypeHandler: " + typeHandler.getClass().getSimpleName());
      
      // 觀察TypeHandler的選擇邏輯
      TypeHandler<?> handler = typeHandlerRegistry.getTypeHandler(
          parameterMapping.getJavaType(), 
          parameterMapping.getJdbcType()
      );
      System.out.println(String.format("類型匹配: javaType=%s, jdbcType=%s -> %s", 
          parameterMapping.getJavaType().getSimpleName(),
          parameterMapping.getJdbcType(),
          handler.getClass().getSimpleName()));
      

      9. 易錯與排錯清單

      9.1 常見問題

      問題 原因 解決方案
      參數設置失敗 ParameterMapping配置錯誤 檢查參數映射的javaType和jdbcType
      類型轉換異常 TypeHandler選擇不當 確認類型處理器配置
      參數值為null 屬性名拼寫錯誤或對象為null 檢查屬性名和參數對象
      反射失敗 私有屬性無getter方法 確保屬性有對應的getter方法
      嵌套屬性訪問失敗 中間對象為null 檢查嵌套對象的初始化
      參數名不可靠 無@Param且未開啟-parameters編譯選項 使用@Param注解或啟用編譯參數保留
      動態SQL參數錯誤 <foreach>的collection配置錯誤導致參數缺失 檢查collection屬性與實際參數類型匹配(list/array/map key),使用@Param明確命名避免歧義

      9.2 性能優化建議

      1. 復用TypeHandler:為常用類型注冊TypeHandler,避免每次查詢都創建新實例
      2. 避免過度反射:復雜對象參數盡量使用字段直接訪問,減少getter調用。MetaObject緩存可減少10-15%反射開銷,通過 Reflector緩存屬性元數據避免重復解析
      3. 批量操作優化:使用 executorType="BATCH"配合 addBatch,減少參數設置次數
      4. 參數命名規范:避免使用過長的嵌套屬性(如 user.address.city.name),層級過深會增加反射開銷

      10. 小結

      恭喜你!看到這里,你已經徹底搞懂ParameterHandler這個"參數翻譯官"是怎么工作的了。

      讓我們快速回顧一下今天的收獲

      1. ParameterHandler接口:簡潔到只有2個方法,但設計得很優雅
      2. DefaultParameterHandler:內置的"四級參數查找機制",從額外參數到反射,層層遞進
      3. ParameterMapping:參數的"身份證",記錄了參數的所有信息
      4. 參數類型處理:基本類型直接用、復雜對象靠反射、集合類型走額外參數
      5. TypeHandler協作:ParameterHandler找值,TypeHandler轉類型,分工明確
      6. 擴展開發:想加密?想驗證?寫個ParameterHandler實現類就完事兒

      三個關鍵認知

      • 類型安全是根本:TypeHandler確保類型轉換不出錯,這是底線
      • 性能優化有門道:優先級策略、MetaObject緩存、Reflector復用,都是為了快
      • 靈活擴展是王道:加密、驗證、日志...想玩什么花樣都行

      一句話總結
      ParameterHandler就像是一個靠譜的翻譯官,它知道怎么把你的Java對象"翻譯"成數據庫能理解的參數。有了它,我們寫代碼時只管傳對象,剩下的臟活累活它全包了!

      小彩蛋
      下一篇我們要學ResultSetHandler了,如果說ParameterHandler是"往數據庫送東西",那ResultSetHandler就是"從數據庫拿東西"。你猜猜它會用什么招數把ResultSet轉成Java對象???

      在下一篇文章中,我們將深入分析ResultSetHandler結果集處理機制,了解SQL查詢結果的處理和對象映射過程。


      思考題

      1. DefaultParameterHandler的參數值獲取為什么要設計優先級策略?各個優先級的應用場景是什么?
      2. ParameterHandler如何與TypeHandler協作完成類型轉換?為什么要這樣設計?
      3. 在什么情況下會產生額外參數(AdditionalParameter)?它們是如何生成和使用的?
      4. 如何設計一個通用的參數處理器來支持多種擴展功能(如加密、驗證、日志等)?
      posted on 2025-10-24 16:51  lunzi_fly  閱讀(105)  評論(0)    收藏  舉報

      主站蜘蛛池模板: 亚洲乱妇老熟女爽到高潮的片| 国内精品自国内精品自久久| 2021国产成人精品久久| 国产成人精品永久免费视频| 北岛玲中文字幕人妻系列| 国产精品久久露脸蜜臀| 国产成人亚洲日韩欧美| 无码中文字幕热热久久| 亚洲日韩国产精品第一页一区| 亚洲欧美精品一中文字幕| 在线观看无码不卡av| 国产午夜影视大全免费观看| 熟女国产精品一区二区三| 久久热精品视频在线视频| 少妇真人直播免费视频| 熟妇人妻无码中文字幕老熟妇| 色一情一乱一区二区三区码| 国产一区日韩二区欧美三区| 国产精品人妻一区二区高| 国产美女久久久亚洲综合| 欧美乱人伦人妻中文字幕| 国产精品黄在线观看免费| 巫溪县| 综合色综合色综合色综合| 武川县| 无码免费大香伊蕉在人线国产| 国产精品人妻中文字幕| 91偷自国产一区二区三区| 亚洲精品专区永久免费区| 欧美 亚洲 另类 丝袜 自拍 动漫| 亚洲欧美综合中文| 久久av无码精品人妻出轨| 亚洲人成网站在线观看播放不卡| 99久久国产一区二区三区| 国产超碰无码最新上传| 亚洲国产色婷婷久久99精品91| 国产精品一级久久黄色片| 日韩中文字幕精品人妻| 国产普通话对白刺激| 久热这里只有精品视频3| 欧美综合人人做人人爱|