自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

探索 Spring 事務(wù)的奧秘

開發(fā)
本文我們對Spring事務(wù)和設(shè)計(jì)理念和底層源碼實(shí)現(xiàn)并結(jié)合相應(yīng)案例對此進(jìn)行了深入的分析,希望對你有幫助。

在當(dāng)今的軟件開發(fā)領(lǐng)域,事務(wù)管理是確保數(shù)據(jù)完整性和一致性的關(guān)鍵環(huán)節(jié)。而 Spring 事務(wù),作為一個(gè)強(qiáng)大且廣泛應(yīng)用的事務(wù)管理機(jī)制,在構(gòu)建可靠、高效的應(yīng)用程序中發(fā)揮著至關(guān)重要的作用。

當(dāng)我們踏上對 Spring 事務(wù)的探索之旅,就仿佛打開了一扇通往復(fù)雜而又精妙世界的大門。在這里,我們將見證事務(wù)如何在幕后默默工作,協(xié)調(diào)各種操作,保障數(shù)據(jù)的準(zhǔn)確性和穩(wěn)定性。它如同一位無聲的守護(hù)者,精心呵護(hù)著系統(tǒng)的運(yùn)行。

無論是應(yīng)對復(fù)雜的業(yè)務(wù)邏輯還是處理大規(guī)模的數(shù)據(jù)交互,Spring 事務(wù)都展現(xiàn)出了其非凡的能力和靈活性。通過深入剖析它的原理、特性和應(yīng)用場景,我們能夠更好地理解如何充分發(fā)揮其優(yōu)勢,解決實(shí)際開發(fā)中面臨的諸多挑戰(zhàn)。讓我們一同開啟這場精彩的旅程,去揭開 Spring 事務(wù)那神秘的面紗,探尋其中蘊(yùn)含的無盡智慧與可能。

一、詳解Spring中的事務(wù)

1.什么是事務(wù)

事務(wù)在邏輯上可以認(rèn)為就是把一組操作看作一個(gè)動(dòng)作。這個(gè)動(dòng)作的內(nèi)容要么都成功,要么都失敗,這樣才能保證結(jié)果的準(zhǔn)確性、一致性。

如下代碼所示,如果下面這段代碼兩個(gè)插入操作不屬于同一個(gè)事務(wù)的話,結(jié)束時(shí)只有張三被插入和李四沒有插入,不符合業(yè)務(wù)上的準(zhǔn)確性。

使用事務(wù)進(jìn)行數(shù)據(jù)庫增刪改查操作時(shí),必須保證當(dāng)前使用的數(shù)據(jù)庫引擎支持事務(wù),以MySQL為例,MySQL默認(rèn)引擎為innodb,他就是支持事務(wù)的。若時(shí)myisam則不支持事務(wù),無法實(shí)現(xiàn)數(shù)據(jù)回滾。

public void transaction_exception_nested_nested(){
        User1 user1=new User1();
        user1.setName("張三");
        user1Service.addNested(user1);
        //報(bào)錯(cuò)
   throw new RuntimeException();
   
        User2 user2=new User2();
        user2.setName("李四");
        user2Service.addNested(user2);
       
    }

2.事務(wù)的特性ACID簡介

英文翻譯成中文大致是:原子性、隔離性、一致性、持久性。分別代表的含義是:

  • 原子性(Atomicity): 屬于同一個(gè)事務(wù)的操作要么都成功,要么都失敗,直接回滾,數(shù)據(jù)庫的數(shù)據(jù)像是沒有被動(dòng)過一樣。
  • 一致性(Consistency):在事務(wù)開始前和事務(wù)結(jié)束后,數(shù)據(jù)庫的完整性沒有被破壞,即操作符合數(shù)據(jù)庫級聯(lián)回滾、預(yù)設(shè)約束、觸發(fā)器要求。
  • 隔離性(Isolation)數(shù)據(jù)庫允許并發(fā)操作,使用準(zhǔn)確的隔離性原則才能保證數(shù)據(jù)一致性,而隔離級別有:讀未提交(Read uncommitted)、讀已提交(read committed)、可重復(fù)讀(repeatable read)、串行化(Serializable)。
  • 持久性(Durability):持久化的數(shù)據(jù)不會丟失,即使系統(tǒng)發(fā)生故障。

