關(guān)于mongodb子類多態(tài)問題的解決方案

問題

系統(tǒng)采用spring data+mongodb driver方式進(jìn)行對(duì)象的保存加勤,以及進(jìn)行相關(guān)的序列化以及反序列化火本。

由于在業(yè)務(wù)系統(tǒng)設(shè)計(jì)過程中主籍,需要根據(jù)業(yè)務(wù)不同保存不同的子類讨彼,然后展示時(shí)也要相應(yīng)的展示鲤嫡。這就要求mongo在保存時(shí)保存完整的數(shù)據(jù)送挑,并且反序列化時(shí)也要能映射到正常的類。

方案

fastjson的JSONType_seeAlso_cn

在fastjson-1.2.11版本中暖眼,@JSONType支持seeAlso配置惕耕,類似于JAXB中的XmlSeeAlso。

從用法上看基本能滿足需求诫肠,相比jackson方案比較簡(jiǎn)潔司澎,也支持隱藏類的展示。不過在序列化類的時(shí)候需要加上@type關(guān)鍵字注明類名或者別名栋豫。如果要改變關(guān)鍵字挤安,可以使用setDefaultTypeKey來改變。

JavaBean Config

@JSONType(seeAlso={Dog.class, Cat.class})
public static class Animal {
}

@JSONType(typeName = "dog")
public static class Dog extends Animal {
    public String dogName;
}

@JSONType(typeName = "cat")
public static class Cat extends Animal {
    public String catName;
}

Usage

Dog dog = new Dog();
dog.dogName = "dog1001";

String text = JSON.toJSONString(dog, SerializerFeature.WriteClassName);
Assert.assertEquals("{\"@type\":\"dog\",\"dogName\":\"dog1001\"}", text);

jackson的XmlSeeAlso

Jackson可以輕松的將Java對(duì)象轉(zhuǎn)換成json對(duì)象和xml文檔丧鸯,同樣也可以將json蛤铜、xml轉(zhuǎn)換成Java對(duì)象。

可以通過配置多態(tài)類型處理,配置相對(duì)復(fù)雜一點(diǎn)围肥,不過比較靈活剿干。支持使用多種識(shí)別碼以及識(shí)別碼可以選擇多種方式,包括兄弟屬性穆刻,擴(kuò)展屬性以及已存在屬性等置尔,并且也支持隱藏類名,以及自定義每個(gè)類的識(shí)別碼氢伟。

JavaBean Config

public class Car extends Vehicle {
    private int seatingCapacity;
    private double topSpeed;
 
    public Car(String make, String model, int seatingCapacity, double topSpeed) {
        super(make, model);
        this.seatingCapacity = seatingCapacity;
        this.topSpeed = topSpeed;
    }
 
    // no-arg constructor, getters and setters
}

@JsonTypeInfo(
  use = JsonTypeInfo.Id.NAME, 
  include = JsonTypeInfo.As.PROPERTY, 
  property = "type")
