Saving, Updating, and Removing Documents

MongoTemplate / ReactiveMongoTemplatge 允许你保存、更新和删除你的领域对象,并将这些对象映射到存储在 MongoDB 中的文档。命令式 API 和响应式 API 的 API 签名主要相同,仅返回类型不同。同步 API 使用 void、单个 ObjectList,而响应式对应项由 Mono<Void>Mono<Object>Flux 组成。

MongoTemplate / ReactiveMongoTemplatge let you save, update, and delete your domain objects and map those objects to documents stored in MongoDB. The API signatures of the imperative and reactive API are mainly the same only differing in their return types. While the synchronous API uses void, single Object and List the reactive counterpart consists of Mono<Void>, Mono<Object> and Flux.

考虑以下类:

Consider the following class:

Unresolved include directive in modules/ROOT/pages/mongodb/template-crud-operations.adoc - include::example$example/Person.java[]

给定前面示例中的 Person 类,你可以保存、更新和删除对象,如下所示示例:

Given the Person class in the preceding example, you can save, update and delete the object, as the following example shows:

  • Imperative

public class MongoApplication {

  private static final Log log = LogFactory.getLog(MongoApplication.class);

  public static void main(String[] args) {

    MongoOperations template = new MongoTemplate(new SimpleMongoClientDbFactory(MongoClients.create(), "database"));

    Person p = new Person("Joe", 34);

    // Insert is used to initially store the object into the database.
    template.insert(p);
    log.info("Insert: " + p);

    // Find
    p = template.findById(p.getId(), Person.class);
    log.info("Found: " + p);

    // Update
    template.updateFirst(query(where("name").is("Joe")), update("age", 35), Person.class);
    p = template.findOne(query(where("name").is("Joe")), Person.class);
    log.info("Updated: " + p);

    // Delete
    template.remove(p);

    // Check that deletion worked
    List<Person> people =  template.findAll(Person.class);
    log.info("Number of people = : " + people.size());


    template.dropCollection(Person.class);
  }
}

前面的示例将生成以下日志输出(包括来自 MongoTemplate 的调试消息):

The preceding example would produce the following log output (including debug messages from MongoTemplate):

DEBUG apping.MongoPersistentEntityIndexCreator:  80 - Analyzing class class org.spring.example.Person for index information.
DEBUG work.data.mongodb.core.MongoTemplate: 632 - insert Document containing fields: [_class, age, name] in collection: person
INFO               org.spring.example.MongoApp:  30 - Insert: Person [id=4ddc6e784ce5b1eba3ceaf5c, name=Joe, age=34]
DEBUG work.data.mongodb.core.MongoTemplate:1246 - findOne using query: { "_id" : { "$oid" : "4ddc6e784ce5b1eba3ceaf5c"}} in db.collection: database.person
INFO               org.spring.example.MongoApp:  34 - Found: Person [id=4ddc6e784ce5b1eba3ceaf5c, name=Joe, age=34]
DEBUG work.data.mongodb.core.MongoTemplate: 778 - calling update using query: { "name" : "Joe"} and update: { "$set" : { "age" : 35}} in collection: person
DEBUG work.data.mongodb.core.MongoTemplate:1246 - findOne using query: { "name" : "Joe"} in db.collection: database.person
INFO               org.spring.example.MongoApp:  39 - Updated: Person [id=4ddc6e784ce5b1eba3ceaf5c, name=Joe, age=35]
DEBUG work.data.mongodb.core.MongoTemplate: 823 - remove using query: { "id" : "4ddc6e784ce5b1eba3ceaf5c"} in collection: person
INFO               org.spring.example.MongoApp:  46 - Number of people = : 0
DEBUG work.data.mongodb.core.MongoTemplate: 376 - Dropped collection [database.person]
Reactive
public class ReactiveMongoApplication {

  private static final Logger log = LoggerFactory.getLogger(ReactiveMongoApplication.class);

