最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Spring事務(wù)管理中@Transactional失效的8個常見場景與解決方案

 更新時間:2025年11月12日 09:27:13   作者:大頭an  
在實際開發(fā)中,你是否遇到過明明在方法上添加了@Transactional注解,但事務(wù)卻沒有按預(yù)期工作,本文將深入剖析Spring事務(wù)管理中常見的8個陷阱,幫助你徹底解決這些問題

引言

在實際開發(fā)中,你是否遇到過這樣的困擾:明明在方法上添加了@Transactional注解,但事務(wù)卻沒有按預(yù)期工作——該回滾的時候沒有回滾,該保持原子性的操作卻出現(xiàn)了部分成功?本文將深入剖析Spring事務(wù)管理中常見的8個陷阱,幫助你徹底解決這些問題。

前置知識

在深入問題之前,我們先簡單回顧Spring事務(wù)的核心機制:

  • PlatformTransactionManager:事務(wù)管理器核心接口
  • @Transactional:聲明式事務(wù)注解
  • 事務(wù)傳播機制:PROPAGATION_REQUIRED、PROPAGATION_REQUIRES_NEW等
  • AOP代理機制:Spring通過代理實現(xiàn)事務(wù)管理

陷阱一:非Public方法

現(xiàn)象描述

在非public方法上使用@Transactional注解,事務(wù)完全不生效。

原理分析

Spring事務(wù)管理基于AOP代理實現(xiàn)。在默認配置下,Spring的AOP代理只能攔截public方法。這是由于Spring使用的代理機制(JDK動態(tài)代理和CGLIB)的限制所致。

@Service
public class UserService {
    
    // 錯誤示例:事務(wù)不會生效
    @Transactional
    void createUserInternal(User user) {
        userMapper.insert(user);
        // 如果這里出現(xiàn)異常,事務(wù)不會回滾
        if (user.getAge() < 0) {
            throw new RuntimeException("年齡不能為負數(shù)");
        }
    }
    
    // 正確示例:使用public修飾符
    @Transactional
    public void createUser(User user) {
        userMapper.insert(user);
        if (user.getAge() < 0) {
            throw new RuntimeException("年齡不能為負數(shù)");
        }
    }
}

解決方案

  • 將需要使用事務(wù)的方法聲明為public
  • 如果需要保護方法訪問,可以使用其他訪問控制機制

陷阱二:自調(diào)用問題

現(xiàn)象描述

在同一個類中,一個方法調(diào)用另一個帶有@Transactional注解的方法,事務(wù)不生效。

原理分析

Spring通過代理對象來管理事務(wù)。當在同一個類中直接調(diào)用方法時,調(diào)用的是目標對象的方法,而不是代理對象的方法,因此事務(wù)攔截器不會起作用。

@Service
public class OrderService {
    
    @Autowired
    private OrderMapper orderMapper;
    
    public void placeOrder(Order order) {
        // 其他業(yè)務(wù)邏輯
        validateOrder(order);
        
        // 自調(diào)用 - 事務(wù)不會生效!
        createOrder(order);
        
        // 正確的調(diào)用方式
        // orderService.createOrder(order);
    }
    
    @Transactional
    public void createOrder(Order order) {
        orderMapper.insert(order);
        updateInventory(order.getItems());
        // 如果更新庫存失敗,期望訂單創(chuàng)建回滾,但自調(diào)用時不會回滾
    }
    
    private void updateInventory(List<OrderItem> items) {
        for (OrderItem item : items) {
            if (item.getQuantity() > getStock(item.getProductId())) {
                throw new RuntimeException("庫存不足");
            }
            // 更新庫存...
        }
    }
}

解決方案

方案一:使用AopContext獲取代理對象

@Service
public class OrderService {
    
    public void placeOrder(Order order) {
        validateOrder(order);
        
        // 通過AopContext獲取代理對象
        OrderService proxy = (OrderService) AopContext.currentProxy();
        proxy.createOrder(order);
    }
    
    @Transactional
    public void createOrder(Order order) {
        // 事務(wù)操作...
    }
}

需要在啟動類上添加@EnableAspectJAutoProxy(exposeProxy = true)

方案二:重構(gòu)代碼結(jié)構(gòu)

@Service
public class OrderService {
    
    @Autowired
    private OrderTransactionService orderTransactionService;
    
