我正在开发一种使用Java(Swing GUI)和MongoDB数据存储解决方案构建的无线网络调查工具。我是MongoDB的新手,几乎不是Java专家,因此我需要一些帮助。我想查找数据库中是否存在网络,并将听到的点附加到网络文档中。如果网络不存在,我想为该网络创建一个文档并添加听到的观点。我已经尝试了好几天来解决这个问题,但是似乎无法解决这个问题。另外,如果BSSID是唯一ID,那也很好,这样我就不会得到任何重复的网络。我理想的数据结构如下所示:
{ '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);
}
我觉得我对这个保留意见很遥远。任何支持都会有所帮助,非常感谢!
更新
我正在使用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();
}
参考方案
您有两种方法可以真正解决此问题,这取决于您实际如何使用数据。无论哪种情况,首先要解决的是您的“理想数据结构”,主要是因为它无效。这是错误的部分:
'heardpoints' : {
'point' : { 'lat' : 36.12345, 'long' : -75.234564 },
'point' : { 'lat' : 36.34567, 'long' : -75.345678 }
}
因此,此“哈希/映射”无效,因为您具有两次命名的相同“密钥”。您无法执行此操作,而可能需要使用“数组”,以及希望稍后在以下位置使用GeoSpatial查询的某些操作:
阵列法
"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”值的所有“ hearddata”保存在“单个文档”中的表格,每个位置都保存在一个数组中。注意,除了第一个创建实例外,这实际上并不一定是"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
,但基于对“ upsert”选项的基本考虑,了解如何同时处理Query
和Update
文档部分非常重要在更新操作中。
应用于Update
文档的最重要的事情是它具有两种形式。如果仅以标准对象形式提供“键”和“值”,则所提供的内容将“覆盖”匹配文档中的任何现有内容。另一种形式(将在所有示例中使用)是使用"update operators",它允许修改或“扩充”文档的“部分”。这是重要的区别。但是上面的例子。
在空白集合或至少一个不存在指定的“ bssid”值的集合上,然后将创建一个包含该“ bssid”字段值的新文档。此外,还会发生其他一些行为。
这里有一个特殊的“更新运算符”,称为$setOnInsert
。就像语句的Query
部分中指定的条件一样,此处插入的任何字段和值仅在插入“新”文档时才在文档中“创建”。因此,如果找到与查询条件匹配的文档,则实际上不会执行此处的任何操作来更改找到的文档。这是设置初始值的好地方,也可以将对文档的写活动限制为仅需要它的字段。
Update
文档的第二部分是另一个称为$push
的“更新运算符”。正如计算语言中的通用术语所期望的那样,这将“添加项目”到“数组”中。因此,在创建文档时,将创建一个新的数组,并将这些项目附加或以其他方式添加到找到的文档中的“现有”数组内容中。
这里有一些有趣的修饰符,它们有各自的用途。 $each
是一种修饰符,它允许一次将多个项目发送给像$push
这样的运算符。我们仅将其用于单个项目,但通常将它与我们感兴趣的其他两个修饰符一起使用。
下一个是$sort
,它应用于文档中存在的数组元素,以便按条件对它们进行“排序”。在这种情况下,数组元素上有一个“时间”字段,因此“排序”可确保在添加新元素时始终对数组的内容进行排序,以便“最新”条目始终位于数组的开头。数组。
最后是$slice
,它通过为数组指定“上限”来补充$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")
}
]
如果要在给定坐标上仅“更新”现有点上的时间,则需要采用其他方法。但是,这又是两次更新,相反,您尝试先更新文档,然后再执行其他操作(如果未成功)。意味着“ upsert”尝试是第二个操作:
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 }
);
}
因此,有两个分离,其中一个试图通过首先查询该位置来“更新”现有阵列条目。该第一个操作不能成为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”和“ point”值匹配所有文档,或者“更新”匹配的“时间”,或者仅插入具有“ 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)
)
);
Java Double与BigDecimal - java我正在查看一些使用双精度变量来存储(360-359.9998779296875)结果为0.0001220703125的代码。 double变量将其存储为-1.220703125E-4。当我使用BigDecimal时,其存储为0.0001220703125。为什么将它双重存储为-1.220703125E-4? 参考方案 我不会在这里提及精度问题,而只会提及数字…
当回复有时是一个对象有时是一个数组时,如何在使用改造时解析JSON回复? - java我正在使用Retrofit来获取JSON答复。这是我实施的一部分-@GET("/api/report/list") Observable<Bills> listBill(@Query("employee_id") String employeeID); 而条例草案类是-public static class…
Java-父类正在从子类中调用方法? - java抱歉,我还是编码的新手,可能还没有掌握所有术语。希望您仍然能理解我的问题。我想得到的输出是:"Cost for Parent is: 77.77" "Cost for Child is: 33.33" 但是,我得到这个:"Cost for Parent is: 33.33" "Cost f…
Java Map,如何将UTF-8字符串正确放置到地图? - java我有一个地图,LinkedHashMap更确切地说。我想在上面放一个字符串对象。然后,我读取此值以查看实际存储的内容。字符串本身具有非ASCII字符(西里尔文,韩文等)。将其放到地图上然后阅读后,这些字符将替换为??? s。一些代码:Map obj = new LinkedHashMap(); System.out.println("name: &…
在小数点后三位显示秒。 -Java - java我在Java程序中使用毫秒,并将其转换为秒。我的方法执行完此操作后,它将以长格式返回秒。System.out.println("[" + threadID + "]" + " " + "SeqSum res=" + grandTotal + " secs=" …