第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 {// 类型处理器注册表,用于获取对应的TypeHandlerprivate 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;}@Overridepublic 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方法、字段类型等),* 避免重复反射解析,显著减少复杂对象处理的反射开销*/@Overridepublic 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. 参数对象为nullvalue = 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> {@Overridepublic 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()));}}@Overridepublic Date getResult(ResultSet rs, String columnName) throws SQLException {Timestamp timestamp = rs.getTimestamp(columnName);return timestamp != null ? new Date(timestamp.getTime()) : null;}@Overridepublic Date getResult(ResultSet rs, int columnIndex) throws SQLException {Timestamp timestamp = rs.getTimestamp(columnIndex);return timestamp != null ? new Date(timestamp.getTime()) : null;}@Overridepublic 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);// 构建ParameterMappingreturn new ParameterMapping.Builder(configuration, property, typeHandler).javaType(javaType).jdbcType(jdbcType).build();
}
6. 不同参数类型的处理策略
6.1 基本类型参数处理
/*** 基本类型参数处理示例* 当参数是基本类型时,直接使用parameterObject作为参数值*/
public class BasicParameterExample {/*** 单个基本类型参数* SQL: SELECT * FROM user WHERE id = ?*/@Testpublic void testSingleBasicParameter() throws SQLException {// 参数对象就是基本类型值Object parameterObject = 123L;// MyBatis会检测到Long类型有对应的TypeHandlerboolean hasTypeHandler = typeHandlerRegistry.hasTypeHandler(parameterObject.getClass());System.out.println("Long类型有TypeHandler: " + hasTypeHandler);// 直接使用parameterObject作为参数值if (hasTypeHandler) {Object value = parameterObject; // 123LSystem.out.println("参数值: " + value);}}/*** 多个基本类型参数* SQL: SELECT * FROM user WHERE id = ? AND status = ?* MyBatis会自动包装为Map*/@Testpublic 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"); // 123LObject 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 (?, ?, ?)*/@Testpublic void testPojoParameter() {// 参数对象是User POJOUser parameterObject = new User();parameterObject.setName("张三");parameterObject.setEmail("zhangsan@example.com");parameterObject.setAge(25);// MyBatis检测User类型没有内置TypeHandlerboolean hasTypeHandler = typeHandlerRegistry.hasTypeHandler(parameterObject.getClass());System.out.println("User类型有TypeHandler: " + hasTypeHandler); // falseif (!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"); // 25System.out.println("name属性值: " + nameValue);System.out.println("email属性值: " + emailValue);System.out.println("age属性值: " + ageValue);}}/*** 嵌套对象参数* SQL: SELECT * FROM order WHERE user.id = ? AND user.name = ?*/@Testpublic 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"); // 123LObject 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>*/@Testpublic 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"); // 1LObject value2 = boundSql.getAdditionalParameter("__frch_id_1"); // 2LObject value3 = boundSql.getAdditionalParameter("__frch_id_2"); // 3LSystem.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}*/@Testpublic void testMapParameter() {// 参数是MapMap<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"); // 25System.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参数实际应用示例*/@Testpublic 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;}@Overridepublic Object getParameterObject() {return delegate.getParameterObject();}@Overridepublic 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 {@Overridepublic 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;}@Overridepublic Object getParameterObject() {return delegate.getParameterObject();}@Overridepublic 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)?它们是如何生成和使用的?
- 如何设计一个通用的参数处理器来支持多种扩展功能(如加密、验证、日志等)?