1. 程式人生 > 其它 >spring事務為什麼不生效,回滾失效,事務try catch(事務失效)

spring事務為什麼不生效,回滾失效,事務try catch(事務失效)

Spring事務的原理

Spring事務的本質其實就是資料庫Innodb對事務的支援,沒有innodb是事務支援,spring是無法提供事務支援的。真正的資料庫層的事務提交和回滾是通過binlog或者redo log實現的。

對於純jdbc操作資料庫,想要用到事務,需要按照以下的步驟進行:

  1. 獲取連線Connection connection = DriverManager.getConnection(url, username, root);
  2. 開啟事務connection .setAutoCommit(true/false);
  3. 執行CRUD
  4. 提交事務/回滾事務 connection .commit() / connection .rollback();
  5. 關閉連線 connection .close();

程式碼如下

 	try {
            Connection connection = DriverManager.getConnection(url, username, root);
            connection .setAutoCommit(false); //開啟事務,禁止自動提交
            preparedStatement = conn.prepareStatement("update t_category t set t.name='測試' where t.id =?");
            preparedStatement.
setInt(1, 10); preparedStatement.executeUpdate() ; connection .commit(); //提交事務 }catch(Exception e ){ connection .rollback(); }

使用Spring的事務管理功能後,我們可以不再寫步驟 2 和 4 的程式碼,而是由Spirng 自動完成。

Spring的事務機制

Spring的事務機制提供了一個PlatformTransactionManager介面,不同的資料訪問技術的事務使用不同的介面實現,如表所示:

資料訪問技術

實現

JDBC

DataSourceTransactionManager

JPA

JapTransactionManager

Hibernate

HibernateTransactionManager

JDO

HibernateTransactionManager

分散式事務

JtaTransactionManager

以上參考出處:https://my.oschina.net/xiaolyuh/blog/3109049

以JDBC為例,可以在程式碼配置事務管理器:

 @Bean
    public PlatformTransactionManager transactionManager() {
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
        transactionManager.setDataSource(druidDataSource());
        return transactionManager;
    }

使用@Transactional註解在方法上表明該方法需要事務支援。這是一個基於AOP的實現操作。

AOP的代理:

1.JDK動態代理實現(原理是使用反射機制)

具體有如下四步驟:

  1. 通過實現 InvocationHandler 介面建立自己的呼叫處理器;
  2. 通過為 Proxy 類指定 ClassLoader 物件和一組 interface 來建立動態代理類; ’通過反射機制獲得動態代理類的建構函式,其唯一引數型別是呼叫處理器介面型別; 通過建構函式建立動態代理類例項,構造時呼叫處理器物件作為引數被傳入。

2.CGLIB代理

1、如果目標物件實現了介面,預設情況下會採用JDK的動態代理實現AOP 2、如果目標物件實現了介面,可以強制使用CGLIB實現AOP 3、如果目標物件沒有實現了介面,必須採用CGLIB庫,spring會自動在JDK動態代理和CGLIB之間轉換

資料庫隔離級別

隔離級別

隔離級別的值

導致的問題

Read-Uncommitted

0

導致髒讀

Read-Committed

1

避免髒讀,允許不可重複讀和幻讀

Repeatable-Read

2

避免髒讀,不可重複讀,允許幻讀

Serializable

3

序列化讀,事務只能一個一個執行,避免了髒讀、不可重複讀、幻讀。執行效率慢,使用時慎重

Spring 事務的7個傳播屬性

常量名稱

常量解釋

PROPAGATION_REQUIRED

支援當前事務,如果當前沒有事務,就新建一個事務。這是最常見的選擇,也是 Spring 預設的事務的傳播。

PROPAGATION_REQUIRES_NEW

新建事務,如果當前存在事務,把當前事務掛起。新建的事務將和被掛起的事務沒有任何關係,是兩個獨立的事務,外層事務失敗回滾之後,不能回滾內層事務執行的結果,內層事務失敗丟擲異常,外層事務捕獲,也可以不處理回滾操作

PROPAGATION_SUPPORTS

支援當前事務,如果當前沒有事務,就以非事務方式執行。

PROPAGATION_MANDATORY

支援當前事務,如果當前沒有事務,就丟擲異常。

PROPAGATION_NOT_SUPPORTED

以非事務方式執行操作,如果當前存在事務,就把當前事務掛起。

PROPAGATION_NEVER

以非事務方式執行,如果當前存在事務,則丟擲異常。

PROPAGATION_NESTED

如果一個活動的事務存在,則執行在一個巢狀的事務中。如果沒有活動事務,則按REQUIRED屬性執行。它使用了一個單獨的事務,這個事務擁有多個可以回滾的儲存點。內部事務的回滾不會對外部事務造成影響。它只對DataSourceTransactionManager事務管理器起效。

注:Spring的預設事務傳播特性是PROPAGATION_REQUIRED,MySQL預設的隔離級別是Repeatable-Read

事務的巢狀例子

package com.yudianxx.springBootDemo.transation;

import com.yudianxx.springBootDemo.mapper.image.ImageCategoryMapper;
import com.yudianxx.springBootDemo.model.image.Category;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;


@Service
@Slf4j
public class TransactionTestServiceImpl implements TransactionTestService {


    @Autowired
    TransactionTestServiceImpl transactionTestService;

    @Autowired
    ImageCategoryMapper imageCategoryMapper;

    /**
     * 事務測試
     *
     * @return
     */
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void testTransactional() {
        Category category = Category.builder().name("事務測試").build();
/*//       情況 1.
        try {
            a(category);  //內部類呼叫,不走AOP,事務不起作用,加入a()報錯了,插入仍然有效,相當於普通呼叫
            b(category);
        } catch (Exception e) {
            e.printStackTrace();
        }*/

/*
//        情況2.
        transactionTestService.a(category);  //解決情況一的問題
        transactionTestService.b(category);
//        throw new RuntimeException();   //沒有try catch ,父、子同一事務,父報錯,全回滾
*/

/*//        情況3.
        try{
            transactionTestService.a(category);
            transactionTestService.b(category);
            throw new RuntimeException();  //父、子同一事務,子方法沒有拋異常,父雖然拋了異常但是被catch到,等於沒丟擲過,所以都不會回滾
        }catch (Exception e){

        }*/


/*//          情況4.
        try {
            transactionTestService.a(category);
            transactionTestService.b(category);
            throw new RuntimeException();
        } catch (Exception e) {
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); //強制回滾,假如子是REQUIRES_NEW,則子不回滾
        }*/

/*//          情況 5.
        try {
            transactionTestService.a(category);
            transactionTestService.b(category);
            throw new RuntimeException();
        } catch (Exception e) {
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); //強制回滾,假如子是REQUIRES_NEW,則子不回滾
        }*/

//          情況6.
        try {
            transactionTestService.a(category);
            transactionTestService.b(category);
//            transactionTestService.c(category);  //不回滾
//           transactionTestService.d(category); //回滾
//           transactionTestService.e(category); //不回滾 e是另外的事務
//           transactionTestService.f(category); //a、b不回滾,f回滾
            transactionTestService.g(category); //a、b、g都不回滾
        } catch (Exception e) {

        }

    }

    //    @Transactional(propagation = Propagation.REQUIRES_NEW)
// 會單獨起一個事務,成功了則插入,不受其他事務影響
    @Transactional(propagation = Propagation.REQUIRED)
    public void a(Category category) {
        log.info("進入A方法");
        category.setName("事務測試a");
        imageCategoryMapper.insert(category);
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public void b(Category category) {
        log.info("進入B方法");
        category.setName("事務測試b");
        imageCategoryMapper.insert(category);
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public void c(Category category) {
        log.info("進入c方法");
        try {
            int j = 1 / 0;
        } catch (Exception e) {
//            throw e;  //如果是把錯誤丟擲來了,上層捕獲了就會回滾事務的,如果沒有throw,這個方法自己處理了異常就不會拋,相當於沒拋異常正常執行
            //簡單的說,throw e 了相當於沒有加try catch
        }
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public void d(Category category) {
        log.info("進入d方法");
        int j = 1 / 0;
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void e(Category category) {
        log.info("進入e方法");
        int j = 1 / 0;
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void f(Category category) {
        log.info("進入f方法");
        category.setName("事務測試f");
        imageCategoryMapper.insert(category);
        int j = 1 / 0;
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void g(Category category) {
        log.info("進入g方法");
        category.setName("事務測試g");
        imageCategoryMapper.insert(category);
        try {
            int j = 1 / 0;
        } catch (Exception e) {

        }
    }
}

PROPAGATION_REQUIRED(spring 預設)

情況6中, transactionTestService.a(category) 的時候spring已經起了事務,這時呼叫 transactionTestService.b(category), transactionTestService.b(category)看到自己已經執行在 transactionTestService.a(category) 的事務內部,就不再起新的事務,加入到a的事務。

PROPAGATION_REQUIRES_NEW

情況6中, transactionTestService.e(category) 是新的事務,a和b的事務會掛起,e會新起一個事務。a、b、e回不回滾主要看是否丟擲異常。

spring 什麼情況下進行事務回滾?

Spring、EJB的宣告式事務預設情況下都是在丟擲unchecked exception後才會觸發事務的回滾 unchecked異常,即執行時異常runntimeException 回滾事務;

checked異常,即Exception可try{}捕獲的不會回滾.當然也可配置spring引數讓其回滾. 配置如下:

@Transactional( rollbackFor = Exception.class)

spring的事務邊界是在呼叫業務方法之前開始的,業務方法執行完畢之後來執行commit or rollback(Spring預設取決於是否丟擲runtime異常). 如果丟擲runtime exception 並在你的業務方法中沒有catch到的話,事務會回滾。 一般不需要在業務方法中catch異常,如果非要catch,在做完你想做的工作後(比如關閉檔案等)一定要丟擲runtime exception,否則spring會將你的操作commit,這樣就會產生髒資料.所以你的catch程式碼是畫蛇添足。

結論:

  1. 無論內外報 非RuntimeException 錯誤,都不會回滾。
  2. 如果加上rollbackFor = Exception.class,無論內外怎麼報錯,都會回滾。

參考: https://my.oschina.net/xiaolyuh/blog/3109049 https://www.cnblogs.com/pjjlt/p/10926398.html

本文參與 騰訊雲自媒體分享計劃 ,歡迎熱愛寫作的你一起參與! 本文分享自作者個人站點/部落格:https://blog.csdn.net/yudianxiaoxiao複製 如有侵權,請聯絡 [email protected] 刪除。 來源:spring事務為什麼不生效,回滾失效,事務try catch - 雲+社群 - 騰訊雲 (tencent.com)