一、 問(wèn)題描述
兩個(gè)使用@Transaction注解的Service,A和B,在A中引入了B的方法用于更新數(shù)據(jù) ,當(dāng)A中捕捉到B中有異常時(shí),回滾動(dòng)作正常執(zhí)行,但是當(dāng)return時(shí)則出現(xiàn) org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only 異常。
代碼示例:
@Service
public class ServiceA {
@Autowired
private ServiceB serviceB;
@Transactional
public void methodA() {
try{
serviceB.methodB();
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Service
public class serviceB {
@Transactional
public void methodB() {
throw new RuntimeException();
}
}
二、問(wèn)題分析
@Transactional的默認(rèn)方式為@Transactional(propagation= Propagation.REQUIRED):如果當(dāng)前存在事務(wù),則加入該事務(wù);如果當(dāng)前沒(méi)有事務(wù),則創(chuàng)建一個(gè)新的事務(wù)。
在這種情況下,外層事務(wù)(ServiceA)和內(nèi)層事務(wù)(ServiceB)就是一個(gè)事務(wù),任何一個(gè)出現(xiàn)異常,都會(huì)在methodA執(zhí)行完畢后回滾。
??
如果內(nèi)層事務(wù)B拋出異常e(沒(méi)有catch,繼續(xù)向外層拋出),在內(nèi)層事務(wù)結(jié)束時(shí),spring會(huì)把事務(wù)B標(biāo)記為“rollback-only”。
這時(shí)外層事務(wù)A發(fā)現(xiàn)了異常e,如果外層事務(wù)A catch了異常并處理掉,那么外層事務(wù)A的方法會(huì)繼續(xù)執(zhí)行代碼,直到外層事務(wù)也結(jié)束。
外層事務(wù)A結(jié)束后想commit,因?yàn)檎=Y(jié)束沒(méi)有向外拋異常,但是內(nèi)外層事務(wù)AB是同一個(gè)事務(wù),事務(wù)B(同時(shí)也是事務(wù)A)已經(jīng)被內(nèi)層方法標(biāo)記為“rollback-only”,需要回滾,無(wú)法commit。
因此spring就拋出了異常org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only,意思是“事務(wù)已經(jīng)被標(biāo)記為回滾,無(wú)法提交”。
三、解決方法
methodB和methodA放在同一個(gè)service中(這個(gè)不大現(xiàn)實(shí), 也不符合代碼規(guī)范);
外層事務(wù)不使用try-catch代碼塊, 讓其自然拋出異常;
在內(nèi)層事務(wù)中做異常捕獲處理,并且不向外拋異常;
在內(nèi)層事務(wù)中做手動(dòng)回滾, 代碼示例:
// 回滾整個(gè)方法
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
// 回滾指定的一段操作
// 設(shè)置回滾點(diǎn)
Object savePoint = TransactionAspectSupport.currentTransactionStatus().createSavepoint();
// 回滾到回滾點(diǎn)
TransactionAspectSupport.currentTransactionStatus().rollbackToSavepoint(savePoint);
推薦:如果希望內(nèi)層事務(wù)回滾,但不影響外層事務(wù)提交,需要將內(nèi)層事務(wù)的傳播方式指定為
@Transactional(propagation= Propagation.NESTED),外層事務(wù)的提交和回滾能夠控制嵌套的內(nèi)層事務(wù)回滾;而內(nèi)層事務(wù)報(bào)錯(cuò)時(shí),只回滾內(nèi)層事務(wù),外層事務(wù)可以繼續(xù)提交。。如果這個(gè)異常發(fā)生時(shí),內(nèi)層需要事務(wù)回滾的代碼還沒(méi)有執(zhí)行,則可以
@Transactional(noRollbackFor = {內(nèi)層拋出的異常}.class),指定內(nèi)層也不為這個(gè)異?;貪L。