From 0141fc578baf4274beabfc169b3c79738b629ac4 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Tue, 3 Oct 2023 22:39:38 +0200 Subject: [PATCH] Add java configuration for the Repository item reader/writer sample Issue #3663 --- spring-batch-samples/README.md | 8 +- .../CustomerCreditCrudRepository.java | 4 +- ...tomerCreditPagingAndSortingRepository.java | 2 +- .../jpa/JpaRepositoryJobConfiguration.java | 127 ++++++++++++++++++ .../batch/sample/jpa/README.md | 20 ++- .../batch/sample/jpa/job}/repository.xml | 38 +++++- .../iosample/RepositoryFunctionalTests.java | 42 ------ .../sample/jpa/RepositoryFunctionalTests.java | 68 ++++++++++ 8 files changed, 250 insertions(+), 59 deletions(-) rename spring-batch-samples/src/main/java/org/springframework/batch/sample/{data => jpa}/CustomerCreditCrudRepository.java (89%) rename spring-batch-samples/src/main/java/org/springframework/batch/sample/{data => jpa}/CustomerCreditPagingAndSortingRepository.java (95%) create mode 100644 spring-batch-samples/src/main/java/org/springframework/batch/sample/jpa/JpaRepositoryJobConfiguration.java rename spring-batch-samples/src/main/resources/{jobs/iosample => org/springframework/batch/sample/jpa/job}/repository.xml (55%) delete mode 100644 spring-batch-samples/src/test/java/org/springframework/batch/sample/iosample/RepositoryFunctionalTests.java create mode 100644 spring-batch-samples/src/test/java/org/springframework/batch/sample/jpa/RepositoryFunctionalTests.java diff --git a/spring-batch-samples/README.md b/spring-batch-samples/README.md index 25c9f0b7f..3a89fbccf 100644 --- a/spring-batch-samples/README.md +++ b/spring-batch-samples/README.md @@ -109,12 +109,12 @@ efficient updates to a database table. [Jdbc Readers and Batch Update sample](./src/main/java/org/springframework/batch/sample/jdbc/README.md) -### JPA Reader and Writer sample +### JPA Readers and Writers sample -The purpose of this sample is to show to usage of the `JpaPagingItemReader` -and the `JpaItemWriter` to read and write data from/to a database with JPA. +The purpose of this sample is to show to usage of the JPA item readers and writers +to read and write data from/to a database with JPA and Hibernate. -[JPA Reader and Writer sample](./src/main/java/org/springframework/batch/sample/jpa/README.md) +[JPA Readers and Writers sample](./src/main/java/org/springframework/batch/sample/jpa/README.md) ### Amqp Job Sample diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/sample/data/CustomerCreditCrudRepository.java b/spring-batch-samples/src/main/java/org/springframework/batch/sample/jpa/CustomerCreditCrudRepository.java similarity index 89% rename from spring-batch-samples/src/main/java/org/springframework/batch/sample/data/CustomerCreditCrudRepository.java rename to spring-batch-samples/src/main/java/org/springframework/batch/sample/jpa/CustomerCreditCrudRepository.java index a808ab811..2d8fee35c 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/sample/data/CustomerCreditCrudRepository.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/sample/jpa/CustomerCreditCrudRepository.java @@ -1,5 +1,5 @@ /* - * Copyright 2022 the original author or authors. + * Copyright 2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.batch.sample.data; +package org.springframework.batch.sample.jpa; import org.springframework.batch.sample.domain.trade.CustomerCredit; diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/sample/data/CustomerCreditPagingAndSortingRepository.java b/spring-batch-samples/src/main/java/org/springframework/batch/sample/jpa/CustomerCreditPagingAndSortingRepository.java similarity index 95% rename from spring-batch-samples/src/main/java/org/springframework/batch/sample/data/CustomerCreditPagingAndSortingRepository.java rename to spring-batch-samples/src/main/java/org/springframework/batch/sample/jpa/CustomerCreditPagingAndSortingRepository.java index a5f4bfd81..a72282f31 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/sample/data/CustomerCreditPagingAndSortingRepository.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/sample/jpa/CustomerCreditPagingAndSortingRepository.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.batch.sample.data; +package org.springframework.batch.sample.jpa; import java.math.BigDecimal; diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/sample/jpa/JpaRepositoryJobConfiguration.java b/spring-batch-samples/src/main/java/org/springframework/batch/sample/jpa/JpaRepositoryJobConfiguration.java new file mode 100644 index 000000000..b4367890c --- /dev/null +++ b/spring-batch-samples/src/main/java/org/springframework/batch/sample/jpa/JpaRepositoryJobConfiguration.java @@ -0,0 +1,127 @@ +/* + * Copyright 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.sample.jpa; + +import java.math.BigDecimal; +import java.util.Map; + +import javax.sql.DataSource; +import jakarta.persistence.EntityManagerFactory; + +import org.springframework.batch.core.Job; +import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; +import org.springframework.batch.core.configuration.annotation.StepScope; +import org.springframework.batch.core.job.builder.JobBuilder; +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.core.step.builder.StepBuilder; +import org.springframework.batch.item.data.RepositoryItemReader; +import org.springframework.batch.item.data.RepositoryItemWriter; +import org.springframework.batch.item.data.builder.RepositoryItemReaderBuilder; +import org.springframework.batch.item.data.builder.RepositoryItemWriterBuilder; +import org.springframework.batch.item.database.JpaItemWriter; +import org.springframework.batch.item.database.JpaPagingItemReader; +import org.springframework.batch.sample.domain.trade.CustomerCredit; +import org.springframework.batch.sample.domain.trade.internal.CustomerCreditIncreaseProcessor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.domain.Sort; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; +import org.springframework.orm.jpa.JpaTransactionManager; +import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; +import org.springframework.orm.jpa.persistenceunit.DefaultPersistenceUnitManager; +import org.springframework.orm.jpa.persistenceunit.PersistenceUnitManager; +import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; + +/** + * Hibernate JPA dialect does not support custom tx isolation levels => overwrite with + * ISOLATION_DEFAULT. + * + * @author Mahmoud Ben Hassine + */ +@Configuration +@EnableBatchProcessing(isolationLevelForCreate = "ISOLATION_DEFAULT") +@EnableJpaRepositories(basePackages = "org.springframework.batch.sample.jpa") +public class JpaRepositoryJobConfiguration { + + @Bean + @StepScope + public RepositoryItemReader itemReader(@Value("#{jobParameters['credit']}") Double credit, + CustomerCreditPagingAndSortingRepository repository) { + return new RepositoryItemReaderBuilder().name("itemReader") + .pageSize(2) + .methodName("findByCreditGreaterThan") + .repository(repository) + .arguments(BigDecimal.valueOf(credit)) + .sorts(Map.of("id", Sort.Direction.ASC)) + .build(); + } + + @Bean + public RepositoryItemWriter itemWriter(CustomerCreditCrudRepository repository) { + return new RepositoryItemWriterBuilder().repository(repository).methodName("save").build(); + } + + @Bean + public Job job(JobRepository jobRepository, JpaTransactionManager transactionManager, + RepositoryItemReader itemReader, RepositoryItemWriter itemWriter) { + return new JobBuilder("ioSampleJob", jobRepository) + .start(new StepBuilder("step1", jobRepository).chunk(2, transactionManager) + .reader(itemReader) + .processor(new CustomerCreditIncreaseProcessor()) + .writer(itemWriter) + .build()) + .build(); + } + + // Infrastructure beans + + @Bean + public DataSource dataSource() { + return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.HSQL) + .addScript("/org/springframework/batch/core/schema-drop-hsqldb.sql") + .addScript("/org/springframework/batch/core/schema-hsqldb.sql") + .addScript("/org/springframework/batch/sample/jpa/sql/schema.sql") + .build(); + } + + @Bean + public JpaTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) { + return new JpaTransactionManager(entityManagerFactory); + } + + @Bean + public EntityManagerFactory entityManagerFactory(PersistenceUnitManager persistenceUnitManager, + DataSource dataSource) { + LocalContainerEntityManagerFactoryBean factoryBean = new LocalContainerEntityManagerFactoryBean(); + factoryBean.setDataSource(dataSource); + factoryBean.setPersistenceUnitManager(persistenceUnitManager); + factoryBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter()); + factoryBean.afterPropertiesSet(); + return factoryBean.getObject(); + } + + @Bean + public PersistenceUnitManager persistenceUnitManager(DataSource dataSource) { + DefaultPersistenceUnitManager persistenceUnitManager = new DefaultPersistenceUnitManager(); + persistenceUnitManager.setDefaultDataSource(dataSource); + persistenceUnitManager.afterPropertiesSet(); + return persistenceUnitManager; + } + +} diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/sample/jpa/README.md b/spring-batch-samples/src/main/java/org/springframework/batch/sample/jpa/README.md index 4510a2022..9c9884e2e 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/sample/jpa/README.md +++ b/spring-batch-samples/src/main/java/org/springframework/batch/sample/jpa/README.md @@ -1,13 +1,13 @@ -### JPA Reader and Writer sample +### JPA Readers and Writers sample ## About -The purpose of this sample is to show to usage of the `JpaPagingItemReader` -and the `JpaItemWriter` to read and write data from/to a database with JPA. +The purpose of this sample is to show to usage of the JPA item readers and writers +to read and write data from/to a database with JPA and Hibernate. -## Run the sample +## Run the samples -You can run the sample from the command line as following: +You can run the sample of the `JpaPagingItemReader`/`JpaItemWriter` from the command line as following: ``` $>cd spring-batch-samples @@ -16,3 +16,13 @@ $>../mvnw -Dtest=JpaFunctionalTests#testLaunchJobWithXmlConfig test # Launch the sample using the Java configuration $>../mvnw -Dtest=JpaFunctionalTests#testLaunchJobWithJavaConfig test ``` + +You can run the sample of the `RepositoryItemReader`/`RepositoryItemWriter` from the command line as following: + +``` +$>cd spring-batch-samples +# Launch the sample using the XML configuration +$>../mvnw -Dtest=RepositoryFunctionalTests#testLaunchJobWithXmlConfig test +# Launch the sample using the Java configuration +$>../mvnw -Dtest=RepositoryFunctionalTests#testLaunchJobWithJavaConfig test +``` diff --git a/spring-batch-samples/src/main/resources/jobs/iosample/repository.xml b/spring-batch-samples/src/main/resources/org/springframework/batch/sample/jpa/job/repository.xml similarity index 55% rename from spring-batch-samples/src/main/resources/jobs/iosample/repository.xml rename to spring-batch-samples/src/main/resources/org/springframework/batch/sample/jpa/job/repository.xml index 92b517746..da24f204b 100644 --- a/spring-batch-samples/src/main/resources/jobs/iosample/repository.xml +++ b/spring-batch-samples/src/main/resources/org/springframework/batch/sample/jpa/job/repository.xml @@ -1,11 +1,27 @@ + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns:batch="http://www.springframework.org/schema/batch" + xmlns:jdbc="http://www.springframework.org/schema/jdbc" + xmlns:jpa="http://www.springframework.org/schema/data/jpa" + xsi:schemaLocation=" + http://www.springframework.org/schema/batch https://www.springframework.org/schema/batch/spring-batch.xsd + http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd + http://www.springframework.org/schema/jdbc https://www.springframework.org/schema/jdbc/spring-jdbc.xsd + http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd"> - + + + + + + + + + + + @@ -58,4 +74,16 @@ + + + + + + + + + + + diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/sample/iosample/RepositoryFunctionalTests.java b/spring-batch-samples/src/test/java/org/springframework/batch/sample/iosample/RepositoryFunctionalTests.java deleted file mode 100644 index 63bc43d2f..000000000 --- a/spring-batch-samples/src/test/java/org/springframework/batch/sample/iosample/RepositoryFunctionalTests.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2013-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.batch.sample.iosample; - -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.scope.context.StepSynchronizationManager; -import org.springframework.batch.item.ItemReader; -import org.springframework.batch.sample.domain.trade.CustomerCredit; -import org.springframework.batch.test.MetaDataInstanceFactory; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -@SpringJUnitConfig(locations = "/jobs/iosample/repository.xml") -class RepositoryFunctionalTests extends AbstractIoSampleTests { - - @Override - protected void pointReaderToOutput(ItemReader reader) { - JobParameters jobParameters = super.getUniqueJobParametersBuilder().addDouble("credit", 0.).toJobParameters(); - StepExecution stepExecution = MetaDataInstanceFactory.createStepExecution(jobParameters); - StepSynchronizationManager.close(); - StepSynchronizationManager.register(stepExecution); - } - - @Override - protected JobParameters getUniqueJobParameters() { - return super.getUniqueJobParametersBuilder().addString("credit", "10000").toJobParameters(); - } - -} diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/sample/jpa/RepositoryFunctionalTests.java b/spring-batch-samples/src/test/java/org/springframework/batch/sample/jpa/RepositoryFunctionalTests.java new file mode 100644 index 000000000..7c774a23d --- /dev/null +++ b/spring-batch-samples/src/test/java/org/springframework/batch/sample/jpa/RepositoryFunctionalTests.java @@ -0,0 +1,68 @@ +/* + * Copyright 2013-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.sample.jpa; + +import org.junit.jupiter.api.Test; + +import org.springframework.batch.core.BatchStatus; +import org.springframework.batch.core.Job; +import org.springframework.batch.core.JobExecution; +import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.JobParametersBuilder; +import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.test.JobLauncherTestUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +@SpringJUnitConfig( + locations = { "/org/springframework/batch/sample/jpa/job/repository.xml", "/job-runner-context.xml" }) +class RepositoryFunctionalTests { + + @Autowired + private JobLauncherTestUtils jobLauncherTestUtils; + + @Test + void testLaunchJobWithXmlConfig() throws Exception { + // given + JobParameters jobParameters = new JobParametersBuilder().addDouble("credit", 10000D).toJobParameters(); + + // when + JobExecution jobExecution = this.jobLauncherTestUtils.launchJob(jobParameters); + + // then + assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus()); + } + + @Test + public void testLaunchJobWithJavaConfig() throws Exception { + // given + ApplicationContext context = new AnnotationConfigApplicationContext(JpaRepositoryJobConfiguration.class); + JobLauncher jobLauncher = context.getBean(JobLauncher.class); + Job job = context.getBean(Job.class); + JobParameters jobParameters = new JobParametersBuilder().addDouble("credit", 10000D).toJobParameters(); + + // when + JobExecution jobExecution = jobLauncher.run(job, jobParameters); + + // then + assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus()); + } + +}