Understanding Spring Transaction Management: Concepts, Propagation, Isolation, and Practical Examples
This article explains Spring's transaction management, covering ACID principles, transaction propagation types, isolation levels, timeout settings, read‑only flags, rollback behavior, and the effect of asynchronous execution, supplemented with practical code examples and common pitfalls.
Questions
What is a transaction?
What functions does Spring transaction provide?
Will Spring transaction isolation override the database isolation level?
What is the difference between NESTED and REQUIRED propagation?
Does a Spring transaction work in asynchronous threads?
Introduction
In everyday development a transaction means a group of SQL statements that must either all succeed or all fail, guaranteeing data consistency (ACID).
Atomicity : the smallest indivisible unit of work.
Consistency : data remains consistent before and after the transaction.
Isolation : concurrent transactions do not interfere with each other.
Durability : once committed, changes survive failures.
Spring Transaction
Spring implements transaction management using AOP. It can be used programmatically or declaratively; the article focuses on the declarative approach with the @Transactional annotation.
Programmatic : flexible but hard to maintain.
Declarative : moves transaction code out of business methods; simply annotate with @Transactional .
Usage
Most scenarios use the @Transactional annotation. The annotation definition is shown below:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
@AliasFor("transactionManager")
String value() default "";
@AliasFor("value")
String transactionManager() default "";
Propagation propagation() default Propagation.REQUIRED;
Isolation isolation() default Isolation.DEFAULT;
boolean readOnly() default false;
int timeout() default -1;
Class
[] rollbackFor() default {};
String[] rollbackForClassName() default {};
Class
[] noRollbackFor() default {};
String[] noRollbackForClassName() default {};
}The annotation can be placed on a class (affecting all its methods) or on a single method.
Preparation
SQL to create the required tables:
-- user table
create table temp_user (
id bigint primary key,
name varchar(255)
);
-- user action table
create table temp_user_action (
id bigint primary key,
user_id bigint,
action smallint
);Java repository fields and a simple service method:
@Resource
private TempUserRepo tempUserRepo;
@Resource
private TempUserActionRepo tempUserActionRepo;
@Transactional
public void test() {
tempUserRepo.saveUser(new TempUser(1, "1"));
tempUserActionRepo.saveUserAction(new TempUserAction(1, 1, 1));
}Propagation
Spring defines seven propagation behaviors:
REQUIRED : join existing transaction or create a new one.
SUPPORTS : join if a transaction exists, otherwise execute non‑transactionally.
MANDATORY : must run within a transaction, otherwise throw an exception.
REQUIRES_NEW : always start a new transaction, suspending the current one.
NOT_SUPPORTED : suspend any existing transaction.
NEVER : must not run within a transaction.
NESTED : run a nested transaction; if the outer transaction rolls back, the nested one rolls back as well.
The default is REQUIRED . An example demonstrates the difference between NESTED and REQUIRED when an exception occurs inside the nested call.
@Transactional
public void test() {
tempUserRepo.saveUser(new TempUser(1, "spring事务"));
tempUserActionRepo.saveUserAction(new TempUserAction(1, 1, 1));
}
@Transactional(propagation = Propagation.NESTED)
public void saveUser(TempUser tempUser) {
tempUserMapper.insert(tempUser);
}
@Transactional(propagation = Propagation.NESTED)
public void saveUserAction(TempUserAction tempUserAction) {
tempUserActionMapper.insert(tempUserAction);
try {
int i = 1 / 0;
} catch (Exception e) {
throw new RuntimeException();
}
}Result: the user record is committed, the user‑action record is rolled back because the nested transaction created a savepoint.
Isolation
Setting isolation level in Spring translates to executing SET TRANSACTION ISOLATION LEVEL … before the transaction starts. Example using READ_UNCOMMITTED :
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public void test() {
TempUser tempUser = tempUserRepo.getById(1);
log.info("User: {}", tempUser);
}The database still reports its global level (e.g., REPEATABLE_READ), but the current transaction runs with the overridden level.
Timeout
Timeout is specified in seconds; if the method exceeds the limit, a TransactionTimedOutException is thrown.
@Transactional(propagation = Propagation.REQUIRES_NEW, timeout = 1)
public void test() {
try { Thread.sleep(3000L); } catch (Exception e) {}
tempUserRepo.saveUser(new TempUser(2, "spring事务"));
}Read‑Only
When readOnly = true , any write operation will cause a Connection is read‑only exception.
Rollback / No‑Rollback
By default, any RuntimeException triggers a rollback. The noRollbackFor attribute can exclude specific exceptions (usually non‑database exceptions).
@Transactional(noRollbackFor = {RuntimeException.class})
public void test() {
tempUserRepo.saveUser(new TempUser(2, "spring事务"));
tempUserActionRepo.saveUserAction(new TempUserAction(2, 2, 2));
try { int i = 1/0; } catch (Exception e) { throw new RuntimeException(); }
}Asynchronous Threads
Annotating a method called from a new thread with @Transactional creates a separate transaction that works independently of the main thread.
@Transactional
public void test() {
tempUserActionRepo.saveUserAction(new TempUserAction(3, 1, 1));
new Thread(() -> asyncService.asyncMethod()).start();
tempUserActionRepo.saveUserAction(new TempUserAction(4, 1, 1));
}
@Transactional
public void asyncMethod() {
tempUserRepo.saveUser(new TempUser(1, "spring事务"));
try { int i = 10/0; } catch (Exception e) { throw new RuntimeException(); }
tempUserActionRepo.saveUserAction(new TempUserAction(1, 1, 1));
}Both actions in the async method are rolled back, while the main thread actions are committed.
Precautions
No transaction manager configured.
Service not managed by Spring.
Method is final , static , or non‑public.
Internal method calls within the same class.
Database does not support transactions.
Exception swallowed without rethrowing.
Conclusion
Spring transactions provide ACID guarantees and extend them with propagation, isolation, timeout, read‑only, and fine‑grained rollback control. Spring can override the database isolation level with SET TRANSACTION ISOLATION LEVEL … , and NESTED uses savepoints to roll back only part of the work, while REQUIRED rolls back the whole transaction. Transactions also work in asynchronous threads when the method is annotated.
Selected Java Interview Questions
A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!
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.