virsha
virsha

Reputation: 1168

@TransactionalEventListener doesn't works where as @EventListener works like charms for the same context

I'm working in an event-driven design of spring boot application.

The code consists of following files:

  1. Spring boot: ApplicationEvent File i.e MyBusinessEvent.{java

    @Data
    @AllArgsConstructor
    public class MyBusinessEvent {
    
       private String data;
    
    }
    
  2. Event Publisher File: MyBusinessService.java

    @Slf4j
    @Service
    public class MyBusinessService {
    
        private final ApplicationEventPublisher applicationEventPublisher;
    
        @Autowired
        public MyBusinessService(
                ApplicationEventPublisher applicationEventPublisher) {
    
            this.applicationEventPublisher = applicationEventPublisher;
        }
    
        @Override
        public void save() {
            String data = "Testing event data";
            MyBusinessEvent event = new MyBusinessEvent(data);
            applicationEventPublisher.publishEvent(event);
        }
    }
    
  3. EventListener : MyBusinessEventListener.java

    @Slf4j
    @Component
    public class MyBusinessEventListener {
    
        @EventListener
        public void handleEvent(MyBusinessEvent myBusinessEvent) {
            log.info("[MyBusinessEventListener] New event received with following data: {}", myBusinessEvent);
        }
    }
    

The point is when Listener file consists @EventListener then the app works as expected but when I do @TransactionEventListener. For example:

  1. TransactionalEventListener : MyBusinessEventListener.java

    @Slf4j
    @Component
    public class MyBusinessEventListener {
    
        @TransactionalEventListener
        public void handleEvent(MyBusinessEvent myBusinessEvent) {
            log.info("[MyBusinessEventListener] New event received with following data: {}", myBusinessEvent);
        }
    }
    

with @TransactionalEventListener, it doesn't work at all.

The application doesn't through any exception (not even at runtime) but there is no logging as expected.

Is any configuration missing?

Upvotes: 8

Views: 23384

Answers (2)

Shaunak Patel
Shaunak Patel

Reputation: 1671

Add @Transactional annotation to MyBusinessService.save should fix this issue.

As per java documentation, @TransactionalEventListener work within @Transactional boundary.

Here what documentation says,

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.

If you don't want your service method in transaction boundary then use @TransactionalEventListener(fallbackExecution = true)

EDIT: There are three possible solutions to the above problem.

i. Mark class as @Transactional to be considered as transactional

@Transactional
@Service
public class MyBusinessService {

    private final ApplicationEventPublisher applicationEventPublisher;

    @Autowired
    public MyBusinessService(
            ApplicationEventPublisher applicationEventPublisher) {

        this.applicationEventPublisher = applicationEventPublisher;
    }

    @Override
    public void save() {
        String data = "Testing event data";
        MyBusinessEvent event = new MyBusinessEvent(data);
        applicationEventPublisher.publishEvent(event);
    }
 }

ii. Make the specific method of service as @Transactional

@Override
@Transactional
public void save() {
    String data = "Testing event data";
    MyBusinessEvent event = new MyBusinessEvent(data);
    applicationEventPublisher.publishEvent(event);
}

iii. If you don't want your service method in transaction boundary then use

@Slf4j
@Component
public class MyBusinessEventListener {

    @TransactionalEventListener(fallbackExecution = true)
    public void handleEvent(MyBusinessEvent myBusinessEvent) {
        log.info("[MyBusinessEventListener] New event received with following data: {}", myBusinessEvent);
    }
}

Upvotes: 13

Valerio Vaudi
Valerio Vaudi

Reputation: 4532

your ApplicationEventPublisher class should be like below:@Slf4j @Service public

class MyBusinessService {

    private final ApplicationEventPublisher applicationEventPublisher;

    @Autowired
    public MyBusinessService(
            ApplicationEventPublisher applicationEventPublisher) {

        this.applicationEventPublisher = applicationEventPublisher;
    }

    @Override
    @Transactional
    public void save() {
        String data = "Testing event data";
        MyBusinessEvent event = new MyBusinessEvent(data);
        applicationEventPublisher.publishEvent(event);
    }
 }

however pay attention that this capability transactionality in particular is referred to database transactionality.

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.

If you use Spring Cloud yet in your application I can suggest to use Spring Cloud Stream that can be help you to create an event driven application.

The good things with spring cloud stream that it provide you some abstraction that are indipendent from the message broker used.

You can switch between all supported binder whithout change anything in your application

Upvotes: 3

Related Questions