第7篇:ParameterHandler参数处理机制

1. 学习目标确认

1.0 第6篇思考题解答

在深入学习ParameterHandler参数处理机制之前,让我们先回顾并解答第6篇中提出的思考题,这将帮助我们更好地理解ParameterHandler在整个执行流程中的关键作用。

思考题1:为什么MyBatis要设计RoutingStatementHandler?直接使用具体的StatementHandler不行吗?

答案要点

  • 统一入口:RoutingStatementHandler提供统一的创建入口,屏蔽了具体实现的复杂性
  • 策略模式:根据StatementType动态选择合适的处理器,实现了策略模式
  • 扩展性:便于添加新的StatementHandler类型,不影响现有代码
  • 简化调用:Executor只需要与RoutingStatementHandler交互,不需要关心具体类型
  • 插件支持:为插件拦截提供统一的拦截点

与ParameterHandler的关系:每种StatementHandler都需要ParameterHandler来设置SQL参数,RoutingStatementHandler确保了参数处理的一致性。

思考题2:PreparedStatementHandler相比SimpleStatementHandler有什么优势?为什么是默认选择?

答案要点

  • 安全优势:支持参数绑定,有效防止SQL注入攻击
  • 性能优势:SQL预编译,相同SQL结构可以复用执行计划,减少10-20%的SQL解析时间(视数据库实现)
  • 类型安全:通过TypeHandler进行类型转换,确保类型安全
  • 维护性:参数与SQL分离,代码结构更清晰
  • 功能丰富:支持主键生成、批量操作等高级功能

ParameterHandler的重要性:PreparedStatementHandler的核心优势来自于ParameterHandler的参数绑定机制。

思考题3:CallableStatementHandler如何处理存储过程的输出参数?与普通查询有什么区别?

答案要点

  • 参数注册:需要预先注册输出参数的类型和位置
  • 参数模式:支持IN、OUT、INOUT三种参数模式
  • 结果获取:通过CallableStatement.getXxx()方法获取输出参数值
  • 处理时机:在SQL执行完成后处理输出参数
  • 类型转换:输出参数也需要进行类型转换
  • 多结果集支持:复杂存储过程可能返回多个结果集,需通过ResultSetHandler.handleMultipleResults()方法处理

存储过程多结果集处理示例

// 调用存储过程,处理多个结果集和输出参数
CallableStatement cs = connection.prepareCall("{call getUserOrders(?, ?)}");

// 注册输出参数
cs.setLong(1, userId);  // 输入参数:用户ID
cs.registerOutParameter(2, Types.INTEGER);  // 输出参数:订单总数

// 执行存储过程
cs.execute();

// 处理第一个结果集(用户信息)
ResultSet rs1 = cs.getResultSet();
while (rs1.next()) {
    System.out.println("用户: " + rs1.getString("name"));
}

// 切换到下一个结果集(订单列表)
if (cs.getMoreResults()) {
    ResultSet rs2 = cs.getResultSet();
    while (rs2.next()) {
        System.out.println("订单: " + rs2.getString("order_no"));
    }
}

// 获取输出参数
int totalOrders = cs.getInt(2);
System.out.println("订单总数: " + totalOrders);

ParameterHandler的扩展:CallableStatementHandler需要扩展ParameterHandler来处理输出参数的注册和获取。

思考题4:StatementHandler与ParameterHandler、ResultSetHandler是如何协作的?

答案要点

  • 组合模式:StatementHandler通过组合包含ParameterHandler和ResultSetHandler
  • 职责分工:StatementHandler负责Statement管理,ParameterHandler负责参数设置,ResultSetHandler负责结果处理
  • 执行流程:prepare() → parameterize() → execute() → handleResults()
  • 统一管理:StatementHandler统一协调三者的执行时序

核心协作流程

StatementHandler.prepare()     // 创建Statement
↓
ParameterHandler.setParameters() // 设置参数
↓
Statement.execute()            // 执行SQL
↓
ResultSetHandler.handleResultSets() // 处理结果

1.1 本篇学习目标

通过本文你将能够:

  1. 深入理解ParameterHandler参数处理器的设计思想和核心职责
  2. 掌握DefaultParameterHandler的实现机制和参数设置流程
  3. 理解参数映射(ParameterMapping)的配置和使用方式
  4. 掌握TypeHandler类型处理器与参数处理的协作关系
  5. 了解不同参数类型(基本类型、对象、集合)的处理策略
  6. 理解额外参数(AdditionalParameter)的生成与使用机制
  7. 掌握参数验证和性能优化策略
  8. 具备自定义ParameterHandler扩展开发的能力

2. ParameterHandler参数处理器体系总览

还记得第6篇我们说StatementHandler是"SQL执行的总指挥"吗?那ParameterHandler就是这位总指挥手下最得力的"后勤部长"——专门负责把我们Java代码里的参数"翻译"成数据库能听懂的话。

想象一下,你要给外国朋友寄包裹,得先把中文地址翻译成英文对吧?ParameterHandler做的就是这个活儿:把 User user = new User("张三", 18)这样的Java对象,翻译成 ps.setString(1, "张三"); ps.setInt(2, 18)这样的JDBC调用。比如将 #{user.name}转换为 ps.setString(1, "张三")这样神奇的操作!

2.1 参数处理器继承关系图

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

2.2 参数处理器职责分工

组件 核心职责 主要功能 性能特点
ParameterHandler 参数处理接口 定义参数设置规范 统一入口,零性能损耗
DefaultParameterHandler 默认参数处理实现 参数值获取、类型转换、参数设置 内置优先级策略,高效稳定
ParameterMapping 参数映射配置 参数属性、类型、模式配置 建造者模式,减少对象创建开销
TypeHandler 类型转换处理 Java类型与JDBC类型互转 TypeHandler缓存可提升5-10%参数设置效率
LanguageDriver 语言驱动器 创建ParameterHandler实例 动态选择实现,支持多种SQL方言

2.3 参数处理流程图