@JsonSubTypes({ 
  @Type(value = Car.class, name = "car"))
public abstract class Vehicle {
    // fields, constructors, getters and setters
}

Usage

  • use:定義使用哪一種類型識(shí)別碼榜轿,它有下面幾個(gè)可選值:
    1. JsonTypeInfo.Id.CLASS:使用完全限定類名做識(shí)別
    2. JsonTypeInfo.Id.MINIMAL_CLASS:若基類和子類在同一包類,使用類名(忽略包名)作為識(shí)別碼
    3. JsonTypeInfo.Id.NAME:一個(gè)合乎邏輯的指定名稱
    4. JsonTypeInfo.Id.CUSTOM:自定義識(shí)別碼腐芍,由@JsonTypeIdResolver對(duì)應(yīng)差导,稍后解釋
    5. JsonTypeInfo.Id.NONE:不使用識(shí)別碼
  • include(可選):指定識(shí)別碼是如何被包含進(jìn)去的,它有下面幾個(gè)可選值:
    1. JsonTypeInfo.As.PROPERTY:作為數(shù)據(jù)的兄弟屬性
    2. JsonTypeInfo.As.EXISTING_PROPERTY:作為POJO中已經(jīng)存在的屬性
    3. JsonTypeInfo.As.EXTERNAL_PROPERTY:作為擴(kuò)展屬性
    4. JsonTypeInfo.As.WRAPPER_OBJECT:作為一個(gè)包裝的對(duì)象
    5. JsonTypeInfo.As.WRAPPER_ARRAY:作為一個(gè)包裝的數(shù)組
      property(可選):制定識(shí)別碼的屬性名稱
      此屬性只有當(dāng)use為JsonTypeInfo.Id.CLASS(若不指定property則默認(rèn)為@class)猪勇、JsonTypeInfo.Id.MINIMAL_CLASS(若不指定property則默認(rèn)為@c)设褐、JsonTypeInfo.Id.NAME(若不指定property默認(rèn)為@type),include為JsonTypeInfo.As.PROPERTY泣刹、JsonTypeInfo.As.EXISTING_PROPERTY助析、JsonTypeInfo.As.EXTERNAL_PROPERTY時(shí)才有效
  • defaultImpl(可選):如果類型識(shí)別碼不存在或者無效,可以使用該屬性來制定反序列化時(shí)使用的默認(rèn)類型
  • visible(可選椅您,默認(rèn)為false):是否可見
    屬性定義了類型標(biāo)識(shí)符的值是否會(huì)通過JSON流成為反序列化器的一部分外冀,默認(rèn)為fale,也就是說,jackson會(huì)從JSON內(nèi)容中處理和刪除類型標(biāo)識(shí)符再傳遞給JsonDeserializer。

結(jié)果

通過對(duì)這兩種方法的調(diào)研掀泳,都能滿足在序列化以及反序列化時(shí)實(shí)現(xiàn)子類多態(tài)雪隧,并且不會(huì)有泄漏類名的風(fēng)險(xiǎn)。

發(fā)現(xiàn)在mongodb保存類時(shí)正常员舵,但是當(dāng)查詢類的內(nèi)容時(shí)脑沿,mongodb會(huì)直接使用定義的父類進(jìn)行反序列化。后面通過對(duì)mongodb存儲(chǔ)以及查詢?cè)淼恼{(diào)研發(fā)現(xiàn)马僻,mongodb在存儲(chǔ)時(shí)并不是采用json進(jìn)行保存庄拇,而是采用了BSON這種格式。也就是拓展了JSON的相關(guān)信息韭邓。

JSON can only represent a subset of the types supported by BSON. To preserve type information, MongoDB adds the following extensions to the JSON format:

* Strict mode. Strict mode representations of BSON types conform to the JSON RFC. Any JSON parser can parse these strict mode representations as key/value pairs; however, only the MongoDB internal JSON parser recognizes the type information conveyed by the format.
* mongo Shell mode. The MongoDB internal JSON parser and the mongo shell can parse this mode.

而mongodb的driver在解析保存的信息時(shí)措近,也是使用的BSON的相關(guān)解析方案。于是想是否可以修改相關(guān)解析器來實(shí)現(xiàn)女淑。發(fā)現(xiàn)mongodb在進(jìn)行序列化以及凡序列化時(shí)使用MappingMongoConverter的行為進(jìn)行定義瞭郑。于是修改相關(guān)定義

@Override
    public MappingMongoConverter mappingMongoConverter() throws Exception {
        DbRefResolver dbRefResolver = new DefaultDbRefResolver(mongoDbFactory());
        ObjectMapper mapper = new ObjectMapper()
                .configure(FAIL_ON_UNKNOWN_PROPERTIES, false)
                .registerModule(new SimpleModule() {
                    {
                        addDeserializer(ObjectId.class, new JsonDeserializer<ObjectId>() {
                            @Override
                            public ObjectId deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
                                TreeNode oid = p.readValueAsTree().get("$oid");
                                String string = oid.toString().replaceAll("\"", "");

                                return new ObjectId(string);
                            }
                        });
                    }
                });

        MappingMongoConverter converter = new MappingMongoConverter(dbRefResolver, mongoMappingContext()) {
            @Override
            public <S> S read(Class<S> clazz, DBObject dbo) {
                String string = JSON.serialize(dbo);
                try {
                    return mapper.readValue(string, clazz);
                } catch (IOException e) {
                    throw new RuntimeException(string, e);
                }
            }

            @Override
            public void write(Object obj, DBObject dbo) {
                String string = null;
                try {
                    string = mapper.writeValueAsString(obj);
                } catch (JsonProcessingException e) {
                    throw new RuntimeException(string, e);
                }
                dbo.putAll((DBObject) JSON.parse(string));
            }
        };

        return converter;
    }

