Understanding Spring Transaction Management: Propagation, Isolation, and Best Practices
This article explains the fundamentals of database transactions, the ACID properties, Spring's @Transactional support, various propagation behaviors and isolation levels, and provides practical code examples for both declarative and programmatic transaction management in Java backend development.
The article begins with a story about a new colleague who incorrectly rewrote a @Transactional annotation, leading to a discussion on why such changes are harmful.
What is a transaction? A transaction is a logical group of operations that must either all succeed or all fail, ensuring data consistency in databases such as MySQL.
The four ACID properties are described:
Atomicity – all operations complete or none do.
Consistency – database integrity is preserved before and after the transaction.
Isolation – concurrent transactions do not interfere with each other.
Durability – once committed, changes survive system failures.
Isolation levels (Read Uncommitted, Read Committed, Repeatable Read, Serializable) are listed with their effects on phenomena like dirty reads, non‑repeatable reads, and phantom reads.
Spring's support for transactions includes declarative (annotation‑based) and programmatic approaches. The most common declarative method uses a single @Transactional annotation on a public method or class, which Spring translates into a transaction managed by a TransactionManager .
Example of a simple declarative transaction:
@Transactional
public void savePosts(PostsParam postsParam) {
// save article
save(posts);
// handle tags
insertOrUpdateTag(postsParam, posts);
}Programmatic transaction management can be done with TransactionTemplate :
@Autowired
private TransactionTemplate transactionTemplate;
public void testTransaction() {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
try {
// business logic
} catch (Exception e) {
// rollback
status.setRollbackOnly();
}
}
});
}Or using PlatformTransactionManager directly:
@Autowired
private PlatformTransactionManager transactionManager;
public void testTransaction() {
TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
try {
// business logic
transactionManager.commit(status);
} catch (Exception e) {
transactionManager.rollback(status);
}
}The core Spring transaction abstraction is the TransactionManager interface, with implementations such as DataSourceTransactionManager , HibernateTransactionManager , and JpaTransactionManager . The TransactionDefinition interface defines propagation behavior, isolation level, timeout, and read‑only flag, which correspond to attributes of the @Transactional annotation.
All seven propagation types are explained, with examples for PROPAGATION_REQUIRED (default) and PROPAGATION_REQUIRES_NEW , showing how they affect transaction boundaries and rollback behavior.
Isolation levels are mapped to the constants in TransactionDefinition (DEFAULT, READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE) and the article notes that MySQL’s default is REPEATABLE_READ.
Transaction timeout and read‑only attributes are discussed, emphasizing that long‑running transactions can hold database locks and that read‑only transactions allow the database to apply optimizations.
The default rollback rule is that only unchecked (runtime) exceptions trigger a rollback; checked exceptions do not unless explicitly configured with rollbackFor :
@Transactional(rollbackFor = MyException.class)
public void someMethod() { ... }Practical tips are given: @Transactional works only on public methods, it should not be placed on interfaces, and self‑invocation within the same class bypasses the proxy, causing the transaction to be ignored.
Testing the transaction behavior is demonstrated by configuring Spring Boot logging to DEBUG, creating a controller endpoint that updates a user, and observing that the database change is rolled back when a RuntimeException is thrown inside a @Transactional service method.
Finally, the article provides links to the source code repositories for the examples and includes a short promotional block with additional reading suggestions.
IT Services Circle
Delivering cutting-edge internet insights and practical learning resources. We're a passionate and principled IT media platform.
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.