1. 程式人生 > 程式設計 >Spring @Transactional註解失效解決方案

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

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。