TypeHandler 的应用之 JSON 处理

推荐阅读:MyBatis 自定义 TypeHandler 及原理解析

背景介绍

在实际开发中,我们经常会遇到需要将对象序列化为 JSON 存储到数据库,或者从数据库读取 JSON 字符串反序列化为对象的需求。MyBatis-Plus 提供了 AbstractJsonTypeHandler 来方便自定义 TypeHandler 以支持 JSON 处理。本文将介绍如何通过继承 AbstractJsonTypeHandler 自定义 TypeHandler 来实现 JSON 数据的处理。

核心接口和抽象类

IJsonTypeHandler 接口

MyBatis-Plus 提供了IJsonTypeHandler接口,它定义了两个核心方法:

public interface IJsonTypeHandler<T> {
    T parse(String json);    // 将 JSON 字符串解析为对象
    String toJson(T obj);    // 将对象转换为 JSON 字符串
}

AbstractJsonTypeHandler 抽象类

AbstractJsonTypeHandler是一个实现了IJsonTypeHandler接口的抽象类,它继承自 MyBatis 的BaseTypeHandler。这个抽象类主要提供了以下主要功能:

  1. 处理了 null 值的情况
  2. 支持通过字段类型进行初始化
public abstract class AbstractJsonTypeHandler<T> extends BaseTypeHandler<T> implements IJsonTypeHandler<T> {

    protected final Log log = LogFactory.getLog(this.getClass());

    protected final Class<?> type;

    /**
     * @since 3.5.6
     */
    protected Type genericType;

    /**
     * 默认初始化
     *
     * @param type 类型
     */
    public AbstractJsonTypeHandler(Class<?> type) {
        this.type = type;
        if (log.isTraceEnabled()) {
            log.trace(this.getClass().getSimpleName() + "(" + type + ")");
        }
        Assert.notNull(type, "Type argument cannot be null");
    }

    /**
     * 通过字段初始化
     *
     * @param type  类型
     * @param field 字段
     * @since 3.5.6
     */
    public AbstractJsonTypeHandler(Class<?> type, Field field) {
        this(type);
        this.genericType = field.getGenericType();
    }

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

    /**
     * 帮子类处理了 null 值的情况
     */
    @Override
    public T getNullableResult(ResultSet rs, String columnName) throws SQLException {
        final String json = rs.getString(columnName);
        return StringUtils.isBlank(json) ? null : parse(json);
    }

    @Override
    public T getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        final String json = rs.getString(columnIndex);
        return StringUtils.isBlank(json) ? null : parse(json);
    }

    @Override
    public T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        final String json = cs.getString(columnIndex);
        return StringUtils.isBlank(json) ? null : parse(json);
    }

    public Type getFieldType() {
        return this.genericType != null ? this.genericType : this.type;
    }

}

实际应用示例

下面通过一个支付渠道配置的例子(来自 ruoyi-vue-pro 的支付模块)来展示如何继承 AbstractJsonTypeHandler 来处理 JSON:

1. 定义 TypeHandler

public static class PayClientConfigTypeHandler extends AbstractJsonTypeHandler<Object> {

    public PayClientConfigTypeHandler(Class<?> type) {
        super(type);
    }

    public PayClientConfigTypeHandler(Class<?> type, Field field) {
        super(type, field);
    }

    @Override
    public Object parse(String json) {
        PayClientConfig config = JsonUtils.parseObjectQuietly(json, new TypeReference<PayClientConfig>() {});
        if (config != null) {
            return config;
        }

        // 兼容老版本的包路径
        String className = JsonUtils.parseObject(json, "@class", String.class);
        className = StrUtil.subAfter(className, ".", true);
        switch (className) {
            case "AlipayPayClientConfig":
                return JsonUtils.parseObject2(json, AlipayPayClientConfig.class);
            case "WxPayClientConfig":
                return JsonUtils.parseObject2(json, WxPayClientConfig.class);
            case "NonePayClientConfig":
                return JsonUtils.parseObject2(json, NonePayClientConfig.class);
            default:
                throw new IllegalArgumentException("未知的 PayClientConfig 类型:" + json);
        }
    }

    @Override
    public String toJson(Object obj) {
        return JsonUtils.toJsonString(obj);
    }

}

可以看到子类继承 AbstractJsonTypeHandler,只需要考虑序列化及反序列化,即实现 toJson 和 parse 方法。

另:这里 AlipayPayClientConfig 等实现了 PayClientConfig 接口,PayClientConfig 接口上添加了@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS),序列化时会增加 @class 属性保存实现类全路径:

@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
// @JsonTypeInfo 注解的作用,Jackson 多态
// 1. 序列化到时数据库时,增加 @class 属性。
// 2. 反序列化到内存对象时,通过 @class 属性,可以创建出正确的类型
@JsonIgnoreProperties(ignoreUnknown = true) // 目的:忽略未知的属性,避免反序列化失败
public interface PayClientConfig {

    /**
     * 参数校验
     *
     * @param validator 校验对象
     */
    void validate(Validator validator);

}

parse 方法先从该属性获取到类名,再进行反序列化。

2. 在实体类中使用

@TableName(value = "pay_channel", autoResultMap = true)
public class PayChannelDO extends TenantBaseDO {
    @TableField(typeHandler = PayClientConfigTypeHandler.class)
    private PayClientConfig config;
}

注意需要将 @TableName autoResultMap 设置为 true(开启映射注解)。

总结

  1. 自定义 TypeHandler 继承AbstractJsonTypeHandler类,实现parsetoJson方法
  2. 在实体类中使用@TableField注解指定 TypeHandler,同时需要将 @TableName autoResultMap 设置为 true

实际上 MyBatis-Plus 官方提供了 JacksonTypeHandler 等实现,来处理 JSON 数据,没有自定义需求时直接使用即可。

参考:字段类型处理器

posted @ 2025-06-01 15:54  Higurashi-kagome  阅读(267)  评论(0)    收藏  举报