精品国产人成在线_亚洲高清无码在线观看_国产在线视频国产永久2021_国产AV综合第一页一个的一区免费影院黑人_最近中文字幕MV高清在线视频

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

8個Spring事務失效的場景介紹

jf_78858299 ? 來源:JAVA旭陽 ? 作者:JAVA旭陽 ? 2023-05-11 10:41 ? 次閱讀

前言

作為Java開發工程師,相信大家對Spring種事務的使用并不陌生。但是你可能只是停留在基礎的使用層面上,在遇到一些比較特殊的場景,事務可能沒有生效,直接在生產上暴露了,這可能就會導致比較嚴重的生產事故。今天,我們就簡單來說下Spring事務的原理,然后總結一下spring事務失敗的場景,并提出對應的解決方案。

Spring事務原理

大家還記得在JDBC中是如何操作事務的嗎?偽代碼可能如下:

//Get database connection
Connection connection = DriverManager.getConnection();
//Set autoCommit is false
connection.setAutoCommit(false);
//use sql to operate database
.........
//Commit or rollback
connection.commit()/connection.rollback

connection.close();

需要在各個業務代碼中編寫代碼如commit()close()來控制事務。

但是Spring不樂意這么干了,這樣對業務代碼侵入性太大了,所有就用一個事務注解@Transactional來控制事務,底層實現是基于切面編程AOP實現的,而Spring中實現AOP機制采用的是動態代理,具體分為JDK動態代理和CGLIB動態代理兩種模式。圖片

  1. Springbean的初始化過程中,發現方法有Transactional注解,就需要對相應的Bean進行代理,生成代理對象。
  2. 然后在方法調用的時候,會執行切面的邏輯,而這里切面的邏輯中就包含了開啟事務、提交事務或者回滾事務等邏輯。

另外注意一點的是,Spring 本身不實現事務,底層還是依賴于數據庫的事務。沒有數據庫事務的支持,Spring事務是不會生效的。

接下來我們進入正題,看看哪些場景會導致Spring事務失敗。

Spring事務失效場景

1. 拋出檢查異常

比如你的事務控制代碼如下:

@Transactional
public void transactionTest() throws IOException{
    User user = new User();
    UserService.insert(user);
    throw new IOException();
}

如果@Transactional 沒有特別指定,Spring 只會在遇到運行時異常RuntimeException或者error時進行回滾,而IOException等檢查異常不會影響回滾。

public boolean rollbackOn(Throwable ex) {
	return (ex instanceof RuntimeException || ex instanceof Error);
}

解決方案:

知道原因后,解決方法也很簡單。配置rollbackFor屬性,例如@Transactional(rollbackFor = Exception.class)

2. 業務方法本身捕獲了異常

@Transactional(rollbackFor = Exception.class)
public void transactionTest() {
    try {
        User user = new User();
        UserService.insert(user);
        int i = 1 / 0;
    }catch (Exception e) {
        e.printStackTrace();
    }
}

這種場景下,事務失敗的原因也很簡單,Spring是否進行回滾是根據你是否拋出異常決定的,所以如果你自己捕獲了異常,Spring 也無能為力。

看了上面的代碼,你可能認為這么簡單的問題你不可能犯這么愚蠢的錯誤,但是我想告訴你的是,我身邊幾乎一半的人都被這一幕困擾過。

寫業務代碼的時候,代碼可能比較復雜,嵌套的方法很多。如果你不小心,很可能會觸發此問題。舉一個非常簡單的例子,假設你有一個審計功能。每個方法執行后,審計結果保存在數據庫中,那么代碼可能會這樣寫。

@Service
public class TransactionService {

    @Transactional(rollbackFor = Exception.class)
    public void transactionTest() throws IOException {
        User user = new User();
        UserService.insert(user);
        throw new IOException();

    }
}

@Component
public class AuditAspect {

	@Autowired
	private auditService auditService;

