当前使用版本(必填,否则不予处理)
3.5.2
该问题是如何引起的?(确定最新版也有问题再提!!!)
写入H2数据库时 JSON 对象写入成了 JSON String 对象.
重现步骤(如果有就写完整)
@Data
@TableName(value = "json_table", autoResultMap = true)
public class JsonModel {
@TableId(type = IdType.AUTO)
private Integer id;
@TableField(typeHandler = JacksonTypeHandler.class, jdbcType = JdbcType.BINARY)
private FieldType jfield;
@Data
@Accessors(chain = true)
public static class FieldType {
private int intValue;
private String strValue;
}
}
public interface JsonModelMapper extends BaseMapper<JsonModel> {
}
src/test/resources/schema.sql
create table json_table (
id int not null default '0' auto_increment,
jfield json not null default '{}',
PRIMARY KEY (`id`)
);
@MybatisPlusTest
@AutoConfigureJdbc
class JsonModelMapperTest {
@Autowired
JsonModelMapper jmapper;
@Test
void json() {
JsonModel entity = new JsonModel();
JsonModel.FieldType field = new JsonModel.FieldType().setIntValue(1).setStrValue("hello");
entity.setJfield(field);
jmapper.insert(entity);
entity = jmapper.selectById(entity.getId());
assertEquals(field, entity.getJfield());
}
}
报错信息
org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.executor.result.ResultMapException: Error attempting to get column 'jfield' from result set. Cause: java.lang.RuntimeException: com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of `org.example.mybatisplus.JsonModel$FieldType` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('{"intValue":1,"strValue":"hello"}')
at [Source: (String)""{\"intValue\":1,\"strValue\":\"hello\"}""; line: 1, column: 1]
...
Caused by: com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of `org.example.mybatisplus.JsonModel$FieldType` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('{"intValue":1,"strValue":"hello"}')
at [Source: (String)""{\"intValue\":1,\"strValue\":\"hello\"}""; line: 1, column: 1]
从报错信息可以看出写入数据库的是 Source: (String)""{\"intValue\":1,\"strValue\":\"hello\"}"", {}两侧的引号表明数据是JSON 意义上的String, 而不是 JSON Object.
H2文档中建议在sql中加入 format json 后缀: http://www.h2database.com/html/datatypes.html#json_type
com.baomidou.mybatisplus.extension.handlers.AbstractJsonTypeHandler#setNonNullParameter 函数使用的 ps.setString() 方法. 我本地改成 setBytes() 之后也可以通过.
Comment From: miemieYaho
不支持带泛型的,泛型会丢失
Comment From: fishautumn
不支持带泛型的,泛型会丢失
更新示意代码, 去掉泛型了.
Comment From: miemieYaho
JacksonTypeHandler 只支持数据库字段类型是 varchar
Comment From: TheVanguardOfLonely
我也遇到了,mysql 数据源 没问题,h2就有问题
org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.executor.result.ResultMapException: Error attempting to get column 'variable_list' from result set. Cause: java.lang.RuntimeException: com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize value of type `com.alibaba.fastjson.JSONArray` from String value (token `JsonToken.VALUE_STRING`)
at [Source: (String)""[\"variable_001\",\"variable_002\"]""; line: 1, column: 1]
Comment From: qmdx
自定义 typeHandler
Comment From: PerfySchu
楼主有解决方案了吗?我使用的JOOQ框架,也遇到一样的问题
Comment From: HarrisonQi
楼主是如何解决的呢,这个自定义 typehandler 还是同样的问题,反而引发了更多的反射失败
Comment From: HarrisonQi
使用了这个方法暂时绕过了报错,还想学习大家的更优雅的解决方案
import com.baomidou.mybatisplus.core.toolkit.Assert;
import com.baomidou.mybatisplus.extension.handlers.AbstractJsonTypeHandler;
import com.baomidou.mybatisplus.extension.handlers.GsonTypeHandler;
import com.google.gson.Gson;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;
import org.apache.ibatis.type.MappedTypes;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@MappedTypes({Object.class})
@MappedJdbcTypes({JdbcType.VARCHAR})
public class MyJsonTypeHandler extends AbstractJsonTypeHandler<Object> {
private static final Logger log = LoggerFactory.getLogger(MyJsonTypeHandler.class);
private static Gson GSON;
private final Class<?> type;
public MyJsonTypeHandler(Class<?> type) {
if (log.isTraceEnabled()) {
log.trace("MyJsonTypeHandler(" + type + ")");
}
Assert.notNull(type, "Type argument cannot be null", new Object[0]);
this.type = type;
}
protected Object parse(String json) {
// 只在单元测试环境 h2 进行转换 TODO 寻找更优雅的解决方案
if (StringUtils.isNotBlank(json)){
json = json.replaceAll("\\\\\"", "\"");
if (json.startsWith("\"") && json.endsWith("\"")) {
json = json.substring(1, json.length() - 1);
}
}
return getGson().fromJson(json, this.type);
}
protected String toJson(Object obj) {
return getGson().toJson(obj);
}
public static Gson getGson() {
if (null == GSON) {
GSON = new Gson();
}
return GSON;
}
public static void setGson(Gson gson) {
Assert.notNull(gson, "Gson should not be null", new Object[0]);
GSON = gson;
}
}