事務 - 失效的場景

CyrusHuang發表於2024-10-20

1. 沒有使用代理

場景: 如果你在一個類內部呼叫同一個類中的另一個方法,Spring 事務管理無法生效。

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * 當在 controller 呼叫 executeTask 時事務會失效
 * 1,因為標註了 @Transaction 註解,spring 會為 MyService 建立一個代理物件
 * 2,controller 呼叫 executeTask 方法時,呼叫的是真實物件的方法,因為這個方法沒有標註事務註解
 * 3,executeTask 再呼叫 performTask 時,其實是 this.performTask,這裡的 this 是普通物件,而不是代理物件
 */
@Service
public class MyService {

    @Transactional
    public void performTask() {
        // 模擬資料庫操作
        System.out.println("Performing task...");
        // 這裡可以新增更多資料庫操作程式碼
        // 例如:userRepository.save(new User());
    }

    public void executeTask() {
        performTask();  // 直接呼叫同一類中的方法
    }
}

原因: 這是因為 Spring 事務管理是基於代理的。當你在一個類中呼叫另一個方法時,實際上是在同一個物件的上下文中執行的,這樣事務註解不會被代理攔截,導致事務失效。

解決方法: 1,將方法提取到不同的類中,2,在同一個類中使用 self 注入。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class MyService {

    @Autowired
    private MyService self;  // 自我注入

    @Transactional
    public void performTask() {
        System.out.println("Performing task...");
        // 資料庫操作
    }

    public void executeTask() {
        self.performTask();  // 使用自我注入呼叫,因為此時 self 已經是代理物件了,所以會走 AOP 的通知
    }
}

2. 非執行時異常

場景: Spring 預設只對執行時異常(RuntimeException)進行回滾,如果丟擲的是檢查性異常(如 IOException),則不會回滾事務。

@Transactional
public void test(){
	userDao.save(user);
   	new File("D:\\不存在的檔案.jpg")
}

原因: 這是 Spring 事務管理的預設行為,非執行時異常不會觸發事務回滾。

解決方法: 可以在 @Transactional 註解中指定回滾的異常型別,例如:

@Transactional(rollbackFor = Exception.class)
public void test(){
	userDao.save(user);
   	new File("D:\\不存在的檔案.jpg")
}

3. 事務傳播行為設定不當

場景: 在多個事務之間的互動時,傳播行為設定不當可能導致事務失效。

// 訂單 service
@Service
public class OrderService {
    
    private final PaymentService paymentService;

    public OrderService(PaymentService paymentService) {
        this.paymentService = paymentService;
    }

    @Transactional
    public void createOrder() {
        // 訂單建立邏輯
        System.out.println("Creating order...");
        
        // 呼叫 PaymentService 處理支付
        paymentService.processPayment();
        
        // 其他訂單處理邏輯
    }
}

// 支付 service
@Service
public class PaymentService {

    // 這個方法使用的是 REQUIRES_NEW,會新開一個事務,出現異常只是當前事務會回滾,不會影響上層的事務
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void processPayment() {
        // 支付處理邏輯
        System.out.println("Processing payment...");
        // 模擬異常
        if (true) {
            throw new RuntimeException("Payment failed!");
        }
    }
}

4. 隔離級別不合適

場景: 在高併發情況下,使用不合適的隔離級別可能導致事務表現不符合預期。

# 1,假設隔離級別是讀未提交
# 2,A 事務先新增一條資料
# 3,此時 B 事務讀到這條資料了,做業務的途中發生異常,要回滾資料(將要回滾還沒有回滾的時候,A RollBack 了)
# 4,B 事務咋回滾,要回滾的資料都沒了

5. Spring Boot 和 Spring 版本不相容

場景: 在使用 Spring Boot 的時候,某些 Spring 版本之間可能存在相容性問題。

原因: 在不同版本的 Spring 或 Spring Boot 中,事務管理的實現可能會有所不同。

解決方法: 確保使用相容的 Spring 和 Spring Boot 版本,並查閱相應的文件。

相關文章