亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

為了賬號安全,請及時綁定郵箱和手機立即綁定
已解決430363個問題,去搜搜看,總會有你想問的

Go + MongoDB:多態查詢

Go + MongoDB:多態查詢

Go
慕的地8271018 2023-06-12 14:24:03
我將 Go 和 MongoDB 與 mgo 驅動程序一起使用。我試圖在同一個 MongoDB 集合中保留和檢索不同的結構(實現通用接口)。我來自 Java 世界(使用 Spring 很容易做到這一點,幾乎沒有配置),但我很難用 Go 做類似的事情。這是我用于測試的簡化設置。假設兩個結構S1andS2實現了一個公共接口I。S2有一個類型的隱式字段S1,這意味著在結構方面S2嵌入了一個S1值,并且在類型方面S2實現了I。type I interface {    f1()}type S1 struct {    X int}type S2 struct {    S1    Y int}func (*S1) f1() {    fmt.Println("f1")}S1現在我可以保存or的實例并S2輕松使用mgo.Collection.Insert(),但是為了正確填充一個值 usingmgo.Collection.Find().One()例如,我需要傳遞一個指向S1or的現有值的指針S2,這意味著我已經知道我想要讀取的對象的類型??!我希望能夠從 MongoDB 集合中檢索文檔,而不知道它是S1、還是S2,或者實際上是實現I.這是我到目前為止的位置:我沒有直接保存我想要保留的對象,而是保存了一個Wrapper包含 MongoDB id、類型標識符和實際值的結構。類型標識符是packageName + "."的串聯。+ typeName,它用于在類型注冊表中查找類型,因為在 Go 中沒有從類型名稱映射到 Type 對象的本地機制。這意味著我需要注冊我希望能夠保留和檢索的類型,但我可以接受。事情是這樣的:typeregistry.Register(reflect.TypeOf((*S1)(nil)).Elem())typeregistry.Register(reflect.TypeOf((*S2)(nil)).Elem())這是類型注冊表的代碼:var types map[string]reflect.Typefunc init() {    types = make(map[string]reflect.Type)}func Register(t reflect.Type) {    key := GetKey(t)    types[key] = t}func GetKey(t reflect.Type) string {    key := t.PkgPath() + "." + t.Name()    return key}func GetType(key string) reflect.Type {    t := types[key]    return t}保存對象的代碼非常簡單:func save(coll *mgo.Collection, s I) (bson.ObjectId, error) {    t := reflect.TypeOf(s)    wrapper := Wrapper{        Id:      bson.NewObjectId(),        TypeKey: typeregistry.GetKey(t),        Val:     s,    }    return wrapper.Id, coll.Insert(wrapper)}檢索對象的代碼有點棘手:func getById(coll *mgo.Collection, id interface{}) (*I, error) {    // read wrapper    wrapper := Wrapper{}    err := coll.Find(bson.M{"_id": id}).One(&wrapper)    if err != nil {        return nil, err    }}這在返回的對象類型正確時部分起作用,但我無法弄清楚的是如何使用pt從 MongoDB 檢索的數據填充值,這些數據存儲wrapper.Val為bson.M.所以基本上剩下的問題是:如何從bson.M值填充未知結構?我確定必須有一個簡單的解決方案......在此先感謝您的幫助。這是包含所有代碼的 Github 要點:https://gist.github.com/ogerardin/5aa272f69563475ba9d7b3194b12ae57
查看完整描述

2 回答

?
江戶川亂折騰

TA貢獻1851條經驗 獲得超5個贊

首先,您應該始終檢查返回的錯誤。bson.Marshal()bson.Unmarshal()返回您不檢查的錯誤。這樣做揭示了為什么它不起作用:

unmarshal 無法處理結構值。使用指針

pt是類型reflect.Value(恰好是一個結構),而不是你應該傳遞給bson.Unmarshal().?例如,您應該傳遞一個指向要解組的結構值的指針(它將被包裝在一個interface{}值中)。所以調用Value.Interface()返回的值reflect.New()

pt?:=?reflect.New(t).Interface()

您可以將其傳遞給bson.Unmarshal()

bsonBytes, err := bson.Marshal(m)

if err != nil {

? ? panic(err)

}

if err = bson.Unmarshal(bsonBytes, pt); err != nil {

? ? panic(err)

}

(在你的真實代碼中你想做一些除了 panic 之外的事情,這只是為了表明你應該總是檢查錯誤?。?/p>

另請注意,可以將映射直接轉換為結構(直接意味著無需編組和解組)。您可以手動實施或使用現成的第 3 方庫。

另請注意,有更聰明的方法可以解決您想要做的事情。您可以將類型存儲在 ID 本身中,因此如果您有 ID,則可以構造一個類型的值以解組到查詢結果中,這樣您就可以跳過整個過程。這會簡單得多,效率也高得多。

例如,您可以使用以下 ID 結構:

<type>-<id>

例如:

my.package.S1-123

獲取/加載此文檔時,您可以使用反射來創建 的值my.package.S1,并直接解組到該值(將其傳遞給Query.One())。


查看完整回答
反對 回復 2023-06-12
?
森欄

TA貢獻1810條經驗 獲得超5個贊

這是一個getById()實際有效的修改版本:


func getById(coll *mgo.Collection, id interface{}) (*I, error) {

? ? // read wrapper

? ? wrapper := Wrapper{}

? ? err := coll.Find(bson.M{"_id": id}).One(&wrapper)

? ? if err != nil {

? ? ? ? return nil, err

? ? }


? ? // obtain Type from registry

? ? t := typeregistry.GetType(wrapper.TypeKey)


? ? // get a pointer to a new value of this type

? ? pt := reflect.New(t)


? ? // populate value using wrapper.Val

? ? err = mapstructure.Decode(wrapper.V, pt.Interface())

? ? if err != nil {

? ? ? ? return nil, err

? ? }


? ? // return the value as *I

? ? i := pt.Elem().Interface().(I)

? ? return &i, nil

}

從結構到結構的轉換由https://github.com/mitchellh/mapstructurebson.M處理,而不是 marshalling-unmarshaling。


查看完整回答
反對 回復 2023-06-12
  • 2 回答
  • 0 關注
  • 214 瀏覽
慕課專欄
更多

添加回答

舉報

0/150
提交
取消
微信客服

購課補貼
聯系客服咨詢優惠詳情

幫助反饋 APP下載

慕課網APP
您的移動學習伙伴

公眾號

掃描二維碼
關注慕課網微信公眾號