然后修改driver相關(guān)的Converter,但是測(cè)試發(fā)現(xiàn)鸭你,獲取到的bson數(shù)據(jù)會(huì)帶有額外的元數(shù)據(jù)凰浮,導(dǎo)致類無法正常反序列化我抠。后面通過查看文檔發(fā)現(xiàn),需要增加JsonWriterSettings來對(duì)元數(shù)據(jù)進(jìn)行處理袜茧。

JsonWriterSettings settings = JsonWriterSettings.builder()
                .int64Converter((value, writer) -> writer.writeNumber(value.toString()))
                .objectIdConverter((value,write)->write.writeString(value.toString()))
                .dateTimeConverter((value, writer) -> writer.writeString(value.toString())
                .build();

然后進(jìn)行相關(guān)測(cè)試,沒發(fā)現(xiàn)什么問題瓣窄。但是后面排查發(fā)現(xiàn)笛厦,系統(tǒng)還采用了mongoGridFSd的方案,Converter也是采用的此方案俺夕,但是bson在轉(zhuǎn)換時(shí)裳凸,對(duì)于binary對(duì)象是沒有數(shù)據(jù)展示的,于是修改相關(guān)Converter為原來的方案劝贸。至此問題解決姨谷。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市映九,隨后出現(xiàn)的幾起案子梦湘,更是在濱河造成了極大的恐慌,老刑警劉巖件甥,帶你破解...
    沈念sama閱讀 222,681評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件捌议,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡引有,警方通過查閱死者的電腦和手機(jī)瓣颅,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,205評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來譬正,“玉大人宫补,你說我怎么就攤上這事≡遥” “怎么了粉怕?”我有些...
    開封第一講書人閱讀 169,421評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)您单。 經(jīng)常有香客問我斋荞,道長(zhǎng),這世上最難降的妖魔是什么虐秦? 我笑而不...
    開封第一講書人閱讀 60,114評(píng)論 1 300
  • 正文 為了忘掉前任平酿,我火速辦了婚禮,結(jié)果婚禮上悦陋,老公的妹妹穿的比我還像新娘蜈彼。我一直安慰自己,他們只是感情好俺驶,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,116評(píng)論 6 398
  • 文/花漫 我一把揭開白布幸逆。 她就那樣靜靜地躺著棍辕,像睡著了一般。 火紅的嫁衣襯著肌膚如雪还绘。 梳的紋絲不亂的頭發(fā)上楚昭,一...
    開封第一講書人閱讀 52,713評(píng)論 1 312
  • 那天,我揣著相機(jī)與錄音拍顷,去河邊找鬼抚太。 笑死,一個(gè)胖子當(dāng)著我的面吹牛昔案,可吹牛的內(nèi)容都是我干的尿贫。 我是一名探鬼主播,決...
    沈念sama閱讀 41,170評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼踏揣,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼庆亡!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起捞稿,我...
    開封第一講書人閱讀 40,116評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤又谋,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后括享,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體搂根,經(jīng)...
    沈念sama閱讀 46,651評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,714評(píng)論 3 342
  • 正文 我和宋清朗相戀三年铃辖,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了剩愧。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,865評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡娇斩,死狀恐怖仁卷,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情犬第,我是刑警寧澤锦积,帶...
    沈念sama閱讀 36,527評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站歉嗓,受9級(jí)特大地震影響丰介,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜鉴分,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,211評(píng)論 3 336
  • 文/蒙蒙 一哮幢、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧志珍,春花似錦橙垢、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,699評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)嗽元。三九已至,卻和暖如春喂击,著一層夾襖步出監(jiān)牢的瞬間剂癌,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,814評(píng)論 1 274
  • 我被黑心中介騙來泰國(guó)打工惭等, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留珍手,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,299評(píng)論 3 379
  • 正文 我出身青樓辞做,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親寡具。 傳聞我的和親對(duì)象是個(gè)殘疾皇子秤茅,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,870評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容