Transaction management belongs logically in the service layer. Spring supports two primary approaches:
- Programmatic transaction management (for advanced use cases)
- Declarative transaction management (recommended for most applications)
Declarative transactions can be implemented either via XML configuration or annotations — the latter being the preferred and more maintainable approach. Under the hood, Spring’s declarative transaction support relies on AOP, which in turn uses dynamic proxies to intercept method calls and apply transactional behavior.
The core abstraction is PlatformTransactionManager, an interface with concrete implementations tailored to different data access technologies — for example, DataSourceTransactionManager for JDBC-based operations.
To enable annotation-driven transaction management, configure a transaction manager and activate annotation processing in your Spring context:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd">
<context:component-scan base-package="com.example.bank" />
<context:property-placeholder location="classpath:db.properties" />
<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close">
<property name="jdbcUrl" value="${db.url}" />
<property name="username" value="${db.user}" />
<property name="password" value="${db.pass}" />
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<constructor-arg ref="dataSource" />
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<tx:annotation-driven transaction-manager="transactionManager" />
</beans>
Apply @Transactional directly on service methods to define transaction boundaries:
package com.example.bank.service;
import com.example.bank.dao.AccountRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class FundTransferService {
private final AccountRepository accounts;
public FundTransferService(AccountRepository accounts) {
this.accounts = accounts;
}
@Transactional
public boolean executeTransfer(int sourceId, int targetId, long amount) {
accounts.deduct(sourceId, amount);
// Simulate failure to trigger rollback
if (amount > 10000L) {
throw new IllegalArgumentException("Transfer exceeds limit");
}
accounts.credit(targetId, amount);
return true;
}
}
When an exception propagates out of a @Transactional method, Spring automatically rolls back the transaction unless configured otherwise. Successful completion leads to automatic commit.
The @Transactional annotation accepts multiple attributes that fine-tune behavior:
propagation: Controls how transactions behave when nested or chained (e.g.,REQUIRED,REQUIRES_NEW).isolation: Specifies the database isolation level (DEFAULT,READ_COMMITTED,REPEATABLE_READ, etc.).timeout: Maximum allowed duration (in seconds) before forced rollback.readOnly: Optimizes for read-only operations; some databases enforce immutability.rollbackFor/noRollbackFor: Define which exception types trigger or suppress rollback.
Here’s a usage example with custom settings:
@Transactional(
propagation = Propagation.REQUIRED,
isolation = Isolation.READ_COMMITTED,
timeout = 30,
readOnly = false,
rollbackFor = {SQLException.class, InsufficientFundsException.class},
noRollbackFor = IllegalArgumentException.class
)
public void processPayment(String orderId) { /* ... */ }
Propagation behaviors describe how transactions interact across method invocations:
REQUIRED: Join existing transaction or start a new one.REQUIRES_NEW: Always suspend any current transaction and begin a fresh one.SUPPORTS: Execute within a transaction if present; otherwise, proceed non-transactionally.NOT_SUPPORTED: Suspend any active transaction and run without one.MANDATORY: Require an active transaction; fail otherwise.NEVER: Prohibit execution within a transaction.NESTED: Execute within a nested transaction (if suported by the underlying platform).
Isolation levels determine visibility of uncommitted changes between concurrent transactions:
DEFAULT: Uses the database’s native default.READ_UNCOMMITTED: Allows dirty reads.READ_COMMITTED: Prevents dirty reads but allows non-repeatable reads.REPEATABLE_READ: Prevents dirty and non-repeatable reads; phantom reads may still occur.SERIALIZABLE: Highest isolation; prevents all concurrency anomalies at significant performance cost.
Timeout values are enforced by Spring’s transaction infrastructure and cause automatic rollback if exceeded. The readOnly flag signals intent — enabling optimizations like connection-level read-only mode or query plan caching. Exception-based rollback rules let developers precisely control recovery semantics without altering business logic.