Go + MongoDB:多态查询

时间:2019-02-25 11:55:27

标签: mongodb go polymorphism mgo

(对不起,这个问题比我想象的要久...)

我将Go和MongoDB与mgo驱动程序配合使用。我试图在同一个MongoDB集合中坚持并检索不同的结构(实现一个通用接口)。我来自Java世界(使用Spring可以很容易地完成它,而实际上没有配置),并且我很难用Go来做类似的事情。 我已经阅读了我能找到的所有上一篇相关文章或帖子或StackExchange问​​题,但仍然没有找到完整的解决方案。这包括:

这是我用于测试的简化设置。假设两个结构S1S2实现了公共接口IS2具有类型为S1的隐式字段,这意味着结构化的S2嵌入了一个S1值,而类型化的S2实现了{{1 }}。

I

现在,我可以使用type I interface { f1() } type S1 struct { X int } type S2 struct { S1 Y int } func (*S1) f1() { fmt.Println("f1") } 轻松保存S1S2的实例,但是例如,要使用mgo.Collection.Insert()正确填充值,我需要传递一个指针到mgo.Collection.Find().One()S1的现有值,这意味着我已经知道我要读取的对象的类型! 我希望能够从MongoDB集合中检索文档,而无需知道它是S2还是S1,或者实际上是任何实现S2的对象。

到目前为止,这就是我要去的地方:我没有保存直接保存要保留的对象,而是保存了一个I结构,该结构包含MongoDB id,类型的标识符和实际值。类型标识符是 packageName +“的串联。 + typeName ,它用于在类型注册表中查找类型,因为在Go中没有从类型名映射到Type对象的本地机制。这意味着我需要注册我希望能够持久和检索的类型,但是我可以接受。这是怎么回事:

Wrapper

这是类型注册表的代码:

typeregistry.Register(reflect.TypeOf((*S1)(nil)).Elem())
typeregistry.Register(reflect.TypeOf((*S2)(nil)).Elem())

用于保存对象的代码非常简单:

var types map[string]reflect.Type

func 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)
}

这部分工作是因为正确键入了返回的对象,但是我不知道是如何用从MongoDB检索到的数据填充值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) // FIXME populate value using wrapper.Val (type bson.M) // HOW ??? // return the value as *I i := pt.Elem().Interface().(I) return &i, nil } 的,该数据存储为pt wrapper.Val

我尝试了以下操作,但不起作用:

bson.M

所以基本上剩下的问题是:如何从m := wrapper.Val.(bson.M) bsonBytes, _ := bson.Marshal(m) bson.Unmarshal(bsonBytes, pt) 值填充未知结构?我敢肯定必须有一个简单的解决方案... 预先感谢您的帮助。

这里是Github要点,其中包含所有代码:https://gist.github.com/ogerardin/5aa272f69563475ba9d7b3194b12ae57

2 个答案:

答案 0 :(得分:1)

首先,您应该始终检查返回的错误。 bson.Marshal()bson.Unmarshal()返回您未检查的错误。这样做揭示了为什么它不起作用:

  

unmarshal无法处理结构值。使用指针

