Saving, Updating, and Removing Documents

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

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

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

  • 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 的调试消息):

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 之间 隐式转换。 前面的示例旨在展示对 MongoTemplate / ReactiveMongoTemplate 的保存、更新和删除操作的使用,而不是显示复杂映射功能。在前面的示例中使用的查询语法将在 “Querying Documents” 部分进行更详细的说明。

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

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

Insert / Save

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

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

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

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

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

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

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

  • void save (Object objectToSave):将对象保存到默认集合。

  • void save (Object objectToSave, String collectionName):将对象保存到指定的集合。

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

  • void insert (Object objectToSave): 将对象插入默认集合。

  • void insert (Object objectToSave, String collectionName): 将对象插入到指定集合。

How the _id Field is Handled in the Mapping Layer

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

  1. @Id (org.springframework.data.annotation.Id) 注释的属性或字段映射到 _id 字段。

  2. 一个没有注释但名为 id 的属性或字段映射到 _id 字段。

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

  1. 如果可能,在 Java 类中声明为 Stringid 属性或字段将通过使用 Spring Converter&lt;String, ObjectId&gt; 转换并存储为 ObjectId。有效转换规则委托给 MongoDB Java 驱动程序。如果无法转换为 ObjectId,则该值将作为字符串存储在数据库中。

  2. 在 Java 类中声明为 BigIntegerid 属性或字段将通过使用 Spring Converter&lt;BigInteger, ObjectId&gt; 转换并存储为 ObjectId

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

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

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

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 id 被视为 String,并且没有进一步转换。
2 id 被视为 ObjectId
3 如果给定的 String 是有效的 ObjectId 十六进制,则将 id视为 ObjectId,否则视为 String。对应于 @Id 用法。

Into Which Collection Are My Documents Saved?

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

Inserting or Saving Individual Objects

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

  • insert: 插入一个对象。如果存在具有相同 id 的现有文档,则会生成错误。

  • insertAll: 将一组对象作为第一个参数。此方法会检查每个对象,并根据前面指定的规则将其插入到相应的集合中。

  • save: 保存对象,覆盖可能具有相同 id 的任何对象。

Inserting Several Objects in a Batch

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

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

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

Update

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

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 样式。

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

Methods for Running Updates for Documents

  • updateFirst: 使用更新后的文档更新与查询文档 criteria 匹配的第一个文档。

  • updateMulti: 使用更新后的文档更新所有与查询文档 criteria 匹配的对象。

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

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

Methods in the Update Class

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

Update 类包含以下方法:

  • UpdateaddToSet`(String key, Object value)`使用 `$addToSet`更新修饰符进行更新

  • UpdatecurrentDate`(String key)`使用 `$currentDate`更新修饰符进行更新

  • UpdatecurrentTimestamp(String key)`使用带 `$type``timestamp 的 `$currentDate`更新修饰符进行更新

  • Updateinc`(String key, Number inc)`使用 `$inc`更新修饰符进行更新

  • Updatemax`(String key, Object max)`使用 `$max`更新修饰符进行更新

  • Updatemin`(String key, Object min)`使用 `$min`更新修饰符进行更新

  • Updatemultiply`(String key, Number multiplier)`使用 `$mul`更新修饰符进行更新

  • Updatepop`(String key, Update.Position pos)`使用 `$pop`更新修饰符进行更新

  • Updatepull`(String key, Object value)`使用 `$pull`更新修饰符进行更新

  • UpdatepullAll`(String key, Object[] values)`使用 `$pullAll`更新修饰符进行更新

  • Updatepush`(String key, Object value)`使用 `$push`更新修饰符进行更新

  • UpdatepushAll`(String key, Object[] values)`使用 `$pushAll`更新修饰符进行更新

  • Updaterename`(String oldName, String newName)`使用 `$rename`更新修饰符进行更新

  • Updateset`(String key, Object value)`使用 `$set`更新修饰符进行更新

  • UpdatesetOnInsert`(String key, Object value)`使用 `$setOnInsert`更新修饰符进行更新

  • Updateunset`(String key)`使用 `$unset`更新修饰符进行更新

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

// { $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。在更新中使用聚合允许通过使用单个操作来表达多个阶段和多个条件,从而更新一个或多个字段。

更新可以包含以下阶段:

  • AggregationUpdate.set(&#8230;&#8203;).toValue(&#8230;&#8203;)$set : { &#8230;&#8203; }

  • AggregationUpdate.unset(&#8230;&#8203;)$unset : [ &#8230;&#8203; ]

  • AggregationUpdate.replaceWith(&#8230;&#8203;)$replaceWith : { &#8230;&#8203; }

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 第 1 个 $set 阶段根据 tests 字段的平均值计算一个新字段 average
2 第 2 个 $set 阶段根据第一聚合阶段计算的 average 字段计算一个新字段 grade
3 该管道运行于 students 集合,使用 Student 进行聚合字段映射。
4 将更新应用到集合中所有匹配的文档。

Upsert

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

  • 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

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

Replacing Documents in a Collection

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

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 用于识别需要替换的单个文档的查询。
3 设置替换文档,其中必须包含与现有 _id 相同的 _id 或根本没有 _id
4 运行替换操作..用 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 对于 upsert,_id 值必须是存在的,否则 MongoDB 将创建一个新的,可能会与域类型不匹配的 ObjectId。由于 MongoDB 无法识别你的域类型,所以不会考虑任何 @Field(targetType) 提示,而产生的 ObjectId 可能与你的域模型不兼容。
2 如果找不到匹配项,请使用 upsert 插入新文档

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

Find and Modify

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

<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 操作:

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 的选项。示例延续自上一个代码片段,如下所示:

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 部分中了解更多信息。

Find and Replace

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

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 使用具有给定域类型的流畅更新 API 来映射查询并获取集合名称,或者仅使用 MongoOperations#findAndReplace
2 针对给定域类型映射的实际匹配查询。通过该查询提供 sortfieldscollation 设置。
3 用于提供默认值之外的其他选项的附加可选挂钩,如 upsert
4 用于映射操作结果的可选投影类型。如果未给出,将使用初始域类型。
5 触发实际处理。使用 findAndReplaceValue 获得可为 null 的结果,而不是 Optional

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

Delete

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

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 从相关集合中移除由其 _id 指定的单个实体。
2 GOT 集合中移除所有与查询条件匹配的文档。
3 移除 GOT 集合中的前三个文档。与 <2> 不同的是,要移除的文档由其 _id 标识,先运行给定的查询,应用 sortlimitskip 选项,然后分批移除所有文档。
4 GOT 集合中移除所有与查询条件匹配的文档。与 <3> 不同的是,文档不是批量删除,而是逐个删除。
5 移除 GOT 集合中的前三个文档。与 <3> 不同的是,文档不是批量删除,而是逐个删除。

Optimistic Locking

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

@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 1. 最初插入文档。version 设置为 0
2 2. 加载刚刚插入的文档。version 仍然是 0
3 使用 version = 0 更新文档。设置 lastname,并将 version 增加到 1
4 尝试更新之前加载的仍具有 version = 0 的文档。该操作失败,并带有 OptimisticLockingFailureException,因为当前 version1

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

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

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

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