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).