    @Around(value = "execution (* com.alvin.*.*(..))")
    public Object around(ProceedingJoinPoint pjp) {
        try {
            Audit audit = new Audit();
            Signature signature = pjp.getSignature();
            MethodSignature methodSignature = (MethodSignature) signature;
            String[] strings = methodSignature.getParameterNames();
            audit.setMethod(signature.getName());
            audit.setParameters(strings);
            Object proceed = pjp.proceed();
            audit.success(true);
            return proceed;
        } catch (Throwable e) {
            log.error("{}", e);
            audit.success(false);
        }

        auditService.save(audit);
        return null;
    }

}

在上面的示例中,事務將失敗。原因是Spring的事務切面優先級最低,所以如果異常被切面捕獲,Spring自然不能正常處理事務,因為事務管理器無法捕獲異常。

解決方案:

看,雖然我們知道在處理事務時業務代碼不能自己捕獲異常,但是只要代碼變得復雜,我們就很可能再次出錯,所以我們在處理事務的時候要小心,還是不要使用聲明式事務, 并使用編程式事務— transactionTemplate.execute()

3. 同一類中的方法調用

@Service
public class DefaultTransactionService implement Service {

    public void saveUser() throws Exception {
        //do something
        doInsert();
    }

    @Transactional(rollbackFor = Exception.class)
    public void doInsert() throws IOException {
        User user = new User();
        UserService.insert(user);
        throw new IOException();

    }
}

這也是一個容易出錯的場景。事務失敗的原因也很簡單,因為Spring的事務管理功能是通過動態代理實現的,而Spring默認使用JDK動態代理,而JDK動態代理采用接口實現的方式,通過反射調用目標類。簡單理解,就是saveUser()方法中調用this.doInsert(),這里的this是被真實對象,所以會直接走doInsert的業務邏輯,而不會走切面邏輯,所以事務失敗。

解決方案:

方案一 :解決方法可以是直接在啟動類中添加@Transactional注解saveUser()

方案二@EnableAspectJAutoProxy(exposeProxy = true)在啟動類中添加,會由Cglib代理實現。

4. 方法使用 final 或 static關鍵字

如果Spring使用了Cglib代理實現(比如你的代理類沒有實現接口),而你的業務方法恰好使用了final或者static關鍵字,那么事務也會失敗。更具體地說,它應該拋出異常,因為Cglib使用字節碼增強技術生成被代理類的子類并重寫被代理類的方法來實現代理。如果被代理的方法的方法使用finalstatic關鍵字,則子類不能重寫被代理的方法。

如果Spring使用JDK動態代理實現,JDK動態代理是基于接口實現的,那么finalstatic修飾的方法也就無法被代理。

總而言之,方法連代理都沒有,那么肯定無法實現事務回滾了。

解決方案:

想辦法去掉final或者static關鍵字

5. 方法不是public

如果方法不是publicSpring事務也會失敗,因為Spring的事務管理源碼AbstractFallbackTransactionAttributeSource中有判斷computeTransactionAttribute()。如果目標方法不是公共的,則TransactionAttribute返回null

// Don't allow no-public methods as required.
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
  return null;
}

解決方案:

是將當前方法訪問級別更改為public

6. 錯誤使用傳播機制

Spring事務的傳播機制是指在多個事務方法相互調用時,確定事務應該如何傳播的策略。Spring提供了七種事務傳播機制:REQUIREDSUPPORTSMANDATORYREQUIRES_NEWNOT_SUPPORTEDNEVERNESTED。如果不知道這些傳播策略的原理,很可能會導致交易失敗。

@Service
public class TransactionService {


    @Autowired
    private UserMapper userMapper;

    @Autowired
    private AddressMapper addressMapper;