  public static void main(String[] args) throws Exception {

    CountDownLatch latch = new CountDownLatch(1);

    ReactiveMongoTemplate template = new ReactiveMongoTemplate(MongoClients.create(), "database");

    template.insert(new Person("Joe", 34)).doOnNext(person -> log.info("Insert: " + person))
      .flatMap(person -> template.findById(person.getId(), Person.class))
      .doOnNext(person -> log.info("Found: " + person))
      .zipWith(person -> template.updateFirst(query(where("name").is("Joe")), update("age", 35), Person.class))
      .flatMap(tuple -> template.remove(tuple.getT1())).flatMap(deleteResult -> template.findAll(Person.class))
      .count().doOnSuccess(count -> {
        log.info("Number of people: " + count);
        latch.countDown();
      })

      .subscribe();

    latch.await();
  }
}

MongoConverter 通过识别(通过惯例)Id 属性名称,在 String 和数据库中存储的 ObjectId 之间 隐式转换。

MongoConverter caused implicit conversion between a String and an ObjectId stored in the database by recognizing (through convention) the Id property name.

前面的示例旨在展示对 MongoTemplate / ReactiveMongoTemplate 的保存、更新和删除操作的使用,而不是显示复杂映射功能。在前面的示例中使用的查询语法将在 “Querying Documents” 部分进行更详细的说明。

The preceding example is meant to show the use of save, update, and remove operations on MongoTemplate / ReactiveMongoTemplate and not to show complex mapping functionality. The query syntax used in the preceding example is explained in more detail in the section “Querying Documents”.

MongoDB 要求您为所有文档设置一个`_id` 字段。有关此字段的特殊处理的详细信息,请参阅ID handling 部分。

MongoDB requires that you have an _id field for all documents. Please refer to the ID handling section for details on the special treatment of this field.

MongoDB 集合可以包含表示各种类型实例的文档。有关详细信息,请参阅type mapping

MongoDB collections can contain documents that represent instances of a variety of types. Please refer to the type mapping for details.

Insert / Save

MongoTemplate 上有几种方便的方法,用于保存和插入你的对象。要对转换过程有更细粒度的控制,你可以使用 MappingMongoConverter 注册 Spring 转换器,例如 Converter<Person, Document>Converter<Document, Person>

There are several convenient methods on MongoTemplate for saving and inserting your objects. To have more fine-grained control over the conversion process, you can register Spring converters with the MappingMongoConverter — for example Converter<Person, Document> and Converter<Document, Person>.

插入和保存操作之间的差别在于,如果对象不存在,保存操作将执行插入操作。

The difference between insert and save operations is that a save operation performs an insert if the object is not already present.

使用保存操作的简单情况是保存 POJO。在这种情况下,集合名称由类的名称(不完全限定)确定。你也可以使用特定集合名称调用保存操作。你可以使用映射元数据来覆盖要存储对象的集合。

The simple case of using the save operation is to save a POJO. In this case, the collection name is determined by name (not fully qualified) of the class. You may also call the save operation with a specific collection name. You can use mapping metadata to override the collection in which to store the object.

在插入或保存时,如果未设置 Id 属性,则假设其值将由数据库自动生成。因此,要成功自动生成 ObjectId,类中 Id 属性或字段的类型必须是 StringObjectIdBigInteger

When inserting or saving, if the Id property is not set, the assumption is that its value will be auto-generated by the database. Consequently, for auto-generation of an ObjectId to succeed, the type of the Id property or field in your class must be a String, an ObjectId, or a BigInteger.

以下示例演示了如何保存文档并检索其内容:

The following example shows how to save a document and retrieving its contents:

Inserting and retrieving documents using the MongoTemplate
  • Imperative

  • Reactive

import static org.springframework.data.mongodb.core.query.Criteria.where;
import static org.springframework.data.mongodb.core.query.Criteria.query;

//...

template.insert(new Person("Bob", 33));

Person person = template.query(Person.class)
    .matching(query(where("age").is(33)))
    .oneValue();
import static org.springframework.data.mongodb.core.query.Criteria.where;
import static org.springframework.data.mongodb.core.query.Criteria.query;

//...

Mono<Person> person = mongoTemplate.insert(new Person("Bob", 33))
    .then(mongoTemplate.query(Person.class)
        .matching(query(where("age").is(33)))
        .one());

以下插入和保存操作可用:

The following insert and save operations are available:

  • void save (Object objectToSave): Save the object to the default collection.

  • void save (Object objectToSave, String collectionName): Save the object to the specified collection.

也有一组类似的插入操作可用:

A similar set of insert operations is also available:

  • void insert (Object objectToSave): Insert the object to the default collection.

  • void insert (Object objectToSave, String collectionName): Insert the object to the specified collection.

