MyBatis 扩展BaseTypeHandler 转换泛型 JSON 列表

最近发现一个mybatis里面json转换的bug, 写了这么多年Java这方面还是没有理清楚, 把正确的处理方法记录一下.

一. 对象JSON转换

这个是比较简单的情况, 有通用的处理方法, 例如

用Jackson实现一个通用的 TypeHandler

@Slf4j
public class JacksonTypeHandler<T> extends BaseTypeHandler<T> {
    private static ObjectMapper OBJECT_MAPPER;
    private final Class<T> clazz;

    public JacksonTypeHandler(Class<T> clazz) {
        if (log.isTraceEnabled()) {
            log.trace("JacksonTypeHandler[{}]", clazz);
        }
        this.clazz = clazz;
    }

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

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

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

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

    protected T parse(String json) {
        if (json == null || json.isEmpty()) return null;
        try {
            return getObjectMapper().readValue(json, clazz);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    protected String toJson(T obj) {
        try {
            return getObjectMapper().writeValueAsString(obj);
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
    }

    private static ObjectMapper getObjectMapper() {
        if (null == OBJECT_MAPPER) {
            OBJECT_MAPPER = new ObjectMapper();
            OBJECT_MAPPER.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
        }
        return OBJECT_MAPPER;
    }
}

使用时直接指定 typeHandler 就行

<result property="groupFilter" column="group_filter" typeHandler="com.somewhere.mybatis.JacksonTypeHandler"/>

以及

#{groupFilter, typeHandler=com.somewhere.mybatis.JacksonTypeHandler},

二. 列表JSON Array转换

字段中更常见的是 List 类型的JSON Array结构, 这种情况通过扩展 BaseTypeHandler 没有通用的处理方法, 有两种实现途径

通用的TypeHandler, 需要指定javaType

用 Jackson 实现一个通用的 TypeHandler, 注意构造函数中的 clazz, 如果不指定, 在默认情况下 mybatis 传进来的是一个不带泛型参数的 List

@Slf4j
public class JacksonListTypeHandler<T> extends BaseTypeHandler<List<T>> {
    private static ObjectMapper OBJECT_MAPPER;
    private final TypeReference<List<T>> type;

    public JacksonListTypeHandler(Class<T> clazz) {
        log.info("JacksonListTypeHandler[{}]", clazz);
        this.type = new TypeReference<>() {
            @Override
            public Type getType() {
                // 返回参数化类型 List<T>
                return new ParameterizedType() {
                    @Override
                    public Type[] getActualTypeArguments() {
                        return new Type[]{clazz};
                    }

                    @Override
                    public Type getRawType() {
                        return List.class;
                    }

                    @Override
                    public Type getOwnerType() {
                        return null;
                    }
                };
            }
        };
    }

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

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

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

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

    private List<T> parse(String json) {
        if (json == null || json.isEmpty()) return null;
        try {
            return getObjectMapper().readValue(json, type);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    protected String toJson(List<T> obj) {
        try {
            return getObjectMapper().writeValueAsString(obj);
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
    }

    public static ObjectMapper getObjectMapper() {
        if (null == OBJECT_MAPPER) {
            OBJECT_MAPPER = new ObjectMapper();
            OBJECT_MAPPER.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
        }
        return OBJECT_MAPPER;
    }
}

除了用 TypeReference, 还可以用 JavaType

@Slf4j
public class ListJacksonTypeHandler<T> extends BaseTypeHandler<List<T>> {
    private final JavaType type;

    public ListJacksonTypeHandler(Class<T> clazz) {
        log.info("ListJacksonTypeHandler[{}]", clazz);
        // 创建 List<T> 类型的 JavaType
        this.type = JacksonUtil.getObjectMapper()
                .getTypeFactory()
                .constructCollectionType(List.class, clazz);
    }

    // 其它一样
}

需要在 mapper 中强制指定javaType, 如果是MyBatis自带的简单类型, 可以直接用 alias(见下面的表格), 如果是自定义的对象, 则需要用对象类的完整包路径

<result property="ips" column="ips" javaType="string" typeHandler="com.somewhere.mybatis.JacksonListTypeHandler"/>

以及

#{users,javaType=string,typeHandler=com.somewhere.mybatis.JacksonListTypeHandler},

这样启动后, 如果初始化的type是正确的string 才能正确解析

2025-12-15T10:33:42.896+08:00  INFO 1 --- [some-service] [main] c.somewhere.mybatis.JacksonListTypeHandler: JacksonListTypeHandler[class java.lang.String]

抽象类, 根据对象类型实现具体的 TypeHandler

如果不在 mapper 中指定类型, 就需要在 TypeHandler 中指定, 这样就不是通用的了

写一个基类 AbstractListTypeHandler

@Slf4j
public abstract class AbstractListTypeHandler<T> extends BaseTypeHandler<List<T>> {
    private static ObjectMapper OBJECT_MAPPER;

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

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

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

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

    protected List<T> parse(String json) {
        if (json == null || json.isEmpty()) return null;
        try {
            return getObjectMapper().readValue(json, specificType());
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    protected String toJson(List<T> obj) {
        try {
            return getObjectMapper().writeValueAsString(obj);
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
    }

    private static ObjectMapper getObjectMapper() {
        if (null == OBJECT_MAPPER) {
            OBJECT_MAPPER = new ObjectMapper();
            OBJECT_MAPPER.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
        }
        return OBJECT_MAPPER;
    }

    protected abstract TypeReference<List<T>> specificType();
}

根据具体的使用场景, 创建对应的实现类

public class ListLongTypeHandler extends AbstractListTypeHandler<Long> {
    @Override
    protected TypeReference<List<Long>> specificType() {
        return new TypeReference<>() {};
    }
}

使用时, 直接用 typeHandler 指定, 不需要指定类型

#{setIds, typeHandler=com.somewhere.mybatis.ListLongTypeHandler},
...
@Result(column="set_ids",                       property="setIds", typeHandler= ListLongTypeHandler.class),

附: MyBatis 内建的 javaType Alias

链接: https://mybatis.org/mybatis-3/configuration.html#typeAliases

aliasjavaType
_bytebyte
_char (since 3.5.10)char
_character (since 3.5.10)char
_longlong
_shortshort
_intint
_integerint
_doubledouble
_floatfloat
_booleanboolean
stringString
byteByte
char (since 3.5.10)Character
character (since 3.5.10)Character
longLong
shortShort
intInteger
integerInteger
doubleDouble
floatFloat
booleanBoolean
dateDate
decimalBigDecimal
bigdecimalBigDecimal
bigintegerBigInteger
objectObject
date[]Date[]
decimal[]BigDecimal[]
bigdecimal[]BigDecimal[]
biginteger[]BigInteger[]
object[]Object[]
mapMap
hashmapHashMap
listList
arraylistArrayList
collectionCollection
iteratorIterator
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值