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 Template
Programmatic 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
solution
to this is to have separateclasses
fornon-@Transactional
and@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_NEW
NESTED
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
.