How the _id Field is Handled in the Mapping Layer

MongoDB 要求所有文档都有一个 _id 字段。如果你没有提供,则驱动程序会分配一个带有生成值的 ObjectId,而不考虑你的领域模型,因为服务器不知道你的标识符类型。当你使用 MappingMongoConverter 时,某些规则会控制如何将 Java 类的属性映射到此 _id 字段:

MongoDB requires that you have an _id field for all documents. If you do not provide one, the driver assigns an ObjectId with a generated value without considering your domain model as the server isn’t aware of your identifier type. When you use the MappingMongoConverter, certain rules govern how properties from the Java class are mapped to this _id field:

  1. A property or field annotated with @Id (org.springframework.data.annotation.Id) maps to the _id field.

  2. A property or field without an annotation but named id maps to the _id field.

以下内容概述了使用 MappingMongoConverterMongoTemplate 的默认值)时对映射到 _id 文档字段的属性进行的类型转换(如有)。

The following outlines what type conversion, if any, is done on the property mapped to the _id document field when using the MappingMongoConverter (the default for MongoTemplate).

  1. If possible, an id property or field declared as a String in the Java class is converted to and stored as an ObjectId by using a Spring Converter<String, ObjectId>. Valid conversion rules are delegated to the MongoDB Java driver. If it cannot be converted to an ObjectId, then the value is stored as a string in the database.

  2. An id property or field declared as BigInteger in the Java class is converted to and stored as an ObjectId by using a Spring Converter<BigInteger, ObjectId>.

如果 Java 类中不存在以前各组规则中指定字段或属性,则驱动程序会生成一个隐式的 _id 文件,但不将其映射到 Java 类的属性或字段。

If no field or property specified in the previous sets of rules is present in the Java class, an implicit _id file is generated by the driver but not mapped to a property or field of the Java class.

在查询和更新时,MongoTemplate 使用与保存文档的前述规则相对应的转换器,以便查询中使用的字段名称和类型可以与你的域类中的保持一致。

When querying and updating, MongoTemplate uses the converter that corresponds to the preceding rules for saving documents so that field names and types used in your queries can match what is in your domain classes.

某些环境需要一种自定义方法来映射 Id 值,例如存储在未通过 Spring 数据映射层运行的 MongoDB 中的数据。文档可以包含可表示为 ObjectIdString_id 值。将文档从存储中读回到域类型时工作得很好。由于隐式 ObjectId 转换,通过其 id 查询文档可能很麻烦。因此,无法以这种方式检索文档。对于这些情况,@MongoId 对实际 ID 映射尝试提供了更多控制。

Some environments require a customized approach to map Id values such as data stored in MongoDB that did not run through the Spring Data mapping layer. Documents can contain _id values that can be represented either as ObjectId or as String. Reading documents from the store back to the domain type works just fine. Querying for documents via their id can be cumbersome due to the implicit ObjectId conversion. Therefore documents cannot be retrieved that way. For those cases @MongoId provides more control over the actual id mapping attempts.

Example 1. @MongoId mapping
public class PlainStringId {
  @MongoId String id; 1
}

public class PlainObjectId {
  @MongoId ObjectId id; 2
}

public class StringToObjectId {
  @MongoId(FieldType.OBJECT_ID) String id; 3
}
1 The id is treated as String without further conversion.
2 The id is treated as ObjectId.
3 The id is treated as ObjectId if the given String is a valid ObjectId hex, otherwise as String. Corresponds to @Id usage.

Into Which Collection Are My Documents Saved?

有两种方法可以管理用于文档的集合名称。使用的默认集合名称是类名称更改为以小写字母开头。因此,com.test.Person 类存储在 person 集合中。可以通过使用 @Document 注释提供不同的集合名称来对其进行自定义。你还可以通过为所选 MongoTemplate 方法调用提供自己的集合名称作为最后一个参数来覆盖集合名称。

There are two ways to manage the collection name that is used for the documents. The default collection name that is used is the class name changed to start with a lower-case letter. So a com.test.Person class is stored in the person collection. You can customize this by providing a different collection name with the @Document annotation. You can also override the collection name by providing your own collection name as the last parameter for the selected MongoTemplate method calls.

Inserting or Saving Individual Objects

