偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

別在 MyBatis-Plus 里瞎用 @Transactional!這鍋背到我差點(diǎn)被踢出項(xiàng)目組

開發(fā) 前端
今兒個(gè)咱就掰開揉碎了聊,保證讓你看完再也不會(huì)在事務(wù)管理上栽跟頭,說不定還能反向指導(dǎo)新人避坑,妥妥的升職加薪小技巧??!

兄弟們,咱今天來(lái)嘮嘮 MyBatis-Plus 里那個(gè)讓人又愛又恨的 @Transactional 注解。先給大家講個(gè)真實(shí)的 "血淚教訓(xùn)":上個(gè)月我在項(xiàng)目里一頓操作猛如虎,對(duì)著 Service 層狂甩 @Transactional 注解,自以為事務(wù)管理穩(wěn)如老狗,結(jié)果線上接連爆雷 —— 訂單創(chuàng)建成功但庫(kù)存沒扣,用戶退款申請(qǐng)?zhí)峤涣说?cái)務(wù)系統(tǒng)沒同步,最慘的是某天凌晨收到運(yùn)維大哥的奪命連環(huán) call,說數(shù)據(jù)庫(kù)連接池被撐爆了。當(dāng)時(shí)項(xiàng)目經(jīng)理看我的眼神,恨不得把我連人帶電腦一起踢出項(xiàng)目組...

痛定思痛之后,我抱著 MyBatis-Plus 官方文檔和 Spring 事務(wù)源碼啃了三天三夜,終于摸清了這個(gè)注解在 MyBatis-Plus 里的各種坑。今兒個(gè)咱就掰開揉碎了聊,保證讓你看完再也不會(huì)在事務(wù)管理上栽跟頭,說不定還能反向指導(dǎo)新人避坑,妥妥的升職加薪小技巧??!

一、先搞明白:MyBatis-Plus 的事務(wù)和 Spring 到底啥關(guān)系?

好多小伙伴可能跟我當(dāng)初一樣犯迷糊:MyBatis-Plus 不是 ORM 框架嗎,為啥要用 Spring 的 @Transactional 注解?這就得從 MyBatis-Plus 的出身說起了 —— 它本質(zhì)上是 MyBatis 的增強(qiáng)工具,本身并不提供事務(wù)管理功能,而是完全依賴 Spring 的事務(wù)管理機(jī)制。所以咱們聊的 @Transactional,本質(zhì)上還是 Spring 的注解,只不過在 MyBatis-Plus 的使用場(chǎng)景里,有些細(xì)節(jié)需要特別注意。

1.1 底層原理:Spring 是怎么玩轉(zhuǎn)事務(wù)的?

這里咱用個(gè)接地氣的比喻:Spring 的事務(wù)管理就像一場(chǎng)舞臺(tái)劇,@Transactional 注解就是導(dǎo)演給演員(方法)貼的標(biāo)簽,告訴幕后的 AOP 代理(替身演員):"這一段戲需要開啟事務(wù),要是演砸了(拋異常)得回滾??!" 具體來(lái)說:

  • 代理模式:Spring 會(huì)給加了 @Transactional 的類生成動(dòng)態(tài)代理(JDK 代理或 CGLIB 代理),當(dāng)調(diào)用目標(biāo)方法時(shí),實(shí)際執(zhí)行的是代理類中的事務(wù)增強(qiáng)邏輯。
  • 事務(wù)攔截器:org.springframework.transaction.interceptor.TransactionInterceptor 是核心攔截器,它會(huì)在方法執(zhí)行前開啟事務(wù),執(zhí)行過程中監(jiān)控異常,執(zhí)行完畢后根據(jù)情況提交或回滾事務(wù)。
  • 數(shù)據(jù)源綁定:通過 ThreadLocal 將數(shù)據(jù)庫(kù)連接與當(dāng)前線程綁定,確保同一個(gè)事務(wù)內(nèi)使用的是同一個(gè)數(shù)據(jù)庫(kù)連接。

