欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

MongoDB学习笔记(十三)——修改文档

程序员文章站 2024-01-23 08:34:34
...

在本篇博文中,我将演示MongoDB的修改文档。

MongoDB shell

在MonogDB 的官方文档中,提供的集合方法中,关于修改的方法有四个:findOneAndUpdate()、update()、updateOne()、updateMany()。从字面上大家应该就可以判断出其功能了:

findOneAndUpdate():修改筛选出来的文档中的第一个文档,并返回,可以使用参数控制返回修改前还是修改后的文档。
update():修改单个文档或批量修改文档。
updateOne():修改单个文档。
updateMany() : 批量修改文档。

其各方法结构如下

db.collection.findOneAndUpdate(
   <filter>,
   <update>,
   {
     projection: <document>,
     sort: <document>,
     maxTimeMS: <number>,
     upsert: <boolean>,
     returnNewDocument: <boolean>,
     collation: <document>,
     arrayFilters: [ <filterdocument1>, ... ]
   }
)
db.collection.update(
   <query>,
   <update>,
   {
     upsert: <boolean>,
     multi: <boolean>,
     writeConcern: <document>,
     collation: <document>,
     arrayFilters: [ <filterdocument1>, ... ]
   }
)
db.collection.updateOne(
   <filter>,
   <update>,
   {
     upsert: <boolean>,
     writeConcern: <document>,
     collation: <document>,
     arrayFilters: [ <filterdocument1>, ... ]
   }
)
db.collection.updateMany(
   <filter>,
   <update>,
   {
     upsert: <boolean>,
     writeConcern: <document>,
     collation: <document>,
     arrayFilters: [ <filterdocument1>, ... ]
   }
)

其中出现的各字段如下:

filter : 是一个查询筛选过滤器,用于限定符合条件的文档,
update : 则是用于修改内容的文档,该字段需要注意一下,如果你只是替换文档中的字段,你可以直接使用类似{key:new value,key2:new value}的结构替换文档,但是如果你想在原有字段上计算等操作,就需要使用修改操作符,如:$inc 用于计算值,$set 用于替换值,其各操作符一会我将使用表格展现出来。例:{ $set: { "name" : "A.B. Abracus", "assignment" : 5}, $inc : { "points" : 5 } } //设置name字段为A.B. Abracus、assignment字段值为5,pioints字段值增加5 注:在findOneAndUpdate()方法中必须包含修改操作符。即使你只是替换字段的值,否则会报错。
projection:可选。设置要返回文档的字段子集。若要返回文档中的所有字段,请省略此参数。
sort:可选。指定上面的筛选过滤器的排序方式。具体使用可以参考:排序,例如{ age : -1, posts: 1 }按照age降序排列,按照posts升序排列
maxTimeMS:可选。指定该修改操作必须在多少时间内完成。若指定时间内完不成,则会报错
upsert:可选。用于设置是否在修改的文档不存在时,创建一个新的文档,即我们常说的saveOrUpdate,其值默认false。
returnNewDocument:可选。设置返回的文档是更新后的文档还是更新前的文档,默认false,返回更新前的文档。
collation:可选。指定用于操作的collation设置,具体使用可以参考官方文档,其结构如下:

collation: {
   locale: <string>,
   caseLevel: <boolean>,
   caseFirst: <string>,
   strength: <int>,
   numericOrdering: <boolean>,
   alternate: <string>,
   maxVariable: <string>,
   backwards: <boolean>
}

arrayFilters:可选。用于数组参数的筛选过滤,其结构为一个数组,该数组中可以设置如何操作数组参数中的值。其使用占位符$[identifier]来操作,具体可以参考其官方文档,如:

//修改grades数组中大于或等于100的所有元素
db.students.findOneAndUpdate(//修改第一个文档并返回
   { grades: { $gte: 100 } },//筛选出grades数组中有>=100的参数
   { $set: { "grades.$[element]" : 100 } },//将符合下面过滤条件的数组值替换成100
   { arrayFilters: [ { "element": { $gte: 100 } } ] }//数组过滤条件,用于筛选出>=100的值
)

db.students2.findOneAndUpdate(
   { },
   { $set: { "grades.$[elem].mean" : 100 } },//修改符合下方的数组文档元素的mean字段值为100
   { arrayFilters: [ { "elem.grade": { $gte: 85 } } ] }//过滤出grades数组中的文档元素的grade字段值>=85的数组元素
)

writeConcern : 可选,写入关注等级,可参考前面的博文。
multi : 是否批量修改 true,是,默认false

修改操作符表:

