2014-11-04 44 views
2

我正在開發一個使用Java(Swing GUI)和MongoDB數據存儲解決方案構建的無線網絡調查工具。我是MongoDB的新手,幾乎不是Java大師,所以我需要一些幫助。我想查找一個網絡是否存在於我的數據庫中,並將聽到的點附加到網絡文檔中。如果網絡不存在,我想爲該網絡創建一個文檔並添加聽到的點。我一直在努力解決這個問題,但我似乎無法圍繞解決方案。另外,如果BSSID是唯一的ID,那麼它將會很好,所以我沒有得到任何重複的網絡。我的理想數據結構看起來像這樣:如何檢查MongoDB對象是否存在並分別創建/更新?

{ 'bssid' : 'ca:fe:de:ad:be:ef', 
    'channel' : 6, 
    'heardpoints' : { 
     'point' : { 'lat' : 36.12345, 'long' : -75.234564 }, 
     'point' : { 'lat' : 36.34567, 'long' : -75.345678 } 
    } 

這是我迄今爲止嘗試過的。它似乎增加了初始點,但在第一個點之後沒有增加額外點。

BasicDBObject query = new BasicDBObject(); 
query.put("bssid", pkt[1]); 
DBCursor cursor = coll.find(query); 

if (!cursor.hasNext()) { 
    // Document doesnt exist so create one 
    BasicDBObject document = new BasicDBObject(); 
    document.put("bssid", pkt[1]); 
    BasicDBObject heardpoints = new BasicDBObject(); 
    BasicDBObject point = new BasicDBObject(); 
    point.put("lat", latitude); 
    point.put("long", longitude); 
    heardpoints.put("point", point); 
    document.put("heardpoints", heardpoints); 
    coll.insert(document); 
} else { 
    // Document exists so we will update here 
    DBObject network = cursor.next(); 
    BasicDBObject heardpoints = new BasicDBObject(); 
    BasicDBObject point = new BasicDBObject(); 
    point.put("lat", latitude); 
    point.put("long", longitude); 
    heardpoints.put("point", point); 
    network.put("heardpoints", heardpoints); 
    coll.save(network); 
} 

我覺得我在這一點上的保留路上。任何支持都會有所幫助,非常感謝!

UPDATE 我正在使用upsert建議,但仍有一些問題。毫無疑問,這會對我有用,我只是沒有正確地做。我還沒有得到任何新的點,增加了第一個點。

BasicDBObject query = new BasicDBObject("bssid", pkt[1]); 
System.out.println(query); 
DBCursor cursor = coll.find(query); 
System.out.println(cursor); 

try { 
    DBObject network = cursor.next(); 
    System.out.println(network); 


    network.put("heardpoints", new BasicDBObject("point", 
      new BasicDBObject("lat", latitude) 
      .append("long", longitude))); 

    coll.update(query, network, true, false); 
} catch (NoSuchElementException ex) { 
    System.err.println("mongo error"); 
} finally { 
    cursor.close(); 
} 
+0

make it,coll.update(query,network,true,true); – BatScream 2014-11-05 01:15:12

+0

@BatScream當你正確地閱讀實現時,它並不是簡單的「只使用upserts」。有幾種處理這種數據的方法。同時也指出了OP在這裏做什麼的主要問題,只是提出了「upsert」的建議。 – 2014-11-05 02:19:33

+0

@neil lunn-hmm。是的,你是對的。我剛剛閱讀了問題的最後部分,並留下了評論。我的錯。 – BatScream 2014-11-05 04:32:38

回答

5

你有兩種方法可以解決這個問題,這取決於你真正想要如何使用數據。無論哪種情況,首先要解決的就是你的「理想數據結構」,而且主要是因爲它是無效的。這是錯誤的部分:

'heardpoints' : { 
     'point' : { 'lat' : 36.12345, 'long' : -75.234564 }, 
     'point' : { 'lat' : 36.34567, 'long' : -75.345678 } 
    } 

所以這個「散列/圖」是無效的,因爲你有同樣的「鑰匙」命名兩次。你不能做到這一點,你可能想,當你想,你必須使用地理空間查詢後的希望「陣列」,而不是,以及東西:

陣手法

"heardpoints": [ 
    { 
     "geometry": { 
      "type": "Point", 
      "coordinates": [-75.234564, 36.12345 ] 
     }, 
     "time": ISODate("2014-11-04T21:09:18.437Z") 
    }, 
    { 
     "geometry": { 
      "type": "Point", 
      "coordinates": [ -75.345678, 36.34567 ] 
     }, 
     "time": ISODate("2014-11-04T21:10:28.919Z") 
    } 
] 

和正確的按照如何遵循MongoDB和GeoJSON規範的順序排列「lon」和「lat」。

現在,這是針對您要將所有「聽過的數據」保存在每個「bssid」值的「單個文檔」中的表單,每個位置都保存在一個數組中。請注意,除非在第一個創建實例中,否則它本身並不一定是"upsert"。主要目的是「更新」相同的「bssid」值文件。就在現在與Java語法翻譯後殼形式:

db.collection.update(
    { "bssid": "ca:fe:de:ad:be:ef" }, 
    { 
     "$setOnInsert": { "channel": 6 }, 
     "$push": { 
      "heardpoints": { 
       "$each": [{ 
        "geometry": { 
         "type": "Point", 
         "coordinates": [-75.234564, 36.12345 ] 
        }, 
        "time": ISODate("2014-11-04T21:09:18.437Z") 
       }], 
       "$sort": { "time": -1 }, 
       "$slice": 20 
      } 
     } 
    }, 
    { "upsert": true } 
); 

不管是什麼語言和API表示,基本上有部分到MongoDB的更新操作。本質上這:

[ <Query>, <Update> ] 

根據API呈現有技術上的「三」部分,其中第三是Options但上的「更新插入」選項的基本考慮,理解是很重要的是如何既QueryUpdate文檔部分在更新操作中處理。

適用於Update文件最重要的是它有兩種形式。如果您只是在標準對象形式中提供「鍵」和「值」,則提供的內容將「覆蓋」匹配文檔中的任何現有內容。另一種形式(將在所有示例中使用)是使用"update operators",其允許文檔的「部分」被修改或「增強」。這是重要的區別。但與例子。

在空白集或至少一個,其中所指定的「BSSID」值不存在,則一個新的文檔將被創建包含「BSSID」字段值。此外還有一些其他行爲將會發生。

有一個特殊的「更新操作」,在這裏叫$setOnInsert。就像在聲明的Query部分規定的條件,這裏所提到的任何字段和值只插入一個「新」文檔時,「創造」在文檔中。因此,如果找到與查詢條件匹配的文檔,那麼這裏沒有任何操作實際執行來更改找到的文檔。這是一個設置初始值的好地方,也可以將文檔上的寫入活動限制在需要的地方。

Update文檔中的第二部分是另一個稱爲$push的「更新運算符」。正如計算語言中的常見術語所預期的那樣,這「將項目」添加到「數組」中。所以在創建文檔時,會創建一個新數組,並將這些項添加或添加到查找文檔中的「現有」數組內容中。

這裏有一些有趣的改性劑有自己的目的。 $each是一種修飾符,允許一次將多個項目發送給像$push這樣的操作員。我們僅將它用於單個項目,但通常與我們感興趣的其他兩個修改器一起使用它。

接下來是$sort,它應用於文檔中存在的數組元素以「排序「他們的條件。在這種情況下,數組元素上有一個「時間」字段,所以「排序」可以確保在添加新元素時,數組的內容總是有序的,這樣「最新」條目總是在陣列。

最後有$slice這是補充$sort通過本質上指定一個數組「封頂量」。因此,爲了確保文檔不會太大,修改器將在「修改器」之後應用修改器,該修改器已完成它的工作,然後「移除」超出指定「最大」條目的任何條目,並維護該數字的「最大」長度。非常有用的功能。

當然,如果您不關心「時間」值,那麼還有另一種方法來處理這種情況,以便「座標」數據僅保留爲「獨特」組合。這種方式是使用$addToSet運營商管理陣列或自行「設定」條目:

db.collection.update(
    { "bssid": "ca:fe:de:ad:be:ef" }, 
    { 
     "$setOnInsert": { "channel": 6 }, 
     "$addToSet": { 
      "heardpoints": { 
       "$each": [{ 
        "geometry": { 
         "type": "Point", 
         "coordinates": [-75.234564, 36.12345 ] 
        } 
       }] 
      } 
     } 
    }, 
    { "upsert": true } 
); 

現在實際上並不需要$each修改,但它只是留在那裏未來點。 $addToSet本質上看着現有的數組內容,並比較它做你提供的元素。如果這一數據不準確比賽的東西已經存在的數組,那麼它被添加到「設置」的。否則,由於數據已經存在,因此沒有任何反應。

所以,如果你只是想收集數據的具體點,他們不同,那麼這是一個很好的方法。但是有一個「捕捉」,其實是值得一提的一對夫婦。

假設您只想保留前面提到的20個條目。雖然$addToSet支持$each修改,不幸的是,其他改性劑如$slice不被支持。所以,你不能「保持帽」有一個更新語句,你會實際上有爲了實現這一頒發的「二」更新操作:

db.collection.update(
    { "bssid": "ca:fe:de:ad:be:ef" }, 
    { 
     "$setOnInsert": { "channel": 6 }, 
     "$addToSet": { 
      "heardpoints": { 
       "$each": [{ 
        "geometry": { 
         "type": "Point", 
         "coordinates": [-75.234564, 36.12345 ] 
        } 
       }] 
      } 
     } 
    }, 
    { "upsert": true } 
); 

db.collection.update(
    { "bssid": "ca:fe:de:ad:be:ef" }, 
    { 
     "$setOnInsert": { "channel": 6 }, 
     "$push": { 
      "heardpoints": { 
       "$each": [], 
       "$slice": 20 
      } 
     } 
    } 
) 

但即便如此,我們在這裏有一個新的問題。除了現在計算在「兩個」操作中,保持這個上限還有另一個問題,基本上是一個「集合」是以任何方式「不是有序的」。因此,您可以使用第二次更新限制列表中的項目總數,但無法刪除「最老」的項目。

爲了做到這一點,那麼你需要一個「時間」字段的「最後更新」,但是有再次捕捉。一旦你提供了一個「時間」值,那麼使得「設置」的「不同數據」不再是真實的。一個$addToSet操作認爲,下列因素是兩個「不同」條目各個領域,而不僅僅是「協調」的數據被認爲是:

"heardpoints": [ 
    { 
     "geometry": { 
      "type": "Point", 
      "coordinates": [-75.234564, 36.12345 ] 
     }, 
     "time": ISODate("2014-11-04T21:09:18.437Z") 
    }, 
    { 
     "geometry": { 
      "type": "Point", 
      "coordinates": [-75.234564, 36.12345 ] 
     }, 
     "time": ISODate("2014-11-04T21:10:28.919Z") 
    } 
] 

凡意圖是在現有的只是「更新時間」指向給定的座標,那麼你需要採取不同的方法。但是,這又是兩次更新,反過來,你會嘗試先更新一個文件,然後做一些其他的事情,如果沒有成功。意義上的「更新插入」的嘗試是第二操作:

var result = db.collection.update(
    { 
     "bssid": "ca:fe:de:ad:be:ef", 
     "heardpoints.geometry.coordinates": [-75.234564, 36.12345 ] 
    }, 
    { 
     "$set": { 
      "heardpoints.$.time": ISODate("2014-11-04T21:10:28.919Z") 
     } 
    } 
); 

// If result did not match and modify anything existing then perform the upsert 
if () { 

    db.collection.update(
     { "bssid": "ca:fe:de:ad:be:ef" }, // just this key and not the array 
     { 
      "$setOnInsert": { "channel": 6 }, 
      "$push": { 
       "heardpoints": { 
        "$each": [{ 
         "geometry": { 
          "type": "Point", 
          "coordinates": [-75.234564, 36.12345 ] 
         }, 
         "time": ISODate("2014-11-04T21:09:18.437Z") 
        }], 
        "$sort": { "time": -1 }, 
        "$slice": 20 
       } 
      } 
     }, 
     { "upsert": true } 
    ); 

} 

所以其中一個試圖「更新」現有陣列由第一查詢爲那個位置條目2個sepations。第一個操作不能是upsert,因爲它會創建一個具有相同「bssid」和未找到的數組條目的新文檔。如果可以的話,但這不允許positional $運算符使用找到的元素的匹配位置,以便可以通過$set運算符更改該元素。

在Java調用有返回一個WriteResult類型的可以這樣使用:

WriteResult writeResult = collection.update(query1, update1, false, false); 

    if (writeResult.getN() == 0) { 
     // Upsert would be tried if the array item was not found 
     writeResult = collection.update(query2, update2, true, false); 
    } 

如果沒有更新的東西,然後序列化的內容是這樣的:

{ "serverUsed" : "192.168.2.3:27017" , "ok" : 1 , "n" : 0 , "updatedExisting" : true} 

這意味着你基本上嵌套n值來查看發生了什麼,並根據查詢匹配該數組項的位置來決定是「更新」數組項還是「推送」一個新項。


文檔方法

總的結論從上面的是要保持不同的數據爲「座標」,只是修改了「時間」項,則上述過程會導致混亂。這些操作並不是理想的原子,雖然可以進行一些調整,但它可能不適合大批量更新。

這是一種情況,其邏輯是「移除」數組存儲,然後將每個不同的「點」存儲在其自己的文檔中,並將相關的「bssid」字段存儲。這簡化了是否更新或「插入」新操作模型的情況。集合中的文檔現在看起來是這樣的:

 { 
     "bssid": "ca:fe:de:ad:be:ef", 
     "channel": 6, 
     "geometry": { 
      "type": "Point", 
      "coordinates": [-75.234564, 36.12345 ] 
     }, 
     "time": ISODate("2014-11-04T21:09:18.437Z") 
    }, 
    { 
     "bssid": "ca:fe:de:ad:be:ef", 
     "channel": 6, 
     "geometry": { 
      "type": "Point", 
      "coordinates": [ -75.345678, 36.34567 ] 
     }, 
     "time": ISODate("2014-11-04T21:10:28.919Z") 
    } 

的不同之處在自己的收藏和數組下在同一文件中未綁定。有數據複製,但「升級」過程現在簡單得多:

db.collection.update(
    { 
     "bssid": "ca:fe:de:ad:be:ef", 
     "geometry": { 
      "type": "Point", 
      "coordinates": [-75.234564, 36.12345 ] 
     } 
    }, 
    { 
     "$setOnInsert": { "channel": 6 }, 
     "$set": { "time": ISODate("2014-11-04T21:10:28.919Z") } 
    } 
    { "upsert": true } 
) 

和所有的確實將匹配基於所提供的「BSSID」和「點」值不是「更新」「時間的文件「它匹配的位置,或者只是插入一個新文檔,其中包含未找到該」bssid「和」point「數據的所有值。


整體的情況是,其中這開始了與簡單的需求,這是細到「嵌入」陣列到陣列,保持更復雜的需求可以是一個可能的疼痛使用該存儲形式。另一方面,在集合中使用單獨的文檔有一方面的好處,但是您必須自己做一些工作來「清理」超出您可能想要的任何上限的條目。但可能不一定需要成爲「實時」操作是有爭議的。

不同的方法,所以與最適合你的方法一起工作。這只是以任何方式實施並顯示缺陷和解決方案的指南。什麼最適合你,只有你可以告訴。

這實際上比特定的Java編碼更關注技術。這一部分並不難,所以這裏只是上面最難以參考的部分結構:

DBObject update = new BasicDBObject(
     "$setOnInsert", new BasicDBObject(
      "channel", 6 
     ) 
    ).append(
     "$push", new BasicDBObject(
      "heardpoints", new BasicDBObject(
       "$each", new DBObject[]{ 
        new BasicDBObject(
         "geometry", 
         new BasicDBObject("type","Point").append(
          "coordinates", new double[]{-75.234564, 36.12345} 
         ) 
        ).append(
         "time", new DateTime(2014,1,1,0,0,DateTimeZone.UTC).toDate() 
        ) 
       } 
      ).append(
       "$sort", new BasicDBObject(
        "time", -1 
       ) 
      ).append("$slice", 20) 
     ) 
    ); 
+0

哇,我只是被你的迴應吹走了。我從你身上學到的東西比花在梳理網絡上的時間多。我必須同意並傾向於文檔方法。稍後,我想只需查詢來自特定BSSID的所有聽到的點就會容易得多。雖然我確實幾乎立即使用陣列設計來運行它。我將與他們一起比賽,看看哪種效果最好,但我有一種感覺,即文檔模型最終會成爲贏家!感謝您的辛勤工作,我會經常回顧一下!再次感謝! – pyRabbit 2014-11-05 03:04:59

+0

@pyRabbit對於使用MongoDB的人來說,最困難的事情之一很大程度上取決於數據的實際使用模式,以及對模型進行建模的最佳方式。這是值得詳細解釋的。另外,Java對象表示可能會變得多毛,因此一個複雜結構的例子也是值得的。與朋友分享,如果你覺得它有用。 – 2014-11-05 03:14:12

相關問題