深入理解MyBatis类型转换机制,掌握Java与JDBC之间的桥梁
一、MyBatis整体架构与类型处理模块
在深入类型处理模块之前,我们先了解MyBatis的整体架构,以及类型处理模块在其中的重要地位。

从架构图可以看出,MyBatis采用了分层架构设计,而类型处理模块(TypeHandler)位于基础支撑层,是连接Java类型与JDBC类型的桥梁。它负责在SQL执行过程中,将Java对象参数转换为JDBC能够识别的参数类型,以及将ResultSet中的数据转换为Java对象。
型处理模块的核心职责
类型处理模块主要承担以下核心职责:
sql
✅ Java类型与JDBC类型相互转换 - 实现双向类型转换
✅ 参数设置时的类型转换 - 将Java参数转换为JDBC参数
✅ 结果集获取时的类型转换 - 将JDBC结果转换为Java对象
✅ Null值处理 - 处理Java空值与JDBC NULL的转换
✅ 类型注册与管理 - 维护Java类型与TypeHandler的映射关系
为什么需要TypeHandler?
在Java应用程序和数据库之间,存在着类型系统的差异:
| Java类型 | JDBC类型 | 说明 |
|---|---|---|
| String | VARCHAR, CHAR | 字符串类型 |
| Integer | INTEGER | 整数类型 |
| Long | BIGINT | 长整数类型 |
| Date | TIMESTAMP | 日期时间类型 |
| BigDecimal | NUMERIC | 精确数值类型 |
TypeHandler的核心作用就是解决这些类型之间的映射和转换问题。
二、TypeHandler体系架构
TypeHandler采用了泛型接口设计,支持各种类型的扩展。

TypeHandler接口
TypeHandler是类型转换的核心接口,定义了类型转换的基本方法:
java
public interface TypeHandler<T> {
// 设置PreparedStatement参数(Java → JDBC)
void setParameter(PreparedStatement ps, int i,
T parameter, JdbcType jdbcType)
throws SQLException;
// 获取ResultSet结果(JDBC → Java,按列名)
T getResult(ResultSet rs, String columnName)
throws SQLException;
// 获取ResultSet结果(JDBC → Java,按列索引)
T getResult(ResultSet rs, int columnIndex)
throws SQLException;
// 获取CallableStatement结果(存储过程)
T getResult(CallableStatement cs, int columnIndex)
throws SQLException;
}
BaseTypeHandler抽象类
为了简化TypeHandler的实现,MyBatis提供了BaseTypeHandler抽象类,它帮我们处理了Null值检查等通用逻辑:
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值
ps.setNull(i, jdbcType.TYPE_CODE);
} else {
// 设置非空参数,由子类实现
setNonNullParameter(ps, i, parameter, jdbcType);
}
}
@Override
public T getResult(ResultSet rs, String columnName)
throws SQLException {
T result = getNullableResult(rs, columnName);
// 检查是否为null
return rs.wasNull() ? null : result;
}
// 抽象方法,由子类实现
protected abstract void setNonNullParameter(
PreparedStatement ps, int i,
T parameter, JdbcType jdbcType)
throws SQLException;
protected abstract T getNullableResult(
ResultSet rs, String columnName)
throws SQLException;
}
TypeHandler继承体系

三、类型转换双向流程
TypeHandler实现了Java类型与JDBC类型之间的双向转换。

Java → JDBC 转换流程
当执行SQL语句时,需要将Java参数转换为JDBC参数:
ini
// 1. ParameterHandler获取参数值
Object value = getParameterValue(
parameterObject, parameterMapping);
// 2. 获取对应的TypeHandler
TypeHandler<?> typeHandler =
parameterMapping.getTypeHandler();
// 3. 调用setParameter设置参数
typeHandler.setParameter(ps, i + 1, value, jdbcType);
JDBC → Java 转换流程
当从ResultSet获取结果时,需要将JDBC数据转换为Java对象:
scss
// 1. ResultSetHandler处理ResultSet
while (resultSet.next()) {
// 2. 根据ResultMapping获取TypeHandler
TypeHandler<?> typeHandler =
resultMapping.getTypeHandler();
// 3. 调用getResult获取结果
Object value = typeHandler.getResult(
resultSet, column);
// 4. 设置到目标对象
metaObject.setValue(property, value);
}
四、参数设置时的类型转换
参数设置是Java类型向JDBC类型转换的过程。

