美文网首页程序员阿里云
MongoDB-第三章-创建、更新和删除文档

MongoDB-第三章-创建、更新和删除文档

作者: weipeng2k | 来源:发表于2019-10-07 11:37 被阅读0次

创建、更新和删除文档

    使用一款数据库,最主要的就是对其(关系或集合)进行增删改查,接下来就介绍如何在MongoDB中进行数据的新增、删除和修改,以及这些操作在MongoDB中的特点。

插入文档

    通过使用insert命令可以向一个集合中插入新的文档,对于新增的文档,MongoDB客户端会生成一个_id

> item = {"name":"murdock", "age":18}
> db.foo.insert(item)
> show collections
author_test_collection
blog_test_collection
foo
mongo_task_instance_collection
mongo_test_collecion
mongo_test_collection

    首先定义了一个对象item,随后通过insert命令将其插入到集合foo中。

如果集合不存在,MongoDB会新建立一个集合。

通过show collections可以查看当前数据库中的所有集合。

    在Java中,我们可以使用 spring-data 来进行文档的插入。

@Test
public void insert() {
    DBObject dbObject = new BasicDBObject();
    dbObject.put("name", "test");
    dbObject.put("age", 18);
    mongoTemplate.getCollection("foo").insert(dbObject);
}

    通过构建DBObject,向其设置值,并将其插入到文档中。DBObject类似Map。和关系数据库类似,对于插入,MongoDB也提供了批量新增的方式,也可以使用insert方法进行。

> items = [{"name":"murdock", "age":19}, {"name":"john", "age":20}]
[
    {
        "name" : "murdock",
        "age" : 19
    },
    {
        "name" : "john",
        "age" : 20
    }
]
> db.foo.insert(items)
BulkWriteResult({
    "writeErrors" : [ ],
    "writeConcernErrors" : [ ],
    "nInserted" : 2,
    "nUpserted" : 0,
    "nMatched" : 0,
    "nModified" : 0,
    "nRemoved" : 0,
    "upserted" : [ ]
})
> db.foo.find()
{ "_id" : ObjectId("5cfcf12a6c1c3f471c95190c"), "name" : "murdock", "age" : 18 }
{ "_id" : ObjectId("5cfcfba16c1c3f471c95190d"), "name" : "murdock", "age" : 19 }
{ "_id" : ObjectId("5cfcfba16c1c3f471c95190e"), "name" : "john", "age" : 20 }

    可以看到对于insert方法,传入了一个数组,MongoDB可以按照数组内容进行插入,好处在于一次通信交互,可以新增多个文档。

和MongoDB之间的消息长度不能超过16MB,所以对于批量新增,也需要做好单次容量的估算。

对于Java端的操作可以参考:com.murdock.books.mongodbguide.chapter3.InsertTest

插入的原理

    在InsertTest中可以使用如下代码对foo集合中进行插入。

@Test
public void insert() {
    DBObject dbObject = new BasicDBObject();
    dbObject.put("name", "test");
    dbObject.put("age", 18);
    mongoTemplate.getCollection("foo").insert(dbObject);
}

    这里使用的是MongoDB的Java驱动,当进行insert时,如果没有对应的集合时,MongoDB就会创建对应的集合。前面提到_id字段,这个是每个文档都会具备的,而例子中进行插入的数据,没有设置_id字段。MongoDB会检查插入的文档,当文档中没有包含_id,MongoDB客户端会生成一个。

public WriteResult insert(final List<? extends DBObject> documents, final InsertOptions insertOptions) {
    WriteConcern writeConcern = insertOptions.getWriteConcern() != null ? insertOptions.getWriteConcern() : getWriteConcern();
    Encoder<DBObject> encoder = toEncoder(insertOptions.getDbEncoder());

    List<InsertRequest> insertRequestList = new ArrayList<InsertRequest>(documents.size());
    for (DBObject cur : documents) {
        if (cur.get(ID_FIELD_NAME) == null) {
            cur.put(ID_FIELD_NAME, new ObjectId());
        }
        insertRequestList.add(new InsertRequest(new BsonDocumentWrapper<DBObject>(cur, encoder)));
    }
    return insert(insertRequestList, writeConcern, insertOptions.isContinueOnError(), insertOptions.getBypassDocumentValidation());
}

com.mongodb.DBCollection#insert(java.util.List<? extends com.mongodb.DBObject>, com.mongodb.InsertOptions)

    可以看到,文档中没有_id属性,MongoDB客户端就会生成一个。可以看一下ObjectId的构造函数,以及相关的处理逻辑。

