Reputation: 1168
I'm working in an event-driven design of spring boot application.
The code consists of following files:
Spring boot: ApplicationEvent File i.e MyBusinessEvent.{java
@Data
@AllArgsConstructor
public class MyBusinessEvent {
private String data;
}
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);
}
}
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:
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
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
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