這里有個(gè)關(guān)鍵知識(shí)點(diǎn):MyBatis-Plus 的 Mapper 方法在執(zhí)行時(shí),必須通過 Spring 容器獲取的 Mapper 代理對(duì)象調(diào)用,才能保證處于 Spring 的事務(wù)管理范圍內(nèi)。如果你在代碼里自己 new 了一個(gè) Mapper 實(shí)例(雖然正常人不會(huì)這么干,但咱得防著新手踩坑),那事務(wù)肯定不會(huì)生效。

1.2 MyBatis-Plus 的特殊點(diǎn):這些操作會(huì)影響事務(wù)嗎?

咱都知道 MyBatis-Plus 有很多貼心的增強(qiáng)功能,比如自動(dòng)填充、樂觀鎖、邏輯刪除等,這些功能在事務(wù)中會(huì)不會(huì)搞事情呢?

  • 自動(dòng)填充(MetaObjectHandler):放心,自動(dòng)填充是在 Mapper 執(zhí)行 SQL 之前完成的,屬于業(yè)務(wù)邏輯的一部分,只要在事務(wù)方法內(nèi)調(diào)用 Mapper,填充的數(shù)據(jù)會(huì)跟著事務(wù)一起提交或回滾。
  • 樂觀鎖(@Version):重點(diǎn)來(lái)了!當(dāng)使用樂觀鎖時(shí),MyBatis-Plus 會(huì)在更新語(yǔ)句中添加版本號(hào)校驗(yàn)條件。如果在事務(wù)中多個(gè)線程同時(shí)更新同一條數(shù)據(jù),后提交的線程會(huì)因?yàn)榘姹咎?hào)不一致導(dǎo)致更新失敗,此時(shí)事務(wù)會(huì)回滾,這是正常現(xiàn)象,不是 bug 哦。
  • 邏輯刪除(@TableLogic):邏輯刪除本質(zhì)上是執(zhí)行 update 語(yǔ)句,將 deleted 字段標(biāo)記為 1,和普通的 update 操作一樣,受事務(wù)管理控制,刪除操作會(huì)在事務(wù)提交時(shí)生效。

二、這些 "想當(dāng)然" 的操作,分分鐘讓事務(wù)失效!

2.1 同類方法調(diào)用:別以為加了注解就萬(wàn)事大吉

先看一段讓我栽跟頭的代碼:

@Service
public class OrderService {
    @Autowired
    private OrderMapper orderMapper;
    @Autowired
    private StockService stockService;
    // 外層方法加了事務(wù)
    @Transactional(rollbackFor = Exception.class)
    public void createOrder(OrderDTO orderDTO) {
        OrderEntity orderEntity = convertToEntity(orderDTO);
        orderMapper.insert(orderEntity); // 插入訂單
        updateStock(orderDTO.getProductId(), orderDTO.getQuantity()); // 調(diào)用內(nèi)部方法扣庫(kù)存
    }
    // 內(nèi)部方法沒加注解,以為會(huì)跟著外層事務(wù)走
    private void updateStock(Long productId, Integer quantity) {
        StockEntity stock = stockMapper.selectById(productId);
        if (stock.getStockQuantity() < quantity) {
            throw new BusinessException("庫(kù)存不足");
        }
        stock.setStockQuantity(stock.getStockQuantity() - quantity);
        stockMapper.updateById(stock); // 這里出問題了!
    }
}

看起來(lái)挺合理吧?外層方法有事務(wù),內(nèi)部方法應(yīng)該跟著一起回滾。但實(shí)際情況是:當(dāng) updateStock 拋異常時(shí),訂單數(shù)據(jù)居然已經(jīng)插入數(shù)據(jù)庫(kù)了!為啥呢?原因解析:Spring 的 AOP 代理是基于接口或類的,當(dāng)在同一個(gè)類中調(diào)用另一個(gè)方法時(shí),實(shí)際上是通過 this 引用調(diào)用的,而不是通過代理對(duì)象調(diào)用。這時(shí)候,事務(wù)增強(qiáng)邏輯就不會(huì)生效,內(nèi)部方法相當(dāng)于在事務(wù)之外執(zhí)行。

解決方案

  • 方法上移:把 updateStock 的邏輯直接寫在外層方法里,別搞內(nèi)部調(diào)用。
  • 自我注入:在類中注入自己,通過代理對(duì)象調(diào)用方法:
