全部学科
Python全栈
python
NodeJS全栈
nodejs
小程序首页
📅 2026-05-20 8 分钟 ✍️ juanwangdev

ParameterHandler 参数处理

ParameterHandler 是 MyBatis 四大核心组件之一,负责将 Java 参数对象转换为 PreparedStatement 的占位符值,并通过 TypeHandler 完成类型转换。

ParameterHandler 体系

Java
ParameterHandler(接口)
  └── DefaultParameterHandler(唯一实现)
        ├── 获取参数值
        ├── 通过 TypeHandler 转换类型
        └── 设置到 PreparedStatement

注意:MyBatis 只提供一个 ParameterHandler 实现类,与 Executor/StatementHandler 不同,它没有策略模式。

核心接口

Java
public interface ParameterHandler {
  
  // 获取参数对象
  Object getParameterObject();
  
  // 设置参数到 PreparedStatement
  void setParameters(PreparedStatement ps) throws SQLException;
}

DefaultParameterHandler 结构

Java
public class DefaultParameterHandler implements ParameterHandler {
  
  private final TypeHandlerRegistry typeHandlerRegistry;    // 类型处理器注册表
  private final Object parameterObject;                      // 传入的参数对象
  private final BoundSql boundSql;                           // SQL 和参数映射定义
  private final Configuration configuration;                 // 全局配置
  
  public DefaultParameterHandler(Object parameterObject, Configuration configuration, BoundSql boundSql) {
    this.parameterObject = parameterObject;
    this.configuration = configuration;
    this.boundSql = boundSql;
    this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
  }
}

setParameters 详细实现

XML
@Override
public void setParameters(PreparedStatement ps) {
  ErrorContext.instance().activity("setting parameters").object(mappedStatement.getId());
  
  List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
  
  if (parameterMappings != 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();
        
        // 1. 获取参数值
        if (boundSql.hasAdditionalParameter(propertyName)) {
          // 从 BoundSql 的额外参数中获取(通常是 <foreach> 生成的参数)
          value = boundSql.getAdditionalParameter(propertyName);
        } else if (parameterObject == null) {
          value = null;
        } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
          // 参数是简单类型(String/Integer等),直接使用
          value = parameterObject;
        } else {
          // 参数是复杂对象(POJO),通过反射获取属性值
          MetaObject metaObject = configuration.newMetaObject(parameterObject);
          value = metaObject.getValue(propertyName);
        }
        
        // 2. 获取 TypeHandler
        TypeHandler typeHandler = parameterMapping.getTypeHandler();
        JdbcType jdbcType = parameterMapping.getJdbcType();
        
        if (jdbcType == JdbcType.UNDEFINED) {
          jdbcType = configuration.getJdbcTypeForNull();  // 默认为 OTHER
        }
        
        // 3. 设置参数到 PreparedStatement
        typeHandler.setParameter(ps, i + 1, value, jdbcType);
      } else {
        // 注册 OUT 参数(存储过程)
        registerOutputParameter(ps, parameterMapping);
      }
    }
  }
}

索引注意i + 1 因为 PreparedStatement 参数索引从 1 开始,而 parameterMappings 列表索引从 0 开始。

ParameterMapping 解析

来源

ParameterMapping 从 Mapper XML 的 SQL 语句解析中生成:

Java
<select id="findUser" parameterType="UserQuery">
  SELECT * FROM users 
  WHERE age > #{minAge, jdbcType=INTEGER} 
    AND name LIKE #{searchName, jdbcType=VARCHAR}
    AND status IN
    <foreach item="status" collection="statusList" open="(" separator="," close=")">
      #{status}
    </foreach>
</select>

ParameterMapping 结构

Java
public class ParameterMapping {
  
  private String property;          // 属性名:minAge, searchName, status
  private ParameterMode mode;       // 参数模式:IN/OUT/INOUT
  private Class<?> javaType;        // Java 类型:Integer, String
  private JdbcType jdbcType;        // JDBC 类型:INTEGER, VARCHAR
  private TypeHandler<?> typeHandler;  // 类型处理器
  private String resultMapId;       // 关联的 ResultMap ID
  private String numericScale;      // 数字精度
  private String expression;        // OGNL 表达式
}

