第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 参数处理器继承关系图
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获取参数值可不是瞎猜的,人家有一套严格的"优先级规则",就像你找东西一样------先找最有可能的地方:
java
// 第一优先级:额外参数(最高优先级)
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使用示例:
java
// 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)?它们是如何生成和使用的?
- 如何设计一个通用的参数处理器来支持多种扩展功能(如加密、验证、日志等)?