Spring @Transactional注解失效解決方案
這篇文章主要介紹了Spring @Transactional注解失效解決方案,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
這幾天在項(xiàng)目里面發(fā)現(xiàn)我使用@Transactional注解事務(wù)之后,拋了異常居然不回滾。后來終于找到了原因。
如果你也出現(xiàn)了這種情況,可以從下面開始排查。
一、特性
先來了解一下@Transactional注解事務(wù)的特性吧,可以更好排查問題
1、service類標(biāo)簽(一般不建議在接口上)上添加@Transactional,可以將整個(gè)類納入spring事務(wù)管理,在每個(gè)業(yè)務(wù)方法執(zhí)行時(shí)都會開啟一個(gè)事務(wù),不過這些事務(wù)采用相同的管理方式。
2、@Transactional 注解只能應(yīng)用到 public 可見度的方法上。 如果應(yīng)用在protected、private或者 package可見度的方法上,也不會報(bào)錯,不過事務(wù)設(shè)置不會起作用。
3、默認(rèn)情況下,Spring會對unchecked異常進(jìn)行事務(wù)回滾;如果是checked異常則不回滾。
辣么什么是checked異常,什么是unchecked異常
java里面將派生于Error或者RuntimeException(比如空指針,1/0)的異常稱為unchecked異常,其他繼承自java.lang.Exception得異常統(tǒng)稱為Checked Exception,如IOException、TimeoutException等
辣么再通俗一點(diǎn):你寫代碼出現(xiàn)的空指針等異常,會被回滾,文件讀寫,網(wǎng)絡(luò)出問題,spring就沒法回滾了。然后我教大家怎么記這個(gè),因?yàn)楹芏嗤瑢W(xué)容易弄混,你寫代碼的時(shí)候有些IOException我們的編譯器是能夠檢測到的,說以叫checked異常,你寫代碼的時(shí)候空指針等死檢測不到的,所以叫unchecked異常。這樣是不是好記一些啦
4、只讀事務(wù):
@Transactional(propagation=Propagation.NOT_SUPPORTED,readOnly=true)
只讀標(biāo)志只在事務(wù)啟動時(shí)應(yīng)用,否則即使配置也會被忽略。
啟動事務(wù)會增加線程開銷,數(shù)據(jù)庫因共享讀取而鎖定(具體跟數(shù)據(jù)庫類型和事務(wù)隔離級別有關(guān))。通常情況下,僅是讀取數(shù)據(jù)時(shí),不必設(shè)置只讀事務(wù)而增加額外的系統(tǒng)開銷。
二:事務(wù)傳播模式
Propagation枚舉了多種事務(wù)傳播模式,部分列舉如下:
1、REQUIRED(默認(rèn)模式):業(yè)務(wù)方法需要在一個(gè)容器里運(yùn)行。如果方法運(yùn)行時(shí),已經(jīng)處在一個(gè)事務(wù)中,那么加入到這個(gè)事務(wù),否則自己新建一個(gè)新的事務(wù)。
2、NOT_SUPPORTED:聲明方法不需要事務(wù)。如果方法沒有關(guān)聯(lián)到一個(gè)事務(wù),容器不會為他開啟事務(wù),如果方法在一個(gè)事務(wù)中被調(diào)用,該事務(wù)會被掛起,調(diào)用結(jié)束后,原先的事務(wù)會恢復(fù)執(zhí)行。
3、REQUIRESNEW:不管是否存在事務(wù),該方法總匯為自己發(fā)起一個(gè)新的事務(wù)。如果方法已經(jīng)運(yùn)行在一個(gè)事務(wù)中,則原有事務(wù)掛起,新的事務(wù)被創(chuàng)建。
4、 MANDATORY:該方法只能在一個(gè)已經(jīng)存在的事務(wù)中執(zhí)行,業(yè)務(wù)方法不能發(fā)起自己的事務(wù)。如果在沒有事務(wù)的環(huán)境下被調(diào)用,容器拋出例外。
5、SUPPORTS:該方法在某個(gè)事務(wù)范圍內(nèi)被調(diào)用,則方法成為該事務(wù)的一部分。如果方法在該事務(wù)范圍外被調(diào)用,該方法就在沒有事務(wù)的環(huán)境下執(zhí)行。
6、NEVER:該方法絕對不能在事務(wù)范圍內(nèi)執(zhí)行。如果在就拋例外。只有該方法沒有關(guān)聯(lián)到任何事務(wù),才正常執(zhí)行。
7、NESTED:如果一個(gè)活動的事務(wù)存在,則運(yùn)行在一個(gè)嵌套的事務(wù)中。如果沒有活動事務(wù),則按REQUIRED屬性執(zhí)行。它使用了一個(gè)單獨(dú)的事務(wù),這個(gè)事務(wù)擁有多個(gè)可以回滾的保存點(diǎn)。內(nèi)部事務(wù)的回滾不會對外部事務(wù)造成影響。它只對DataSourceTransactionManager事務(wù)管理器起效。
三:解決Transactional注解不回滾
1、檢查你方法是不是public的
2、你的異常類型是不是unchecked異常
如果我想check異常也想回滾怎么辦,注解上面寫明異常類型即可
@Transactional(rollbackFor=Exception.class)
類似的還有norollbackFor,自定義不回滾的異常
3、數(shù)據(jù)庫引擎要支持事務(wù),如果是MySQL,注意表要使用支持事務(wù)的引擎,比如innodb,如果是myisam,事務(wù)是不起作用的
4、是否開啟了對注解的解析
<tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true"/>
5、spring是否掃描到你這個(gè)包,如下是掃描到org.test下面的包
<context:component-scan base-package="org.test" ></context:component-scan>
6、檢查是不是同一個(gè)類中的方法調(diào)用(如a方法調(diào)用同一個(gè)類中的b方法)
7、異常是不是被你catch住了
問題: 為什么方法修飾符不是public就會出現(xiàn)事務(wù)注解失效?
這幾天,同事遇到一個(gè)問題,方向Aop 切入Controller,打請求日志,結(jié)果調(diào)Service層的方法報(bào)空指針錯誤,由于是service沒有注入進(jìn)來。用了@Autowired和@Resource注解都注入不進(jìn)來。一行一行的檢查代碼,都沒有發(fā)現(xiàn)錯誤,后來只能一個(gè)方法一個(gè)方法的刪除,看到哪里可以運(yùn)行,結(jié)果發(fā)現(xiàn)是因?yàn)镃ontroller方法是private私有類型的。后來改了成public就可以了。
貌似不能攔截私有方法?
試了很多次,都失敗了,是不是不行???
我想了一下,因?yàn)閍op底層是代理,
jdk是代理接口,私有方法必然不會存在在接口里,所以就不會被攔截到;
cglib是子類,private的方法照樣不會出現(xiàn)在子類里,也不能被攔截。
我不是類內(nèi)部直接調(diào)用方法,而是通過維護(hù)一個(gè)自身實(shí)例的代理
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.*(..))
還有個(gè)奇怪的現(xiàn)象,execution里如果不寫權(quán)限,那么public protected package的方法都能被攔截到
如果寫了public,那就只攔截public方法這個(gè)沒問題,
如果寫了protected,他就什么事情都不做,連protected的方法也不攔截。
分析
private方法 在Spring使用純Spring AOP(只能攔截public/protected/包)都是無法被攔截的 因?yàn)樽宇悷o法覆蓋;包級別能被攔截的原因是,如果子類和父類在同一個(gè)包中是能覆蓋的。
在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));
}
如果想要實(shí)現(xiàn)攔截private方法的 可以使用 原生 AspectJ 編譯期/運(yùn)行期織入。
引用
如果寫了protected,他就什么事情都不做,連protected的方法也不攔截;這個(gè)應(yīng)該不會
原因基本分析明白了:
是否能應(yīng)用增強(qiáng)的判斷代碼如下(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";
}
}
因?yàn)榍腥朦c(diǎn)沒有訪問修飾符,即可以是任意,因此canApply方法能拿到如wait這種public方法,即可以實(shí)施代理。
場景2:execution(public * *(..))
public class Impl2 {
public String testAop2() {
System.out.println("234");
return "1233";
}
}
因?yàn)閿r截public的,因此canApply方法能拿到如wait這種public方法,即可以實(shí)施代理。
場景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環(huán)境下不行的原因。
注,@Transactional注解事務(wù)的特殊情況:
引用
方法的可見度和 @Transactional
在使用代理的時(shí)候,@Transactional 注解應(yīng)該只被應(yīng)用到 public 可見度的方法上。 如果你在 protected、private 或者 package-visible 的方法上使用 @Transactional 注解,系統(tǒng)也不會報(bào)錯, 但是這個(gè)被注解的方法將不會執(zhí)行已配置的事務(wù)設(shè)置。如果你非要注解非公共方法的話,請參考使用AspectJ
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持我們。
上一篇:springboot單元測試兩種方法實(shí)例詳解
欄 目:Java
下一篇:Java的優(yōu)先隊(duì)列PriorityQueue原理及實(shí)例分析
本文標(biāo)題:Spring @Transactional注解失效解決方案
本文地址:http://www.jygsgssxh.com/a1/Java/8897.html
您可能感興趣的文章
- 01-10Springboot中@Value的使用詳解
- 01-10springboot實(shí)現(xiàn)文件上傳步驟解析
- 01-10springboot jta atomikos實(shí)現(xiàn)分布式事物管理
- 01-10SpringBoot使用RabbitMQ延時(shí)隊(duì)列(小白必備)
- 01-10如何基于SpringBoot部署外部Tomcat過程解析
- 01-10SPRING BOOT啟動命令參數(shù)及源碼詳析
- 01-10springboot集成fastDfs過程代碼實(shí)例
- 01-10springmvc級聯(lián)屬性處理無法轉(zhuǎn)換異常問題解決
- 01-10SPRINGBOOT讀取PROPERTIES配置文件數(shù)據(jù)過程詳解
- 01-10Spring注解和同步鎖不能同步問題解決