    @Transactional(propagation = Propagation.REQUIRES_NEW,rollbackFor = Exception.class)
    public  void doInsert(User user,Address address) throws Exception {
        //do something
        userMapper.insert(user);
        saveAddress(address);
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public  void saveAddress(Address address) {
        //do something
        addressMapper.insert(address);
    }
}

在上面的例子中,如果用戶插入失敗,不會導致saveAddress()回滾,因為這里使用的傳播是REQUIRES_NEW,傳播機制REQUIRES_NEW的原理是如果當前方法中沒有事務,就會創建一個新的事務。如果一個事務已經存在,則當前事務將被掛起,并創建一個新事務。在當前事務完成之前,不會提交父事務。如果父事務發生異常,則不影響子事務的提交。

事務的傳播機制說明如下:

  • REQUIRED 如果當前上下文中存在事務,那么加入該事務,如果不存在事務,創建一個事務,這是默認的傳播屬性值。
  • SUPPORTS 如果當前上下文存在事務,則支持事務加入事務,如果不存在事務,則使用非事務的方式執行。
  • MANDATORY 如果當前上下文中存在事務,否則拋出異常。
  • REQUIRES_NEW 每次都會新建一個事務,并且同時將上下文中的事務掛起,執行當前新建事務完成以后,上下文事務恢復再執行。
  • NOT_SUPPORTED 如果當前上下文中存在事務,則掛起當前事務,然后新的方法在沒有事務的環境中執行。
  • NEVER 如果當前上下文中存在事務,則拋出異常,否則在無事務環境上執行代碼。
  • NESTED 如果當前上下文中存在事務,則嵌套事務執行,如果不存在事務,則新建事務。

解決方案 :

將事務傳播策略更改為默認值REQUIREDREQUIRED原理是如果當前有一個事務被添加到一個事務中,如果沒有,則創建一個新的事務,父事務和被調用的事務在同一個事務中。即使被調用的異常被捕獲,整個事務仍然會被回滾。

7. 沒有被Spring管理

// @Service
public class OrderServiceImpl implements OrderService {
    @Transactional
    public void updateOrder(Order order) {
        // update order
    }
}

如果此時把 @Service 注解注釋掉,這個類就不會被加載成一個 Bean,那這個類就不會被 Spring 管理了,事務自然就失效了。

解決方案 :

需要保證每個事務注解的每個Bean被Spring管理。

8. 多線程

@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;
    @Autowired
    private RoleService roleService;

    @Transactional
    public void add(UserModel userModel) throws Exception {

        userMapper.insertUser(userModel);
        new Thread(() -> {
             try {
                 test();
             } catch (Exception e) {
                roleService.doOtherThing();
             }
        }).start();
    }
}

@Service
public class RoleService {

    @Transactional
    public void doOtherThing() {
         try {
             int i = 1/0;
             System.out.println("保存role表數據");
         }catch (Exception e) {
            throw new RuntimeException();
        }
    }
}

我們可以看到事務方法add中,調用了事務方法doOtherThing,但是事務方法doOtherThing是在另外一個線程中調用的。

這樣會導致兩個方法不在同一個線程中,獲取到的數據庫連接不一樣,從而是兩個不同的事務。如果想doOtherThing方法中拋了異常,add方法也回滾是不可能的。

我們說的同一個事務,其實是指同一個數據庫連接,只有擁有同一個數據庫連接才能同時提交和回滾。如果在不同的線程,拿到的數據庫連接肯定是不一樣的,所以是不同的事務。

解決方案:

這里就有點分布式事務的感覺了,盡量還是保證在同一個事務中處理。

總結

本文簡單闡述了下Spring中事務實現的原理,同時列舉了8種Spring事務失敗的場景,相信很多朋友可能都遇到過, 失敗的原因也有詳細說明。希望大家對Spring事務有一個新的認識。

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • JAVA
    +關注

    關注

    19

    文章

    2960

    瀏覽量

    104565
  • spring
    +關注

    關注

    0

    文章

    338

    瀏覽量

    14312
  • 開發工程師
    +關注

    關注

    1

    文章

    91

    瀏覽量

    14928
