当前使用版本(必填,否则不予处理)

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;
    }
}