I try to save a Parent document containing a list of Child document. While saving a Parent, i check if the nested Children need to be saved based on an annotation. if yes, i save the Child document before the parent document and i want to cancel the operations if the Parent document can t be saved.
To test it, i throw an exception just after saving the Child Document and check if the document is saved.
Here the code i use, this is my implementation of AbstractMongoEventListener.onBeforeConvert():
@Component
public class CascadeSaveMongoListener extends AbstractMongoEventListener<Object> {
private final ReactiveMongoTemplate reactiveMongoTemplate;
public CascadeSaveMongoListener(ReactiveMongoTemplate reactiveMongoTemplate) {
this.reactiveMongoTemplate = reactiveMongoTemplate;
}
@Override
public void onBeforeConvert(BeforeConvertEvent<Object> event) {
ReflectionUtils.doWithFields(event.getSource().getClass(), new CascadeSaveCallback(event, reactiveMongoTemplate));
}
}
and this Callback:
public class CascadeSaveCallback implements ReflectionUtils.FieldCallback {
private MongoMappingEvent event;
private ReactiveMongoTemplate reactiveMongoTemplate;
CascadeSaveCallback(MongoMappingEvent event, ReactiveMongoTemplate reactiveMongoTemplate) {
this.event = event;
this.reactiveMongoTemplate = reactiveMongoTemplate;
}
@Override
public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
ReflectionUtils.makeAccessible(field);
if (field.isAnnotationPresent(DBRef.class) && field.isAnnotationPresent(CascadeSave.class)) {
final Object fieldValue = field.get(event.getSource());
if (Collection.class.isAssignableFrom(fieldValue.getClass())) {
Mono<Collection<Object>> mono = Mono.just((Collection<Object>) fieldValue);
reactiveMongoTemplate.insertAll(mono).then().subscribe();
throw new IllegalArgumentException("Not yet implemented");
} else {
reactiveMongoTemplate.insert(fieldValue);
}
}
}
}
To give you more information, I use mongo with docker from the official image and here the command i use to run it with replica and enable transaction :
$ docker run --name my-mongo -p 27017:27017 -d mongo --replSet rs0 && sleep 2 && docker exec my-mongo mongosh --eval "rs.initiate();"
and here the SpringBootApplication class, the service, the Entity and my mongoReactiveConfig class:
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@EnableTransactionManagement
@EnableMongoRepositories
public class ApiApplication {
public static void main(String[] args) {
SpringApplication.run(ApiApplication.class, args);
}
}
@Service
public class ApiService {
private final ParentRepository parentRepository;
public ApiService(ParentRepository parentRepository) {
this.parentRepository = parentRepository;
}
@Transactional
public Mono<Parent> save(Parent parent) {
return parentRepository.save(parent);
}
}
@Document
@Data
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class Parent extends BaseEntity {
@JsonFormat(pattern = "dd-MM-yyyy HH:mm:ss")
private LocalDateTime startDate;
@DBRef
@CascadeSave
List<Child> children;
}
@Configuration
public class MongoReactiveConfig extends AbstractReactiveMongoConfiguration {
@Bean
ReactiveMongoTransactionManager transactionManager(ReactiveMongoDatabaseFactory factory) {
return new ReactiveMongoTransactionManager(factory);
}
@Override
public MongoClient reactiveMongoClient() {
return MongoClients.create("mongodb://localhost:27017/api");
}
@Override
protected String getDatabaseName() {
return "api";
}
}
First, i tried without injecting the ReactiveMongoTemplate. but the transaction didn t work. Then i tried with it and i get the same result.
When i run the code, i can see the exception, but i still see the Child document in mongoDB and i expect it to not be here.
Do anyone have an idea please ? PS: English is not my native langage, i apologize for any mistakes i did.
Edit : From the logs i get, it seems related to the bound between the thread where ApiService.save() is called and the one used for the custom listener. A new transaction is created, then the custom listener throw an exception and the rollback start. but then i can see the starting of the insert instruction.