Mastering Spring Boot 3 TransactionExecutionListener with Real‑World Code

This article introduces Spring Boot 3's TransactionExecutionListener, explains its purpose compared to traditional transaction hooks, provides a complete code example—including entity, service, custom listener, and test cases—and demonstrates how to observe transaction phases and handle both successful commits and rollbacks.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Mastering Spring Boot 3 TransactionExecutionListener with Real‑World Code

1. Introduction

Traditional Spring transaction hooks such as TransactionSynchronizationManager#registerSynchronization and @TransactionalEventListener allow callbacks at various transaction stages. Starting with Spring 6.1, the framework adds TransactionExecutionListener, a stateless interface for observing transaction creation and completion, mainly for monitoring and statistics. For resource‑management tasks that require state, the classic TransactionSynchronization approach is still recommended.

2. Practical Example

2.1 Interface Overview

TransactionExecutionListener

defines callbacks before and after each transaction step (begin, commit, rollback). The TransactionExecution argument represents the current transaction—either a TransactionStatus for a PlatformTransactionManager or a ReactiveTransaction for a reactive manager.

public interface TransactionExecutionListener {
    default void beforeBegin(TransactionExecution transaction) {}
    default void afterBegin(TransactionExecution transaction, @Nullable Throwable beginFailure) {}
    default void beforeCommit(TransactionExecution transaction) {}
    default void afterCommit(TransactionExecution transaction, @Nullable Throwable commitFailure) {}
    default void beforeRollback(TransactionExecution transaction) {}
    default void afterRollback(TransactionExecution transaction, @Nullable Throwable rollbackFailure) {}
}

2.2 Entity Definition

@Entity
@Table(name = "t_account")
public class Account {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private BigDecimal amount;
}

2.3 Service Method

@Transactional
public Account save(Account account) {
    return this.accountRepository.saveAndFlush(account);
}

2.4 Custom Listener Implementation

@Component
public class PackTransactionExecutionListener implements TransactionExecutionListener {
    @Override
    public void beforeBegin(TransactionExecution transaction) {
        System.out.printf("准备开始事务之前beforeBegin, 事务名: %s, 是否只读: %s, 是否嵌套: %s%n",
                transaction.getTransactionName(),
                transaction.isReadOnly(),
                transaction.isNested());
    }
    @Override
    public void afterBegin(TransactionExecution transaction, Throwable beginFailure) {
        System.err.printf("准备事务之后afterBegin - %s, 事务名: %s, 是否只读: %s, 是否嵌套: %s, 异常信息: %s%n",
                beginFailure == null ? "成功" : "失败",
                transaction.getTransactionName(),
                transaction.isReadOnly(),
                transaction.isNested(),
                beginFailure == null ? null : beginFailure.getMessage());
    }
    @Override
    public void beforeCommit(TransactionExecution transaction) {
        System.out.printf("提交事务之前beforeCommit, 事务名: %s, 是否只读: %s, 是否嵌套: %s, 是否回滚: %s%n",
                transaction.getTransactionName(),
                transaction.isReadOnly(),
                transaction.isNested(),
                transaction.isRollbackOnly());
    }
    @Override
    public void afterCommit(TransactionExecution transaction, Throwable commitFailure) {
        System.err.printf("提交事务之后afterCommit - %s, 事务名: %s, 是否只读: %s, 是否嵌套: %s, 是否回滚: %s, 异常信息: %s%n",
                commitFailure == null ? "成功" : "失败",
                transaction.getTransactionName(),
                transaction.isReadOnly(),
                transaction.isNested(),
                transaction.isRollbackOnly(),
                commitFailure == null ? null : commitFailure.getMessage());
    }
    @Override
    public void beforeRollback(TransactionExecution transaction) {
        System.out.printf("事务回滚之前beforeRollback, 事务名: %s, 是否只读: %s, 是否嵌套: %s, 是否回滚: %s%n",
                transaction.getTransactionName(),
                transaction.isReadOnly(),
                transaction.isNested(),
                transaction.isRollbackOnly());
    }
    @Override
    public void afterRollback(TransactionExecution transaction, Throwable rollbackFailure) {
        System.err.printf("事务回滚之后afterRollback - %s, 事务名: %s, 是否只读: %s, 是否嵌套: %s, 是否回滚: %s, 异常信息: %s%n",
                rollbackFailure == null ? "成功" : "失败",
                transaction.getTransactionName(),
                transaction.isReadOnly(),
                transaction.isNested(),
                transaction.isRollbackOnly(),
                rollbackFailure == null ? null : rollbackFailure.getMessage());
    }
}

2.5 Test Cases

Normal save operation produces the following console output, confirming that each callback is invoked and reports success.

准备开始事务之前beforeBegin, 事务名: com.pack.service.AccountService.save, 是否只读: false, 是否嵌套: false
准备事务之后afterBegin - 成功, 事务名: com.pack.service.AccountService.save, 是否只读: false, 是否嵌套: false, 异常信息: null
提交事务之前beforeCommit, 事务名: com.pack.service.AccountService.save, 是否只读: false, 是否嵌套: false, 是否回滚: false
提交事务之后afterCommit - 成功, 事务名: com.pack.service.AccountService.save, 是否只读: false, 是否嵌套: false, 是否回滚: false, 异常信息: null

When an exception (e.g., division by zero) is thrown inside the service method, the transaction rolls back and the listener logs rollback callbacks. The failure message shown is the business‑logic exception; the rollback itself does not generate an additional exception.

准备开始事务之前beforeBegin, 事务名: com.pack.service.AccountService.save, 是否只读: false, 是否嵌套: false
准备事务之后afterBegin - 成功, 事务名: com.pack.service.AccountService.save, 是否只读: false, 是否嵌套: false, 异常信息: null
事务回滚之前beforeRollback, 事务名: com.pack.service.AccountService.save, 是否只读: false, 是否嵌套: false, 是否回滚: false
事务回滚之后afterRollback - 成功, 事务名: com.pack.service.AccountService.save, 是否只读: false, 是否嵌套: false, 是否回滚: false, 异常信息: null

3. Conclusion

The newly introduced TransactionExecutionListener offers developers fine‑grained, stateless visibility into each transaction phase, making it ideal for monitoring and statistical analysis. For scenarios that require stateful resource handling or detailed transaction‑bound logic, the traditional TransactionSynchronization mechanism remains the appropriate choice.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

BackendJavaspringSpring BootTransaction ManagementTransactionExecutionListener
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

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.