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