3.Spring支持的兩種事務(wù)管理

有兩種姿勢,分別是手動(dòng)式事務(wù)和注解式事務(wù),前者是手動(dòng)的,比較少使用,對應(yīng)的類是TransactionTemplate或者TransactionManager,使用示例如下所示:

@Autowired
private TransactionTemplate transactionTemplate;
public void testTransaction() {

        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {

                try {

                    // ....  業(yè)務(wù)代碼
                } catch (Exception e){
                    //回滾
                    transactionStatus.setRollbackOnly();
                }

            }
        });
}

或者下面這樣一段代碼,都是通過都是傳入需要進(jìn)行事務(wù)管理的bean定義,進(jìn)行手動(dòng)操作管理:

@Autowired
private PlatformTransactionManager transactionManager;

public void testTransaction() {

  TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
          try {
               // ....  業(yè)務(wù)代碼
              transactionManager.commit(status);
          } catch (Exception e) {
              transactionManager.rollback(status);
          }
}

而后者就比較常用了,基于注解(底層是通過AOP實(shí)現(xiàn)的),使用的示例代碼如下所示:

@Transactional
    public void transaction_exception_nested_nested(){
        User1 user1=new User1();
        user1.setName("張三");
        user1Service.addNested(user1);

        User2 user2=new User2();
        user2.setName("李四");
        user2Service.addNested(user2);
        throw new RuntimeException();
    }

4.Spring事務(wù)隔離級別和傳播行為有哪些(筆試常問)

先來說說事務(wù)隔離級別:

  • default(默認(rèn)):PlatfromTransactionManager默認(rèn)的隔離級別,使用數(shù)據(jù)庫默認(rèn)的事務(wù)隔離級別,除了default 其它幾個(gè)Spring事務(wù)隔離級別與JDBC事務(wù)隔離級別相對應(yīng)。
  • read_uncommited(讀未提交):一個(gè)事務(wù)可以讀取另外一個(gè)事務(wù)未提交的數(shù)據(jù),這可能出現(xiàn)臟讀 而且不可重復(fù)度,出現(xiàn)幻像讀等。
  • read_commited(讀已提交):一個(gè)事務(wù)可以讀取另一個(gè)事務(wù)已經(jīng)提交的數(shù)據(jù),不可以讀取未提交的數(shù)據(jù) 可以避免臟讀 但是無法避免不可重復(fù)讀和幻像讀。
  • repeatTable_read(可重復(fù)讀):一個(gè)事務(wù)可以讀取另外一個(gè)事務(wù)已經(jīng)提交的數(shù)據(jù),可以避免臟讀的前提下 ,也可以避免不可重復(fù)讀,但是還是無法避免幻像讀。
  • serializable(串行化):這是一個(gè)花費(fèi)較高但是比較可靠的事務(wù)隔離級別,可以避免臟讀 幻像讀和不可重復(fù)讀(事務(wù)被處理為順序執(zhí)行)

Spring事務(wù)傳播屬性:

  • required(默認(rèn)屬性):Propagation.REQUIRED內(nèi)外部屬于統(tǒng)一事務(wù),一個(gè)回滾全部回滾。(后文會有代碼演示)
  • Mandatory:如果當(dāng)前存在事務(wù),則支持當(dāng)前事務(wù),如果不存在事務(wù),則拋出異常
  • Never:以非事務(wù)方式執(zhí)行,如果當(dāng)前存在事務(wù),則拋出異常
  • Supports:如果當(dāng)前存在事務(wù),則支持當(dāng)前事務(wù),.如果不存在事務(wù),以非事務(wù)方式執(zhí)行
  • Not_Supports:以非事務(wù)方式執(zhí)行操作,如果存在事務(wù),則掛起當(dāng)前事務(wù)
  • required_new:在外圍方法開啟事務(wù)的情況下Propagation.REQUIRES_NEW修飾的內(nèi)部方法依然會單獨(dú)開啟獨(dú)立事務(wù),且與外部方法事務(wù)也獨(dú)立,內(nèi)部方法之間、內(nèi)部方法和外部方法事務(wù)均相互獨(dú)立,互不干擾。
  • Nested:嵌套,支持當(dāng)前事務(wù),內(nèi)層事務(wù)的執(zhí)行失敗不會導(dǎo)致外層事務(wù)的回滾,但是外層事務(wù)的回滾會影響內(nèi)層事務(wù)導(dǎo)致內(nèi)層事務(wù)隨外層事務(wù)一同回滾.

