1.1 什么是类型处理器及其在MyBatis中的作用
类型处理器是MyBatis框架中一个看似简单却至关重要的组件。它默默承担着Java类型与数据库类型之间双向转换的桥梁角色。想象一下,当你在Java代码中使用枚举类型,而数据库只认识简单的字符串或数字时,类型处理器就在背后完成了这种"翻译"工作。
我记得第一次使用MyBatis时,遇到一个需求要将用户的VIP等级枚举存储到数据库。当时直接存储枚举的ordinal值,结果后来枚举顺序调整导致数据全乱了。正是这个教训让我意识到类型处理器的重要性。
类型处理器的核心价值在于它让开发者能够专注于业务逻辑,而不必担心数据在Java世界和数据库世界之间的格式差异。它处理着从PreparedStatement设置参数到从ResultSet获取结果的全过程转换。
1.2 内置类型处理器的分类与使用场景
MyBatis贴心地为我们准备了一整套内置类型处理器,覆盖了日常开发中的绝大多数场景。这些处理器大致可以分为几个主要类别:
基本类型处理器处理int、long、boolean等基础类型,它们通常直接将Java类型映射到对应的数据库类型。字符串处理器可能是最常用的,它们处理着VARCHAR、CHAR等文本类型的转换。
日期时间处理器家族比较庞大,从传统的Date到Java 8新的时间API都有对应支持。我特别喜欢LocalDateTime处理器,它让时间处理变得如此自然。
还有专门处理Blob、Clob等大对象类型的处理器,它们在处理文件、图片等二进制数据时表现出色。
实际开发中,大部分基础数据类型转换都不需要我们操心。MyBatis已经帮我们做好了默认映射。但遇到特殊需求时,了解这些内置处理器的存在和适用场景就显得尤为重要。
1.3 为何需要自定义类型处理器
尽管内置类型处理器已经很强大,但现实业务场景往往更加复杂。当遇到以下情况时,自定义类型处理器就变得必要:
数据库存储的格式与Java对象结构不匹配是最常见的场景。比如数据库用逗号分隔的字符串存储标签,而Java端希望用List来操作。或者数据库用Y/N表示布尔值,而Java使用true/false。
枚举类型的处理也是个典型例子。直接存储枚举的序号风险很大,存储枚举名称相对安全,但有时业务要求存储特定的代码值。这时候自定义枚举处理器就能完美解决这个问题。
JSON数据的存储越来越普遍。数据库可能只提供文本字段,而Java端希望直接操作对象。自定义JSON处理器能让这种转换变得透明。
我曾经参与过一个项目,需要将地理坐标对象存储到数据库。数据库只有简单的经度和纬度字段,而Java端使用专业的GeoPoint对象。通过自定义类型处理器,我们实现了两者之间的无缝转换,大大简化了业务代码。
自定义类型处理器的魅力在于,它让复杂的类型转换逻辑被封装在专门的组件中,使主要的数据访问代码保持简洁和清晰。 public enum OrderStatus {
PENDING("01"),
PROCESSING("02"),
COMPLETED("03"),
CANCELLED("04");
private final String code;
OrderStatus(String code) {
this.code = code;
}
public String getCode() {
return code;
}
public static OrderStatus fromCode(String code) {
return Arrays.stream(values())
.filter(status -> status.code.equals(code))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("未知状态编码: " + code));
}
}
<typeHandler handler="com.example.JsonTypeHandler"
javaType="com.example.UserConfig"/>
public class AddressTypeHandler implements TypeHandler
{@Override
public void setParameter(PreparedStatement ps, int i, Address parameter, JdbcType jdbcType) {
// 将Address对象序列化为JSON字符串
String json = JSON.toJSONString(parameter);
ps.setString(i, json);
}
@Override
public Address getResult(ResultSet rs, String columnName) {
// 从JSON字符串反序列化为Address对象
String json = rs.getString(columnName);
return JSON.parseObject(json, Address.class);
}
}
@Override public UserConfig getResult(ResultSet rs, String columnName) throws SQLException {
String rawValue = rs.getString(columnName);
log.debug("开始转换字段{},原始值:{}", columnName, rawValue);
if (rawValue == null) {
log.debug("字段值为空,返回默认配置");
return new UserConfig();
}
try {
UserConfig result = mapper.readValue(rawValue, UserConfig.class);
log.debug("转换完成,结果:{}", result);
return result;
} catch (Exception e) {
log.error("JSON转换失败,原始值:{}", rawValue, e);
throw new SQLException("类型转换异常", e);
}
}