Mastering Spring Transactions: From Basics to Advanced Propagation Types
This article explains the origins and purpose of Spring transactions, demonstrates how to quickly set them up with code examples, explores key features and common pitfalls, and provides an in‑depth comparison of the REQUIRED, REQUIRES_NEW, and NESTED propagation types for robust backend development.
Spring transactions are essential for handling complex, consistent business operations. Understanding their background helps clarify why they exist and how they differ from plain database transactions.
Background
A transaction is a unit of work that must be completed entirely or not at all, characterized by the ACID properties:
Atomicity : all operations succeed or none do.
Consistency : database integrity is preserved before and after the transaction.
Isolation : concurrent transactions do not interfere with each other.
Durability : once committed, changes survive system failures.
Spring implements these concepts using AOP to start, commit, or roll back transactions at the appropriate points in the business layer.
Why Use Spring Transactions?
Consider a bank transfer: deducting money from one account and adding it to another must happen atomically. Spring transactions ensure both operations succeed together or are both rolled back, preventing data inconsistency.
If you are familiar with MySQL transactions, you know they can be controlled manually. Spring abstracts this by applying transaction boundaries automatically via annotations or programmatic APIs.
Usage Guide
Spring supports two usage styles: declarative (annotation‑based) and programmatic. Declarative is more common.
Project Setup
Create two simple tables, tablea and tableb, each with id and name columns.
CREATE TABLE `tablea` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(45) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1;
CREATE TABLE `tableb` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(45) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1;Add Spring Boot, MyBatis, and MySQL dependencies to the pom.xml:
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>Implement the controller, service, mapper, and entity classes as shown below.
@SpringBootApplication
@RestController
@RequestMapping("/api")
public class SpringTransactionController {
@Autowired
private TransactionServiceA transactionServiceA;
@RequestMapping("/spring-transaction")
public String testTransaction() {
transactionServiceA.methodA();
return "SUCCESS";
}
} public interface TableService {
void insertTableA(TableEntity tableEntity);
void insertTableB(TableEntity tableEntity);
} @Service
public class TransactionServiceA {
@Autowired
private TableService tableService;
@Autowired
private TransactionServiceB transactionServiceB;
public void methodA(){
System.out.println("methodA");
tableService.insertTableA(new TableEntity());
transactionServiceB.methodB();
}
} @Service
public class TransactionServiceB {
@Autowired
private TableService tableService;
public void methodB(){
System.out.println("methodB");
tableService.insertTableB(new TableEntity());
}
} @Mapper
public interface TableMapper {
@Insert("INSERT INTO tablea(id, name) " +
"VALUES(#{id}, #{name})")
@Options(useGeneratedKeys = true, keyProperty = "id")
void insertTableA(TableEntity tableEntity);
@Insert("INSERT INTO tableb(id, name) " +
"VALUES(#{id}, #{name})")
@Options(useGeneratedKeys = true, keyProperty = "id")
void insertTableB(TableEntity tableEntity);
} @Data
public class TableEntity {
private static final long serialVersionUID = 1L;
private Long id;
private String name;
public TableEntity() {}
public TableEntity(String name) { this.name = name; }
}Configure the datasource in application.yml (or application.properties).
spring:
datasource:
url: jdbc:mysql://localhost:3306/test?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
type-aliases-package: tech.shuyi.javacodechip.spring_transaction.model
configuration:
map-underscore-to-camel-case: trueRun the Spring Boot application and access http://localhost:8080/api/spring-transaction. If everything is set up correctly, both tablea and tableb will contain a new row.
Transaction Propagation Types
Propagation defines how transactions interact when one transactional method calls another. Spring defines seven types; the most commonly used are REQUIRED, REQUIRES_NEW, and NESTED.
REQUIRED
When a transaction already exists, the called method joins it; otherwise a new transaction is created. All participating methods share a single transaction, so any exception causes a rollback of the whole unit.
public void methodA(){
System.out.println("methodA");
tableService.insertTableA(new TableEntity());
transactionServiceB.methodB();
}
@Transactional
public void methodB(){
System.out.println("methodB");
tableService.insertTableB(new TableEntity());
throw new RuntimeException();
}Result: tablea is inserted, tableb is not, because the transaction rolls back.
REQUIRES_NEW
This propagation always starts a new, independent transaction for the called method, regardless of any existing transaction. Parent and child transactions do not affect each other unless the parent propagates the child's exception.
@Transactional
public void methodA(){
tableService.insertTableA(new TableEntity());
transactionServiceB.methodB();
throw new RuntimeException();
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB(){
tableService.insertTableB(new TableEntity());
}Result: tablea rolls back, but tableb remains committed.
If the child throws an exception and the parent catches it, the parent transaction can still commit:
@Transactional
public void methodA(){
tableService.insertTableA(new TableEntity());
try {
transactionServiceB.methodB();
} catch (Exception e) {
e.printStackTrace();
}
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB(){
tableService.insertTableB(new TableEntity());
throw new RuntimeException();
}Result: tablea is inserted, tableb is not.
NESTED
NESTED creates a save‑point within the existing transaction. If the nested transaction rolls back, the outer transaction can decide whether to continue based on exception handling.
@Transactional
public void methodA(){
tableService.insertTableA(new TableEntity());
transactionServiceB.methodB();
throw new RuntimeException();
}
@Transactional(propagation = Propagation.NESTED)
public void methodB(){
tableService.insertTableB(new TableEntity());
}Result: both tables roll back because the outer transaction fails.
@Transactional
public void methodA(){
tableService.insertTableA(new TableEntity());
transactionServiceB.methodB();
}
@Transactional(propagation = Propagation.NESTED)
public void methodB(){
tableService.insertTableB(new TableEntity());
throw new RuntimeException();
}Result: both tables roll back when the nested transaction throws an exception and the parent does not catch it.
@Transactional
public void methodA(){
tableService.insertTableA(new TableEntity());
try {
transactionServiceB.methodB();
} catch (Exception e) {
// handle
}
}
@Transactional(propagation = Propagation.NESTED)
public void methodB(){
tableService.insertTableB(new TableEntity());
throw new RuntimeException();
}Result: tablea is committed, tableb is rolled back because the exception is caught.
Summary of Propagation Types
REQUIRED : Joins existing transaction or creates a new one; all participants share the same transaction, so any failure rolls back the whole unit.
REQUIRES_NEW : Always starts a new, independent transaction; parent and child transactions are isolated, but the parent must handle child exceptions to avoid unintended rollbacks.
NESTED : Executes within a save‑point of the existing transaction; the outer transaction can decide to continue if the nested one fails and the exception is caught.
How to Use Spring Transactions Effectively
Analyze the business scenario and decide which propagation type achieves the desired transactional behavior.
Annotate service‑layer methods with @Transactional and set the appropriate propagation attribute.
Remember that Spring transactions only work on public methods called from outside the bean, that the bean must be managed by Spring, and that the underlying database must support transactions.
When Transactions May Fail
If a method without @Transactional calls a transactional method within the same class, the transaction will not be applied because Spring AOP proxies only intercept external calls.
Also, @Transactional only works on public methods, and the class must be a Spring‑managed component.
Easter Egg
Spring rolls back only for unchecked exceptions (RuntimeException or Error). Checked exceptions do not trigger a rollback unless explicitly configured, so both tables will be inserted when a checked Exception is thrown.
@Transactional
public void methodA() throws Exception {
tableService.insertTableA(new TableEntity());
transactionServiceB.methodB();
}
@Transactional
public void methodB() throws Exception {
tableService.insertTableB(new TableEntity());
throw new Exception(); // checked exception, no rollback
}References
SegmentFault: "从头到尾说一次 Spring 事务管理(器)"
ZhiHu: "Spring 事务原理一探"
JavaGuide: "Spring 事务详解"
博客园: "事务之六:spring 嵌套事务"
博客园: "NESTED 区别!spring 事务传播行为详解"
Michael 翔: "Spring Boot 实战 —— MyBatis(注解版)使用方法"
云扬四海: "记一次事务的坑 Transaction rolled back because it has been marked as rollback-only"
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Su San Talks Tech
Su San, former staff at several leading tech companies, is a top creator on Juejin and a premium creator on CSDN, and runs the free coding practice site www.susan.net.cn.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