二、事務(wù)傳播行為進(jìn)階知識

1.前置鋪墊,代碼示例編寫

為了更好的解答后續(xù)的問題,這里我們給出了一個(gè)示例,user1表的service:

@Service
public class User1Service {

    @Resource
    private User1Mapper user1Mapper;

    @Transactional(propagation = Propagation.REQUIRED)
    public void addRequired(User1 user){
        user1Mapper.insert(user);
    }


    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void addRequiresNew(User1 user){
        user1Mapper.insert(user);
    }



    @Transactional(propagation = Propagation.NESTED)
    public void addNested(User1 user){
        user1Mapper.insert(user);
    }



}

user2表的service,可以看到對于數(shù)據(jù)庫的操作都在注解上標(biāo)出不同的傳播行為:

@Service
public class User2Service {

    @Resource
    private User2Mapper user2Mapper;

    @Transactional(propagation = Propagation.REQUIRED)
    public void addRequired(User2 user){
        user2Mapper.insert(user);
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public void addRequiredException(User2 user){
        user2Mapper.insert(user);
        throw new RuntimeException();
    }


    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void addRequiresNew(User2 user){
        user2Mapper.insert(user);
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void addRequiresNewException(User2 user){
        user2Mapper.insert(user);
        throw new RuntimeException();
    }


    @Transactional(propagation = Propagation.NESTED)
    public void addNested(User2 user){
        user2Mapper.insert(user);
    }

    @Transactional(propagation = Propagation.NESTED)
    public void addNestedException(User2 user){
        user2Mapper.insert(user);
        throw new RuntimeException();
    }
}

2. TransactionDefinition.PROPAGATION_REQUIRED

它是Spring的默認(rèn)傳播行為,說白了發(fā)生嵌套在內(nèi)部的事務(wù)會和外部的事務(wù)融合,所以外部事務(wù)報(bào)錯(cuò)了內(nèi)部事務(wù)也會回滾。

如下面這段代碼,外部的方法沒有加事務(wù),且user1Service、user2Service的方法都是PROPAGATION_REQUIRED這個(gè)傳播級別,所以外部報(bào)錯(cuò)不影響兩者的內(nèi)部提交

/**
     * 彼此都有獨(dú)立的事務(wù),外部沒有開事務(wù),所以兩者數(shù)據(jù)都會入庫
     */
    @GetMapping("/test/add1")
    public void notransaction_exception_required_required() {
        User1 user1 = new User1();
        user1.setName("張三");
        user1Service.addRequired(user1);

        User2 user2 = new User2();
        user2.setName("李四");
        user2Service.addRequired(user2);

        throw new RuntimeException();
    }

然后我們再來看看這樣一段代碼,外部沒有加事務(wù),所以內(nèi)部兩個(gè)事務(wù)彼此獨(dú)立??梢钥吹絬ser2Service報(bào)錯(cuò),所以只有user1Service插入成功:

 @GetMapping("/test/add2")
    public void notransaction_required_required_exception() {
     //插入成功
        User1 user1 = new User1();
        user1.setName("張三");
        user1Service.addRequired(user1);

  //事務(wù)是獨(dú)立的插入失敗
        User2 user2 = new User2();
        user2.setName("李四");
        user2Service.addRequiredException(user2);

        throw new RuntimeException();
    }

最后在看看這個(gè),外部加了事務(wù),也是REQUIRED,所以內(nèi)部兩個(gè)事務(wù)與其融合成為一個(gè)事務(wù),當(dāng)外部方法報(bào)錯(cuò),兩者插入操作都失敗,數(shù)據(jù)直接回滾:

/**
     * 外部開啟事務(wù),報(bào)錯(cuò)均回滾
     */
    @GetMapping("/test/add3")
    @Transactional(propagation = Propagation.REQUIRED)
    public void transaction_exception_required_required() {
        User1 user1 = new User1();
        user1.setName("張三");
        user1Service.addRequired(user1);

        User2 user2 = new User2();
        user2.setName("李四");
        user2Service.addRequired(user2);


        throw new RuntimeException();
    }

再來看看一個(gè)比較好玩的,內(nèi)外部都有事務(wù),第2個(gè)內(nèi)部事務(wù)報(bào)錯(cuò),由于三者事務(wù)融為一體,所以user2Service的錯(cuò)誤被外部transaction_required_required_exception方法感知,user1Service插入也是失敗的,所以這個(gè)方法兩張表都沒有插入數(shù)據(jù)

 @GetMapping("/test/add4")
    @Transactional(propagation = Propagation.REQUIRED)
    public void transaction_required_required_exception() {
        User1 user1 = new User1();
        user1.setName("張三");
        user1Service.addRequired(user1);

        User2 user2 = new User2();
        user2.setName("李四");
        //錯(cuò)誤被外部感知,所以所有user1的插入也被回滾了
        user2Service.addRequiredException(user2);
    }

這個(gè)也比較特殊,由于三個(gè)事務(wù)合為一體,所以即使user2Service報(bào)錯(cuò)不被感知,兩張表的數(shù)據(jù)也還是沒有插入:

@GetMapping("/test/add5")
    @Transactional
    public void transaction_required_required_exception_try() {
        User1 user1 = new User1();
        user1.setName("張三");
        user1Service.addRequired(user1);

        User2 user2 = new User2();
        user2.setName("李四");
        try {
        //雖然異常被捕獲,但是三個(gè)內(nèi)外部事務(wù)融合了,一個(gè)報(bào)錯(cuò)就全部插入回滾
            user2Service.addRequiredException(user2);
        } catch (Exception e) {
            System.out.println("方法回滾");
        }
    }

總結(jié):Propagation.REQUIRED內(nèi)外部屬于統(tǒng)一事務(wù),一個(gè)回滾全部回滾,無視try塊代碼的捕獲。

3. TransactionDefinition.PROPAGATION_REQUIRES_NEW

我們還是通過看代碼的方式來講述吧:第一個(gè)例子,外部沒有加事務(wù),兩個(gè)service彼此事務(wù)獨(dú)立,外部報(bào)錯(cuò),但是兩者事務(wù)都已提交,所以都插入了:

 @GetMapping("/test/add6")
    public void notransaction_exception_requiresNew_requiresNew() {
        User1 user1 = new User1();
        user1.setName("張三");
        user1Service.addRequiresNew(user1);

        User2 user2 = new User2();
        user2.setName("李四");
        user2Service.addRequiresNew(user2);
        throw new RuntimeException();

    }

外部還是沒有開啟事務(wù),user2Service報(bào)錯(cuò)事務(wù)回滾,所以只有user1Service插入了。

@GetMapping("/test/add7")
    public void notransaction_requiresNew_requiresNew_exception() {
        User1 user1 = new User1();
        user1.setName("張三");
        //正常插入
        user1Service.addRequiresNew(user1);

        User2 user2 = new User2();
        user2.setName("李四");
        //保存回滾了 
        user2Service.addRequiresNewException(user2);
    }

來看一個(gè)綜合的,外部加了REQUIRED,所以內(nèi)部第一個(gè)事務(wù)和外部融合,后兩個(gè)事務(wù)獨(dú)立,在外部報(bào)錯(cuò)的情況下只有addRequired回滾。李四、王五均被插入:

@GetMapping("/test/add8")
    @Transactional(propagation = Propagation.REQUIRED)
    public void transaction_exception_required_requiresNew_requiresNew() {
        User1 user1 = new User1();
        user1.setName("張三");
        //和外部事務(wù)融合,外部報(bào)錯(cuò)插入被回滾
        user1Service.addRequired(user1);

        User2 user2 = new User2();
        user2.setName("李四");
        // 事務(wù)獨(dú)立,不受外部影響,正常插入
        user2Service.addRequiresNew(user2);

        User2 user3 = new User2();
        user3.setName("王五");
        // 事務(wù)獨(dú)立,不受外部影響,正常插入
        user2Service.addRequiresNew(user3);
        throw new RuntimeException();
    }

外部加了事務(wù),由于王五報(bào)錯(cuò)被外部感知,張三的事務(wù)和外部融合,所以張三沒有被插入,這題只有李四被插入了:

 @GetMapping("/test/add9")
    @Transactional(propagation = Propagation.REQUIRED)
    public void transaction_required_requiresNew_requiresNew_exception() {
        User1 user1 = new User1();
        user1.setName("張三");
        //和外部融合
        user1Service.addRequired(user1);

        User2 user2 = new User2();
        user2.setName("李四");
        //事務(wù)獨(dú)立,正常插入
        user2Service.addRequiresNew(user2);

        User2 user3 = new User2();
        user3.setName("王五");
        //報(bào)錯(cuò),插入被回滾,外部感知到了錯(cuò)誤,所以張三的插入也被回滾了
        user2Service.addRequiresNewException(user3);
    }

王五報(bào)錯(cuò)回滾,但是錯(cuò)誤沒有被外部感知到,張三和外部事務(wù)融合,正常插入、李四正常插入。

@GetMapping("/test/add10")
    @Transactional(propagation = Propagation.REQUIRED)
    public void transaction_required_requiresNew_requiresNew_exception_try() {
        User1 user1 = new User1();
        user1.setName("張三");
        //正常插入
        user1Service.addRequired(user1);

        User2 user2 = new User2();
        user2.setName("李四");
        //和外部事務(wù)彼此獨(dú)立,正常插入
        user2Service.addRequiresNew(user2);
        User2 user3 = new User2();
        user3.setName("王五");
        try {
        // 報(bào)錯(cuò)回滾,但錯(cuò)誤并沒有被外部感知,所以只有這個(gè)事務(wù)被回滾
            user2Service.addRequiresNewException(user3);
        } catch (Exception e) {
            System.out.println("回滾");
        }
    }

總結(jié): 在外圍方法開啟事務(wù)的情況下Propagation.REQUIRES_NEW修飾的內(nèi)部方法依然會單獨(dú)開啟獨(dú)立事務(wù),且與外部方法事務(wù)也獨(dú)立,內(nèi)部方法之間、內(nèi)部方法和外部方法事務(wù)均相互獨(dú)立,互不干擾。

4. TransactionDefinition.PROPAGATION_NESTED

代碼如下,外部沒有事務(wù),張三、李四彼此獨(dú)立一個(gè)事務(wù),數(shù)據(jù)均插入,外部異常不影響成功提交:

 @GetMapping("/test/add11")
    public void notransaction_exception_nested_nested() {
        User1 user1 = new User1();
        user1.setName("張三");
        user1Service.addNested(user1);

        User2 user2 = new User2();
        user2.setName("李四");
        user2Service.addNested(user2);
        throw new RuntimeException();
    }

同理,外部沒有事務(wù),后者報(bào)錯(cuò)不影響前者正常插入:

 @GetMapping("/test/add12")
    public void notransaction_nested_nested_exception() {
        User1 user1 = new User1();
        user1.setName("張三");
        //獨(dú)立的事務(wù),不受下方報(bào)錯(cuò)影響
        user1Service.addNested(user1);

        User2 user2 = new User2();
        user2.setName("李四");
        //外部沒有事務(wù),報(bào)錯(cuò)回滾
        user2Service.addNestedException(user2);
    }

外部開啟事務(wù)(默認(rèn)級別),內(nèi)部事務(wù)與其融合,一錯(cuò)全部回滾:

 @GetMapping("/test/add13")
    @Transactional
    public void transaction_exception_nested_nested(){
    //外部開啟事務(wù),所有nest的事務(wù)都與外部事務(wù)融合,一個(gè)報(bào)錯(cuò)全部回滾
        User1 user1=new User1();
        user1.setName("張三");
        user1Service.addNested(user1);

        User2 user2=new User2();
        user2.setName("李四");
        user2Service.addNested(user2);
        throw new RuntimeException();
    }

同上,外部開啟事務(wù)后內(nèi)部事務(wù)與外部融合,異常能被感知后回滾了:

@GetMapping("/test/add14")
    @Transactional
    public void transaction_nested_nested_exception(){
        User1 user1=new User1();
        user1.setName("張三");
        user1Service.addNested(user1);

        User2 user2=new User2();
        user2.setName("李四");
        user2Service.addNestedException(user2);
    }

異常捕獲李四的報(bào)錯(cuò)自己消化了,外部不回滾,這就是transaction_nested和REQUIRED的區(qū)別:

 @GetMapping("/test/add15")
    @Transactional
    public void transaction_nested_nested_exception_try(){
        User1 user1=new User1();
        user1.setName("張三");
        user1Service.addNested(user1);

        User2 user2=new User2();
        user2.setName("李四");
        try {
            user2Service.addNestedException(user2);
        } catch (Exception e) {
            System.out.println("方法回滾");
        }
    }

總結(jié):如果當(dāng)前存在事務(wù),則在嵌套事務(wù)內(nèi)執(zhí)行。如果當(dāng)前沒有事務(wù),則進(jìn)行與PROPAGATION_REQUIRED類似的操作。

5.事務(wù)的幾個(gè)回滾規(guī)則

這個(gè)我們從源碼的注釋中就能看出端倪了,如下所示,注釋中已經(jīng)說明了只有運(yùn)行時(shí)異?;蛘逧rror可以觸發(fā)回滾,對于檢查型異常是不會回滾。

/**

  * <p>By default, a transaction will be rolling back on {@link RuntimeException}
  * and {@link Error} but not on checked exceptions (business exceptions). See
  * org.springframework.transaction.interceptor.DefaultTransactionAttribute#rollbackOn(Throwable)
  */
 Class<? extends Throwable>[] rollbackFor() default {};

那我如果要自定義一個(gè)異常使用怎么辦?如下所示即可,在注解上聲明我們需要回滾的錯(cuò)誤類型即可。

@Transactional(rollbackFor= MyException.class)

6.詳解@Transactional 注解

@Transactional 作用在不同的地方會有不同的效果,我們最常見的用法就是作用于方法上,如果在方法上加該注解,就會將當(dāng)前方法中的數(shù)據(jù)庫操作加入事務(wù)中。注意方法必須是public否則事務(wù)不會生效。 如果作用于類上,則意味著這個(gè)類中所有public的方法都會用到事務(wù)。接口同理,但我們不建議這么用。

它的常見參數(shù)配置如下:

  • 傳播屬性(propagation):事務(wù)的傳播行為,默認(rèn)值為REQUIRED,可選的值在上面介紹過
  • 隔離級別(isolation):事務(wù)的隔離級別,默認(rèn)值采用DEFAULT,可選的值在上面介紹過
  • 回滾規(guī)則(rollbackFor):用于指定能夠觸發(fā)事務(wù)回滾的異常類型,并且可以指定多個(gè)異常類型。
  • 只讀屬性(readOnly):指定事務(wù)是否為只讀事務(wù),默認(rèn)值為 false。
  • 超時(shí)時(shí)間(timeout):事務(wù)的超時(shí)時(shí)間,默認(rèn)值為-1(不會超時(shí))。如果超過該時(shí)間限制但事務(wù)還沒有完成,則自動(dòng)回滾事務(wù)。

7.Spring AOP自調(diào)用問題

這個(gè)問題,我們不妨舉個(gè)例子來說吧,首先我們看看下面這段代碼,很明顯如果我們直接調(diào)用add17報(bào)錯(cuò)了事務(wù)會回滾,原因很簡單,這個(gè)method1加了注解,所以如果我們通過api等工具調(diào)用method1時(shí),真正執(zhí)行這段代碼的對象是結(jié)果Spring容器bpp處理過的cglib代理類:

 @Transactional
    @GetMapping("/test/add17")
    public void method1(){
        User2 user2 = new User2();
        user2.setName("李四");
        user2Service.insert(user2);
        System.out.println(1/0);
    }

好了,有了上文的鋪墊,我們再來說說嵌套調(diào)用失效問題,代碼如下所示,當(dāng)我們使用接口調(diào)用工具調(diào)用時(shí),發(fā)現(xiàn)method1執(zhí)行出錯(cuò),李四還是被成功插入了,這是為什么呢? 原因很簡單method1執(zhí)行者并不是cglib代理對象,下面這段method1,完整的代碼應(yīng)該是this.method1,

 @GetMapping("/test/add16")
    public void add16() {
        method1();

    }

    @Transactional
    @GetMapping("/test/add17")
    public void method1(){
        User2 user2 = new User2();
        user2.setName("李四");
        user2Service.insert(user2);
        System.out.println(1/0);
    }

這就導(dǎo)致執(zhí)行調(diào)用method1調(diào)用者是this,而不是cglib代理的增強(qiáng)類,如下圖所示,正是因?yàn)檎{(diào)用者不是代理,導(dǎo)致代理根本不知道m(xù)ethod1被調(diào)用了,所以事務(wù)就失效了

如何解決spring自調(diào)用問題呢?

最干脆就是調(diào)用時(shí)避免嵌套使用就好了,但是有時(shí)候應(yīng)該這個(gè)方法要依賴外部的處理邏輯,而外部方法又臭又長改造兩量很大導(dǎo)致無法重構(gòu)。這時(shí)候我們只能想別的辦法。我以前解決的辦法就比較干脆了,既然問題的根源是調(diào)用對象錯(cuò)誤,那我就干脆找出這個(gè)對象來調(diào)用不就解決了?

所以我們的思路是這樣的,如下代碼所示:

首先的controller中假如應(yīng)用上下文:

  @Autowired
    private ApplicationContext applicationContext;

用這個(gè)上下文去容器中把他撈出來調(diào)用method1,問題解決

@GetMapping("/test/add16")
    public void add16() {
        TestController t = (TestController) applicationContext.getBean("testController");
        t.method1();


    }

    @Transactional
    @GetMapping("/test/add17")
    public void method1() {
        User2 user2 = new User2();
        user2.setName("李四");
        user2Service.insert(user2);
        System.out.println(1 / 0);
    }

當(dāng)然這里還有一種方法,將代理的service類注入,因?yàn)閟pring注入的類都是經(jīng)過cglib增強(qiáng)的類,所以使用注入的bean也能解決問題,只不過寫法很丑陋而已。

@Autowired
private        TestController t;

@GetMapping("/test/add16")
    public void add16() {

        t.method1();


    }