    public void placeOrder(Order order) {
        validateOrder(order);
        orderTransactionService.createOrder(order);
    }
}

@Service
public class OrderTransactionService {
    
    @Transactional
    public void createOrder(Order order) {
        // 事務(wù)操作...
    }
}

陷阱三:異常被捕獲

現(xiàn)象描述

方法中拋出了異常,但事務(wù)沒有回滾,因為異常在方法內(nèi)部被捕獲處理了。

原理分析

Spring默認只在拋出RuntimeExceptionError時回滾事務(wù)。如果異常被捕獲,或者拋出的是受檢異常(Checked Exception),事務(wù)不會自動回滾。

@Service
public class PaymentService {
    
    @Autowired
    private PaymentMapper paymentMapper;
    
    @Autowired
    private AccountService accountService;
    
    // 錯誤示例:異常被捕獲
    @Transactional
    public void processPayment(Payment payment) {
        try {
            paymentMapper.insert(payment);
            accountService.deductBalance(payment.getUserId(), payment.getAmount());
            // 可能拋出受檢異常
            sendPaymentNotification(payment);
        } catch (Exception e) {
            // 異常被捕獲,事務(wù)不會回滾!
            log.error("支付處理失敗", e);
        }
    }
    
    // 錯誤示例:拋出受檢異常
    @Transactional
    public void processPaymentWithCheckedException(Payment payment) throws IOException {
        paymentMapper.insert(payment);
        accountService.deductBalance(payment.getUserId(), payment.getAmount());
        
        if (payment.getAmount().compareTo(BigDecimal.ZERO) < 0) {
            // 受檢異常,默認不會觸發(fā)回滾
            throw new IOException("支付金額不能為負數(shù)");
        }
    }
}

解決方案

方案一:手動回滾

@Transactional
public void processPayment(Payment payment) {
    try {
        paymentMapper.insert(payment);
        accountService.deductBalance(payment.getUserId(), payment.getAmount());
        sendPaymentNotification(payment);
    } catch (Exception e) {
        log.error("支付處理失敗", e);
        // 手動設(shè)置回滾
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        throw new RuntimeException("支付失敗", e);
    }
}

方案二:指定回滾異常

// 指定所有Exception都觸發(fā)回滾
@Transactional(rollbackFor = Exception.class)
public void processPaymentWithCheckedException(Payment payment) throws IOException {
    paymentMapper.insert(payment);
    accountService.deductBalance(payment.getUserId(), payment.getAmount());
    
    if (payment.getAmount().compareTo(BigDecimal.ZERO) < 0) {
        throw new IOException("支付金額不能為負數(shù)");
    }
}

// 更精確的控制
@Transactional(rollbackFor = {BusinessException.class, IOException.class}, 
               noRollbackFor = {ValidationException.class})
public void processPaymentWithSpecificExceptions(Payment payment) {
    // 業(yè)務(wù)邏輯...
}

Spring配置中驗證事務(wù)管理器:

@Configuration
@EnableTransactionManagement
public class TransactionConfig {
    
    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
}

陷阱四:數(shù)據(jù)庫引擎不支持事務(wù)

現(xiàn)象描述

在MySQL中使用MyISAM存儲引擎,事務(wù)注解完全無效。

原理分析

MyISAM是MySQL的默認存儲引擎(在舊版本中),但它**不支持事務(wù)**。只有支持事務(wù)的存儲引擎(如InnoDB)才能正常工作。

解決方案

檢查并修改數(shù)據(jù)庫引擎:

-- 檢查表使用的存儲引擎
SHOW TABLE STATUS LIKE 'your_table_name';

-- 修改表存儲引擎為InnoDB
ALTER TABLE your_table_name ENGINE=InnoDB;

-- 創(chuàng)建新表時指定存儲引擎
CREATE TABLE example (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(100)
) ENGINE=InnoDB;

陷阱五:錯誤的傳播機制

現(xiàn)象描述

期望一個操作在獨立事務(wù)中執(zhí)行,但由于傳播機制設(shè)置不當,導(dǎo)致事務(wù)行為不符合預(yù)期。

原理分析

Spring提供了多種事務(wù)傳播機制,錯誤的使用會導(dǎo)致事務(wù)邊界混亂。

@Service
public class AuditService {
    
    @Autowired
    private AuditMapper auditMapper;
    