SQL 解析过程

Java
#{minAge, jdbcType=INTEGER}
       ↓
SqlSourceBuilder.parse()
       ↓
ParameterMappingTokenHandler 解析
       ↓
ParameterMapping {
  property = "minAge",
  jdbcType = INTEGER,
  javaType = Integer,
  mode = IN,
  typeHandler = IntegerTypeHandler
}
       ↓
BoundSql 中的 SQL 转换为:WHERE age > ?

TypeHandler 体系

TypeHandler 是 ParameterHandler 完成 Java 类型与 JDBC 类型转换的核心组件。

TypeHandler 接口

Java
public interface TypeHandler<T> {
  
  // 设置参数:Java -> JDBC
  void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
  
  // 获取结果:JDBC -> Java
  T getResult(ResultSet rs, String columnName) throws SQLException;
  T getResult(ResultSet rs, int columnIndex) throws SQLException;
  T getResult(CallableStatement cs, int columnIndex) throws SQLException;
}

BaseTypeHandler 抽象基类

Java
public abstract class BaseTypeHandler<T> implements TypeHandler<T> {
  
  @Override
  public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
    if (parameter == null) {
      // null 值处理
      if (jdbcType == null) {
        throw new TypeException("JDBC requires that the JdbcType must be specified for all nullable parameters.");
      }
      ps.setNull(i, jdbcType.TYPE_CODE);
    } else {
      try {
        // 调用子类实现的非空设置方法
        setNonNullParameter(ps, i, parameter, jdbcType);
      } catch (Exception e) {
        throw new TypeException("Error setting null for parameter #" + i + " with JdbcType " + jdbcType, e);
      }
    }
  }
  
  // 子类实现
  protected abstract void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
}

TypeHandlerRegistry 注册机制

Java
public class TypeHandlerRegistry {
  
  // 按 Java 类型注册
  private final Map<Type, TypeHandler<?>> typeHandlers = new HashMap<>();
  
  // 按 Java 类型 + JDBC 类型注册(用于精确匹配)
  private final Map<Type, Map<JdbcType, TypeHandler<?>>> typeHandlerMap = new HashMap<>();
  
  public <T> void register(TypeHandler<T> typeHandler) {
    boolean mappedTypeFound = false;
    
    // 获取注解上的 MappedTypes
    MappedTypes mappedTypes = typeHandler.getClass().getAnnotation(MappedTypes.class);
    if (mappedTypes != null) {
      for (Class<?> javaType : mappedTypes.value()) {
        register(javaType, typeHandler);
        mappedTypeFound = true;
      }
    }
    
    // 获取注解上的 MappedJdbcTypes
    MappedJdbcTypes mappedJdbcTypes = typeHandler.getClass().getAnnotation(MappedJdbcTypes.class);
    if (mappedJdbcTypes != null) {
      for (JdbcType jdbcType : mappedJdbcTypes.value()) {
        register(typeHandler, jdbcType);
        mappedTypeFound = true;
      }
    }
    
    if (!mappedTypeFound) {
      register((Class<T>) null, typeHandler);
    }
  }
}

自动注册:自定义 TypeHandler 只需添加 @MappedTypes@MappedJdbcTypes 注解,MyBatis 会自动扫描并注册。

常用 TypeHandler 示例

Java
// String 类型处理
public class StringTypeHandler extends BaseTypeHandler<String> {
  @Override
  protected void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
    ps.setString(i, parameter);
  }
  
  @Override
  public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
    return rs.getString(columnName);
  }
}

// 枚举类型处理
public class EnumTypeHandler<E extends Enum<E>> extends BaseTypeHandler<E> {
  private final Class<E> type;
  private final E[] enums;
  
  @Override
  protected void setNonNullParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType) throws SQLException {
    if (parameter == null) {
      throw new TypeException("Parameter cannot be null");
    }
    // 存储枚举的名称(字符串)
    ps.setString(i, parameter.name());
  }
}

参数对象解析流程

简单类型参数

Java
// 调用方式
mapper.findUserById(123L);

// 参数解析
parameterObject = 123L (Long 类型)
typeHandlerRegistry.hasTypeHandler(Long.class) = true
value = parameterObject  // 直接使用
typeHandler.setParameter(ps, 1, 123L, JdbcType.BIGINT)

