在開發過程中,Spring的聲明式事務可以通過一個簡單的@Transactional註解,就讓我們輕鬆進行事務處理。我們知道Spring事務基於AOP,採用動態代理實現,雖然使用簡單,但是在實際場景中,我們也會遇到一些坑。這簡單記錄了一些常見的情況。
一.常見事務失效問題
1.@Transactional屬性設置問題
@Transactional
的rollbackFor用於指定能夠觸發事務回滾的異常類型,可以指定多個,用逗號分隔。rollbackFor
默認值爲UncheckedException,包括了RuntimeException和Error.
當我們直接使用@Transactional
不指定rollbackFor
時,Exception及其子類都不會觸發回滾。所以需要根據情況而定,一般我們會所使用:@Transactional
(rollbackFor=Exception.class),這樣,Exception及其子類都會觸發回滾。
Exception結構圖:
2.一個沒有事務的方法調用本類的另一個有事務的方法導致事務失效
如下這種情況:
public class TranTestService {
@Autowired
private CuponConfirmDao confirmDao;
@Autowired
private TranTest2Service tranTest2Service;
// 方法 A
public int updateStatusT(CuponModel cuponModel) {
System.out.println("更新方法1執行開始");
// 調用本類的方法 B
int count = this.updateOrderStatusT(cuponModel);
System.out.println("更新方法1執行結束");
return count;
}
// 方法 B
@Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.DEFAULT,rollbackFor=Exception.class)
public int updateOrderStatusT(CuponModel cuponModel) {
int count = confirmDao.updateOrderStatusT(cuponModel); // 更新表操作
int sum = 6/0;// 模擬異常
int count1 = confirmDao.updateCupoNoStateT(cuponModel); // 更新表操作
return count + count1;
}
}
以上是部分實驗代碼,小結一下:
a. 一個沒有事務的方法,調用同一個類下面的另一個有事務的方法——事務失效。
b. 一個沒有事務的方法,調用另一個類下面一個有事務的方法——事務有效。
c. 一個有事務的方法,調用同一個類下面的另一個沒有事務的方法——事務有效。
d. 一個有事務的方法,調用另一個類下面一個沒有事務的方法——事務有效。
e. 兩個方法都有事務—— 兩種調用方式事務都有效。
3.try catch異常處理導致事務失效
示例代碼如下:
@Service
public class TranTest2Service {
@Autowired
private CuponConfirmDao confirmDao;
// 方法 B
@Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.DEFAULT,rollbackFor=Exception.class)
public int updateOrderStatusT(CuponModel cuponModel) {
try {
confirmDao.updateOrderStatusT(cuponModel); // 更新表操作
int sum = 1 / 0;// 模擬異常
confirmDao.updateCupoNoStateT(cuponModel); // 更新表操作
} catch (Exception e) {
e.printStackTrace();
}
return 0;
}
}
由此可知,在spring中如果某個業務方法被一個
try{
//bisiness logic code
} catch(Exception e) {
//handle the exception
}
整個包裹起來,則這個業務方法也就等於脫離了spring事務的管理,因爲沒有任何異常會從業務方法中拋出!全被捕獲併吞掉,導致spring異常拋出觸發事務回滾策略失效。
解決方案:
方案一 、外拋
把異常往外拋(不加cry catch),然後在外層 try catch(比如:service層拋出,controller層try catch處理),但是問題來了,出現異常之後,service層代碼就停止運行了,如果我們希望流程能繼續走下去,那就使用方案二(最佳)。
方案二、手動回滾
示例代碼:
public class TranTest2Service {
@Autowired
private CuponConfirmDao confirmDao;
// 方法 B
@Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.DEFAULT,rollbackFor=Exception.class)
public int updateOrderStatusT(CuponModel cuponModel) {
try {
confirmDao.updateOrderStatusT(cuponModel); // 更新表操作
int sum = 1 / 0;// 模擬異常
confirmDao.updateCupoNoStateT(cuponModel); // 更新表操作
} catch (Exception e) {
e.printStackTrace();
//手動回滾事務
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
return 0;
}
}
在catch代碼塊里加上回滾語句:
try {
// 操作代碼塊
} catch (Exception e) {
//手動回滾事務
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
加上該語句即可:TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();