8 Hard‑Earned Rules for Using Spring @Transactional Correctly
Drawing on a decade of production experience, the article presents eight concrete rules for Spring @Transactional—covering transaction duration, proxy limitations, rollback settings, exception handling, read‑only flags, method visibility, bean separation for retries, and logging—to prevent common bugs and ensure reliable database operations.
After more than ten years of using Spring Boot and encountering recurring transaction bugs in production, the author distills eight practical rules for correctly applying @Transactional annotations.
1. Keep transactions short and limited to database work
Long‑running calls to external APIs, email services, or file uploads keep the database connection open for the entire method, exhausting the connection pool under high load and causing the application to hang.
2. Never invoke a transactional method from within the same class
Spring uses a proxy; a direct call like this.method() bypasses the proxy, so the annotation is ignored without warning.
3. Specify rollbackFor = Exception.class for critical operations
By default, only RuntimeException triggers a rollback. Checked exceptions such as PaymentException cause the transaction to commit, leaving the balance deducted while the payment fails.
4. Do not swallow exceptions inside a transaction
If an exception is caught and not re‑thrown, the proxy sees a normal return value and commits the transaction, resulting in partially persisted data. Either re‑throw the exception, log and re‑throw, or call
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()to force a rollback.
5. Use readOnly = true on all read‑only methods
This tells Hibernate to skip dirty checking and snapshot comparison, reducing CPU and memory usage. It may also route queries to a read‑only replica, but it does not prevent explicit save() or flush() calls.
6. Transactional annotations work only on public methods
Spring’s dynamic proxy cannot intercept private methods; such annotations have no effect and produce no warnings.
7. Separate retry logic and transactional logic into different beans
When @Retryable and @Transactional are on the same method, a checked exception causes the transaction to commit before the retry, leading to duplicate data on each retry attempt. Placing retry handling in an outer bean and transaction handling in an inner bean ensures each retry runs in a fresh transaction.
8. Enable transaction tracing and connection‑leak detection in every environment
Set
logging.level.org.springframework.transaction.interceptor: TRACEto log transaction start, commit, and rollback times. Configure spring.datasource.hikari.leak-detection-threshold: 15000 so any connection held longer than fifteen seconds logs a full stack trace, making hidden issues visible.
Following these eight rules—short transactions, proper proxy usage, explicit rollback settings, careful exception handling, read‑only optimization, public method visibility, bean separation for retries, and comprehensive logging—eliminates the majority of transaction‑related bugs in Spring applications.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
