SpringBoot2.x學習-事務管理

一、事務介紹

1、數據庫事務:是數據庫管理系統執行過程中的一個邏輯單位,由一個有限的數據庫操作序列構成。這些操作要麼全部執行成功提交(commit),要麼全部中止失敗(abort,rollback)。就是在數據庫執行多條SQL語句,要麼都執行成功,要麼都執行失敗。
2、數據庫事務必須同時滿足4個特性:原子性(Atomic)、一致性(Consistency)、隔離性(Isolation)和持久性(Durabiliy),簡稱爲ACID四大特性。

  • 原子性:表示組成一個事務的多個數據庫操作是一個不可分割的原子單元,只有所有的操作執行成功,整個事務才提交。
    事務中的任何一個數據庫操作失敗,已經執行的任何操作都必須撤銷,讓數據庫返回到初始狀態。
  • 一致性:事務操作成功後,數據接所處的狀態和它的業務規則是一致的,數據不會被破壞。
  • 隔離性:在併發數據操作時,不同的事務擁有各自的數據空間,它們的操作不會對對方產生干擾。準確地說,並非要求做到完全無干擾。數據庫規定了多種事務隔離級別,不同的隔離級別對應不同的干擾程度,隔離級別越高,數據一致性越好,但併發性越弱。
  • 持久性:一旦事務提交成功之後,事務中所有的數據操作都必須被持久化到數據庫中。即使在提交事務之後,數據庫馬上崩潰,在數據庫重啓時,也必須保證能夠通過某種機制恢復數據。

在這些事務特性中,數據"一致性"是最終目標,其他特性都是爲達到這個目標而採取的措施、要求或手段。
3、數據庫管理系統一般採用重執行日誌來保證原子性、一致性和持久性。
數據庫管理系統採用數據庫鎖機制保證事務的隔離性。當多個事務試圖對相同的數據進行操作時,只有持有鎖的事務才能操作數據,直到前一個事務完成後,後面的事務纔有機會對數據進行操作。

二、事務隔離性(四大隔離級別)

1.當多個線程都開啓事務操作數據庫中的數據時,數據庫系統要能進行隔離操作,以保證各個線程獲取數據的準確性。如果不考慮事務的隔離性,會有以下幾種問題:

  1. 髒讀(dirty read)
  2. 不可重複讀(unrepeatable read)
  3. 幻象讀(phantom read)

2.四大隔離級別

  1. Read Uncommitted 讀取未提交
  2. Read Committed 讀取已提交
  3. Repeated Read 可重複讀
  4. Serializable 可序列化

三、Spring事務的7種傳播特性

在業務開發中會有服務接口方法嵌套調用的情況就會出現事務嵌套。Spring通過事務傳播行爲控制當前的事務如何傳播到被嵌套調用的目標服務接口方法中,就是多個事務方法相互調用時,事務如何在這些方法間傳播。
Spring規定了7種類型的事務傳播行爲,規定了事務方法和事務方法發生嵌套調用時事務如何進行傳播。

  1. PROPAGATION_REQUIRED 1.支持當前事務,如果當前執行線程沒有事務,則新建事務2.如果當前執行線程存在事務,則加入當前事務,合併成一個事務 3.這個是spring的默認事務傳播行爲
  2. REQUIRES_NEW 1.新建事務,如果當前存在事務,則把當前事務掛起2.這個方法會獨立提交事務,不受調用者的事務影響,父級異常,它也是正常提交。 當前事務和新開啓的事務沒有關聯。
  3. PROPAGATION_NOT_SUPPORTED 以非事務方式執行操作(不支持事務)。如果當前存在事務,就把當前事務掛起。
  4. PROPAGATION_SUPPORTS 支持當前事務,如果當前沒有事務,就以非事務方式執行。
  5. PROPAGATION_NEVER 以非事務方式執行,如果當前存在事務,就拋出異常。
  6. PROPAGATION_NESTED 如果當前存在事務,就在嵌套事務內執行。如果當前沒有事務,就執行與PROPAGATION_REQUIRED類型的操作。當前事務和內嵌的事務有關聯,如果外部事務回滾,內嵌事務也會回滾;如果內嵌事務回滾,外部的事務不回滾。
  7. PROPAGATION_MANDATORY 使用當前事務,如果當前沒有事務,就拋出異常。

四、Spring事務的幾種實現方式

1.編程式事務。
2.聲明式事務(通過AOP增強類的功能)。

  • 使用XML配置聲明式事務
  • 使用註解配置聲明式事務

3.@EnableTransactionManagement註解
該註解用於啓用Spring的註解驅動的事務管理功能
SpringBoot自動了配置了事務,開啓了事務支持。在debug級別日誌中,可以看到如下日誌信息:
Creating shared instance of singleton bean ‘org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration$EnableTransactionManagementConfiguration’ 。
EnableTransactionManagement註解包含以下屬性:

  • proxyTargetClass 基於實現類來代理業務類(CGLIB)爲(true) ,基於Java接口的代理(false) 默認值爲 false
  • mode 指示應如何應用事務通知。 默認值 AdviceMode.PROXY
  • order aop事務攔截順序,如果業務類除了事務切面外,還需要織如其他的切面,則通過該屬性可以控制事務切面在目標連接點的織入順序。
    可以在啓動類添加該註解覆蓋掉系統的默認配置。

