Saya hampir kehabisan akal. Saya membaca/google tanpa henti sejauh ini dan mencoba solusi pada semua posting google/stackoverflow yang memiliki masalah serupa (ada beberapa). Beberapa tampak menjanjikan, tetapi belum ada yang berhasil untuk saya; meskipun saya telah membuat beberapa kemajuan dan saya berada di jalur yang benar, saya percaya (saya percaya pada titik ini ada sesuatu dengan manajer Transaksi dan beberapa kemungkinan konflik dengan Spring Batch vs. Spring Data JPA).

Referensi:

  1. Repositori boot musim semi tidak disimpan ke DB jika dipanggil dari pekerjaan yang dijadwalkan
  2. JpaItemWriter: tidak ada transaksi yang sedang berlangsung

Mirip dengan postingan di atas, saya memiliki aplikasi Spring Boot yang menggunakan Spring Batch dan Spring Data JPA. Itu membaca data yang dibatasi koma dari file .csv, kemudian melakukan beberapa pemrosesan/transformasi, dan berusaha untuk bertahan/menyimpan ke database menggunakan metode Repositori JPA, khususnya di sini .saveAll() ( Saya juga mencoba metode .save() dan ini melakukan hal yang sama), karena saya menyimpan List<MyUserDefinedDataType> dari tipe data yang ditentukan pengguna (sisipan batch).

Sekarang, kode saya berfungsi dengan baik di Spring Boot starter 1.5.9.RELEASE, tetapi saya baru-baru ini mencoba meningkatkan ke 2.XX, yang saya temukan, setelah berjam-jam debugging, hanya versi 2.2.0.RELEASE yang akan bertahan/menyimpan data ke basis data. Jadi peningkatan ke >= 2.2.1.RELEASE merusak kegigihan. Semuanya dibaca dengan baik dari .csv, hanya ketika pertama kali aliran kode mengenai metode repositori JPA seperti .save() .saveAll(), aplikasi terus berjalan tetapi tidak ada yang bertahan. Saya juga memperhatikan log kumpulan Hikari "active=1 idle=4", tetapi ketika saya melihat log yang sama ketika di versi 1.5.9.RELEASE, dikatakan active=0 idle=5 segera setelah menyimpan data, jadi aplikasi pasti hang. Saya masuk ke debugger dan bahkan melihat setelah melompat ke panggilan Repositori, itu masuk ke siklus yang hampir tak terbatas melalui perpustakaan Spring AOP dan semacamnya (semua pihak ketiga) dan saya tidak percaya pernah kembali ke aplikasi/logika bisnis yang sebenarnya yang saya tulis.

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)

Bagaimanapun, saya mencoba solusi paling umum yang berhasil untuk orang lain yaitu:

  1. Mendefinisikan JpaTransactionManager @Bean dan memasukkannya ke dalam fungsi Step, sambil mempertahankan JobRepository menggunakan PlatformTransactionManager. Ini tidak berhasil. Kemudian saya juga saya mencoba menggunakan JpaTransactionManager juga di JobRepository @Bean, ini juga tidak berhasil.
  2. Menentukan titik akhir @RestController di aplikasi saya untuk memicu Pekerjaan ini secara manual, alih-alih melakukannya secara manual dari kelas Application.java utama saya. (Saya berbicara tentang ini lebih lanjut di bawah). Dan per salah satu posting yang saya posting di atas, data bertahan dengan benar ke database bahkan pada musim semi> = 2.2.1, yang selanjutnya saya curigai sekarang sesuatu dengan manajer ketekunan/entitas/transaksi Spring Batch kacau.

Kode pada dasarnya adalah ini: 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;
}

Metode utama saya: Application.java

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

Sekarang, biasanya saya membaca .csv ini dari ember Amazon S3, tetapi karena saya menguji secara lokal, saya hanya menempatkan .csv di direktori proyek dan membacanya langsung dengan memicu pekerjaan di Application.java kelas utama (seperti yang Anda lihat di atas). Juga, saya memiliki beberapa kacang lain yang didefinisikan di kelas BatchConfiguration ini tetapi saya tidak ingin memperumit posting ini lebih dari yang sudah ada dan dari googling yang telah saya lakukan, masalahnya mungkin adalah dengan metode Saya memposting (semoga).

Juga, saya ingin menunjukkan, mirip dengan salah satu posting lain di Google/stackoverflow dengan pengguna yang memiliki masalah yang sama, saya membuat titik akhir @RestController yang hanya memanggil metode .run() metode JobLauncher dan saya memasukkan JobToLoadTheData Bean, dan itu memicu penyisipan batch. Tebak apa? Data tetap tersimpan di database dengan baik, bahkan pada musim semi >= 2.2.1.

Apa yang terjadi disini? apakah ini petunjuk? apakah ada sesuatu yang funky yang salah dengan beberapa jenis entitas atau manajer transaksi? Saya akan mengambil tips saran! Saya dapat memberikan informasi lebih lanjut yang mungkin Anda butuhkan, jadi silakan tanyakan saja.

0
ennth 21 Mei 2021, 08:26

1 menjawab

Jawaban Terbaik

Anda mendefinisikan kacang tipe JobRepository dan mengharapkannya diambil oleh Spring Batch. Ini tidak benar. Anda perlu memberikan BatchConfigurer dan menimpa getJobRepository. Ini dijelaskan dalam dokumentasi referensi< /a>:

You can customize any of these beans by creating a custom implementation of the
BatchConfigurer interface. Typically, extending the DefaultBatchConfigurer
(which is provided if a BatchConfigurer is not found) and overriding the required
getter is sufficient.

Ini juga didokumentasikan dalam rel Javadoc dari @EnableBatchProcessing. Jadi dalam kasus Anda, Anda perlu mendefinisikan kacang bertipe Batchconfigurer dan menimpa getJobRepository dan getTransactionManager, seperti:

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

Dalam konteks Spring Boot, Anda juga dapat mengganti metode createTransactionManager dan createJobRepository dari org.springframework.boot.autoconfigure.batch.JpaBatchConfigurer jika diperlukan.

1
Mahmoud Ben Hassine 21 Mei 2021, 08:50