如何检查MongoDB对象是否存在并分别创建/更新? - java

我正在开发一种使用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”选项的基本考虑,了解如何同时处理QueryUpdate文档部分非常重要在更新操作中。

应用于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=" …