最近有个需求: 大批量插入数据,多个线程的事物需要保证事务的四大特性,若有一个线程出现异常,所有线程都需要回滚事物,若都成功,则提交事物,现在整理了一下,分享给大家
多线程事物示例
- pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>31.1-jre</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
- 实体类Dog
@Data
@Entity
@Table(name = "dog")
public class Dog {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long dogId;
private String dogName;
}
- repository
@Repository
public interface DogRepository extends JpaRepository<Dog,Long> {
}
- 懒得写service,所以所有代码直接放controller
@Slf4j
@RestController
@RequiredArgsConstructor
public class DogController {
private final DogRepository dogRepository;
private final PlatformTransactionManager transactionManager;
@GetMapping("/save")
public String testMultiThread() {
//创建1000只狗
List<Dog> dogList = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
Dog dog = new Dog();
dog.setDogName("中华田园犬-" + i);
dogList.add(dog);
}
//将狗分片,每片100只狗
List<List<Dog>> dogs = Lists.partition(dogList, 100);
//此处手动创建一个线程池,线程数量为10个,注意,实际情况下此处线程数量不宜过多,
//若实际插入数据库的方法用时较长,会一直占用数据库连接
ExecutorService executorService = Executors.newFixedThreadPool(10);
//定义成功数量
AtomicInteger successCount = new AtomicInteger(0);
//批量插入是否成功,所有子线程执行完毕后,会根据此状态判断是回滚还是提交事物
AtomicBoolean success = new AtomicBoolean(true);
//子线程计数器
CountDownLatch childThreadLatch = new CountDownLatch(dogs.size());
//主线程计数器
CountDownLatch mainThreadLatch = new CountDownLatch(1);
log.info("开始执行多线程批量插入!");
for (List<Dog> list : dogs) {
CompletableFuture.runAsync(() -> {
//手动开启事物
DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
TransactionStatus transaction = transactionManager.getTransaction(definition);
try {
//步骤1,插入数据
dogRepository.saveAll(list);
} catch (Exception e) {
//若出现异常,修改状态为false,
success.set(false);
log.error("线程:{},执行出现异常", Thread.currentThread().getName());
} finally {
//步骤2,子线程计数器减1
childThreadLatch.countDown();
log.info("线程:{},执行完毕", Thread.currentThread().getName());
}
//等待核心计数器
try {
mainThreadLatch.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//步骤4,若所有子线程中,有一个出现异常,则事物回滚,否则提交事物
if (success.get()) {
transactionManager.commit(transaction);
} else {
transactionManager.rollback(transaction);
}
}, executorService);
}
//步骤3,子线程计数器等待
try {
childThreadLatch.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
//主线程计数器-1,步骤4等待此处执行完后才会继续执行
mainThreadLatch.countDown();
}
log.info("所有线程已执行完毕,是否出现异常:{}", !success.get());
return "success";
}
}
- 执行结果,正常情况
数据库
- 手动模拟抛出异常,测试事物回滚
try {
//手动模拟异常情况
double random = Math.random() * 10;
if (random < 5) {
throw new RuntimeException("测试异常");
}
//插入数据
dogRepository.saveAll(list);
} catch (Exception e) {
//若出现异常,修改状态为false,
success.set(false);
log.error("线程:{},执行出现异常", Thread.currentThread().getName());
} finally {
//子线程计数器减1
childThreadLatch.countDown();
log.info("线程:{},执行完毕", Thread.currentThread().getName());
}
- 执行结果
数据库