From 84fe5048d63bcd9eed0d521a5e85f7e5dc9fbed5 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Tue, 3 Oct 2023 21:20:21 +0200 Subject: [PATCH] Add java configuration for the JPA sample Issue #3663 --- spring-batch-samples/README.md | 7 ++ spring-batch-samples/pom.xml | 11 +- .../batch/sample/jpa/JpaJobConfiguration.java | 112 ++++++++++++++++++ .../batch/sample/jpa/README.md | 18 +++ .../batch/sample/jpa/job}/jpa.xml | 112 +++++++++++------- .../batch/sample/jpa/sql/schema.sql | 19 +++ .../sample/iosample/JpaFunctionalTests.java | 30 ----- .../batch/sample/jpa/JpaFunctionalTests.java | 62 ++++++++++ 8 files changed, 291 insertions(+), 80 deletions(-) create mode 100644 spring-batch-samples/src/main/java/org/springframework/batch/sample/jpa/JpaJobConfiguration.java create mode 100644 spring-batch-samples/src/main/java/org/springframework/batch/sample/jpa/README.md rename spring-batch-samples/src/main/resources/{jobs/iosample => org/springframework/batch/sample/jpa/job}/jpa.xml (53%) create mode 100644 spring-batch-samples/src/main/resources/org/springframework/batch/sample/jpa/sql/schema.sql delete mode 100644 spring-batch-samples/src/test/java/org/springframework/batch/sample/iosample/JpaFunctionalTests.java create mode 100644 spring-batch-samples/src/test/java/org/springframework/batch/sample/jpa/JpaFunctionalTests.java diff --git a/spring-batch-samples/README.md b/spring-batch-samples/README.md index 191bafd1f..25c9f0b7f 100644 --- a/spring-batch-samples/README.md +++ b/spring-batch-samples/README.md @@ -109,6 +109,13 @@ 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 + +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. + +[JPA Reader and Writer sample](./src/main/java/org/springframework/batch/sample/jpa/README.md) + ### Amqp Job Sample This sample shows the use of Spring Batch to write to an `AmqpItemWriter`. diff --git a/spring-batch-samples/pom.xml b/spring-batch-samples/pom.xml index 980bd63f6..80070b3b8 100644 --- a/spring-batch-samples/pom.xml +++ b/spring-batch-samples/pom.xml @@ -42,6 +42,11 @@ spring-jdbc ${spring-framework.version} + + org.springframework + spring-orm + ${spring-framework.version} + org.springframework spring-context-support @@ -227,12 +232,6 @@ ${spring-framework.version} test - - org.springframework - spring-orm - ${spring-framework.version} - test - jakarta.el jakarta.el-api diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/sample/jpa/JpaJobConfiguration.java b/spring-batch-samples/src/main/java/org/springframework/batch/sample/jpa/JpaJobConfiguration.java new file mode 100644 index 000000000..fc3f2f9b3 --- /dev/null +++ b/spring-batch-samples/src/main/java/org/springframework/batch/sample/jpa/JpaJobConfiguration.java @@ -0,0 +1,112 @@ +/* + * 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 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.job.builder.JobBuilder; +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.core.step.builder.StepBuilder; +import org.springframework.batch.item.database.JpaItemWriter; +import org.springframework.batch.item.database.JpaPagingItemReader; +import org.springframework.batch.item.database.builder.JpaItemWriterBuilder; +import org.springframework.batch.item.database.builder.JpaPagingItemReaderBuilder; +import org.springframework.batch.sample.domain.trade.CustomerCredit; +import org.springframework.batch.sample.domain.trade.internal.CustomerCreditIncreaseProcessor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +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") +public class JpaJobConfiguration { + + @Bean + public JpaPagingItemReader itemReader(EntityManagerFactory entityManagerFactory) { + return new JpaPagingItemReaderBuilder().name("itemReader") + .entityManagerFactory(entityManagerFactory) + .queryString("select c from CustomerCredit c") + .build(); + } + + @Bean + public JpaItemWriter itemWriter(EntityManagerFactory entityManagerFactory) { + return new JpaItemWriterBuilder().entityManagerFactory(entityManagerFactory).build(); + } + + @Bean + public Job job(JobRepository jobRepository, JpaTransactionManager transactionManager, + JpaPagingItemReader itemReader, JpaItemWriter 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 new file mode 100644 index 000000000..4510a2022 --- /dev/null +++ b/spring-batch-samples/src/main/java/org/springframework/batch/sample/jpa/README.md @@ -0,0 +1,18 @@ +### JPA Reader and Writer 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. + +## Run the sample + +You can run the sample from the command line as following: + +``` +$>cd spring-batch-samples +# Launch the sample using the XML configuration +$>../mvnw -Dtest=JpaFunctionalTests#testLaunchJobWithXmlConfig test +# Launch the sample using the Java configuration +$>../mvnw -Dtest=JpaFunctionalTests#testLaunchJobWithJavaConfig test +``` diff --git a/spring-batch-samples/src/main/resources/jobs/iosample/jpa.xml b/spring-batch-samples/src/main/resources/org/springframework/batch/sample/jpa/job/jpa.xml similarity index 53% rename from spring-batch-samples/src/main/resources/jobs/iosample/jpa.xml rename to spring-batch-samples/src/main/resources/org/springframework/batch/sample/jpa/job/jpa.xml index 8921196a6..c943acf8d 100644 --- a/spring-batch-samples/src/main/resources/jobs/iosample/jpa.xml +++ b/spring-batch-samples/src/main/resources/org/springframework/batch/sample/jpa/job/jpa.xml @@ -1,44 +1,68 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-batch-samples/src/main/resources/org/springframework/batch/sample/jpa/sql/schema.sql b/spring-batch-samples/src/main/resources/org/springframework/batch/sample/jpa/sql/schema.sql new file mode 100644 index 000000000..727f18616 --- /dev/null +++ b/spring-batch-samples/src/main/resources/org/springframework/batch/sample/jpa/sql/schema.sql @@ -0,0 +1,19 @@ +DROP TABLE CUSTOMER_SEQ IF EXISTS; +DROP TABLE CUSTOMER IF EXISTS; + +CREATE TABLE CUSTOMER_SEQ ( + ID BIGINT IDENTITY +); +INSERT INTO CUSTOMER_SEQ (ID) values (5); + +CREATE TABLE CUSTOMER ( + ID BIGINT IDENTITY NOT NULL PRIMARY KEY , + VERSION BIGINT , + NAME VARCHAR(45) , + CREDIT DECIMAL(10,2) +) ; + +INSERT INTO CUSTOMER (ID, VERSION, NAME, CREDIT) VALUES (1, 0, 'customer1', 100000); +INSERT INTO CUSTOMER (ID, VERSION, NAME, CREDIT) VALUES (2, 0, 'customer2', 100000); +INSERT INTO CUSTOMER (ID, VERSION, NAME, CREDIT) VALUES (3, 0, 'customer3', 100000); +INSERT INTO CUSTOMER (ID, VERSION, NAME, CREDIT) VALUES (4, 0, 'customer4', 100000); \ No newline at end of file diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/sample/iosample/JpaFunctionalTests.java b/spring-batch-samples/src/test/java/org/springframework/batch/sample/iosample/JpaFunctionalTests.java deleted file mode 100644 index 053349dec..000000000 --- a/spring-batch-samples/src/test/java/org/springframework/batch/sample/iosample/JpaFunctionalTests.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2008-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.item.ItemReader; -import org.springframework.batch.sample.domain.trade.CustomerCredit; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -@SpringJUnitConfig(locations = "/jobs/iosample/jpa.xml") -class JpaFunctionalTests extends AbstractIoSampleTests { - - @Override - protected void pointReaderToOutput(ItemReader reader) { - // no-op - } - -} diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/sample/jpa/JpaFunctionalTests.java b/spring-batch-samples/src/test/java/org/springframework/batch/sample/jpa/JpaFunctionalTests.java new file mode 100644 index 000000000..ce15703f0 --- /dev/null +++ b/spring-batch-samples/src/test/java/org/springframework/batch/sample/jpa/JpaFunctionalTests.java @@ -0,0 +1,62 @@ +/* + * Copyright 2008-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.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/jpa.xml", "/job-runner-context.xml" }) +class JpaFunctionalTests { + + @Autowired + private JobLauncherTestUtils jobLauncherTestUtils; + + @Test + void testLaunchJobWithXmlConfig() throws Exception { + // when + JobExecution jobExecution = this.jobLauncherTestUtils.launchJob(); + + // then + assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus()); + } + + @Test + public void testLaunchJobWithJavaConfig() throws Exception { + // given + ApplicationContext context = new AnnotationConfigApplicationContext(JpaJobConfiguration.class); + JobLauncher jobLauncher = context.getBean(JobLauncher.class); + Job job = context.getBean(Job.class); + + // when + JobExecution jobExecution = jobLauncher.run(job, new JobParameters()); + + // then + assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus()); + } + +}