Spring 的声明式事务在多线程的场景当中会失效,该怎么解决呢?

Spring声明式事务在多线程中失效,核心原因是事务上下文(ThreadLocal绑定)无法跨线程传递,导致子线程无法继承主线程的事务状态。解决需围绕"确保事务上下文共享 "或"避免跨线程事务依赖"展开,具体方案如下:

1. 方案一:手动管理事务(编程式事务)

放弃声明式事务的注解(@Transactional),通过PlatformTransactionManager手动控制事务边界,主动将事务上下文传递到子线程。

核心逻辑:主线程开启事务后,将TransactionStatus对象传递给子线程,子线程通过事务管理器手动提交/回滚。

代码示例:

scss 复制代码
public class PlatformTransactionMangerDemo {
    //手动事务管理
    @Autowired
    private PlatformTransactionManager transactionManager;

    public void doTask() {
        DefaultTransactionDefinition def = new DefaultTransactionDefinition();
        // 注意:在实际应用中,PlatformTransactionManager 通常是由 Spring 容器自动注入的
        TransactionStatus status = transactionManager.getTransaction(def);
        CountDownLatch latch = new CountDownLatch(1); // 用于等待子线程完成

        try {
            //子线程执行任务,传入事务状态
            new Thread(() -> {
                try {
                    // 模拟一些操作
                    System.out.println("子线程正在执行任务...");
                    latch.countDown(); // 子线程任务开始,通知主线程
                    doSonTask(); // 执行子线程任务
                    System.out.println("子线程任务完成");
                } catch (Exception e) {

                    // 如果子线程被中断,抛出异常
//                    throw new RuntimeException("子线程执行异常", e);
                    transactionManager.rollback(status); // 回滚事务
                }
            }).start();
            // 主线程等待子线程完成
            latch.await();
            System.out.println("主线程等待子线程完成");
            //主线程业务逻辑
            doMianTask();
            System.out.println("主线程任务完成");
            // 如果操作成功,提交事务
            transactionManager.commit(status);
        } catch (Exception e) {
            // 如果发生异常,回滚事务
            transactionManager.rollback(status);
        }
    }

    private void doMianTask() {
//        System.out.println("主线程任务完成");
        // 模拟主线程任务执行
        try {
            Thread.sleep(1000); // 模拟耗时操作
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt(); // 恢复中断状态
            System.out.println("主线程被中断");
        }
        System.out.println("主线程任务完成");
    }

    private void doSonTask() {
        System.out.println("子线程任务完成");
        // 模拟子线程任务执行
        try {
            Thread.sleep(1000); // 模拟耗时操作
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt(); // 恢复中断状态
            System.out.println("子线程被中断");
        }
    }

    public static void main(String[] args) {
        PlatformTransactionMangerDemo demo = new PlatformTransactionMangerDemo();
        demo.doTask(); // 执行任务
    }

}

注意:需确保主线程等待子线程执行完成(如用CountDownLatch),否则主线程提前提交/回滚会导致子线程事务失效。

2. 方案二:使用ThreadLocal传递事务上下文(谨慎使用)

Spring事务默认通过TransactionSynchronizationManager(基于ThreadLocal)存储上下文,可手动将主线程的上下文复制到子线程。

核心步骤:

1.主线程在开启事务后,获取TransactionSynchronizationManager中的上下文(如事务连接、事务状态)。

2.子线程启动前,将主线程的上下文手动设置到子线程的ThreadLocal中。

3.子线程执行完毕后,清除子线程的ThreadLocal(避免内存泄漏)。

风险提示:

  • 破坏Spring事务的线程隔离设计,可能导致连接泄漏、事务状态混乱。

  • 仅适用于简单场景(如单数据源、无嵌套事务),不推荐生产环境使用。

3. 方案三:重构业务逻辑,避免跨线程事务依赖

这是最推荐的方案------从设计层面消除"多线程共享事务"的需求,通过业务拆分避免事务跨线程

常见拆分思路:

  • 单线程事务+异步通知:主线程先完成核心事务(如数据入库),子线程通过异步任务(如@Async)处理非核心逻辑(如日志、通知),两者无需共享事务。

  • 分布式事务:若多线程需操作不同数据源/服务,改用分布式事务框架(如Seata、Saga),而非依赖本地事务跨线程。

  • 状态标记+补偿机制:多线程任务通过"状态字段"(如task_status)标记执行结果,主线程根据所有子线程的状态,统一进行提交/回滚(或补偿操作)。

代码示例: 实体类:

vbnet 复制代码
@Data
public class Order {
    private Long orderId;
    private String userId;
    private String productId;
    private int quantity;
    private String status;
}

dao 层:

kotlin 复制代码
@Repository
public class OrderDao {
    //模拟数据库插入动作
    public Long insertOrder(Order order) {
        // 模拟数据库插入操作
        Long orderId = System.currentTimeMillis(); // 使用当前时间戳作为订单ID
        order.setOrderId(orderId);
        System.out.println("【dao】订单已插入到数据库,订单消息: " + order);
        return orderId;
    }
}

service层:

OrderService //模拟订单的核心逻辑