buffer.put(int3(timestamp));
buffer.put(int2(timestamp));
buffer.put(int1(timestamp));
buffer.put(int0(timestamp));
buffer.put(int2(machineIdentifier));
buffer.put(int1(machineIdentifier));
buffer.put(int0(machineIdentifier));
buffer.put(short1(processIdentifier));
buffer.put(short0(processIdentifier));
buffer.put(int2(counter));
buffer.put(int1(counter));
buffer.put(int0(counter));

    对于默认的ObjectId的生成,一个12bytes的主键,前4个byte是时间戳,后三个是机器标示(一般是网卡相关的信息),接下来是进程标示,最后是随机生成的3个byte。

由于timestamp的单位是秒,那么在一个进程中进行区分的就是counter部分的3个byte,而3个byte的数据内容是:16777216,一千六百多万。可以说在一秒内,对于一个进程内容,如果不超过一千六百万的TPS,就不会有问题。事实上,现实中不会有这么高的TPS,因为单进程这么高的TPS已经让网卡都被打满。

删除文档

    通过使用remove命令,可以完成对集合中文档的删除动作。

> db.foo.find()
{ "_id" : ObjectId("5d0638e1afe8102aeab43b52"), "name" : "test", "age" : 18 }
{ "_id" : ObjectId("5d0638e1afe8102aeab43b53"), "name" : "test", "age" : 19 }
{ "_id" : ObjectId("5d0638e1afe8102aeab43b54"), "name" : "test", "age" : 20 }
{ "_id" : ObjectId("5d0638e1afe8102aeab43b55"), "name" : "test", "age" : 21 }
{ "_id" : ObjectId("5d0638e1afe8102aeab43b56"), "name" : "test", "age" : 22 }
{ "_id" : ObjectId("5d0638e1afe8102aeab43b57"), "name" : "test", "age" : 23 }
{ "_id" : ObjectId("5d0638e1afe8102aeab43b58"), "name" : "test", "age" : 24 }
{ "_id" : ObjectId("5d0638e1afe8102aeab43b59"), "name" : "test", "age" : 25 }
{ "_id" : ObjectId("5d0638e1afe8102aeab43b5a"), "name" : "test", "age" : 26 }
{ "_id" : ObjectId("5d0638e1afe8102aeab43b5b"), "name" : "test", "age" : 27 }
{ "_id" : ObjectId("5d0638e1afe8102aeab43b5c"), "name" : "test", "age" : 28 }
{ "_id" : ObjectId("5d0638e1afe8102aeab43b5d"), "name" : "test", "age" : 29 }
> db.foo.remove({"age" : {"$gte":19}})
WriteResult({ "nRemoved" : 11 })
> db.foo.find()
{ "_id" : ObjectId("5d0638e1afe8102aeab43b52"), "name" : "test", "age" : 18 }

对于Java端的操作可以参考:com.murdock.books.mongodbguide.chapter3.RemoveTest

    使用MongoDB的Java客户端,进行删除文档的关键逻辑如下:

DBObject ageGte = new BasicDBObject();
ageGte.put("$gte", 19);

DBObject removeQuery = new BasicDBObject();
removeQuery.put("age", ageGte);

WriteResult writeResult = mongoTemplate.getCollection("foo").remove(removeQuery);
System.out.println("remove:" + writeResult.getN());
Assert.assertTrue(writeResult.getN() >= 11);
System.out.println("count:" + mongoTemplate.getCollection("foo").count());

    可以看到通过DBObject可以构造出remove方法需要的参数,其中$gte是一个操作,代表大于等于一个值。

更新文档

    通过使用update命令,可以完成对集合中文档的修改动作。

> db.foo.find()
{ "_id" : ObjectId("5d0dfb1becfa8c49ce3fb2b5"), "name" : "test", "age" : 18 }
> var f = {"name" : "test", "age" : 18}
> f
{ "name" : "test", "age" : 18 }
> f.level = "high"
high
> f
{ "name" : "test", "age" : 18, "level" : "high" }
> db.foo.update({"name":"test"}, f)
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.foo.find()
{ "_id" : ObjectId("5d0dfb1becfa8c49ce3fb2b5"), "name" : "test", "age" : 18, "level" : "high" }

对于Java端的操作可以参考:com.murdock.books.mongodbguide.chapter3.UpdateTest

    使用MongoDB的Java客户端,进行更新文档的关键逻辑如下:

