Spring Transaction Management


🔹 What is Transaction Management?


Transaction Management ensures that a set of database operations either:
  • 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
Example: Money Transfer
  1. Debit account A
  2. Credit account B

    👉 If step 2 fails, step 1 must roll back.



🔹 Transaction Management Approaches in Spring


1. Declarative Transaction Management
  • 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