在异步执行的过程中,对数据库进行操作,同时确保在主线程和异步线程中捕获并处理异常,以保障事务的一致性。
[方法一]
public void a1() {
try {
studentMapper.insertA();
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
// 获取当前线程的事务状态
TransactionStatus subStatus = TransactionAspectSupport.currentTransactionStatus();
try {
b();
c();
} catch (Exception e) {
System.err.println("线程异常");
subStatus.setRollbackOnly();
// 使用 completeExceptionally 将异常传播到 CompletableFuture 中
CompletableFuture<Void> exceptionallyCompleted = new CompletableFuture<>();
exceptionallyCompleted.completeExceptionally(e);
throw new CompletionException(e);
}
});
// 使用 join 方法等待异步任务的完成,并检查是否有异常
future.join();
} catch (Exception e) {
System.err.println("主线程异常");
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
}
如果b和c都是第三方接口,那就需要依赖返回的状态码去处理逻辑
public void a1() {
try {
studentMapper.insertA();
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
// 获取当前线程的事务状态
TransactionStatus subStatus = TransactionAspectSupport.currentTransactionStatus();
try {
if (!b()) {
throw new BusinessException("调用第三方服务b失败");
}
if (!c()) {
throw new BusinessException("调用第三方服务c失败");
}
} catch (BusinessException e) {
System.err.println("线程异常: " + e.getMessage());
subStatus.setRollbackOnly();
throw new CompletionException(e);
}
});
// 使用 join 方法等待异步任务的完成,并检查是否有异常
future.join();
} catch (Exception e) {
System.err.println("主线程异常");
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
}
private boolean b() {
// 调用第三方服务b,根据返回值判断是否成功
// 返回 true 表示成功,返回 false 表示失败
return true; // or false
}
private boolean c() {
// 调用第三方服务c,根据返回值判断是否成功
// 返回 true 表示成功,返回 false 表示失败
return true; // or false
}
[方法二]
public class BatchApplicationTest {
@Autowired
private UserMapper userMapper;
@Autowired
private PlatformTransactionManager transactionManager;
@Test
public void test() throws Exception {
List<User> userList = buildUser();
ExecutorService executorService = Executors.newFixedThreadPool(userList.size());
CountDownLatch countDownLatch = new CountDownLatch(userList.size());
//创建线程屏障对象,让所有线程执行完成后处于同一个等待状态,也可以使用
CyclicBarrier cyclicBarrier = new CyclicBarrier(userList.size());
int i = 0;
//用于统计是否存在失败线程
AtomicBoolean flag = new AtomicBoolean(true);
for (; i < userList.size(); i++) {
final Integer tmp = i;
executorService.submit(() -> {
TransactionStatus status = null;
try {
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
// 事物隔离级别,开启新事务,这样会比较安全些。
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
status = transactionManager.getTransaction(def);
//随机设置一个线程失败并抛出异常;注释掉会全部完成,否则全部回滚
// if (tmp == userList.size() - 1) {
// throw new RuntimeException();
// }
// 具体的数据处理类
userMapper.insert(userList.get(tmp));
} catch (Exception e) {
//如果出现异常就设置失败标志位
flag.set(false);
e.printStackTrace();
} finally {
try {
//让所有线程都处于等待状态,不管成功还是失败
cyclicBarrier.await();
System.out.println("---" + flag.get());
//根据标志位判断事务最终执行状态
if (flag.get()) {
transactionManager.commit(status);
} else {
transactionManager.rollback(status);
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
countDownLatch.countDown();
}
});
}
//主线程等待最终结果
countDownLatch.await();
System.out.println("--end--");
}
}
方法一:使用 CompletableFuture 和 TransactionAspectSupport
优势:
简化事务管理: 利用 Spring 的 TransactionAspectSupport 简化了事务管理,通过捕获异常并设置回滚标志,避免了手动处理事务的复杂性。
异步处理: 使用 CompletableFuture 可以更方便地进行异步处理,通过 join 方法等待异步任务完成。
劣势:
固定的线程池: 使用 CompletableFuture.runAsync 默认使用 ForkJoinPool.commonPool(),线程池是固定的,不能灵活地控制线程数。
不可控的等待机制: 由于 CompletableFuture 不提供类似 CountDownLatch 的等待机制,可能需要额外的手段来实现主线程等待所有异步任务完成。
方法二:使用线程池和 CountDownLatch
优势:
粒度控制: 通过自定义线程池,可以更灵活地控制并发执行的线程数,适应不同的硬件资源和性能需求。
可控的等待机制: 使用 CountDownLatch 和 CyclicBarrier 可以实现主线程等待所有子线程完成后再继续执行的机制,使得主线程可以在所有子线程完成后进行一些后续操作。
劣势:
手动事务管理: 在每个线程中手动处理事务,包括开启新事务、提交或回滚事务。这增加了代码的复杂性,容易出错。
线程间通信: 使用共享的 AtomicBoolean 标志位进行线程间通信,可能存在并发问题,需要额外的注意和保护。
综合考虑:
如果对并发度有更灵活的要求,且需要更细粒度的线程控制,方法一可能更适合。
如果更注重简化事务管理,且异步处理更符合需求,方法二可能更适合。