Java 使用CompleteableFuture和Spring事务耗尽池

Java 使用CompleteableFuture和Spring事务耗尽池,java,spring-boot,transactions,spring-transactions,Java,Spring Boot,Transactions,Spring Transactions,我正在尝试使用CompletableFutures将数据库快速加载到内存中。我在方法级别启动Spring事务: @Transactional() private void loadErUp() { StopWatch sw = StopWatch.createStarted(); List<CompletableFuture<Void>> calls = new ArrayList<>(); final

我正在尝试使用CompletableFutures将数据库快速加载到内存中。我在方法级别启动Spring事务:

@Transactional()
    private void loadErUp() {
        StopWatch sw = StopWatch.createStarted();
        List<CompletableFuture<Void>> calls = new ArrayList<>();
        final ZonedDateTime zdt = ZonedDateTime.now(ZoneId.of(ZoneOffset.UTC.getId())).minusMinutes(REFRESH_OVERLAP);

        for (long i = 1; i < 12 + 1; i++) {
            Long holder = i;
            CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
                this.loadPartition(holder, zdt);
            }, this.forkJoinPool);
            calls.add(future);
        }
        CompletableFuture.allOf(calls.toArray(new CompletableFuture[0])).join();
        log.info("All data refreshed in ({}ms) since:{}", sw.getTime(), zdt.format(DateTimeFormatter.ISO_INSTANT));
    }
@Transactional()
私有void loadErUp(){
StopWatch sw=StopWatch.createStarted();
列表调用=新建ArrayList();
final ZonedDateTime zdt=ZonedDateTime.now(ZoneId.of(ZoneOffset.UTC.getId()).minutes(刷新重叠);
对于(长i=1;i<12+1;i++){
长支架=i;
CompletableFuture=CompletableFuture.runAsync(()->{
此为载荷隔板(支架,zdt);
},这是一个游泳池);
添加(未来);
}
CompletableFuture.allOf(calls.toArray(新的CompletableFuture[0])).join();
log.info(“自:{}”、sw.getTime()、zdt.format(DateTimeFormatter.ISO_INSTANT)以来({}ms)中刷新的所有数据);
}
然后通过将每个线程连接到主事务

TransactionSynchronizationManager.setActualTransactionActive(true)

private long-loadPartition(long-partitionKey,ZonedDateTime zdt){
调试(“刷新线程开始:{}”,分区键);
TransactionSynchronizationManager.setActualTransactionActive(true);
StopWatch sw=StopWatch.createStarted();
try(streamauthorityStream=aSqlRepo.findByPartitionKeyAndLastUpdatedTimeStampAfter(partitionKey,zdt)){
长计数=authorityStream.peek(a->{
this.authorityRepository.set(this.GSON.fromJson(a.getJsonData(),AssetAuthority.class));
}).count();
info(“分区{}在({}ms)中用{}项刷新。”,partitionKey,sw.getTime(),count);
返回计数;
}
}
因此,我每30秒运行一次批处理作业,在第9次运行中,我得到4个线程,然后它挂起(12*8次运行=96),因为它正在等待打开一个池。我得到:

无法获取JDBC连接;无法在中获取连接 30秒,无可用[大小:100;忙:100;空闲:0; 最后等待:30000]


因此,很明显,连接没有提交。我想这可能是因为我有自己的ForkJoinPool,但是,我关闭了所有这些线程,似乎没有任何帮助。我还在loadPartition()方法下放置了另一个方法,但这似乎也没有什么帮助。还有一个线程讨论如何使事务工作,但我的线程工作,它们只是不提交。

如果您想让每个
#loadPartition
在它自己的线程和事务中运行,您需要:

  • #loadPartition
    标记为
    @Transactional
  • 调用代理的
    #loadPartition
    方法,以便
    @Transactional
    工作。您可以通过自自动连接或从另一个代理类调用该方法来实现这一点
  • 事务未传播到异步线程,因为(重要!

    所以它看起来像:

    @Component
    public class MyLoaderClass {
    
        // Autowire in this with constructor injection or @Autowired
        private MyLoaderClass myLoaderClass;
    
        // Removed @Transactional annotation
        public void loadErUp() {
            myLoaderClass.loadPartition(holder, zdt);
            ...
        }
    
        // 1) Add the @Transactional annotation to #loadPartition
        // 2) Make public to use self-autowiring (or refactored class, per link above)
        @Transactional
        public <T> long loadPartition(long partitionKey, ZonedDateTime zdt) {
            ...
            // Can remove TransactionSyncManager call
            ...
        }
    
    }
    
    @组件
    公共类MyLoaderClass{
    //使用构造函数注入或@Autowired自动连线
    私有MyLoaderClass MyLoaderClass;
    //已删除@Transactional注释
    公共void loadErUp(){
    myLoaderClass.loadPartition(支架,zdt);
    ...
    }
    //1)将@Transactional注释添加到#loadPartition
    //2)公开使用自组装(或重构类,根据上面的链接)
    @交易的
    公共长加载分区(长分区键,ZonedDateTime zdt){
    ...
    //无法删除TransactionSyncManager调用
    ...
    }
    }
    

    您还需要确保在未确保上一个作业已完成的情况下,批处理作业不会运行。您可以很容易地解决这个问题,方法是为您的表加载,以确保运行不会“重叠”

    嗯。。我猜Spring使用ThreadLocalPattern处理事务/连接池,并通过自己使用ForkJoin池。。没有人打扫。我建议将Spring
    @Async
    注释与
    CompletableFuture
    结合使用,请参见尝试更改为Spring管理未来。没有乐趣。一个事务不能跨越多个线程。这根本行不通。此外,您没有与方法中的调用同步任何内容。基本上,您的设置在没有事务的情况下工作,因为
    私有
    方法上的
    @Transactional
    被忽略(除非您使用AspectJ编译或加载时编织)。它一定是以某种方式工作的。数据库调用是一个流,如果没有打开事务,它将无法工作。有什么建议吗?你只需要一个大的SQL调用就可以完成这个任务。我想使用多个线程加载一个表。我们对sql表进行“分区”的原因是为了更快地加载它。我想在12个线程上打开12个流并读取数据库。spring会处理这个问题吗?完全修改了我的答案。这应该是你想要的;We’我们期待多特汉克斯的帮助!我从同一个类调用事务方法,正如您所指出的,这就是问题所在。我不知道我对将类的实例自动连接到同一个类的感觉如何,但它确实使代码更简单!聪明的代码!
    @Component
    public class MyLoaderClass {
    
        // Autowire in this with constructor injection or @Autowired
        private MyLoaderClass myLoaderClass;
    
        // Removed @Transactional annotation
        public void loadErUp() {
            myLoaderClass.loadPartition(holder, zdt);
            ...
        }
    
        // 1) Add the @Transactional annotation to #loadPartition
        // 2) Make public to use self-autowiring (or refactored class, per link above)
        @Transactional
        public <T> long loadPartition(long partitionKey, ZonedDateTime zdt) {
            ...
            // Can remove TransactionSyncManager call
            ...
        }
    
    }