第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)?它们是如何生成和使用的?
- 如何设计一个通用的参数处理器来支持多种扩展功能(如加密、验证、日志等)?