1. 程式人生 > >Spring中事務內部呼叫引發的慘案

Spring中事務內部呼叫引發的慘案

在一個類內部有2個方法foo和bar,其中bar方法配有註解(@Transactional),即bar是事務執行的,而foo不是事務執行,當foo方法內部呼叫bar方法後,bar方法的事務是不生效的。示例程式碼如下:

public class ServiceTest {

    public void foo(){
        
        this.bar();//呼叫自身的方法;
    }
    
    
    @Transactional
    public void bar(){
        System.out.println("this is bar");
        //資料庫操作
} }

原因如下:

    Spring中通過註解來完成事務的功能,實際是通過SpringAOP來實現的,而SpringAOP中,使用this來呼叫自身的方法時,此物件引用上的方法直接會被呼叫,不會呼叫代理的方法(SpringAOP原理是產生代理類)。因此bar方法的事務不會生效。如果直接呼叫bar方法,此時事務是生效的。

    解決方法有:

    一、將bar方法放在另一個service類中。這種方法簡單,但是造成程式碼的冗餘。

    二、可以將註解@Transactional放在foo方法上。這種方法造成的影響:加入foo方法的一些操作是不需要事務的,這會延長事務執行的時間。

    三、在foo方法中不要直接使用this來呼叫bar方法,通過呼叫代理類的bar方法。

public void foo(){
        
    if(null != AopContext.currentProxy()){  
            ((ServiceTest)AopContext.currentProxy()).bar();  
        }else{  
        	bar();  
        }      
    }

    我們顯示的呼叫了AopContext來獲取當前代理物件,然後呼叫其方法,這樣做還必須的一個步驟是將當前的代理暴露給執行緒使用,在配置檔案中需要配置一個引數:

<property name="exposeProxy">  
            <value>true</value>  
        </property>

或者在application-context.xml檔案中新增配置:

<aop:aspectj-autoproxy proxy-target-class="true" expose-proxy="true"/>

它是ProxyConfig的一個引數,預設是false,如果不設定這個引數,那麼上述java程式碼將無法獲取當前執行緒中的代理物件。

這種方法可以成功觸發攔截,但是也帶來了其他問題,比如程式碼的織入,我們的程式碼將變得複雜而且晦澀,而且嚴格要求系統針對於當前的bean必須配置攔截器,否則會因為找不到攔截器而丟擲異常。