-
Notifications
You must be signed in to change notification settings - Fork 0
Transaction Bound Events π«
Table Of Contents
Spring allows us to bind an 'EventListener' to a phase of the current transaction.
In Spring, whenever we want our class or methods to be executed in a transaction we use '@Transactional' annotation, it is used to combine multiple writes in a database as a single atomic operation. When a call to a method annotated with @Transactional, all or none of the writes in the database are executed.
What does this looks like using events ?
Ref.:[1]Spring provides us a special listener called TransactionalEventListener. This doesn't mean that the event listener is transactional by itself, but the event consumption is delayed until a certain transaction outcome. This gives us more control on when event listeners should get triggered on transaction context based on the transaction phase.
Ref.:[1]- BEFORE_COMMIT : The event will be handled before the transaction commit.
- AFTER_COMMIT : This is the default phase used. The event will be handled when the transaction gets committed successfully.
- AFTER_ROLLBACK : The event will be handled after the transaction has rolled back.
- AFTER_COMPLETION : The event will be handled when the transaction commits or is rolled back. We can use it in cases we want to run always the listener.
Important Rule
Avoid infrastructure interaction within @EventListener that is part of Transactional context.
- Placing an order and send a confirmation email to a customer.
- Placing an order and send a confirmation email to a customer when a rollback has occurred.
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import static com.events.order.Order.OrderStatus.COMPLETED;
@Slf4j
@RequiredArgsConstructor
@Component
public class OrderService {
private final OrderRepository orderRepository;
private final ApplicationEventPublisher publisher;
@Transactional
public void placeOrder(Order order) {
log.info("Placing and order {}", order);
orderRepository.save(order);
order.setStatus(COMPLETED);
log.info("Publishing order completed event");
publisher.publishEvent(new OrderCompletedEvent(order));
}
}
import com.events.customer.CustomerRegisteredEvent;
import com.events.customer.CustomerRemovedEvent;
import com.events.order.OrderCompletedEvent;
import lombok.RequiredArgsConstructor;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import org.springframework.transaction.event.TransactionalEventListener;
@Component
@RequiredArgsConstructor
public class EmailListeners {
private final EmailService emailService;
@TransactionalEventListener(phase= Transaction.AFTER_COMMIT)
public void onOrderCompletedEvent(OrderCompletedEvent event) {
emailService.sendOrderEmail(event.getOrder());
}
}
- @TransactionalEventListener works within @Transactional boundary (class or method level)
- If not 1 above, then : the event is discarded unless the fallbackExecution() flag is explicitly set. If a transaction is running, the event is handled according to its TransactionPhase. @TransactionalEventListener(fallbackExecution = true)
"A time for everything, and to everything its place
Return what has been moved through time and space."
[Charmed]