|
此版本仍在开发中,尚未被视为稳定版。如需最新稳定版本,请使用 Spring Data MongoDB 5.0.4! |
保存、更新和删除文档
MongoTemplate / ReactiveMongoTemplatge 允许您保存、更新和删除领域对象,并将这些对象映射到存储在 MongoDB 中的文档。
命令式(imperative)API 与响应式(reactive)API 的方法签名基本相同,仅在返回类型上有所区别。
同步 API 使用 void、单个 Object 和 List,而其响应式对应版本则使用 Mono<Void>、Mono<Object> 和 Flux。
考虑以下类:
public class Person {
private String id;
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getId() {
return id;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
@Override
public String toString() {
return "Person [id=" + id + ", name=" + name + ", age=" + age + "]";
}
}
鉴于前面示例中的 Person 类,您可以保存、更新和删除该对象,如下例所示:
-
Imperative
-
Reactive
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]
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 通过识别(按照约定)String 属性名称,导致了数据库中存储的 ObjectId 与 Id 之间的隐式转换。
前面的示例旨在展示在 MongoTemplate / ReactiveMongoTemplate 上使用保存、更新和删除操作,而非展示复杂的映射功能。
前面示例中使用的查询语法在“查询文档”一节中有更详细的说明。
MongoDB 要求所有文档都必须包含一个 _id 字段。有关此字段的特殊处理详情,请参阅ID 处理部分。 |
| MongoDB 集合可以包含表示多种类型实例的文档。有关详细信息,请参阅类型映射。 |
插入 / 保存
MongoTemplate 提供了多种便捷方法用于保存和插入您的对象。
若要对转换过程进行更细粒度的控制,您可以向 MappingMongoConverter 注册 Spring 转换器 —— 例如 Converter<Person, Document> 和 Converter<Document, Person>。
| 插入(insert)操作与保存(save)操作的区别在于:如果对象尚未存在,保存操作会执行插入。 |
使用 save 操作的简单情况是保存一个 POJO。 在这种情况下,集合名称由该类的名称(非全限定名)决定。 您也可以在调用 save 操作时指定一个具体的集合名称。您可以使用映射元数据来覆盖用于存储对象的集合。
在执行插入或保存操作时,如果未设置 Id 属性,则假定其值将由数据库自动生成。
因此,为了成功实现 ObjectId 的自动生成,您类中的 Id 属性或字段的类型必须是 String、ObjectId 或 BigInteger。
以下示例展示了如何保存一个文档并检索其内容:
-
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());
提供以下插入和保存操作:
-
voidsave(Object objectToSave):将对象保存到默认集合中。 -
voidsave(Object objectToSave, String collectionName):将对象保存到指定的集合中。
还提供了一组类似的插入操作:
-
voidinsert(Object objectToSave):将对象插入到默认集合中。 -
voidinsert(Object objectToSave, String collectionName):将对象插入到指定的集合中。
如何_id字段在映射层中处理
MongoDB 要求所有文档都必须包含一个 _id 字段。
如果您未提供该字段,驱动程序会分配一个具有自动生成值的 ObjectId,而不会考虑您的领域模型,因为服务器并不了解您所使用的标识符类型。
当您使用 MappingMongoConverter 时,某些规则将决定如何将 Java 类中的属性映射到该 _id 字段:
-
使用
@Id(org.springframework.data.annotation.Id)注解的属性或字段将映射到_id字段。 -
一个没有注解但名为
id的属性或字段会映射到_id字段。
以下说明了在使用 _id(MappingMongoConverter 的默认转换器)时,对映射到文档字段 MongoTemplate 的属性所执行的类型转换(如果有的话)。
-
如果可能,Java 类中声明为
id类型的String属性或字段会通过 Spring 的ObjectId转换并以Converter<String, ObjectId>形式存储。有效的转换规则由 MongoDB Java 驱动程序处理。如果无法转换为ObjectId,则该值将以字符串形式存储在数据库中。 -
声明为
id的Date属性或字段会被转换并存储为ObjectId。 -
在 Java 类中声明为
id的BigInteger属性或字段,会通过使用 Spring 的ObjectId转换并存储为Converter<BigInteger, ObjectId>。
如果 Java 类中不存在前述规则集中指定的任何字段或属性,则驱动程序会生成一个隐式的 _id 字段,但该字段不会映射到 Java 类的任何属性或字段上。
在进行查询和更新时,MongoTemplate 会使用与前述文档保存规则相对应的转换器,以确保查询中使用的字段名称和类型能够与您的领域类中的内容相匹配。
某些环境需要采用自定义的方式来映射 Id 值,例如存储在 MongoDB 中但未经过 Spring Data 映射层处理的数据。文档中可能包含 _id 值,这些值既可以表示为 ObjectId,也可以表示为 String。
从存储中将文档读取回领域类型可以正常工作。但由于隐式的 id 转换,通过 ObjectId 查询文档可能会变得很麻烦,因此无法通过这种方式检索文档。
针对这些情况,@MongoId 提供了对实际 ID 映射尝试的更多控制。
@MongoId 映射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 | 如果给定的 ObjectId 是一个有效的 String 十六进制字符串,则该 id 被视为 ObjectId,否则被视为 String。这对应于 @Id 注解的用法。 |
我的文档保存到哪个集合中?
有两种方式可以管理用于文档的集合名称。
默认使用的集合名称是将类名首字母改为小写后的形式。
因此,com.test.Person 类会被存储在 person 集合中。
您可以通过在 @Document 注解中指定不同的集合名称来自定义该名称。
此外,在调用所选的 MongoTemplate 方法时,也可以通过将自定义的集合名称作为最后一个参数传入来覆盖默认的集合名称。
插入或保存单个对象
MongoDB 驱动程序支持在单个操作中插入一组文档。
MongoOperations 接口中的以下方法支持此功能:
-
insert:插入一个对象。如果已存在具有相同
id的文档,则会生成错误。 -
insertAll:以一个对象的
Collection作为第一个参数。该方法会检查每个对象,并根据前面指定的规则将其插入到相应的集合中。 -
save:保存该对象,覆盖任何可能具有相同
id的对象。
批量插入多个对象
MongoDB 驱动程序支持在一次操作中插入一组文档。
MongoOperations 接口中的以下方法通过 insert 方法或专用的 BulkOperations 接口支持此功能。
-
Imperative
-
Reactive
Collection<Person> inserted = template.insert(List.of(...), Person.class);
Flux<Person> inserted = template.insert(List.of(...), Person.class);
-
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();
|
批处理和批量操作的服务器性能是相同的。 然而,批量操作不会发布生命周期事件。 |
|
任何 |
更新
对于更新操作,您可以使用 MongoOperation.updateFirst 更新找到的第一个文档,也可以使用 MongoOperation.updateMulti 方法或流式 API 中的 all 方法来更新所有匹配查询条件的文档。
以下示例展示了如何使用 SAVINGS 操作符对所有 $inc 账户进行更新,为其余额一次性增加 $50.00 的奖金:
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)风格。
|
|
运行文档更新的方法
-
updateFirst:使用更新后的文档,更新第一个匹配查询文档条件的文档。
-
updateMulti:使用更新后的文档,更新所有匹配查询文档条件的对象。
updateFirst 不支持 MongoDB 8.0 以下版本的排序功能。如果您正在使用较旧的版本,请使用 findAndModify 来应用 Sort。 |
更新操作的索引提示可以通过 Query.withHint(…) 提供。 |
方法在Update类
你可以对 Update 类使用一些“语法糖”,因为它的方法设计为可链式调用。
此外,你还可以通过使用 Update 方法并配合静态导入,快速创建一个新的 public static Update update(String key, Object value) 实例。
Update 类包含以下方法:
-
UpdateaddToSet(String key, Object value)使用$addToSet更新修饰符进行更新 -
UpdatecurrentDate(String key)使用$currentDate更新修饰符进行更新 -
UpdatecurrentTimestamp(String key)使用$currentDate更新修饰符并指定$type为timestamp进行更新 -
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");
聚合管道更新
MongoOperations 和 ReactiveMongoOperations 所公开的更新方法也通过 xref page 接受一个聚合管道(Aggregation Pipeline)。
使用 AggregationUpdate 可以在更新操作中利用MongoDB 4.2 的聚合功能。
在更新操作中使用聚合,可以通过单个操作表达多个阶段和多个条件,从而更新一个或多个字段。
更新可以包含以下阶段:
-
AggregationUpdate.set(…).toValue(…)→$set : { … } -
AggregationUpdate.unset(…)→$unset : [ … ] -
AggregationUpdate.replaceWith(…)→$replaceWith : { … }
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 | 第一个 $set 阶段根据 tests 字段的平均值计算出一个新字段 average。 |
| 2 | 第二个 $set 阶段根据第一个聚合阶段计算出的 average 字段,计算出一个新字段 grade。 |
| 3 | 该管道在 students 集合上运行,并使用 Student 进行聚合字段映射。 |
| 4 | 将更新应用于集合中所有匹配的文档。 |
插入或更新
与执行 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。 |
|
|
替换集合中的文档
通过 replace 提供的各种 MongoTemplate 方法可以覆盖第一个匹配的文档。
如果未找到匹配项,可以通过提供配置适当的 ReplaceOptions 来插入一条新文档(如前一节所述)。
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 | 插入一个新文档。 |
| 2 | 用于标识要替换的单个文档的查询。 |
| 3 | 设置替换文档,该文档必须包含与现有文档相同的 _id,或者完全不包含 _id。 |
| 4 | 执行替换操作。 将 one 替换为 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,该 ID 可能与您的领域类型不兼容。
由于 MongoDB 并不了解您的领域类型,因此任何 @Field(targetType) 提示都不会被考虑,所生成的 ObjectId 可能与您的领域模型不兼容。 |
| 2 | 使用 upsert 在未找到匹配项时插入新文档 |
|
无法通过替换操作更改现有文档的 |
查找并修改
findAndModify(…) 上的 MongoCollection 方法可以在单个操作中更新一个文档,并返回旧文档或新更新的文档。
MongoTemplate 提供了四个重载的 findAndModify 方法,这些方法接收 Query 和 Update 类,并将 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 方法允许你设置 returnNew、upsert 和 remove 选项。
以下是一个基于前述代码片段的示例:
Person upserted = template.update(Person.class)
.matching(new Query(Criteria.where("firstName").is("Mary")))
.apply(update)
.withOptions(FindAndModifyOptions.options().upsert(true).returnNew(true))
.findAndModifyValue()
|
|
查找和替换
替换整个Document最直接的方法是通过其id使用save方法。
然而,这并不总是可行的。
findAndReplace提供了一种替代方案,允许通过一个简单查询来识别要替换的文档。
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 | 针对给定领域类型映射的实际 match 查询。通过查询提供 sort、fields 和 collation 设置。 |
| 3 | 额外的可选钩子,用于提供除默认值之外的选项,例如 upsert。 |
| 4 | 用于映射操作结果的可选投影类型。如果未指定,则使用初始领域类型。 |
| 5 | 触发实际的处理。使用 findAndReplaceValue 来获取可为空的结果,而不是 Optional。 |
请注意,替换文档自身不得包含 id,因为现有 id 的 Document 将由存储本身自动沿用到替换文档中。同时请注意,findAndReplace 仅会根据可能指定的排序顺序,替换第一个匹配查询条件的文档。 |
删除
你可以使用五个重载方法中的任意一个从数据库中删除对象:
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 进行标识:首先执行给定的查询,并应用 sort、limit 和 skip 选项,然后在单独的步骤中一次性全部删除。 |
| 4 | 从 GOT 集合中删除所有符合查询条件的文档。与 <3> 不同,文档不是以批处理方式删除,而是逐个删除。 |
| 5 | 删除 GOT 集合中的前三个文档。与 <3> 不同,这些文档不会以批处理方式删除,而是逐个删除。 |
乐观锁
@Version 注解在 MongoDB 的上下文中提供了与 JPA 类似的语法,并确保更新操作仅应用于具有匹配版本号的文档。
因此,版本属性的实际值会被添加到更新查询中,这样如果在此期间其他操作已修改了该文档,则此次更新将不会产生任何效果。
在这种情况下,会抛出一个 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 | 初始插入文档。version 被设置为 0。 |
| 2 | 加载刚刚插入的文档。version 仍然是 0。 |
| 3 | 使用 version = 0 更新文档。设置 lastname 并将 version 增加至 1。 |
| 4 | 尝试更新之前加载的、其 version = 0 仍保持不变的文档。由于当前 OptimisticLockingFailureException 已为 version,该操作会失败并抛出 1 异常。 |
只有某些对 MongoTemplate 的 CRUD 操作会考虑并修改版本属性。详细信息请参阅 MongoOperations 的 Java 文档。
乐观锁需要服务器返回写入确认(即写入结果响应)。使用 WriteConcern.UNACKNOWLEDGED 可能会导致 OptimisticLockingFailureException 被静默吞掉。 |
从 2.2 版本起,MongoOperations 在从数据库中删除实体时也会包含 @Version 属性。
若要移除一个 Document 而不进行版本检查,请使用 MongoOperations#remove(Query,…),而不是 MongoOperations#remove(Object)。 |
从 2.2 版本开始,当删除带版本控制的实体时,仓库会检查已确认删除操作的结果。
如果通过 OptimisticLockingFailureException 无法删除带版本控制的实体,则会抛出 CrudRepository.delete(Object) 异常。这种情况通常是因为该实体的版本在此期间已被更改,或者该对象已被删除。若要绕过乐观锁机制并无视版本直接删除对象,请使用 CrudRepository.deleteById(ID)。 |