MongoDB 驱动程序支持通过单个操作插入文档集合。MongoOperations 接口中的以下方法支持此功能:

The MongoDB driver supports inserting a collection of documents in a single operation. The following methods in the MongoOperations interface support this functionality:

  • insert: Inserts an object. If there is an existing document with the same id, an error is generated.

  • insertAll: Takes a Collection of objects as the first parameter. This method inspects each object and inserts it into the appropriate collection, based on the rules specified earlier.

  • save: Saves the object, overwriting any object that might have the same id.

Inserting Several Objects in a Batch

MongoDB 驱动程序支持通过一个操作插入文档集合。MongoOperations 接口中的以下方法通过 insert 或专用 BulkOperations 接口支持此功能。

The MongoDB driver supports inserting a collection of documents in one operation. The following methods in the MongoOperations interface support this functionality via insert or a dedicated BulkOperations interface.

Batch Insert
  • Imperative

  • Reactive

Collection<Person> inserted = template.insert(List.of(...), Person.class);
Flux<Person> inserted = template.insert(List.of(...), Person.class);
Bulk Insert
  • Imperative

  • Reactive

BulkWriteResult result = template.bulkOps(BulkMode.ORDERED, Person.class)
    .insert(List.of(...))
    .execute();
Mono<BulkWriteResult> result = template.bulkOps(BulkMode.ORDERED, Person.class)
    .insert(List.of(...))
    .execute();

批处理和批量处理的服务器性能是相同的。但是,批量操作不发布 lifecycle events

Server performance of batch and bulk is identical. However bulk operations do not publish lifecycle events.

任何在调用 insert 之前还未设置的 @Version 属性将使用 1(对于 int 等简单类型)自动初始化或在包装器类型(例如 Integer)中使用 0 自动初始化。请在 Optimistic Locking 部分中了解更多信息。

Any @Version property that has not been set prior to calling insert will be auto initialized with 1 (in case of a simple type like int) or 0 for wrapper types (eg. Integer). Read more in the see Optimistic Locking section.

Update

对于更新,你可以使用 MongoOperation.updateFirst 更新找到的第一个文档,也可以使用 MongoOperation.updateMulti 方法或 fluent API 上的 all 更新找到与查询匹配的所有文档。下面的示例显示了更新所有 SAVINGS 帐户,其中我们使用 $inc 运算符为余额添加一次性 $50.00 奖励:

For updates, you can update the first document found by using MongoOperation.updateFirst or you can update all documents that were found to match the query by using the MongoOperation.updateMulti method or all on the fluent API. The following example shows an update of all SAVINGS accounts where we are adding a one-time $50.00 bonus to the balance by using the $inc operator:

Updating documents by using the MongoTemplate / ReactiveMongoTemplate
  • Imperative

  • Reactive

import static org.springframework.data.mongodb.core.query.Criteria.where;
import org.springframework.data.mongodb.core.query.Update;

// ...

UpdateResult result = template.update(Account.class)
    .matching(where("accounts.accountType").is(Type.SAVINGS))
    .apply(new Update().inc("accounts.$.balance", 50.00))
    .all();
import static org.springframework.data.mongodb.core.query.Criteria.where;
import org.springframework.data.mongodb.core.query.Update;

// ...

Mono<UpdateResult> result = template.update(Account.class)
    .matching(where("accounts.accountType").is(Type.SAVINGS))
    .apply(new Update().inc("accounts.$.balance", 50.00))
    .all();

除了前面讨论的 Query 之外,我们还通过使用 Update 对象提供更新定义。Update 类具有与 MongoDB 可用的更新修改器匹配的方法。大多数方法返回 Update 对象,以针对 API 提供 fluent 样式。

In addition to the Query discussed earlier, we provide the update definition by using an Update object. The Update class has methods that match the update modifiers available for MongoDB. Most methods return the Update object to provide a fluent style for the API.

如果 @Version 属性未包含在 Update 中,则会自动对其进行递增。请在 Optimistic Locking 部分中了解更多信息。

@Version properties if not included in the Update will be automatically incremented. Read more in the see Optimistic Locking section.

Methods for Running Updates for Documents

  • updateFirst: Updates the first document that matches the query document criteria with the updated document.

  • updateMulti: Updates all objects that match the query document criteria with the updated document.