DBObject dbObject = new BasicDBObject();
dbObject.put("name", "test");
dbObject.put("level", "high");
dbObject.put("age", 20);

DBObject query = new BasicDBObject();
query.put("name", "test");

WriteResult result = mongoTemplate.getCollection("foo").update(query, dbObject);
System.out.println(result.getN() >= 1);

     以上的修改方式,属于覆盖式修改,但是我们对于文档的修改,往往是集中在某个属性。针对部分属性的修改,可以使用以下方式。

$SET

    由于默认的Update是全部覆盖,当需要为指定的文档添加一个字段,就需要使用set进行操作。

> db.foo.findOne()
{
    "_id" : ObjectId("5d6bb09124b8e520faedc0aa"),
    "name" : "test",
    "age" : 18,
    "sex" : "male"
}
> db.foo.update({"name":"test"}, {"$set":{"sex":"female"}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.foo.findOne()
{
    "_id" : ObjectId("5d6bb09124b8e520faedc0aa"),
    "name" : "test",
    "age" : 18,
    "sex" : "female"
}

    可以看到使用$set,将sex属性从male改为了female。

对于Java端的操作可以参考:com.murdock.books.mongodbguide.chapter3.UpdateTest

    使用MongoDB的Java客户端,进行更新文档的关键逻辑如下:

DBObject update = new BasicDBObject();
DBObject prop = new BasicDBObject();

prop.put("sex", "male");
update.put("$set", prop);
mongoTemplate.getCollection("foo").update(query, update);

    使用$set进行更新,当文档没有对应的key时会添加,有的话,则修改。

$INC

    针对文档中的数字类型,可以使用增加inc进行操作,将一个文档中的数字属性进行增加或者减少(负数)。

> db.foo.findOne()
{
    "_id" : ObjectId("5d0e0dceecfa8c49ce3fb2b7"),
    "name" : "test",
    "age" : 18,
    "level" : "high"
}
> db.foo.update({"name":"test"} , {"$inc":{"age":1}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.foo.update({"name":"test"} , {"$inc":{"age":1}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.foo.findOne()
{
    "_id" : ObjectId("5d0e0dceecfa8c49ce3fb2b7"),
    "name" : "test",
    "age" : 20,
    "level" : "high"
}

    可以看到使用$inc,将age从18变为20。

对于Java端的操作可以参考:com.murdock.books.mongodbguide.chapter3.UpdateTest

    使用MongoDB的Java客户端,进行更新文档的关键逻辑如下:

DBObject update = new BasicDBObject();
DBObject prop = new BasicDBObject();
prop.put("age", 1);
update.put("$inc", prop);

mongoTemplate.getCollection("foo").update(query, update);

    使用$inc进行更新,能够保证当前文档对应的属性更新是原子化的。

更新文档,数组属性

$PUSH

    MongoDB支持的数据类型除了:整型、浮点型、日期以及字符串这种单值类型的之外,还有数组类型。如果对于一个文档中的数组属性进行操作时,可以使用push进行操作。

该操作只能针对数组属性

> db.foo.update({"name":"test"}, {"$push":{"hobbies":"reading"}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.foo.findOne()
{
    "_id" : ObjectId("5d6bb09124b8e520faedc0aa"),
    "name" : "test",
    "age" : 18,
    "sex" : "female",
    "hobbies" : [
        "reading"
    ]
}
> db.foo.update({"name":"test"}, {"$push":{"hobbies":"tvgaming"}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.foo.findOne()
{
    "_id" : ObjectId("5d6bb09124b8e520faedc0aa"),
    "name" : "test",
    "age" : 18,
    "sex" : "female",
    "hobbies" : [
        "reading",
        "tvgaming"
    ]
}

    可以看到通过使用push将一个元素添加到对应的数组的尾部。

对于Java端的操作可以参考:com.murdock.books.mongodbguide.chapter3.ArrayUpdateTest

    使用MongoDB的Java客户端,进行更新文档的关键逻辑如下:

DBObject update = new BasicDBObject();
DBObject prop = new BasicDBObject();

prop.put("hobbies", "reading");
update.put("$push", prop);
mongoTemplate.getCollection("foo").update(query, update);

    使用$push进行数组元素的更新,当文档没有对应的数组key时会添加,并将元素添加到数组的尾部。

$ADDTOSET

    使用push命令可以向一个文档中的数组属性里添加内容,但是如果该内容已经在数组中出现,而本次添加是当元素在数组中不存在时才生效时,就需要使用addToSet

> db.foo.update({"name":"test"}, {"$addToSet":{"hobbies":"football"}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.foo.findOne()
{
    "_id" : ObjectId("5d6bc02d24b8e52357933ed0"),
    "name" : "test",
    "age" : 18,
    "hobbies" : [
        "reading",
        "tvgaming",
        "football"
    ]
}
> db.foo.update({"name":"test"}, {"$addToSet":{"hobbies":"football"}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 0 })
> db.foo.findOne()
{
    "_id" : ObjectId("5d6bc02d24b8e52357933ed0"),
    "name" : "test",
    "age" : 18,
    "hobbies" : [
        "reading",
        "tvgaming",
        "football"
    ]
}

    可以看到通过使用addToSet将一个元素以集合Set的视角添加入一个数组,当数组中存在该元素的时候,不会添加。

对于Java端的操作可以参考:com.murdock.books.mongodbguide.chapter3.ArrayUpdateTest

    使用MongoDB的Java客户端,进行更新文档的关键逻辑如下:

DBObject update = new BasicDBObject();
DBObject prop = new BasicDBObject();

prop.put("hobbies", "reading");
update.put("$addToSet", prop);
mongoTemplate.getCollection("foo").update(query, update);

$POP

    使用pop命令可以将一个文档中的数组属性里移出元素,就好比栈一样,调用pop就会将栈顶元素移出。可以使用{"$pop":{key:-1}}从尾部移出元素。

> db.foo.find()
{ "_id" : ObjectId("5d6bc02d24b8e52357933ed0"), "name" : "test", "age" : 18, "hobbies" : [ "reading", "tvgaming", "football", "tvgaming", "basketball" ] }
> db.foo.update({"name":"test"}, {"$pop":{"hobbies":-1}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.foo.find()
{ "_id" : ObjectId("5d6bc02d24b8e52357933ed0"), "name" : "test", "age" : 18, "hobbies" : [ "tvgaming", "football", "tvgaming", "basketball" ] }
> db.foo.update({"name":"test"}, {"$pop":{"hobbies":1}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.foo.find()
{ "_id" : ObjectId("5d6bc02d24b8e52357933ed0"), "name" : "test", "age" : 18, "hobbies" : [ "tvgaming", "football", "tvgaming" ] }
> db.foo.update({"name":"test"}, {"$pop":{"hobbies":-1}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.foo.find()
{ "_id" : ObjectId("5d6bc02d24b8e52357933ed0"), "name" : "test", "age" : 18, "hobbies" : [ "football", "tvgaming" ] }

    可以看到通过使用pop将一个数组中的一个元素移除。

对于Java端的操作可以参考:com.murdock.books.mongodbguide.chapter3.ArrayUpdateTest

    使用MongoDB的Java客户端,进行更新文档的关键逻辑如下:

DBObject pop = new BasicDBObject();
DBObject popKey = new BasicDBObject();
popKey.put("hobbies", -1);
pop.put("$pop", popKey);
mongoTemplate.getCollection("foo").update(query, pop);

也可以通过pull命名,指定的移出某个数组中的元素。

更新文档,高阶特性

$UPSERT

    在进行数据插入时,经常会遇到一个问题,就是如果主键存在则获取已经存在的数据,否则插入,每当这时候我们需要谨慎的处理,需要增加主键的约束,处理回滚的事务。MongoDB提供了upsert命令用来完成这个工作,且该命令是原子性的。

> db.foo.update({"name": "test"}, {"$inc": {"age": 1}}, true)
WriteResult({
    "nMatched" : 0,
    "nUpserted" : 1,
    "nModified" : 0,
    "_id" : ObjectId("5d9a89e540eabd2b62ced5e9")
})
> db.foo.find()
{ "_id" : ObjectId("5d9a89e540eabd2b62ced5e9"), "name" : "test", "age" : 1 }
> db.foo.update({"name": "test"}, {"$inc": {"age": 1}}, true)
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.foo.find()
{ "_id" : ObjectId("5d9a89e540eabd2b62ced5e9"), "name" : "test", "age" : 2 }

    可以看到通过在update的第三个参数传入true,可以将一个修改的命令变成一个upsert命令。

对于java端的操作可以参考:com.murdock.books.mongodbguide.chapter3.AdvancedUpdateTest

    使用MongoDB的Java客户端,进行更新文档的关键逻辑如下:

DBObject query = new BasicDBObject();
query.put("name", "test");

DBObject update = new BasicDBObject();
DBObject age = new BasicDBObject();
age.put("age", 1);
update.put("$inc", age);

WriteResult update1 = collection.update(query, update, true, false);

    如果多次通过update进行更新,可以看到第一次更新是进行数据的插入,而后续的更新都是针对该文档的更新。

FindAndModify

    当我们遇到并发更新一行数据时,在使用关系数据库时,经常使用乐观锁进行控制。比如:定义一个字段version,当更新时,需要传入查询出的最新version,也只有version和对应行记录中的version一致时,方可更新。这样是可以完成对于一行的并发更新,问题就是需要将这个version定义在每个需要并发更新的数据结构中。

    这种单行(文档)的并发更新,是一种常见的更新场景,MongoDB对于这种更新场景提供了更加简单的使用方式。

> db.runCommand({
... "findAndModify":"foo",
... "query":{"name": "test"},
... "update":{"$set":{"age":30}}
... })
{
    "lastErrorObject" : {
        "n" : 1,
        "updatedExisting" : true
    },
    "value" : {
        "_id" : ObjectId("5d9aa2d640eabd2b62ced639"),
        "name" : "test",
        "age" : 5
    },
    "ok" : 1
}
> db.foo.findOne()
{
    "_id" : ObjectId("5d9aa2d640eabd2b62ced639"),
    "name" : "test",
    "age" : 30
}

    findAndModify命令需要输入集合等相关的更新信息,对应的键和类型如下:

类型 描述
findAndModify 字符串 对应的集合名
query 文档 查询的文档,用来检索
sort 文档 排序的文档,用来对检索的结果进行排序
update 文档 修改的文档,用来修改
remove 布尔 是否删除文档
new 布尔 是否返回更新后的文档

    findAndModify只能对已经存在的文档进行更新,而且一次只能更新一行。相比较而言findAndModify的耗时相当于一次查询、一次更新和执行一次getLastError所需的时间。

对于java端的操作可以参考:com.murdock.books.mongodbguide.chapter3.AdvancedUpdateTest

    使用MongoDB的Java客户端,进行更新文档的关键逻辑如下:

DBObject query = new BasicDBObject();
query.put("name", "test");

DBObject update = new BasicDBObject();
DBObject age = new BasicDBObject();
age.put("age", 20);
update.put("$set", age);

DBObject andModify = collection.findAndModify(query, null, null, false, update, true, false);

相关文章

  • MongoDB-第三章-创建、更新和删除文档

    创建、更新和删除文档 使用一款数据库,最主要的就是对其(关系或集合)进行增删改查,接下来就介绍如何在MongoDB...

  • 3. 创建/更新和删除文档

    插入并保存文档 使用insert向目标集合插入文档下面代码向db所指向的数据库的foo集合中插入一个文档 使用ba...

  • 4、创建、更新和删除文档(MongoDB笔记)

    一、插入并保存文档 可以使用insert方法向目标集合插入一个文档: 这个操作会给文档自动增加一个"_id"键(要...

  • mongodb常用命令

    一、数据库 创建数据库 删除数据库 二、集合 创建集合 删除集合 三、文档 插入文档 删除文档 更新文档 四、查询...

  • Making queries(执行查询)

    一旦你创建了你的数据模型,Django会自动为你提供一个数据库抽取API,让你创建、检索、更新和删除对象。 本文档...

  • Elasticsearch 7.x 入门之restful请求(2

    使用restful操作 创建文档新建文档.png 查看文档文档信息.png 删除文档文档删除.png 新建文档自动...

  • MongoDB基本操作

    创建数据库 删除数据库 插入文档 删除文档 参数说明: query:可选,删除的文档的条件 justOne:可选,...

  • ElasticSearch-文档-基本增删改查

    Maven项目的pom.xml 创建文档 1.新建一个类用于封装数据 2.创建 批量创建文档 修改文档 删除文档 ...

  • MongoDB常用命令

    创建/删除数据库 创建/删除集合 插入文档 MongoDB 使用 insert() 或 save() 方法向集合中...

  • Elasticsearch 增删改查

    1、索引的创建和查询 2、索引的mapping导入和查询 3、索引的删除 4、文档的插入 5、文档的查询 6、文档的删除

网友评论

    本文标题:MongoDB-第三章-创建、更新和删除文档

    本文链接:https://www.haomeiwen.com/subject/icqmpctx.html