最近发现一个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 |