复杂对象参数

Java
// 调用方式
UserQuery query = new UserQuery();
query.setMinAge(18);
query.setSearchName("张%");
mapper.findUser(query);

// 参数解析
parameterObject = query (UserQuery 类型)
typeHandlerRegistry.hasTypeHandler(UserQuery.class) = false
metaObject = configuration.newMetaObject(query)

// 解析第一个 #{minAge}
propertyName = "minAge"
value = metaObject.getValue("minAge")  // 通过反射获取
typeHandler.setParameter(ps, 1, 18, JdbcType.INTEGER)

// 解析第二个 #{searchName}
propertyName = "searchName"
value = metaObject.getValue("searchName")  // 通过反射获取
typeHandler.setParameter(ps, 2, "张%", JdbcType.VARCHAR)

@Param 注解参数

XML
// 接口定义
List<User> findUsers(@Param("age") int minAge, @Param("name") String searchName);

// 参数解析
parameterObject = new ParamMap() {
  "age" -> 18,
  "name" -> "张%",
  "param1" -> 18,
  "param2" -> "张%"
}

// #{age} 映射到 param1,#{name} 映射到 param2

ParamMap 机制:多个参数时,MyBatis 自动包装为 ParamMap,key 是 @Param 注解值,同时生成 param1/param2 等通用 key。

自定义 TypeHandler

继承 BaseTypeHandler

text
@MappedTypes({LocalDateTime.class})
@MappedJdbcTypes({JdbcType.TIMESTAMP})
public class LocalDateTimeTypeHandler extends BaseTypeHandler<LocalDateTime> {
  
  @Override
  protected void setNonNullParameter(PreparedStatement ps, int i, LocalDateTime parameter, JdbcType jdbcType) throws SQLException {
    ps.setTimestamp(i, Timestamp.valueOf(parameter));
  }
  
  @Override
  public LocalDateTime getNullableResult(ResultSet rs, String columnName) throws SQLException {
    Timestamp timestamp = rs.getTimestamp(columnName);
    return timestamp == null ? null : timestamp.toLocalDateTime();
  }
}

注册方式

text
<!-- 方式1:在配置文件中注册 -->
<typeHandlers>
  <typeHandler handler="com.example.LocalDateTimeTypeHandler"/>
</typeHandlers>

<!-- 方式2:自动扫描(需要 @MappedTypes 注解) -->
<typeHandlers>
  <package name="com.example.typehandlers"/>
</typeHandlers>

TypeHandler 选择流程

text
获取参数值后 → 获取 ParameterMapping 中的 TypeHandler
                    ↓
              TypeHandler 为空?
              ├── Yes → 根据 Java 类型从 TypeHandlerRegistry 查找
              │         ↓
              │   找到多个 TypeHandler?
              │   ├── Yes → 根据 JDBC 类型精确匹配
              │   └── No  → 使用匹配的 TypeHandler
              └── No → 使用 ParameterMapping 指定的 TypeHandler
                    ↓
              设置参数到 PreparedStatement

要点总结

  • ParameterHandler 唯一实现:只有 DefaultParameterHandler,没有策略模式
  • 参数值获取分三类:BoundSql 额外参数(foreach 生成)→ 简单类型直接使用 → 复杂对象通过 MetaObject 反射获取
  • TypeHandler 是转换核心:负责 Java 类型与 JDBC 类型之间的双向转换,setParameter 和 getResult 两个方向
  • TypeHandler 注册机制:通过 @MappedTypes@MappedJdbcTypes 注解可自动扫描注册
  • null 值处理:TypeHandler 对 null 值需要指定 JdbcType,否则会抛出异常
  • 索引从 1 开始:PreparedStatement 参数索引从 1 开始,循环中需要 i + 1

📝 发现内容有误?点击此处直接编辑

← 上一篇 Executor 执行器体系
下一篇 → ResultSetHandler 结果处理
想查看更多题目和详细解析?
小程序提供完整的题库、模拟考试和详细解析
马上就来

长按或扫描二维码,立即体验

扫码体验小程序
马上就来
使用微信扫描二维码
立即体验完整题库