多线程事物

136 阅读2分钟

最近有个需求: 大批量插入数据,多个线程的事物需要保证事务的四大特性,若有一个线程出现异常,所有线程都需要回滚事物,若都成功,则提交事物,现在整理了一下,分享给大家

多线程事物示例

  • 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";
    }
}
  • 执行结果,正常情况 image.png 数据库

image.png

  • 手动模拟抛出异常,测试事物回滚
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());
}
  • 执行结果

image.png 数据库

image.png