閱讀排行
本欄相關(guān)
- 01-10Java實(shí)現(xiàn)動態(tài)模擬時(shí)鐘
- 01-10Springboot中@Value的使用詳解
- 01-10JavaWeb實(shí)現(xiàn)郵件發(fā)送功能
- 01-10利用Java實(shí)現(xiàn)復(fù)制Excel工作表功能
- 01-10Java實(shí)現(xiàn)動態(tài)數(shù)字時(shí)鐘
- 01-10java基于poi導(dǎo)出excel透視表代碼實(shí)例
- 01-10java實(shí)現(xiàn)液晶數(shù)字字體顯示當(dāng)前時(shí)間
- 01-10基于Java驗(yàn)證jwt token代碼實(shí)例
- 01-10Java動態(tài)顯示當(dāng)前日期和時(shí)間
- 01-10淺談Java中真的只有值傳遞么
隨機(jī)閱讀
- 08-05織夢dedecms什么時(shí)候用欄目交叉功能?
- 08-05DEDE織夢data目錄下的sessions文件夾有什
- 08-05dedecms(織夢)副欄目數(shù)量限制代碼修改
- 01-10delphi制作wav文件的方法
- 01-10使用C語言求解撲克牌的順子及n個(gè)骰子
- 01-10C#中split用法實(shí)例總結(jié)
- 01-10SublimeText編譯C開發(fā)環(huán)境設(shè)置
- 01-11ajax實(shí)現(xiàn)頁面的局部加載
- 04-02jquery與jsp,用jquery
- 01-11Mac OSX 打開原生自帶讀寫NTFS功能(圖文


