1. 程式人生 > >@Transactional 無效的解決方案

@Transactional 無效的解決方案

關於@Transactional註解 一般都認為要注意以下三點

1 .在需要事務管理的地方加@Transactional 註解。@Transactional 註解可以被應用於介面定義和介面方法、類定義和類的 public 方法上 。

2 . @Transactional 註解只能應用到 public 可見度的方法上 。 如果你在 protected、private 或者 package-visible 的方法上使用 @Transactional 註解,它也不會報錯, 但是這個被註解的方法將不會展示已配置的事務設定。

3 . 注意僅僅 @Transactional 註解的出現不足於開啟事務行為,它僅僅 是一種元資料。必須在配置檔案中使用配置元素,才真正開啟了事務行為。

最近在專案中發現註解無效,經過跟蹤原始碼發現了問題,於是在網上找到相同出現此問題的人,以下為原文,講解的很詳細:

只要避開Spring目前的AOP實現上的限制,要麼都宣告要事務,要麼分開成兩個類,要麼直接在方法裡使用程式設計式事務 
[問題] 
Spring的宣告式事務,我想就不用多介紹了吧,一句話“自從用了Spring AOP啊,事務管理真輕鬆啊,真輕鬆;事務管理程式碼沒有了,腦不酸了,手不痛了,一口氣全配上了事務;輕量級,測試起來也簡單,嘿!”。不管從哪個角度看,輕量級宣告式事務都是一件解放生產力的大好事。所以,我們“一直用它”。

  不過,最近的一個專案裡,卻碰到了一個事務管理上的問題:有一個服務類,其一個聲明瞭事務的方法,裡面做了三次插入SQL操作,
但是在後面出錯回滾時,卻發現前面插入成功了,也是說,這個聲明瞭事務的方法,實際上並沒有真正啟動事務!怎麼回事呢?難道Spring的宣告式事務失效了?

[探幽] 
其實以前也會碰到有人說,Spring的事務配置不起作用,但是根據第一反應和以往經驗,我總會告訴他,肯定是你的配置有問題啦;所以這一次,我想也不會例外,大概是把事務註解配在了介面上而不是實現方法上,或者,如果是用XML宣告方式的話,很可能是切入點的表示式沒有配對。

 不過,在檢查了他們的配置後,卻發現沒有配置問題,該起事務的實現方法上,用了@Transactional事務註解宣告,XML裡也配了註解驅動<tx:annotation-driven .../>,配置很正確啊,怎麼會不起作用?

 我很納悶,於是往下問:
 問1:其他方法有這種情況麼?
 答1:沒有。
 問2:這個方法有什麼特別的麼(以下簡稱方法B)?
 答2:就是調後臺插了三條記錄啊,沒啥特別的。
 問3:這個方法是從Web層直接呼叫的吧?
 答3:不是,是這個Service類(以下簡稱ServiceA)的另外一個方法調過來的(以下簡稱方法A)。
 問4:哦,那個呼叫它的方法配了事務麼(問題可能在這了)?
 答4:沒有。
 問5:那WEB層的Action(用的是Struts2),呼叫的是沒有宣告事務的方法A,方法A再呼叫聲明瞭事務的方法B?
 答5:對的。
 問6:你直接在方法A上加上事務宣告看看
 答6:好。。。

 看來可能找到問題所在了,於是把@Transactional也加在方法A上,啟動專案測試,結果是:事務正常生效,
方法A和方法B都在一個事務裡了。 好了,現在總結一下現象: 1、ServiceA類為Web層的Action服務 2、Action呼叫了ServiceA的方法A,而方法A沒有宣告事務(原因是方法A本身比較耗時而又不需要事務) 3、ServiceA的方法A呼叫了自己所在class的方法B,而方法B聲明瞭事務,但是方法B的事務宣告在這種情況失效了。 4、如果在方法A上也宣告事務,則在Action呼叫方法A時,事務生效,而方法B則自動參與了這個事務。 我讓他先把A也加上事務宣告,決定回來自己再測一下。 這個問題,表面上是事務宣告失效的問題,實質上很可能是Spring的AOP機制實現角度的問題。

我想到很久以前研究Spring的AOP實現時發現的一個現象:對於以Cglib方式增強的AOP目標類,會建立兩個物件,一個事Bean例項本身,一個是Cglib增強代理物件,而不僅僅是隻有後者。我曾經疑惑過這一點,但當時沒有再仔細探究下去。

  我們知道,Spring的AOP實現方式有兩種:1、Java代理方式;2、Cglib動態增強方式,這兩種方式在Spring中是可以無縫自由切換的。

Java代理方式的優點是不依賴第三方jar包,缺點是不能代理類,只能代理介面。 
Spring通過AopProxy介面,抽象了這兩種實現,實現了一致的AOP方式: 
這裡寫圖片描述 
現在看來,這種抽象同樣帶了一個缺陷,那就是抹殺了Cglib能夠直接建立普通類的增強子類的能力,Spring相當於把Cglib動態生成的子類,當普通的代理類了,這也是為什麼會建立兩個物件的原因。下圖顯示了Spring的AOP代理類的實際呼叫過程: 
這裡寫圖片描述

因此,從上面的分析可以看出,methodB沒有被AopProxy通知到, 
導致最終結果是: 
被Spring的AOP增強的類,在同一個類的內部方法呼叫時,其被呼叫方法上的增強通知將不起作用。

  而這種結果,會造成什麼影響呢:
  1:內部呼叫時,被呼叫方法的事務宣告將不起作用
  2:換句話說,你在某個方法上宣告它需要事務的時候,如果這個類還有其他開發者,你將不能保證這個方法真的會在事務環境中
  3:再換句話說,Spring的事務傳播策略在內部方法呼叫時將不起作用。

不管你希望某個方法需要單獨事務,是RequiresNew,還是要巢狀事務,要Nested,等等,統統不起作用。 
4:不僅僅是事務通知,所有你自己利用Spring實現的AOP通知,都會受到同樣限制。。。。

[解難]

  問題的原因已經找到,其實,我理想中的AOP實現,應該是下面這樣:

這裡寫圖片描述

只要一個Cglib增強物件就好,對於Java代理方式,我的選擇是毫不猶豫的拋棄。 
至於前面的事務問題,只要避開Spring目前的AOP實現上的限制,要麼都宣告要事務,要麼分開成兩個類,要麼直接在方法裡使用程式設計式事務,那麼一切OK。