spring 的相關的事務失效的問題總結

  這一個問題,真的是很有意思。之前不瞭解的時候覺得會用就行了。但是真的不知道里邊這麼多坑。不過這些坑都真的很有意思。它推着你去了解底層。推着你看源碼,瞭解實現原理。

  也是因爲我在最近的開發中遇到了一些問題,自己進行排查,然後谷歌了很多很多的文章。這篇文章算是一個總結吧。希望可以做到的是,關於事務失效的問題看這一篇文章就夠了。

  儘可能的做到全一點,篇幅可能會大一點。這裏爲了省事,一些demo就不自己敲了,借鑑一下比人的。

  ~ps 如果你是因爲事務失效問題,看到這篇文章沒有解決的,親請給我留言,我可能會對你失效的原因很感興趣,我們一塊探討探討,然後爭取解決了放在這裏。

 

 

# # 事務失效分類、

  首先事務失效的原因有這幾個層面:

  •    代碼層面
  •    數據庫層面
  •    框架層面

 

# #  代碼層面(這個也是有很多個原因)

   這裏先列一下,然後再逐個展開:

  •  沒有加@Transactional註解 
  •  因爲try catch原因
  •  因爲沒加@Service,沒有被 spring 管理
  •  因爲方法不是public d 
  •  因爲事務的傳播行爲導致事務失效。

 

  •   這個很多就是我們不小心造成的問題,比方說我們沒有加 @Transactional註解,注意這個註解,就是加在 service 層的,不要亂加,再注意一點,如果能確定加在方法上就不要加在類上。因爲根據底層的實現。你給加在類上,就意味着給全部的方法都加事務,再根據底層的實現,事務是用 AOP 實現的,動態代碼要損失一點性能。

         解決方案:注意這是 service 層的方法,之前不懂事,我也加在 dao層過。

           

 

  •   try catch 吞掉了異常,接下來看一個例子。這個例子是事務失效的例子

   注意這裏的例子,我持久層用的是 jpa,這裏的 save 就相當於是一個insert操作、

@Transactional
	public void transactionalTest() {
		try {
			TmcFinanceServiceFeeCheckLogEntity entity = new TmcFinanceServiceFeeCheckLogEntity();
			entity.setCompanyNo("555666");
			entity.setDefineNo("6666666");
			entity.setServiceType("03");
			entity.setServiceTypeDetail("031");
			entity.setServiceNumTotal(1);
			entity.setTotalNo("2222");
			entity.setStatisticsTimeStart(new Timestamp(System.currentTimeMillis()));
			entity.setStatisticsTimeStop(new Timestamp(System.currentTimeMillis()));
			entity.setCheckType("1");
			entity.setCheckStatus("2");
			entity.setStartTime(new Timestamp(System.currentTimeMillis()));
			entity.setStatisticsInsideDate("20200405");
			tmcFinanceServiceFeeCheckLogRepository.save(entity);
			int temp = 1/0;
		}catch (Exception e){
			e.printStackTrace();
		}
	}

 

    有的人,特別熱衷於 try catch ,像例子裏邊的 try catch 就是非常多餘的。我們程序應該是配置全局異常。而不是在這裏手動的去補貨異常。甚至,這個try catch 起到了畫蛇添足的作用。因爲它把異常吞掉了,而這個加了事務註解的方法就看不到異常了。spring 的事務 @Transactional不做任何配置 默認是對拋出的unchecked異常回滾,checked異常不會回滾。顯然,這裏你 catch,已經是檢查異常了,既然你 catch,那你就自己做處理好了。我事務不管了。所以  try catch會讓事務失效。

  解決方案:try catch 在這裏是多餘的,僅僅需要去掉就可以了。就是能不用則不用。 那如果遇到了一定要用的情況怎麼辦?那就是自己殺的自己埋。既然你都已經捕獲了,就自己處理。在 catch裏邊 自己進行事務的回滾。代碼如下:注意看 catch裏邊的就可以了,這是手動回滾事務的操作。

	@Transactional
	public void transactionalTest() {
		try {
			TmcFinanceServiceFeeCheckLogEntity entity = new TmcFinanceServiceFeeCheckLogEntity();
			entity.setCompanyNo("555666");
			entity.setDefineNo("6666666");
			entity.setServiceType("03");
			entity.setServiceTypeDetail("031");
			entity.setServiceNumTotal(1);
			entity.setTotalNo("2222");
			entity.setStatisticsTimeStart(new Timestamp(System.currentTimeMillis()));
			entity.setStatisticsTimeStop(new Timestamp(System.currentTimeMillis()));
			entity.setCheckType("1");
			entity.setCheckStatus("2");
			entity.setStartTime(new Timestamp(System.currentTimeMillis()));
			entity.setStatisticsInsideDate("20200405");
			tmcFinanceServiceFeeCheckLogRepository.save(entity);
			int temp = 1/0;
		}catch (Exception e){
            // 重點看這裏
			TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
			e.printStackTrace();
		}
	}

  

  • 沒有加 @service

  例子如下:

