博客 / 詳情

返回

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

alias javaType
_byte byte
_char (since 3.5.10) char
_character (since 3.5.10) char
_long long
_short short
_int int
_integer int
_double double
_float float
_boolean boolean
string String
byte Byte
char (since 3.5.10) Character
character (since 3.5.10) Character
long Long
short Short
int Integer
integer Integer
double Double
float Float
boolean Boolean
date Date
decimal BigDecimal
bigdecimal BigDecimal
biginteger BigInteger
object Object
date[] Date[]
decimal[] BigDecimal[]
bigdecimal[] BigDecimal[]
biginteger[] BigInteger[]
object[] Object[]
map Map
hashmap HashMap
list List
arraylist ArrayList
collection Collection
iterator Iterator
user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.