sequenceDiagram participant SH as StatementHandler participant PH as ParameterHandler participant PM as ParameterMapping participant TH as TypeHandler participant PS as PreparedStatement SH->>PH: setParameters(ps) Note over PH: MetaObject缓存反射结果<br/>优化复杂对象处理 loop 遍历参数映射 PH->>PM: getProperty() PH->>PH: getPropertyValue() alt 额外参数 PH->>PH: boundSql.getAdditionalParameter() else 基本类型 PH->>PH: parameterObject直接使用 else 复杂对象 Note over PH: 使用MetaObject反射<br/>性能开销较大 PH->>PH: metaObject.getValue() end PH->>TH: setParameter(ps, index, value, jdbcType) TH->>PS: setXxx(index, value) end PS-->>SH: 参数设置完成

3. ParameterHandler接口定义

3.1 接口源码分析

ParameterHandler接口非常简洁,只定义了两个核心方法:

package org.apache.ibatis.executor.parameter;

import java.sql.PreparedStatement;
import java.sql.SQLException;

/**
 * 参数处理器接口
 * 负责为PreparedStatement设置参数
 * 
 * @author Clinton Begin
 */
public interface ParameterHandler {

    /**
     * 获取参数对象
     * @return 参数对象,可能为null、基本类型(如Long、String)、复杂对象(如User)或集合(如List<User>)
     * 
     * 示例返回值:
     * - 基本类型:Long id = 123L
     * - 复杂对象:User user = new User()
     * - 集合类型:List<Long> ids = Arrays.asList(1L, 2L, 3L)
     * - 空值:null(无参数方法)
     */
    Object getParameterObject();
  
    // 使用示例
    // Object param = handler.getParameterObject();
    // 可能是 Long、User、List<User> 或 null

    /**
     * 为PreparedStatement设置参数
     * 这是ParameterHandler的核心方法,负责:
     * 1. 获取参数值
     * 2. 进行类型转换
     * 3. 调用PreparedStatement的setXxx方法设置参数
     * 
     * @param ps PreparedStatement对象
     * @throws SQLException 参数设置过程中的SQL异常
     */
    void setParameters(PreparedStatement ps) throws SQLException;
}

3.2 接口设计特点

简洁得让人惊讶

  • 就两个方法!getParameterObject()和setParameters(),简单到你都怀疑"这就完了?"
  • 但别小看它,MyBatis的"单一职责原则"在这儿体现得淋漓尽致

扩展性拉满

  • 想加个参数加密?写个实现类就行
  • 想做参数校验?也是写个实现类
  • 这就是面向接口编程的魅力啊!

团队协作能手

  • 跟TypeHandler配合:"兄弟,这个Date类型你来转一下"
  • 跟StatementHandler配合:"老大,参数我都设置好了,可以执行了!"

4. DefaultParameterHandler默认实现

4.1 核心源码分析

DefaultParameterHandler是ParameterHandler的默认实现,承担了参数处理的核心逻辑:

package org.apache.ibatis.scripting.defaults;

import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.ParameterMode;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeException;
import org.apache.ibatis.type.TypeHandler;
import org.apache.ibatis.type.TypeHandlerRegistry;

import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.List;

/**
 * 默认参数处理器实现
 * 负责将Java参数对象转换为PreparedStatement的参数
 * 
 * @author Clinton Begin
 * @author Eduardo Macarron
 */
public class DefaultParameterHandler implements ParameterHandler {

    // 类型处理器注册表,用于获取对应的TypeHandler
    private final TypeHandlerRegistry typeHandlerRegistry;
  
    // SQL映射语句,包含参数映射配置
    private final MappedStatement mappedStatement;
  
    // 参数对象,可能是null、基本类型、Map、POJO等
    private final Object parameterObject;
  
    // 绑定SQL对象,包含参数映射列表
    private final BoundSql boundSql;
  
    // MyBatis全局配置对象
    private final Configuration configuration;

    /**
     * 构造方法
     * 初始化参数处理器所需的核心组件
     * 
     * @param mappedStatement SQL映射语句,包含参数映射配置
     * @param parameterObject 参数对象,可能为null、基本类型、复杂对象或集合
     * @param boundSql 绑定SQL对象,包含参数映射列表和额外参数
     */
    public DefaultParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
        this.mappedStatement = mappedStatement;
        this.configuration = mappedStatement.getConfiguration();
        this.typeHandlerRegistry = mappedStatement.getConfiguration().getTypeHandlerRegistry();
        this.parameterObject = parameterObject;
        this.boundSql = boundSql;
    }

    @Override
    public Object getParameterObject() {
        return parameterObject;
    }

    /**
     * 设置PreparedStatement参数的核心方法
     * 遍历所有参数映射,为每个参数设置值
     * 
     * 核心处理流程(伪代码):
     * for (ParameterMapping pm : boundSql.getParameterMappings()) {
     *     Object value = getPropertyValue(pm, metaObject);  // 优先级策略获取值
     *     TypeHandler th = pm.getTypeHandler();           // 获取类型处理器
     *     th.setParameter(ps, index++, value, pm.getJdbcType()); // 设置参数
     * }
     * 
     * MetaObject缓存优化: MetaObject内部使用Reflector缓存属性元数据(getter/setter方法、字段类型等),
     * 避免重复反射解析,显著减少复杂对象处理的反射开销
     */
    @Override
    public void setParameters(PreparedStatement ps) throws SQLException {
        // 设置错误上下文,便于问题定位
        ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
      
        // 获取参数映射列表
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
      
        if (parameterMappings != null) {
            MetaObject metaObject = null;
          
            // 遍历每个参数映射
            for (int i = 0; i < parameterMappings.size(); i++) {
                ParameterMapping parameterMapping = parameterMappings.get(i);
              
                // 跳过输出参数(OUT参数用于存储过程)
                if (parameterMapping.getMode() != ParameterMode.OUT) {
                    Object value;
                    String propertyName = parameterMapping.getProperty();
                  
                    // 参数值获取的优先级策略
                    if (boundSql.hasAdditionalParameter(propertyName)) {
                        // 1. 优先从额外参数中获取(如foreach生成的参数)
                        value = boundSql.getAdditionalParameter(propertyName);
                    } else if (parameterObject == null) {
                        // 2. 参数对象为null
                        value = null;
                    } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                        // 3. 参数对象是基本类型(有对应的TypeHandler)
                        value = parameterObject;
                    } else {
                        // 4. 参数对象是复杂类型,通过反射获取属性值
                        // MetaObject使用Reflector缓存属性元数据,减少反射开销
                        if (metaObject == null) {
                            metaObject = configuration.newMetaObject(parameterObject);
                        }
                        value = metaObject.getValue(propertyName);
                    }
                  
                    // 获取对应的类型处理器
                    TypeHandler typeHandler = parameterMapping.getTypeHandler();
                    JdbcType jdbcType = parameterMapping.getJdbcType();
                  
                    // 处理null值的JDBC类型
                    if (value == null && jdbcType == null) {
                        jdbcType = configuration.getJdbcTypeForNull();
                    }
                  
                    try {
                        // 使用TypeHandler设置参数
                        typeHandler.setParameter(ps, i + 1, value, jdbcType);
                    } catch (TypeException | SQLException e) {
                        // 封装异常信息,便于问题定位
                        throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
                    }
                }
            }
        }
    }
}

