在 Spring 框架里,事务管理是极为关键的功能,它能保证数据操作的一致性与完整性。下面详细介绍 Spring 事务的相关内容:
核心概念
事务的传播行为
传播行为定义了在嵌套事务场景中事务的处理方式。Spring 提供了 7 种传播行为,常见的有:
- REQUIRED(默认):若当前没有事务,就创建一个新事务;若已有事务,就加入该事务。
- REQUIRES_NEW:不管当前是否存在事务,都会创建一个新事务,原事务会被挂起。
- SUPPORTS:如果当前有事务,就加入该事务;如果没有,就以非事务方式执行。
- NOT_SUPPORTED:以非事务方式执行操作,若当前存在事务,就将其挂起。
- MANDATORY:要求当前必须存在一个事务,否则会抛出异常。
事务的隔离级别
隔离级别用于处理多事务并发时可能出现的问题。Spring 支持以下隔离级别:
- DEFAULT:使用底层数据库的默认隔离级别。
- READ_UNCOMMITTED:允许读取尚未提交的数据变更,可能会导致脏读、不可重复读和幻读。
- READ_COMMITTED:只允许读取已经提交的数据,可以避免脏读,但不可重复读和幻读仍有可能发生。
- REPEATABLE_READ:确保在同一个事务中多次读取同一数据的结果是一致的,可以避免脏读和不可重复读,但幻读仍有可能出现。
- SERIALIZABLE:最高的隔离级别,通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。
事务的回滚规则
默认情况下,Spring 事务只会在遇到运行时异常(RuntimeException 及其子类)和错误(Error)时才会回滚。不过,你也可以通过@Transactional
注解的rollbackFor
属性来指定需要回滚的异常类型。
声明式事务(推荐)
使用@Transactional
注解是实现声明式事务最简单的方式。下面是一个示例:
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
// 声明式事务示例
@Transactional(
propagation = Propagation.REQUIRED,
isolation = Isolation.READ_COMMITTED,
timeout = 30,
readOnly = false,
rollbackFor = {Exception.class}
)
public void transferMoney(Long fromUserId, Long toUserId, BigDecimal amount) {
// 从源账户扣款
User fromUser = userRepository.findById(fromUserId)
.orElseThrow(() -> new RuntimeException("User not found"));
fromUser.setBalance(fromUser.getBalance().subtract(amount));
userRepository.save(fromUser);
// 模拟异常
if (amount.compareTo(BigDecimal.valueOf(1000)) > 0) {
throw new RuntimeException("Transfer amount too large");
}
// 向目标账户入账
User toUser = userRepository.findById(toUserId)
.orElseThrow(() -> new RuntimeException("User not found"));
toUser.setBalance(toUser.getBalance().add(amount));
userRepository.save(toUser);
}
}
在这个示例中:
@Transactional
注解应用在transferMoney
方法上,表示该方法在事务环境中执行。propagation
属性设置为REQUIRED
,意味着如果当前没有事务,会创建一个新事务。isolation
属性设置为READ_COMMITTED
,使用数据库的默认隔离级别。timeout
属性指定事务的超时时间为 30 秒。readOnly
属性设置为false
,表示这是一个读写事务。rollbackFor
属性指定遇到任何异常都要回滚事务。
编程式事务
编程式事务适用于需要在代码中精确控制事务边界的场景。下面是一个使用TransactionTemplate
的示例:
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 AccountService {
private final TransactionTemplate transactionTemplate;
private final AccountRepository accountRepository;
public AccountService(TransactionTemplate transactionTemplate, AccountRepository accountRepository) {
this.transactionTemplate = transactionTemplate;
this.accountRepository = accountRepository;
}
// 编程式事务示例
public void updateAccountBalance(Long accountId, BigDecimal amount) {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
try {
Account account = accountRepository.findById(accountId)
.orElseThrow(() -> new RuntimeException("Account not found"));
// 更新账户余额
account.setBalance(account.getBalance().add(amount));
accountRepository.save(account);
// 模拟业务逻辑
if (amount.signum() < 0 && account.getBalance().compareTo(BigDecimal.ZERO) < 0) {
throw new RuntimeException("Insufficient balance");
}
} catch (Exception e) {
// 手动设置事务回滚
status.setRollbackOnly();
throw e;
}
}
});
}
}
在这个示例中:
- 通过
TransactionTemplate
的execute
方法来执行事务操作。 - 在
doInTransactionWithoutResult
方法中定义事务内要执行的业务逻辑。 - 如果发生异常,使用
status.setRollbackOnly()
手动设置事务回滚。
配置事务管理器
要使用 Spring 事务,需要配置事务管理器。下面是基于 Java 配置的示例:
import javax.sql.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@Configuration
@EnableTransactionManagement
public class TransactionConfig {
private final DataSource dataSource;
public TransactionConfig(DataSource dataSource) {
this.dataSource = dataSource;
}
// 配置事务管理器
@Bean
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource);
}
}
在这个配置中:
@EnableTransactionManagement
注解启用 Spring 的事务管理功能。DataSourceTransactionManager
是基于 JDBC 的事务管理器,适用于使用 JDBC 或 JPA 进行数据访问的场景。
核心传播行为对比表
传播行为 | 英文描述 | 核心逻辑 | 典型场景 |
---|---|---|---|
REQUIRED | Support a current transaction, create a new one if none exists. | 当前有事务则加入,无则创建新事务。方法调用链共享同一事务,统一提交 / 回滚。 | 主业务流程(如订单创建 + 库存扣减),需保证整体一致性。 |
REQUIRES_NEW | Create a new transaction, and suspend the current transaction if one exists. | 强制创建新事务,挂起外层事务。新事务独立提交 / 回滚,不影响外层事务状态。 | 异步操作、日志记录(需独立事务),或内层操作需与外层隔离(如重试机制)。 |
SUPPORTS | Support a current transaction, execute non-transactionally if none exists. | 有事务则加入,无则非事务执行。适合只读或可选事务场景。 | 查询接口(无写操作时避免事务开销),或根据调用方决定是否启用事务。 |
NOT_SUPPORTED | Execute non-transactionally, suspend the current transaction if one exists. | 强制非事务执行,挂起外层事务。适合明确不需要事务的操作。 | 性能敏感的只读操作(如缓存查询),或外层事务需释放资源(如避免锁竞争)。 |
MANDATORY | Requires a current transaction, throw an exception if none exists. | 必须存在外层事务,否则抛异常。确保操作在事务上下文中执行。 | 内部服务方法(如事务内回调),禁止独立调用(防止数据不一致)。 |
NEVER | Do not support a current transaction, throw an exception if one exists. | 禁止存在事务,否则抛异常。强制非事务执行。 | 绝对禁止事务的场景(如纯计算逻辑),避免意外加入事务。 |
NESTED | Execute within a nested transaction if a current transaction exists. | 嵌套事务,通过数据库保存点(Savepoint)实现部分回滚。外层回滚会触发内层回滚,内层回滚不影响外层。 | 复杂业务分支(如主订单 + 子订单),需支持部分失败时回滚子操作,保留主操作结果。 |
最佳实践
- 事务边界控制:事务范围应尽可能小,避免在事务中包含耗时操作,如网络调用、文件读写等。
- 异常处理:确保了解事务的回滚规则,必要时使用
rollbackFor
属性指定需要回滚的异常。 - 避免嵌套事务:嵌套事务容易导致意外的回滚行为,尽量使用扁平的事务结构。
- 只读事务优化:对于只读操作,使用
readOnly = true
可以提高性能。 - 监控与调优:监控事务性能,调整隔离级别和超时设置以平衡数据一致性和性能。