5分鐘探究Spring事務失效原因

廣州蘆葦科技Java開發團隊發表於2019-02-16

前言

Spring的事務管理,大家在專案中幾乎都會使用上,但是我們是否正確使用了嗎?原理是否真的知道呢?本文將會結合業務場景快速講解Spring事務失效的原理

1 業務場景

如果有這樣的業務,A類中的save方法需要呼叫本類的save2方法,不管save2中的方法執行成功與否,都不能影響save方法的執行,因此,我們會想到把save2的事務傳播行為設定成 REQUIRES_NEW,程式碼如下:

@Service
@Slf4j
public class UserService {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Autowired
    private UserService2 userService2;

    @Transactional
    public void save() {
        jdbcTemplate.execute("INSERT INTO user (id, name) VALUES\n" +
                "(5, 'Jack5')");
        try {
            save2();
        } catch (Exception e) {
            System.err.println("出錯啦");
        }

    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void save2() {
        jdbcTemplate.execute("INSERT INTO user (id, name) VALUES\n" +
                "(6, 'Jack6')");
        int i = 1 / 0;
    }
}

複製程式碼

由於,save2方法不能影響save方法的執行,所以必須補抓 save2方法。 預期結果應該是 save方法正常插入資料,save2方法插入資料失敗

執行結果:

結果1
結果2
是的,你並沒有看錯,save2方法竟然插入成功!如果知道原因,可以不用繼續看下文了~

2 探究

2.1 Spring的傳播行為

再貼一下Spring的傳播行為 public enum Propagation {
REQUIRED(0), SUPPORTS(1), MANDATORY(2), REQUIRES_NEW(3), NOT_SUPPORTED(4), NEVER(5), NESTED(6); } REQUIRED :如果當前存在事務,則加入該事務;如果當前沒有事務,則建立一個新的事務。 SUPPORTS :如果當前存在事務,則加入該事務;如果當前沒有事務,則以非事務的方式繼續執行。 MANDATORY :如果當前存在事務,則加入該事務;如果當前沒有事務,則丟擲異常。 REQUIRES_NEW :建立一個新的事務,如果當前存在事務,則把當前事務掛起。 NOT_SUPPORTED :以非事務方式執行,如果當前存在事務,則把當前事務掛起。 NEVER :以非事務方式執行,如果當前存在事務,則丟擲異常。 NESTED :如果當前存在事務,則建立一個事務作為當前事務的巢狀事務來執行;如果當前沒有事務,則該取值等價於 REQUIRED 。

可以肯定的是 ,該業務確實是使用 REQUIRES_NEW 。但是為什麼失效呢?

2.2 動態代理

在繼續探究前,先簡單帶過一下動態代理。 代理模式主要功能是為了增強一個類中的方法誕生的一種設計模式。 而代理模式分為動態代理和靜態代理,動態代理的代理類是在執行時生成的,而靜態代理是在編譯時生成的。動態代理可以分為基於介面的JDK動態代理和基於類的Cglib動態代理。

下面講解一下基於JDK的動態代理: 在java的java.lang.reflect包下提供了一個Proxy類和一個InvocationHandler介面,通過這個類和這個介面可以生成JDK動態代理類和動態代理物件。

public interface Person {
    void work();
}

public class Student implements Person {
    @Override
    public void work() {
        System.out.println("讀書");
    }
}

public class MyInvocationHandler implements InvocationHandler {
    //增強的目標類
    private Person person;

    public MyInvocationHandler(Person person) {
        this.person = person;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("先吃飯-----再看書");
        method.invoke(person, args);
        return null;
    }
}

public class Main {
    public static void main(String[] args) {
        Person person = new Student();
        MyInvocationHandler myInvocationHandler = new MyInvocationHandler(person);
        System.out.println(Arrays.toString(Student.class.getInterfaces()));
        Person proPerson = (Person) Proxy.newProxyInstance(Student.class.getClassLoader(), Student.class.getInterfaces(), myInvocationHandler);
        proPerson.work();
    }
}
複製程式碼

結果為: 先吃飯-----再看書 讀書

詳細程式碼可以在github下載, github.com/229319258/s…

2.3 動態代理的坑

至此,我們可以知道,Spring事務是基於動態代理實現的。那麼,Spring事務失效的真正原因和動態代理有什麼關聯呢?

模擬Spring事務失效的問題,把上文的程式碼稍微修改一下,

public class Student implements Person {
    @Override
    public void work() {
        System.out.println("讀書");
        try {
            this.work2();
        } catch (Exception e) {

        }
    }
    public void work2() {
        System.out.println("不想讀啊");
        int i = 1 / 0;
    }
}

複製程式碼

大家,可以把重心放在try的程式碼塊上,我們可以發現,實際上呼叫work2方法的是Student例項,並不是所謂的work2的增強類。 同理,上文中Spring事務失效的save2方法,呼叫的例項並不是代理類,而是未增強的普通物件UserService。

因此,沒有使用Proxy生成的方法,Spring事務當然會失效~

那麼,問題又來了。如果我確實想要讓save2的事務生效,應該怎麼處理呢? 有兩種方法

  • 把save2重新放在另一個類上
  • 使用方法 AopContext.currentProxy() 獲取當前代理物件
    @Transactional
    public void save() {
        jdbcTemplate.execute("INSERT INTO user (id, name) VALUES\n" +
                "(5, 'Jack5')");
        try {
            UserService proxy = (UserService) AopContext.currentProxy();
            proxy.save2();
        } catch (Exception e) {
            System.err.println("出錯啦");
        }

    }
複製程式碼

3 結論

1.我們在使用Spring事務的時候,不能直接在一個定義 @Transactional呼叫同一個類的 @Transactional(propagation = Propagation.REQUIRES_NEW)

2.除了這種情況失效外,我們也不能直接在一個未設定 @Transactional的方法,呼叫同一個類中呼叫@Transactional的方法,因為,實際上呼叫的並不是 proxy類的方法,而是本身的方法。 如:


    //    @Transactional
    public void save() {
        save2();
    }

    @Transactional()
    public void save2() {
        jdbcTemplate.execute("INSERT INTO user (id, name) VALUES\n" +
                "(7, 'Jack7')");
        int i = 1 / 0;
    }
複製程式碼

檢視資料庫的資料,同樣save2的資料並不會回滾,因為並不是呼叫代理類,而是呼叫普通的this(UserService)的方法。因此,事務同樣失效。

是不是有一種想要馬上看一下自己寫的程式碼,有沒有以上的問題。-.-

程式碼地址

程式碼地址

參考文獻

java動態代理實現與原理詳細分析 Spring 事務失效那點事

房清

廣州蘆葦科技Java開發團隊

蘆葦科技-廣州專業網際網路軟體服務公司

抓住每一處細節 ,創造每一個美好

關注我們的公眾號,瞭解更多

想和我們一起奮鬥嗎?lagou搜尋“ 蘆葦科技 ”或者投放簡歷到 server@talkmoney.cn 加入我們吧

5分鐘探究Spring事務失效原因

關注我們,你的評論和點贊對我們最大的支援

相關文章