第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 本篇學習目標
通過本文你將能夠:
- 深入理解ParameterHandler參數處理器的設計思想和核心職責
- 掌握DefaultParameterHandler的實現機制和參數設置流程
- 理解參數映射(ParameterMapping)的配置和使用方式
- 掌握TypeHandler類型處理器與參數處理的協作關系
- 了解不同參數類型(基本類型、對象、集合)的處理策略
- 理解額外參數(AdditionalParameter)的生成與使用機制
- 掌握參數驗證和性能優化策略
- 具備自定義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 參數處理器繼承關系圖
2.2 參數處理器職責分工
| 組件 | 核心職責 | 主要功能 | 性能特點 |
|---|---|---|---|
| ParameterHandler | 參數處理接口 | 定義參數設置規范 | 統一入口,零性能損耗 |
| DefaultParameterHandler | 默認參數處理實現 | 參數值獲取、類型轉換、參數設置 | 內置優先級策略,高效穩定 |
| ParameterMapping | 參數映射配置 | 參數屬性、類型、模式配置 | 建造者模式,減少對象創建開銷 |
| TypeHandler | 類型轉換處理 | Java類型與JDBC類型互轉 | TypeHandler緩存可提升5-10%參數設置效率 |
| LanguageDriver | 語言驅動器 | 創建ParameterHandler實例 | 動態選擇實現,支持多種SQL方言 |
2.3 參數處理流程圖
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);
}
為什么要這樣設計?
- 額外參數優先:動態SQL(像foreach)會生成臨時參數,這些肯定是最新的,當然要先用
- null處理:空指針異常是程序員的噩夢,先判斷null能避免很多問題
- 基本類型直接用:
Long id = 123L這種,還反射個啥?直接用多省事 - 復雜對象才反射: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斷點:
DefaultParameterHandler.setParameters()- 參數設置入口- 參數值獲取的優先級判斷邏輯
typeHandler.setParameter()- 類型處理器設置參數
ParameterMapping斷點:
ParameterMapping.Builder.build()- 參數映射構建resolveTypeHandler()- 類型處理器解析
MetaObject斷點:
MetaObject.getValue()- 反射獲取屬性值- 嵌套屬性訪問邏輯
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 性能優化建議
- 復用TypeHandler:為常用類型注冊TypeHandler,避免每次查詢都創建新實例
- 避免過度反射:復雜對象參數盡量使用字段直接訪問,減少getter調用。MetaObject緩存可減少10-15%反射開銷,通過
Reflector緩存屬性元數據避免重復解析 - 批量操作優化:使用
executorType="BATCH"配合addBatch,減少參數設置次數 - 參數命名規范:避免使用過長的嵌套屬性(如
user.address.city.name),層級過深會增加反射開銷
10. 小結
恭喜你!看到這里,你已經徹底搞懂ParameterHandler這個"參數翻譯官"是怎么工作的了。
讓我們快速回顧一下今天的收獲:
- ParameterHandler接口:簡潔到只有2個方法,但設計得很優雅
- DefaultParameterHandler:內置的"四級參數查找機制",從額外參數到反射,層層遞進
- ParameterMapping:參數的"身份證",記錄了參數的所有信息
- 參數類型處理:基本類型直接用、復雜對象靠反射、集合類型走額外參數
- TypeHandler協作:ParameterHandler找值,TypeHandler轉類型,分工明確
- 擴展開發:想加密?想驗證?寫個ParameterHandler實現類就完事兒
三個關鍵認知:
- 類型安全是根本:TypeHandler確保類型轉換不出錯,這是底線
- 性能優化有門道:優先級策略、MetaObject緩存、Reflector復用,都是為了快
- 靈活擴展是王道:加密、驗證、日志...想玩什么花樣都行
一句話總結:
ParameterHandler就像是一個靠譜的翻譯官,它知道怎么把你的Java對象"翻譯"成數據庫能理解的參數。有了它,我們寫代碼時只管傳對象,剩下的臟活累活它全包了!
小彩蛋:
下一篇我們要學ResultSetHandler了,如果說ParameterHandler是"往數據庫送東西",那ResultSetHandler就是"從數據庫拿東西"。你猜猜它會用什么招數把ResultSet轉成Java對象???
在下一篇文章中,我們將深入分析ResultSetHandler結果集處理機制,了解SQL查詢結果的處理和對象映射過程。
思考題:
- DefaultParameterHandler的參數值獲取為什么要設計優先級策略?各個優先級的應用場景是什么?
- ParameterHandler如何與TypeHandler協作完成類型轉換?為什么要這樣設計?
- 在什么情況下會產生額外參數(AdditionalParameter)?它們是如何生成和使用的?
- 如何設計一個通用的參數處理器來支持多種擴展功能(如加密、驗證、日志等)?
浙公網安備 33010602011771號