Why @Transactional Fails: 6 Common Pitfalls and How to Fix Them

This article explains the purpose of Spring's @Transactional annotation, details its configurable attributes, and enumerates six typical scenarios—such as non‑public methods, wrong propagation settings, self‑invocation, and unsupported database engines—that cause the annotation to become ineffective, providing code examples and solutions.

macrozheng
macrozheng
macrozheng
Why @Transactional Fails: 6 Common Pitfalls and How to Fix Them

Yesterday a follower asked which scenarios cause the @Transactional annotation to fail, so this article shares essential knowledge about @Transactional.

Transaction Types

Spring provides two transaction management mechanisms: programmatic (code‑driven) and declarative (annotation‑driven).

Programmatic Transaction

Example of manual transaction control:

try {
    // TODO something
    transactionManager.commit(status);
} catch (Exception e) {
    transactionManager.rollback(status);
    throw new InvoiceApplyException("异常失败");
}

Declarative Transaction

Based on AOP, it decouples business logic from transaction handling. It can be configured via XML (using TX or AOP) or simply by adding the @Transactional annotation.

@Transactional
@GetMapping("/test")
public String test() {
    int insert = cityInfoDictMapper.insert(cityInfoDict);
    return insert + "";
}

@Transactional Overview

Where Can It Be Applied?

The annotation can be placed on interfaces, classes, and class methods.

On class : all public methods inherit the same transaction attributes.

On method : method‑level attributes override class‑level ones.

On interface : not recommended; when using CGLIB proxies it may cause the annotation to be ignored.

Key Attributes

Propagation

Propagation.REQUIRED

: join existing transaction or create a new one. Propagation.SUPPORTS: join if a transaction exists, otherwise execute non‑transactionally. Propagation.MANDATORY: must join an existing transaction, otherwise throw an exception. Propagation.REQUIRES_NEW: always start a new transaction, suspending the current one. Propagation.NOT_SUPPORTED: execute non‑transactionally, suspending any existing transaction. Propagation.NEVER: execute non‑transactionally and throw an exception if a transaction exists. Propagation.NESTED: behaves like REQUIRED.

Isolation

Isolation.DEFAULT
Isolation.READ_UNCOMMITTED
Isolation.READ_COMMITTED
Isolation.REPEATABLE_READ
Isolation.SERIALIZABLE

Timeout

Specifies the transaction timeout in seconds; default is -1 (no timeout).

ReadOnly

Indicates whether the transaction is read‑only; default is false.

rollbackFor

Specifies exception types that should trigger a rollback.

noRollbackFor

Specifies exception types that should **not** trigger a rollback.

Common Scenarios Where @Transactional Fails

1. Applied to non‑public methods

If @Transactional is placed on a protected or private method, Spring will ignore it because the transaction interceptor only processes public methods.

2. Wrong propagation setting

Using propagation values such as SUPPORTS, NOT_SUPPORTED, or NEVER can cause the transaction not to roll back when an exception occurs.

3. Incorrect rollbackFor configuration

Only unchecked exceptions (RuntimeException, Error) trigger rollback by default. To roll back for checked exceptions, specify them with rollbackFor.

// Custom exception that should trigger rollback
@Transactional(propagation=Propagation.REQUIRED, rollbackFor=MyException.class)
public void doSomething() { ... }

4. Self‑invocation within the same class

When a method in the same class calls another @Transactional method, the call bypasses the Spring proxy, so the transaction is not applied.

@Transactional
public Integer insertB() throws Exception { ... }

private Integer A() throws Exception {
    // Direct call – transaction will not be activated
    this.insertB();
    // ...
}

5. Catching exceptions inside the transactional method

If a method catches an exception that should cause a rollback, the transaction will be committed, leading to data inconsistency.

@Transactional
public void A() throws Exception {
    try {
        // business logic that may throw
    } catch (Exception e) {
        e.printStackTrace(); // swallowing the exception disables rollback
    }
}

6. Database engine does not support transactions

MySQL's InnoDB engine supports transactions, while MyISAM does not; using a non‑transactional engine makes @Transactional ineffective.

Conclusion

@Transactional appears simple, but misunderstanding its usage can lead to many pitfalls. Hopefully this guide helps you avoid common mistakes.

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.

Backendjavaaopdatabasespringtransactionaltransaction-management
macrozheng
Written by

macrozheng

Dedicated to Java tech sharing and dissecting top open-source projects. Topics include Spring Boot, Spring Cloud, Docker, Kubernetes and more. Author’s GitHub project “mall” has 50K+ stars.

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.