SpringBoot @Async 异步任务与 @Transactional 事务实战
📅 2026/7/13 23:20:15
👁️ 阅读次数
📝 编程学习
异步任务和事务管理是 SpringBoot 中两个非常重要但又容易出错的特性。这篇把它们的核心用法和常见坑一次讲清楚。
一、@Async 异步任务
1. 开启异步
@SpringBootApplication@EnableAsync// 开启异步任务publicclassSeckillApplication{publicstaticvoidmain(String[]args){SpringApplication.run(SeckillApplication.class,args);}}2. 配置线程池
@ConfigurationpublicclassAsyncConfig{@Bean("taskExecutor")publicExecutortaskExecutor(){ThreadPoolTaskExecutorexecutor=newThreadPoolTaskExecutor();executor.setCorePoolSize(5);// 核心线程数executor.setMaxPoolSize(10);// 最大线程数executor.setQueueCapacity(100);// 队列容量executor.setKeepAliveSeconds(60);// 空闲线程存活时间executor.setThreadNamePrefix("async-");executor.setWaitForTasksToCompleteOnShutdown(true);executor.setRejectedExecutionHandler(newThreadPoolExecutor.CallerRunsPolicy());executor.initialize();returnexecutor;}}3. 使用 @Async
@ServicepublicclassSeckillAsyncService{privatestaticfinalLoggerlog=LoggerFactory.getLogger(SeckillAsyncService.class);/** * 异步发送通知 */@Async("taskExecutor")publicvoidsendNotification(LonguserId,Stringmessage){log.info("异步发送通知给用户 {}, 线程: {}",userId,Thread.currentThread().getName());// 模拟耗时操作try{Thread.sleep(2000);}catch(InterruptedExceptione){Thread.currentThread().interrupt();}log.info("通知发送完成");}/** * 异步记录日志 */@Async("taskExecutor")publicvoidrecordLog(Stringoperation,Stringdetail){log.info("记录操作日志: {} - {}",operation,detail);}}4. @Async 的使用场景
@ServicepublicclassOrderService{@AutowiredprivateSeckillAsyncServiceasyncService;publicOrdercreateOrder(OrderDTOdto){// 1. 主流程:同步创建订单Orderorder=doCreateOrder(dto);// 2. 副流程:异步执行,不影响主流程asyncService.sendNotification(order.getUserId(),"下单成功");asyncService.recordLog("createOrder",order.getOrderNo());returnorder;}}5. @Async 的返回值
/** * 异步方法可以返回 void 或 Future * 如果需要获取异步结果,使用 Future */@Async("taskExecutor")publicFuture<String>processAsync(LongorderId){try{Thread.sleep(3000);returnAsyncResult.forResult("处理完成: "+orderId);}catch(InterruptedExceptione){returnAsyncResult.forExecutionException(e);}}// 调用Future<String>future=asyncService.processAsync(orderId);Stringresult=future.get();// 阻塞等待结果二、@Transactional 事务
1. 事务的 ACID 特性
原子性(Atomicity):事务中的所有操作要么全成功,要么全回滚 一致性(Consistency):事务前后数据保持一致 隔离性(Isolation):并发事务互不干扰 持久性(Durability):提交后数据永久保存2. 基本使用
@ServicepublicclassOrderServiceImplextendsServiceImpl<OrderMapper,Order>implementsOrderService{/** * 创建订单 + 扣减库存 * 两个操作必须在一个事务中,要么都成功,要么都失败 */@Override@Transactional(rollbackFor=Exception.class)publicOrdercreateOrder(OrderDTOdto){// 1. 创建订单Orderorder=newOrder();order.setUserId(dto.getUserId());order.setProductId(dto.getProductId());order.setAmount(dto.getAmount());save(order);// 2. 扣减库存productService.deductStock(dto.getProductId(),1);returnorder;}}3. 事务传播行为
/** * 事务传播行为:当一个事务方法调用另一个事务方法时,如何处理 */@ServicepublicclassPayService{/** * REQUIRED(默认):支持当前事务,如果不存在则创建新事务 * SUPPORTS:支持当前事务,如果不存在则以非事务方式执行 * REQUIRES_NEW:创建新事务,挂起当前事务 * NESTED:嵌套事务(JDBC 的 Savepoint 机制) * NEVER:以非事务方式执行,如果有事务则抛异常 */@Transactional(propagation=Propagation.REQUIRES_NEW)publicvoidprocessPayment(LongorderId){// 支付处理,独立于调用方的事务}@Transactional(propagation=Propagation.REQUIRES_NEW)publicvoidrefundPayment(LongorderId){// 退款处理,即使主事务回滚也提交}}4. 事务隔离级别
/** * 事务隔离级别解决并发问题: * 脏读:读到未提交的数据 * 不可重复读:同一事务中两次读到不同数据 * 幻读:同一事务中读到新增的数据 */@ServicepublicclassInventoryService{@Transactional(isolation=Isolation.READ_COMMITTED)publicvoidupdateStock(LongproductId,intquantity){// READ_COMMITTED:MySQL 默认级别,解决脏读// REPEATABLE_READ:解决脏读 + 不可重复读// SERIALIZABLE:解决所有并发问题(性能最差)}}| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| READ UNCOMMITTED | ✅ | ✅ | ✅ |
| READ COMMITTED | ❌ | ✅ | ✅ |
| REPEATABLE READ | ❌ | ❌ | ✅ |
| SERIALIZABLE | ❌ | ❌ | ❌ |
三、@Async + @Transactional 组合
1. 异步方法中的事务
/** * 异步方法中的 @Transactional 是独立的 * 调用方的异常不会导致异步方法回滚 */@ServicepublicclassAsyncTransactionService{@Async("taskExecutor")@Transactional(rollbackFor=Exception.class)publicvoidasyncWithTransaction(LongorderId){// 这个事务和调用方的事务是独立的orderMapper.updateStatus(orderId,1);}}2. 注意事项
/** * ❌ 错误:同一类中的方法调用 @Async 不生效 */@ServicepublicclassOrderService{publicvoidcreateOrder(){// 这里调用的 asyncMethod 不会异步执行// 因为 Spring 的代理机制只在外部调用时生效this.asyncMethod();}@AsyncpublicvoidasyncMethod(){// 不会被异步执行!}}/** * ✅ 正确:注入自身代理 */@ServicepublicclassOrderService{@Autowired@LazyprivateOrderServiceself;publicvoidcreateOrder(){self.asyncMethod();// 通过代理调用,异步生效}@AsyncpublicvoidasyncMethod(){// 异步执行}}四、秒杀系统中的事务应用
1. 秒杀下单事务
@ServicepublicclassSeckillService{@Override@Transactional(rollbackFor=Exception.class)publicOrderdoSeckill(LongproductId,LonguserId){// 1. 校验库存Productproduct=productService.getById(productId);if(product.getStock()<=0){thrownewBusinessException(400,"库存不足");}// 2. 扣减库存(乐观锁)booleansuccess=productService.deductStock(productId);if(!success){thrownewBusinessException(400,"库存不足");}// 3. 创建订单Orderorder=newOrder();order.setUserId(userId);order.setProductId(productId);order.setStatus(0);save(order);returnorder;}}2. 异步处理超时订单
@ServicepublicclassOrderTimeoutService{@Async("taskExecutor")@Transactional(rollbackFor=Exception.class)publicvoidcancelTimeoutOrder(LongorderId){// 1. 取消订单Orderorder=getById(orderId);if(order==null||order.getStatus()!=0){return;}order.setStatus(2);// 已取消updateById(order);// 2. 归还库存productService.restoreStock(order.getProductId(),order.getQuantity());}}五、常见问题
1. 事务不回滚
// ❌ 错误:没有指定 rollbackFor@TransactionalpublicvoidcreateOrder(){save(order);thrownewRuntimeException("失败");// RuntimeException 默认回滚thrownewBusinessException("失败");// 自定义异常默认不回滚!}// ✅ 正确:指定 rollbackFor@Transactional(rollbackFor=Exception.class)publicvoidcreateOrder(){save(order);thrownewBusinessException("失败");// 回滚}2. 异步方法中的异常处理
@Async("taskExecutor")publicvoidprocessAsync(){try{// 业务逻辑}catch(Exceptione){// 异步方法的异常不会被调用方捕获// 必须自己处理log.error("异步处理失败",e);}}3. 事务与锁的顺序
/** * 获取锁 → 开启事务 → 执行业务 → 提交事务 → 释放锁 * * 不能先释放锁再提交事务,否则其他线程会读到未提交的数据 */publicvoidseckillWithLock(LongproductId,LonguserId){StringlockKey="lock:seckill:"+productId;StringlockValue=UUID.randomUUID().toString();try{// 1. 获取锁booleanlocked=redisLock.lock(lockKey,lockValue,10);if(!locked){thrownewBusinessException(400,"操作太频繁");}// 2. 事务操作seckillService.doSeckill(productId,userId);// 3. 释放锁(在事务提交之后)}finally{redisLock.unlock(lockKey,lockValue);}}总结
@Async 用于: 耗时操作(发送邮件、短信通知、生成报表) 不依赖主流程结果的副流程(日志记录、数据分析) 实时性要求不高的批量处理 @Transactional 用于: 多表操作(订单+库存、用户+积分) 需要原子性保证的业务(支付、退款、转账)💡 觉得有用的话,点赞 + 关注【张老师技术栈】吧!每周更新 Java/Python/爬虫 实战干货,不让你白来。
编程学习
技术分享
实战经验