一個程式中不可能沒有事務,而 Spring 中,事務的實現方式分為兩種:程式設計式事務和宣告式事務,又因為程式設計式事務實現相對麻煩,而宣告式事務實現極其簡單,所以在日常專案中,我們都會使用宣告式事務 @Transactional 來實現事務。
@Transactional 使用極其簡單,只需要在類上或方法上新增 @Transactional 關鍵字,就可以實現事務的自動開啟、提交或回滾了,它的基礎用法如下:
@Transactional
@RequestMapping("/add")
public int add(UserInfo userInfo) {
int result = userService.add(userInfo);
return result;
}
@Transactional 會在方法執行前,會自動開啟事務;在方法成功執行完,會自動提交事務;如果方法在執行期間,出現了異常,那麼它會自動回滾事務。
然而,就是看起來極其簡單的 @Transactional,卻隱藏著一些「坑」,這些坑就是我們今天要講的主題:導致 @Transactional 事務失效的常見場景有哪些?
在開始之前,我們先要明確一個定義,什麼叫做「失效」?
本文中的「失效」指的是「失去(它的)功效」,也就是當 @Transactional 不符合我們預期的結果時,我們就可以說 @Transactional 失效了。
那 @Transactional 失效的場景有哪些呢?接下來我們一一來看。
當 @Transactional 修飾的方法為非 public 時,事務就失效了,比如以下程式碼當遇到異常之後,不能自動實現回滾:
@RequestMapping("/save")
int save(UserInfo userInfo) {
// 非空效驗
if (userInfo == null ||
!StringUtils.hasLength(userInfo.getUsername()) ||
!StringUtils.hasLength(userInfo.getPassword()))
return 0;
// 執行新增操作
int result = userService.save(userInfo);
System.out.println("add 受影響的行數:" + result);
int num = 10 / 0; // 此處設定一個異常
return result;
}
以上程式的執行結果如下:
當程式出現執行時異常時,我們預期的結果是事務應該實現自動回滾,也就是新增使用者失敗,然而當我們查詢資料庫時,卻發現事務並未執行回滾操作,資料庫的資料如下圖所示:
當在 @Transactional 上,設定了一個較小的超時時間時,如果方法本身的執行時間超過了設定的 timeout 超時時間,那麼就會導致本來應該正常插入資料的方法執行失敗,範例程式碼如下:
@Transactional(timeout = 3) // 超時時間為 3s
@RequestMapping("/save")
int save(UserInfo userInfo) throws InterruptedException {
// 非空效驗
if (userInfo == null ||
!StringUtils.hasLength(userInfo.getUsername()) ||
!StringUtils.hasLength(userInfo.getPassword()))
return 0;
int result = userService.save(userInfo);
return result;
}
UserService 的 save 方法實現如下:
public int save(UserInfo userInfo) throws InterruptedException {
// 休眠 5s
TimeUnit.SECONDS.sleep(5);
int result = userMapper.add(userInfo);
return result;
}
以上程式的執行結果如下:
資料庫沒有正確的插入資料,如下圖所示:
在前面 @Transactional 的執行流程中,我們提到:當方法中出現了異常之後,事務會自動回滾。然而,如果在程式中加了 try/catch 之後,@Transactional 就不會自動回滾事務了,範例程式碼如下:
@Transactional
@RequestMapping("/save")
public int save(UserInfo userInfo) throws InterruptedException {
// 非空效驗
if (userInfo == null ||
!StringUtils.hasLength(userInfo.getUsername()) ||
!StringUtils.hasLength(userInfo.getPassword()))
return 0;
int result = userService.save(userInfo);
try {
int num = 10 / 0; // 此處設定一個異常
} catch (Exception e) {
}
return result;
}
以上程式的執行結果如下:
此時,查詢資料庫我們發現,程式並沒有執行回滾操作,資料庫中被成功的新增了一條資料,如下圖所示:
當呼叫類內部的 @Transactional 修飾的方法時,事務是不會生效的,範例程式碼如下:
@RequestMapping("/save")
public int saveMappping(UserInfo userInfo) {
return save(userInfo);
}
@Transactional
public int save(UserInfo userInfo) {
// 非空效驗
if (userInfo == null ||
!StringUtils.hasLength(userInfo.getUsername()) ||
!StringUtils.hasLength(userInfo.getPassword()))
return 0;
int result = userService.save(userInfo);
int num = 10 / 0; // 此處設定一個異常
return result;
}
以上程式碼我們在新增方法 save 中新增了 @Transactional 宣告式事務,並且新增了異常程式碼,我們預期的結果是程式出現異常,事務進行自動回滾,以上程式的執行結果如下:
然而,當我們查詢資料庫時發現,程式執行並不符合我們的預期,新增的資料並沒有進行自動回滾操作,如下圖所示:
我們程式中的 @Transactional 只是給呼叫的資料庫傳送了:開始事務、提交事務、回滾事務的指令,但是如果資料庫本身不支援事務,比如 MySQL 中設定了使用 MyISAM 引擎,那麼它本身是不支援事務的,這種情況下,即使在程式中新增了 @Transactional 註解,那麼依然不會有事務的行為,這就是巧婦也難為無米之炊吧。
當宣告式事務 @Transactional 遇到以下場景時,事務會失效:
www.cnblogs.com/frankyou/p/12691463.html
是非審之於己,譭譽聽之於人,得失安之於數。
公眾號:Java面試真題解析