0%

mybatis之ParameterHandler

MyBatis ParameterHandler:参数绑定的核心桥梁(从源码到实践)

ParameterHandler 是 MyBatis 中连接 Java 参数与 JDBC 预编译 SQL 的关键组件,负责将 Java 中的参数值正确绑定到 SQL 语句的占位符(?)上。它通过协调 TypeHandler 完成 Java 类型到 JDBC 类型的转换,是 SQL 预编译后执行前的最后一步关键操作。本文结合源码,深入解析 ParameterHandler 的工作机制、参数绑定流程及核心设计。

ParameterHandler 接口:参数处理的规范定义

ParameterHandler 接口仅定义了两个核心方法,清晰界定了参数处理的职责边界:

1
2
3
4
5
6
7
public interface ParameterHandler {
// 1. 获取原始参数对象(如 JavaBean、Map、基本类型等)
Object getParameterObject();

// 2. 核心方法:将参数绑定到 PreparedStatement 的占位符上
void setParameters(PreparedStatement ps) throws SQLException;
}
  • getParameterObject():提供对原始参数的访问,方便其他组件(如插件)获取参数信息;
  • setParameters(PreparedStatement):负责将 Java 参数值通过 JDBC API(如 ps.setInt(1, value))绑定到 SQL 占位符,是参数处理的核心逻辑。

DefaultParameterHandler:唯一实现类的核心逻辑

MyBatis 仅提供了一个 ParameterHandler 实现类 ——DefaultParameterHandler,它完整实现了从 Java 参数到 JDBC 占位符的绑定流程。其核心依赖包括:

  • TypeHandlerRegistry:管理所有类型处理器,负责 Java 类型与 JDBC 类型的转换;
  • MappedStatement:包含 SQL 节点的配置信息(如参数映射、类型别名等);
  • parameterObject:用户传入的原始参数(可能是 JavaBean、Map、数组等);
  • BoundSql:包含解析后的 SQL 语句和参数映射列表(ParameterMapping)。

构造函数:初始化核心依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class DefaultParameterHandler implements ParameterHandler {
private final TypeHandlerRegistry typeHandlerRegistry; // 类型处理器注册表
private final MappedStatement mappedStatement; // SQL配置元信息
private final Object parameterObject; // 原始参数对象
private final BoundSql boundSql; // 封装SQL和参数映射
private final Configuration configuration; // MyBatis全局配置

// 构造函数:初始化所有依赖组件
public DefaultParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
this.mappedStatement = mappedStatement;
this.configuration = mappedStatement.getConfiguration();
this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
this.parameterObject = parameterObject;
this.boundSql = boundSql;
}

// 获取原始参数对象
@Override
public Object getParameterObject() {
return parameterObject;
}

// ... 核心方法 setParameters 见下文
}

构造函数通过注入核心依赖,为参数绑定做好准备。其中 TypeHandlerRegistry 是关键,它存储了 MyBatis 内置及用户自定义的类型处理器,用于参数类型转换。

核心方法:setParameters () 详解

setParameters() 是参数处理的核心,其逻辑可概括为:遍历参数映射列表→获取参数值→匹配类型处理器→绑定到 PreparedStatement

完整源码与步骤解析
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
@Override
public void setParameters(PreparedStatement ps) {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());

// 1. 获取SQL中的参数映射列表(每个元素对应一个?占位符)
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings != null) {
// 2. 遍历每个参数映射,依次绑定到PreparedStatement
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);

// 3. 过滤存储过程的OUT参数(OUT参数由CallableStatementHandler单独处理)
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value; // 存储当前参数的值
String propertyName = parameterMapping.getProperty(); // 参数名称(如#{id}中的id)

// 4. 从参数对象中获取参数值
if (boundSql.hasAdditionalParameter(propertyName)) {
// 4.1 优先从额外参数中获取(如动态SQL中通过${}生成的参数)
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
// 4.2 参数对象为null,直接赋值null
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
// 4.3 参数对象本身是简单类型(如Integer、String),直接作为值
value = parameterObject;
} else {
// 4.4 参数对象是复杂类型(如JavaBean、Map),通过MetaObject获取属性值
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}