操作符 功能
字段操作
$currentDate 将字段的值设置为当前日期,无论是日期还是时间戳。
$inc 给指定字段增加指定的值。负数为减少指定的值
$min 如果指定值小于现有字段值,才会更新字段。
$max 如果指定值大于现有字段值,才会更新字段。
$mul 将字段的值乘以指定的值。
$rename 字段重命名
$set 将指定字段的值替换为指定的值
$setOnInsert 如果修改是在一个插入的文档上,则设置字段的值。对修改现有文档的更新操作没有影响。
$unset 从文档中移除指定字段。
数组操作
$ 充当占位符以更新与查询条件匹配的第一个元素。
$[] 充当占位符,以便更新数组中所有匹配查询条件的文档的元素。
$[identifier] 充当占位符,以便更新所有与匹配arrayFilters过滤条件的文档匹配的在条件的元素。
$addToSet 只有在集合中尚未存在元素时才将元素添加到数组中。
$pop 移除数组的第一个或最后一个项。
$pull 删除与指定查询匹配的所有数组元素。
$push 将元素添加到数组中。
$pullAll 从数组中移除所有匹配值。
修饰语
$each 修改\$push和\$addToSet操作符来追加多个参数来进行数组更新。
$position 修改\$push操作符来指定数组中添加元素的位置。
$slice 修改\$push操作符来限制更新数组的大小。
$sort 修改\$push操作符来重新排序存储在数组中的文档。
比特
$bit 执行整数值的按位和、or和异或更新。

下面是我使用shell进行的一些修改操作:
MongoDB学习笔记(十三)——修改文档
如上图:首先查看word_stats集合中给的word字段的值为the的记录,然后使用{word:”the”}来筛选文档,使用{$inc:{size:5},$set:{first:"the",letters:["t","h","u"],"stats.vowels":"ss","charsets.1":{"test":"array test"}}}来设置修改内容,修改为将size字段值增加5,将first字段值修改为the,将letters字段的数组值修改为[“t”,”h”,”u”],将stats字段的子文档的vowels值修改为ss,注:若引用文档的子文档可以使用”.”,而且这时候key必须使用”“引起来,将charsets数组字段的下标为1的元素值修改为“array test”,注:引用数组中的字段值可以使用”.index”来使用。然后我们对比两条记录,应该可以看到修改的结果。

MongoDB学习笔记(十三)——修改文档
该示例用于测试upsert配置,用于设置如果没有该文档,则新增文档,若有文档则修改,实现saveOrUpdate功能。

MongoDB学习笔记(十三)——修改文档

该示例表明,如果使用了修改操作符,则替换的修改操作必须封装到$set操作符中,不能替换操作与$inc操作符平级,否则会报错。

MongoDB学习笔记(十三)——修改文档

该示例用于演示updateOne方法,用于修改符合查询条件文档的第一个文档。

MongoDB学习笔记(十三)——修改文档

该示例用于演示updateMany方法,用于修改所有的符合筛选条件的文档。

MongoDB学习笔记(十三)——修改文档

该示例用于演示findOneAndUpdate方法,并且演示了findOneAndUpdate的修改部分必须封装到修改操作符中,不能直接替换,否则会报错。

js脚本
js脚本的实现与前两篇文档中没有什么差别,这里就不掩饰了,大家可以看前两篇博文,应该就明白了。

MongoDB Compass

compass中修改文档很简单,如图:
MongoDB学习笔记(十三)——修改文档
双击要修改的文档字段,修改文档字段的值,然后点击update按钮即可。

MongoDB java Driver

MongoDB的java驱动提供了12个修改方法:
MongoDB学习笔记(十三)——修改文档

和删除方法等一样,根据类别的不同其方法三类:updateOne()、updteMany()、findOneAndUpdate(),分别用于实现修改一个文档、修改多个文档、修改一个文档并返回。

每一类方法根据传入的参数不同又分为四种方法。其实四个方法最终调用的方法都是同一个,只不过有些没有传入的参数会给其默认值。

下面我们看看方法中出现的参数:
Bson : 方法中每个方法中都有两个Bson参数,这两个Bson参数第一个用于过滤文档的筛选条件,一个用于修改文档的修改操作
ClientSession : 与此修改操作关联的客户端会话
UpdateOptions : 应用于更新操作的选项,该参数等同于上面的一系列的设置,比如upsert、arrayFilter等等。详细可见其官方API
findOneAndUpdate : 与上面的一致,应用与findOneAndUpdate的一些配置。

下面是我自己做的一些示例,大家可以根据下面的示例找到每个参数的使用案例:

