Skip to content

Transaction Bound Events πŸ’«

Lyes S edited this page Jul 10, 2022 · 5 revisions

Table Of Contents

Why Transaction-Bound Events ?

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.

Implementing Custom Transaction Event Listeners

Context

  • Placing an order and send a confirmation email to a customer.

Use Case To Avoid /!\

  • Placing an order and send a confirmation email to a customer when a rollback has occurred.
Ref.:[1]

Order Service

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));
    }
}

Email Listener

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());
    }
}

Important

  1. @TransactionalEventListener works within @Transactional boundary (class or method level)
  2. 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)
Clone this wiki locally