背景
Java
项目,使用 Mybatis Plus
,在 xml
中使用了 JSON_ARRAY/JSON_ARRAYAGG
函数,想直接映射成 Java
实体,势必想到要用 JacksonTypeHandler
,但是有一个问题,就是对于复杂类型(含泛型),就没法在 xml
中通过 javaType
的方式告知 JacksonTypeHandler
了,遂导致解析失败。
解决
MyJacksonTypeHandler.java
public class MyJacksonTypeHandler extends JacksonTypeHandler {
private final Class<?> type;
public MyJacksonTypeHandler(Class<?> type) {
super(type);
this.type = type;
}
protected Object parse(String json) {
try {
if (MyTypeReference.class.isAssignableFrom(type)) {
MyTypeReference typeReference = (MyTypeReference) type.getConstructor().newInstance();
return getObjectMapper().readValue(json, typeReference.getType());
}
return getObjectMapper().readValue(json, this.type);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
MyTypeReference.java
public interface MyTypeReference {
TypeReference<?> getType();
}
TestJsonList.java
@Data
public class TestJsonList {
private Integer id;
private List<Dog> jsonInfo;
@Data
public static class Dog {
private String name;
}
public static class DogListTypeReference implements MyTypeReference {
@Override
public TypeReference<?> getType() {
return new TypeReference<List<Dog>>(){};
}
}
}
TestJsonMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="xxx.mapper.TestJsonMapper">
<resultMap id="testJsonResultMap" type="xxx.model.TestJson">
<result property="jsonInfo" column="json_info" jdbcType="LONGVARCHAR"
javaType="xxx.model.TestJson$Dog"
typeHandler="com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler"/>
</resultMap>
<resultMap id="testJsonListResultMap" type="xxx.model.TestJsonList">
<result property="jsonInfo" column="json_info" jdbcType="LONGVARCHAR"
javaType="xxx.model.TestJsonList$DogListTypeReference"
typeHandler="xxx.handler.MyJacksonTypeHandler"/>
</resultMap>
<select id="testJson" resultMap="testJsonResultMap">
select 1 as id, '{"name": "dahuang"}' as json_info
</select>
<select id="testJsonList" resultMap="testJsonListResultMap">
select 1 as id, '[{"name": "dahuang"}]' as json_info
</select>
</mapper>
后续
实际测试过程中,发现使用了 LEFT JOIN
后,得到的 json
的所有 value
都会 null
,解析后就得到一个所有成员都为 null
的对象,需要过滤下:
MyJacksonTypeHandler.java
public class MyJacksonTypeHandler extends JacksonTypeHandler {
private final Class<?> type;
public MyJacksonTypeHandler(Class<?> type) {
super(type);
this.type = type;
}
@Override
protected Object parse(String json) {
try {
// 如果指定了 MyTypeReference 类型,需要特殊处理
if (MyTypeReference.class.isAssignableFrom(type)) {
MyTypeReference typeReference = (MyTypeReference) type.getConstructor().newInstance();
Object result = getObjectMapper().readValue(json, typeReference.getType());
// 由于使用了 LEFT JOIN,右表可能为空,导致这里的 json 所有 value 都为 null,单独处理下
if (isAllFieldNull(result)) {
return null;
} else {
removeNullItem(result);
return result;
}
}
// 否则,使用默认的处理方式就行
return getObjectMapper().readValue(json, type);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 去除 List 中对象成员均为 null 的项
*/
private void removeNullItem(Object object) {
Class<?> clazz = object.getClass();
if (List.class.isAssignableFrom(clazz)) {
((List<?>) object).removeIf(this::isAllFieldNull);
}
}
/**
* 判断对象是否所有成员都为 null
*/
private boolean isAllFieldNull(Object object) {
Class<?> clazz = object.getClass();
for (Field field : clazz.getDeclaredFields()) {
field.setAccessible(true);
try {
if (field.get(object) != null) {
return false;
}
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
return true;
}
}
补充
如果不在 XxxMapper.xml 中书写 sql,可以直接在 Java 类上指定 JacksonTypeHandler
即可,也能处理复杂类型(含泛型)
TestJson.java
@Data
@TableName(value = "test_json", autoResultMap = true)
public class TestJson {
private Integer id;
@TableField(typeHandler = JacksonTypeHandler.class)
private Dog jsonInfo;
@TableField(typeHandler = JacksonTypeHandler.class)
private List<Dog> jsonInfoList;
@Data
public static class Dog {
private String name;
}
}
TestController.java
// 不使用 XxxMapper.xml 中的方法
TestJson testJson = testJsonMapper.selectOne(null);
因为在 XxxMapper.xml 中书写 sql,只能通过 resultMap 的方式指定 javaType 和 typeHandler(@TableField 注解指定的 typeHandler 不会被使用),但是却无法将泛型信息传递给指定的 typeHandler。