收藏 人收藏

    評論

    相關推薦

    Spring事務失效的十種常見場景

    Spring針對Java Transaction API (JTA)、JDBC、Hibernate和Java Persistence API(JPA)等事務 API,實現了一致的編程模型,而
    的頭像 發表于 12-11 15:03 ?866次閱讀

    Spring事務實現原理

    作者:京東零售 范錫軍 1、引言 springspring-tx模塊提供了對事務管理支持,使用spring事務可以讓我們從復雜的
    的頭像 發表于 11-08 10:10 ?747次閱讀
    <b class='flag-5'>Spring</b><b class='flag-5'>事務</b>實現原理

    什么是java spring

    。在SSH項目中管理事務以及對象的注入Spring是非侵入式的:基于Spring開發的系統中的對象一般不依賴于Spring的類。組成 Spring
    發表于 09-11 11:16

    Spring的兩種方式事務管理和API接口介紹

    Spring事務管理
    發表于 03-21 06:52

    Spring事務分析的實現方式

    Spring事務原理分析
    發表于 07-02 15:19

    詳解Spring事務管理

    在學習spring事務管理時,我忍不住要問,spring為什么進行事務管理,spring怎么進行的事務
    發表于 07-12 06:54

    Spring事務管理詳解說明

    Spring事務管理詳解
    發表于 05-20 13:46

    spring中聲明式事務實現原理猜想

    ? @Transactional注解簡介 @Transactional 是spring中聲明式事務管理的注解配置方式,相信這個注解的作用大家都很清楚。 @Transactional 注解可以幫助
    的頭像 發表于 10-13 09:20 ?1613次閱讀

    Spring認證是什么?

    ,例如:配置、組件掃描、AOP、數據訪問和事務、REST、安全、自動配置、執行器、 Spring boot測試等。
    的頭像 發表于 07-04 10:19 ?1285次閱讀
    <b class='flag-5'>Spring</b>認證是什么?

    淺談Spring事務的那些坑

    對于從事java開發工作的同學來說,spring事務肯定再熟悉不過了。在某些業務場景下,如果同時有多張表的寫入操作,為了保證操作的原子性(要么同時成功,要么同時失敗)避免數據不一致的情況,我們一般都會使用
    的頭像 發表于 10-11 10:31 ?726次閱讀

    發現一Spring事務的巨坑bug 你必須要小心了

    1.錯誤的訪問權限 2.方法被定義成final的 3.方法內部調用 4.當前實體沒有被spring管理 5.錯誤的spring事務傳播特性 6.數據庫不支持事務 7.自己吞掉了異常
    的頭像 發表于 10-11 18:17 ?841次閱讀

    淺談Spring事務底層原理

    開啟Spring事務本質上就是增加了一Advisor,但我們使用@EnableTransactionManagement注解來開啟Spring事務
    的頭像 發表于 12-06 09:56 ?678次閱讀

    Spring事務在哪幾種情況下會不生效?

    日常開發中,我們經常使用到spring事務。最近星球一位還有去美團面試,被問了這么一道面試題: Spring 事務在哪幾種情況下會不生效?
    的頭像 發表于 05-10 17:53 ?901次閱讀
    <b class='flag-5'>Spring</b><b class='flag-5'>事務</b>在哪幾種情況下會不生效?

    spring事務失效的一些場景

    對于從事java開發工作的同學來說,spring事務肯定再熟悉不過了。 在某些業務場景下,如果一請求中,需要同時寫入多張表的數據。為了保證操作的原子性(要么同時成功,要么同時失敗)
    的頭像 發表于 10-08 14:27 ?429次閱讀
    <b class='flag-5'>spring</b><b class='flag-5'>事務</b><b class='flag-5'>失效</b>的一些<b class='flag-5'>場景</b>

    Spring事務傳播性的相關知識

    本文主要介紹Spring事務傳播性的相關知識。
    的頭像 發表于 01-10 09:29 ?409次閱讀
    <b class='flag-5'>Spring</b><b class='flag-5'>事務</b>傳播性的相關知識