🔹 What is Transaction Management?
- All succeed → commit
- All fail → rollback
It follows ACID properties:
- Atomicity → All or nothing
- Consistency → Database remains valid
- Isolation → Transactions don’t interfere
- Durability → Once committed, data is permanent
- Debit account A
- Credit account B
👉 If step 2 fails, step 1 must roll back.
🔹 Transaction Management Approaches in Spring
- Uses @Transactional annotation (or XML configuration)
- Recommended for most cases
- Spring opens and closes transactions automatically
- Clean, minimal boilerplate
a) Annotation-based (modern, recommended)
@Service
public class BankServiceDeclarative {
private final AccountRepository repo;
public BankServiceDeclarative(AccountRepository repo) {
this.repo = repo;
}
@Transactional // Declarative, annotation-based
public void transfer(Long fromId, Long toId, Double amount) {
// --- Debit ---
Account from = repo.findById(fromId).orElseThrow();
from.setBalance(from.getBalance() - amount);
repo.save(from);
// --- Credit ---
Account to = repo.findById(toId).orElseThrow();
to.setBalance(to.getBalance() + amount);
repo.save(to);
}
}
b) XML-based (older, still supported)
public class BankServiceXml {
private AccountRepository repo;
public void setRepo(AccountRepository repo) {
this.repo = repo;
}
public void transfer(Long fromId, Long toId, Double amount) {
// Debit
Account from = repo.findById(fromId).orElseThrow();
from.setBalance(from.getBalance() - amount);
repo.save(from);
// Credit
Account to = repo.findById(toId).orElseThrow();
to.setBalance(to.getBalance() + amount);
repo.save(to);
}
}
spring-config.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- Transaction Manager -->
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
<!-- Transaction advice -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="transfer" propagation="REQUIRED"/>
<tx:method name="*" read-only="true"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="bankServiceMethods" expression="execution(* com.example.service.BankServiceXml.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="bankServiceMethods"/>
</aop:config>
</beans>
📌 Behavior:
- Transaction opens at method entry
- Commits on success
- Rolls back on RuntimeException by default
2. Programmatic Transaction Management
- Uses PlatformTransactionManager or TransactionTemplate
- Explicitly starts/commits/rolls back transactions
- Useful for fine-grained or multi-database logic
- Verbose and more error-prone
Example with PlatformTransactionManager
@Service
public class BankServiceProgrammatic {
private final AccountRepository repo;
private final PlatformTransactionManager txManager;
public BankServiceProgrammatic(AccountRepository repo, PlatformTransactionManager txManager) {
this.repo = repo;
this.txManager = txManager;
}
public void transfer(Long fromId, Long toId, Double amount) {
TransactionStatus status = txManager.getTransaction(new DefaultTransactionDefinition());
try {
// Debit
Account from = repo.findById(fromId).orElseThrow();
from.setBalance(from.getBalance() - amount);
repo.save(from);
// Credit
Account to = repo.findById(toId).orElseThrow();
to.setBalance(to.getBalance() + amount);
repo.save(to);
txManager.commit(status);
} catch (Exception ex) {
txManager.rollback(status);
throw ex;
}
}
}
Example with TransactionTemplate
@Service
public class BankServiceTemplate {
private final AccountRepository repo;
private final TransactionTemplate txTemplate;
public BankServiceTemplate(AccountRepository repo, TransactionTemplate txTemplate) {
this.repo = repo;
this.txTemplate = txTemplate;
}
public void transfer(Long fromId, Long toId, Double amount) {
txTemplate.execute(status -> {
Account from = repo.findById(fromId).orElseThrow();
from.setBalance(from.getBalance() - amount);
repo.save(from);
Account to = repo.findById(toId).orElseThrow();
to.setBalance(to.getBalance() + amount);
repo.save(to);
return null;
});
}
}
🔹 Comparison: Declarative vs Programmatic
Aspect | Declarative (@Transactional / XML) | Programmatic (TxManager / Template) |
Definition | Annotations or XML rules | Manual code |
Control | Spring manages | Full developer control |
Boilerplate | Minimal | Verbose |
Flexibility | Good (propagation, isolation) | Maximum (multi-DB, dynamic) |
Use Cases | Service-level business logic | Complex flows, batch jobs |
Error-prone | Less | More |
🔹 Rollback Rules (with Examples)
- Default: Rolls back for RuntimeException & Error
- Does NOT rollback for checked exceptions (Exception, SQLException)
Example 1: Default rollback (RuntimeException 👍 )
@Transactional
public void transferRuntime(Long fromId, Long toId, double amount) {
Account from = repo.findById(fromId).orElseThrow();
from.setBalance(from.getBalance() - amount);
repo.save(from);
throw new RuntimeException("Simulated failure"); // rollback triggered
}
👉 Both debit & credit rollback.
Example 2: Checked exception does NOT rollback 👎
@Transactional
public void transferChecked(Long fromId, Long toId, double amount) throws Exception {
Account from = repo.findById(fromId).orElseThrow();
repo.save(from);
throw new Exception("Checked exception"); // no rollback
}
👉 Debit is committed (partial update).
Example 3: Force rollback for checked exception 👍
@Transactional(rollbackFor = Exception.class)
public void transferRollbackForChecked(Long fromId, Long toId, double amount) throws Exception {
Account from = repo.findById(fromId).orElseThrow();
repo.save(from);
throw new Exception("Will rollback now"); // rollback enforced
}
Example 4: Prevent rollback for RuntimeException 👍
public class NonCriticalException extends RuntimeException {}
@Transactional(noRollbackFor = NonCriticalException.class)
public void transferNoRollback(Long fromId, Long toId, double amount) {
repo.save(new Account("A", 100.0));
throw new NonCriticalException(); // still commits
}
Example 5: Catching exceptions inside @Transactional
@Transactional
public void transferCatch(Long fromId, Long toId, double amount) {
try {
throw new RuntimeException("Problem");
} catch (RuntimeException e) {
System.out.println("Caught inside, no rollback");
// transaction commits
}
}
If you want rollback while catching:
@Transactional
public void transferCatchAndRollback(Long fromId, Long toId, double amount) {
try {
throw new RuntimeException("Problem");
} catch (RuntimeException e) {
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
}
🔹 Transaction Propagation Types with Examples
When one transactional method calls another, propagation defines transaction behavior.
Entities
@Entity
public class Account {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String owner;
private Double balance;
// getters/setters
}
Repository
public interface AccountRepository extends JpaRepository<Account, Long> {}
Service Layer
@Service
public class AccountService {
private final AccountRepository repo;
public AccountService(AccountRepository repo) {
this.repo = repo;
}
@Transactional(propagation = Propagation.REQUIRED)
public void deduct(Long accountId, Double amount) {
Account acc = repo.findById(accountId).orElseThrow();
acc.setBalance(acc.getBalance() - amount);
repo.save(acc);
}
@Transactional(propagation = Propagation.REQUIRED)
public void add(Long accountId, Double amount) {
Account acc = repo.findById(accountId).orElseThrow();
acc.setBalance(acc.getBalance() + amount);
repo.save(acc);
}
}
Bank Service – Propagation Examples
e.g. calling above methods deduct(), add() in BankService class
1. REQUIRED (default)
- Joins existing transaction or creates new
- If parent rolls back → child also rolls back
@Transactional(propagation = Propagation.REQUIRED)
public void transferRequired(Long fromId, Long toId, Double amount) {
accountService.deduct(fromId, amount); // joins parent tx
if (amount > 1000) throw new RuntimeException("Failure");
accountService.add(toId, amount); // joins parent tx
}
👉 Behavior: If failure → both debit & credit rollback.
2. REQUIRES_NEW
- Always starts a new transaction
- Parent transaction is suspended
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void transferRequiresNew(Long fromId, Long toId, Double amount) {
accountService.deduct(fromId, amount);
try {
accountService.addRequiresNew(toId, amount); // runs independently
} catch (Exception e) {
// only child rollback, parent continues
}
}
👉 Behavior: Debit may rollback, credit can commit independently.
3. MANDATORY
- Must run inside existing transaction
- Throws error if no parent transaction exists
@Transactional(propagation = Propagation.MANDATORY)
public void transferMandatory(Long fromId, Long toId, Double amount) {
accountService.deduct(fromId, amount);
accountService.addMandatory(toId, amount); // fails if no parent tx
}
4. SUPPORTS
- Runs in transaction if parent exists, else non-transactional
@Transactional(propagation = Propagation.SUPPORTS)
public void transferSupports(Long fromId, Long toId, Double amount) {
accountService.deduct(fromId, amount);
accountService.addSupports(toId, amount); // joins or runs without tx
}
5. NOT_SUPPORTED
- Always runs without transaction, suspends parent
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void transferNotSupported(Long fromId, Long toId, Double amount) {
accountService.deduct(fromId, amount);
accountService.addNotSupported(toId, amount); // runs without tx
}
6. NEVER
- Fails if a transaction exists
@Transactional(propagation = Propagation.NEVER)
public void transferNever(Long fromId, Long toId, Double amount) {
accountService.deduct(fromId, amount);
accountService.addNever(toId, amount); // error if parent tx exists
}
7. NESTED
- Creates a savepoint inside parent transaction
- If child fails, only child rolls back
@Transactional(propagation = Propagation.NESTED)
public void transferNested(Long fromId, Long toId, Double amount) {
accountService.deduct(fromId, amount);
try {
accountService.addNested(toId, amount); // rollback only nested
} catch (Exception e) {
// parent still continues
}
}
Propagation Summary Table
Propagation | Behavior | Example Use Case |
REQUIRED (default) | Join existing or create new | Money transfer (atomic debit + credit) |
REQUIRES_NEW | Always new, suspends parent | Logging/Audit independent of main tx |
MANDATORY | Must run inside existing tx | Child service forced into parent tx |
SUPPORTS | Join if exists, else no tx | Read-only queries |
NOT_SUPPORTED | Always run without tx | Sending email after transfer |
NEVER | Fail if tx exists | Utility tasks, non-transactional operations |
NESTED | Savepoint inside parent tx | Partially rolling back batch operations |
🔹 Summary tables
1. Quick Reference Table for @Transactional Attributes
Attribute | Description | Default | Example |
propagation | Defines how transactions interact | REQUIRED | @Transactional(propagation = Propagation.REQUIRES_NEW) |
isolation | Isolation level for the transaction | DEFAULT | @Transactional(isolation = Isolation.SERIALIZABLE) |
rollbackFor | Exceptions that trigger rollback | RuntimeException & Error | @Transactional(rollbackFor = Exception.class) |
noRollbackFor | Exceptions that do not trigger rollback | None | @Transactional(noRollbackFor = NonCriticalException.class) |
readOnly | Marks transaction as read-only | false | @Transactional(readOnly = true) |
timeout | Max time allowed for transaction | None | @Transactional(timeout = 30) |
This table is super useful for quickly choosing attributes when writing transactional methods.
2. Rollback Behavior Summary
Exception Type | Default Behavior | Can Configure |
RuntimeException | Rollback | noRollbackFor to prevent rollback |
Checked Exception | No rollback | rollbackFor to enforce rollback |
Error | Rollback | Cannot change |
3. Isolation Levels (Optional Advanced Section)
Isolation Level | Description | Use Case |
DEFAULT | Uses DB default | Most cases |
READ_UNCOMMITTED | Dirty reads allowed | Very rare, high performance |
READ_COMMITTED | Prevents dirty reads | Common default in many DBs |
REPEATABLE_READ | Prevents non-repeatable reads | Medium isolation |
SERIALIZABLE | Highest isolation, prevents phantom reads | Critical financial transactions |
4. Transactional Method Checklist
Before marking a method @Transactional, check:
- Is this method modifying data? Use transactional
- Is this method read-only? Use readOnly = true
- Does it call other transactional methods? Decide propagation
- Do you need custom rollback rules? Set rollbackFor / noRollbackFor
- Is it long-running? Consider timeout
- Will it interact with multiple databases? Programmatic management may be better
5. Quick Propagation Decision Table
Scenario | Recommended Propagation |
Simple service-level method | REQUIRED |
Independent audit/log | REQUIRES_NEW |
Must run inside existing tx | MANDATORY |
Optional transactional method | SUPPORTS |
Should never run in tx | NEVER |
Long-running method that can suspend tx | NOT_SUPPORTED |
Partial rollback within parent tx | NESTED |