基本类型转换示例
java
public class StringTypeHandler
extends BaseTypeHandler<String> {
@Override
public void setNonNullParameter(
PreparedStatement ps, int i,
String parameter, JdbcType jdbcType)
throws SQLException {
// String → VARCHAR
ps.setString(i, parameter);
}
@Override
public String getNullableResult(
ResultSet rs, String columnName)
throws SQLException {
// VARCHAR → String
return rs.getString(columnName);
}
}
public class LongTypeHandler
extends BaseTypeHandler<Long> {
@Override
public void setNonNullParameter(
PreparedStatement ps, int i,
Long parameter, JdbcType jdbcType)
throws SQLException {
// Long → BIGINT
ps.setLong(i, parameter);
}
@Override
public Long getNullableResult(
ResultSet rs, String columnName)
throws SQLException {
// BIGINT → Long
long value = rs.getLong(columnName);
// ⚠️ 关键:检查是否为NULL
return value == 0 && rs.wasNull() ? null : value;
}
}
public class DateTypeHandler
extends BaseTypeHandler<Date> {
@Override
public void setNonNullParameter(
PreparedStatement ps, int i,
Date parameter, JdbcType jdbcType)
throws SQLException {
// Date → Timestamp
ps.setTimestamp(i,
new Timestamp(parameter.getTime()));
}
@Override
public Date getNullableResult(
ResultSet rs, String columnName)
throws SQLException {
// Timestamp → Date
Timestamp timestamp = rs.getTimestamp(columnName);
return timestamp == null ? null :
new Date(timestamp.getTime());
}
}
Null值处理
Null值处理是参数设置中的重要环节:
xml
<!-- 场景1:明确的jdbcType -->
<insert id="insertUser">
INSERT INTO t_user (name, email)
VALUES (
#{name, jdbcType=VARCHAR},
#{email, jdbcType=VARCHAR}
)
</insert>
<!-- 场景2:全局配置Null的jdbcType -->
<settings>
<setting name="jdbcTypeForNull" value="NULL"/>
</settings>
重要提示:未指定jdbcType时,传入null参数会抛出异常!
五、结果集获取时的类型转换
结果集获取是JDBC类型向Java类型转换的过程。

ResultSet结果获取流程
scss
// 遍历ResultSet的完整流程
while (resultSet.next()) {
for (ResultMapping mapping :
resultMap.getPropertyResultMappings()) {
// 获取列名
String column = mapping.getColumn();
// 获取TypeHandler
TypeHandler<?> handler =
mapping.getTypeHandler();
// 获取结果值
Object value = handler.getResult(
resultSet, column);
// 设置到目标对象
metaObject.setValue(
mapping.getProperty(), value);
}
}
三种getResult方式
TypeHandler提供了三种获取结果的方式:
arduino
@Override
public String getNullableResult(
ResultSet rs, String columnName)
throws SQLException {
return rs.getString(columnName);
}
使用场景:
java
<result column="user_name" property="userName"/>
@Override
public String getNullableResult(
ResultSet rs, int columnIndex)
throws SQLException {
return rs.getString(columnIndex);
}
使用场景:自动映射或需要按索引获取时
java
@Override
public String getNullableResult(
CallableStatement cs, int columnIndex)
throws SQLException {
return cs.getString(columnIndex);
}
使用场景:
sql
<select id="callProcedure" statementType="CALLABLE">
{call get_user_info(
#{userId, mode=IN, jdbcType=BIGINT},
#{userName, mode=OUT, jdbcType=VARCHAR}
)}
</select>
wasNull()的重要性
在类型转换中,wasNull()方法非常关键:
java
@Override
public Long getNullableResult(
ResultSet rs, String columnName)
throws SQLException {
long value = rs.getLong(columnName);
// ⚠️ 如果数据库是NULL,getLong()返回0
// ✅ 必须检查wasNull()区分真实的0和NULL
return value == 0 && rs.wasNull() ? null : value;
}
六、TypeHandler注册机制
TypeHandlerRegistry负责管理所有TypeHandler的注册和查找。

