Spring Boot 事務配置管理

原本已收錄到我寫的10萬字Springboot經典學習筆記中,筆記在持續更新……文末有領取方式java

1. 事務相關

場景:咱們在開發企業應用時,因爲數據操做在順序執行的過程當中,線上可能有各類沒法預知的問題,任何一步操做都有可能發生異常,異常則會致使後續的操做沒法完成。此時因爲業務邏輯並未正確的完成,因此在以前操做過數據庫的動做並不可靠,須要在這種狀況下進行數據的回滾。mysql

事務的做用就是爲了保證用戶的每個操做都是可靠的,事務中的每一步操做都必須成功執行,只要有發生異常就回退到事務開始未進行操做的狀態。這很好理解,轉帳、購票等等,必須整個事件流程所有執行完才能人爲該事件執行成功,不能轉錢轉到一半,系統死了,轉帳人錢沒了,收款人錢還沒到。web

事務管理是 Spring Boot 框架中最爲經常使用的功能之一,咱們在實際應用開發時,基本上在 service 層處理業務邏輯的時候都要加上事務,固然了,有時候可能因爲場景須要,也不用加事務(好比咱們就要往一個表裏插數據,相互沒有影響,插多少是多少,不能由於某個數據掛了,把以前插的所有回滾)。spring

2. Spring Boot 事務配置

2.1 依賴導入

在 Spring Boot 中使用事務,須要導入 mysql 依賴:sql

<dependency>
 <groupId>org.mybatis.spring.boot</groupId>
 <artifactId>mybatis-spring-boot-starter</artifactId>
 <version>1.3.2</version>
</dependency>

導入了 mysql 依賴後,Spring Boot 會自動注入 DataSourceTransactionManager,咱們不須要任何其餘的配置就能夠用 @Transactional 註解進行事務的使用。關於 mybatis 的配置,在上一節課中已經說明了,這裏仍是使用上一節課中的 mybatis 配置便可。數據庫

2.2 事務的測試

咱們首先在數據庫表中插入一條數據:編程

id user_name password
1 倪升武 123456

而後咱們寫一個插入的 mapper:微信

public interface UserMapper {

    @Insert("insert into user (user_name, password) values (#{username}, #{password})")
    Integer insertUser(User user);
}

OK,接下來咱們來測試一下 Spring Boot 中的事務處理,在 service 層,咱們手動拋出個異常來模擬實際中出現的異常,而後觀察一下事務有沒有回滾,若是數據庫中沒有新的記錄,則說明事務回滾成功。mybatis

@Service
public class UserServiceImpl implements UserService {

    @Resource
    private UserMapper userMapper;

    @Override
    @Transactional
    public void isertUser(User user) {
        // 插入用戶信息
        userMapper.insertUser(user);
        // 手動拋出異常
        throw new RuntimeException();
    }
}

咱們來測試一下:架構

@RestController
public class TestController {

    @Resource
    private UserService userService;

    @PostMapping("/adduser")
    public String addUser(@RequestBody User user) throws Exception {
        if (null != user) {
            userService.isertUser(user);
            return "success";
        } else {
            return "false";
        }
    }
}

咱們使用 postman 調用一下該接口,由於在程序中拋出了個異常,會形成事務回滾,咱們刷新一下數據庫,並無增長一條記錄,說明事務生效了。事務很簡單,咱們平時在使用的時候,通常不會有多少問題,可是並不只僅如此……

3. 常見問題總結

從上面的內容中能夠看出,Spring Boot 中使用事務很是簡單,@Transactional 註解便可解決問題,說是這麼說,可是在實際項目中,是有不少小坑在等着咱們,這些小坑是咱們在寫代碼的時候沒有注意到,並且正常狀況下不容易發現這些小坑,等項目寫大了,某一天忽然出問題了,排查問題很是困難,到時候確定是抓瞎,須要費很大的精力去排查問題。

這一小節,我專門針對實際項目中常常出現的,和事務相關的細節作一下總結,但願讀者在讀完以後,可以落實到本身的項目中,能有所受益。

3.1 異常並無被 」捕獲「 到

首先要說的,就是異常並無被 」捕獲「 到,致使事務並無回滾。咱們在業務層代碼中,也許已經考慮到了異常的存在,或者編輯器已經提示咱們須要拋出異常,可是這裏面有個須要注意的地方:並非說咱們把異常拋出來了,有異常了事務就會回滾,咱們來看一個例子:

@Service
public class UserServiceImpl implements UserService {

    @Resource
    private UserMapper userMapper;
    
    @Override
    @Transactional
    public void isertUser2(User user) throws Exception {
        // 插入用戶信息
        userMapper.insertUser(user);
        // 手動拋出異常
        throw new SQLException("數據庫異常");
    }
}

咱們看上面這個代碼,其實並無什麼問題,手動拋出一個 SQLException 來模擬實際中操做數據庫發生的異常,在這個方法中,既然拋出了異常,那麼事務應該回滾,實際卻不如此,讀者可使用我源碼中 controller 的接口,經過 postman 測試一下,就會發現,仍然是能夠插入一條用戶數據的。

