SpringBoot事务管理(四)
记录几条SpringBoot事务管理中踩过的坑及解决办法:
1. 自调用问题
问题描述
在同一个类中,一个非事务方法调用另一个有 @Transactional
注解的事务方法,事务不会生效。因为 Spring 的事务管理是基于 AOP 代理实现的,自调用时不会经过代理对象,所以事务注解不起作用。
示例代码
@Service
public class UserService {
public void nonTransactionalMethod() {
// 调用事务方法
this.transactionalMethod();
}
@Transactional
public void transactionalMethod() {
// 数据库操作
}
}
解决办法
可以通过注入自身的代理对象来解决自调用问题,或者将事务方法提取到另一个服务类中。
@Service
public class UserService {
@Autowired
private UserService self;
public void nonTransactionalMethod() {
// 通过代理对象调用事务方法
self.transactionalMethod();
}
@Transactional
public void transactionalMethod() {
// 数据库操作
}
}
2. 异常捕获问题
问题描述
在事务方法中捕获了异常但没有重新抛出,会导致事务不会回滚。因为 Spring 默认只对未检查异常(如 RuntimeException
及其子类)进行回滚,捕获异常后没有抛出,Spring 无法感知到异常,就不会触发回滚机制。
示例代码
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Transactional
public void saveUser(User user) {
try {
userRepository.save(user);
// 模拟异常
int result = 1 / 0;
} catch (Exception e) {
// 捕获异常但未重新抛出
e.printStackTrace();
}
}
}
解决办法
在捕获异常后,根据业务需求重新抛出未检查异常,或者在 @Transactional
注解中指定需要回滚的异常类型。
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Transactional(rollbackFor = Exception.class)
public void saveUser(User user) {
try {
userRepository.save(user);
// 模拟异常
int result = 1 / 0;
} catch (Exception e) {
// 重新抛出异常
throw new RuntimeException(e);
}
}
}
3. 事务传播行为误用
问题描述
在嵌套事务中,如果错误地使用了事务传播行为,可能会导致事务管理不符合预期。例如,在需要独立事务的场景下使用了 REQUIRED
传播行为,会使内层事务加入到外层事务中,当外层事务回滚时,内层事务也会回滚。
示例代码
@Service
public class OrderService {
@Autowired
private PaymentService paymentService;
@Transactional
public void createOrder(Order order) {
// 创建订单
// 调用支付服务
paymentService.processPayment(order);
}
}
@Service
public class PaymentService {
@Transactional(propagation = Propagation.REQUIRED)
public void processPayment(Order order) {
// 处理支付
}
}
解决办法
根据业务需求选择合适的事务传播行为。如果需要独立事务,可以使用 REQUIRES_NEW
传播行为。
@Service
public class PaymentService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void processPayment(Order order) {
// 处理支付
}
}
4. 数据库隔离级别不匹配
问题描述
在不同的数据库和业务场景下,如果使用了不匹配的事务隔离级别,可能会出现数据不一致的问题。例如,在高并发场景下使用了较低的隔离级别,可能会导致脏读、不可重复读和幻读问题。
示例代码
@Service
public class ProductService {
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public Product getProductById(Long id) {
// 查询产品信息
return productRepository.findById(id).orElse(null);
}
}
解决办法
根据业务需求和数据库特性选择合适的隔离级别。在高并发场景下,为了保证数据一致性,可以使用较高的隔离级别,如 REPEATABLE_READ
或 SERIALIZABLE
,但要注意可能会影响并发性能。
@Service
public class ProductService {
@Transactional(isolation = Isolation.REPEATABLE_READ)
public Product getProductById(Long id) {
// 查询产品信息
return productRepository.findById(id).orElse(null);
}
}
5. 多数据源事务问题
问题描述
在使用多数据源的 Spring Boot 应用中,如果没有正确配置事务管理器,可能会导致事务管理混乱。不同数据源需要不同的事务管理器来管理事务。
解决办法
为每个数据源配置独立的事务管理器,并在 @Transactional
注解中指定使用的事务管理器。
@Configuration
public class DataSourceConfig {
@Bean(name = "dataSource1")
public DataSource dataSource1() {
// 配置数据源 1
return DataSourceBuilder.create().build();
}
@Bean(name = "dataSource2")
public DataSource dataSource2() {
// 配置数据源 2
return DataSourceBuilder.create().build();
}
@Bean(name = "transactionManager1")
public PlatformTransactionManager transactionManager1(@Qualifier("dataSource1") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean(name = "transactionManager2")
public PlatformTransactionManager transactionManager2(@Qualifier("dataSource2") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
@Service
public class MultiDataSourceService {
@Transactional("transactionManager1")
public void operationOnDataSource1() {
// 对数据源 1 进行操作
}
@Transactional("transactionManager2")
public void operationOnDataSource2() {
// 对数据源 2 进行操作
}
}