Java 使用CompleteableFuture和Spring事务耗尽池
我正在尝试使用CompletableFutures将数据库快速加载到内存中。我在方法级别启动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
@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
...
}
}