Spring @Transactional註解失效解決方案
這篇文章主要介紹了Spring @Transactional註解失效解決方案,文中通過示例程式碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
這幾天在專案裡面發現我使用@Transactional註解事務之後,拋了異常居然不回滾。後來終於找到了原因。
如果你也出現了這種情況,可以從下面開始排查。
一、特性
先來了解一下@Transactional註解事務的特性吧,可以更好排查問題
1、service類標籤(一般不建議在介面上)上新增@Transactional,可以將整個類納入spring事務管理,在每個業務方法執行時都會開啟一個事務,不過這些事務採用相同的管理方式。
2、@Transactional 註解只能應用到 public 可見度的方法上。 如果應用在protected、private或者 package可見度的方法上,也不會報錯,不過事務設定不會起作用。
3、預設情況下,Spring會對unchecked異常進行事務回滾;如果是checked異常則不回滾。
辣麼什麼是checked異常,什麼是unchecked異常
java裡面將派生於Error或者RuntimeException(比如空指標,1/0)的異常稱為unchecked異常,其他繼承自java.lang.Exception得異常統稱為Checked Exception,如IOException、TimeoutException等
辣麼再通俗一點:你寫程式碼出現的空指標等異常,會被回滾,檔案讀寫,網路出問題,spring就沒法回滾了。然後我教大家怎麼記這個,因為很多同學容易弄混,你寫程式碼的時候有些IOException我們的編譯器是能夠檢測到的,說以叫checked異常,你寫程式碼的時候空指標等死檢測不到的,所以叫unchecked異常。這樣是不是好記一些啦
4、只讀事務:
@Transactional(propagation=Propagation.NOT_SUPPORTED,readOnly=true)
只讀標誌只在事務啟動時應用,否則即使配置也會被忽略。
啟動事務會增加執行緒開銷,資料庫因共享讀取而鎖定(具體跟資料庫型別和事務隔離級別有關)。通常情況下,僅是讀取資料時,不必設定只讀事務而增加額外的系統開銷。
二:事務傳播模式
Propagation枚舉了多種事務傳播模式,部分列舉如下:
1、REQUIRED(預設模式):業務方法需要在一個容器裡執行。如果方法執行時,已經處在一個事務中,那麼加入到這個事務,否則自己新建一個新的事務。
2、NOT_SUPPORTED:宣告方法不需要事務。如果方法沒有關聯到一個事務,容器不會為他開啟事務,如果方法在一個事務中被呼叫,該事務會被掛起,呼叫結束後,原先的事務會恢復執行。
3、REQUIRESNEW:不管是否存在事務,該方法總彙為自己發起一個新的事務。如果方法已經執行在一個事務中,則原有事務掛起,新的事務被建立。
4、 MANDATORY:該方法只能在一個已經存在的事務中執行,業務方法不能發起自己的事務。如果在沒有事務的環境下被呼叫,容器丟擲例外。
5、SUPPORTS:該方法在某個事務範圍內被呼叫,則方法成為該事務的一部分。如果方法在該事務範圍外被呼叫,該方法就在沒有事務的環境下執行。
6、NEVER:該方法絕對不能在事務範圍內執行。如果在就拋例外。只有該方法沒有關聯到任何事務,才正常執行。
7、NESTED:如果一個活動的事務存在,則執行在一個巢狀的事務中。如果沒有活動事務,則按REQUIRED屬性執行。它使用了一個單獨的事務,這個事務擁有多個可以回滾的儲存點。內部事務的回滾不會對外部事務造成影響。它只對DataSourceTransactionManager事務管理器起效。
三:解決Transactional註解不回滾
1、檢查你方法是不是public的
2、你的異常型別是不是unchecked異常
如果我想check異常也想回滾怎麼辦,註解上面寫明異常型別即可
@Transactional(rollbackFor=Exception.class)
類似的還有norollbackFor,自定義不回滾的異常
3、資料庫引擎要支援事務,如果是MySQL,注意表要使用支援事務的引擎,比如innodb,如果是myisam,事務是不起作用的
4、是否開啟了對註解的解析
<tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true"/>
5、spring是否掃描到你這個包,如下是掃描到org.test下面的包
<context:component-scan base-package="org.test" ></context:component-scan>
6、檢查是不是同一個類中的方法呼叫(如a方法呼叫同一個類中的b方法)
7、異常是不是被你catch住了
問題: 為什麼方法修飾符不是public就會出現事務註解失效?
這幾天,同事遇到一個問題,方向Aop 切入Controller,打請求日誌,結果調Service層的方法報空指標錯誤,由於是service沒有注入進來。用了@Autowired和@Resource註解都注入不進來。一行一行的檢查程式碼,都沒有發現錯誤,後來只能一個方法一個方法的刪除,看到哪裡可以執行,結果發現是因為Controller方法是private私有型別的。後來改了成public就可以了。
貌似不能攔截私有方法?
試了很多次,都失敗了,是不是不行啊?
我想了一下,因為aop底層是代理,
jdk是代理介面,私有方法必然不會存在在接口裡,所以就不會被攔截到;
cglib是子類,private的方法照樣不會出現在子類裡,也不能被攔截。
我不是類內部直接呼叫方法,而是通過維護一個自身例項的代理
execution(* test.aop.ServiceA.*(..))
public class ServiceA { private ServiceA self; public void setSelf(ServiceA self) { this.self = self; } public String methodA(String str) { System.out.println("methodA: args=" + str); self.methodB("b"); return "12345" + str; } private String methodB(String str) { System.out.println("methodB: args=" + str); self.methodC("c"); return "12345" + str; } public String methodC(String str) { System.out.println("methodC: args=" + str); return "12345" + str; } }
是不是這麼回事?
但是stackoverflow上,有人說 it works fine
execution(public * test.aop.ServiceA.*(..))
還有個奇怪的現象,execution裡如果不寫許可權,那麼public protected package的方法都能被攔截到
如果寫了public,那就只攔截public方法這個沒問題,
如果寫了protected,他就什麼事情都不做,連protected的方法也不攔截。
分析
private方法 在Spring使用純Spring AOP(只能攔截public/protected/包)都是無法被攔截的 因為子類無法覆蓋;包級別能被攔截的原因是,如果子類和父類在同一個包中是能覆蓋的。
在cglib代理情況下, execution(* *(..)) 可以攔截 public/protected/包級別方法(即這些方法都是能代理的)。
private static boolean isOverridable(Method method,Class targetClass) { if (Modifier.isPrivate(method.getModifiers())) { return false; } if (Modifier.isPublic(method.getModifiers()) || Modifier.isProtected(method.getModifiers())) { return true; } return getPackageName(method.getDeclaringClass()).equals(getPackageName(targetClass)); }
如果想要實現攔截private方法的 可以使用 原生 AspectJ 編譯期/執行期織入。
引用
如果寫了protected,他就什麼事情都不做,連protected的方法也不攔截;這個應該不會
原因基本分析明白了:
是否能應用增強的判斷程式碼如下(org.springframework.aop.support.AopUtils):
public static boolean canApply(Pointcut pc,Class targetClass,boolean hasIntroductions) { if (!pc.getClassFilter().matches(targetClass)) { return false; } MethodMatcher methodMatcher = pc.getMethodMatcher(); IntroductionAwareMethodMatcher introductionAwareMethodMatcher = null; if (methodMatcher instanceof IntroductionAwareMethodMatcher) { introductionAwareMethodMatcher = (IntroductionAwareMethodMatcher) methodMatcher; } Set classes = new HashSet(ClassUtils.getAllInterfacesForClassAsSet(targetClass)); classes.add(targetClass); for (Iterator it = classes.iterator(); it.hasNext();) { Class clazz = (Class) it.next(); Method[] methods = clazz.getMethods(); for (int j = 0; j < methods.length; j++) { if ((introductionAwareMethodMatcher != null && introductionAwareMethodMatcher.matches(methods[j],targetClass,hasIntroductions)) || methodMatcher.matches(methods[j],targetClass)) { return true; } } } return false; }
此處Method[] methods = clazz.getMethods();只能拿到public方法。。
場景1:execution(* *(..))
public class Impl2 { protected/public String testAop2() { System.out.println("234"); return "1233"; } }
因為切入點沒有訪問修飾符,即可以是任意,因此canApply方法能拿到如wait這種public方法,即可以實施代理。
場景2:execution(public * *(..))
public class Impl2 { public String testAop2() { System.out.println("234"); return "1233"; } }
因為攔截public的,因此canApply方法能拿到如wait這種public方法,即可以實施代理。
場景3:execution(protected * *(..))
public class Impl2 { protected String testAop2() { System.out.println("234"); return "1233"; } }
還記得之前說過,在canApply方法中 的 Method[] methods = clazz.getMethods();只能拿到public方法的,因此跟protected訪問修飾符是無法匹配的,所以如果“execution(protected * *(..))” 是 無法代理的。
這就是為什麼execution(protected * *(..))在純Spring AOP環境下不行的原因。
注,@Transactional註解事務的特殊情況:
引用
方法的可見度和 @Transactional
在使用代理的時候,@Transactional 註解應該只被應用到 public 可見度的方法上。 如果你在 protected、private 或者 package-visible 的方法上使用 @Transactional 註解,系統也不會報錯, 但是這個被註解的方法將不會執行已配置的事務設定。如果你非要註解非公共方法的話,請參考使用AspectJ
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。