spring事務管理的一些注意點

lieyikong發表於2016-11-17

總結一些自己最近在使用spring事務管理時碰到的一些注意點

 

一、關於目標物件內部方法自我呼叫時的一些情形和存在的問題

1、情形1:只給b方法上加事務註解,a方法上不加

目標類的介面和實現程式碼示例:

public interface AService {

    public void a();

    public void b();

}

 

@Service()

public class AServiceImpl implements AService{

    public void a() {

        this.b();

    }

    @Transactional(rollbackFor={Exception.class})

       public void b() {

         insert();

         update();

    }

}

 

 

只要給目標類AServiceImpl的某個方法加上註解@Transactional,spring就會為目標類生成對應的代理類,以後呼叫AServiceImpl中的所有方法都會先走代理類(即使呼叫未加事務註解的方法a,也會走代理類),即在通過getBean(“AServiceImpl”)獲得的業務類時,實際上得到的是一個代理類,假設這個類叫做AServiceImplProxy ,spring為AServiceImpl生成的代理類類似於如下程式碼:

public class AServiceImplProxy implements AService{

    public void a() {

      //反射呼叫目標類的a方法

    }

    public void b() {

          //啟動事務的程式碼

 

      //反射呼叫目標類的b方法

 

     //事務提交的程式碼

    }

}

 

 

 

由於目標類中只有b方法加入了事務管理,所以代理類中只為b方法加入了橫切事務邏輯,spring事務管理的本質是通過aop為目標類生成動態代理類,並在需要進行事務管理的方法中加入事務管理的橫切邏輯程式碼(如AServiceImplProxy中的b方法所示)。

呼叫getBean(“AServiceImpl”).a()時,實際上執行的是AServiceImplProxy.a(),代理類的a方法會通過反射呼叫目標類的a方法, 再在目標類的a方法中呼叫b方法,故最終a中呼叫的b方法是來自於AServiceImpl中的b方法,AServiceImpl的b方法並沒有橫切事務邏輯程式碼(切記:事務邏輯程式碼在代理類中,@Transactional只是標記此方法在代理類中要加入事務邏輯程式碼)。所以呼叫a方法時,b方法的事務會失效。

 

其實,在proxy物件與目標物件之間還有一個InvocationHandler物件(以jdk動態代理為例),真正的橫切邏輯是放到InvocationHandler物件中的,呼叫邏輯分離到InvocationHandler中主要是為了構造出具有通用性和簡單性的代理類,此處為了簡化處理過程,統一放到代理物件中來說明,動態代理簡化的呼叫關係圖如下:

aop中存在方法巢狀呼叫時,相應的呼叫過程式列圖如下:

 

2、情形2:給a方法加事務註解,b方法上加或不加

對1中的程式碼做修改,為a方法也加上事務註解:

 

@Service()

public class AServiceImpl implements AService{

   @Transactional(rollbackFor={Exception.class})

     public void a() {

        this.b();

    }

  @Transactional(rollbackFor={Exception.class})

    public void b() {

         insert();

         update();

    }

}

 

此時生成的代理類類似如下程式碼:

 

public class AServiceImplProxy implements AService{

    public void a() {

     //啟動事務的程式碼

 

         //反射呼叫目標類的a方法

 

         //事務提交的程式碼

    }

    public void b() {

          //啟動事務的程式碼

 

     //反射呼叫目標類的b方法

 

     //事務提交的程式碼

      }

}

 

即為a和b都加入了事務橫切邏輯。在這種情況下,呼叫順序還和1中情形類似,區別在於在反射呼叫目標物件的a方法前,會對a方法開啟事務管理,雖然呼叫的b方法還是目標物件中沒有加事務邏輯的程式碼,spring卻會把b合併到a的事務中去,此時相當於只有一個事務。

如果再將目標類程式碼改為:

 

@Service()

public class AServiceImpl implements AService{

    @Transactional(rollbackFor={Exception.class})

       public void a() {

       this.b();

    }

 

      public void b() {

       insert();

       update();

    }

}

 

即只在a上加事務控制,由於b會合併到a的事務中,所以b中的邏輯也可以被事務管理。

 

由於a和b都合併到了a的事務中,所以這種情形下事務傳遞規則不適用。代理類中加了事務邏輯的b方法永遠不會被呼叫。

 

那麼問題來了,如果我想讓b也執行自己的事務邏輯,即呼叫b時執行代理類中b方法的事務邏輯,該怎麼辦?

 

修改目標類中的a方法:

 

@Transactional(rollbackFor={Exception.class})

public void a() {

    ((AService) AopContext.currentProxy()).b();

    //即呼叫AOP代理物件的b方法即可執行事務切面進行事務增強

}

 

這時,就會強制要求呼叫代理類中的b方法,從而開啟b上的事務,此時b事務上標註的事務傳遞規則也就可以生效了,詳情參見:http://jinnianshilongnian.iteye.com/blog/1487235

個人覺得這種方法不太好,會汙染業務邏輯程式碼,使程式碼變複雜。

 

還有一種辦法就是介面下沉,把b方法分離到另一個介面中,從根源上避免目標物件內部方法自我呼叫。

 

二、try catch的問題

有時需要在業務邏輯程式碼中顯式try catch包裹事務程式碼,以便在出現異常時進行一些別的處理。

目標類的介面和實現示例程式碼如下:

 

public interface AService {

      public void a();

}

 

@Service()

public class AServiceImpl implements AService{

    @Transactional(rollbackFor={Exception.class})

    public void a() {

        try{

                 insert();

             update();

             }catch(Exception e){

             }

    }

}

 

自己在程式碼中顯式捕獲異常會導致spring事務回滾失效,原因:spring事務是通過aop捕獲到異常後再執行回滾,如果業務程式碼中顯式捕獲了異常,會導致spring捕獲不到,回滾自然失敗。

有如下幾種解決辦法:

(1)業務程式碼catch住異常後重新丟擲,如:

 

public void a() throws Exception{

        try{

                   insert();

               update();

            }catch(Exception e){

                   throw new Exception(e);

            }

    }

 

不足是本方法的呼叫端也必須顯式捕獲異常。

(2)使用程式設計式事務顯式回滾:

 

public void a() {

        try{

                   insert();

                   update();

             }catch(Exception e){

                    //顯式回滾

                   TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();

             }

    }

 

不足是事務控制程式碼會侵入業務程式碼,也正是因為程式設計式事務管理會侵入業務邏輯程式碼,所以才有了申明式事務管理。

(3)介面下沉,將需要事務控制的程式碼分到另一個介面方法中,如:

 

public interface BService {

    public void a();

    public void b();

}

 

@Service()

public class BServiceImpl implements BService{

    @Transactional(rollbackFor={Exception.class})

    public void b() {

         insert();

        update();

    }

}

 

相應的呼叫端a方法中變為:

public void a() throws Exception{

        try{

                      bService.b();

             }catch(Exception e){

                      throw new Exception(e);

       }

    }

 

 


相關文章