MyBatis 3自定义类型处理器完全指南:解决90%的特殊数据转换问题
MyBatis 3自定义类型处理器完全指南:解决90%的特殊数据转换问题
你是否还在为MyBatis中日期、JSON、枚举等特殊数据类型的转换而头疼?是否遇到过数据库字段与Java对象属性类型不匹配导致的异常?本文将带你全面掌握自定义类型处理器(TypeHandler)的实现方法,通过简单三步即可解决90%的特殊数据转换场景,让你的数据交互更流畅。
读完本文你将学会:
- 理解TypeHandler的工作原理及核心接口
- 掌握自定义类型处理器的完整实现步骤
- 学会在XML和注解中配置并使用自定义处理器
- 解决LocalDateTime、JSON等复杂类型的映射问题
- 了解高级特性如类型别名和null值处理技巧
什么是TypeHandler
TypeHandler(类型处理器)是MyBatis中负责Java类型与JDBC类型之间转换的组件。当MyBatis执行SQL语句时,会使用TypeHandler将Java对象参数转换为JDBC兼容的类型;当查询结果返回时,又会使用TypeHandler将JDBC类型转换为Java对象。
MyBatis已经内置了大量常用类型的处理器,如IntegerTypeHandler、StringTypeHandler等,这些处理器都继承自BaseTypeHandler抽象类。你可以在src/main/java/org/apache/ibatis/type/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值处理逻辑
} else {
setNonNullParameter(ps, i, parameter, jdbcType);
}
}
// 抽象方法需要子类实现
public abstract void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
public abstract T getNullableResult(ResultSet rs, String columnName) throws SQLException;
// 其他抽象方法...
}
类型处理器工作流程
以下是TypeHandler在MyBatis中的工作流程:
自定义TypeHandler的步骤
实现自定义TypeHandler只需三步:
1. 继承BaseTypeHandler
创建一个类继承BaseTypeHandler<T>,其中T是你要处理的Java类型。需要实现以下四个抽象方法:
setNonNullParameter(): 设置非空参数时调用getNullableResult(ResultSet, String): 通过列名从结果集获取值getNullableResult(ResultSet, int): 通过列索引从结果集获取值getNullableResult(CallableStatement, int): 从存储过程结果获取值
2. 实现转换逻辑
以处理Java 8中的LocalDateTime类型为例,MyBatis已提供src/main/java/org/apache/ibatis/type/LocalDateTimeTypeHandler.java实现:
public class LocalDateTimeTypeHandler extends BaseTypeHandler<LocalDateTime> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, LocalDateTime parameter, JdbcType jdbcType)
throws SQLException {
ps.setObject(i, parameter); // 将LocalDateTime转换为JDBC支持的类型
}
@Override
public LocalDateTime getNullableResult(ResultSet rs, String columnName) throws SQLException {
return rs.getObject(columnName, LocalDateTime.class); // 将JDBC类型转换为LocalDateTime
}
@Override
public LocalDateTime getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
return rs.getObject(columnIndex, LocalDateTime.class);
}
@Override
public LocalDateTime getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
return cs.getObject(columnIndex, LocalDateTime.class);
}
}
3. 配置TypeHandler
有两种方式可以配置自定义TypeHandler:
XML配置方式
在MyBatis配置文件中添加typeHandlers配置:
<typeHandlers>
<!-- 配置自定义类型处理器 -->
<typeHandler handler="com.example.typehandler.JsonTypeHandler"/>
<!-- 包扫描方式配置 -->
<package name="com.example.typehandler"/>
</typeHandlers>
注解配置方式
使用@MappedTypes和@MappedJdbcTypes注解指定处理器支持的Java类型和JDBC类型:
@MappedTypes({LocalDateTime.class})
@MappedJdbcTypes({JdbcType.TIMESTAMP})
public class LocalDateTimeTypeHandler extends BaseTypeHandler<LocalDateTime> {
// 实现代码...
}
实战案例:处理JSON类型
假设我们需要将Java对象与数据库JSON类型字段进行映射,以下是实现步骤:
1. 创建JSON类型处理器
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class JsonTypeHandler<T> extends BaseTypeHandler<T> {
private static final ObjectMapper objectMapper = new ObjectMapper();
private final Class<T> type;
public JsonTypeHandler(Class<T> type) {
this.type = type;
}
@Override
public void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
try {
ps.setString(i, objectMapper.writeValueAsString(parameter));
} catch (JsonProcessingException e) {
throw new SQLException("Error converting object to JSON", e);
}
}
@Override
public T getNullableResult(ResultSet rs, String columnName) throws SQLException {
String json = rs.getString(columnName);
return json == null ? null : parseJson(json);
}
@Override
public T getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
String json = rs.getString(columnIndex);
return json == null ? null : parseJson(json);
}
@Override
public T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
String json = cs.getString(columnIndex);
return json == null ? null : parseJson(json);
}
private T parseJson(String json) {
try {
return objectMapper.readValue(json, type);
} catch (JsonProcessingException e) {
throw new RuntimeException("Error parsing JSON", e);
}
}
}
2. 配置JSON类型处理器
在MyBatis配置文件中注册:
<typeHandlers>
<typeHandler handler="com.example.typehandler.JsonTypeHandler" javaType="com.example.model.UserInfo"/>
</typeHandlers>
或者使用注解方式:
@MappedTypes({UserInfo.class})
@MappedJdbcTypes({JdbcType.VARCHAR})
public class JsonTypeHandler<T> extends BaseTypeHandler<T> {
// 实现代码...
}
3. 在映射文件中使用
<resultMap id="userResultMap" type="User">
<id property="id" column="id"/>
<result property="name" column="name"/>
<!-- 使用typeHandler属性指定处理器 -->
<result property="info" column="info" typeHandler="com.example.typehandler.JsonTypeHandler"/>
</resultMap>
<select id="getUser" resultMap="userResultMap">
SELECT id, name, info FROM users WHERE id = #{id}
</select>
<insert id="insertUser">
INSERT INTO users (id, name, info)
VALUES (#{id}, #{name}, #{info,typeHandler=com.example.typehandler.JsonTypeHandler})
</insert>
高级特性
类型别名
为了简化配置,你可以为TypeHandler创建类型别名。MyBatis的TypeAliasRegistry类(src/main/java/org/apache/ibatis/type/TypeAliasRegistry.java)负责管理类型别名。
在配置文件中注册别名:
<typeAliases>
<typeAlias type="com.example.typehandler.JsonTypeHandler" alias="json"/>
</typeAliases>
<!-- 使用别名 -->
<result property="info" column="info" typeHandler="json"/>
MyBatis已为常用类型预定义了别名,如:
public TypeAliasRegistry() {
registerAlias("string", String.class);
registerAlias("int", Integer.class);
registerAlias("long", Long.class);
registerAlias("date", Date.class);
// 更多预定义别名...
}
null值处理
在处理null值时,需要注意BaseTypeHandler的setParameter方法已经处理了null值判断:
@Override
public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
if (parameter == 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 {
setNonNullParameter(ps, i, parameter, jdbcType);
}
}
当处理可能为null的参数时,建议在SQL中显式指定JdbcType:
INSERT INTO users (id, name, email)
VALUES (#{id}, #{name}, #{email,jdbcType=VARCHAR})
处理枚举类型
MyBatis提供了两个枚举处理器:
EnumTypeHandler: 使用枚举名称映射(默认)EnumOrdinalTypeHandler: 使用枚举索引映射
你可以在配置文件中全局配置默认枚举处理器:
<settings>
<setting name="defaultEnumTypeHandler" value="org.apache.ibatis.type.EnumOrdinalTypeHandler"/>
</settings>
或者在字段级别指定:
<result property="status" column="status" typeHandler="org.apache.ibatis.type.EnumOrdinalTypeHandler"/>
最佳实践
- 保持单一职责:每个TypeHandler只处理一种类型转换
- 处理null值:确保你的处理器能正确处理null值情况
- 线程安全:TypeHandler是单例的,确保其无状态或线程安全
- 异常处理:适当捕获并转换异常为MyBatis的TypeException
- 测试覆盖:为TypeHandler编写单元测试,覆盖各种边界情况
总结
自定义TypeHandler是解决MyBatis中特殊类型映射的强大工具。通过继承BaseTypeHandler并实现四个核心方法,你可以轻松处理任何复杂的数据类型转换需求。无论是Java 8日期时间类型、JSON对象,还是自定义业务类型,TypeHandler都能提供优雅的解决方案。
官方文档中关于类型处理器的更多配置细节,请参考src/site/markdown/configuration.md。
掌握TypeHandler将极大提升你使用MyBatis处理复杂数据类型的能力,让数据访问层代码更加简洁和可维护。
更多推荐


所有评论(0)