    // 錯誤使用傳播機制
    @Transactional(propagation = Propagation.SUPPORTS)
    public void logOperation(String operation) {
        auditMapper.insert(new AuditLog(operation));
        // 如果當前沒有事務(wù),這個插入操作不會在事務(wù)中執(zhí)行
    }
}

@Service
public class BusinessService {
    
    @Autowired
    private AuditService auditService;
    
    @Transactional
    public void businessMethod() {
        // 主業(yè)務(wù)邏輯...
        
        // 審計日志 - 期望獨立事務(wù),但實際使用了主事務(wù)
        auditService.logOperation("業(yè)務(wù)操作完成");
    }
}

解決方案

@Service
public class AuditService {
    
    // 正確使用REQUIRES_NEW
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void logOperationInNewTransaction(String operation) {
        auditMapper.insert(new AuditLog(operation));
        // 這個操作會在獨立的新事務(wù)中執(zhí)行
        // 即使外部事務(wù)回滾,審計日志仍然會保存
    }
    
    // 正確使用NOT_SUPPORTED
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public void logOperationWithoutTransaction(String operation) {
        auditMapper.insert(new AuditLog(operation));
        // 這個操作會在無事務(wù)環(huán)境中執(zhí)行
    }
}

陷阱六:方法內(nèi)手動提交

現(xiàn)象描述

在方法中手動獲取Connection并執(zhí)行commit,干擾了Spring的事務(wù)管理。

原理分析

Spring通過ThreadLocal來管理事務(wù)上下文,手動操作Connection會破壞這種管理機制。

// 錯誤示例:手動管理Connection
@Transactional
public void updateWithManualCommit(Data data) {
    Connection conn = null;
    try {
        conn = dataSource.getConnection();
        conn.setAutoCommit(false);
        
        // 執(zhí)行SQL操作
        updateData(conn, data);
        
        // 手動提交
        conn.commit();
    } catch (SQLException e) {
        if (conn != null) {
            conn.rollback();
        }
        throw new RuntimeException(e);
    } finally {
        if (conn != null) {
            conn.close();
        }
    }
}

解決方案

完全依賴Spring的事務(wù)管理:

@Transactional
public void updateWithSpringTransaction(Data data) {
    // 直接使用Spring管理的數(shù)據(jù)訪問組件
    jdbcTemplate.update("UPDATE table SET column = ? WHERE id = ?", 
                       data.getValue(), data.getId());
    // Spring會自動處理事務(wù)提交和回滾
}

陷阱七:異步方法調(diào)用

現(xiàn)象描述

在異步方法中使用@Transactional,事務(wù)上下文無法傳遞到新線程。

原理分析

事務(wù)信息存儲在ThreadLocal中,異步執(zhí)行時會切換到新的線程,事務(wù)上下文丟失。

@Service
public class AsyncService {
    
    @Async
    @Transactional
    public void asyncProcess(Data data) {
        // 這個事務(wù)不會生效!
        dataMapper.insert(data);
        processData(data);
    }
}

解決方案

方案一:在調(diào)用異步方法前完成事務(wù)操作

@Service
public class MainService {
    
    @Autowired
    private AsyncService asyncService;
    
    @Transactional
    public void mainProcess(Data data) {
        // 在主事務(wù)中完成核心數(shù)據(jù)操作
        dataMapper.insert(data);
        
        // 異步處理非核心業(yè)務(wù)
        asyncService.asyncProcessNonCritical(data);
    }
}

@Service
public class AsyncService {
    
    @Async
    public void asyncProcessNonCritical(Data data) {
        // 非核心的異步處理,不要求事務(wù)
        sendNotification(data);
        updateCache(data);
    }
}

方案二:使用編程式事務(wù)管理

@Service
public class AsyncService {
    
    @Autowired
    private TransactionTemplate transactionTemplate;
    
    @Async
    public void asyncProcessWithTransaction(Data data) {
        transactionTemplate.execute(status -> {
            // 在編程式事務(wù)中執(zhí)行
            dataMapper.insert(data);
            processData(data);
            return null;
        });
    }
}

陷阱八:多數(shù)據(jù)源事務(wù)配置錯誤

現(xiàn)象描述

在多數(shù)據(jù)源環(huán)境下,事務(wù)管理器配置不正確,導(dǎo)致事務(wù)無法正確綁定到對應(yīng)的數(shù)據(jù)源。

