我遇到了一个Spring Batch作业的问题,该作业需要读取一个大型CSV文件(几百万条记录)并将这些记录保存到数据库中。作业使用FlatFileItemReader读取CSV,并使用JpaItemWriter将已读取和已处理的记录写入数据库。问题在于,JpaItemWriter在将另一项项目刷新到数据库后不会清除持久性上下文,并且作业最终以OutOfMemoryError结尾。

我已经解决了问题,方法是扩展JpaItemWriter并重写write方法,以便在写一堆之后调用EntityManager.clear(),但是我想知道Spring Batch是否已经解决了这个问题,而问题的根源在job config中。如何正确解决这个问题?

我的解决方案:

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

最佳答案

这是有道理的。 JpaItemWriter(和HibernateItemWriter)用于清除持久性上下文,但是已在BATCH-1635中将其删除(此处是the commit将其删除)。但是,已在BATCH-1759HibernateItemWriter中通过clearSession参数(请参见此commit)(而不是在JpaItemWriter中)对其进行了重新添加和配置。

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

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

希望这可以帮助。

07-27 15:13