面試必問|哪些場景下Spring的事務會失效?
在日常工作中,如果對Spring的事務管理功能使用不當,則會造成Spring事務不生效的問題。而針對Spring事務不生效的問題,也是在跳槽面試中被問的比較頻繁的一個問題。
今天,我們就一起梳理下有哪些場景會導致Spring事務生效。
注:部分內容引用自冰河與貓大人出版的《深入理解分布式事務:原理與實戰(zhàn)》一書。
文章收錄于GitHub和Gitee:
GitHub: https://github.com/sunshinelyz/technology-binghe
Gitee: https://gitee.com/binghe001/technology-binghe
Spring事務不生效總覽
簡單來說,Spring事務會在幾種特定的場景下失效,如下圖所示。
數(shù)據(jù)庫不支持事務
Spring事務生效的前提是所連接的數(shù)據(jù)庫要支持事務,如果底層的數(shù)據(jù)庫都不支持事務,則Spring的事務肯定會失效。例如,如果使用的數(shù)據(jù)庫為MySQL,并且選用了MyISAM存儲引擎,則Spring的事務就會失效。
事務方法未被Spring管理
如果事務方法所在的類沒有加載到Spring IOC容器中,也就是說,事務方法所在的類沒有被Spring管理,則Spring事務會失效,示例如下。
- public class ProductService {
- @Autowired
- private ProductDao productDao;
- @Transactional(propagation = Propagation.REQUIRES_NEW)
- public void updateProductStockCountById(Integer stockCount, Long id){
- productDao.updateProductStockCountById(stockCount, id);
- }
- }
ProductService類上沒有標注@Service注解,Product的實例沒有加載到Spring IOC容器中,就會造成updateProductStockCountById()方法的事務在Spring中失效。
方法沒有被public修飾
如果事務所在的方法沒有被public修飾,此時Spring的事務會失效,例如,如下代碼所示。
- @Service
- public class ProductService {
- @Autowired
- private ProductDao productDao;
- @Transactional(propagation = Propagation.REQUIRES_NEW)
- private void updateProductStockCountById(Integer stockCount, Long id){
- productDao.updateProductStockCountById(stockCount, id);
- }
- }
雖然ProductService上標注了@Service注解,同時updateProductStockCountById()方法上標注了@Transactional(propagation = Propagation.REQUIRES_NEW)注解。
但是,由于updateProductStockCountById()方法為內部的私有方法(使用private修飾),那么此時updateProductStockCountById()方法的事務在Spring中會失效。
同一類中方法調用
如果同一個類中的兩個方法分別為A和B,方法A上沒有添加事務注解,方法B上添加了 @Transactional事務注解,方法A調用方法B,則方法B的事務會失效。例如,如下代碼所示。
- @Service
- public class OrderService {
- @Autowired
- private OrderDao orderDao;
- @Autowired
- private ProductDao productDao;
- public void submitOrder(){
- //生成訂單
- Order order = new Order();
- long number = Math.abs(new Random().nextInt(500));
- order.setId(number);
- order.setOrderNo("order_" + number);
- orderDao.saveOrder(order);
- //減庫存
- this.updateProductStockCountById(1, 1L);
- }
- @Transactional(propagation = Propagation.REQUIRES_NEW)
- public void updateProductStockCountById(Integer stockCount, Long id){
- productDao.updateProductStockCountById(stockCount, id);
- }
- }
submitOrder()方法和updateProductStockCountById()方法都在OrderService類中,submitOrder()方法上沒有標注事務注解,updateProductStockCountById()方法上標注了事務注解,submitOrder()方法調用了updateProductStockCountById()方法,此時,updateProductStockCountById()方法的事務在Spring中會失效。
未配置事務管理器
如果在項目中沒有配置Spring的事務管理器,即使使用了Spring的事務管理功能,Spring的事務也不會生效。
例如,沒有在項目的配置類中配置如下代碼。
- @Bean
- public PlatformTransactionManager transactionManager(DataSource dataSource) {
- return new DataSourceTransactionManager(dataSource);
- }
此時,Spring的事務就會失效。
方法的事務傳播類型不支持事務
如果內部方法的事務傳播類型為不支持事務的傳播類型,則內部方法的事務在Spring中會失效。
例如,如下代碼所示。
- @Service
- public class OrderService {
- @Autowired
- private OrderDao orderDao;
- @Autowired
- private ProductDao productDao;
- @Transactional(propagation = Propagation.REQUIRED)
- public void submitOrder(){
- //生成訂單
- Order order = new Order();
- long number = Math.abs(new Random().nextInt(500));
- order.setId(number);
- order.setOrderNo("order_" + number);
- orderDao.saveOrder(order);
- //減庫存
- this.updateProductStockCountById(1, 1L);
- }
- @Transactional(propagation = Propagation.NOT_SUPPORTED)
- public void updateProductStockCountById(Integer stockCount, Long id){
- productDao.updateProductStockCountById(stockCount, id);
- }
- }
由于updateProductStockCountById()方法的事務傳播類型為NOT_SUPPORTED,不支持事務,則updateProductStockCountById()方法的事務會在Spring中失效。
不正確的捕獲異常
不正確的捕獲異常也會導致Spring的事務失效,示例如下。
- @Service
- public class OrderService {
- @Autowired
- private OrderDao orderDao;
- @Autowired
- private ProductDao productDao;
- @Transactional(propagation = Propagation.REQUIRED)
- public void submitOrder(){
- //生成訂單
- Order order = new Order();
- long number = Math.abs(new Random().nextInt(500));
- order.setId(number);
- order.setOrderNo("order_" + number);
- orderDao.saveOrder(order);
- //減庫存
- this.updateProductStockCountById(1, 1L);
- }
- @Transactional(propagation = Propagation.REQUIRED)
- public void updateProductStockCountById(Integer stockCount, Long id){
- try{
- productDao.updateProductStockCountById(stockCount, id);
- int i = 1 / 0;
- }catch(Exception e){
- logger.error("扣減庫存異常:", e.getMesaage());
- }
- }
- }
updateProductStockCountById()方法中使用try-catch代碼塊捕獲了異常,即使updateProductStockCountById()方法內部會拋出異常,但也會被catch代碼塊捕獲到,此時updateProductStockCountById()方法的事務會提交而不會回滾,并且submitOrder()方法的事務會提交而不會回滾,這就造成了Spring事務的回滾失效問題。
錯誤的標注異常類型
如果在@Transactional注解中標注了錯誤的異常類型,則Spring事務的回滾會失效,示例如下。
- @Transactional(propagation = Propagation.REQUIRED)
- public void updateProductStockCountById(Integer stockCount, Long id){
- try{
- productDao.updateProductStockCountById(stockCount, id);
- }catch(Exception e){
- logger.error("扣減庫存異常:", e.getMesaage());
- throw new Exception("扣減庫存異常");
- }
- }
在updateProductStockCountById()方法中捕獲了異常,并且在異常中拋出了Exception類型的異常,此時,updateProductStockCountById()方法事務的回滾會失效。
為何會失效呢?這是因為Spring中對于默認回滾的事務異常類型為RuntimeException,上述代碼拋出的是Exception異常。
默認情況下,Spring事務中無法捕獲到Exception異常,所以此時updateProductStockCountById()方法事務的回滾會失效。
此時可以手動指定updateProductStockCountById()方法標注的事務異常類型,如下所示。
- @Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)
這里,需要注意的是:Spring事務注解@Transactional中的rollbackFor屬性可以指定 Throwable 異常類及其子類。
本文轉載自微信公眾號「冰河技術」,可以通過以下二維碼關注。轉載本文請聯(lián)系冰河技術公眾號。