Transactional not working while implementing AbstractMongoEventListener

80 views Asked by At

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.

0

There are 0 answers