1. 什么是事务?
事务是由一组操作组成的逻辑单元,要么全部成功执行,要么全部失败回滚。事务必须满足 ACID 特性:
- 原子性(Atomicity):事务是一个不可分割的整体。
- 一致性(Consistency):事务完成后,数据从一个一致性状态转移到另一个一致性状态。
- 隔离性(Isolation):并发事务之间相互独立。
- 持久性(Durability):事务完成后,数据的修改永久保存。
2. Spring 事务管理
Spring 提供了对事务的统一管理,支持多种事务管理器(如 JDBC、JPA、Hibernate 等)。Spring 事务主要分为两种类型:
- 编程式事务管理:通过代码显式控制事务的提交或回滚。
- 声明式事务管理:通过注解或 XML 配置控制事务,推荐使用。
3. Spring事务回滚机制
(1) 事务的传播方式
Spring 定义了多种事务传播行为,如 REQUIRED
、REQUIRES_NEW
、NESTED
等,它们决定了事务如何在方法之间传播。最常用的传播方式是 REQUIRED
,即当前方法嵌套调用时共享同一个事务。
(2) 事务回滚触发条件
Spring 事务的回滚主要基于异常:
- 默认情况下:
- 捕获
RuntimeException
或Error
会导致事务回滚。 - 捕获
Checked Exception
(如IOException
)不会回滚,除非明确配置。
- 捕获
- 可配置性:
- 使用
@Transactional(rollbackFor = Exception.class)
明确指定需要回滚的异常。
- 使用
(3) 回滚的实现流程
Spring 事务的回滚过程涉及以下关键步骤:
- Spring 使用代理对象管理事务方法。
- 方法执行时,事务管理器开启事务。
- 如果方法抛出指定异常,事务管理器调用数据库的回滚操作。
- 如果方法成功执行,事务管理器提交事务。
4. 声明式事务回滚示例
(1) 默认情况下的回滚
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Transactional
public void saveUserWithRollback() {
userRepository.save(new User("Alice", "alice@example.com"));
// 模拟异常
throw new RuntimeException("Simulated Exception");
}
}
代码说明:
- 方法标注了
@Transactional
,表示该方法受事务管理。 - 当
RuntimeException
抛出时,Spring 会自动回滚事务,save
操作不会持久化到数据库。
(2) 指定回滚条件
默认情况下,Checked Exception
不会触发事务回滚。我们可以通过 rollbackFor
属性显式指定需要回滚的异常类型。
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Transactional(rollbackFor = Exception.class)
public void saveUserWithCheckedException() throws Exception {
userRepository.save(new User("Bob", "bob@example.com"));
// 抛出受检异常
throw new Exception("Checked Exception");
}
}
代码说明:
- 通过
rollbackFor = Exception.class
,即使抛出Checked Exception
,事务也会回滚。
5. 编程式事务回滚示例
Spring 提供了 TransactionTemplate
类,用于编程式管理事务。我们可以显式捕获异常并手动控制事务的提交或回滚。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
@Service
public class UserService {
@Autowired
private TransactionTemplate transactionTemplate;
public void saveUserWithProgrammaticTransaction() {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
try {
// 数据库操作
userRepository.save(new User("Charlie", "charlie@example.com"));
// 模拟异常
throw new RuntimeException("Simulated Exception");
} catch (Exception e) {
// 回滚事务
status.setRollbackOnly();
System.out.println("Transaction rolled back due to: " + e.getMessage());
}
}
});
}
}
代码说明:
- 使用
TransactionTemplate
明确声明事务逻辑。 - 当捕获到异常时,通过
status.setRollbackOnly()
手动触发事务回滚。
6. 事务回滚的注意事项
(1) 异常传播问题
事务回滚依赖异常的传播,如果异常被捕获而未抛出,Spring 无法感知事务需要回滚。
@Transactional
public void saveUserWithCaughtException() {
try {
userRepository.save(new User("David", "david@example.com"));
throw new RuntimeException("Simulated Exception");
} catch (Exception e) {
// 异常被捕获,事务不会回滚
System.out.println("Exception caught: " + e.getMessage());
}
}
解决方法:
- 捕获异常后重新抛出,或直接让异常传播。
@Transactional
public void saveUserWithRethrowException() {
try {
userRepository.save(new User("David", "david@example.com"));
throw new RuntimeException("Simulated Exception");
} catch (Exception e) {
throw e; // 重新抛出异常
}
}
(2) 自调用导致事务失效
Spring 事务依赖 AOP 实现,如果同一个类中自调用事务方法,事务可能失效。
@Transactional
public void methodA() {
userRepository.save(new User("Emma", "emma@example.com"));
methodB(); // 自调用,事务失效
}
@Transactional
public void methodB() {
throw new RuntimeException("Simulated Exception");
}
解决方法:
- 将
methodB
移到另一个服务类,由代理对象调用。 - 使用
@Transactional(propagation = Propagation.REQUIRES_NEW)
强制新建事务。
(3) 只读事务
设置 @Transactional(readOnly = true)
时,事务回滚可能不会触发,因为只读事务通常用于查询操作。
7. 事务传播行为与回滚
Spring 支持多种事务传播行为,不同传播方式下事务的回滚机制也不同。
常用传播行为
- REQUIRED:如果当前存在事务,则加入;否则新建事务。
- REQUIRES_NEW:每次都新建事务。
- NESTED:嵌套事务,内外事务可以独立回滚。
示例:REQUIRES_NEW
回滚
@Transactional
public void parentMethod() {
userRepository.save(new User("Parent", "parent@example.com"));
try {
childMethod(); // 新建事务
} catch (Exception e) {
System.out.println("Child transaction rolled back.");
}
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void childMethod() {
userRepository.save(new User("Child", "child@example.com"));
throw new RuntimeException("Simulated Exception");
}
分析:
childMethod
新建事务,抛出异常后只回滚子事务,不影响父事务。
8. 事务回滚的最佳实践
-
明确异常回滚条件:
- 默认只回滚
RuntimeException
和Error
。 - 使用
rollbackFor
属性覆盖默认行为。
- 默认只回滚
-
避免自调用事务方法:
- 将事务方法拆分到不同类,由代理对象调用。
-
正确处理异常:
- 避免捕获异常而不抛出,确保 Spring 能感知异常。
-
使用事务传播机制:
- 根据业务场景选择合适的传播方式。
9. 总结
Spring 提供了强大的事务回滚机制,通过注解和配置,开发者可以轻松实现复杂的事务控制。无论是声明式事务还是编程式事务,正确的回滚配置可以显著提升数据一致性和系统稳定性。
关键点
总结:
- 默认回滚
RuntimeException
,需要配置其他异常的回滚行为。 - 避免异常捕获后不抛出,确保事务管理器能够感知事务状态。
- 根据业务需求选择合适的事务传播方式和回滚策略。