@Service
public class OrderService {
    @Autowired
    private OrderService self; // 注入自己
    @Transactional(rollbackFor = Exception.class)
    public void createOrder(OrderDTO orderDTO) {
        OrderEntity orderEntity = convertToEntity(orderDTO);
        orderMapper.insert(orderEntity);
        self.updateStock(orderDTO.getProductId(), orderDTO.getQuantity()); // 通過代理對(duì)象調(diào)用
    }
    private void updateStock(Long productId, Integer quantity) {
        // ... 邏輯不變
    }
}

不過這種方法有點(diǎn)反直覺,建議還是盡量避免同類內(nèi)的方法調(diào)用,保持事務(wù)方法的原子性。

2.2 不同數(shù)據(jù)源:多數(shù)據(jù)源場(chǎng)景下事務(wù)會(huì) "迷路"

現(xiàn)在微服務(wù)架構(gòu)里,多數(shù)據(jù)源場(chǎng)景很常見,比如主庫(kù)寫、從庫(kù)讀,或者分庫(kù)分表。這時(shí)候如果在一個(gè)事務(wù)里操作多個(gè)數(shù)據(jù)源,@Transactional 還能生效嗎?

先看配置:

@Configuration
public class DataSourceConfig {
    @Bean("masterDataSource")
    @Primary
    public DataSource masterDataSource() {
        // 主數(shù)據(jù)源配置
    }
    @Bean("slaveDataSource")
    public DataSource slaveDataSource() {
        // 從數(shù)據(jù)源配置
    }
    @Bean("masterTransactionManager")
    @Primary
    public DataSourceTransactionManager masterTransactionManager(
            @Qualifier("masterDataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
    @Bean("slaveTransactionManager")
    public DataSourceTransactionManager slaveTransactionManager(
            @Qualifier("slaveDataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
}

然后在 Service 里:

@Service
public class MultiDataSourceService {
    @Autowired
    private MasterOrderMapper masterOrderMapper;
    @Autowired
    private SlaveStockMapper slaveStockMapper;
    // 以為加了@Transactional就能跨庫(kù)事務(wù)
    @Transactional(rollbackFor = Exception.class)
    public void updateBoth(String orderNo, Integer stockId) {
        MasterOrderEntity order = masterOrderMapper.selectByOrderNo(orderNo);
        order.setStatus("已支付");
        masterOrderMapper.updateById(order);
        SlaveStockEntity stock = slaveStockMapper.selectById(stockId);
        stock.setStockStatus("已扣除");
        slaveStockMapper.updateById(stock);
        if (orderNo.equals("異常訂單")) {
            throw new RuntimeException("模擬異常");
        }
    }
}

實(shí)際運(yùn)行結(jié)果:主庫(kù)的訂單狀態(tài)更新了,但從庫(kù)的庫(kù)存狀態(tài)沒回滾!為啥呢?因?yàn)?@Transactional 默認(rèn)使用的是主數(shù)據(jù)源的事務(wù)管理器,而跨數(shù)據(jù)源的事務(wù)需要分布式事務(wù)解決方案(如 Seata、XA 協(xié)議等),單純的 @Transactional 搞不定。解決方案

  • 明確指定事務(wù)管理器:如果只是操作同一個(gè)數(shù)據(jù)源內(nèi)的不同庫(kù)(比如分庫(kù)),可以通過 @Transactional (transactionManager = "masterTransactionManager") 指定正確的事務(wù)管理器。
  • 分布式事務(wù)方案:涉及多個(gè)獨(dú)立數(shù)據(jù)源時(shí),必須使用分布式事務(wù)框架,別指望 Spring 的本地事務(wù)能搞定。

2.3 枚舉類型坑:數(shù)據(jù)庫(kù)類型和 Java 枚舉對(duì)不上,事務(wù)不回滾

MyBatis-Plus 支持將 Java 枚舉映射到數(shù)據(jù)庫(kù)字段,比如:

public enum OrderStatus {
    NEW(0, "新建"),
    PAID(1, "已支付"),
    CANCELLED(2, "已取消");
    private Integer code;
    private String desc;
    // 構(gòu)造器和getter省略
}
@TableField(typeHandler = OrderStatusTypeHandler.class)
private OrderStatus status;

然后在 Service 里:

@Transactional(rollbackFor = Exception.class)
public void updateOrderStatus(Long orderId, Integer statusCode) {
    OrderEntity order = orderMapper.selectById(orderId);
    order.setStatus(OrderStatus.valueOf(statusCode)); // 這里可能拋IllegalArgumentException
    orderMapper.updateById(order);
    // 假設(shè)后面還有其他操作
    int i = 1 / 0; // 模擬除零異常
}

當(dāng) statusCode 傳了一個(gè)不存在的枚舉值時(shí),OrderStatus.valueOf 會(huì)拋 IllegalArgumentException,按理說整個(gè)事務(wù)應(yīng)該回滾。但實(shí)際情況是:訂單狀態(tài)可能已經(jīng)更新了,后面的除零異常導(dǎo)致方法報(bào)錯(cuò),但事務(wù)沒回滾!原因解析:Spring 默認(rèn)只對(duì) RuntimeException 及其子類回滾,CheckedException 不回滾。而 IllegalArgumentException 是 RuntimeException,按道理應(yīng)該回滾???問題出在 MyBatis-Plus 的 TypeHandler 上,當(dāng)枚舉映射錯(cuò)誤時(shí),MyBatis 會(huì)在執(zhí)行 SQL 之前就拋出異常,此時(shí)事務(wù)可能還沒真正開啟,或者說異常發(fā)生在事務(wù)增強(qiáng)邏輯之外。

解決方案

  • 顯式指定回滾異常:@Transactional (rollbackFor = {Exception.class, IllegalArgumentException.class}),雖然有點(diǎn)粗暴,但能確保所有異常都回滾。
  • 提前校驗(yàn)參數(shù):在設(shè)置枚舉值之前,先檢查 statusCode 是否合法,避免在 MyBatis 處理時(shí)拋異常。

2.4 序列化陷阱:事務(wù)方法參數(shù)或返回值不能序列化

在分布式環(huán)境下(比如 Spring Cloud),如果 @Transactional 方法的參數(shù)或返回值包含無(wú)法序列化的對(duì)象,會(huì)導(dǎo)致事務(wù)失效,甚至應(yīng)用啟動(dòng)報(bào)錯(cuò)。比如:

@Service
public class UserService {
    @Transactional(rollbackFor = Exception.class)
    public UserEntity updateUser(UserEntity user) {
        // 假設(shè)UserEntity里有一個(gè)ThreadLocal類型的字段
        userMapper.updateById(user);
        return user;
    }
}

如果 UserEntity 包含 ThreadLocal、Socket 等無(wú)法序列化的字段,當(dāng)通過 RMI、Feign 等遠(yuǎn)程調(diào)用時(shí),就會(huì)報(bào)錯(cuò)。雖然這不是 MyBatis-Plus 特有的問題,但在分布式場(chǎng)景下經(jīng)常和 MyBatis-Plus 一起出現(xiàn),必須注意。解決方案

  • 確保實(shí)體類可序列化:讓實(shí)體類實(shí)現(xiàn) Serializable 接口,并且所有字段都是可序列化的。
  • 避免在遠(yuǎn)程接口中使用事務(wù)方法:事務(wù)方法盡量只在本地調(diào)用,遠(yuǎn)程接口專注于業(yè)務(wù)邏輯,別加 @Transactional。

三、深度解析:@Transactional 的核心參數(shù),你真的懂嗎?

3.1 propagation:事務(wù)傳播行為,組隊(duì)打副本的策略

這是最容易搞錯(cuò)的參數(shù),決定了多個(gè)事務(wù)方法嵌套調(diào)用時(shí)的行為。類比組隊(duì)打副本:

  • REQUIRED(默認(rèn)值):如果當(dāng)前有事務(wù),就加入;沒有就創(chuàng)建新事務(wù)。就像組隊(duì)打 BOSS,你要是已經(jīng)在隊(duì)伍里,就跟著一起打;沒隊(duì)伍就自己建一個(gè)。
  • REQUIRES_NEW:不管有沒有事務(wù),都創(chuàng)建新事務(wù),掛起當(dāng)前事務(wù)。相當(dāng)于自己開個(gè)新隊(duì)伍,不管原來(lái)有沒有隊(duì)伍。
  • SUPPORTS:支持當(dāng)前事務(wù),沒有就以非事務(wù)方式執(zhí)行。就是能組隊(duì)就組隊(duì),組不了就單刷。
  • NOT_SUPPORTED:不支持事務(wù),掛起當(dāng)前事務(wù),以非事務(wù)方式執(zhí)行。就是拒絕組隊(duì),自己?jiǎn)嗡ⅰ?/span>
  • NEVER:必須沒有事務(wù),否則拋異常。就是堅(jiān)決不組隊(duì),看到隊(duì)伍就跑。
  • NESTED:嵌套事務(wù),在一個(gè)事務(wù)中開啟子事務(wù),子事務(wù)回滾不影響父事務(wù),父事務(wù)回滾子事務(wù)跟著回滾。相當(dāng)于副本里的小 BOSS 戰(zhàn),小 BOSS 滅了可以重來(lái),整個(gè)副本失敗就全完。

舉個(gè)栗子:

@Transactional(propagation = Propagation.REQUIRED)
public void parentMethod() {
    childMethod();
    // 這里拋異常,parent和child都回滾
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void childMethod() {
    // 這里拋異常,child回滾,parent不受影響(如果parent在child之后拋異常,child還是會(huì)回滾,因?yàn)閜arent的事務(wù)包含child)
}

注意:NESTED 需要數(shù)據(jù)庫(kù)支持,比如 MySQL 的 InnoDB 引擎支持 Savepoint 實(shí)現(xiàn)嵌套事務(wù),而 REQUIRES_NEW 是通過新的事務(wù)連接實(shí)現(xiàn)的,性能上會(huì)有差異。

3.2 isolation:事務(wù)隔離級(jí)別,解決并發(fā)問題的關(guān)鍵

默認(rèn)值是 Isolation.DEFAULT(使用數(shù)據(jù)庫(kù)默認(rèn)隔離級(jí)別,MySQL 是 REPEATABLE_READ,Oracle 是 READ_COMMITTED)。常見的隔離級(jí)別:

  • READ_UNCOMMITTED:讀未提交,可能出現(xiàn)臟讀、不可重復(fù)讀、幻讀。
  • READ_COMMITTED:讀已提交,避免臟讀,可能出現(xiàn)不可重復(fù)讀、幻讀。
  • REPEATABLE_READ:可重復(fù)讀,避免臟讀、不可重復(fù)讀,可能出現(xiàn)幻讀(MySQL 通過 MVCC 解決了幻讀,Oracle 沒有)。
  • SERIALIZABLE:串行化,最高隔離級(jí)別,避免所有并發(fā)問題,但性能最差。

在 MyBatis-Plus 中使用時(shí)要注意:

  • 高隔離級(jí)別會(huì)影響性能,別一拍腦袋就設(shè)為 SERIALIZABLE。
  • 如果業(yè)務(wù)中存在幻讀風(fēng)險(xiǎn)(比如統(tǒng)計(jì)數(shù)據(jù)時(shí)插入新數(shù)據(jù)),MySQL 可以用 REPEATABLE_READ,Oracle 需要用 SERIALIZABLE 或應(yīng)用層加鎖。

3.3 timeout:事務(wù)超時(shí)時(shí)間,別讓事務(wù)一直 "掛機(jī)"

默認(rèn)值是 - 1(永不超時(shí)),但在實(shí)際項(xiàng)目中,必須設(shè)置合理的超時(shí)時(shí)間,避免長(zhǎng)事務(wù)占用數(shù)據(jù)庫(kù)連接,導(dǎo)致連接池耗盡(這就是我之前踩的坑!)。比如:

@Transactional(timeout = 30) // 30秒超時(shí)
public void longRunningTransaction() {
    // 長(zhǎng)時(shí)間運(yùn)行的操作,比如批量插入、復(fù)雜計(jì)算
}

設(shè)置時(shí)要根據(jù)業(yè)務(wù)邏輯估算最長(zhǎng)執(zhí)行時(shí)間,預(yù)留一定緩沖,比如批量插入 10 萬(wàn)條數(shù)據(jù),測(cè)試發(fā)現(xiàn)平均 20 秒完成,超時(shí)時(shí)間可以設(shè) 30 秒。

3.4 readOnly:只讀事務(wù),提升性能的小技巧

如果方法只是查詢數(shù)據(jù),沒有寫操作,建議設(shè)置 readOnly=true:

@Transactional(readOnly = true)
public List<OrderEntity> listOrders() {
    return orderMapper.selectList(null);
}

這樣做有兩個(gè)好處:

  • 數(shù)據(jù)庫(kù)可以做優(yōu)化,比如 MySQL 在只讀事務(wù)中不記錄 binlog(如果開啟了 binlog_format=ROW)。
  • 提醒開發(fā)者這個(gè)方法是只讀的,避免誤操作添加寫邏輯。

四、MyBatis-Plus 事務(wù)最佳實(shí)踐:這樣寫才規(guī)范

4.1 事務(wù)范圍:能小則小,別搞 "大包圍"

很多新手喜歡在 Service 層的入口方法上加 @Transactional,把整個(gè)方法都包在事務(wù)里,包括日志記錄、參數(shù)校驗(yàn)、遠(yuǎn)程調(diào)用等和數(shù)據(jù)庫(kù)無(wú)關(guān)的操作。這會(huì)導(dǎo)致事務(wù)持續(xù)時(shí)間過長(zhǎng),增加鎖競(jìng)爭(zhēng)和超時(shí)風(fēng)險(xiǎn)。正確的做法是:

  • 事務(wù)只包裹真正的數(shù)據(jù)庫(kù)操作,無(wú)關(guān)邏輯放在事務(wù)外。
  • 批量操作時(shí),合理拆分批次,避免單次事務(wù)處理太多數(shù)據(jù)(比如每次處理 1000 條數(shù)據(jù),提交一次事務(wù))。

4.2 異常處理:別吞掉回滾的 "信號(hào)"

@Transactional(rollbackFor = Exception.class)
public void processOrder() {
    try {
        // 數(shù)據(jù)庫(kù)操作
        orderMapper.insert(order);
        // 模擬異常
        int i = 1 / 0;
    } catch (Exception e) {
        // 錯(cuò)誤做法:吞掉異常,不拋出去
        log.error("處理訂單失敗", e);
    }
}

這樣寫的后果是:事務(wù)不會(huì)回滾,因?yàn)?Spring 是根據(jù)方法是否拋異常來(lái)決定是否回滾的,異常被捕獲且沒有重新拋出,事務(wù)會(huì)認(rèn)為執(zhí)行成功,正常提交。正確的做法是:

@Transactional(rollbackFor = Exception.class)
public void processOrder() {
    try {
        orderMapper.insert(order);
        int i = 1 / 0;
    } catch (Exception e) {
        log.error("處理訂單失敗", e);
        // 必須重新拋出異常,或者調(diào)用TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        throw new RuntimeException("處理訂單失敗", e);
    }
}

或者在不需要處理異常的情況下,直接讓異常往上拋,別在事務(wù)方法內(nèi)捕獲后不處理。

4.3 字段更新:注意 MyBatis-Plus 的更新策略

MyBatis-Plus 的 updateById、update 等方法,默認(rèn)會(huì)更新所有非 null 字段,這在事務(wù)中可能導(dǎo)致意外結(jié)果。比如:

@Transactional(rollbackFor = Exception.class)
public void updateUserPartial(Long userId, UserEntity user) {
    // 假設(shè)user只有name字段有值,其他字段為null
    user.setId(userId);
    userMapper.updateById(user); // 會(huì)更新所有非null字段,包括name,其他字段不會(huì)被更新
}

這本來(lái)是正常行為,但如果在事務(wù)中,多個(gè)線程同時(shí)更新同一個(gè)用戶的不同字段,可能會(huì)出現(xiàn)更新順序問題。建議明確使用 updateWrapper,指定需要更新的字段:

userMapper.update(user, Wrappers.<UserEntity>update().set("name", user.getName()).eq("id", userId));

這樣更清晰,也避免因?qū)嶓w類字段變化導(dǎo)致的意外更新。

4.4 分布式場(chǎng)景:別把本地事務(wù)當(dāng)萬(wàn)能藥

前面提到的多數(shù)據(jù)源問題,本質(zhì)上是分布式事務(wù)范疇。在微服務(wù)架構(gòu)中,如果涉及跨服務(wù)、跨數(shù)據(jù)源的事務(wù),必須使用分布式事務(wù)解決方案。這里簡(jiǎn)單提一下常見方案:

