引入
在 MyBatis 中,TypeHandler
是一个 类型处理器,它的作用是处理数据库中 JDBC 类型和 Java 类型之间的转换。有时数据库的字段类型和 Java 对象的字段类型不完全一致,或者我们希望对某些字段的映射进行定制化处理,这时就需要用到 TypeHandler
。
TypeHandler
提供了一种灵活的机制,用于在 MyBatis 执行 SQL 时对参数、返回值和数据库列进行类型转换。MyBatis 提供了许多常见类型的默认处理器(如字符串、整数、日期等),但在某些情况下,我们可能需要自定义 TypeHandler
来处理特定的数据转换需求。
你可以重写已有的类型处理器或创建你自己的类型处理器来处理不支持的或非标准的类型。 具体做法为:实现 org.apache.ibatis.type.TypeHandler
接口, 或继承一个很便利的类 org.apache.ibatis.type.BaseTypeHandler
, 并且可以(可选地)将它映射到一个 JDBC 类型。
1. TypeHandler
的作用
- 传入参数时转换类型:将 Java 对象中的属性值转换为适合 SQL 语句的 JDBC 类型。
- 查询结果时转换类型:将查询结果中的字段值转换为 Java 对象的属性。
2. TypeHandler
的接口
MyBatis 的 TypeHandler
是一个接口,主要有以下方法:
public interface TypeHandler<T> {
// 设置 PreparedStatement 的参数
void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
// 从 ResultSet 获取结果
T getResult(ResultSet rs, String columnName) throws SQLException;
T getResult(ResultSet rs, int columnIndex) throws SQLException;
T getResult(CallableStatement cs, int columnIndex) throws SQLException;
}
- setParameter:将参数值从 Java 类型转换为 JDBC 类型,并设置到
PreparedStatement
中。 - getResult:将查询结果从数据库的 JDBC 类型转换为 Java 类型。
3. 默认的 TypeHandler
MyBatis 默认已经为许多常见的 Java 类型提供了类型处理器。例如:
- String 类型:
StringTypeHandler
- Integer 类型:
IntegerTypeHandler
- Date 类型:
DateTypeHandler
- Boolean 类型:
BooleanTypeHandler
当我们传递这些常见类型的对象时,MyBatis 会自动选择合适的 TypeHandler
来执行类型转换。
4. 自定义 TypeHandler
的场景
虽然 MyBatis 提供了默认的 TypeHandler
,在某些特殊场景下,我们可能需要自定义 TypeHandler
来处理复杂的类型转换。常见的应用场景包括:
- 枚举类型与数据库值之间的映射:例如数据库存储
0
和1
来表示枚举值YES
和NO
,在 Java 中则用枚举来表示。 - JSON 字符串与对象之间的转换:例如将 JSON 字符串转换为 Java 对象(如使用
Gson
或Jackson
)。 - 日期格式不一致:例如数据库存储的是字符串日期格式,而 Java 中存储的是
Date
类型,需要进行格式转换。 - 自定义的数据类型:例如数据库中存储的
VARCHAR
字段可以是某个自定义的类型,比如存储一组特定格式的字符串,需要转换为一个自定义对象。
5. 自定义 TypeHandler
实现
假设我们有一个场景,需要将数据库中的 status
字段(存储为字符串 “ACTIVE” 或 “INACTIVE”)映射为 Java 中的枚举类型 Status
。我们可以通过自定义 TypeHandler
来实现。
枚举类型示例
枚举类型 Status
:
public enum Status {
ACTIVE("ACTIVE"),
INACTIVE("INACTIVE");
private final String value;
Status(String value) {
this.value = value;
}
public String getValue() {
return value;
}
public static Status fromString(String value) {
for (Status status : Status.values()) {
if (status.getValue().equalsIgnoreCase(value)) {
return status;
}
}
return null;
}
}
自定义 TypeHandler
:
@MappedTypes(Status.class) // 指定适用于 Status 类型
public class StatusTypeHandler implements TypeHandler<Status> {
@Override
public void setParameter(PreparedStatement ps, int i, Status parameter, JdbcType jdbcType) throws SQLException {
if (parameter != null) {
ps.setString(i, parameter.getValue()); // 将枚举转换为数据库中的字符串
} else {
ps.setNull(i, Types.VARCHAR);
}
}
@Override
public Status getResult(ResultSet rs, String columnName) throws SQLException {
String value = rs.getString(columnName);
return Status.fromString(value); // 将数据库中的字符串转换为枚举
}
@Override
public Status getResult(ResultSet rs, int columnIndex) throws SQLException {
String value = rs.getString(columnIndex);
return Status.fromString(value);
}
@Override
public Status getResult(CallableStatement cs, int columnIndex) throws SQLException {
String value = cs.getString(columnIndex);
return Status.fromString(value);
}
}
setParameter
:将枚举类型的Status
转换为数据库中的字符串(例如 “ACTIVE” 或 “INACTIVE”)。getResult
:将从数据库查询结果中获取的字符串转换回Status
枚举类型。
在 MyBatis 中注册和使用 TypeHandler
- 注册
TypeHandler
的方式:- 可以通过
@MappedTypes
注解自动注册(如上例所示),在类型处理器的类上增加一个@MappedTypes
注解指定与其关联的 Java 类型列表。 如果在javaType
属性中也同时指定,则注解上的配置将被忽略。 - 也可以在 MyBatis 配置文件中通过
<typeHandlers>
配置进行注册。类型处理器的配置元素(typeHandler 元素)上增加一个javaType
属性(比如:javaType="String"
);
- 可以通过
- 可以通过两种方式来指定关联的 JDBC 类型:
- 在类型处理器的配置元素上增加一个
jdbcType
属性(比如:jdbcType="VARCHAR"
); - 在类型处理器的类上增加一个
@MappedJdbcTypes
注解指定与其关联的 JDBC 类型列表。 如果在jdbcType
属性中也同时指定,则注解上的配置将被忽略。
- 在类型处理器的配置元素上增加一个
下面给出在MyBatis 配置文件中通过 <typeHandlers>
配置的示例:
<typeHandlers>
<typeHandler handler="org.mybatis.example.ExampleTypeHandler"
javaType="..."
jdbcType="..."/>
</typeHandlers>
查询和插入示例
User
实体类:
public class User {
private Integer id;
private String username;
private Status status; // 使用自定义的枚举类型
// Getters and Setters
}
Mapper
接口:
在@Result
的typeHandler
属性中指定我们自定义的typeHandler
public interface UserMapper {
@Select("SELECT id, username, status FROM t_user WHERE id = #{id}")
@Results({
@Result(property = "status", column = "status", typeHandler = StatusTypeHandler.class)
})
User selectUserById(int id);
@Insert("INSERT INTO t_user (username, status) VALUES (#{username}, #{status, typeHandler=com.example.handler.StatusTypeHandler})")
void insertUser(User user);
}
这样,在查询时status
字段会使用 StatusTypeHandler
进行转换,插入时也会用 StatusTypeHandler
处理枚举和数据库字符串之间的转换。