kotlin 复制代码
@Service
public class OrderService {
    //模拟核心业务逻辑
    private final OrderDao orderDao;
    //注入异步通知
    private final AsyncNotificationService asyncNotificationService;

    //构造器注入
    @Autowired
    public OrderService(OrderDao orderDao, AsyncNotificationService asyncNotificationService) {
        this.orderDao = orderDao;
        this.asyncNotificationService = asyncNotificationService;
    }

    /**
     * 核心业务创建订单(单线程事务)
     */
    @Transactional(rollbackFor = Exception.class)
    public Long createOrder(String userId, String productId, int quantity) {

        try {
            // 创建订单对象
            Order order = new Order();
            order.setUserId(userId);
            order.setProductId(productId);
            order.setQuantity(quantity);
            order.setStatus("已创建");
//            int a = 1/0; // 模拟异常,测试事务回滚

            // 插入订单到数据库
            Long orderId = orderDao.insertOrder(order);
            System.out.println("【主线程事务】订单创建成功,订单 ID: " + orderId );
            // 异步发送通知

            asyncNotificationService.sendOrderNotification(orderId, userId);
            return orderId;
        } catch (Exception e) {
            // 处理异常,可能是记录日志或抛出自定义异常
            System.err.println("【主线程事务】创建订单失败, 事务回滚 错误信息: " + e.getMessage());
            throw e; // 重新抛出异常以触发事务回滚
        }
    }
}

//开启异步支持

less 复制代码
@EnableAsync
@Configuration
public class AsyncConfig {
}

异步处理非核心,无事务依赖的操作

kotlin 复制代码
@Service
public class AsyncNotificationService {
    // 发送通知
    @Async
    public void sendOrderNotification(Long orderId, String userId) {
        try {
            // 模拟发送通知的逻辑
            System.out.println("【异步线程】开始发送订单通知, 订单 ID: " + orderId + " 用户 ID: " + userId);
            sleep(1000); // 模拟耗时操作
            //模拟异步线程失败
            //int b = 1/0; // 模拟异常,测试异步线程失败
            System.out.println("【异步线程】发送订单成功,订单 ID: " + orderId );
        } catch (InterruptedException e) {
            System.err.println("【异步线程】发送订单通知失败,订单 ID: " + orderId + ",错误信息: " + e.getMessage());
        }


    }
}

//进行测试

java 复制代码
@Component
public class OrderTestRunner implements CommandLineRunner {
    private final OrderService orderService;

    // 构造器注入 OrderService
    public OrderTestRunner(OrderService orderService) {
        this.orderService = orderService;
    }
    @Override
    public void run(String... args) throws Exception {
        //测试创建订单 核心操作入库在事务中,通知在异步线程执行
        orderService.createOrder("user123", "product456", 2);
        //主线程等待2秒
        try {
            Thread.sleep(2000); // 等待异步操作完成
            System.out.println("【主线程】等待完成,异步操作可能已执行完毕。");
        } catch (InterruptedException e) {
            System.err.println("【主线程】等待中断,错误信息: " + e.getMessage());
        }
    }
}

启动application:

typescript 复制代码
@SpringBootApplication
public class RedisDataDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(RedisDataDemoApplication.class, args);
    }
}

运行后结果:

yaml 复制代码
【dao】订单已插入到数据库,订单消息: Order(orderId=1755825346090, userId=user123, productId=product456, quantity=2, status=已创建)
【主线程事务】订单创建成功,订单 ID: 1755825346090
【异步线程】开始发送订单通知, 订单 ID: 1755825346090 用户 ID: user123
【异步线程】发送订单成功,订单 ID: 1755825346090
【主线程】等待完成,异步操作可能已执行完毕。

总结

  • 优先选择方案三(重构业务),从根源避免问题,保证系统稳定性。

  • 若必须跨线程共享事务,短期可尝试方案一(编程式事务),需严格控制线程同步(如等待子线程完成)。

  • 方案二(手动传递ThreadLocal) 风险高,仅作为临时调试手段,不推荐生产使用。

相关推荐
大鸡腿同学2 小时前
【成长类】《只有偏执狂才能生存》读书笔记:程序员的偏执型成长地图
后端
0xDevNull2 小时前
MySQL数据冷热分离详解
后端·mysql
一定要AK2 小时前
Spring 入门核心笔记
java·笔记·spring
AI袋鼠帝2 小时前
OpenClaw(龙虾)最强开源对手!Github 40K Star了,又一个爆火的Agent..
后端
凯尔萨厮2 小时前
创建SpringWeb项目(Spring2.0)
spring·mvc·mybatis
哈里谢顿2 小时前
如何实现分布式锁
面试
新知图书3 小时前
搭建Spring Boot开发环境
java·spring boot·后端
宸津-代码粉碎机3 小时前
Spring Boot 4.0虚拟线程实战调优技巧,最大化发挥并发优势
java·人工智能·spring boot·后端·python
小码哥_常4 小时前
一个Starter搞定六种防护,Spring Boot API的超强护盾来了
后端
白露与泡影5 小时前
Java面试题库及答案解析(2026版)
java·开发语言·面试