全部学科
NodeJS全栈
nodejs
Python全栈
python
小程序首页
📅 2026-05-20 8 分钟 ✍️ juanwangdev

JSON 类型处理

现代数据库(MySQL 5.7+、PostgreSQL、Oracle 12c+)都原生支持 JSON 类型字段。在 MyBatis 中处理 JSON 字段,需要将 Java 对象与 JSON 字符串之间进行双向转换,TypeHandler 是实现这一转换的标准方式。

为什么需要 JSON TypeHandler

数据库存储 JSON 数据:

idusernameconfig
1Alice{"theme":"dark","lang":"zh","notify":true}
2Bob{"theme":"light","lang":"en","notify":false}

Java 中对应的对象:

Java
public class UserConfig {
    private String theme;
    private String lang;
    private boolean notify;
    // getter/setter
}

没有 TypeHandler 时,需要手动序列化和反序列化:

Java
// 写入时需要手动转 JSON 字符串
UserConfig config = new UserConfig("dark", "zh", true);
String jsonStr = objectMapper.writeValueAsString(config);
// 然后设置到 SQL 参数中

// 读取时需要手动解析
String jsonStr = rs.getString("config");
UserConfig config = objectMapper.readValue(jsonStr, UserConfig.class);

有了 TypeHandler,MyBatis 自动完成转换,Java 代码中直接使用对象。

Jackson 通用 JSON TypeHandler

使用泛型实现通用的 JSON 处理器,支持任意 Java 类型:

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

    private static final ObjectMapper MAPPER = new ObjectMapper();
    private final Class<T> type;

    public JsonTypeHandler(Class<T> type) {
        if (type == null) {
            throw new IllegalArgumentException("Type argument cannot be null");
        }
        this.type = type;
    }

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i,
            T parameter, JdbcType jdbcType) throws SQLException {
        try {
            ps.setString(i, MAPPER.writeValueAsString(parameter));
        } catch (JsonProcessingException e) {
            throw new SQLException("JSON 序列化失败: " + type.getName(), e);
        }
    }

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

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

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

    private T parseJson(String json) throws SQLException {
        if (json == null || json.isEmpty()) {
            return null;
        }
        try {
            return MAPPER.readValue(json, type);
        } catch (JsonProcessingException e) {
            throw new SQLException("JSON 反序列化失败: " + json, e);
        }
    }
}

关键设计要点

设计点说明
泛型 T支持任意 Java 类型,一个 TypeHandler 通用处理
ObjectMapper 静态复用ObjectMapper 是线程安全的,避免每次创建导致性能浪费
null 值处理parseJson 方法先判断 null,避免反序列化异常
异常包装JsonProcessingException 包装为 SQLException 向上抛出

在 Mapper XML 中使用

方式一:resultMap 中指定 typeHandler

XML
<resultMap id="userResultMap" type="User">
    <id column="id" property="id"/>
    <result column="username" property="username"/>
    <result column="config" property="config"
            typeHandler="com.example.handler.JsonTypeHandler"/>
</resultMap>

<select id="selectById" resultMap="userResultMap">
    SELECT * FROM user WHERE id = #{id}
</select>

方式二:参数中使用 typeHandler

