這一個問題,真的是很有意思。之前不瞭解的時候覺得會用就行了。但是真的不知道里邊這麼多坑。不過這些坑都真的很有意思。它推着你去了解底層。推着你看源碼,瞭解實現原理。
也是因爲我在最近的開發中遇到了一些問題,自己進行排查,然後谷歌了很多很多的文章。這篇文章算是一個總結吧。希望可以做到的是,關於事務失效的問題看這一篇文章就夠了。
儘可能的做到全一點,篇幅可能會大一點。這裏爲了省事,一些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