Spring boot Spring批处理/数据JPA应用程序在调用JPA存储库(save,saveAll)方法时未将数据持久化/保存到Postgres数据库
我快不知所措了。到目前为止,我阅读/谷歌搜索了无数次,并在所有谷歌/stackoverflow帖子上尝试了解决方案,这些帖子都有类似的问题(有很多)。有些似乎很有希望,但对我来说什么都不起作用;虽然我已经取得了一些进展,我相信我走在了正确的轨道上(我相信在这一点上它与事务管理器有关,并且与SpringBatch与SpringDataJPA可能存在一些冲突) 参考文献: 与前面提到的帖子类似,我有一个Spring Boot应用程序,它使用Spring批处理和Spring数据JPA。它从Spring boot Spring批处理/数据JPA应用程序在调用JPA存储库(save,saveAll)方法时未将数据持久化/保存到Postgres数据库,spring-boot,hibernate,jpa,transactions,spring-batch,Spring Boot,Hibernate,Jpa,Transactions,Spring Batch,我快不知所措了。到目前为止,我阅读/谷歌搜索了无数次,并在所有谷歌/stackoverflow帖子上尝试了解决方案,这些帖子都有类似的问题(有很多)。有些似乎很有希望,但对我来说什么都不起作用;虽然我已经取得了一些进展,我相信我走在了正确的轨道上(我相信在这一点上它与事务管理器有关,并且与SpringBatch与SpringDataJPA可能存在一些冲突) 参考文献: 与前面提到的帖子类似,我有一个Spring Boot应用程序,它使用Spring批处理和Spring数据JPA。它从.csv
.csv
文件中读取逗号分隔的数据,然后进行一些处理/转换,并尝试使用JPA存储库方法持久化/保存到数据库,特别是这里的.saveAll()
(我也尝试了.save()
方法,这也做了同样的事情),因为我正在保存用户定义的数据类型(批插入)的列表
现在,我的代码在Spring Boot starter1.5.9.RELEASE
上运行良好,但我最近尝试升级到2.X.X,经过无数个小时的调试,我发现只有版本2.2.0.RELEASE
将数据保存到数据库。因此升级到>=2.2.1.RELEASE
会破坏持久性。从.csv
中读取的所有内容都很好,就在代码流第一次命中JPA存储库方法时,如.save()
.saveAll()
,应用程序保持运行,但没有任何内容被持久化。我还注意到Hikari池日志“active=1 idle=4”
,但当我在版本1.5.9.RELEASE
上查看同一日志时,它会在持久化数据后立即显示active=0 idle=5
,因此应用程序肯定是挂起的。我进入调试器,甚至看到在跳入存储库调用之后,它通过Spring AOP库等(都是第三方)进入了一个几乎无限的循环,我认为永远不会回到我编写的真正的应用程序/业务逻辑
3c22fb53ed64 2021-05-20 23:53:43.909 DEBUG
[HikariPool-1 housekeeper] com.zaxxer.hikari.pool.HikariPool - HikariPool-1 - Pool stats (total=5, active=1, idle=4, waiting=0)
无论如何,我尝试了对其他人有效的最常见的解决方案:
定义JpaTransactionManager
@Bean
并将其注入步骤
函数,同时使用平台TransactionManager
保留作业存储库
。这不起作用。然后我还尝试在JobRepository@Bean
中使用JpaTransactionManager
,这也不起作用
在我的应用程序中定义一个@RestController
端点来手动触发此作业,而不是从我的主application.java
类手动执行。(我将在下面详细讨论这一点)。根据我上面发布的一篇文章,即使在spring>=2.2.1上,数据也正确地保存到了数据库中,我进一步怀疑spring批处理持久化/实体/事务管理器的某些方面出了问题
代码基本上是这样的:
BatchConfiguration.java
@Configuration
@EnableBatchProcessing
@Import({DatabaseConfiguration.class})
public class BatchConfiguration {
// Datasource is a Postgres DB defined in separate IntelliJ project that I add to my pom.xml
DataSource dataSource;
@Autowired
public BatchConfiguration(@Qualifier("dataSource") DataSource dataSource) {
this.dataSource = dataSource;
}
@Bean
@Primary
public JpaTransactionManager jpaTransactionManager() {
final JpaTransactionManager tm = new JpaTransactionManager();
tm.setDataSource(dataSource);
return tm;
}
@Bean
public JobRepository jobRepository(PlatformTransactionManager transactionManager) throws Exception {
JobRepositoryFactoryBean jobRepositoryFactoryBean = new JobRepositoryFactoryBean();
jobRepositoryFactoryBean.setDataSource(dataSource);
jobRepositoryFactoryBean.setTransactionManager(transactionManager);
jobRepositoryFactoryBean.setDatabaseType("POSTGRES");
return jobRepositoryFactoryBean.getObject();
}
@Bean
public JobLauncher jobLauncher(JobRepository jobRepository) {
SimpleJobLauncher simpleJobLauncher = new SimpleJobLauncher();
simpleJobLauncher.setJobRepository(jobRepository);
return simpleJobLauncher;
}
@Bean(name = "jobToLoadTheData")
public Job jobToLoadTheData() {
return jobBuilderFactory.get("jobToLoadTheData")
.start(stepToLoadData())
.listener(new CustomJobListener())
.build();
}
@Bean
@StepScope
public TaskExecutor taskExecutor() {
ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
threadPoolTaskExecutor.setCorePoolSize(maxThreads);
threadPoolTaskExecutor.setThreadGroupName("taskExecutor-batch");
return threadPoolTaskExecutor;
}
@Bean(name = "stepToLoadData")
public Step stepToLoadData() {
TaskletStep step = stepBuilderFactory.get("stepToLoadData")
.transactionManager(jpaTransactionManager())
.<List<FieldSet>, List<myCustomPayloadRecord>>chunk(chunkSize)
.reader(myCustomFileItemReader(OVERRIDDEN_BY_EXPRESSION))
.processor(myCustomPayloadRecordItemProcessor())
.writer(myCustomerWriter())
.faultTolerant()
.skipPolicy(new AlwaysSkipItemSkipPolicy())
.skip(DataValidationException.class)
.listener(new CustomReaderListener())
.listener(new CustomProcessListener())
.listener(new CustomWriteListener())
.listener(new CustomSkipListener())
.taskExecutor(taskExecutor())
.throttleLimit(maxThreads)
.build();
step.registerStepExecutionListener(stepExecutionListener());
step.registerChunkListener(new CustomChunkListener());
return step;
}
@Autowired
@Qualifier("jobToLoadTheData")
private Job loadTheData;
@Autowired
private JobLauncher jobLauncher;
@PostConstruct
public void launchJob () throws JobParametersInvalidException, JobExecutionAlreadyRunningException, JobRestartException, JobInstanceAlreadyCompleteException
{
JobParameters parameters = (new JobParametersBuilder()).addDate("random", new Date()).toJobParameters();
jobLauncher.run(loadTheData, parameters);
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
现在,通常我是从AmazonS3 bucket中读取这个.csv
,但由于我是在本地测试,我只是将.csv放在项目目录中,并通过触发应用程序.java
主类中的作业直接读取它(如上所示)。另外,我在这个BatchConfiguration
类中定义了一些其他bean,但我不想让这篇文章过于复杂,从我所做的谷歌搜索来看,问题可能在于我发布的方法(希望如此)
另外,我想指出,与Google/stackoverflow上的一篇文章类似,一个用户也有类似的问题,我创建了一个@RestController
端点,它只调用.run()
方法JobLauncher
,然后传入JobToLoadTheData
Bean,并触发批插入。你猜怎么着即使在spring>=2.2.1上,数据也能很好地保存到数据库中
这是怎么回事?这是线索吗?某种类型的实体或事务管理器是否出了问题?我会接受任何建议!我可以提供你们可能需要的更多信息,所以请提问。您正在定义一个类型为JobRepository
的bean,并希望它能在Spring Batch中获取。这是不对的。您需要提供一个BatchConfigurer
并覆盖getJobRepository
。下文对此进行了解释:
这也记录在启用批处理的部分中。因此,在您的情况下,需要定义类型为Batchconfigurer
的bean,并覆盖getJobRepository
和getTransactionManager
,类似于:
@Bean
public BatchConfigurer batchConfigurer(EntityManagerFactory entityManagerFactory, DataSource dataSource) {
return new DefaultBatchConfigurer(dataSource) {
@Override
public PlatformTransactionManager getTransactionManager() {
return new JpaTransactionManager(entityManagerFactory);
}
@Override
public JobRepository getJobRepository() {
JobRepositoryFactoryBean jobRepositoryFactoryBean = new JobRepositoryFactoryBean();
jobRepositoryFactoryBean.setDataSource(dataSource);
jobRepositoryFactoryBean.setTransactionManager(getTransactionManager());
// set other properties
return jobRepositoryFactoryBean.getObject();
}
};
}
在Spring引导上下文中,如果需要,您还可以覆盖org.springframework.Boot.autoconfigure.batch.jpabatchconfigure
的createTransactionManager
和createJobRepository
方法。我在您的帖子中添加了“BatchConfigurer”bean,但这不起作用。然后我删除了它,并让我在上面发布的“BatchConfiguration”类扩展了“DefaultBatchConfigurer”,然后我以这种方式重写了这些方法,但这不起作用。我尝试了不同于PlatformTransactionManager的事务管理器,并在Step vs JobRepository中使用了不同的事务管理器,而且在Spring>=2.2.1上几乎没有其他功能(它在Spring 2.2.0上运行良好)。我对您关于JobRepository没有被我的Spring接受的评论有点困惑,正如我这样定义的,在SpringBootStarter2.2.0上,数据可以很好地保存到数据库中吗?无需定义任何BatchConfigurer匿名类或扩展
@Bean
public BatchConfigurer batchConfigurer(EntityManagerFactory entityManagerFactory, DataSource dataSource) {
return new DefaultBatchConfigurer(dataSource) {
@Override
public PlatformTransactionManager getTransactionManager() {
return new JpaTransactionManager(entityManagerFactory);
}
@Override
public JobRepository getJobRepository() {
JobRepositoryFactoryBean jobRepositoryFactoryBean = new JobRepositoryFactoryBean();
jobRepositoryFactoryBean.setDataSource(dataSource);
jobRepositoryFactoryBean.setTransactionManager(getTransactionManager());
// set other properties
return jobRepositoryFactoryBean.getObject();
}
};
}