4.2 参数值获取策略

DefaultParameterHandler获取参数值可不是瞎猜的,人家有一套严格的"优先级规则",就像你找东西一样——先找最有可能的地方:

// 第一优先级:额外参数(最高优先级)
if (boundSql.hasAdditionalParameter(propertyName)) {
    // "哎,这个参数是foreach动态生成的,我先用这个!"
    value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
    // 第二优先级:参数对象是null
    // "啊?你啥参数都没给我?那我也没办法,只能是null了"
    value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
    // 第三优先级:基本类型
    // "哦,你就传了个Long/String?那我直接用就行了"
    value = parameterObject;
} else {
    // 第四优先级:复杂对象(最低优先级)
    // "嗯?User对象?我得反射一下拿到user.getName()这些属性值"
    if (metaObject == null) {
        metaObject = configuration.newMetaObject(parameterObject);
    }
    value = metaObject.getValue(propertyName);
}

为什么要这样设计?

  1. 额外参数优先:动态SQL(像foreach)会生成临时参数,这些肯定是最新的,当然要先用
  2. null处理:空指针异常是程序员的噩梦,先判断null能避免很多问题
  3. 基本类型直接用Long id = 123L这种,还反射个啥?直接用多省事
  4. 复杂对象才反射:User、Order这些对象,没办法,只能用反射了(虽然慢点,但准确啊)

4.3 类型处理器协作

DefaultParameterHandler跟TypeHandler的配合就像是"翻译官"和"专业术语顾问"的关系:

内置TypeHandler全家桶

  • StringTypeHandler:String ↔ VARCHAR,这个最常用
  • IntegerTypeHandler:Integer ↔ INTEGER,处理数字的
  • DateTypeHandler:Date ↔ TIMESTAMP,时间类型专用
  • EnumTypeHandler:枚举 ↔ VARCHAR,枚举值的好帮手

它俩怎么配合的?

// 获取类型处理器
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();

// 处理null值的JDBC类型
if (value == null && jdbcType == null) {
    jdbcType = configuration.getJdbcTypeForNull();
}

try {
    // 委托给TypeHandler进行具体的参数设置
    // TypeHandler会根据Java类型和JDBC类型进行转换
    typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException | SQLException e) {
    throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}

协作示例: 当参数类型为 String 时,StringTypeHandler 会调用 PreparedStatement.setString(index, value) 设置参数。

内置TypeHandler使用示例:

// StringTypeHandler 处理字符串参数
TypeHandler<String> stringHandler = new StringTypeHandler();
stringHandler.setParameter(ps, 1, "张三", JdbcType.VARCHAR);

// IntegerTypeHandler 处理整数参数
TypeHandler<Integer> intHandler = new IntegerTypeHandler();
intHandler.setParameter(ps, 2, 25, JdbcType.INTEGER);

// DateTypeHandler 处理日期参数
TypeHandler<Date> dateHandler = new DateTypeHandler();
dateHandler.setParameter(ps, 3, new Date(), JdbcType.TIMESTAMP);

自定义TypeHandler示例

/**
 * 自定义日期类型处理器
 * 将Java Date转换为JDBC Timestamp
 */
public class CustomDateTypeHandler implements TypeHandler<Date> {
  
    @Override
    public void setParameter(PreparedStatement ps, int i, Date parameter, JdbcType jdbcType) throws SQLException {
        if (parameter == null) {
            ps.setNull(i, Types.TIMESTAMP);
        } else {
            ps.setTimestamp(i, new Timestamp(parameter.getTime()));
        }
    }
  
    @Override
    public Date getResult(ResultSet rs, String columnName) throws SQLException {
        Timestamp timestamp = rs.getTimestamp(columnName);
        return timestamp != null ? new Date(timestamp.getTime()) : null;
    }
  
    @Override
    public Date getResult(ResultSet rs, int columnIndex) throws SQLException {
        Timestamp timestamp = rs.getTimestamp(columnIndex);
        return timestamp != null ? new Date(timestamp.getTime()) : null;
    }
  
    @Override
    public Date getResult(CallableStatement cs, int columnIndex) throws SQLException {
        Timestamp timestamp = cs.getTimestamp(columnIndex);
        return timestamp != null ? new Date(timestamp.getTime()) : null;
    }
}

// 注册自定义TypeHandler
configuration.getTypeHandlerRegistry().register(Date.class, JdbcType.TIMESTAMP, new CustomDateTypeHandler());

5. ParameterMapping参数映射配置

5.1 参数映射核心属性

ParameterMapping定义了参数的映射配置信息:

package org.apache.ibatis.mapping;

import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeHandler;

/**
 * 参数映射配置类
 * 包含参数的详细映射信息
 */
public class ParameterMapping {

    private Configuration configuration;
  
    // 参数属性名称(在参数对象中的属性名)
    private String property;
  
