Based on an article, Better application events in Spring Framework 4.2, I set up all related classes. The most of my code works as desired with an exception in a method of a listener.
The controller:
@PostMapping("/Foos")
public ResponseEntity<Foo> handle(@RequestBody Foo foo){
Optional<Foo> f = fooService.save(foo);
return f.isPresent() ? new ResponseEntity<>(f, HttpStatus.OK) :
new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
The service:
@Transactional
public Optional<Foo> save(Foo foo){
foo = fooRepository.save(foo);
publisher.publishEvent(new FooEvent(foo));
return Optional.of(foo);
}
Without @Transcational in the above method, the following method won't be triggered.
The Listener
@Async
@TransactionalEventListener(condition ="#event.ok", phase = TransactionPhase.AFTER_COMMIT)
public void handle(FooEvent event){
Product product = new Product(event.getData());
productService.save(product);
}
The ProductService
@Transactional(propagation = Propagation.REQUIRES_NEW)
public Optional<Product> save(Product product){
product = productRepository.save(product);
return Optional.of(product);
}
The Product data isn't saved at all although the listener method is invoked. The code is run in a Spring Boot app BTW. I haven't found any related information online yet. How to solve this problem?
An EventListener that is invoked according to a TransactionPhase . This is an annotation-based equivalent of TransactionalApplicationListener . If the event is not published within an active transaction, the event is discarded unless the fallbackExecution() flag is explicitly set.
transaction. Synchronization interface, which issues notifications before and after a transaction is completed. Objects implementing this interface can be registered with a Transaction object.
Interface ApplicationEventPublisher This is a functional interface and can therefore be used as the assignment target for a lambda expression or method reference. @FunctionalInterface public interface ApplicationEventPublisher. Interface that encapsulates event publication functionality.
By default spring events are synchronous, meaning the publisher thread blocks until all listeners have finished processing the event.
As per java documentation, @TransactionalEventListener work within @Transactional boundary. If the event is not published within the boundaries of a managed transaction, the * event is discarded unless the {@link #fallbackExecution} flag is explicitly set.
The default phase of `TransactionalEventListener` is `AFTER_COMMIT`. If you want to run something right before the current transaction completes, try `@TransactionalEventListener (phase = TransactionPhase.BEFORE_COMMIT)`
If you expect that if the message consumed by your event listener can be available again in case of exception during the event listener it is a feature not supported in this case you should be think about a message broker like rabbitMQ that support transactional messaging.
It is also possible to make the event listener conditional by defining a boolean SpEL expression on the @EventListener annotation. In this case, the event handler will only be invoked for a successful GenericSpringEvent of String:
The solution might be different depending on what you want to achieve:
If you want to save product within the scope of existing transaction (where you published an event) then just change the phase to TransactionPhase.BEFORE_COMMIT
and you should be good.
If you want to save product within the new independent transaction just after the previous one then add a @Transactional(propagation = Propagation.REQUIRES_NEW)
to your handle method and left everything else as is.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With