2014-11-04 16 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

相关问题