在反应式 (webflux) 应用程序中正确使用可变 mongodb 实体
Correct use of mutable mongodb entities in reactive (webflux) application
当前 spring-data-mongodb 项目使用可变实体从数据库加载状态,即使在反应性应用程序中也是如此。这被认为是一种不好的做法,我什至可以在 spring-data-mongodb 项目本身中发现一些问题。例如来自 ReactiveMongoTemplate 的代码片段:
protected <T> Mono<T> doSave(String collectionName, T objectToSave, MongoWriter<Object> writer) {
assertUpdateableIdIfNotSet(objectToSave);
return createMono(collectionName, collection -> {
T toSave = maybeEmitEvent(new BeforeConvertEvent<T>(objectToSave, collectionName)).getSource();
AdaptibleEntity<T> entity = operations.forEntity(toSave, mongoConverter.getConversionService());
Document dbDoc = entity.toMappedDocument(writer).getDocument();
maybeEmitEvent(new BeforeSaveEvent<T>(toSave, dbDoc, collectionName));
return saveDocument(collectionName, dbDoc, toSave.getClass()).map(id -> {
T saved = entity.populateIdIfNecessary(id);
return maybeEmitEvent(new AfterSaveEvent<>(saved, dbDoc, collectionName)).getSource();
});
});
}
如您所见,entity.populateIdIfNecessary(id) 正在另一个线程上改变实体对象,这被认为是多线程应用程序中的错误,除非您使用所有 setter 同步的实体。
使用具有反应性 mongodb 接口的可变实体来处理并发问题的正确和推荐做法是什么?
考虑这个例子:
reactiveMongoOperations.findById("id2", Customer.class) // calls Customer setters on thread1
.zipWith(reactiveMongoOperations.findById("id2", Account.class)) // calls Account setters on thread2
.map(t -> perform(t.getT1(), t.getT2())); // access objects on thread2
为了使此代码安全,您需要具有不可变的客户和帐户 类(spring 数据 mongodb 不支持),或者所有 setter 必须是synchronized/volatile 使您的代码非常线程敏感且容易出错。
我刚刚在有关项目反应堆和内存模型的答案中发现了类似的问题 Project Reactor and the Java memory model。因此,如果您不使用并行运算符,似乎可以使用内存屏障(volatile 关键字)来保证内存一致性。
当前 spring-data-mongodb 项目使用可变实体从数据库加载状态,即使在反应性应用程序中也是如此。这被认为是一种不好的做法,我什至可以在 spring-data-mongodb 项目本身中发现一些问题。例如来自 ReactiveMongoTemplate 的代码片段:
protected <T> Mono<T> doSave(String collectionName, T objectToSave, MongoWriter<Object> writer) {
assertUpdateableIdIfNotSet(objectToSave);
return createMono(collectionName, collection -> {
T toSave = maybeEmitEvent(new BeforeConvertEvent<T>(objectToSave, collectionName)).getSource();
AdaptibleEntity<T> entity = operations.forEntity(toSave, mongoConverter.getConversionService());
Document dbDoc = entity.toMappedDocument(writer).getDocument();
maybeEmitEvent(new BeforeSaveEvent<T>(toSave, dbDoc, collectionName));
return saveDocument(collectionName, dbDoc, toSave.getClass()).map(id -> {
T saved = entity.populateIdIfNecessary(id);
return maybeEmitEvent(new AfterSaveEvent<>(saved, dbDoc, collectionName)).getSource();
});
});
}
如您所见,entity.populateIdIfNecessary(id) 正在另一个线程上改变实体对象,这被认为是多线程应用程序中的错误,除非您使用所有 setter 同步的实体。
使用具有反应性 mongodb 接口的可变实体来处理并发问题的正确和推荐做法是什么? 考虑这个例子:
reactiveMongoOperations.findById("id2", Customer.class) // calls Customer setters on thread1
.zipWith(reactiveMongoOperations.findById("id2", Account.class)) // calls Account setters on thread2
.map(t -> perform(t.getT1(), t.getT2())); // access objects on thread2
为了使此代码安全,您需要具有不可变的客户和帐户 类(spring 数据 mongodb 不支持),或者所有 setter 必须是synchronized/volatile 使您的代码非常线程敏感且容易出错。
我刚刚在有关项目反应堆和内存模型的答案中发现了类似的问题 Project Reactor and the Java memory model。因此,如果您不使用并行运算符,似乎可以使用内存屏障(volatile 关键字)来保证内存一致性。