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
📝 发现内容有误?点击此处直接编辑