【问题标题】:Spring Batch memory leak - CSV to database using JpaItemWriterSpring Batch 内存泄漏 - 使用 JpaItemWriter 到数据库的 CSV
【发布时间】:2019-02-18 11:57:00
【问题描述】:

我在使用 Spring Batch 作业读取大型 CSV 文件(几百万条记录)并将记录从中保存到数据库时遇到问题。该作业使用FlatFileItemReader 读取CSV,使用JpaItemWriter 将读取和处理的记录写入数据库。问题是JpaItemWriter 在将另一块项目刷新到数据库后没有清除持久性上下文,作业以OutOfMemoryError 结束。

我已经通过扩展 JpaItemWriter 并覆盖 write 方法解决了这个问题,以便它在编写一堆之后调用 EntityManager.clear(),但我想知道 Spring Batch 是否已经解决了这个问题,问题的根源在于作业配置。如何正确解决这个问题?

我的解决方案:

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;
        }
    }

在write方法中可以看到添加的entityManager.clear();

工作配置:

@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();
    }

【问题讨论】:

  • 如果您确定 EM 问题,可能使用 ChunkListener#afterChunkItemWriteListener#afterWrite 的方法比您的解决方案更具侵入性。每次写入后都会检查 jpa-writer 代码 EntityManager.flush,因此不应发生问题。您是否尝试过不同的(小)块大小/跳过限制?
  • @LucaBassoRicci 我可能错了,但刷新并不能清除上下文。听众确实看起来比我的解决方案更好,我只是不太了解 API。在作业失败之前,我使用的 1000 的跳过限制是 CSV 中“坏”记录的适当百分比,并且 5000 的块大小比原始 10k 块小一半。此处stackoverflow.com/questions/13886608/… 的答案表示,在进行批处理时必须调用 EM.clear,因此在处理大文件时,侦听器可能是调用 EM.clear 的地方
  • 我为此创建了jira.spring.io/browse/BATCH-2797。感谢您的报告。

标签: java spring spring-batch


【解决方案1】:

这是一个有效的观点。 JpaItemWriter(和HibernateItemWriter)用于清除持久性上下文,但已在BATCH-1635 中删除(这里是删除它的the commit)。但是,这已被重新添加并通过clearSession 参数在BATCH-1759 中的HibernateItemWriter 中进行配置(请参阅此commit),但不在JpaItemWriter 中。

所以我建议针对 Spring Batch 打开一个问题,以在 JpaItemWriter 中添加相同的选项,以便在写入项目后清除持久性上下文(这将与 HibernateItemWriter 一致)。

也就是说,要回答您的问题,您确实可以像以前一样使用自定义编写器来清除持久性上下文。

希望这会有所帮助。

【讨论】:

猜你喜欢
  • 2019-10-20
  • 2016-08-02
  • 2023-02-24
  • 2023-03-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多