自定义类型处理器:MyBatis-Plus处理JSON、加密等复杂类型的完整指南
引言:为什么需要自定义类型处理器?
在日常开发中,你是否遇到过这样的场景:
- 数据库存储JSON字符串,但代码中需要操作对象
- 敏感数据需要加密存储,但查询时需要自动解密
- 枚举类型需要特殊处理,存储值而非名称
- 复杂对象需要序列化/反序列化操作
MyBatis-Plus提供了强大的类型处理器机制,让你能够优雅地处理这些复杂数据类型。本文将深入探讨MyBatis-Plus自定义类型处理器的实现原理、使用方法和最佳实践。
类型处理器基础概念
什么是类型处理器?
类型处理器(TypeHandler)是MyBatis框架中的核心组件,负责Java类型与JDBC类型之间的转换。MyBatis-Plus在此基础上进行了增强,提供了更便捷的类型处理机制。
MyBatis-Plus类型处理器体系
MyBatis-Plus提供了丰富的内置类型处理器:
| 处理器类型 | 功能描述 | 适用场景 |
|---|---|---|
| AbstractJsonTypeHandler | JSON处理基类 | 所有JSON序列化需求 |
| JacksonTypeHandler | Jackson实现 | 高性能JSON处理 |
| FastjsonTypeHandler | Fastjson实现 | 阿里系项目兼容 |
| GsonTypeHandler | Gson实现 | Google系项目兼容 |
| MybatisEnumTypeHandler | 枚举处理 | 枚举值存储优化 |
内置JSON类型处理器详解
AbstractJsonTypeHandler:基类设计
AbstractJsonTypeHandler是所有JSON类型处理器的基类,实现了IJsonTypeHandler接口:
public abstract class AbstractJsonTypeHandler<T> extends BaseTypeHandler<T>
implements IJsonTypeHandler<T> {
protected final Class<?> type;
protected Type genericType;
// 核心方法实现
@Override
public void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) {
ps.setString(i, toJson(parameter));
}
@Override
public T getNullableResult(ResultSet rs, String columnName) {
final String json = rs.getString(columnName);
return StringUtils.isBlank(json) ? null : parse(json);
}
}
JacksonTypeHandler:推荐选择
Jackson是目前最流行的JSON处理库,JacksonTypeHandler提供了完整的实现:
@MappedTypes({Object.class})
@MappedJdbcTypes(JdbcType.VARCHAR)
public class JacksonTypeHandler extends AbstractJsonTypeHandler<Object> {
private static ObjectMapper OBJECT_MAPPER;
@Override
public Object parse(String json) {
ObjectMapper objectMapper = getObjectMapper();
JavaType javaType = typeFactory.constructType(getFieldType());
return objectMapper.readValue(json, javaType);
}
@Override
public String toJson(Object obj) {
return getObjectMapper().writeValueAsString(obj);
}
}
实战:自定义类型处理器开发
场景一:JSON对象处理
假设我们需要处理用户配置信息,数据库存储为JSON字符串:
// 用户配置实体
@Data
public class UserConfig {
private Boolean notifications;
private String theme;
private List<String> permissions;
private Map<String, Object> preferences;
}
// 自定义JSON类型处理器
public class UserConfigTypeHandler extends AbstractJsonTypeHandler<UserConfig> {
private static final ObjectMapper objectMapper = new ObjectMapper();
public UserConfigTypeHandler(Class<?> type) {
super(type);
}
@Override
public UserConfig parse(String json) {
try {
return objectMapper.readValue(json, UserConfig.class);
} catch (Exception e) {
throw new RuntimeException("JSON解析失败", e);
}
}
@Override
public String toJson(UserConfig config) {
try {
return objectMapper.writeValueAsString(config);
} catch (Exception e) {
throw new RuntimeException("JSON序列化失败", e);
}
}
}
场景二:数据加密处理
敏感数据如手机号、身份证号需要加密存储:
// 加密类型处理器
public class EncryptTypeHandler extends BaseTypeHandler<String> {
private final EncryptionService encryptionService;
public EncryptTypeHandler() {
this.encryptionService = new AesEncryptionService();
}
@Override
public void setNonNullParameter(PreparedStatement ps, int i,
String parameter, JdbcType jdbcType) {
String encrypted = encryptionService.encrypt(parameter);
ps.setString(i, encrypted);
}
@Override
public String getNullableResult(ResultSet rs, String columnName) {
String encrypted = rs.getString(columnName);
return encrypted != null ? encryptionService.decrypt(encrypted) : null;
}
@Override
public String getNullableResult(ResultSet rs, int columnIndex) {
String encrypted = rs.getString(columnIndex);
return encrypted != null ? encryptionService.decrypt(encrypted) : null;
}
@Override
public String getNullableResult(CallableStatement cs, int columnIndex) {
String encrypted = cs.getString(columnIndex);
return encrypted != null ? encryptionService.decrypt(encrypted) : null;
}
}
场景三:枚举值处理
优化枚举类型的存储和读取:
// 状态枚举
public enum UserStatus {
ACTIVE(1, "活跃"),
INACTIVE(0, "非活跃"),
LOCKED(-1, "锁定");
private final int code;
private final String desc;
UserStatus(int code, String desc) {
this.code = code;
this.desc = desc;
}
public int getCode() { return code; }
public String getDesc() { return desc; }
public static UserStatus fromCode(int code) {
for (UserStatus status : values()) {
if (status.code == code) {
return status;
}
}
throw new IllegalArgumentException("无效状态码: " + code);
}
}
// 枚举类型处理器
public class UserStatusTypeHandler extends BaseTypeHandler<UserStatus> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i,
UserStatus parameter, JdbcType jdbcType) {
ps.setInt(i, parameter.getCode());
}
@Override
public UserStatus getNullableResult(ResultSet rs, String columnName) {
int code = rs.getInt(columnName);
return rs.wasNull() ? null : UserStatus.fromCode(code);
}
@Override
public UserStatus getNullableResult(ResultSet rs, int columnIndex) {
int code = rs.getInt(columnIndex);
return rs.wasNull() ? null : UserStatus.fromCode(code);
}
@Override
public UserStatus getNullableResult(CallableStatement cs, int columnIndex) {
int code = cs.getInt(columnIndex);
return cs.wasNull() ? null : UserStatus.fromCode(code);
}
}
配置和使用方式
注解配置方式
在实体类字段上使用@TableField注解指定类型处理器:
@Data
@TableName("user")
public class User {
@TableId
private Long id;
private String name;
// JSON配置字段
@TableField(typeHandler = UserConfigTypeHandler.class)
private UserConfig config;
// 加密手机号字段
@TableField(typeHandler = EncryptTypeHandler.class)
private String phone;
// 枚举状态字段
@TableField(typeHandler = UserStatusTypeHandler.class)
private UserStatus status;
}
XML配置方式
在MyBatis配置文件中全局注册类型处理器:
<typeHandlers>
<!-- 注册自定义类型处理器 -->
<typeHandler handler="com.example.handler.UserConfigTypeHandler"
javaType="com.example.entity.UserConfig"/>
<typeHandler handler="com.example.handler.EncryptTypeHandler"
javaType="java.lang.String"/>
<typeHandler handler="com.example.handler.UserStatusTypeHandler"
javaType="com.example.enums.UserStatus"/>
<!-- 注册内置JSON处理器 -->
<typeHandler handler="com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler"
javaType="java.lang.Object"/>
</typeHandlers>
ResultMap配置
对于复杂类型,建议使用ResultMap进行精确控制:
<resultMap id="userResultMap" type="User">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="config" column="config"
javaType="com.example.entity.UserConfig"
typeHandler="com.example.handler.UserConfigTypeHandler"/>
<result property="phone" column="phone"
typeHandler="com.example.handler.EncryptTypeHandler"/>
<result property="status" column="status"
javaType="com.example.enums.UserStatus"
typeHandler="com.example.handler.UserStatusTypeHandler"/>
</resultMap>
高级特性与最佳实践
泛型支持
MyBatis-Plus的类型处理器支持泛型类型识别:
// 泛型列表处理器
public class GenericListTypeHandler<T> extends AbstractJsonTypeHandler<List<T>> {
private final TypeReference<List<T>> typeReference;
public GenericListTypeHandler(Class<?> type, Field field) {
super(type, field);
this.typeReference = new TypeReference<List<T>>() {
@Override
public Type getType() {
return field.getGenericType();
}
};
}
@Override
public List<T> parse(String json) {
return objectMapper.readValue(json, typeReference);
}
}
性能优化建议
- 对象复用:对于JSON处理器,复用ObjectMapper实例
- 缓存机制:缓存解析结果,避免重复解析
- 懒加载:延迟初始化重型组件
- 线程安全:确保类型处理器的线程安全性
// 高性能JSON处理器
public class HighPerformanceJsonTypeHandler extends AbstractJsonTypeHandler<Object> {
private static final ThreadLocal<ObjectMapper> OBJECT_MAPPER_CACHE =
ThreadLocal.withInitial(() -> {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return mapper;
});
private static final ConcurrentHashMap<Type, JavaType> TYPE_CACHE =
new ConcurrentHashMap<>();
@Override
public Object parse(String json) {
ObjectMapper mapper = OBJECT_MAPPER_CACHE.get();
JavaType javaType = TYPE_CACHE.computeIfAbsent(getFieldType(),
type -> mapper.getTypeFactory().constructType(type));
return mapper.readValue(json, javaType);
}
}
错误处理与日志
完善的错误处理机制:
public class SafeJsonTypeHandler extends AbstractJsonTypeHandler<Object> {
@Override
public Object parse(String json) {
if (StringUtils.isBlank(json)) {
return null;
}
try {
return objectMapper.readValue(json, getFieldType());
} catch (Exception e) {
log.warn("JSON解析失败,原始数据: {}", json, e);
// 返回默认值或抛出业务异常
return createDefaultInstance();
}
}
private Object createDefaultInstance() {
try {
return getFieldType().newInstance();
} catch (Exception e) {
throw new BusinessException("创建默认实例失败", e);
}
}
}
常见问题与解决方案
问题1:类型处理器不生效
解决方案:
- 检查类型处理器是否正确注册
- 确认字段注解配置正确
- 验证MyBatis配置加载顺序
问题2:JSON解析性能问题
优化方案:
- 使用Jackson的Smile或CBOR二进制格式
- 启用缓存机制
- 避免在循环中创建ObjectMapper实例
问题3:加密数据查询困难
解决方案:
- 实现模糊查询加密方案
- 使用数据库加密函数
- 考虑应用层加密+数据库层加密结合
测试策略
单元测试示例
@ExtendWith(MockitoExtension.class)
class UserConfigTypeHandlerTest extends BaseTypeHandlerTest {
private static final UserConfigTypeHandler HANDLER =
new UserConfigTypeHandler(UserConfig.class);
@Test
void setParameter() throws Exception {
UserConfig config = new UserConfig();
config.setNotifications(true);
config.setTheme("dark");
HANDLER.setParameter(preparedStatement, 1, config, JdbcType.VARCHAR);
verify(preparedStatement).setString(1, "{\"notifications\":true,\"theme\":\"dark\"}");
}
@Test
void getResultFromResultSet() throws Exception {
when(resultSet.getString("config")).thenReturn("{\"notifications\":true,\"theme\":\"dark\"}");
UserConfig config = (UserConfig) HANDLER.getResult(resultSet, "config");
assertNotNull(config);
assertTrue(config.getNotifications());
assertEquals("dark", config.getTheme());
}
}
集成测试建议
- 数据库兼容性测试:在不同数据库上测试类型处理器
- 并发测试:验证多线程环境下的稳定性
- 性能测试:评估类型处理器的性能影响
- 异常测试:测试各种边界情况和异常输入
总结与展望
MyBatis-Plus的类型处理器机制为处理复杂数据类型提供了强大的支持。通过本文的详细介绍,你应该能够:
- 理解类型处理器的工作原理和体系结构
- 掌握内置JSON类型处理器的使用方法
- 学会开发自定义类型处理器解决特定业务需求
- 了解性能优化和错误处理的最佳实践
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



