Backend Development 9 min read

Master Spring Boot Transaction Event Listeners: Code Samples & Real-World Use Cases

This article explains how to decouple business logic using Spring Boot transaction event listeners, demonstrates both manual TransactionSynchronization and @TransactionalEventListener implementations with full code examples, and shows a practical user‑registration scenario that avoids common pitfalls.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Master Spring Boot Transaction Event Listeners: Code Samples & Real-World Use Cases

1. Introduction

When you need to decouple operations such as data‑update notifications, asynchronous processing, state tracking, or logging, Spring transaction listeners let you react to specific transaction phases.

Data update notification : Notify other components after insert, update, or delete.

Asynchronous processing : Trigger background tasks during a transaction.

State notification : Record transaction status for monitoring (e.g., commit or rollback).

Logging : Capture key events and exceptions for later analysis.

Since Spring 4.2, listeners can bind to a particular transaction phase, typically handling events after a successful commit.

2. Code Implementation

2.1 Registering TransactionSynchronization

Define a custom TransactionSynchronization to hook into each transaction stage.

<code>public class PersonTransactionSynchronization implements TransactionSynchronization {
  @Override
  public void beforeCommit(boolean readOnly) {
    System.out.printf("事务提交前beforeCommit,只读:%b%n", readOnly);
  }
  @Override
  public void beforeCompletion() {
    System.out.println("事务完成前beforeCompletion");
  }
  @Override
  public void afterCommit() {
    System.out.println("事务提交后afterCommit");
  }
  @Override
  public void afterCompletion(int status) {
    // status: 0=COMMITTED, 1=ROLLED_BACK, 2=UNKNOWN
    System.out.printf("事务提交完成后afterCompletion, 事务状态:%d%n", status);
  }
}
</code>

Register the synchronizer inside a transactional method:

<code>@Transactional
public void save(Person person) {
  TransactionSynchronizationManager.registerSynchronization(new PersonTransactionSynchronization());
  personRepository.save(person);
}
</code>

Test case:

<code>@Test
public void testSave() {
  Person person = new Person();
  int num = new Random().nextInt(100);
  person.setAge(num);
  person.setName("姓名 - " + num);
  personServiceEvent.save(person);
}
</code>

Console output on success:

<code>事务提交前beforeCommit,只读:false
事务完成前beforeCompletion
事务提交后afterCommit
事务提交完成后afterCompletion, 事务状态:0
</code>

If an exception occurs in the save method, the output changes to:

<code>事务完成前beforeCompletion
事务提交完成后afterCompletion, 事务状态:1
</code>

2.2 Using @TransactionalEventListener

Annotate listener methods to react to specific phases.

<code>@Component
public class EntityTransactionListener {
  @TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT)
  public void beforeCommit(EntityEvent event) {
    System.out.println("事务提交前beforeCommit");
  }
  @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
  public void afterCommit(EntityEvent event) {
    System.out.println("事务提交成功后afterCommit");
  }
  @TransactionalEventListener(phase = TransactionPhase.AFTER_COMPLETION)
  public void afterCompletion(EntityEvent event) {
    System.out.println("事务完成后afterCompletion");
  }
  @TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK)
  public void afterRollback(EntityEvent event) {
    System.out.println("事务回滚后afterRollback");
  }
}
</code>

When using the annotation approach, you must publish an event inside the transactional method:

<code>@Transactional
public void save(Person person) {
  eventMulticaster.multicastEvent(new EntityEvent(person));
  personRepository.save(person);
}
</code>

Note: afterCompletion runs regardless of commit or rollback.

3. Real‑World Example: User Registration with Email Confirmation

Goal: Send a confirmation email only after the user record is successfully persisted.

Problematic implementation (email sent inside the same transaction):

<code>@Transactional
public Result registerUser(User user) {
  validator.valid(user);
  userRepository.save(user);
  mailService.sendMail(user);
  // other logic
}
</code>

Issues:

If sendMail throws a runtime exception, the transaction rolls back and the user cannot register.

If an exception occurs after the email is sent, the user registration rolls back but the email cannot be undone, leading to inconsistent state.

Solution: Publish an event after validation and persistence, and handle email sending in a separate listener that runs after commit.

<code>@Transactional
public Result registerUser(User user) {
  eventMulticaster.multicastEvent(new EmailEvent(user)); // publish event
  validator.valid(user);
  userRepository.save(user);
}
</code>
<code>@Component
public class EmailTransactionListener {
  private final EmailService emailService;
  public EmailTransactionListener(EmailService emailService) {
    this.emailService = emailService;
  }
  @Async
  @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
  public void afterCommit(EntityEvent event) {
    emailService.sendMail(event.getSource());
  }
}
</code>

With this design, email sending is asynchronous and will not block the transaction; failures in email delivery do not affect user registration.

Conclusion

Spring Boot provides two flexible ways—manual TransactionSynchronization and the @TransactionalEventListener annotation—to execute code at precise transaction phases, enabling clean separation of concerns such as email notifications, logging, or asynchronous tasks.

JavaTransactionBackend DevelopmentSpring BootEvent ListenerTransactionalEventListener
Spring Full-Stack Practical Cases
Written by

Spring Full-Stack Practical Cases

Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.