  • TCC 模式(Try-Confirm-Cancel):適合強(qiáng)一致性場(chǎng)景,比如資金轉(zhuǎn)賬,實(shí)現(xiàn)復(fù)雜度高。
  • 可靠消息最終一致性:通過消息中間件保證事務(wù)最終一致,適合異步場(chǎng)景,比如訂單創(chuàng)建后通知庫(kù)存服務(wù)扣庫(kù)存。
  • Seata 框架:阿里巴巴開源的分布式事務(wù)解決方案,支持 AT 模式(自動(dòng)生成回滾日志)、TCC 模式等,和 Spring Cloud 集成良好。

4.5 監(jiān)控和日志:讓事務(wù)問題無(wú)處遁形

  • 開啟事務(wù)日志:在 application.properties 中配置:
# Spring事務(wù)日志
logging.level.org.springframework.transaction=DEBUG
# MyBatis-Plus執(zhí)行日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

這樣可以在控制臺(tái)看到事務(wù)的開啟、提交、回滾過程,以及執(zhí)行的 SQL 語(yǔ)句,方便排查問題。

  • 自定義注解和切面:可以寫一個(gè) @TransactionalLog 注解,結(jié)合 AOP 記錄事務(wù)方法的執(zhí)行時(shí)間、參數(shù)、異常信息等,幫助監(jiān)控事務(wù)性能和異常情況。

五、總結(jié):用好 @Transactional 的 "三板斧"

