聊一聊使用Spring事物時不生效的場景

2023-09-05 12:01:17

前言

今天介紹一下Spring事物不生效的場景,事物是我們在專案中經常使用的,如果是Java的話,基本上都使用Spring的事物,不過Spring的事物如果使用不當,那麼就會導致事物失效或者不回滾,最終導致資料不一致,所以很有必要去研究一下Spring事物不生效的一些場景,避免掉坑。

下面我們意義列舉不生效的場景,並給出解決方法。

一.類沒被Spring管理

如果我們的類沒有被Spring管理,那麼即使使用了Spring事物也不會生效,要讓Spring管理我們的類,需要標註@Component,@Service等註解。

二.沒有標註@Transactional註解的方法呼叫了標註@Transactional註解的方法

如果一個方法沒有使用@Transactional註解,但是它去呼叫了帶@Transactional註解的方法,那麼當前方法的事物不生效。

    public void saveUser(User user) throws Exception {
        save(user);
    }

    @Transactional(rollbackFor = Exception.class)
    public void save(User user) {
        try {
            userDao.save(user);
            exceptionMethod();
            roleService.save(user.getRole());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

如上所示,saveUser方法呼叫了save方法,但是saveUser沒有標@Transactional註解,而它呼叫了save方法,save方法標了@Transactional,不過事物不會生效,這是因為方法沒被代理,直接是普通方法呼叫,所以事物自然不生效。

如果一個方法中呼叫了其他方法,需要在主方法上加@Transactional註解這個方法才能被代理,如下程式碼,當然,遠端呼叫@Transactional就不生效了,就需要分散式事物了。

    @Transactional(rollbackFor = Exception.class)
    public void saveAnnotation(User user) throws Exception {
        save(user);
    }

    public void save(User user) {
        try {
            userDao.save(user);
            exceptionMethod();
            roleService.save(user.getRole());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

三.異常使用不正確

如果異常使用不當,那麼事物也不會生效,這裡的異常有兩種,一種是我們丟擲的異常,一種是@Transactional註解所接受的異常。

1.如果我們在程式程式碼中自己捕獲了異常導致Spring事物捕獲不到異常,那麼事物也不會生效,如下,exceptionMethod方法捕獲了異常並列印異常資訊,那麼異常並不會被Spring事物捕獲到,所以事物並不會回滾。

    @Transactional(rollbackFor = Exception.class)
    public void saveAnnotation(User user) throws Exception {
        userDao.save(user);
        exceptionMethod();
        roleService.save(user.getRole());
    }

    private void exceptionMethod() throws Exception {
        try {
            int i = 1 / 0;
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

如果需要事物回滾,那麼就需要將異常拋到saveAnnotation方法,這樣Spring事物才能感知到異常,從而進行事物回滾。

2.@Transactional註解有一個屬性rollbackFor,它代表回滾的異常,也就是說只有捕獲到這種異常事物才會回滾,它預設的是RunTimeException。

    @Transactional
    public void saveAnnotation(User user) throws Exception {
        userDao.save(user);
        exceptionMethod();
        roleService.save(user.getRole());
    }

    private void exceptionMethod() throws Exception {
        try {
            int i = 1 / 0;
        } catch (Exception e) {
            throw new Exception("丟擲異常");
        }
    }

如上程式碼,exceptionMethod方法丟擲了Exception異常,而@Transactional註解我們沒有指定rollbackFor,所以使用的是預設的RunTimeException,所以事物不能回滾,如果我們需要事物回滾,需要讓rollbackFor指定的異常是丟擲異常的父類別或者和自己一樣才行,如下所示。

    @Transactional(rollbackFor = Exception.class)
    public void saveAnnotation(User user) {
        userDao.save(user);
        exceptionMethod();
        roleService.save(user.getRole());
    }

    private void exceptionMethod() {
        try {
            int i = 1 / 0;
        } catch (Exception e) {
            throw new ArithmeticException("運算異常");
        }
    }

四.不正確的傳播行為

如果傳播行為使用的是NOT_SUPPORTED,那麼事物無法回滾。NOT_SUPPORTED表示當前方法不應該有事務,如果有事務存在,將它掛起,以無事務狀態執行。

@Transactional(propagation = Propagation.NOT_SUPPORTED)

五.方法修飾為private

如果方法以private修飾,那麼方法將不會被代理,事物自然不會生效,不過如果在進行業務開發的時候,對於需要其它類進行呼叫的方法,我們都是以public修飾,因為如果以private修飾,其它類想要存取的話需要藉助反射才能存取,在IDEA中,@Transactional方法如果修飾為private,會有錯誤提醒,但是執行不會報錯。

不過一些場景我們可能需要反射呼叫,所以不應該避開這個問題,還是將其修飾為public。

六.資料庫不支援事物

如果資料庫不支援事物,那麼即使專案中使用了Spring事物,也不會生效,因為Spring的事物最終也是JDBC的事物,JDBC事物也要資料庫支援事物才行,MySQL中MyISAM儲存引擎不支援事物,InnoDB才支援事物。

七.沒有設定事務管理器,導致事務失效。

使用非SpringBoot專案,需要設定PlatformTransactionManager,需要加上@EnableTransactionManagement註解,如果是SpringBoot專案,那麼可以不用設定,因為SpringBoot預設幫我們裝配好了,我們直接使用就好。