4.@Transactional註解
@Transactional 註解可以被應用於接口方法和類定義和類的public方法上。屬性如下:

  • transactionManager 用於指定事務管理器
  • propagation 事務傳播類型,默認值爲Propagation.REQUIRED.
  • isolation 事務隔離級別, 默認值 Isolation.DEFAULT.
  • timeout 超時時間 int類型 單位爲秒
  • readOnly 事務讀寫性,布爾型。
  • rollbackFor 用於指定回滾的異常類型,類型爲Class<? extends Throwable>[],默認值爲{},多個異常用逗號隔開。
  • rollbackForClassName 用於指定回滾的異常類名,類型爲String[],默認值爲{}
  • noRollbackFor 用於指定不回滾的異常類型,類型爲Class<? extends Throwable>[] 默認值爲{}
  • noRollbackForClassName 用於指定不回滾的異常類名,類型爲String[],默認值爲{}

在默認情況下,只有當程序拋出運行時異常和unChecked異常時,Spring事務纔會自動回滾事務。也就是隻有當程序拋出一個RuntimeException或其子類實例,以及Error對象時,Spring纔會自動回滾事務。如果事務方法中拋出Checked異常,則事務不會自動回滾。如果想讓事務遇到特定的Checked異常時自動回滾,則需要設置rollbackFor屬性。

五、Spring事務失效的幾種原因

1.方法不加@Transactional註解
如果方法不加@Transactional註解就是沒加事務,這樣就變成了各條SQL單獨的事務,底層JDBC的數據庫連接是自動提交的,就算拋出了運行時異常也不會回滾。

@Override
    public void updateEmp(Emp emp)   throws  Exception{
        empMapper.updateEmp(emp);
        throw  new RuntimeException("運行時異常!");
    }

調用這個updateEmp方法拋出了異常,但是數據庫還是會執行更新語句。
2.自身調用問題(同一個類中)
(1).不帶事務的方法調用帶事務的方法,則事務不生效

/**
 * 員工業務接口實現
 *
 * @author David Lin
 * @version: 1.0
 * @date 2019-11-10 22:32
 */
@Service("empService")
public class EmpServiceImpl implements EmpService {

    @Override
    @Transactional
    public void updateEmp(Emp emp)   throws  Exception{
        empMapper.updateEmp(emp);
        throw  new RuntimeException("運行時異常!");
    }

    @Override
    public void update(Emp emp) throws  Exception {
        this.updateEmp(emp);
    }

調用update方法,發現數據庫還是更新了,updateEmp方法的事務沒有效果。
解決方法:將自己的代理對象注入,沒有事務的方法調用注入對象的方法.

/**
 * 員工業務接口實現
 *
 * @author David Lin
 * @version: 1.0
 * @date 2019-11-10 22:32
 */
@Service("empService")
public class EmpServiceImpl implements EmpService {
  /**
     * 注入自己的代理對象
     */
    @Resource
    private EmpService empService;
    
    @Override
    @Transactional
    public void updateEmp(Emp emp)   throws  Exception{
        empMapper.updateEmp(emp);
        throw  new RuntimeException("運行時異常!");
    }

    @Override
    public void update(Emp emp) throws  Exception {
        //調用的是代理對象增強後的方法
        empService.updateEmp(emp);
    }

這樣updateEmp方法的事務就會有效果,事務就回滾了。還有一種方法也可以讓updateEmp方法的事務生效 如下:

    @Override
    public void update(Emp emp) throws  Exception {
        ((EmpService) (AopContext.currentProxy())).updateEmp(emp);
    }

或者將updateEmp方法寫在其他類中然後注入到當前類中調用。
(2)在一個事務中開啓另外一個事務

@Override
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void updateEmp(Emp emp) {
        empMapper.updateEmp(emp);
    }

    @Override
    @Transactional
    public void update(Emp emp) throws Exception {
        this.updateEmp(emp);
        throw new RuntimeException("運行時異常!");
    }

updateEmp方法註解屬性加了REQUIRES_NEW,代表新開啓一個事務。但是這裏新開啓的事務沒有效果,因爲updateEmp的事務和update方法的事務是同一個(spring事務傳播特性)。解決方法和上面的一樣,注入自己的代理對象然後調用代理對象的方法,這樣updateEmp方法新開啓的事務就會有效果(執行更新語句並且提交事務)。

3.異常沒有拋出

@Override
    @Transactional
    public void updateEmp(Emp emp) {
        try {
            empMapper.updateEmp(emp);
            int num = 20 / 0;
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

由於異常沒有拋出,被‘吃’了導致事務沒有回滾。
解決方法如下:

@Override
    @Transactional
    public void updateEmp(Emp emp) {
        try {
            empMapper.updateEmp(emp);
            int num = 20 / 0;
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("拋出了運行時異常");
        }
    }

4.拋出的異常類型錯誤

@Override
    @Transactional
    public void updateEmp(Emp emp) throws Exception {
        try {
            empMapper.updateEmp(emp);
            int num = 20 / 0;
        } catch (Exception e) {
            e.printStackTrace();
            throw new Exception("拋出了異常");
        }
    }

由於在默認情況下,只有當程序拋出一個RuntimeException或其子類實例,以及Error對象時,Spring纔會自動回滾事務。所以這裏的事務不會回滾。解決方法就是在@Transactional註解上添加rollbackFor屬性 如下:

 @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateEmp(Emp emp) throws Exception {
        try {
            empMapper.updateEmp(emp);
            int num = 20 / 0;
        } catch (Exception e) {
            e.printStackTrace();
            throw new Exception("拋出了異常");
        }
    }

參考文章

發佈了32 篇原創文章 · 獲贊 15 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章