最近折腾了在 SpringMVC 直接传入 JSON 和 MyBatis 读出写入 JSON 的功能。
首先简单了解 Jackson 的类型系统:
- TreeNode 接口是根类型
- JsonNode 抽象类是 TreeNode 接口的直接实现,也是其他 Node 的基类
- 之上再分为 ValueNode 值节点和 ContainerNode 容器节点两种
- 最后是广为人知的 BooleanNode / StringNode / ArrayNode / ObjectNode 等节点
本文综述只有一句话:使用抽象基类,不要使用接口类型。
SpringMVC 里 Controller 中路由映射的方法的参数列表,可以配置 @RequestBody
注解,使某个参数来自解析后的 HTTP 的 body 内容,我们称之为 官网:www.fhadmin.org Form。Form 中需要使用 JsonNode 而不是 TreeNode。
HTTP 请求样例
{
"id": "1",
"custom": {
"key": "value"
}
}
Form 类
@Data
public classMyForm{
private String id;
private JsonNode custom;
}
本文默认使用自动生成代码的 lombok 包,@Data
和 @RequiredArgsConstructor
都出自该包。
路由映射的方法
@RestController
public classMyController{
@PostMapping("/my")
public Object myMethod(@RequestBody MyForm form){
...
}
}
如果我们希望从 MyBatis 官网:www.fhadmin.org 中正确读出写入,也要用 JsonNode 而不是 TreeNode。
@Data
public classUser{
private String id;
private JsonNode custom;
}
需要实现 JsonNodeTypeHandler 并注册到 MyBatis 的 SqlSessionFactory
import java.io.*;
import java.sql.*;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;
import org.apache.ibatis.type.MappedTypes;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
@MappedTypes(JsonNode.class)
@MappedJdbcTypes(JdbcType.VARCHAR)
public classJsonNodeTypeHandlerextendsBaseTypeHandler<JsonNode> {
private final ObjectMapper objectMapper;
@Override 官网:www.fhadmin.org
publicvoidsetNonNullParameter(PreparedStatement ps, int i, JsonNode parameter, JdbcType jdbcType)throws SQLException {
String json = parameter.toString();
ps.setString(i, json);
}
private JsonNode read(String json){
try {
return objectMapper.readTree(json);
} catch (JsonParseException e) {
if (LOG.isWarnEnabled()) {
LOG.warn("JSON parse failed", e);
}
return null;
} catch (IOException e) {
// should not occur, no real i/o...
throw new IllegalArgumentException(e.getMessage(), e);
}
}
@Override
public JsonNode getNullableResult(ResultSet rs, String columnName)throws SQLException {
String json = rs.getString(columnName);
return read(json);
}
@Override
public JsonNode getNullableResult(ResultSet rs, int columnIndex)throws SQLException {
String json = rs.getString(columnIndex);
return read(json);
}
@Override
public JsonNode getNullableResult(CallableStatement cs, int columnIndex)throws SQLException {
String json = cs.getString(columnIndex);
return read(json);
}
}
List<TypeHandler> typeHandlers = ...;
typeHandlers.add(new JsonNodeTypeHandler(objectMapper));
sqlSessionFactory.setTypeHandlers(typeHandlers.toArray(new TypeHandler[typeHandlers.size()]));
这样就大功告成了。