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

时间:2014-11-04 20:49:54

标签: java mongodb mongodb-query

我正在开发一个使用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();
 }

1 个答案:

答案 0 :(得分:7)

您有两种方法可以解决这个问题,这取决于您实际想要如何使用数据。在任何一种情况下,要解决的第一件事是你理想的数据结构",主要是因为它是无效的。这是错误的部分:

  '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")
     }
]

正确订购" lon"和" lat"正如它所遵循的MongoDB和GeoJSON规范所做的那样。

现在,这是用于保存所有"听到数据的形式。在"单个文件" per" bssid"值,每个位置保存在一个数组中。请注意,除了第一个创建实例外,这本身并不一定是"upsert"本身。主要目的是"更新"相同的" bssid"价值文件。现在只是shell形式,稍后使用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的介绍,技术上有&#34;三&#34;第三部分是Options的部分,但基于&#34; upsert&#34;的基本考虑。选项,了解如何在更新操作中处理QueryUpdate文档部分非常重要。

应用于Update文档最重要的是它有两种形式。如果你只是提供&#34;键&#34;和&#34;价值观&#34;在标准对象形式中,然后提供的任何内容将覆盖&#34;覆盖&#34;匹配文档中的任何现有内容。另一种形式(将在所有示例中使用)是使用"update operators"允许&#34;部分&#34;要修改的文件或&#34;增加&#34;。这是一个重要的区别。但是就这些例子而言。

在空白集合或至少一个指定的&#34; bssid&#34;如果值不存在,那么将创建一个包含&#34; bssid&#34;场价值。此外,还会发生一些其他行为。

有一个特殊的&#34;更新运算符&#34;在这里称为$setOnInsert。就像语句的Query部分中指定的条件一样,这里提到的任何字段和值都只是&#34;创建&#34;在一个&#34; new&#34;文件已插入。因此,如果找到与查询条件匹配的文档,则实际上不执行任何操作来更改找到的文档。这是设置初始值并将文档的写入活动限制在只需要它的字段的好地方。

Update文档中的第二部分是另一个&#34;更新运算符&#34;叫$push。正如计算语言中的常用术语所预期的那样,这个&#34;添加项目&#34;到#34;阵列&#34;。因此,在创建文档时,会创建一个新数组,并将项目追加或以其他方式添加到&#34;现有的&#34;找到的文档中的数组内容。

这里有一些有趣的修饰符,它们都有自己的用途。 $each是一个修饰符,允许一次将多个项目发送给$push这样的运算符。我们仅将它用于单个项目,但它通常需要与我们感兴趣的其他两个修饰符一起使用。

下一个是$sort,它应用于文档中存在的数组元素,以便排序&#34;排序&#34;他们的条件。在这种情况下,有一个&#34;时间&#34;数组元素上的字段,所以&#34;排序&#34;确保在添加新元素时,始终对数组的内容进行排序,以便最新的&#34;最新的&#34;条目总是在数组的前面。

最终有$slice补充 $sort ,主要指定&#34;上限金额&#34;对于阵列。因此,只是为了确保文档永远不会变得太大, $slice 修饰符,它将被应用&#34;&#34; $sort 修饰符完成了它的工作然后&#34;删除&#34;任何超出指定&#34;最大值的条目&#34;条目,并保持&#34;最大&#34;这个数字的长度。这是一个非常有用的功能。

当然,如果你不关心&#34;时间&#34;价值然后有另一种方法来处理这个,以便&#34;坐标&#34;数据仅保留为&#34;唯一的&#34;组合。这种方式是使用 $addToSet 运算符来管理数组或&#34; set&#34;条目本身:

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基本上查看现有的数组内容,并将它与您提供的元素进行比较。如果该数据完全匹配数组中已存在的内容,则将其添加到&#34; set&#34;。否则,没有任何反应,因为数据已经存在。

因此,如果您只是希望针对特定点收集的数据发生变化,那么这是一种很好的方法。但是有一个&#34; catch&#34;,实际上还有一对值得一提。

假设您只想保留前面提到的20个条目。虽然 $addToSet 支持 $each 修饰符,但不幸的是,不支持其他修饰符,例如 $slice 。所以你不能保持一个上限&#34;使用单个更新声明,您实际上必须发布&#34;两个&#34;更新操作以实现此目的:

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

但即便如此,我们在这里遇到了一个新问题。除了现在计入&#34;两个&#34;操作,保持这个上限有另一个问题,这基本上是一个&#34;设置&#34;是&#34;没有订购&#34;以任何方式。因此,您可以使用第二次更新来限制列表中的项目总数,但是无法删除最早的&#34;例如。

为了做到这一点,你需要一个&#34;时间&#34; &#34;最后更新&#34;的字段,但是还有一个问题。一旦你提供了&#34;时间&#34;价值然后&#34;不同的数据&#34;这使得&#34;设置&#34;不再是真的。 $addToSet 操作会将以下内容视为两个&#34;不同的&#34;条目作为所有字段而不仅仅是&#34;坐标&#34;数据被认为是:

 "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")
     }
]

意图是“更新时间”#34;在给定坐标处的现有点上,您需要采用不同的方法。但是这又是两个更新,反过来,你首先尝试更新文档,然后如果不成功则执行其他操作。意思是&#34; upsert&#34;尝试是第二次操作:

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

}

所以有两个人试图&#34;更新&#34;首先查询该位置的现有数组条目。第一个操作不能是一个upsert,因为它会创建一个具有相同&#34; bssid&#34;和未找到的数组条目。如果可能的话,但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值以查看发生的情况并决定是否更新&#34;数组项目或&#34;推送&#34;一个新的,取决于查询匹配该数组项的位置。


文件方法

以上的一般结论是,您希望为&#34;坐标&#34;保留不同的数据。并且只需修改&#34;时间&#34;进入然后上面的过程会变得凌乱。这些操作不是理想的原子操作,虽然可以进行一些调整,但它可能不适合大批量更新。

这是一个逻辑是&#34;删除&#34;数组存储,然后存储每个不同的&#34;点&#34;在它自己的相关&#34; bssid&#34;领域。这简化了是否更新或&#34;插入&#34;一个新的单一操作模型。该集合中的文档现在看起来像这样:

     { 
         "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")
     }

在自己的集合中不同,不绑定在数组下的同一文档中。有数据重复,但&#34;更新&#34;过程现在大大简化了:

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

所有这一切都将与基于所提供的&#34; bssid&#34;的文档相匹配。和&#34;指向&#34;值&#34;更新&#34; &#34;时间&#34;在哪里匹配或只是插入一个新文档,所有值都在那里&#34; bssid&#34;和&#34;指向&#34;找不到数据。


总体情况是,这种情况从简单的需求开始,并且可以很好地嵌入&#34;将数组放入数组中,维护更复杂的需求可能会成为使用该存储形式的痛苦。另一方面,在集合中使用单独的文档会在一方面带来好处,但是你必须做自己的工作来清理&#34;超出您可能想要的上限限制的条目。但有争议的可能不一定需要是一个&#34;实时&#34;操作

采用不同的方法,因此请选择最适合您的方法。这只是以任何一种方式实施并显示缺陷和解决方案的指南。什么最适合你,只有你可以告诉。

这实际上更多的是关于技术而不是特定的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)
        )
    );