美文网首页设计方案
Mybatis中如何进行json序列化

Mybatis中如何进行json序列化

作者: 本然酋长 | 来源:发表于2020-05-13 23:08 被阅读0次

    前言

    这部分内容原本只是在一个大的填坑记录中记录了几个片段。但是,到了今天,我觉得有必要单独拿出来说一下。为什么呢?第一,我觉得,这个解决方案短时间内,我无法再有大的进步了。第二,重点的问题基本都已经得到解决了。好吧,少聊天,多上干货。

    mybatis plus的实现

    在3.2.0之后的mybatis plus中,其实是提供了一个版本的json序列化的。它是提供了几个工具的版本:FastjsonTypeHandler,GsonTypeHandler,JacksonTypeHandler。只要在对应属性的TableField指明TypeHandler,并在TableName上表名autoResultMap为true。一切就都ok了。但是吧,这里有两个问题:

    1. 它序列化的字段是String,在AbstractJsonTypeHandler中直接指明了ps.setString(i, toJson(parameter)); 这意味着什么呢?意味着,你只能使用varchar这样的存储字符串的数据类型进行存储。对于很多人来说,其实这也是足够的,其实它只是想把一个对象存在这里而不想再去开一个一对一的表进行维护。但是,随着json数据类型在关系型数据库的不断普及,各数据库已经相继推出了json的数据类型。我用的PostgreSQL中,jsonb就是如此。我们可以直接用sql检索json中的字段,或者对其建立索引。所以,它提供的这个TypeHandler是不够的。
    2. 这个问题,是后来才发现的。就是,它里面虽然使用type进行了序列化和反序列化。但是,你可以尝试使用List或者Map这样的带泛型的接口。据我后来的测试,gson应该会直接失败,fastjson应该会反序列化成JSONObject,然后,我们在调用自己POJO的方法时就会报错。

    json字段类型的实现

    关于这个问题,其实我给mybatis plus的社区提过issue。对方以jdbc不支持json类型,无法提供通用版本为由拒绝了。没办法了,估计这个框架里一时半会不会有了,就只能实现自己的了。先上代码吧,我的通用实现如下:

    @MappedTypes({Object.class})
    public class MybatisJsonTypeHandler extends BaseTypeHandler<Object> {
    
        protected Class<Object> type;
    
        public MybatisJsonTypeHandler(Class<Object> type) {
            this.type=type;
        }
    
        @Override
        public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType) throws SQLException {
            PGobject jsonObject = new PGobject();
            jsonObject.setType("jsonb");
            jsonObject.setValue(JsonUtil.obj2Json(parameter));
            ps.setObject(i, jsonObject);
        }
    
        @Override
        public Object getNullableResult(ResultSet rs, String columnName) throws SQLException {
    
            final String json = rs.getString(columnName);
            return JsonUtil.json2Obj(json,type);
        }
    
        @Override
        public Object getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
            final String json = rs.getString(columnIndex);
            return JsonUtil.json2Obj(json,type);
        }
    
        @Override
        public Object getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
            final String json = cs.getString(columnIndex);
            return JsonUtil.json2Obj(json,type);
        }
    }
    

    其实,再网上也可以找到各种通用版本,但是,他们很多都是以Object或者JSONObject为返回对象的。因为拿不到原来的属性到底是什么类型。其实这件事在TypeHandler的框架中是有的,在构造函数中的第一个参数就是其Class对象,注入进来就可以了。然后,就形成了一个通用的TypeHandler。这个TypeHandler可以正常应对所有的属性为POJO的情况。

    集合接口

    第二个问题就没有那么容易了。这个问题是怎么发现的呢?因为我在一个属性上用了Map,而Map的value是我的一个对象而不是String,然后就出现了问题。我从数据库读出来,使用这个对象的get方法时,跟我说方法不存在。断点看下,发现实际对象是JSONObject。自此,开始填这个坑。
    首先,我尝试使用已经获得的type实例去构建一个正确的类型。但是,经过多方尝试,最终也未能如愿。而且,断点进去,发现这个type实例里面只有Map的类型信息,完全找不到泛型类型,于是就放弃了这条思路。
    我记得之前在Fastjson中使用过TypeReference进行反序列化,可以带上类型。于是,我就构建了这样一个类:

    @MappedTypes({Object.class})
    public class UploadDataItemMapTypeHandler extends BaseTypeHandler<Map<String, UploadDataItem>> {
    
        private TypeReference<Map<String,UploadDataItem>> typeReference=new TypeReference<Map<String, UploadDataItem>>() {};
    
        @Override
        public void setNonNullParameter(PreparedStatement preparedStatement, int i, Map<String, UploadDataItem> stringUploadDataItemMap, JdbcType jdbcType) throws SQLException {
            PGobject jsonObject = new PGobject();
            jsonObject.setType("jsonb");
            jsonObject.setValue(JsonUtil.obj2Json(stringUploadDataItemMap));
            preparedStatement.setObject(i, jsonObject);
        }
    
        @Override
        public Map<String, UploadDataItem> getNullableResult(ResultSet resultSet, String s) throws SQLException {
            final String json = resultSet.getString(s);
            return JsonUtil.json2Obj(json,typeReference);
        }
    
        @Override
        public Map<String, UploadDataItem> getNullableResult(ResultSet resultSet, int i) throws SQLException {
            final String json = resultSet.getString(i);
            return JsonUtil.json2Obj(json,typeReference);
        }
    
        @Override
        public Map<String, UploadDataItem> getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
            final String json = callableStatement.getString(i);
            return JsonUtil.json2Obj(json,typeReference);
        }
    }
    

    经过测试,完全正确。不过,这个类的局限性是需要指定具体的类型。也就是,遇到一种情况就需要建立一个TypeHandler。如果真的需要,其实这样解决问题也不大,暂时就这样吧。有朋友直到怎么构建通用的TypeHandler的话,麻烦跟我说下,我补充进来,谢谢。

    后续填坑补充

    key不可为Integer

    今天发现了一个从来没想到过的坑。我在用上述的TypeHandler转化时,定义类型定义了一个Map<Integer, CmdRecordContentItemDto>。没有想过这个会有什么特殊结果,插入时,给了我一记报错,如下:

    org.springframework.dao.DataIntegrityViolationException: 
    ### Error updating database.  Cause: org.postgresql.util.PSQLException: 错误: invalid input syntax for type json
    详细:期望是字符串或\"}\",但发现结果是\"0\".
    在位置:JSON数据, 行 1: {0...
    ### The error may exist in com/ym/sensor/dao/CmdRecordDao.java (best guess)
    ### The error may involve com.ym.sensor.dao.CmdRecordDao.insert-Inline
    ### The error occurred while setting parameters
    ### SQL: INSERT INTO cmd_record  ( record_id, sensor_code, cmd_code, cmd_content, result, sendtime, message_id, retry_count )  VALUES  ( ?, ?, ?, ?, ?, ?, ?, ? )
    ### Cause: org.postgresql.util.PSQLException: 错误: invalid input syntax for type json
    详细:期望是字符串或\"}\",但发现结果是\"0\".
    在位置:JSON数据, 行 1: {0...
    ; 错误: invalid input syntax for type json
    详细:期望是字符串或\"}\",但发现结果是\"0\".
    在位置:JSON数据, 行 1: {0...; nested exception is org.postgresql.util.PSQLException: 错误: invalid input syntax for type json
    详细:期望是字符串或\"}\",但发现结果是\"0\".
    在位置:JSON数据, 行 1: {0...
    

    说实话,没太想到是怎么回事。于是根据它的秒数,发现,这个0,是json字符串里一段Integer的值。难道它不支持Integer序列化?不能呀,这不是一个很明显且不能不修改的一个bug吗?但是,当我把类型改为Map<String, CmdRecordContentItemDto>时,一切都是那么的和谐。但是,这样我的业务代码就不和谐了,所以我觉得还是改回来用Integer,让后我在json序列化的时候把是不是String的都加上引号。百度了段代码如下:

    JSON.toJSONString(json, SerializerFeature.WriteNonStringValueAsString)
    

    这是fastjson的代码哈,我用的是fastjson,别的我就先不管了。然后试下,嗯,还是不行。我发现,里面Integer的那个0没有加上引号,难道fastjson有bug?我debug进去,expression调试,发现了另一个选项:WriteNonStringKeyAsString。至此,我才发现,它并不是不能识别Integer,而是在实现的时候默认了所有json的属性名字必须有引号。这件事情说得过去,而且也不会引发大面积的问题。至此,算是解决了这个问题。也在此几下,PG的json类型的坑。

    相关文章

      网友评论

        本文标题:Mybatis中如何进行json序列化

        本文链接:https://www.haomeiwen.com/subject/qxsanhtx.html