    // 参数模式:IN(输入)、OUT(输出)、INOUT(输入输出)
    private ParameterMode mode;
  
    // Java类型
    private Class<?> javaType = Object.class;
  
    // JDBC类型
    private JdbcType jdbcType;
  
    // 数字精度(用于NUMERIC和DECIMAL类型)
    // 例如:BigDecimal类型的小数位数,price字段精度为2表示保留两位小数
    // 应用场景:处理金额、汇率等需要精确小数的数值
    private Integer numericScale;
  
    // 类型处理器
    private TypeHandler<?> typeHandler;
  
    // 结果映射ID(用于复杂类型)
    private String resultMapId;
  
    // JDBC类型名称
    private String jdbcTypeName;
  
    // 表达式(用于动态参数)
    private String expression;

    // 构造方法和Getter/Setter略...
  
    /**
     * 参数映射建造器
     * 使用建造者模式构建ParameterMapping对象
     */
    public static class Builder {
        private ParameterMapping parameterMapping = new ParameterMapping();

        public Builder(Configuration configuration, String property, TypeHandler<?> typeHandler) {
            parameterMapping.configuration = configuration;
            parameterMapping.property = property;
            parameterMapping.typeHandler = typeHandler;
        }

        public Builder mode(ParameterMode mode) {
            parameterMapping.mode = mode;
            return this;
        }

        public Builder javaType(Class<?> javaType) {
            parameterMapping.javaType = javaType;
            return this;
        }

        public Builder jdbcType(JdbcType jdbcType) {
            parameterMapping.jdbcType = jdbcType;
            return this;
        }

        public Builder numericScale(Integer numericScale) {
            parameterMapping.numericScale = numericScale;
            return this;
        }

        public Builder resultMapId(String resultMapId) {
            parameterMapping.resultMapId = resultMapId;
            return this;
        }

        public Builder jdbcTypeName(String jdbcTypeName) {
            parameterMapping.jdbcTypeName = jdbcTypeName;
            return this;
        }

        public Builder expression(String expression) {
            parameterMapping.expression = expression;
            return this;
        }

        public ParameterMapping build() {
            resolveTypeHandler();
            validate();
            return parameterMapping;
        }

        private void resolveTypeHandler() {
            if (parameterMapping.typeHandler == null && parameterMapping.javaType != null) {
                Configuration configuration = parameterMapping.configuration;
                TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
                parameterMapping.typeHandler = typeHandlerRegistry.getTypeHandler(parameterMapping.javaType, parameterMapping.jdbcType);
            }
        }

        private void validate() {
            if (parameterMapping.typeHandler == null) {
                throw new BuilderException("Type handler was null on parameter mapping for property '"
                    + parameterMapping.property + "'. It was either not specified and/or could not be found for the javaType ("
                    + parameterMapping.javaType.getName() + ") : jdbcType (" + parameterMapping.jdbcType + ") combination.");
            }
        }
    }
}

5.2 参数映射创建流程

参数映射通过SqlSource解析过程创建,支持在XML或注解中配置精度:

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

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