/**
     * 用于演示MongoDB原生驱动修改数据
     */
    public void updateDataDemo1(){

       // 使用URI链接MongoDB,MonoClientURI格式为:mongodb://[用户名:密码@]host:port[/数据库],强烈建议使用该种身份验证
        MongoClient client = new MongoClient(new MongoClientURI("mongodb://yfl:aaa@qq.com:27017/words"));

        //获取MongoDatabase 对象
        MongoDatabase database = client.getDatabase("words");
        //获取集合对象
        MongoCollection col = database.getCollection("word_stats");

        //测试修改前word字段为“thess”的数据
        System.out.println("修改前word字段为thess数据:");
        for (Document doc : (FindIterable<Document>)col.find(eq("word", "thess"))){
            System.out.println( doc.toJson());
        }
        //修改前word字段为“thess”
        //设置修改内容,将first字段修改为t,将last字段修改为ss
        BasicDBObject ob = new BasicDBObject();
        ob.append("$set",new BasicDBObject("first","t").append("last","ss"));
        ob.append("$inc",new BasicDBObject("size",5));
        col.updateOne(eq("word", "thess"),ob );
        //修改后word字段为thess数据
        System.out.println("修改后word字段为thess数据:");
        for (Document doc : (FindIterable<Document>)col.find(eq("word", "thess"))){
            System.out.println(doc.toJson());
        }

        //测试添加修改配置,以upsert为例
        //测试修改前word字段为“thess”的数据
        System.out.println("修改前word字段为theses数据:");
        for (Document doc : (FindIterable<Document>)col.find(eq("word", "theses"))){
            System.out.println( doc.toJson());
        }
        //修改前word字段为“thess”
        //设置修改内容,将first字段修改为t,将last字段修改为ss
        BasicDBObject ob1 = new BasicDBObject();
        ob1.append("$set",new BasicDBObject("first","t").append("last","ss").append("word","theses"));
        col.updateOne(eq("word", "theses"),ob1 ,new UpdateOptions().upsert(true));
        //修改后word字段为thess数据
        System.out.println("修改后word字段为theses数据:");
        for (Document doc : (FindIterable<Document>)col.find(eq("word", "theses"))){
            System.out.println(doc.toJson());
        }

        //测试修改前word字段为“thess”的数据
        System.out.println("修改前word字段为thess数据:");
        for (Document doc : (FindIterable<Document>)col.find(eq("word", "thess"))){
            System.out.println( doc.toJson());
        }


        //测试修改前word字段为“thess”的数据
        System.out.println("修改前word字段为thess数据:");
        for (Document doc : (FindIterable<Document>)col.find(eq("word", "thess"))){
            System.out.println( doc.toJson());
        }
        //设置修改内容,将first字段修改为t,将last字段修改为ss
        BasicDBObject ob2 = new BasicDBObject();
        ob2.append("$set",new BasicDBObject("first","ths").append("last","s"));
        Document doc = (Document) col.findOneAndUpdate(eq("word", "thess"),ob2 ,new FindOneAndUpdateOptions().sort(new BasicDBObject("size",1)));
        System.out.println(doc.toJson());

    }

对于里面的代码我这里就不在讲解了。大家如果看不懂可以在下面留言。关于Bson的用法,在前面的两篇博文中有讲解到。大家可以往前面翻看一下。

mongoTemplate

官方API

Spring对MongDB的封装 Spring-data-mongo 提供的对象mongoTemplate提供了10个方法用于修改数据。
MongoDB学习笔记(十三)——修改文档
上面的方法可以分为两类,一个就是update(Class<T> domainType)这个方法,另外9个方法都是属于同一类,下面我们先来讲一下update()这个方法。
update()这个方法和其他方法不一样的地方在于,其返回的类型是ExecutableUpdte对象。这个对象不是修改操作的返回结果,其和前面删除方法一样,其就是给给定的实体类class domainType创建一个修改操作 ,这只是一个操作对象,而没有执行修改操作,其方法内部会根据传入的class默认其他参数,设置一个ExecutableUpdte对象。如果大家想要设置要操作的集合、修改操作内容等,大家可以调用其方法,如下示例:

mongoTemplate.update(MongoAddDemo.class).inCollection("word_stats")//设置其操作集合民称
                .matching(new Query(Criteria.where("word").is("thess")))//设置其查询过滤条件
                .apply(new Update().set("last", "se"))//修改操作
                .upsert();//执行updsert方法(有则修改,无则添加)

上面的inCollection、matching方法是可以省略的,省略之后集合名默认为传入的Class类名的首字母小写的集合如上面的MongoAddDemo实体类,其默认的集合名称为mongoAddDemo。查询过滤条件省略之后,默认匹配全部文档。
大家可以看ExecutableUpdte这个对象的官方API