updateFirst 不支持排序。请使用findAndModify 来应用`Sort`。

updateFirst does not support ordering. Please use findAndModify to apply Sort.

可以经由 Query.withHint(…​) 提供更新操作的索引提示。

Index hints for the update operation can be provided via Query.withHint(…​).

Methods in the Update Class

你可以对 Update 类使用一丁点“语法糖”,因为它的方法旨在连接在一起。此外,你可以通过使用 public static Update update(String key, Object value) 并使用静态导入来启动创建新的 Update 实例。

You can use a little "'syntax sugar'" with the Update class, as its methods are meant to be chained together. Also, you can kick-start the creation of a new Update instance by using public static Update update(String key, Object value) and using static imports.

Update 类包含以下方法:

The Update class contains the following methods:

  • Update addToSet (String key, Object value) Update using the $addToSet update modifier

  • Update currentDate (String key) Update using the $currentDate update modifier

  • Update currentTimestamp (String key) Update using the $currentDate update modifier with $type timestamp

  • Update inc (String key, Number inc) Update using the $inc update modifier

  • Update max (String key, Object max) Update using the $max update modifier

  • Update min (String key, Object min) Update using the $min update modifier

  • Update multiply (String key, Number multiplier) Update using the $mul update modifier

  • Update pop (String key, Update.Position pos) Update using the $pop update modifier

  • Update pull (String key, Object value) Update using the $pull update modifier

  • Update pullAll (String key, Object[] values) Update using the $pullAll update modifier

  • Update push (String key, Object value) Update using the $push update modifier

  • Update pushAll (String key, Object[] values) Update using the $pushAll update modifier

  • Update rename (String oldName, String newName) Update using the $rename update modifier

  • Update set (String key, Object value) Update using the $set update modifier

  • Update setOnInsert (String key, Object value) Update using the $setOnInsert update modifier

  • Update unset (String key) Update using the $unset update modifier

某些更新修改器(例如 $push$addToSet)允许嵌套其他运算符。

Some update modifiers, such as $push and $addToSet, allow nesting of additional operators.

// { $push : { "category" : { "$each" : [ "spring" , "data" ] } } }
new Update().push("category").each("spring", "data")

// { $push : { "key" : { "$position" : 0 , "$each" : [ "Arya" , "Arry" , "Weasel" ] } } }
new Update().push("key").atPosition(Position.FIRST).each(Arrays.asList("Arya", "Arry", "Weasel"));

// { $push : { "key" : { "$slice" : 5 , "$each" : [ "Arya" , "Arry" , "Weasel" ] } } }
new Update().push("key").slice(5).each(Arrays.asList("Arya", "Arry", "Weasel"));

// { $addToSet : { "values" : { "$each" : [ "spring" , "data" , "mongodb" ] } } }
new Update().addToSet("values").each("spring", "data", "mongodb");

Aggregation Pipeline Updates

MongoOperationsReactiveMongoOperations 公开的更新方法也通过 AggregationUpdate 接受 Aggregation Pipeline。使用 AggregationUpdate 允许在更新操作中利用 MongoDB 4.2 aggregations。在更新中使用聚合允许通过使用单个操作来表达多个阶段和多个条件,从而更新一个或多个字段。

Update methods exposed by MongoOperations and ReactiveMongoOperations also accept an Aggregation Pipeline via AggregationUpdate. Using AggregationUpdate allows leveraging MongoDB 4.2 aggregations in an update operation. Using aggregations in an update allows updating one or more fields by expressing multiple stages and multiple conditions with a single operation.

更新可以包含以下阶段:

The update can consist of the following stages:

  • AggregationUpdate.set(…​).toValue(…​)$set : { …​ }

  • AggregationUpdate.unset(…​)$unset : [ …​ ]

  • AggregationUpdate.replaceWith(…​)$replaceWith : { …​ }

Example 2. Update Aggregation
AggregationUpdate update = Aggregation.newUpdate()
    .set("average").toValue(ArithmeticOperators.valueOf("tests").avg())     1
    .set("grade").toValue(ConditionalOperators.switchCases(                 2
        when(valueOf("average").greaterThanEqualToValue(90)).then("A"),
        when(valueOf("average").greaterThanEqualToValue(80)).then("B"),
        when(valueOf("average").greaterThanEqualToValue(70)).then("C"),
        when(valueOf("average").greaterThanEqualToValue(60)).then("D"))
        .defaultTo("F")
    );