// 5. 获取当前参数的类型处理器(TypeHandler)
TypeHandler typeHandler = parameterMapping.getTypeHandler();
// 6. 获取当前参数对应的JDBC类型(如VARCHAR、INTEGER)
JdbcType jdbcType = parameterMapping.getJdbcType();
// 6.1 若参数值为null且未指定JdbcType,使用全局默认配置(如NULL)
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}

try {
// 7. 核心:通过TypeHandler将参数值绑定到PreparedStatement的指定位置(i+1,JDBC索引从1开始)
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (SQLException | TypeException e) {
throw new TypeException("参数绑定失败:" + parameterMapping + ". 原因:" + e, e);
}
}
}
}
}
关键步骤拆解
(1)参数映射列表(ParameterMapping)

ParameterMapping 是 SQL 占位符与 Java 参数的映射关系,包含:

  • property:参数名称(如 #{userName} 中的 userName);
  • typeHandler:该参数对应的类型处理器;
  • jdbcType:目标 JDBC 类型(如 VARCHAR);
  • mode:参数模式(IN/OUT/INOUT,默认为 IN)。

这些信息在 MyBatis 解析 Mapper 时生成,存储在 BoundSql 中。

(2)参数值的获取逻辑(优先级)

参数值的获取是核心环节,MyBatis 支持多种参数类型(基本类型、JavaBean、Map 等),获取逻辑按以下优先级:

  1. 额外参数(AdditionalParameter):动态 SQL 中通过 setAdditionalParameter() 添加的临时参数(如 <if test="name != null"> 中生成的参数);
  2. 参数对象为 null:直接返回 null;
  3. 简单类型参数:若参数是基本类型(如 IntegerString)或有对应的 TypeHandler,直接使用参数对象本身;
  4. 复杂类型参数:通过 MetaObject(MyBatis 的反射工具)获取属性值(支持 JavaBean 的 getter 方法、Map 的 key 访问)。

示例

  • 若参数是 User 对象({id:1, name:"张三"}),propertyName="name" 则通过 metaObject.getValue("name") 获取 "张三"
  • 若参数是 Map{id:1, name:"张三"}),propertyName="name" 则直接获取 map.get("name")
(3)TypeHandler:类型转换的执行者

TypeHandler 是参数类型转换的核心,负责将 Java 类型转换为 JDBC 类型。MyBatis 内置了大量类型处理器(如 IntegerTypeHandlerStringTypeHandler),也支持自定义。

typeHandler.setParameter(...) 最终调用 JDBC 方法完成绑定,例如:

  • IntegerTypeHandler 调用 ps.setInt(index, value)
  • StringTypeHandler 调用 ps.setString(index, value)
(4)null 值处理