    @Transactional
    @GetMapping("/test/add17")
    public void method1() {
        User2 user2 = new User2();
        user2.setName("李四");
        user2Service.insert(user2);
        System.out.println(1 / 0);
    }

8.事務(wù)場景注意事項(xiàng)

整體大概有以下幾點(diǎn):

  • 正確的設(shè)置@Transactional 的rollbackFor 和propagation 屬性,否則事務(wù)可能會回滾失敗;
  • 避免同一個(gè)類中調(diào)用@Transactional 注解的方法,這樣會導(dǎo)致事務(wù)失效
  • @Transactional 注解的方法所在的類必須被Spring 管理,否則不生效;
  • @Transactional 注解只有作用到public 方法上事務(wù)才生效;
  • 底層使用的數(shù)據(jù)庫必須支持事務(wù)機(jī)制,否則不生效;

小結(jié)

自此我們Spring事務(wù)和設(shè)計(jì)理念和底層源碼實(shí)現(xiàn)并結(jié)合相應(yīng)案例對此進(jìn)行了深入的分析,希望對你有幫助。

責(zé)任編輯:趙寧寧 來源: 寫代碼的SharkChili
相關(guān)推薦

2025-01-22 16:00:00

MySQL數(shù)據(jù)庫Binlog

2023-09-28 09:03:56

開源搜索分析引擎

2024-02-22 10:36:13

SELECT 語句PostgreSQL數(shù)據(jù)查詢

2024-09-09 16:30:08

Python編程

2024-06-04 15:56:48

Task?.NET異步編程

2013-07-30 12:29:19

Google App Google技術(shù)Engine

2011-04-15 17:43:15

Google App Google

2021-08-09 17:13:39

數(shù)值運(yùn)算

2011-09-16 14:34:20

2009-07-17 14:03:34

ibatis DAO事務(wù)管理

2009-10-21 17:36:36

VB基礎(chǔ)教程

2023-10-30 07:36:19

Spring事務(wù)傳播機(jī)制

2020-08-19 09:45:29

Spring數(shù)據(jù)庫代碼

2023-12-08 07:52:51

Spring項(xiàng)目開發(fā)

2009-06-22 09:01:57

Spring聲明式事務(wù)

2022-04-26 21:49:55

Spring事務(wù)數(shù)據(jù)庫

2009-06-19 18:26:38

Spring事務(wù)配置

2011-02-28 13:51:30

Spring事物配置
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號