重慶分公司,新征程啟航
為企業(yè)提供網(wǎng)站建設(shè)、域名注冊(cè)、服務(wù)器等服務(wù)
為企業(yè)提供網(wǎng)站建設(shè)、域名注冊(cè)、服務(wù)器等服務(wù)
??在Spring項(xiàng)目中,我們經(jīng)常使用注解@Transactional來(lái)標(biāo)識(shí)一個(gè)事務(wù)方法。但是,有時(shí)會(huì)發(fā)現(xiàn)這個(gè)事務(wù)并不是按照我們想要的方式執(zhí)行。通常在以下幾種情況下,你以為的事務(wù)管理可能并不是你認(rèn)為的事務(wù)管理。
??在業(yè)務(wù)方法中使用try catch來(lái)捕獲了異常,然后異常出現(xiàn),會(huì)進(jìn)入到catch塊中,但是沒有進(jìn)行拋出,結(jié)果事務(wù)沒有被正確的回滾。這里沒有正確回滾的原因就在于,沒有正確的理解Spring事務(wù)管理的原理。Spring采用了代理的方式來(lái)實(shí)現(xiàn)事務(wù)管理,調(diào)用的順序是開啟事務(wù),執(zhí)行目標(biāo)方法,提交或回滾事務(wù)。所以雖然目標(biāo)方法中出現(xiàn)了異常,但是卻將異常直接處理了,在代理類眼中,目標(biāo)方法沒有拋出異常,所以事務(wù)也就正常提交。
@Transactional(rollbackFor = Exception.class)
public void transfer(int amount) {
try{
serviceB.sub(amount);
serviceA.add(amount);
} catch (Exception e) {
LOGGER.error("error occur");
}
}
??正確的解決方法有兩種,一種是直接在catch中繼續(xù)拋出異常,第二種就是直接告訴Spring當(dāng)前事務(wù)需要rollback。
// 1
throw new RuntimeException(e);
// 2
TransactionInterceptor.currentTransactionStatus().setRollbackOnly();
拋出檢查異常??在方法中若是拋出檢查異常,比如fileNotFound這種,事務(wù)則是不會(huì)回滾的,這其中的原因就在于@Transactional 注解默認(rèn)的是rollbackFor是運(yùn)行期異常。這也就是在阿里的開發(fā)規(guī)范中要求一定要指定rollbackFor的原因。
所以在使用這個(gè)注解的時(shí)候還是建議寫明 rollbackFor 這樣才能明確知道出現(xiàn)了什么異常才會(huì)回滾的。
@Transactional(rollbackFor = Exception.class)
錯(cuò)誤添加切面??Aop切面順序?qū)е率聞?wù)不能正確回滾,原因是事務(wù)切面優(yōu)先級(jí)最低,但如果自定義的切面優(yōu)先級(jí)和它一樣,則還是自定義切面在內(nèi)層,這是若自定義切面沒有正確拋出異常,在catch中吃掉了異常,此時(shí)就會(huì)出現(xiàn)和第一種情況類似的情況,代理類得不到異常信息,也就不會(huì)回滾。
??解決方案就是在切面中拋出異常,或者就是將自定義的切面的優(yōu)先級(jí)設(shè)置為更小。但是建議使用第一種在切面中拋出異常。
??非public的方法會(huì)導(dǎo)致事務(wù)失效。Spring為方法創(chuàng)建代理,添加事務(wù)通知,前提條件是該方法是public的,這點(diǎn)需要注意的就是要么設(shè)置方法為public,要么可以為非public的方法添加事務(wù)通知。
建議使用public方法而不是添加配置
@Bean
public TransactionAttributeSource transactionAttributeSource(){
return new AnnotationTransactionAttributeSource(false);
}
調(diào)用本類方法導(dǎo)致傳播行為失效??同一個(gè)Service的兩個(gè)方法之間調(diào)用,就會(huì)出現(xiàn)這個(gè)問(wèn)題,原因還是在代理對(duì)象這里,我們期待的調(diào)用時(shí)一個(gè)代理類的調(diào)用,但是我們?nèi)羰侵苯釉诜椒ㄖ袃?nèi)部調(diào)用,則被調(diào)用的方法的事務(wù)失效,沒有被Aop增強(qiáng)。
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
public void a (){
b();
}
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
public void b (){
}
??以上的改進(jìn)方案就是自己調(diào)用自己,自己注入自己。你可能看見過(guò)這樣的代碼,就是為了解決這個(gè)問(wèn)題的。這種解決方案最常見。
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES)
public void a (){
service.b();
}
??還有一種方法可以通過(guò) AopContext 拿到代理對(duì)象,然后再調(diào)用。這里要注意,使用這種方式需要開啟暴露代理。
@EnableAspectJAutoProxy(exposeProxy = true)
public class Application {}
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
public void a (){
((TestService)AopContext.currentProxy()).b();
}
Transactional并沒有保證原子性??這個(gè)問(wèn)題非常常見,我們總會(huì)以為加了事務(wù)管理,尤其是加了 Propagation.REQUIRES_NEW 之后我們的事務(wù)就會(huì)在方法執(zhí)行之后提交事務(wù),或是加了 synchronized 關(guān)鍵字之后,這就是一個(gè)原子操作了,這是不對(duì)的哈。為什么呢?還要從代理類說(shuō)起,代理類開啟事務(wù),執(zhí)行目標(biāo)方法,提交事務(wù)。而不管是 REQUIRES_NEW 還是 synchronized 關(guān)鍵字都只是作用于目標(biāo)方法,即使目標(biāo)方法執(zhí)行成功,可是事務(wù)還是沒有提交呢。
??對(duì)于 insert,delete,update,select — for update,語(yǔ)句來(lái)說(shuō),都是原子性的。但是 select 不是。
??解決方案就是擴(kuò)大 synchronized 的范圍,為整個(gè)代理方法加鎖,而不是把鎖加在目標(biāo)方法上。也可以通過(guò) SQL 來(lái)控制,保證操作的原子性,使用 select — for update
你是否還在尋找穩(wěn)定的海外服務(wù)器提供商?創(chuàng)新互聯(lián)www.cdcxhl.cn海外機(jī)房具備T級(jí)流量清洗系統(tǒng)配攻擊溯源,準(zhǔn)確流量調(diào)度確保服務(wù)器高可用性,企業(yè)級(jí)服務(wù)器適合批量采購(gòu),新人活動(dòng)首月15元起,快前往官網(wǎng)查看詳情吧