template.update(Student.class)                                              3
    .apply(update)
    .all();                                                                 4
db.students.update(                                                         3
   { },
   [
     { $set: { average : { $avg: "$tests" } } },                            1
     { $set: { grade: { $switch: {                                          2
                           branches: [
                               { case: { $gte: [ "$average", 90 ] }, then: "A" },
                               { case: { $gte: [ "$average", 80 ] }, then: "B" },
                               { case: { $gte: [ "$average", 70 ] }, then: "C" },
                               { case: { $gte: [ "$average", 60 ] }, then: "D" }
                           ],
                           default: "F"
     } } } }
   ],
   { multi: true }                                                          4
)
1 The 1st $set stage calculates a new field average based on the average of the tests field.
2 The 2nd $set stage calculates a new field grade based on the average field calculated by the first aggregation stage.
3 The pipeline is run on the students collection and uses Student for the aggregation field mapping.
4 Apply the update to all matching documents in the collection.

Upsert

与执行 updateFirst 操作相关,你还可以执行 upsert 操作,如果未找到与查询匹配的文档,则将执行插入操作。插入的文档是查询文档和更新文档的组合。下面的示例显示了如何使用 upsert 方法:

Related to performing an updateFirst operation, you can also perform an upsert operation, which will perform an insert if no document is found that matches the query. The document that is inserted is a combination of the query document and the update document. The following example shows how to use the upsert method:

  • Imperative

  • Reactive