// @Service
public class OrderServiceImpl implements OrderService {

    @Transactional
    public void updateOrder(Order order) {
        // update order
    }
    
}

   這個問題,說白了,事務是由spring幫我們實現的,那麼想要使用,就必須把類放在spring 容器裏邊來管理。如果你的類沒有加 @service 這就相當於沒有買人家的保險,還想讓人家賠錢,這是不可能的。 

 

  •  因爲方法不是public 的 

關於@Transactional的說明,Spring 有一段描述關於方法可見性:

Method visibility and @Transactional
When using proxies, you should apply the @Transactional annotation only to methods with public visibility. If you do annotate protected, private or package-visible methods with the @Transactional annotation, no error is raised, but the annotated method does not exhibit the configured transactional settings. Consider the use of AspectJ (see below) if you need to annotate non-public methods.

@Transactional註解只對代理類時的public方法有效,被protected、private、package-visible修飾的方法使用@Transactional註解無效,對這類方法使用事務註解,推薦使用AspectJ進行事務管理。Spring框架雖然提升了效率,偶爾也會產生意外的問題,且行且研究。

 

  •  因爲事務的傳播行爲導致事務失效。   

關於這一點Spring Transactional官方說明如下:

In proxy mode (which is the default), only external method calls coming in through the proxy are intercepted. This means that self-invocation, in effect, a method within the target object calling another method of the target object, will not lead to an actual transaction at runtime even if the invoked method is marked with @Transactional.

在代理下(默認或當配置爲proxy-target-class="true"),只有當前代理類的外部方法調用註解方法時代理纔會被攔截。事實上,這意味着:一個目標對象的方法調用該目標對象的另外一個方法,即使被調用的方法已使用了@Transactional註解標記,事務也不會有效執行。

 

  看一個因爲事務傳播原因導致的事務失效的例子:

  描述transactionalTest ()方法調用了 methodTest (),methodTest 已經加了事務註解。我們想當然的以爲。調用的方法加了事務,裏邊出現了錯誤應該會發生回滾的。 而實際上不會回滾。

@Override
	public void transactionalTest() {
        //調用一個加了事務註解,但是會在運行過程中報錯
		methodTest();


	}
	@Transactional
	public void methodTest(){
		// 故意寫一個異常,看會不會回滾
		TmcFinanceServiceFeeCheckLogEntity entity = new TmcFinanceServiceFeeCheckLogEntity();
		entity.setCompanyNo("666666");
		entity.setDefineNo("6666666");
		entity.setServiceType("03");
		entity.setServiceTypeDetail("031");
		entity.setServiceNumTotal(1);
		entity.setTotalNo("2222");
		entity.setStatisticsTimeStart(new Timestamp(System.currentTimeMillis()));
		entity.setStatisticsTimeStop(new Timestamp(System.currentTimeMillis()));
		entity.setCheckType("1");
		entity.setCheckStatus("2");
		entity.setStartTime(new Timestamp(System.currentTimeMillis()));
		entity.setStatisticsInsideDate("20200405");
		tmcFinanceServiceFeeCheckLogRepository.save(entity);
		int temp = 1/0;

	}

 

 對於這個失效的處理方式就是,在調用的方法上也添加一個@Transactional ,也就是調用者之前沒有事務,而被調用者有事務,沒有事務的調用者調用有事務的被調用者,會發生事務失效。 

  這問題,我看別人的文章說,兩個地方都添加事務註解,下邊的被調用的方法的事務,仍然會失效,但是從我的測試用例來看,並沒有失效。這裏大家遇到可以咱們溝通一下,看看是不是因爲事務管理器的差異造成的。

 

# # 數據庫層面的原因

  這個比較好排查,只需要知道自己使用的數據庫(引擎)是否支持事務。舉個例子:MySQL 爲例,其 MyISAM 引擎是不支持事務操作的,InnoDB 纔是支持事務的引擎,一般要支持事務都會使用 InnoDB。

  使用其他的數據庫的自查。

 

# # 框架層面的

  這個是我遇到的問題,就是上邊的情況,都不是我遇到的。

 我的問題是因爲事務管理器造成的事務失效:詳情請看我一篇文章:https://blog.csdn.net/star1210644725/article/details/105170300

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章