Home > Software design >  Why does the repository in an application event handler provide values that have not been updated?
Why does the repository in an application event handler provide values that have not been updated?

Time:05-19

I am working on a Spring Boot application with the JpaRepository.
There are actions in the application that are triggered by a scheduler.
After changing and saving an entity, an event is triggered to notify the rest of the system.
I now have the problem that the value was not updated when the event handler retrieves the entity from the repo.

Below is a highly simplified code section that hopefully clarifies the problem.

A scheduler triggers an action every given time:

@Component
public class MyScheduler {
    //...
    @Scheduled(cron = "0 * * * * *")
    public void scheduleSomething() {
        entityService.doSomething();
    }
    //...

A service modifies and stores an entity and then triggers an event to notify the system:

    //...
    @Transactional
    @Override
    public void doSomething() {
        //...
        var entityId = // some other code
        var entity = entityRepo.findById(entityId);
        entity.setName("new name");
        eventMulticaster.multicastEvent(new MyEvent<>(this, entityRepo.save(entity)));
        // If the entity is retrieved from the repo again, the name has been correctly changed!
        //...
    //...

When the event handler queries the entity, the value is not updated:

    //...
    @Transactional
    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        //...
        var entityFormEvent = ((MyEntity) event.getPayload());
        print(entityFromEvent.getName()); // Prints the updated value.

        var entityFromRepo = entityRepo.findById(entityFromEvent);
        print(entityFromRepo.getName()); // Prints the value before the change!
        //...

It would be great if someone could help me and explain what I am doing wrong.
Thank you very much!

CodePudding user response:

Spring Event Listeners are synchronous unless you explicitly use @Async annotation and make it asynchronous. This means when the below line is called the transaction of doSomething is not yet committed.

eventMulticaster.multicastEvent(new MyEvent<>(this, entityRepo.save(entity)));

Thus you will not see the changes in onApplicationEvent method when you retrieve from entityRepo.

You can try using JpaRepository.saveAndFlush which will save and flush the changes immediately to Database and it should work.

eventMulticaster.multicastEvent(new MyEvent<>(this, entityRepo.saveAndFlush(entity)));

UPDATE

The other thing you can try is removing the @Transactional annotation from your onApplicationEvent method which will force the transaction created from doSomething method to be used.

CodePudding user response:

You're not seeing the changes in the DB from your event listener because it's invoked in the same uncompleted transaction context as the event publisher (doSomething()).

If you don't want to save the entity manually prior to sending it as an event payload, you can use the @TransactionalEventListener-annotated method instead of your ApplicationListener implementation. This will make the listener be invoked only after the publisher's transaction is committed by default (also, it's possible to attach it to other transaction phases).

  • Related