XML
<insert id="insert" parameterType="User">
    INSERT INTO user (username, config)
    VALUES (#{username}, #{config, typeHandler=com.example.handler.JsonTypeHandler})
</insert>

方式三:通过注解自动注册

为特定类型创建专用的 TypeHandler 并注册:

Java
@MappedTypes({UserConfig.class})
@MappedJdbcTypes({JdbcType.VARCHAR})
public class UserConfigTypeHandler extends JsonTypeHandler<UserConfig> {
    public UserConfigTypeHandler() {
        super(UserConfig.class);
    }
}

注册后 XML 中无需再写 typeHandler:

XML
<!-- 自动使用 UserConfigTypeHandler 处理 config 字段 -->
<insert id="insert">
    INSERT INTO user (username, config)
    VALUES (#{username}, #{config})
</insert>

Gson 版本实现

如果项目使用 Gson,实现方式类似:

Java
@MappedJdbcTypes({JdbcType.VARCHAR})
public class GsonTypeHandler<T> extends BaseTypeHandler<T> {

    private static final Gson GSON = new Gson();
    private final Class<T> type;

    public GsonTypeHandler(Class<T> type) {
        this.type = type;
    }

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

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

    @Override
    public T getNullableResult(ResultSet rs, int columnIndex)
            throws SQLException {
        String json = rs.getString(columnIndex);
        return json == null ? null : GSON.fromJson(json, type);
    }

    @Override
    public T getNullableResult(CallableStatement cs, int columnIndex)
            throws SQLException {
        String json = cs.getString(columnIndex);
        return json == null ? null : GSON.fromJson(json, type);
    }
}

PostgreSQL JSONB 类型处理器

PostgreSQL 的 JSON/JSONB 类型需要使用 setObject 方法配合 PGobject

Java
@MappedTypes({UserConfig.class})
@MappedJdbcTypes({JdbcType.OTHER})
public class PostgresJsonTypeHandler extends BaseTypeHandler<UserConfig> {

    private static final ObjectMapper MAPPER = new ObjectMapper();

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i,
            UserConfig config, JdbcType jdbcType) throws SQLException {
        try {
            PGobject pgObject = new PGobject();
            pgObject.setType("jsonb");
            pgObject.setValue(MAPPER.writeValueAsString(config));
            ps.setObject(i, pgObject);
        } catch (JsonProcessingException e) {
            throw new SQLException("JSON 序列化失败", e);
        } catch (SQLException e) {
            throw e;
        }
    }

    @Override
    public UserConfig getNullableResult(ResultSet rs,
            String columnName) throws SQLException {
        return parseJson(rs.getString(columnName));
    }

    @Override
    public UserConfig getNullableResult(ResultSet rs,
            int columnIndex) throws SQLException {
        return parseJson(rs.getString(columnIndex));
    }

    @Override
    public UserConfig getNullableResult(CallableStatement cs,
            int columnIndex) throws SQLException {
        return parseJson(cs.getString(columnIndex));
    }

    private UserConfig parseJson(String json) throws SQLException {
        if (json == null) return null;
        try {
            return MAPPER.readValue(json, UserConfig.class);
        } catch (JsonProcessingException e) {
            throw new SQLException("JSON 反序列化失败", e);
        }
    }
}

PostgreSQL 与普通 JSON TypeHandler 的区别:

特性通用 JSON TypeHandlerPostgreSQL JSONB TypeHandler
写入方法ps.setString()ps.setObject() + PGobject
JDBC 类型JdbcType.VARCHARJdbcType.OTHER
PostgreSQL 类型TEXT/VARCHARJSON/JSONB
类型声明不需要需要指定 pgObject.setType("jsonb")

泛型注册与 Spring 集成

通用 JsonTypeHandler<T> 无法直接通过注解指定具体类型,需要结合 Spring 配置注册:

Java
// MyBatis 配置类
@Configuration
public class MyBatisConfig {

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

当需要处理多种 JSON 类型时,推荐方案:为每种具体类型创建专用的 TypeHandler 子类并添加 @MappedTypes 注解,这样 MyBatis 可以自动注册,无需额外配置。

要点总结

  • JSON TypeHandler 的核心是序列化(写)和反序列化(读)两个方向的双向转换
  • 使用泛型实现通用的 JSON 处理器,配合 ObjectMapper 静态复用避免性能浪费
  • null 值处理是必须,避免对 null 字符串调用反序列化方法导致异常
  • Jackson 和 Gson 的实现方式类似,选择项目已有的序列化库即可
  • PostgreSQL JSONB 类型需要使用 PGobject 配合 setObject 方法写入
  • JSON TypeHandler 注册支持 XML 配置、注解 @MappedTypes、Spring 配置三种方式
  • 推荐为每种具体类型创建专用 TypeHandler 子类并添加注解,实现自动注册

📝 发现内容有误?点击此处直接编辑

← 上一篇 插件拦截原理
下一篇 → TypeHandler 接口
想查看更多题目和详细解析?
小程序提供完整的题库、模拟考试和详细解析
马上就来

长按或扫描二维码,立即体验

扫码体验小程序
马上就来
使用微信扫描二维码
立即体验完整题库