UpdateResult result = template.update(Person.class)
  .matching(query(where("ssn").is(1111).and("firstName").is("Joe").and("Fraizer").is("Update"))
  .apply(update("address", addr))
  .upsert();
Mono<UpdateResult> result = template.update(Person.class)
  .matching(query(where("ssn").is(1111).and("firstName").is("Joe").and("Fraizer").is("Update"))
  .apply(update("address", addr))
  .upsert();

upsert 不支持排序。请使用 findAndModify 来申请 Sort

upsert does not support ordering. Please use findAndModify to apply Sort.

如果 @Version 属性中未包含 Update,则会自动对其进行初始化。请在 Optimistic Locking 部分中了解更多信息。

@Version properties if not included in the Update will be automatically initialized. Read more in the see Optimistic Locking section.

Replacing Documents in a Collection

通过 MongoTemplate 提供的各种 replace 方法允许覆盖第一个匹配的文档。如果没有找到匹配项,可以通过提供带有相应配置的 ReplaceOptions 来插入新匹配项(如上一部分中所述)。

The various replace methods available via MongoTemplate allow to override the first matching Document. If no match is found a new one can be upserted (as outlined in the previous section) by providing ReplaceOptions with according configuration.

Replace one
Person tom = template.insert(new Person("Motte", 21)); 1
Query query = Query.query(Criteria.where("firstName").is(tom.getFirstName())); 2
tom.setFirstname("Tom"); 3
template.replace(query, tom, ReplaceOptions.none()); 4
1 Insert a new document.
2 The query used to identify the single document to replace.
3 Set up the replacement document which must hold either the same _id as the existing or no _id at all.
4 Run the replace operation. .Replace one with upsert
Person tom = new Person("id-123", "Tom", 21) 1
Query query = Query.query(Criteria.where("firstName").is(tom.getFirstName()));
template.replace(query, tom, ReplaceOptions.replaceOptions().upsert()); 2
1 The _id value needs to be present for upsert, otherwise MongoDB will create a new potentially with the domain type incompatible ObjectId. As MongoDB is not aware of your domain type, any @Field(targetType) hints are not considered and the resulting ObjectId might be not compatible with your domain model.
2 Use upsert to insert a new document if no match is found

不可能通过替换操作更改现有文档的 _id。对于 upsert,MongoDB 使用两种方法来确定条目的新 ID:* _id 在查询中以 {"_id" : 1234 } 这种方式使用* _id 显示在替换文档中如果不以任何方式提供 _id,MongoDB 将为该文档创建一个新的 ObjectId。如果所使用的域类型 id 属性具有 Long 等不同类型,则这可能导致映射和数据查找出现故障。

It is not possible to change the _id of existing documents with a replace operation. On upsert MongoDB uses 2 ways of determining the new id for the entry: * The _id is used within the query as in {"_id" : 1234 } * The _id is present in the replacement document. If no _id is provided in either way, MongoDB will create a new ObjectId for the document. This may lead to mapping and data lookup malfunctions if the used domain types id property has a different type like e.g. Long.

Find and Modify

MongoCollection 上的 findAndModify(…) 方法可以在一次操作中更新文档并返回旧文档或新更新的文档。MongoTemplate 提供了四种重载的 findAndModify 方法,它们采用 QueryUpdate 类,并将 Document 转换为您的 POJO:

The findAndModify(…) method on MongoCollection can update a document and return either the old or newly updated document in a single operation. MongoTemplate provides four findAndModify overloaded methods that take Query and Update classes and converts from Document to your POJOs:

<T> T findAndModify(Query query, Update update, Class<T> entityClass);

<T> T findAndModify(Query query, Update update, Class<T> entityClass, String collectionName);

<T> T findAndModify(Query query, Update update, FindAndModifyOptions options, Class<T> entityClass);

<T> T findAndModify(Query query, Update update, FindAndModifyOptions options, Class<T> entityClass, String collectionName);

以下示例向容器中插入一些 Person 对象并执行 findAndUpdate 操作:

The following example inserts a few Person objects into the container and performs a findAndUpdate operation:

template.insert(new Person("Tom", 21));
template.insert(new Person("Dick", 22));
template.insert(new Person("Harry", 23));

Query query = new Query(Criteria.where("firstName").is("Harry"));
Update update = new Update().inc("age", 1);

Person oldValue = template.update(Person.class)
  .matching(query)
  .apply(update)
  .findAndModifyValue(); // oldValue.age == 23

Person newValue = template.query(Person.class)
  .matching(query)
  .findOneValue(); // newValye.age == 24

Person newestValue = template.update(Person.class)
  .matching(query)
  .apply(update)
  .withOptions(FindAndModifyOptions.options().returnNew(true)) // Now return the newly updated document when updating
  .findAndModifyValue(); // newestValue.age == 25

FindAndModifyOptions 方法允许您设置 returnNewupsertremove 的选项。示例延续自上一个代码片段,如下所示:

The FindAndModifyOptions method lets you set the options of returnNew, upsert, and remove. An example extending from the previous code snippet follows:

Person upserted = template.update(Person.class)
  .matching(new Query(Criteria.where("firstName").is("Mary")))
  .apply(update)
  .withOptions(FindAndModifyOptions.options().upsert(true).returnNew(true))
  .findAndModifyValue()

如果 @Version 属性未包含在 Update 中,则会自动对其进行递增。请在 Optimistic Locking 部分中了解更多信息。

@Version properties if not included in the Update will be automatically incremented. Read more in the see Optimistic Locking section.

Find and Replace

替换整个 Document 的最直接方法是使用 save 方法通过其 id 进行替换。但这可能并不总可行。findAndReplace 提供了一种替代方案,允许通过简单的查询来识别要替换的文档。

The most straight forward method of replacing an entire Document is via its id using the save method. However this might not always be feasible. findAndReplace offers an alternative that allows to identify the document to replace via a simple query.

Example 3. Find and Replace Documents
Optional<User> result = template.update(Person.class)      1
    .matching(query(where("firstame").is("Tom")))          2
    .replaceWith(new Person("Dick"))
    .withOptions(FindAndReplaceOptions.options().upsert()) 3
    .as(User.class)                                        4
    .findAndReplace();                                     5
1 Use the fluent update API with the domain type given for mapping the query and deriving the collection name or just use MongoOperations#findAndReplace.
2 The actual match query mapped against the given domain type. Provide sort, fields and collation settings via the query.
3 Additional optional hook to provide options other than the defaults, like upsert.
4 An optional projection type used for mapping the operation result. If none given the initial domain type is used.
5 Trigger the actual processing. Use findAndReplaceValue to obtain the nullable result instead of an Optional.

请注意,替换本身不能包含 id,因为现有 Documentid 由存储本身已转到替换中。还要记住,findAndReplace 只会替换第一个与查询标准匹配的文档,具体取决于可能提供的排序顺序。

Please note that the replacement must not hold an id itself as the id of the existing Document will be carried over to the replacement by the store itself. Also keep in mind that findAndReplace will only replace the first document matching the query criteria depending on a potentially given sort order.

Delete

您可以使用五种重载方法之一从数据库中删除对象:

You can use one of five overloaded methods to remove an object from the database:

template.remove(tywin, "GOT");                                              1

template.remove(query(where("lastname").is("lannister")), "GOT");           2

template.remove(new Query().limit(3), "GOT");                               3

template.findAllAndRemove(query(where("lastname").is("lannister"), "GOT");  4

template.findAllAndRemove(new Query().limit(3), "GOT");                     5
1 Remove a single entity specified by its _id from the associated collection.
2 Remove all documents that match the criteria of the query from the GOT collection.
3 Remove the first three documents in the GOT collection. Unlike <2>, the documents to remove are identified by their _id, running the given query, applying sort, limit, and skip options first, and then removing all at once in a separate step.
4 Remove all documents matching the criteria of the query from the GOT collection. Unlike <3>, documents do not get deleted in a batch but one by one.
5 Remove the first three documents in the GOT collection. Unlike <3>, documents do not get deleted in a batch but one by one.

Optimistic Locking

@Version 注释提供了类似于 JPA 在 MongoDB 上下文中的语法,并确保仅将更新应用于具有匹配版本的文档。因此,在将版本属性的实际值添加到更新查询时,如果其他操作在此期间更改了文档,则更新不会产生任何影响。在这种情况下,将引发 OptimisticLockingFailureException。以下示例演示了这些功能:

The @Version annotation provides syntax similar to that of JPA in the context of MongoDB and makes sure updates are only applied to documents with a matching version. Therefore, the actual value of the version property is added to the update query in such a way that the update does not have any effect if another operation altered the document in the meantime. In that case, an OptimisticLockingFailureException is thrown. The following example shows these features:

@Document
class Person {

  @Id String id;
  String firstname;
  String lastname;
  @Version Long version;
}

Person daenerys = template.insert(new Person("Daenerys"));                            1

Person tmp = template.findOne(query(where("id").is(daenerys.getId())), Person.class); 2

daenerys.setLastname("Targaryen");
template.save(daenerys);                                                              3

template.save(tmp); // throws OptimisticLockingFailureException                       4
1 Intially insert document. version is set to 0.
2 Load the just inserted document. version is still 0.
3 Update the document with version = 0. Set the lastname and bump version to 1.
4 Try to update the previously loaded document that still has version = 0. The operation fails with an OptimisticLockingFailureException, as the current version is 1.

只有 MongoTemplate 上的某些 CRUD 操作会考虑和更改版本属性。请查阅 MongoOperations Java 文档以获取详细信息。

Only certain CRUD operations on MongoTemplate do consider and alter version properties. Please consult MongoOperations java doc for detailed information.

乐观锁定要求将 WriteConcern 设置为 ACKNOWLEDGED。否则 OptimisticLockingFailureException 可能被悄悄吸收。

Optimistic Locking requires to set the WriteConcern to ACKNOWLEDGED. Otherwise OptimisticLockingFailureException can be silently swallowed.

从 2.2 版本开始,MongoOperations 在从数据库中移除实体时,还包含 @Version 属性。要删除 Document 而无需版本检查,请使用 MongoOperations#remove(Query,…​) 代替 MongoOperations#remove(Object)

As of Version 2.2 MongoOperations also includes the @Version property when removing an entity from the database. To remove a Document without version check use MongoOperations#remove(Query,…​) instead of MongoOperations#remove(Object).

从 2.2 版本开始,储存库会在删除版本化实体时,检查已确认删除的结果。如果无法通过 CrudRepository.delete(Object) 删除版本化实体,则会引发 OptimisticLockingFailureException。在这种情况下,版本已更改或对象已在此时删除。使用 CrudRepository.deleteById(ID) 绕过乐观锁定功能,并删除不考虑其版本的任何对象。

As of Version 2.2 repositories check for the outcome of acknowledged deletes when removing versioned entities. An OptimisticLockingFailureException is raised if a versioned entity cannot be deleted through CrudRepository.delete(Object). In such case, the version was changed or the object was deleted in the meantime. Use CrudRepository.deleteById(ID) to bypass optimistic locking functionality and delete objects regardless of their version.