У меня была проблема с заданием 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;
}
}
Вы можете увидеть добавленное 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();
}