<!-- 批量插入时指定精度 -->
<insert id="batchInsert" parameterType="list">
    INSERT INTO product (name, price) VALUES
    <foreach collection="list" item="item" separator=",">
        (#{item.name}, #{item.price, javaType=BigDecimal, jdbcType=DECIMAL, numericScale=2})
    </foreach>
</insert>

参数映射构建代码

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

参数映射构建代码

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

6. 不同参数类型的处理策略

6.1 基本类型参数处理

/**
 * 基本类型参数处理示例
 * 当参数是基本类型时,直接使用parameterObject作为参数值
 */
public class BasicParameterExample {
  
    /**
     * 单个基本类型参数
     * SQL: SELECT * FROM user WHERE id = ?
     */
    @Test
    public void testSingleBasicParameter() throws SQLException {
        // 参数对象就是基本类型值
        Object parameterObject = 123L;
      
        // MyBatis会检测到Long类型有对应的TypeHandler
        boolean hasTypeHandler = typeHandlerRegistry.hasTypeHandler(parameterObject.getClass());
        System.out.println("Long类型有TypeHandler: " + hasTypeHandler);
      
        // 直接使用parameterObject作为参数值
        if (hasTypeHandler) {
            Object value = parameterObject; // 123L
            System.out.println("参数值: " + value);
        }
    }
  
    /**
     * 多个基本类型参数
     * SQL: SELECT * FROM user WHERE id = ? AND status = ?
     * MyBatis会自动包装为Map
     */
    @Test
    public void testMultipleBasicParameters() {
        // MyBatis自动包装:{arg0=123, arg1="ACTIVE", param1=123, param2="ACTIVE"}
        Map<String, Object> parameterObject = new HashMap<>();
        parameterObject.put("arg0", 123L);
        parameterObject.put("arg1", "ACTIVE");
        parameterObject.put("param1", 123L);
        parameterObject.put("param2", "ACTIVE");
      
        // 通过属性名获取值
        Object value1 = ((Map) parameterObject).get("arg0"); // 123L
        Object value2 = ((Map) parameterObject).get("arg1"); // "ACTIVE"
      
        System.out.println("第一个参数: " + value1);
        System.out.println("第二个参数: " + value2);
    }
}

6.2 复杂对象参数处理

/**
 * 复杂对象参数处理示例
 * 当参数是POJO对象时,通过MetaObject反射获取属性值
 */
public class ComplexParameterExample {
  
    /**
     * POJO对象参数
     * SQL: INSERT INTO user (name, email, age) VALUES (?, ?, ?)
     */
    @Test
    public void testPojoParameter() {
        // 参数对象是User POJO
        User parameterObject = new User();
        parameterObject.setName("张三");
        parameterObject.setEmail("zhangsan@example.com");
        parameterObject.setAge(25);
      
        // MyBatis检测User类型没有内置TypeHandler
        boolean hasTypeHandler = typeHandlerRegistry.hasTypeHandler(parameterObject.getClass());
        System.out.println("User类型有TypeHandler: " + hasTypeHandler); // false
      
        if (!hasTypeHandler) {
            // 创建MetaObject进行反射操作
            MetaObject metaObject = configuration.newMetaObject(parameterObject);
          
            // 通过属性名获取值
            Object nameValue = metaObject.getValue("name");       // "张三"
            Object emailValue = metaObject.getValue("email");     // "zhangsan@example.com"
            Object ageValue = metaObject.getValue("age");         // 25
          
            System.out.println("name属性值: " + nameValue);
            System.out.println("email属性值: " + emailValue);
            System.out.println("age属性值: " + ageValue);
        }
    }
  
    /**
     * 嵌套对象参数
     * SQL: SELECT * FROM order WHERE user.id = ? AND user.name = ?
     */
    @Test
    public void testNestedObjectParameter() {
        // 创建嵌套对象
        User user = new User();
        user.setId(123L);
        user.setName("张三");
      
        Order parameterObject = new Order();
        parameterObject.setUser(user);
      
        // 通过MetaObject访问嵌套属性
        MetaObject metaObject = configuration.newMetaObject(parameterObject);
      
        Object userId = metaObject.getValue("user.id");     // 123L
        Object userName = metaObject.getValue("user.name"); // "张三"
      
        System.out.println("嵌套属性user.id: " + userId);
        System.out.println("嵌套属性user.name: " + userName);
    }
}

6.3 集合类型参数处理

/**
 * 集合类型参数处理示例
 * MyBatis通过动态SQL和额外参数处理集合
 */
public class CollectionParameterExample {
  
    /**
     * List参数处理
     * SQL: SELECT * FROM user WHERE id IN <foreach collection="list" item="id" open="(" separator="," close=")">#{id}</foreach>
     */
    @Test
    public void testListParameter() {
        // 参数是List集合
        List<Long> parameterObject = Arrays.asList(1L, 2L, 3L);
      
        // MyBatis会将List包装为Map: {"list": [1L, 2L, 3L]}
        Map<String, Object> wrappedParam = new HashMap<>();
        wrappedParam.put("list", parameterObject);
      
        // 在foreach处理过程中,会生成额外参数
        BoundSql boundSql = new BoundSql(configuration, "SELECT * FROM user WHERE id IN (?, ?, ?)", 
            Arrays.asList(
                createParameterMapping("__frch_id_0", Long.class),
                createParameterMapping("__frch_id_1", Long.class),
                createParameterMapping("__frch_id_2", Long.class)
            ), wrappedParam);
          
        // 设置额外参数
        boundSql.setAdditionalParameter("__frch_id_0", 1L);
        boundSql.setAdditionalParameter("__frch_id_1", 2L);
        boundSql.setAdditionalParameter("__frch_id_2", 3L);
      
        // 获取额外参数(最高优先级)
        Object value1 = boundSql.getAdditionalParameter("__frch_id_0"); // 1L
        Object value2 = boundSql.getAdditionalParameter("__frch_id_1"); // 2L
        Object value3 = boundSql.getAdditionalParameter("__frch_id_2"); // 3L
      
        System.out.println("foreach生成的参数1: " + value1);
        System.out.println("foreach生成的参数2: " + value2);
        System.out.println("foreach生成的参数3: " + value3);
    }
  
    /**
     * Map参数处理
     * SQL: SELECT * FROM user WHERE name = #{name} AND age = #{age}
     */
    @Test
    public void testMapParameter() {
        // 参数是Map
        Map<String, Object> parameterObject = new HashMap<>();
        parameterObject.put("name", "张三");
        parameterObject.put("age", 25);
      
        // Map类型没有内置TypeHandler,但Map实现了特殊处理
        // Map参数可以直接从boundSql.getAdditionalParameter获取
        // 或通过MetaObject的MapWrapper获取
        MetaObject metaObject = configuration.newMetaObject(parameterObject);
      
        // 通过Map的key获取值
        Object nameValue = metaObject.getValue("name"); // "张三"
        Object ageValue = metaObject.getValue("age");   // 25
      
        System.out.println("Map参数name: " + nameValue);
        System.out.println("Map参数age: " + ageValue);
      
        // Map作为参数的特殊处理
        // 当参数是Map时,MyBatis会将其包装为ParamMap
        // SQL中的#{name}会直接从Map中通过"name"键获取值
        System.out.println("Map参数直接映射: #{name} -> " + parameterObject.get("name"));
    }
  
    /**
     * Map参数实际应用示例
     */
    @Test
    public void testMapParameterInAction() {
        Map<String, Object> params = new HashMap<>();
        params.put("id", 1L);
        params.put("name", "张三");
        params.put("age", 25);
      
        // 对应的Mapper方法
        // List<User> findByMap(Map<String, Object> params);
        // SQL: SELECT * FROM user WHERE id = #{id} AND name = #{name} AND age = #{age}
      
        // 实际应用场景:Map参数适合动态条件查询,无需创建专门的参数对象
        // MyBatis会直接从Map中获取键对应的值(如id、name、age),
        // 并通过MetaObject包装后按属性名访问
      
        System.out.println("使用Map参数查询: " + params);
    }
}

7. 实践案例:自定义参数处理器

7.1 参数加密处理器

让我们创建一个参数加密处理器,自动对敏感参数进行加密:

package com.example.parameter;

import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.ParameterMode;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeHandler;
import org.apache.ibatis.type.TypeHandlerRegistry;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Base64;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * 参数加密处理器
 * 对指定的敏感参数进行自动加密
 */
public class EncryptionParameterHandler implements ParameterHandler {
  
    private final DefaultParameterHandler delegate;
    private final Configuration configuration;
    private final TypeHandlerRegistry typeHandlerRegistry;
    private final MappedStatement mappedStatement;
    private final Object parameterObject;
    private final BoundSql boundSql;
  
    // 需要加密的字段名集合
    private static final Set<String> ENCRYPT_FIELDS = new HashSet<>();
  
    // AES加密密钥(实际应用中应从配置文件读取)
    private static final String ENCRYPT_KEY = "MyBatis123456789"; // 16位密钥
  
    static {
        // 配置需要加密的字段
        ENCRYPT_FIELDS.add("password");
        ENCRYPT_FIELDS.add("idCard");
        ENCRYPT_FIELDS.add("phone");
        ENCRYPT_FIELDS.add("email");
    }
  
    public EncryptionParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
        this.delegate = new DefaultParameterHandler(mappedStatement, parameterObject, boundSql);
        this.configuration = mappedStatement.getConfiguration();
        this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
        this.mappedStatement = mappedStatement;
        this.parameterObject = parameterObject;
        this.boundSql = boundSql;
    }
  
    @Override
    public Object getParameterObject() {
        return delegate.getParameterObject();
    }
  
    @Override
    public void setParameters(PreparedStatement ps) throws SQLException {
        System.out.println("=== 使用加密参数处理器 ===");
      
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
      
        if (parameterMappings != null) {
            MetaObject metaObject = null;
          
            for (int i = 0; i < parameterMappings.size(); i++) {
                ParameterMapping parameterMapping = parameterMappings.get(i);
              
                if (parameterMapping.getMode() != ParameterMode.OUT) {
                    Object value;
                    String propertyName = parameterMapping.getProperty();
                  
                    // 获取原始参数值(使用与DefaultParameterHandler相同的逻辑)
                    if (boundSql.hasAdditionalParameter(propertyName)) {
                        value = boundSql.getAdditionalParameter(propertyName);
                    } else if (parameterObject == null) {
                        value = null;
                    } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                        value = parameterObject;
                    } else {
                        if (metaObject == null) {
                            metaObject = configuration.newMetaObject(parameterObject);
                        }
                        value = metaObject.getValue(propertyName);
                    }
                  
                    // 对敏感字段进行加密
                    if (needEncryption(propertyName, value)) {
                        String originalValue = (String) value;
                        value = encryptValue(originalValue);
                        System.out.println(String.format("字段 [%s] 加密: %s -> %s", 
                            propertyName, originalValue, value));
                    }
                  
                    // 设置参数
                    TypeHandler typeHandler = parameterMapping.getTypeHandler();
                    JdbcType jdbcType = parameterMapping.getJdbcType();
                  
                    if (value == null && jdbcType == null) {
                        jdbcType = configuration.getJdbcTypeForNull();
                    }
                  
                    try {
                        typeHandler.setParameter(ps, i + 1, value, jdbcType);
                    } catch (Exception e) {
                        throw new SQLException("Could not set parameter: " + e.getMessage(), e);
                    }
                }
            }
        }
    }
  
    /**
     * 判断是否需要加密
     */
    private boolean needEncryption(String propertyName, Object value) {
        return value instanceof String && 
               ENCRYPT_FIELDS.contains(propertyName) && 
               !((String) value).isEmpty();
    }
  
    /**
     * AES加密
     */
    private String encryptValue(String value) {
        try {
            SecretKeySpec secretKey = new SecretKeySpec(ENCRYPT_KEY.getBytes(StandardCharsets.UTF_8), "AES");
            Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
            cipher.init(Cipher.ENCRYPT_MODE, secretKey);
          
            byte[] encryptedBytes = cipher.doFinal(value.getBytes(StandardCharsets.UTF_8));
            return Base64.getEncoder().encodeToString(encryptedBytes);
        } catch (Exception e) {
            throw new RuntimeException("参数加密失败: " + e.getMessage(), e);
        }
    }
  
    /**
     * 添加加密字段
     */
    public static void addEncryptField(String fieldName) {
        ENCRYPT_FIELDS.add(fieldName);
    }
  
    /**
     * 移除加密字段
     */
    public static void removeEncryptField(String fieldName) {
        ENCRYPT_FIELDS.remove(fieldName);
    }
}

