MyBatis 如何优雅地处理 JSON 与 BLOB 数据?涉及TypeHandler原理

共计 4201 个字符,预计需要花费 11 分钟才能阅读完成。

在日常的 Java 开发中,我们经常需要将复杂对象(如 Java Bean、List、Map)序列化为 JSON 字符串存入数据库,或者将文件、图像等数据序列化为二进制(BLOB)存储。这些类型的数据并不能被 MyBatis 默认识别,因此需要借助一个非常关键的机制 —— TypeHandler。本文将从原理到实战,全面剖析 TypeHandler 在处理 JSON 与 BLOB 数据中的应用与技巧。

一、什么是 TypeHandler?

定义与本质

TypeHandler 是 MyBatis 提供的一个扩展点,作用是在 Java 类型与 JDBC 类型之间做双向转换,它在以下两个时机被调用:

  • 写入数据库时(Java → JDBC):将 Java 对象转换为数据库可识别的类型;
  • 读取数据库时(JDBC → Java):将数据库字段值转换为 Java 对象。

换句话说,TypeHandler 就是 MyBatis 中的数据“编解码器”,隐藏了底层 JDBC 与 Java 类型之间的映射细节。

接口结构

MyBatis 定义了一个 org.apache.ibatis.type.TypeHandler<T> 接口,核心方法如下:

public interface TypeHandler<T> {
    void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;

    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 对象设置到 PreparedStatement 中;
  • getResult:从 ResultSetCallableStatement 中读取字段值并转为 Java 对象。

注册方式

MyBatis 提供了三种方式注册 TypeHandler:

全局扫描(推荐)

在配置文件中配置包扫描路径:

mybatis:
  type-handlers-package: com.example.handler

Mapper XML 中显式指定

<result column="config_json" property="config"
        typeHandler="com.example.handler.JsonTypeHandler"/>

注解中指定

@Result(column = "config_json", property = "config",
        typeHandler = JsonTypeHandler.class)

手动注册(适用于泛型/构造函数注册)

configuration.getTypeHandlerRegistry().register(UserConfig.class, new JsonTypeHandler<>(UserConfig.class));

二、MyBatis 默认支持的类型有哪些?

MyBatis 自带了一套常用类型的处理器:

Java 类型 JDBC 类型 默认 TypeHandler
String VARCHAR StringTypeHandler
Integer / int INTEGER IntegerTypeHandler
Long / long BIGINT LongTypeHandler
Boolean / boolean BIT BooleanTypeHandler
byte[] BLOB ByteArrayTypeHandler
Date / Timestamp DATE / TIMESTAMP DateTypeHandler, DateOnlyTypeHandler

当你使用 Java Bean、Map、List 等复杂结构时,就需要自定义 TypeHandler 来处理了。

三、场景一:处理 JSON 字段

1. 应用背景

数据库字段 config_jsonTEXT / JSON 类型,存储用户个性化设置:

{
  "darkMode": true,
  "favorites": ["java", "spring", "mybatis"]
}

Java 对应结构:

public class UserConfig {
    private boolean darkMode;
    private List<String> favorites;
    // getter/setter
}

2. 编写通用 JsonTypeHandler(支持泛型)

@MappedTypes(Object.class)
@MappedJdbcTypes(JdbcType.VARCHAR)
public class JsonTypeHandler<T> extends BaseTypeHandler<T> {

    private static final ObjectMapper objectMapper = new ObjectMapper();
    private final Class<T> clazz;

    public JsonTypeHandler(Class<T> clazz) {
        this.clazz = clazz;
    }

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType)
            throws SQLException {
        ps.setString(i, objectMapper.writeValueAsString(parameter));
    }

    @Override
    public T getNullableResult(ResultSet rs, String columnName) throws SQLException {
        String json = rs.getString(columnName);
        return json == null ? null : objectMapper.readValue(json, clazz);
    }

    @Override
    public T getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        return getNullableResult(rs, rs.getMetaData().getColumnLabel(columnIndex));
    }

    @Override
    public T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        return getNullableResult(cs.getResultSet(), columnIndex);
    }
}

3. 注册与使用方式

Spring Boot 配置注册泛型 Handler:

@Bean
public ConfigurationCustomizer configurationCustomizer() {
    return configuration ->
        configuration.getTypeHandlerRegistry()
                     .register(UserConfig.class, new JsonTypeHandler<>(UserConfig.class));
}

四、场景二:处理 BLOB 对象(二进制序列化)

1. 应用背景

希望将 Java 对象(如临时缓存、上传数据)存入数据库 BLOB 字段,自动序列化和反序列化。

2. 编写对象序列化 TypeHandler

public class ObjectToBlobTypeHandler<T extends Serializable> extends BaseTypeHandler<T> {

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType)
            throws SQLException {
        try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
             ObjectOutputStream oos = new ObjectOutputStream(bos)) {
            oos.writeObject(parameter);
            ps.setBytes(i, bos.toByteArray());
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public T getNullableResult(ResultSet rs, String columnName) throws SQLException {
        return deserialize(rs.getBytes(columnName));
    }

    private T deserialize(byte[] bytes) {
        if (bytes == null) return null;
        try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes))) {
            return (T) ois.readObject();
        } catch (IOException | ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }
}

五、总结与最佳实践

应用场景 数据库字段类型 Java 映射类型 是否需要自定义 TypeHandler 处理方式建议
JSON 配置 JSON/TEXT Java Bean / Map ✅ 是 使用通用 JsonTypeHandler
文件 / 图片 / 音频 BLOB byte[] ❌ 否(MyBatis 已支持) 使用 ByteArrayTypeHandler
对象序列化持久化 BLOB Java Bean ✅ 是 使用 ObjectToBlobTypeHandler

六、结语

TypeHandler 是 MyBatis 中非常强大的一项机制,无论是处理复杂对象的 JSON 映射,还是处理二进制文件或缓存数据的存储,合理使用 TypeHandler 都能让你的数据访问层更具扩展性、解耦性和可维护性。


我是 李卷卷,专注Java相关知识输出。感谢阅读!

正文完
 0
李卷卷
版权声明:本站原创文章,由 李卷卷 于2025-05-29发表,共计4201字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
评论(没有评论)