最近發現一個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
通用的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 |