Mastering Spring Boot Transactions: From Basics to Advanced Settings
This article explains what database transactions are, demonstrates how to use Spring Boot’s @Transactional annotation with unit tests, and dives into transaction isolation levels and propagation behaviors, providing code examples and guidance for effective transaction management in Java backend applications.
What is a Transaction?
When developing enterprise applications, a business operation often involves multiple database read/write steps. If any step fails, the previous successful operations become unreliable. Two ways to handle this are:
Record the failure point and resume after fixing the issue.
Rollback the entire operation so the system returns to its original state before retrying.
Transaction implements the second approach, ensuring all steps succeed or all fail. For example, creating an order and deducting inventory must be wrapped in a transaction to avoid overselling.
Quick Start
In Spring Boot, adding spring-boot-starter-jdbc or spring-boot-starter-data-jpa automatically configures a transaction manager, so you can use @Transactional without extra configuration.
Using the previous Spring Data JPA example, we create a test that inserts 10 users. The test deliberately triggers an exception by setting a maximum age of 50 on the User entity.
@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class ApplicationTests {
@Autowired
private UserRepository userRepository;
@Test
public void test() throws Exception {
// Create 10 records
userRepository.save(new User("AAA", 10));
userRepository.save(new User("BBB", 20));
userRepository.save(new User("CCC", 30));
userRepository.save(new User("DDD", 40));
userRepository.save(new User("EEE", 50));
userRepository.save(new User("FFF", 60));
userRepository.save(new User("GGG", 70));
userRepository.save(new User("HHH", 80));
userRepository.save(new User("III", 90));
userRepository.save(new User("JJJ", 100));
// ... other verification steps
}
}Running the test without @Transactional shows that after the exception only the first five records are persisted.
The User entity is defined as:
@Entity
@Data
@NoArgsConstructor
public class User {
@Id
@GeneratedValue
private Long id;
private String name;
@Max(50)
private Integer age;
public User(String name, Integer age) {
this.name = name;
this.age = age;
}
}Executing the test produces a ConstraintViolationException and rolls back the transaction, leaving no records in the database.
2020-07-09 11:55:29.581 ERROR ... Validation failed for classes [com.didispace.chapter310.User] during persist time ... ConstraintViolationImpl{interpolatedMessage='最大不能超过50', propertyPath=age, ...}Adding @Transactional to the test method causes the transaction to roll back automatically.
@Test
@Transactional
public void test() throws Exception {
// ... test content
}In real applications, @Transactional is typically placed on service‑layer methods.
public interface UserService {
@Transactional
User update(String name, String password);
}Transaction Details
The default configuration works for basic needs, but complex projects may require specifying a particular transaction manager, isolation level, or propagation behavior.
Isolation Levels
Isolation determines how concurrent transactions are isolated from each other, affecting dirty reads, non‑repeatable reads, and phantom reads.
public enum Isolation {
DEFAULT(-1),
READ_UNCOMMITTED(1),
READ_COMMITTED(2),
REPEATABLE_READ(4),
SERIALIZABLE(8);
} DEFAULT: uses the database’s default, usually READ_COMMITTED. READ_UNCOMMITTED: allows reading uncommitted changes; prone to dirty reads. READ_COMMITTED: prevents dirty reads; recommended for most cases. REPEATABLE_READ: prevents dirty and non‑repeatable reads. SERIALIZABLE: fully isolates transactions but impacts performance.
Set isolation with @Transactional(isolation = Isolation.DEFAULT).
Propagation Behaviors
Propagation defines how a method participates in existing transactions.
public enum Propagation {
REQUIRED(0),
SUPPORTS(1),
MANDATORY(2),
REQUIRES_NEW(3),
NOT_SUPPORTED(4),
NEVER(5),
NESTED(6);
} REQUIRED: join existing or create new. SUPPORTS: join if exists, otherwise non‑transactional. MANDATORY: must have an existing transaction. REQUIRES_NEW: always start a new transaction, suspending any existing. NOT_SUPPORTED: execute non‑transactionally, suspending any existing. NEVER: must not have a transaction. NESTED: execute within a nested transaction if one exists.
Set propagation with @Transactional(propagation = Propagation.REQUIRED).
Code Repository
All examples are available in the chapter3-10 directory of the SpringBoot‑Learning project on GitHub and Gitee.
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.
Programmer DD
A tinkering programmer and author of "Spring Cloud Microservices in Action"
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.