  1. 搞懂原理:知道 Spring 事務(wù)是怎么通過 AOP 代理和數(shù)據(jù)庫(kù)連接綁定實(shí)現(xiàn)的,明白 MyBatis-Plus 只是 ORM 工具,事務(wù)管理靠 Spring。
  2. 避開陷阱:記住同類方法調(diào)用、多數(shù)據(jù)源、枚舉類型、序列化這些常見坑,寫代碼時(shí)多檢查。
  3. 規(guī)范使用:合理設(shè)置事務(wù)傳播行為、隔離級(jí)別、超時(shí)時(shí)間,控制事務(wù)范圍,做好異常處理,分布式場(chǎng)景用對(duì)方案。
責(zé)任編輯:武曉燕 來(lái)源: 石杉的架構(gòu)筆記
相關(guān)推薦

2024-12-20 16:49:15

MyBatis開發(fā)代碼

2023-10-31 08:01:48

Mybatis參數(shù)jdbcurl?

2023-06-07 08:08:37

MybatisSpringBoot

2025-02-27 09:45:47

2023-06-14 08:34:18

Mybatis死鎖框架

2023-07-29 22:02:06

MyBatis數(shù)據(jù)庫(kù)配置

2023-06-07 08:00:00

MySQL批量插入

2024-07-31 09:56:20

2025-05-26 03:20:00

SpringMyBatis數(shù)據(jù)權(quán)限

2024-11-28 19:03:56

2024-02-28 09:35:52

2023-01-12 09:13:49

Mybatis數(shù)據(jù)庫(kù)

2023-01-17 09:13:08

Mybatis后端框架

2025-02-13 07:59:13

2025-02-06 07:45:44

2021-09-29 08:23:56

庫(kù)項(xiàng)目css

2022-05-20 12:24:45

分庫(kù)分表Java依賴

2023-03-27 07:39:07

內(nèi)存溢出優(yōu)化

2023-05-14 22:25:33

內(nèi)存CPU

2018-10-19 16:35:20

運(yùn)維
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)