記錄幾條SpringBoot事務管理中踩過的坑及解決辦法:
1. 自調用問題
問題描述
在同一個類中,一個非事務方法調用另一個有 @Transactional
注解的事務方法,事務不會生效。因為 Spring 的事務管理是基于 AOP 代理實現的,自調用時不會經過代理對象,所以事務注解不起作用。
示例代碼
@Service
public class UserService {public void nonTransactionalMethod() {// 調用事務方法this.transactionalMethod(); }@Transactionalpublic void transactionalMethod() {// 數據庫操作}
}
解決辦法
可以通過注入自身的代理對象來解決自調用問題,或者將事務方法提取到另一個服務類中。
@Service
public class UserService {@Autowiredprivate UserService self;public void nonTransactionalMethod() {// 通過代理對象調用事務方法self.transactionalMethod(); }@Transactionalpublic void transactionalMethod() {// 數據庫操作}
}
2. 異常捕獲問題
問題描述
在事務方法中捕獲了異常但沒有重新拋出,會導致事務不會回滾。因為 Spring 默認只對未檢查異常(如 RuntimeException
及其子類)進行回滾,捕獲異常后沒有拋出,Spring 無法感知到異常,就不會觸發回滾機制。
示例代碼
@Service
public class UserService {@Autowiredprivate UserRepository userRepository;@Transactionalpublic void saveUser(User user) {try {userRepository.save(user);// 模擬異常int result = 1 / 0; } catch (Exception e) {// 捕獲異常但未重新拋出e.printStackTrace(); }}
}
解決辦法
在捕獲異常后,根據業務需求重新拋出未檢查異常,或者在 @Transactional
注解中指定需要回滾的異常類型。
@Service
public class UserService {@Autowiredprivate 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 {@Autowiredprivate PaymentService paymentService;@Transactionalpublic 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() {// 配置數據源 1return DataSourceBuilder.create().build();}@Bean(name = "dataSource2")public DataSource dataSource2() {// 配置數據源 2return 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 進行操作}
}