问题发生的原因:
1、为什么多线程情况下,事务注解会失效呢?
首先最核心的地方在于主线程和子线程都是不同的线程,他们之间都有自己的threadlocal,里面存放了数据库连接的核心操作。
在TransactionSynchronizationManager这个类里面可以看到。
存放了自己的local,然后resource就是存放了数据库连接的。
private static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal("Transactional resources");
private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations = new NamedThreadLocal("Transaction synchronizations");
private static final ThreadLocal<String> currentTransactionName = new NamedThreadLocal("Current transaction name");
private static final ThreadLocal<Boolean> currentTransactionReadOnly = new NamedThreadLocal("Current transaction read-only status");
private static final ThreadLocal<Integer> currentTransactionIsolationLevel = new NamedThreadLocal("Current transaction isolation level");
private static final ThreadLocal<Boolean> actualTransactionActive = new NamedThreadLocal("Actual transaction active");
注解失效是因为这个注解里面的连接池只管理到这个主线程里面的,没有管到子线程,如果让子线程里面的数据库连接也使用主线程的那么此时如果主线程进行回滚,就能一并回滚子线程的事务
二、代码处理
第一步获取到主线程里面的数据库连接,然后子线程里面绑定主线程的连接, 使用完之后要记得解绑。
第二步使用异步线程,可以使用completableFuture,也可以用thread来处理,使用completableFuture就可以直接用join方法,把异常拿到主线程里面,让主线程去发现,往外抛,使用thread也是同理,setUncaughtExceptionHandler来把thread里面的异常拿到外面定义的变量,然后判断是否有发生异常,在手动抛出,这两个是一个意思。
核心要点就是,是用主线程的数据库连接,并且把子线程里面的异常拿到外面主线程去往外抛。
@Override
@Transactional
public void testTransactional() throws SQLException, ExecutionException, InterruptedException {
// 主线程获取数据库连接资源 让这里的子线程也获取同一个数据库资源
ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
TCustomer tCustomer = new TCustomer();
tCustomer.setSex(1);
tCustomer.setAge(5);
tCustomer.setPhone("12322222222");
/*CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
// 子线程绑定
TransactionSynchronizationManager.bindResource(dataSource, conHolder);
tCustomer.setCname("异步线程插入的");
tCustomer.setId(new Random().nextInt(1000));
customerMapper.insert(tCustomer);
//int a = 1 / 0;
// 此处绑定完要解绑
TransactionSynchronizationManager.unbindResource(dataSource);
});*/
final Throwable[] throwable = {null};
// 通过普通线程来处理,然后核心也是把异常往主线程里面拿
Thread thread = new Thread(() -> {
// 子线程绑定
TransactionSynchronizationManager.bindResource(dataSource, conHolder);
tCustomer.setCname("异步线程插入的");
tCustomer.setId(new Random().nextInt(1000));
customerMapper.insert(tCustomer);
int a = 1 / 0;
// 此处绑定完要解绑
TransactionSynchronizationManager.unbindResource(dataSource);
});
thread.start();
thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println(e);
throwable[0] = e;
}
});
TCustomer tCustomer1 = new TCustomer();
tCustomer1.setId(new Random().nextInt(100000));
tCustomer1.setCname("主线程插入的");
tCustomer1.setSex(1);
tCustomer1.setAge(5);
tCustomer1.setPhone("12322222222");
customerMapper.insert(tCustomer1);
/*jdbcTemplatel.update("insert into persion (id,name) values (?,?)",
new Object[]{new Random().nextInt(1000), Thread.currentThread().getName()});*/
// int a = 1 / 0;
//future.join();
// 让子线程先走完才能完成赋值操作
thread.join();
if (throwable[0]!= null){
throw new RuntimeException(throwable[0]);
}
}