/**
* 首先 并行处理的数据必须保存在一个事务之内,会基于Spring进行管理
*/
@Transactional
public Long save(Request request, Long masterId) {
// 1. 保存/更新主表
Long id = tbDomainService.saveOrUpdateMain(mainEntity);
// 2. 批量保存所有明细表
batchSaveAllDetails(request,id);
}
public void batchSaveAllDetails(Request request,Long id) {
// 失败信号量:原子类保证线程安全,标记是否有任务失败
AtomicBoolean hasFailed = new AtomicBoolean(false);
// 存储异常信息:捕获第一个失败的异常(避免后续异常覆盖)
AtomicReference<Throwable> failureException = new AtomicReference<>();
List<CompletableFuture<Void>> futures = new ArrayList<>();
futures.add(CompletableFuture.runAsync(() -> {
if (hasFailed.get()) return;
try {
batchPlanDetails(request, id);
} catch (Throwable t) {
handleFailure(hasFailed, failureException, t);
}
}));
futures.add(CompletableFuture.runAsync(() -> {
if (hasFailed.get()) return;
try {
batchDetails(request, id);
} catch (Throwable t) {
handleFailure(hasFailed, failureException, t);
}
}));
// 等待所有无依赖任务完成(无论成功/失败)
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
// 检查是否有任务失败,有则抛出异常触发事务回滚
if (hasFailed.get()) {
Throwable cause = failureException.get();
if (cause instanceof UdpNotifyAppException) {
throw (UdpNotifyAppException) cause;
} else {
throw new UdpNotifyAppException("明细表处理失败", cause);
}
}
}
/**
* 处理任务失败:标记信号量+存储异常(确保只记录第一个失败的异常)
*/
private void handleFailure(AtomicBoolean hasFailed, AtomicReference<Throwable> failureException, Throwable t) {
// compareAndSet:保证只有第一个失败的任务会设置异常(避免后续异常覆盖)
if (hasFailed.compareAndSet(false, true)) {
failureException.set(t);
}
// 抛出异常,让 CompletableFuture 标记为失败状态(不影响信号量逻辑)
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new RuntimeException(t);
}
}
// 具体的明细处理内容
private void batchPlanDetails(Request request,Long id) {
}
private void batchDetails(Request request,Long id) {
}
-
所有
CompletableFuture.runAsync()虽然在不同线程执行,但 Spring 的事务是通过 ThreadLocal 管理的 -
在同一个事务方法内,即使跨线程,数据库操作仍然使用同一个数据库连接
-
任何线程的异常都会传播到主线程,触发事务回滚
-
AtomicBoolean让其他任务快速跳过 -
确保所有异步任务都使用相同的数据库连接池配置,避免连接泄露。
1050

被折叠的 条评论
为什么被折叠?