TypeHandlerRegistry结构
arduino
public class TypeHandlerRegistry {
// JDBC类型 → TypeHandler
private final Map<JdbcType, TypeHandler<?>>
jdbcTypeHandlerMap = new EnumMap<>(JdbcType.class);
// Java类型 → (JDBC类型 → TypeHandler)
private final Map<Type, Map<JdbcType, TypeHandler<?>>>
typeHandlerMap = new HashMap<>();
// Java类型 → 默认TypeHandler
private final Map<Class<?>, TypeHandler<?>>
allTypeHandlersMap = new HashMap<>();
public TypeHandlerRegistry(){
// 注册所有内置TypeHandler
register(Boolean.class, new BooleanTypeHandler());
register(Integer.class, new IntegerTypeHandler());
register(Long.class, new LongTypeHandler());
register(String.class, new StringTypeHandler());
register(Date.class, new DateTypeHandler());
// ... 更多注册
}
}
注册TypeHandler的四种方式
typescript
public <T> void register(
Class<T> javaType,
TypeHandler<? extends T> typeHandler) {
register((Type) javaType, typeHandler);
}
public <T> void register(
Class<T> type,
JdbcType jdbcType,
TypeHandler<? extends T> typeHandler) {
register((Type) type, jdbcType, typeHandler);
}
public <T> void register(
TypeReference<T> javaTypeReference,
TypeHandler<? extends T> typeHandler) {
register(javaTypeReference.getRawType(),
typeHandler);
}
public void register(String packageName) {
ResolverUtil<Class<?>> resolverUtil =
new ResolverUtil<>();
resolverUtil.find(
new ResolverUtil.IsA(TypeHandler.class),
packageName);
Set<Class<? extends Class<?>>> handlerSet =
resolverUtil.getClasses();
for (Class<?> type : handlerSet) {
if (!type.isAnonymousClass() &&
!type.isInterface() &&
!Modifier.isAbstract(type.getModifiers())) {
register(type);
}
}
}
查找TypeHandler
typescript
// 根据Java类型和JDBC类型查找
public <T> TypeHandler<T> getTypeHandler(
Class<T> type, JdbcType jdbcType) {
// 1. 从Java类型映射中查找
Map<JdbcType, TypeHandler<?>> jdbcHandlerMap =
typeHandlerMap.get(type);
if (jdbcHandlerMap != null) {
TypeHandler<?> handler =
jdbcHandlerMap.get(jdbcType);
if (handler != null) {
return (TypeHandler<T>) handler;
}
}
// 2. 查找默认TypeHandler
return (TypeHandler<T>) getTypeHandler((Type) type);
}
七、自定义TypeHandler
当内置TypeHandler无法满足需求时,可以自定义TypeHandler。
实现TypeHandler接口
下面是一个JSON类型处理器的完整示例:
java
@MappedTypes(List.class)
@MappedJdbcTypes(JdbcType.VARCHAR)
public class JsonListTypeHandler
extends BaseTypeHandler<List<String>> {
private static final Gson GSON = new Gson();
private static final Type LIST_TYPE =
new TypeToken<List<String>>(){}.getType();
@Override
public void setNonNullParameter(
PreparedStatement ps, int i,
List<String> parameter, JdbcType jdbcType)
throws SQLException {
// List<String> → JSON字符串
String json = GSON.toJson(parameter);
ps.setString(i, json);
}
@Override
public List<String> getNullableResult(
ResultSet rs, String columnName)
throws SQLException {
// JSON字符串 → List<String>
String json = rs.getString(columnName);
return parseJson(json);
}
@Override
public List<String> getNullableResult(
ResultSet rs, int columnIndex)
throws SQLException {
String json = rs.getString(columnIndex);
return parseJson(json);
}
@Override
public List<String> getNullableResult(
CallableStatement cs, int columnIndex)
throws SQLException {
String json = cs.getString(columnIndex);
return parseJson(json);
}
private List<String> parseJson(String json) {
if (json == null || json.trim().isEmpty()) {
return null;
}
return GSON.fromJson(json, LIST_TYPE);
}
}
枚举类型处理
MyBatis提供了两种枚举类型处理器:
scala
public class EnumTypeHandler<E extends Enum<E>>
extends BaseTypeHandler<E> {
private final Class<E> type;
@Override
public void setNonNullParameter(
PreparedStatement ps, int i,
E parameter, JdbcType jdbcType)
throws SQLException {
// Enum → name (VARCHAR)
ps.setString(i, parameter.name());
}
@Override
public E getNullableResult(
ResultSet rs, String columnName)
throws SQLException {
// name → Enum
String name = rs.getString(columnName);
return name == null ? null :
Enum.valueOf(type, name);
}
}
public class EnumOrdinalTypeHandler<E extends Enum<E>>
extends BaseTypeHandler<E> {
private final Class<E> type;
@Override
public void setNonNullParameter(
PreparedStatement ps, int i,
E parameter, JdbcType jdbcType)
throws SQLException {
// Enum → ordinal (INTEGER)
ps.setInt(i, parameter.ordinal());
}
@Override
public E getNullableResult(
ResultSet rs, String columnName)
throws SQLException {
// ordinal → Enum
int ordinal = rs.getInt(columnName);
if (ordinal == 0 && rs.wasNull()) {
return null;
}
E[] enums = type.getEnumConstants();
return enums[ordinal];
}
}
注册自定义TypeHandler的三种方式
xml
@MappedTypes(List.class)
@MappedJdbcTypes(JdbcType.VARCHAR)
public class JsonListTypeHandler
extends BaseTypeHandler<List<String>> {
// 实现代码...
}
<typeHandlers>
<!-- 指定类名 -->
<typeHandler
handler="com.example.JsonListTypeHandler"/>
<!-- 指定Java类型和JDBC类型 -->
<typeHandler
javaType="java.util.List"
jdbcType="VARCHAR"
handler="com.example.JsonListTypeHandler"/>
<!-- 包扫描 -->
<package name="com.example.typehandler"/>
</typeHandlers>
@Configuration
public class MyBatisConfig {
@Bean
public ConfigurationCustomizer
configurationCustomizer() {
return configuration -> {
TypeHandlerRegistry registry =
configuration.getTypeHandlerRegistry();
registry.register(
List.class,
JdbcType.VARCHAR,
new JsonListTypeHandler()
);
};
}
}
八、最佳实践
问题1:类型转换异常
sql
// 问题:Cannot convert value '0000' from column 1 to TIMESTAMP
// 原因:日期格式不匹配
// 解决:使用正确的日期类型或自定义TypeHandler
问题2:Null值处理
csharp
// 问题:JDBC requires that the JdbcType must be specified
// 解决1:指定jdbcType
#{name, jdbcType=VARCHAR}
// 解决2:全局配置
<setting name="jdbcTypeForNull" value="NULL"/>
问题3:枚举类型处理
ini
// 配置默认枚举处理器
<settings>
<setting name="defaultEnumTypeHandler" value="org.apache.ibatis.type.EnumTypeHandler"/>
</settings>
// 或在ResultMap中指定
<result column="status" property="status"
typeHandler="org.apache.ibatis.type.EnumOrdinalTypeHandler"/>
九、总结
MyBatis的类型处理模块是整个框架的基础组件,通过精心设计的TypeHandler体系,实现了Java类型与JDBC类型的无缝转换。