原理分析

當存在多個數(shù)據(jù)源時,需要明確指定每個事務(wù)使用哪個事務(wù)管理器。

// 錯誤配置:沒有指定事務(wù)管理器
@Configuration
@EnableTransactionManagement
public class MultiDataSourceConfig {
    
    @Bean
    @Primary
    public DataSource primaryDataSource() {
        // 主數(shù)據(jù)源配置
    }
    
    @Bean
    public DataSource secondaryDataSource() {
        // 次要數(shù)據(jù)源配置
    }
    
    // 只配置了一個事務(wù)管理器
    @Bean
    public PlatformTransactionManager transactionManager() {
        return new DataSourceTransactionManager(primaryDataSource());
    }
}

解決方案

正確配置多數(shù)據(jù)源事務(wù)管理:

@Configuration
@EnableTransactionManagement
public class MultiDataSourceConfig {
    
    @Bean
    @Primary
    public DataSource primaryDataSource() {
        // 主數(shù)據(jù)源配置
    }
    
    @Bean
    public DataSource secondaryDataSource() {
        // 次要數(shù)據(jù)源配置
    }
    
    @Bean
    @Primary
    public PlatformTransactionManager primaryTransactionManager() {
        return new DataSourceTransactionManager(primaryDataSource());
    }
    
    @Bean
    public PlatformTransactionManager secondaryTransactionManager() {
        return new DataSourceTransactionManager(secondaryDataSource());
    }
}

// 使用指定的事務(wù)管理器
@Service
public class UserService {
    
    @Transactional(transactionManager = "primaryTransactionManager")
    public void primaryDatabaseOperation(User user) {
        // 使用主數(shù)據(jù)源的事務(wù)
    }
    
    @Transactional(transactionManager = "secondaryTransactionManager")  
    public void secondaryDatabaseOperation(Log log) {
        // 使用次要數(shù)據(jù)源的事務(wù)
    }
}

總結(jié)與最佳實踐

陷阱場景問題現(xiàn)象解決方案
非Public方法事務(wù)完全不生效將方法改為public
自調(diào)用問題同類調(diào)用事務(wù)失效使用AopContext或重構(gòu)代碼結(jié)構(gòu)
異常被捕獲異常處理但事務(wù)未回滾手動回滾或指定rollbackFor
數(shù)據(jù)庫引擎不支持事務(wù)注解無效使用InnoDB等支持事務(wù)的引擎
錯誤的傳播機制事務(wù)邊界混亂根據(jù)業(yè)務(wù)需求選擇合適的傳播機制
方法內(nèi)手動提交干擾Spring事務(wù)管理完全依賴Spring聲明式事務(wù)
異步方法調(diào)用事務(wù)上下文丟失分離事務(wù)操作與異步處理
多數(shù)據(jù)源配置錯誤事務(wù)綁定錯誤數(shù)據(jù)源明確配置和指定事務(wù)管理器

調(diào)試技巧

查看事務(wù)狀態(tài):

@Transactional
public void debugTransaction() {
    TransactionStatus status = TransactionAspectSupport.currentTransactionStatus();
    System.out.println("是否是新事務(wù): " + status.isNewTransaction());
    System.out.println("是否有保存點: " + status.hasSavepoint());
    System.out.println("是否已完成: " + status.isCompleted());
}

啟用事務(wù)調(diào)試日志:

# application.properties
logging.level.org.springframework.transaction.interceptor=TRACE
logging.level.org.springframework.jdbc.datasource.DataSourceTransactionManager=DEBUG

以上就是Spring事務(wù)管理中@Transactional失效的8個常見場景與解決方案的詳細內(nèi)容,更多關(guān)于Spring @Transactional失效解決的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評論

唐河县| 南宫市| 浦江县| 中宁县| 惠来县| 鄂托克旗| 繁昌县| 东明县| 堆龙德庆县| 汽车| 儋州市| 广灵县| 饶河县| 普宁市| 漠河县| 凤台县| 海城市| 兰溪市| 宁波市| 肇东市| 濉溪县| 崇明县| 体育| 高平市| 孟州市| 崇州市| 罗定市| 崇义县| 东光县| 忻城县| 威海市| 阜平县| 顺平县| 陆良县| 玉环县| 永清县| 云阳县| 焉耆| 中方县| 石阡县| 新巴尔虎左旗|