Spring has its own trasaction approach and this directly derives from the fact that each data access technology handles transactions differently. For instance, the way transactions are applied in JDBC and JPA differs in the code itself:
JDBC Transaction
public void accountTransfer(String fromId, String toId, int money) throws SQLException {
try {
Connection con = dataSource.getConnection();
con.setAutoCommit(false); // Transaction Begins
// Business Logic
bizLogic(con, fromId, toId, money);
con.commit(); // Commit for Success
} catch (Exception e) {
con.rollback(); // Rollback for Failure
throw new IllegalStateException(e);
} finally {
release(con);
}
}
JPA Transaction
public static void main(String[] args) {
// Create EntityManagerFactory
EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpabook");
EntityManager em = emf.createEntityManager(); // Create EntityManager
EntityTransaction tx = em.getTransaction(); // Acquire Transaction
try {
tx.begin(); // Transaction Begins
logic(em); // Business Logic
tx.commit();// Transaction Commit
} catch (Exception e) {
tx.rollback(); // Transaction Rollback
} finally {
em.close(); // Terminate EntityManager
}
emf.close(); // Terminate EntityManager Factory
}
μΈνλ° (κΉμν) Available here
Therefore, if the application ever requires a switch from the JDBC-based transaction to the JPA-based transaction, all relevant codes to the transaction must be modified. To address this issue, Spring provides transaction abstraction where from the perspective of transaction management, Spring's transaction abstraction allows both JDBC and JPA and any further implementations of transactions to be applied by implementing the Spring defined transaction abstraction.
Spring achieves this abstraction through the PlatformTransactionManager interface:
PlatformTransactionManager
public interface PlatformTransactionManager extends TransactionManager {
TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;
void commit(TransactionStatus status) throws TransactionException;
void rollback(TransactionStatus status) throws TransactionException;
}
PlatformTransactionManager's public interface exposes simple transaction, commit, rollback methods and by its abstraction, developers uniformly implement the transaction execution logics while the responsibility of implementing the actual logic falls to the concrete instances inheriting PlatformTransactionManager interface.

μΈνλ° (κΉμν) Available here
Spring Transaction's implementation can be largely divided into two approaches:
π Declarative Transaction Management
@Transactionalπ§π½βπ» Programmatic Transaction Management
TrasactionManager or Transaction TemplateProgrammatic Transaction Management as a converntional transaction management approach invovles the participation of three elements:
TransactionManager (PlatformTransactionManager)
TransactionDefinition
TransactionStatus
where specifically:
PlatformTransactionManager is responsible for starting, committing, or rolling back a transaction.
TransactionDefinition defines the properties of a transaction. TransactionDefinition can set transaction propagation behaviour, isolation levels, timeouts, and more.
TransactionStatus represents the current state of a transaction. It allows you to check whether the transaction is in progress, has been committed, or has been rolled back.
These overall can be graphically represented as below with its code-level example:

μΈνλ° (κΉμν) Available here
Transaction Code
//Transaction begins
TransactionStatus status = transactionManager.getTransaction(new
DefaultTransactionDefinition());
try {
// Business Logic
bizLogic(fromId, toId, money);
transactionManager.commit(status); //Commit for success
} catch (Exception e) {
transactionManager.rollback(status); //Rollback for failure
throw new IllegalStateException(e);
}
Programmatic Transaction Management tightly couples the application code to the transaction-related technical code in which from a maintenance point of view, having two separate concerns in a single code is not considered ideal, leading to Declarative Transaction Management to be preferred and prevail over real-world projects.
Declarative Transaction Management often comes with @Transactional where in-depth, it internally applies proxy-based AOP:

μΈνλ° (κΉμν) Available here
@Transactional Code (Proxy)
public class MemberService {
@Transactional
public void logic() {
// Business Logic
}
}
public class TransactionProxy {
private MemberService target;
public void logic() {
TransactionStatus status = transactionManager.getTransaction(..);
try {
target.logic();
transactionManager.commit(status);
} catch (Exception e) {
transactionManager.rollback(status);
throw new IllegalStateException(e);
}
}
}
Transaction overall also invovles Transaction Synchronisation Manager which is responsible for handling Connection instances that is preserved within the ThreadLocal instance:

μΈνλ° (κΉμν) Available here
One common issue that often arises is the internal calls within a class from a non-@Transactional method to a method with @Transactional, anticipating that transaction should apply.
This is deeply relevant to AOP proxy that @Transactional essentially underlie and Java:


μΈνλ° (κΉμν) Available here
Specifically, in Java, when a method is called without any explicit reference, it defaults to using 'this', which represents the current instance. For example, invoking this.METHOD_WITH_TRANSACTIION directly points to the actual instance of the target object. Since this kind of internal method call doesn't pass through the proxy, it bypasses any transaction management logic. As a result, the METHOD_WITH_TRANSACTIION on the target object is called directly, and the transaction is not applied.
π‘ One simple
solutionto this is to have separateclassesfornon-@Transactionaland@Transactional methods.
Spring transaction management allows controlling the scope of a transaction through propagation properties. Propagation properties define how a transaction interacts with other transactions. Propagation properties enable flexible configuration of a transaction's scope.
Spring supports various propagation properties, such as:
REQUIRED REQUIRES_NEWNESTED REQUIRED propagation property uses the current transaction if one exists, and starts a new transaction if none exists.
REQUIRES_NEW propagation property always starts a new transaction. If a current transaction exists, it is suspended, and a new transaction begins.
NESTED propagation property starts a nested transaction if a current transaction exists, or starts a new transaction if none exists. NESTED transactions are influenced by the outer transaction, but NESTED transactions do not affect the outer transaction. Even if the nested transaction rolls back, the outer transaction can still commit. However, if the outer transaction rolls back, the nested transaction is also rolled back.