7.2 自定义LanguageDriver

创建自定义的LanguageDriver来使用我们的加密参数处理器:

package com.example.parameter;

import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.parsing.XNode;
import org.apache.ibatis.scripting.LanguageDriver;
import org.apache.ibatis.scripting.xmltags.XMLLanguageDriver;
import org.apache.ibatis.session.Configuration;

/**
 * 加密语言驱动器
 * 创建加密参数处理器
 */
public class EncryptionLanguageDriver extends XMLLanguageDriver implements LanguageDriver {
  
    @Override
    public ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
        // 根据配置决定是否使用加密处理器
        Configuration configuration = mappedStatement.getConfiguration();
      
        // 检查是否启用加密(可以通过配置或注解控制)
        if (isEncryptionEnabled(mappedStatement)) {
            System.out.println("创建加密参数处理器 for: " + mappedStatement.getId());
            return new EncryptionParameterHandler(mappedStatement, parameterObject, boundSql);
        } else {
            // 使用默认参数处理器
            return super.createParameterHandler(mappedStatement, parameterObject, boundSql);
        }
    }
  
    /**
     * 判断是否启用加密
     * 可以根据MappedStatement的ID、配置等进行判断
     */
    private boolean isEncryptionEnabled(MappedStatement mappedStatement) {
        String statementId = mappedStatement.getId();
      
        // 示例:对包含"User"的Mapper启用加密
        return statementId.contains("User");
    }
}

7.3 配置使用

插件注册方式

自定义ParameterHandler通常通过MyBatis插件机制注册,在 mybatis-config.xml中配置:

<plugins>
    <!-- 参数验证拦截器 -->
    <plugin interceptor="com.example.ValidationParameterInterceptor"/>
    <!-- 参数加密拦截器 -->
    <plugin interceptor="com.example.EncryptionParameterInterceptor"/>
</plugins>

语言驱动器配置

<configuration>
    <!-- 注册自定义语言驱动器 -->
    <typeAliases>
        <typeAlias alias="encryptionLanguageDriver" type="com.example.parameter.EncryptionLanguageDriver"/>
    </typeAliases>
  
    <!-- 设置默认语言驱动器 -->
    <settings>
        <setting name="defaultScriptingLanguage" value="encryptionLanguageDriver"/>
    </settings>
  
    <!-- 或者在特定的Mapper方法上使用 -->
