我们开发的时候常常会遇到多线程事务的问题。以为添加了@Transactional注解就行了,其实你加了注解之后会发现事务失效。原因:数据库连接spring是放在threadLocal里面,多线程场景下,拿到的数据库连接是不一样的,即是属于不同事务。
Spring支持编程式事务和声明式事务,Spring提供的最原始的事务管理方式是基于TransactionDefinition、PlatformTransactionManager、TransactionStatus 编程式事务。
TransactionTemplate的事务管理是使用模板方法设计模式对原始事务管理方式的封装,因此我们也可以用TransactionTemplate管理事务。
今天我们就来谈下单个线程开启事务的方案:
1. 直接把线程方法单独提出,添加@Transactional注解
// 开启多线程的方法
public void testTransactional() throws ExecutionException, InterruptedException {
int inter = 3;
ExecutorService executorService = Executors.newFixedThreadPool(inter);
ThreadPoolExecutor poolExecutor = (ThreadPoolExecutor) executorService;
FutureTask[] integerFuture = new FutureTask[inter];
for (int i = 0; i < inter; i++) {
int finalI = i + 1;
integerFuture[i] = new FutureTask<>(() -> {
testTransactionalServiceZi.testTransactionalZi(finalI, "计算" + finalI);
System.out.println("多线程运行了---" + finalI);
return "业务执行成功";
});
poolExecutor.execute(integerFuture[i]);
}
for (int i = 0; i < inter; i++) {
System.out.println("integerFuture返回结果 = " + i + "==" + integerFuture[i].get());
}
}
// 单个线程调用,添加注解
@Transactional
public void testTransactionalZi(int num, String str) {
// 修改数据===1===
AdCourse adCourse = new AdCourse();
adCourse.setId(num);
adCourse.setName(str);
int i = adCourseMapper.updateById(adCourse);
// 修改数据===2===
adCourse.setId(num + 3);
adCourse.setName(str + 0);
int result = adCourseMapper.updateById(adCourse);
// 手动制造异常(事务会生效)
result = 1/0;
}
2. 用TransactionTemplate管理事务
通过对源码的解读,TransactionTemplate封装的是PlatformTransactionManager。我们自己手写PlatformTransactionManager管理事务比较麻烦,所以通过TransactionTemplate来进行展示代码,如下:
@Service
public class TestTransactionalServiceImpl implements TestTransactionalService {
@Autowired
private TestTransactionalServiceZi testTransactionalServiceZi;
// 使用Template控制事务
@Autowired
private TransactionTemplate template;
public void testTransactional() throws ExecutionException, InterruptedException {
int inter = 3;
ExecutorService executorService = Executors.newFixedThreadPool(inter);
ThreadPoolExecutor poolExecutor = (ThreadPoolExecutor) executorService;
FutureTask[] integerFuture = new FutureTask[inter];
for (int i = 0; i < inter; i++) {
int finalI = i + 1;
integerFuture[i] = new FutureTask<>(() -> {
return template.execute(new TransactionCallback<String>() {
@Override
public String doInTransaction(TransactionStatus transactionStatus) {
testTransactionalServiceZi.testTransactionalZi(finalI, "计算666" + finalI);
System.out.println("多线程运行了---" + finalI);
return "业务执行成功";
}
});
});
poolExecutor.execute(integerFuture[i]);
}
for (int i = 0; i < inter; i++) {
System.out.println("integerFuture返回结果 = " + i + "==" + integerFuture[i].get());
}
}
}
// 需要调用的方法
public void testTransactionalZi(int num, String str) {
// 修改数据===1===
AdCourse adCourse = new AdCourse();
adCourse.setId(num);
adCourse.setName(str);
int i = adCourseMapper.updateById(adCourse);
// 修改数据===2===
adCourse.setId(num + 3);
adCourse.setName(str + 0);
int result = adCourseMapper.updateById(adCourse);
// 手动制造异常(事务会生效)
result = 1/(num-1);
}
打印结果:第一个线程没有执行成功,出现了异常,数据也没有改变。
总结:以上两种方法可以保证多线程中单个线程事务的问题