import javax.swing.border.LineBorder import java.util.ArrayList import javax.swing.event.DocumentListener import java.awt.* import java.awt.event.* import javax.swing.* import javax.swing.event.DocumentEvent /** * Author of the original version: David @ https://stackoverflow.com/users/1133011/david-kroukamp */ class Test { init { val frame = JFrame() frame.defaultCloseOperation = JFrame.EXIT_ON_CLOSE val f = JTextField(10) val autoSuggestor = object : AutoSuggestor(f, frame, ArrayList(), Color.WHITE.brighter(), Color.BLUE, Color.RED, 0.75f) { override fun wordTyped(typedWord: String?): Boolean { //create list for dictionary this in your case might be done via calling a method which queries db and returns results as arraylist val words = ArrayList<String>() words.add("hello") words.add("heritage") words.add("happiness") words.add("goodbye") words.add("cruel") words.add("car") words.add("war") words.add("will") words.add("world") words.add("wall") setDictionary(words) //addToDictionary("bye");//adds a single word return super.wordTyped(typedWord)//now call super to check for any matches against newest dictionary } } val p = JPanel() p.add(f) frame.add(p) frame.pack() frame.isVisible = true } companion object { @JvmStatic fun main(args: Array<String>) { SwingUtilities.invokeLater { Test() } } } } internal open class AutoSuggestor(val textField: JTextField, val container: Window, words: ArrayList<String>, popUpBackground: Color, private val suggestionsTextColor: Color, private val suggestionFocusedColor: Color, opacity: Float, private val callback: (String) -> Unit = {}) { private val suggestionsPanel: JPanel val autoSuggestionPopUpWindow: JWindow private var typedWord: String? = null private val dictionary = ArrayList<String>() private var currentIndexOfSpace: Int = 0 private var tW: Int = 0 private var tH: Int = 0 private val documentListener = object : DocumentListener { override fun insertUpdate(de: DocumentEvent) { checkForAndShowSuggestions() } override fun removeUpdate(de: DocumentEvent) { checkForAndShowSuggestions() } override fun changedUpdate(de: DocumentEvent) { checkForAndShowSuggestions() } } val addedSuggestionLabels: ArrayList<SuggestionLabel> get() { val sls = ArrayList<SuggestionLabel>() for (i in 0 until suggestionsPanel.componentCount) { if (suggestionsPanel.getComponent(i) is SuggestionLabel) { val sl = suggestionsPanel.getComponent(i) as SuggestionLabel sls.add(sl) } } return sls } //get newest word after last white space if any or the first word if no white spaces val currentlyTypedWord: String get() { val text = textField.text var wordBeingTyped = "" if (text.contains(" ")) { val tmp = text.lastIndexOf(" ") if (tmp >= currentIndexOfSpace) { currentIndexOfSpace = tmp wordBeingTyped = text.substring(text.lastIndexOf(" ")) } } else { wordBeingTyped = text } return wordBeingTyped.trim { it <= ' ' } } init { this.textField.document.addDocumentListener(documentListener) setDictionary(words) typedWord = "" currentIndexOfSpace = 0 tW = 0 tH = 0 autoSuggestionPopUpWindow = JWindow(container) autoSuggestionPopUpWindow.opacity = opacity suggestionsPanel = JPanel() suggestionsPanel.layout = GridLayout(0, 1) suggestionsPanel.background = popUpBackground addFocusListenersToHandleVisibilityOfPopUpWindow() addKeyBindingToRequestFocusInPopUpWindow() } private fun addFocusListenersToHandleVisibilityOfPopUpWindow() { textField.addFocusListener(object:FocusListener { override fun focusLost(e: FocusEvent?) { var focusOnPopUp = false for (i in 0 until suggestionsPanel.componentCount) { if (suggestionsPanel.getComponent(i) is SuggestionLabel) { val label = suggestionsPanel.getComponent(i) as SuggestionLabel if (label.isFocused) focusOnPopUp = true } } if (!focusOnPopUp && !shouldShowPopUpWindow) { autoSuggestionPopUpWindow.isVisible = false } } override fun focusGained(e: FocusEvent?) { shouldShowPopUpWindow = false } }) } private fun addKeyBindingToRequestFocusInPopUpWindow() { textField.getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, true), "Escape released") textField.actionMap.put("Escape released", object : AbstractAction() { override fun actionPerformed(ae: ActionEvent) {// Hide the popwindow shouldShowPopUpWindow = false autoSuggestionPopUpWindow.isVisible = false } }) textField.getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, true), "Down released") textField.actionMap.put("Down released", object : AbstractAction() { override fun actionPerformed(ae: ActionEvent) {//focuses the first label on popwindow for (i in 0 until suggestionsPanel.componentCount) { if (suggestionsPanel.getComponent(i) is SuggestionLabel) { (suggestionsPanel.getComponent(i) as SuggestionLabel).isFocused = true autoSuggestionPopUpWindow.toFront() autoSuggestionPopUpWindow.requestFocusInWindow() suggestionsPanel.requestFocusInWindow() suggestionsPanel.getComponent(i).requestFocusInWindow() break } } } }) textField.getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, true), "Up released") textField.actionMap.put("Up released", object : AbstractAction() { override fun actionPerformed(ae: ActionEvent) {//focuses the last label on popwindow for (i in 0 until suggestionsPanel.componentCount) { val reverseIndex = suggestionsPanel.componentCount-1 - i if (suggestionsPanel.getComponent(reverseIndex) is SuggestionLabel) { (suggestionsPanel.getComponent(reverseIndex) as SuggestionLabel).isFocused = true autoSuggestionPopUpWindow.toFront() autoSuggestionPopUpWindow.requestFocusInWindow() suggestionsPanel.requestFocusInWindow() suggestionsPanel.getComponent(reverseIndex).requestFocusInWindow() break } } } }) suggestionsPanel.getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, true), "Escape released") suggestionsPanel.actionMap.put("Escape released", object : AbstractAction() { override fun actionPerformed(ae: ActionEvent) {// Hide the popwindow shouldShowPopUpWindow = false autoSuggestionPopUpWindow.isVisible = false } }) suggestionsPanel.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, true), "Up released") suggestionsPanel.actionMap.put("Up released", object : AbstractAction() { override fun actionPerformed(ae: ActionEvent) {//allows scrolling of labels in pop window (I know very hacky for now :)) val sls = addedSuggestionLabels val max = sls.size var indexOfFocusedSuggestion = -1 for (i in 0 until max) { val sl = sls[i] if ( sl.isFocused ) indexOfFocusedSuggestion = i } if (indexOfFocusedSuggestion - 1 < 0) { sls[indexOfFocusedSuggestion].isFocused = false autoSuggestionPopUpWindow.isVisible = false setFocusToTextField() checkForAndShowSuggestions()//fire method as if document listener change occured and fired it } else { sls[indexOfFocusedSuggestion].isFocused = false sls[indexOfFocusedSuggestion-1].isFocused = true autoSuggestionPopUpWindow.toFront() autoSuggestionPopUpWindow.requestFocusInWindow() suggestionsPanel.requestFocusInWindow() suggestionsPanel.getComponent(indexOfFocusedSuggestion-1).requestFocusInWindow() } } }) suggestionsPanel.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, true), "Down released") suggestionsPanel.actionMap.put("Down released", object : AbstractAction() { override fun actionPerformed(ae: ActionEvent) {//allows scrolling of labels in pop window (I know very hacky for now :)) val sls = addedSuggestionLabels val max = sls.size var indexOfFocusedSuggestion = -1 for (i in 0 until max) { val sl = sls[i] if ( sl.isFocused ) indexOfFocusedSuggestion = i } if (indexOfFocusedSuggestion + 1 >= max) { sls[indexOfFocusedSuggestion].isFocused = false autoSuggestionPopUpWindow.isVisible = false setFocusToTextField() checkForAndShowSuggestions()//fire method as if document listener change occured and fired it } else { sls[indexOfFocusedSuggestion].isFocused = false sls[indexOfFocusedSuggestion+1].isFocused = true autoSuggestionPopUpWindow.toFront() autoSuggestionPopUpWindow.requestFocusInWindow() suggestionsPanel.requestFocusInWindow() suggestionsPanel.getComponent(indexOfFocusedSuggestion+1).requestFocusInWindow() } } }) } private fun setFocusToTextField() { container.toFront() container.requestFocusInWindow() textField.requestFocusInWindow() } var shouldShowPopUpWindow = false private fun checkForAndShowSuggestions() { typedWord = currentlyTypedWord suggestionsPanel.removeAll()//remove previos words/jlabels that were added //used to calcualte size of JWindow as new Jlabels are added tW = 0 tH = 0 val added = wordTyped(typedWord) if (!added) { if (autoSuggestionPopUpWindow.isVisible) { autoSuggestionPopUpWindow.isVisible = false } } else { shouldShowPopUpWindow = true showPopUpWindow() setFocusToTextField() } } protected fun addWordToSuggestions(word: String) { val suggestionLabel = SuggestionLabel(word, suggestionFocusedColor, suggestionsTextColor, this, callback) calculatePopUpWindowSize(suggestionLabel) suggestionsPanel.add(suggestionLabel) } private fun calculatePopUpWindowSize(label: JLabel) { //so we can size the JWindow correctly if (tW < label.preferredSize.width) { tW = label.preferredSize.width } tH += label.preferredSize.height } private fun showPopUpWindow() { autoSuggestionPopUpWindow.contentPane.add(suggestionsPanel) autoSuggestionPopUpWindow.minimumSize = Dimension(textField.width, 30) autoSuggestionPopUpWindow.setSize(tW, tH) autoSuggestionPopUpWindow.isVisible = true var windowX = 0 var windowY = 0 windowX = container.getX() + textField.x + 5 if (suggestionsPanel.height > autoSuggestionPopUpWindow.minimumSize.height) { windowY = container.getY() + textField.y + textField.height + autoSuggestionPopUpWindow.minimumSize.height } else { windowY = container.getY() + textField.y + textField.height + autoSuggestionPopUpWindow.height } autoSuggestionPopUpWindow.setLocation(windowX, windowY) autoSuggestionPopUpWindow.minimumSize = Dimension(textField.width, 30) autoSuggestionPopUpWindow.revalidate() autoSuggestionPopUpWindow.repaint() } fun setDictionary(words: ArrayList<String>?) { dictionary.clear() if (words == null) { return //so we can call constructor with null value for dictionary without exception thrown } for (word in words) { dictionary.add(word) } } fun addToDictionary(word: String) { dictionary.add(word) } open fun wordTyped(typedWord: String?): Boolean { if (typedWord!!.isEmpty()) { return false } var suggestionAdded = false for (word in dictionary) {//get words in the dictionary which we added var fullyMatches = word.length >= typedWord.length for (i in 0 until typedWord.length) {//each string in the word if (word.length > i && !typedWord.toLowerCase().startsWith(word.toLowerCase()[i].toString(), i)) {//check for match fullyMatches = false break } } if (fullyMatches) { addWordToSuggestions(word) suggestionAdded = true } } return suggestionAdded } } internal class SuggestionLabel(string: String, private val suggestionBorderColor: Color, private val suggestionsTextColor: Color, private val autoSuggestor: AutoSuggestor, private val callback: (String) -> Unit) : JLabel(string) { var isFocused = false set(focused) { if (focused) { border = LineBorder(suggestionBorderColor) } else { border = null } repaint() field = focused } private val autoSuggestionsPopUpWindow: JWindow private val textField: JTextField init { this.textField = autoSuggestor.textField this.autoSuggestionsPopUpWindow = autoSuggestor.autoSuggestionPopUpWindow initComponent() } private fun initComponent() { isFocusable = true foreground = suggestionsTextColor addMouseListener(object : MouseAdapter() { override fun mouseClicked(me: MouseEvent) { super.mouseClicked(me) replaceWithSuggestedText() autoSuggestionsPopUpWindow.isVisible = false } }) getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0, true), "Enter released") actionMap.put("Enter released", object : AbstractAction() { override fun actionPerformed(ae: ActionEvent) { replaceWithSuggestedText() autoSuggestionsPopUpWindow.isVisible = false } }) getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, true), "Escape released") actionMap.put("Escape released", object : AbstractAction() { override fun actionPerformed(ae: ActionEvent) {// Hide the popwindow autoSuggestionsPopUpWindow.isVisible = false } }) } private fun replaceWithSuggestedText() { val suggestedWord = text val text = textField.text val typedWord = autoSuggestor.currentlyTypedWord val t = text.substring(0, text.lastIndexOf(typedWord)) val tmp = t + text.substring(text.lastIndexOf(typedWord)).replace(typedWord, suggestedWord) textField.text = tmp callback(tmp) } } 的类型为reflect.Value(恰好是一个结构),而不是您应该传递给pt的东西。您应该通过例如指向要解组的结构值的指针(它将包装在bson.Unmarshal()值中)。因此,请对Value.Interface()返回的值调用reflect.New()

interface{}

您可以将其传递给pt := reflect.New(t).Interface()

bson.Unmarshal()

(在您的真实代码中,您除了恐慌之外还想做其他事情,这只是表明您应该始终检查错误!)

还请注意,可以将地图直接转换为结构(直接意味着无需封送处理和拆封处理)。您可以手工实现它,也可以使用现成的第三方库。有关详细信息,请参见Converting map to struct

还请注意,还有更多聪明的方法可以解决您想做的事情。您可以将类型存储在ID本身中,因此,如果您具有ID,则可以构造类型的值以将其解组到查询结果中,因此可以跳过此整个过程。这样会更简单,更有效。

例如,您可以使用以下ID结构:

bsonBytes, err := bson.Marshal(m)
if err != nil {
    panic(err)
}
if err = bson.Unmarshal(bsonBytes, pt); err != nil {
    panic(err)
}

例如:

<type>-<id>

在获取/加载该文档时,可以使用反射创建一个my.package.S1-123 值,然后直接将其解编(将其传递给my.package.S1)。

答案 1 :(得分:0)

根据@icza的评论,这是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
}

bson.M到结构的转换由https://github.com/mitchellh/mapstructure处理,而不是编组/拆组。