</configuration>

在Mapper接口中使用:

package com.example.mapper;

import org.apache.ibatis.annotations.*;
import com.example.parameter.EncryptionLanguageDriver;

public interface UserMapper {
  
    /**
     * 使用默认语言驱动器(会自动加密password字段)
     */
    @Insert("INSERT INTO user (username, password, email) VALUES (#{username}, #{password}, #{email})")
    int insertUser(User user);
  
    /**
     * 显式指定语言驱动器
     */
    @Lang(EncryptionLanguageDriver.class)
    @Update("UPDATE user SET password = #{password} WHERE id = #{id}")
    int updatePassword(@Param("id") Long id, @Param("password") String password);
}

完整测试代码

package com.example;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import com.example.mapper.UserMapper;
import com.example.entity.User;

import java.io.InputStream;

/**
 * 加密参数处理器测试
 * 演示自定义ParameterHandler的完整使用流程
 */
public class EncryptionParameterTest {
  
    public static void main(String[] args) {
        SqlSessionFactory factory = MyBatisUtils.getSqlSessionFactory();
      
        testEncryptionParameterHandler(factory);
    }
  
    /**
     * 测试参数加密功能
     */
    private static void testEncryptionParameterHandler(SqlSessionFactory factory) {
        System.out.println("=== 测试参数加密功能 ===");
      
        try (SqlSession session = factory.openSession()) {
            UserMapper mapper = session.getMapper(UserMapper.class);
          
            // 创建用户对象
            User user = new User();
            user.setUsername("testuser");
            user.setPassword("mypassword123");      // 敏感信息,会被加密
            user.setEmail("test@example.com");      // 敏感信息,会被加密
          
            System.out.println(">>> 插入用户前");
            System.out.println("原始密码: " + user.getPassword());
            System.out.println("原始邮箱: " + user.getEmail());
          
            // 插入用户(password和email字段会自动加密)
            int result = mapper.insertUser(user);
          
            System.out.println(">>> 插入结果: " + result);
          
            // 更新密码测试
            System.out.println(">>> 更新密码测试");
            String newPassword = "newpassword456";
            System.out.println("新密码: " + newPassword);
          
            int updateResult = mapper.updatePassword(1L, newPassword);
            System.out.println("更新结果: " + updateResult);
          
            session.commit();
        }
    }
}

7.4 参数验证处理器

再创建一个参数验证处理器:

package com.example.parameter;

import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.ParameterMode;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.TypeHandlerRegistry;

import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
import java.util.regex.Pattern;

/**
 * 参数验证处理器
 * 在设置参数前进行数据验证
 */
public class ValidationParameterHandler implements ParameterHandler {
  
    private final DefaultParameterHandler delegate;
    private final Configuration configuration;
    private final TypeHandlerRegistry typeHandlerRegistry;
    private final MappedStatement mappedStatement;
    private final Object parameterObject;
    private final BoundSql boundSql;
  
    // 验证规则映射
    private static final Map<String, Predicate<Object>> VALIDATION_RULES = new HashMap<>();
  
    static {
        // 邮箱格式验证
        VALIDATION_RULES.put("email", value -> {
            if (value == null) return true;
            String email = value.toString();
            return Pattern.matches("^[A-Za-z0-9+_.-]+@([A-Za-z0-9.-]+\\.[A-Za-z]{2,})$", email);
        });
      
        // 手机号格式验证
        VALIDATION_RULES.put("phone", value -> {
            if (value == null) return true;
            String phone = value.toString();
            return Pattern.matches("^1[3-9]\\d{9}$", phone);
        });
      
        // 用户名长度验证
        VALIDATION_RULES.put("username", value -> {
            if (value == null) return false;
            String username = value.toString();
            return username.length() >= 3 && username.length() <= 20;
        });
      
        // 密码强度验证
        VALIDATION_RULES.put("password", value -> {
            if (value == null) return false;
            String password = value.toString();
            // 至少8位,包含字母和数字
            return password.length() >= 8 && 
                   Pattern.matches(".*[A-Za-z].*", password) && 
                   Pattern.matches(".*\\d.*", password);
        });
    }
  
    public ValidationParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
        this.delegate = new DefaultParameterHandler(mappedStatement, parameterObject, boundSql);
        this.configuration = mappedStatement.getConfiguration();
        this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
        this.mappedStatement = mappedStatement;
        this.parameterObject = parameterObject;
        this.boundSql = boundSql;
    }
  
    @Override
    public Object getParameterObject() {
        return delegate.getParameterObject();
    }
  
    @Override
    public void setParameters(PreparedStatement ps) throws SQLException {
        System.out.println("=== 使用验证参数处理器 ===");
      
        // 首先进行参数验证
        validateParameters();
      
        // 验证通过后,委托给默认处理器
        delegate.setParameters(ps);
    }
  
    /**
     * 验证所有参数
     */
    private void validateParameters() {
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
      
        if (parameterMappings != null) {
            MetaObject metaObject = null;
          
            for (ParameterMapping parameterMapping : parameterMappings) {
                if (parameterMapping.getMode() != ParameterMode.OUT) {
                    Object value;
                    String propertyName = parameterMapping.getProperty();
                  
                    // 获取参数值
                    if (boundSql.hasAdditionalParameter(propertyName)) {
                        value = boundSql.getAdditionalParameter(propertyName);
                    } else if (parameterObject == null) {
                        value = null;
                    } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                        value = parameterObject;
                    } else {
                        if (metaObject == null) {
                            metaObject = configuration.newMetaObject(parameterObject);
                        }
                        value = metaObject.getValue(propertyName);
                    }
                  
                    // 执行验证
                    validateParameter(propertyName, value);
                }
            }
        }
    }
  
    /**
     * 验证单个参数
     */
    private void validateParameter(String propertyName, Object value) {
        Predicate<Object> validator = VALIDATION_RULES.get(propertyName);
      
        if (validator != null) {
            boolean isValid = validator.test(value);
          
            if (!isValid) {
                String message = String.format("参数验证失败: %s = %s", propertyName, value);
                System.err.println(message);
                throw new IllegalArgumentException(message);
            } else {
                System.out.println(String.format("参数验证通过: %s = %s", propertyName, value));
            }
        }
    }
  
    /**
     * 添加验证规则
     */
    public static void addValidationRule(String propertyName, Predicate<Object> validator) {
        VALIDATION_RULES.put(propertyName, validator);
    }
  
    /**
     * 移除验证规则
     */
    public static void removeValidationRule(String propertyName) {
        VALIDATION_RULES.remove(propertyName);
    }
}