其他9中方法,一眼看去仿佛就是三类方法:upsert()、updateFirst()、updateMulti()这三类方法。分别用于操作:有则修改无则添加、修改第一个文档、批量修改文档。
但是其实质上就是一个方法:

 protected WriteResult doUpdate(final String collectionName, final Query query, final Update update, final Class<?> entityClass, final boolean upsert, final boolean multi) {
        return (WriteResult)this.execute(collectionName, new CollectionCallback() {
            public WriteResult doInCollection(DBCollection collection) throws MongoException, DataAccessException {
                MongoPersistentEntity entity = entityClass == null?null:MongoTemplate.this.getPersistentEntity(entityClass);
                MongoTemplate.this.increaseVersionForUpdateIfNecessary(entity, update);
                Object queryObj = query == null?new BasicDBObject():MongoTemplate.this.queryMapper.getMappedObject(query.getQueryObject(), entity);
                Object updateObj = update == null?new BasicDBObject():MongoTemplate.this.updateMapper.getMappedObject(update.getUpdateObject(), entity);
                if(MongoTemplate.LOGGER.isDebugEnabled()) {
                    MongoTemplate.LOGGER.debug("Calling update using query: {} and update: {} in collection: {}", new Object[]{SerializationUtils.serializeToJsonSafely(queryObj), SerializationUtils.serializeToJsonSafely(updateObj), collectionName});
                }

                MongoAction mongoAction = new MongoAction(MongoTemplate.this.writeConcern, MongoActionOperation.UPDATE, collectionName, entityClass, (DBObject)updateObj, (DBObject)queryObj);
                WriteConcern writeConcernToUse = MongoTemplate.this.prepareWriteConcern(mongoAction);
                WriteResult writeResult = writeConcernToUse == null?collection.update((DBObject)queryObj, (DBObject)updateObj, upsert, multi):collection.update((DBObject)queryObj, (DBObject)updateObj, upsert, multi, writeConcernToUse);
                if(entity != null && entity.hasVersionProperty() && !multi && ReflectiveWriteResultInvoker.wasAcknowledged(writeResult) && writeResult.getN() == 0 && MongoTemplate.this.dbObjectContainsVersionProperty((DBObject)queryObj, entity)) {
                    throw new OptimisticLockingFailureException("Optimistic lock exception on saving entity: " + ((DBObject)updateObj).toMap().toString() + " to collection " + collectionName);
                } else {
                    MongoTemplate.this.handleAnyWriteResultErrors(writeResult, (DBObject)queryObj, MongoActionOperation.UPDATE);
                    return writeResult;
                }
            }
        });
    }

上面的不同方法也只是会默认往该方法中配置默认参数,upsert()这一系列方法其会默认upsert参数为true,默认multi方法为false。updateMulti()会默认multi参数为true,而且其upsert默认为false。updateFirst()则默认multi方法为false,而且其upsert默认为false。

那么我们来看看不同方法传入的参数:
Query:该参数就是一个查询对象,用于过滤符合不符合条件的文档,没有该参数是,会默认一个空的Query对象,用于匹配所有文档。
Update:该参数就是一个修改操作的对象,用于构建修改操作。该参数不能为null,必须有值
Class<?>:该参数有两个功能,一个是如果没有下面的collectionName参数时,会根据该class的名称来默认集合名,使用determineCollectionName()来默认,默认规则是名称首字母小写的字符串。第二个功能,没有仔细研究其源代码,暂时没有发现其对修改操作有何影响。有兴趣的可以看一下上面的源代码,深入研究一下
collectionName : 指定操作的集合名,该参数为null时,则上面的Class<?>不能为空。

由于上面9个方法一致,这里就不多演示了,只是演示其中的一种,用于大家看一下Query、Update对象的使用:


    /**
     * 使用spring封装mongodb的对象mongoTemplate修改数据
     */
    public void updateDataDemo2(){

        //修改word_stats集合中word字段为thess的数据,将last字段修改为se
        mongoTemplate.update(MongoAddDemo.class).inCollection("word_stats")
                .matching(new Query(Criteria.where("word").is("thess")))
                .apply(new Update().set("last", "se"))
                .upsert();

        Query query = new Query(Criteria.where("word").is("thess"));//定义查询
        mongoTemplate.upsert(query,new Update().set("last", "see"),MongoAddDemo.class);//修改mongoAddDemo集合中的数据
        mongoTemplate.upsert(query,new Update().set("last", "see"),MongoAddDemo.class,"word_stats");
    }
}

关于Query对象和Update对象,大家可以看其官方API