MyBatis 默认提供的类型处理器无法满足你的需求时,例如:
- 需要将 Java 中的枚举类型映射到数据库中的特定值(如 ID、代码),而不是默认的名称 (
name()) 或序号 (ordinal())。 - 需要处理 JSON 字符串与 Java 对象之间的转换。
- 需要处理数据库中不支持的 Java 类型(如 Joda-Time、Java 8 Date/Time API 的某些特殊用法,虽然新版 MyBatis 对 Java 8 时间 API 支持较好)。
- 需要对数据进行加密/解密处理。
- 需要处理逗号分隔的字符串与
List<String>之间的转换。
这时,我们就可以通过实现 org.apache.ibatis.type.TypeHandler 接口或继承其抽象基类 org.apache.ibatis.type.BaseTypeHandler 来创建自定义的类型处理器。通常推荐继承 BaseTypeHandler,因为它处理了 null 值检查,简化了实现。
编写自定义 TypeHandler 的步骤:
步骤 1:创建自定义 TypeHandler 类
-
创建一个 Java 类。
-
让这个类继承
org.apache.ibatis.type.BaseTypeHandler<YourJavaType>,其中YourJavaType是你这个处理器要处理的 Java 类型。 -
实现
BaseTypeHandler中的四个抽象方法:setNonNullParameter(PreparedStatement ps, int i, YourJavaType parameter, JdbcType jdbcType):- 作用: 将 Java 类型 (
parameter) 转换为数据库能识别的类型,并设置到PreparedStatement(ps) 的指定位置 (i)。 - 参数:
ps: 当前的PreparedStatement对象。i: 参数在PreparedStatement中的索引(从 1 开始)。parameter: 从 Java 传入的非null参数值。jdbcType: 目标 JDBC 类型(可能为null,需要处理)。
- 作用: 将 Java 类型 (
getNullableResult(ResultSet rs, String columnName):- 作用: 从
ResultSet(rs) 中根据列名 (columnName) 获取数据,并将其转换为目标 Java 类型 (YourJavaType)。 - 返回值: 转换后的 Java 对象,如果数据库值为
null,应返回null。
- 作用: 从
getNullableResult(ResultSet rs, int columnIndex):- 作用: 从
ResultSet(rs) 中根据列索引 (columnIndex) 获取数据,并将其转换为目标 Java 类型 (YourJavaType)。 - 返回值: 转换后的 Java 对象,如果数据库值为
null,应返回null。
- 作用: 从
getNullableResult(CallableStatement cs, int columnIndex):- 作用: 从存储过程的
CallableStatement(cs) 中根据列索引 (columnIndex) 获取输出参数,并将其转换为目标 Java 类型 (YourJavaType)。 - 返回值: 转换后的 Java 对象,如果数据库值为
null,应返回null。
- 作用: 从存储过程的
示例:处理枚举类型映射到数据库中的整型 ID
假设有一个表示状态的枚举:
public enum Status {
ACTIVE(1, "Active"),
INACTIVE(0, "Inactive"),
PENDING(2, "Pending");
private final int id;
private final String description;
Status(int id, String description) {
this.id = id;
this.description = description;
}
public int getId() {
return id;
}
public String getDescription() {
return description;
}
public static Status getById(Integer id) {
if (id == null) {
return null;
}
for (Status status : Status.values()) {
if (status.id == id) {
return status;
}
}
throw new IllegalArgumentException("No Status found for id: " + id);
}
}
现在编写一个 TypeHandler 来处理 Status 枚举和数据库中的 INT 类型之间的映射:
package com.example.typehandler;
import com.example.model.Status; // 引入你的 Status 枚举类
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;
import org.apache.ibatis.type.MappedTypes;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
// 可选注解:声明此 Handler 处理的 Java 类型和 JDBC 类型
@MappedTypes(Status.class) // 指定处理的 Java 类型
@MappedJdbcTypes(JdbcType.INTEGER) // 指定对应的 JDBC 类型
public class StatusTypeHandler extends BaseTypeHandler<Status> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, Status parameter, JdbcType jdbcType) throws SQLException {
// 将 Status 枚举的 ID 设置到 PreparedStatement
ps.setInt(i, parameter.getId());
}
@Override
public Status getNullableResult(ResultSet rs, String columnName) throws SQLException {
// 从 ResultSet 根据列名获取 int 值
int id = rs.getInt(columnName);
// 如果 rs.wasNull() 为 true,表示数据库值为 NULL,BaseTypeHandler 会处理返回 null
return rs.wasNull() ? null : Status.getById(id);
}
@Override
public Status getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
// 从 ResultSet 根据列索引获取 int 值
int id = rs.getInt(columnIndex);
return rs.wasNull() ? null : Status.getById(id);
}
@Override
public Status getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
// 从 CallableStatement 根据列索引获取 int 值
int id = cs.getInt(columnIndex);
return cs.wasNull() ? null : Status.getById(id);
}
}
步骤 2:注册自定义 TypeHandler
有三种主要方式注册你的 TypeHandler,让 MyBatis 知道它的存在:
方式一:在 MyBatis 配置文件 (mybatis-config.xml) 中全局注册
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- ... 其他配置 ... -->
<typeHandlers>
<!-- 方式 1: 只指定 handler,MyBatis 会尝试通过泛型推断 Java 类型 -->
<typeHandler handler="com.example.typehandler.StatusTypeHandler"/>
<!-- 方式 2: 指定 handler 和 Java 类型,明确该 handler 处理哪个 Java 类型 -->
<!-- <typeHandler javaType="com.example.model.Status" handler="com.example.typehandler.StatusTypeHandler"/> -->
<!-- 方式 3: 指定 handler、Java 类型和 JDBC 类型,更精确的匹配 -->
<!-- <typeHandler javaType="com.example.model.Status" jdbcType="INTEGER" handler="com.example.typehandler.StatusTypeHandler"/> -->
<!-- 方式 4: 注册包,MyBatis 会自动扫描包下所有 TypeHandler (需要配合 @MappedTypes/@MappedJdbcTypes 注解) -->
<!-- <package name="com.example.typehandler"/> -->
</typeHandlers>
<!-- ... 其他配置 ... -->
</configuration>
- 推荐做法: 如果你的
TypeHandler使用了@MappedTypes和/或@MappedJdbcTypes注解,可以使用<package>元素进行包扫描注册,或者只指定handler让 MyBatis 通过注解信息进行注册。如果没用注解,则明确指定javaType是个好习惯。
方式二:使用注解 @MappedTypes 和 @MappedJdbcTypes (如上例所示)
在 TypeHandler 类上添加 @MappedTypes (指定处理的 Java 类型) 和 @MappedJdbcTypes (指定对应的 JDBC 类型) 注解。
- 如果使用了包扫描注册 (
<package name="..."/>或 Spring Boot 集成中的type-handlers-package属性),MyBatis 会自动发现并注册带有这些注解的TypeHandler。
方式三:在 Mapper XML 文件中显式指定
可以在具体的参数映射 (parameterMap, #{}) 或结果映射 (resultMap, <result>) 中显式指定使用哪个 TypeHandler,这会覆盖全局注册的处理器。
<mapper namespace="com.example.mapper.UserMapper">
<resultMap id="userResultMap" type="com.example.model.User">
<id property="id" column="id"/>
<result property="username" column="username"/>
<!-- 显式指定 Status 字段使用 StatusTypeHandler -->
<result property="status" column="status_id" typeHandler="com.example.typehandler.StatusTypeHandler"/>
</resultMap>
<select id="selectUserById" resultMap="userResultMap">
SELECT id, username, status_id FROM users WHERE id = #{id}
</select>
<insert id="insertUser">
INSERT INTO users (username, status_id)
VALUES (
#{username},
<!-- 显式指定参数 status 使用 StatusTypeHandler -->
#{status, typeHandler=com.example.typehandler.StatusTypeHandler, jdbcType=INTEGER}
)
</insert>
<update id="updateUserStatus">
UPDATE users SET
status_id = #{status, typeHandler=com.example.typehandler.StatusTypeHandler, jdbcType=INTEGER}
WHERE id = #{id}
</update>
</mapper>
- 注意: 在参数映射
#{}中指定typeHandler时,通常也建议同时指定jdbcType,特别是当参数可能为null时,这有助于 JDBC 驱动正确处理null值。
步骤 3:使用自定义 TypeHandler
一旦注册完成,MyBatis 在进行类型映射时:
- 如果某个 Java 属性类型与全局注册或注解指定的
javaType匹配,MyBatis 会自动使用对应的TypeHandler。 - 如果在 Mapper XML 中显式指定了
typeHandler,则优先使用指定的TypeHandler。
之后,你就可以像处理普通类型一样在你的实体类中使用 Status 枚举,MyBatis 会通过你的 StatusTypeHandler 自动完成与数据库 INT 类型之间的转换。
总结:
编写自定义 TypeHandler 的核心是实现 TypeHandler 接口(或继承 BaseTypeHandler),处理好 Java 类型与 JDBC 类型之间的双向转换逻辑。然后通过配置文件、注解或在 Mapper XML 中显式指定的方式将其注册给 MyBatis,即可实现对特殊类型的无缝处理。
7146

被折叠的 条评论
为什么被折叠?