8. 源码调试指导

8.1 关键断点位置

DefaultParameterHandler断点

  1. DefaultParameterHandler.setParameters() - 参数设置入口
  2. 参数值获取的优先级判断逻辑
  3. typeHandler.setParameter() - 类型处理器设置参数

ParameterMapping断点

  1. ParameterMapping.Builder.build() - 参数映射构建
  2. resolveTypeHandler() - 类型处理器解析

MetaObject断点

  1. MetaObject.getValue() - 反射获取属性值
  2. 嵌套属性访问逻辑

8.2 调试技巧

工具建议

  • 使用IDEA的条件断点,表达式:parameterObject.getClass().getSimpleName().equals("User")
  • 使用IDEA的字段监视(Field Watchpoint)观察 parameterObject的变化
  • 启用MyBatis日志:<setting name="logImpl" value="STDOUT_LOGGING"/>

观察参数类型判断

// 在setParameters方法中观察类型判断逻辑
if (boundSql.hasAdditionalParameter(propertyName)) {
    System.out.println("使用额外参数: " + propertyName);
} else if (parameterObject == null) {
    System.out.println("参数对象为null");
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
    System.out.println("参数是基本类型: " + parameterObject.getClass().getSimpleName());
} else {
    System.out.println("参数是复杂对象: " + parameterObject.getClass().getSimpleName());
}

观察参数映射

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

观察类型处理器选择逻辑

// 在TypeHandlerRegistry.getTypeHandler方法设置断点
// 观察类型处理器的匹配过程
TypeHandler typeHandler = parameterMapping.getTypeHandler();
System.out.println("使用的TypeHandler: " + typeHandler.getClass().getSimpleName());

// 观察TypeHandler的选择逻辑
TypeHandler<?> handler = typeHandlerRegistry.getTypeHandler(
    parameterMapping.getJavaType(), 
    parameterMapping.getJdbcType()
);
System.out.println(String.format("类型匹配: javaType=%s, jdbcType=%s -> %s", 
    parameterMapping.getJavaType().getSimpleName(),
    parameterMapping.getJdbcType(),
    handler.getClass().getSimpleName()));

9. 易错与排错清单

9.1 常见问题

问题 原因 解决方案
参数设置失败 ParameterMapping配置错误 检查参数映射的javaType和jdbcType
类型转换异常 TypeHandler选择不当 确认类型处理器配置
参数值为null 属性名拼写错误或对象为null 检查属性名和参数对象
反射失败 私有属性无getter方法 确保属性有对应的getter方法
嵌套属性访问失败 中间对象为null 检查嵌套对象的初始化
参数名不可靠 无@Param且未开启-parameters编译选项 使用@Param注解或启用编译参数保留
动态SQL参数错误 <foreach>的collection配置错误导致参数缺失 检查collection属性与实际参数类型匹配(list/array/map key),使用@Param明确命名避免歧义

9.2 性能优化建议

  1. 复用TypeHandler:为常用类型注册TypeHandler,避免每次查询都创建新实例
  2. 避免过度反射:复杂对象参数尽量使用字段直接访问,减少getter调用。MetaObject缓存可减少10-15%反射开销,通过 Reflector缓存属性元数据避免重复解析
  3. 批量操作优化:使用 executorType="BATCH"配合 addBatch,减少参数设置次数
  4. 参数命名规范:避免使用过长的嵌套属性(如 user.address.city.name),层级过深会增加反射开销

10. 小结

恭喜你!看到这里,你已经彻底搞懂ParameterHandler这个"参数翻译官"是怎么工作的了。

让我们快速回顾一下今天的收获

  1. ParameterHandler接口:简洁到只有2个方法,但设计得很优雅
  2. DefaultParameterHandler:内置的"四级参数查找机制",从额外参数到反射,层层递进
  3. ParameterMapping:参数的"身份证",记录了参数的所有信息
  4. 参数类型处理:基本类型直接用、复杂对象靠反射、集合类型走额外参数
  5. TypeHandler协作:ParameterHandler找值,TypeHandler转类型,分工明确
  6. 扩展开发:想加密?想验证?写个ParameterHandler实现类就完事儿

三个关键认知

  • 类型安全是根本:TypeHandler确保类型转换不出错,这是底线
  • 性能优化有门道:优先级策略、MetaObject缓存、Reflector复用,都是为了快
  • 灵活扩展是王道:加密、验证、日志...想玩什么花样都行

一句话总结
ParameterHandler就像是一个靠谱的翻译官,它知道怎么把你的Java对象"翻译"成数据库能理解的参数。有了它,我们写代码时只管传对象,剩下的脏活累活它全包了!

小彩蛋
下一篇我们要学ResultSetHandler了,如果说ParameterHandler是"往数据库送东西",那ResultSetHandler就是"从数据库拿东西"。你猜猜它会用什么招数把ResultSet转成Java对象?😏

在下一篇文章中,我们将深入分析ResultSetHandler结果集处理机制,了解SQL查询结果的处理和对象映射过程。


思考题

  1. DefaultParameterHandler的参数值获取为什么要设计优先级策略?各个优先级的应用场景是什么?
  2. ParameterHandler如何与TypeHandler协作完成类型转换?为什么要这样设计?
  3. 在什么情况下会产生额外参数(AdditionalParameter)?它们是如何生成和使用的?
  4. 如何设计一个通用的参数处理器来支持多种扩展功能(如加密、验证、日志等)?
posted on 2025-10-24 16:51  lunzi_fly  阅读(26)  评论(0)    收藏  举报