当参数值为 null 时,需指定 JDBC 类型(否则数据库可能无法识别):

  • 优先使用 ParameterMapping 中配置的 jdbcType(如 #{name,jdbcType=VARCHAR});
  • 若未配置,使用全局配置 configuration.setJdbcTypeForNull(JdbcType.NULL)(默认为 OTHER,部分数据库可能不兼容,建议显式配置)。

TypeHandler:类型转换的关键角色

ParameterHandler 的参数绑定依赖 TypeHandler 完成类型转换,二者是策略模式的典型应用:ParameterHandler 负责流程调度,TypeHandler 负责具体类型的转换逻辑。

TypeHandler 接口定义

1
2
3
4
5
6
7
8
9
public interface TypeHandler<T> {
// 设置参数(核心方法)
void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;

// 从结果集中获取值(用于查询结果映射)
T getResult(ResultSet rs, String columnName) throws SQLException;
T getResult(ResultSet rs, int columnIndex) throws SQLException;
T getResult(CallableStatement cs, int columnIndex) throws SQLException;
}

TypeHandler 不仅用于参数绑定(setParameter),还用于查询结果映射(getResult),是连接 Java 与 JDBC 类型的双向桥梁。

TypeHandlerRegistry:类型处理器的注册表

TypeHandlerRegistry 管理所有类型处理器,提供:

  • 内置类型处理器的注册(如 StringVARCHARIntegerINTEGER);
  • 自定义类型处理器的注册(通过 @MappedTypes@MappedJdbcTypes 注解);
  • 根据 Java 类型和 JDBC 类型匹配合适的 TypeHandler

DefaultParameterHandler 中,通过 typeHandlerRegistry.hasTypeHandler(parameterObject.getClass()) 判断参数是否为简单类型,简化参数值获取逻辑。

实战场景与常见问题

1. 不同参数类型的处理示例

(1)基本类型参数
1
2
3
<select id="selectById" resultType="User">
SELECT * FROM user WHERE id = #{id}
</select>
  • 参数:Integer id = 1
  • 处理流程:parameterObjectInteger(简单类型)→ value = parameterObject(即 1)→ 匹配 IntegerTypeHandler → 调用 ps.setInt(1, 1)
(2)JavaBean 参数
1
2
3
<insert id="insertUser">
INSERT INTO user(name, age) VALUES(#{name}, #{age})
</insert>
  • 参数:User user = new User("张三", 20)
  • 处理流程:parameterObjectUser(复杂类型)→ 通过 MetaObject 获取 nameage → 分别使用 StringTypeHandlerIntegerTypeHandler 绑定。
(3)Map 参数
1
2
3
<select id="selectByMap" resultType="User">
SELECT * FROM user WHERE name = #{name} AND age = #{age}
</select>
  • 参数:Map<String, Object> params = new HashMap<>(); params.put("name", "张三"); params.put("age", 20)
  • 处理流程:parameterObjectMap → 通过 MetaObject 直接获取 map.get("name")map.get("age") → 绑定参数。
(4)多参数(@Param 注解)
1
User selectByParams(@Param("name") String name, @Param("age") Integer age);
1
2
3
<select id="selectByParams" resultType="User">
SELECT * FROM user WHERE name = #{name} AND age = #{age}
</select>
  • 处理流程:MyBatis 自动将多参数封装为 Map(key 为 @Param 注解值)→ 后续逻辑同 Map 参数。

2. 常见问题与解决方案

(1)参数为 null 时绑定失败
  • 问题#{name} 对应的参数值为 null,且未指定 jdbcType,导致数据库无法识别类型;

  • 解决方案:显式指定jdbcType:

    1
    SELECT * FROM user WHERE name = #{name, jdbcType=VARCHAR}
(2)自定义类型参数绑定
  • 场景:需要将 Java 枚举类型(如 StatusEnum)绑定为数据库 INT 类型(存储枚举的 code);

  • 解决步骤:

    1. 自定义TypeHandler:

      1
      2
      3
      4
      5
      6
      7
      public class StatusEnumTypeHandler extends BaseTypeHandler<StatusEnum> {
      @Override
      public void setParameter(PreparedStatement ps, int i, StatusEnum parameter, JdbcType jdbcType) throws SQLException {
      ps.setInt(i, parameter.getCode()); // 绑定枚举的code值
      }
      // 其他方法省略(查询时使用)
      }
    2. 注册类型处理器(mybatis-config.xml):

      1
      2
      3
      <typeHandlers>
      <typeHandler handler="com.example.StatusEnumTypeHandler" javaType="com.example.StatusEnum"/>
      </typeHandlers>
    3. 在 Mapper 中使用:

      1
      2
      3
      <update id="updateStatus">
      UPDATE user SET status = #{status} WHERE id = #{id}
      </update>
(3)参数名称与属性不匹配
  • 问题#{userName} 对应的参数对象中属性为 name,导致 MetaObject 获取值失败;
  • 解决方案:
    • 修正参数名称与属性一致;
    • 或使用别名:#{userName, property=name}(指定实际属性名)。

总结

ParameterHandler 作为 MyBatis 参数绑定的核心组件,通过以下机制确保 Java 参数正确映射到 JDBC 预编译 SQL:

  1. 流程驱动setParameters() 方法按 “遍历参数映射→获取参数值→类型转换→绑定参数” 的流程执行;
  2. 类型转换:依赖 TypeHandler 完成 Java 类型到 JDBC 类型的转换,支持内置和自定义处理器;
  3. 灵活性:支持多种参数类型(基本类型、JavaBean、Map 等),通过 MetaObject 统一处理复杂对象的属性访问

欢迎关注我的其它发布渠道