那麼問題出在哪呢?由於 Spring Boot 默認的事務規則是遇到運行異常(RuntimeException)和程序錯誤(Error)纔會回滾。好比上面咱們的例子中拋出的 RuntimeException 就沒有問題,可是拋出 SQLException 就沒法回滾了。針對非檢測異常,若是要進行事務回滾的話,能夠在 @Transactional 註解中使用 rollbackFor 屬性來指定異常,好比 @Transactional(rollbackFor = Exception.class),這樣就沒有問題了,因此在實際項目中,必定要指定異常。

3.2 異常被 」吃「 掉

這個標題很搞笑,異常怎麼會被吃掉呢?仍是迴歸到現實項目中去,咱們在處理異常時,有兩種方式,要麼拋出去,讓上一層來捕獲處理;要麼把異常 try catch 掉,在異常出現的地方給處理掉。就由於有這中 try...catch,因此致使異常被 」吃「 掉,事務沒法回滾。咱們仍是看上面那個例子,只不過簡單修改一下代碼:

@Service
public class UserServiceImpl implements UserService {

    @Resource
    private UserMapper userMapper;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void isertUser3(User user
{
        try {
            // 插入用戶信息
            userMapper.insertUser(user);
            // 手動拋出異常
            throw new SQLException("數據庫異常");
        } catch (Exception e) {
   // 異常處理邏輯
        }
    }
}

讀者可使用我源碼中 controller 的接口,經過 postman 測試一下,就會發現,仍然是能夠插入一條用戶數據,說明事務並無由於拋出異常而回滾。這個細節每每比上面那個坑更難以發現,由於咱們的思惟很容易致使 try...catch 代碼的產生,一旦出現這種問題,每每排查起來比較費勁,因此咱們平時在寫代碼時,必定要多思考,多注意這種細節,儘可能避免給本身埋坑。

那這種怎麼解決呢?直接往上拋,給上一層來處理便可,千萬不要在事務中把異常本身 」吃「 掉。

3.3 事務的範圍

事務範圍這個東西比上面兩個坑埋的更深!我之因此把這個也寫上,是由於這是我以前在實際項目中遇到的,該場景在這個課程中我就不模擬了,我寫一個 demo 讓你們看一下,把這個坑記住便可,之後在寫代碼時,遇到併發問題,就會注意這個坑了,那麼這節課也就有價值了。

我來寫個 demo:

@Service
public class UserServiceImpl implements UserService {

    @Resource
    private UserMapper userMapper;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public synchronized void isertUser4(User user
{
        // 實際中的具體業務……
        userMapper.insertUser(user);
    }
}

能夠看到,由於要考慮併發問題,我在業務層代碼的方法上加了個 synchronized 關鍵字。我舉個實際的場景,好比一個數據庫中,針對某個用戶,只有一條記錄,下一個插入動做過來,會先判斷該數據庫中有沒有相同的用戶,若是有就不插入,就更新,沒有才插入,因此理論上,數據庫中永遠就一條同一用戶信息,不會出現同一數據庫中插入了兩條相同用戶的信息。

可是在壓測時,就會出現上面的問題,數據庫中確實有兩條同一用戶的信息,分析其緣由,在於事務的範圍和鎖的範圍問題。

從上面方法中能夠看到,方法上是加了事務的,那麼也就是說,在執行該方法開始時,事務啓動,執行完了後,事務關閉。可是 synchronized 沒有起做用,其實根本緣由是由於事務的範圍比鎖的範圍大。也就是說,在加鎖的那部分代碼執行完以後,鎖釋放掉了,可是事務還沒結束,此時另外一個線程進來了,事務沒結束的話,第二個線程進來時,數據庫的狀態和第一個線程剛進來是同樣的。即因爲mysql Innodb引擎的默認隔離級別是可重複讀(在同一個事務裏,SELECT的結果是事務開始時時間點的狀態),線程二事務開始的時候,線程一還沒提交完成,致使讀取的數據還沒更新。第二個線程也作了插入動做,致使了髒數據。

這個問題能夠避免,第一,把事務去掉便可(不推薦);第二,在調用該 service 的地方加鎖,保證鎖的範圍比事務的範圍大便可。

4. 總結

本章主要總結了 Spring Boot 中如何使用事務,只要使用 @Transactional 註解便可使用,很是簡單方便。除此以外,重點總結了三個在實際項目中可能遇到的坑點,這很是有意義,由於事務這東西不出問題還好,出了問題比較難以排查,因此總結的這三點注意事項,但願能幫助到開發中的朋友。

該文已收錄到我寫的《10萬字Springboot經典學習筆記》中,點擊下面小卡片,進入【Java開發寶典】,回覆:筆記,便可免費獲取。


點贊是最大的支持 

本文分享自微信公衆號 - 武哥聊編程(eson_15)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索