Java Spring批处理内存泄漏-使用JpaItemWriter将CSV发送到数据库
我在Spring批处理作业中遇到了一个问题,即读取一个大的CSV文件(几百万条记录)并将其保存到数据库中。作业使用Java Spring批处理内存泄漏-使用JpaItemWriter将CSV发送到数据库,java,spring,spring-batch,Java,Spring,Spring Batch,我在Spring批处理作业中遇到了一个问题,即读取一个大的CSV文件(几百万条记录)并将其保存到数据库中。作业使用FlatFileItemReader读取CSV,并使用JpaItemWriter将读取和处理的记录写入数据库。问题是JpaItemWriter在将另一个项目块刷新到数据库后,不会清除持久性上下文,作业最终会出现OutOfMemoryError 我已经通过扩展JpaItemWriter并重写write方法解决了这个问题,这样它就可以在编写了一堆之后调用EntityManager.cle
FlatFileItemReader
读取CSV,并使用JpaItemWriter
将读取和处理的记录写入数据库。问题是JpaItemWriter
在将另一个项目块刷新到数据库后,不会清除持久性上下文,作业最终会出现OutOfMemoryError
我已经通过扩展JpaItemWriter
并重写write方法解决了这个问题,这样它就可以在编写了一堆之后调用EntityManager.clear()
,但是我想知道Spring批处理是否已经解决了这个问题,问题的根源是否在作业配置中。如何以正确的方式解决这个问题
我的解决方案:
class ClearingJpaItemWriter<T> extends JpaItemWriter<T> {
private EntityManagerFactory entityManagerFactory;
@Override
public void write(List<? extends T> items) {
super.write(items);
EntityManager entityManager = EntityManagerFactoryUtils.getTransactionalEntityManager(entityManagerFactory);
if (entityManager == null) {
throw new DataAccessResourceFailureException("Unable to obtain a transactional EntityManager");
}
entityManager.clear();
}
@Override
public void setEntityManagerFactory(EntityManagerFactory entityManagerFactory) {
super.setEntityManagerFactory(entityManagerFactory);
this.entityManagerFactory = entityManagerFactory;
}
}
这是一个正确的观点。用于清除持久性上下文的
JpaItemWriter
(和HibernateItemWriter
)已在中删除(此处是已删除的)。但是,已通过clearSession
参数(参见此)在中的HibernateItemWriter
中重新添加并使其可配置,但未在JpaItemWriter
中进行配置
因此,我建议针对Spring批处理打开一个问题,将相同的选项添加到JpaItemWriter
中,以便在编写项目后清除持久性上下文(这将与HibernateItemWriter
一致)
也就是说,为了回答您的问题,您确实可以像以前那样使用自定义编写器来清除持久性上下文
希望这有帮助。这是一个正确的观点。用于清除持久性上下文的
JpaItemWriter
(和HibernateItemWriter
)已在中删除(此处是已删除的)。但是,已通过clearSession
参数(参见此)在中的HibernateItemWriter
中重新添加并使其可配置,但未在JpaItemWriter
中进行配置
因此,我建议针对Spring批处理打开一个问题,将相同的选项添加到JpaItemWriter
中,以便在编写项目后清除持久性上下文(这将与HibernateItemWriter
一致)
也就是说,为了回答您的问题,您确实可以像以前那样使用自定义编写器来清除持久性上下文
希望这能有所帮助。如果您确定EM问题,那么使用
ChunkListener#afterChunk
或ItemWriteListener#afterWrite
的方法可能比您的解决方案更具侵入性。检查jpa编写器代码aEntityManager.flush
是在每次写入后执行的,所以问题不应该发生。您是否尝试了不同的(小)块大小/跳过限制?@LucaBasoricci I可能是错误的,但flush没有清除上下文。侦听器看起来确实比我的解决方案好,我只是不太了解API。我使用的跳过限制1000是作业失败前CSV中“坏”记录的适当百分比,而块大小5000是原始10k块大小的一半。这里的答案是,在执行批处理时必须调用EM.clear,因此,在处理我为此创建的大型文件时,侦听器可能是调用EM.clear的地方。感谢您的报告。如果您确定EM问题,那么使用ChunkListener#afterChunk
或ItemWriteListener#afterWrite
的方法可能比您的解决方案更具侵入性。检查jpa编写器代码aEntityManager.flush
是在每次写入后执行的,所以问题不应该发生。您是否尝试了不同的(小)块大小/跳过限制?@LucaBasoricci I可能是错误的,但flush没有清除上下文。侦听器看起来确实比我的解决方案好,我只是不太了解API。我使用的跳过限制1000是作业失败前CSV中“坏”记录的适当百分比,而块大小5000是原始10k块大小的一半。这里的答案是,在执行批处理时必须调用EM.clear,因此,在处理我为此创建的大型文件时,侦听器可能是调用EM.clear的地方。感谢您的报告。我创建是为了在JpaItemWriter
中添加新参数。我创建是为了在JpaItemWriter
中添加新参数。
@Bean
public JpaItemWriter postgresWriter() {
JpaItemWriter writer = new ClearingJpaItemWriter();
writer.setEntityManagerFactory(pgEntityManagerFactory);
return writer;
}
@Bean
public Step appontmentInitStep(JpaItemWriter<Appointment> writer, FlatFileItemReader<Appointment> reader) {
return stepBuilderFactory.get("initEclinicAppointments")
.transactionManager(platformTransactionManager)
.<Appointment, Appointment>chunk(5000)
.reader(reader)
.writer(writer)
.faultTolerant()
.skipLimit(1000)
.skip(FlatFileParseException.class)
.build();
}
@Bean
public Job appointmentInitJob(@Qualifier("initEclinicAppointments") Step step) {
return jobBuilderFactory.get(JOB_NAME)
.incrementer(new RunIdIncrementer())
.preventRestart()
.start(step)
.build();
}