Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Save Data In A Method of @EventListener or @TransactionalEventListener

Tags:

spring

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?

like image 423
vic Avatar asked Jun 26 '17 02:06

vic


People also ask

What is TransactionalEventListener?

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.

What is transaction synchronization?

transaction. Synchronization interface, which issues notifications before and after a transaction is completed. Objects implementing this interface can be registered with a Transaction object.

What is ApplicationEventPublisher?

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.

Are spring events synchronous?

By default spring events are synchronous, meaning the publisher thread blocks until all listeners have finished processing the event.

Where does @transactionaleventlistener work?

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.

What is the default phase of transactional event listener?

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

Can the message consumed by an event listener be available again?

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.

Is it possible to make the event listener conditional?

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:


1 Answers

The solution might be different depending on what you want to achieve:

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

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

like image 96
Bohdan Levchenko Avatar answered Oct 18 '22 20:10

Bohdan Levchenko