自定义类型处理器:MyBatis-Plus处理JSON、加密等复杂类型的完整指南

自定义类型处理器:MyBatis-Plus处理JSON、加密等复杂类型的完整指南

【免费下载链接】mybatis-plus An powerful enhanced toolkit of MyBatis for simplify development 【免费下载链接】mybatis-plus 项目地址: https://gitcode.com/gh_mirrors/my/mybatis-plus

引言:为什么需要自定义类型处理器?

在日常开发中,你是否遇到过这样的场景:

  • 数据库存储JSON字符串,但代码中需要操作对象
  • 敏感数据需要加密存储,但查询时需要自动解密
  • 枚举类型需要特殊处理,存储值而非名称
  • 复杂对象需要序列化/反序列化操作

MyBatis-Plus提供了强大的类型处理器机制,让你能够优雅地处理这些复杂数据类型。本文将深入探讨MyBatis-Plus自定义类型处理器的实现原理、使用方法和最佳实践。

类型处理器基础概念

什么是类型处理器?

类型处理器(TypeHandler)是MyBatis框架中的核心组件,负责Java类型与JDBC类型之间的转换。MyBatis-Plus在此基础上进行了增强,提供了更便捷的类型处理机制。

mermaid

MyBatis-Plus类型处理器体系

MyBatis-Plus提供了丰富的内置类型处理器:

处理器类型功能描述适用场景
AbstractJsonTypeHandlerJSON处理基类所有JSON序列化需求
JacksonTypeHandlerJackson实现高性能JSON处理
FastjsonTypeHandlerFastjson实现阿里系项目兼容
GsonTypeHandlerGson实现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);
    }
}

性能优化建议

  1. 对象复用:对于JSON处理器,复用ObjectMapper实例
  2. 缓存机制:缓存解析结果,避免重复解析
  3. 懒加载:延迟初始化重型组件
  4. 线程安全:确保类型处理器的线程安全性
// 高性能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());
    }
}

集成测试建议

  1. 数据库兼容性测试:在不同数据库上测试类型处理器
  2. 并发测试:验证多线程环境下的稳定性
  3. 性能测试:评估类型处理器的性能影响
  4. 异常测试:测试各种边界情况和异常输入

总结与展望

MyBatis-Plus的类型处理器机制为处理复杂数据类型提供了强大的支持。通过本文的详细介绍,你应该能够:

  1. 理解类型处理器的工作原理和体系结构
  2. 掌握内置JSON类型处理器的使用方法
  3. 学会开发自定义类型处理器解决特定业务需求
  4. 了解性能优化和错误处理的最佳实践

【免费下载链接】mybatis-plus An powerful enhanced toolkit of MyBatis for simplify development 【免费下载链接】mybatis-plus 项目地址: https://gitcode.com/gh_mirrors/my/mybatis-plus

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值