Skip to content

Commit b23e307

Browse files
committed
Add JPA/Hibernate sample with PostgreSQL database
Related to #5006
1 parent d36db64 commit b23e307

File tree

3 files changed

+190
-0
lines changed

3 files changed

+190
-0
lines changed

spring-batch-samples/pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,12 @@
262262
<version>${testcontainers.version}</version>
263263
<scope>test</scope>
264264
</dependency>
265+
<dependency>
266+
<groupId>org.testcontainers</groupId>
267+
<artifactId>postgresql</artifactId>
268+
<version>${testcontainers.version}</version>
269+
<scope>test</scope>
270+
</dependency>
265271
<dependency>
266272
<groupId>org.hamcrest</groupId>
267273
<artifactId>hamcrest-library</artifactId>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
CREATE TABLE CUSTOMER (
2+
ID BIGINT NOT NULL PRIMARY KEY,
3+
VERSION BIGINT,
4+
NAME VARCHAR(45),
5+
CREDIT DECIMAL(10,2)
6+
) ;
7+
8+
INSERT INTO CUSTOMER (ID, VERSION, NAME, CREDIT) VALUES (1, 0, 'customer1', 100000);
9+
INSERT INTO CUSTOMER (ID, VERSION, NAME, CREDIT) VALUES (2, 0, 'customer2', 100000);
10+
INSERT INTO CUSTOMER (ID, VERSION, NAME, CREDIT) VALUES (3, 0, 'customer3', 100000);
11+
INSERT INTO CUSTOMER (ID, VERSION, NAME, CREDIT) VALUES (4, 0, 'customer4', 100000);
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
/*
2+
* Copyright 2025-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.batch.samples.jpa;
17+
18+
import javax.sql.DataSource;
19+
import jakarta.persistence.EntityManagerFactory;
20+
21+
import org.junit.jupiter.api.BeforeEach;
22+
import org.junit.jupiter.api.Test;
23+
import org.postgresql.ds.PGSimpleDataSource;
24+
import org.testcontainers.containers.PostgreSQLContainer;
25+
import org.testcontainers.junit.jupiter.Container;
26+
import org.testcontainers.junit.jupiter.Testcontainers;
27+
import org.testcontainers.utility.DockerImageName;
28+
29+
import org.springframework.batch.core.ExitStatus;
30+
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
31+
import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository;
32+
import org.springframework.batch.core.job.Job;
33+
import org.springframework.batch.core.job.JobExecution;
34+
import org.springframework.batch.core.job.builder.JobBuilder;
35+
import org.springframework.batch.core.job.parameters.JobParameters;
36+
import org.springframework.batch.core.job.parameters.JobParametersBuilder;
37+
import org.springframework.batch.core.launch.JobOperator;
38+
import org.springframework.batch.core.repository.JobRepository;
39+
import org.springframework.batch.core.step.builder.StepBuilder;
40+
import org.springframework.batch.infrastructure.item.database.JpaItemWriter;
41+
import org.springframework.batch.infrastructure.item.database.JpaPagingItemReader;
42+
import org.springframework.batch.infrastructure.item.database.builder.JpaItemWriterBuilder;
43+
import org.springframework.batch.infrastructure.item.database.builder.JpaPagingItemReaderBuilder;
44+
import org.springframework.batch.samples.domain.trade.CustomerCredit;
45+
import org.springframework.batch.samples.domain.trade.internal.CustomerCreditIncreaseProcessor;
46+
import org.springframework.beans.factory.annotation.Autowired;
47+
import org.springframework.context.annotation.Bean;
48+
import org.springframework.context.annotation.Configuration;
49+
import org.springframework.core.io.ClassPathResource;
50+
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
51+
import org.springframework.orm.jpa.JpaTransactionManager;
52+
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
53+
import org.springframework.orm.jpa.persistenceunit.DefaultPersistenceUnitManager;
54+
import org.springframework.orm.jpa.persistenceunit.PersistenceUnitManager;
55+
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
56+
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
57+
58+
import static org.junit.jupiter.api.Assertions.assertEquals;
59+
import static org.junit.jupiter.api.Assertions.assertNotNull;
60+
61+
/**
62+
* @author Mahmoud Ben Hassine
63+
*/
64+
@Testcontainers(disabledWithoutDocker = true)
65+
@SpringJUnitConfig
66+
class PostgreSQLJpaIntegrationTests {
67+
68+
// TODO find the best way to externalize and manage image versions
69+
private static final DockerImageName POSTGRESQL_IMAGE = DockerImageName.parse("postgres:17.5");
70+
71+
@Container
72+
public static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>(POSTGRESQL_IMAGE);
73+
74+
@Autowired
75+
private DataSource dataSource;
76+
77+
@Autowired
78+
private JobOperator jobOperator;
79+
80+
@Autowired
81+
private Job job;
82+
83+
@BeforeEach
84+
void setUp() {
85+
ResourceDatabasePopulator databasePopulator = new ResourceDatabasePopulator();
86+
databasePopulator.addScript(new ClassPathResource("/org/springframework/batch/core/schema-postgresql.sql"));
87+
databasePopulator.addScript(
88+
new ClassPathResource("/org/springframework/batch/samples/common/business-schema-postgresql.sql"));
89+
databasePopulator.execute(this.dataSource);
90+
}
91+
92+
@Test
93+
void testJobExecution() throws Exception {
94+
// given
95+
JobParameters jobParameters = new JobParametersBuilder().toJobParameters();
96+
97+
// when
98+
JobExecution jobExecution = this.jobOperator.start(this.job, jobParameters);
99+
100+
// then
101+
assertNotNull(jobExecution);
102+
assertEquals(ExitStatus.COMPLETED, jobExecution.getExitStatus());
103+
}
104+
105+
@Configuration
106+
@EnableBatchProcessing
107+
@EnableJdbcJobRepository(transactionManagerRef = "jpaTransactionManager")
108+
static class TestConfiguration {
109+
110+
@Bean
111+
public Job job(JobRepository jobRepository, JpaTransactionManager jpaTransactionManager,
112+
JpaPagingItemReader<CustomerCredit> itemReader, JpaItemWriter<CustomerCredit> itemWriter) {
113+
return new JobBuilder("ioSampleJob", jobRepository)
114+
.start(new StepBuilder("step1", jobRepository).<CustomerCredit, CustomerCredit>chunk(2)
115+
.transactionManager(jpaTransactionManager)
116+
.reader(itemReader)
117+
.processor(new CustomerCreditIncreaseProcessor())
118+
.writer(itemWriter)
119+
.build())
120+
.build();
121+
}
122+
123+
@Bean
124+
public JpaPagingItemReader<CustomerCredit> itemReader(EntityManagerFactory entityManagerFactory) {
125+
return new JpaPagingItemReaderBuilder<CustomerCredit>().name("itemReader")
126+
.entityManagerFactory(entityManagerFactory)
127+
.queryString("select c from CustomerCredit c")
128+
.build();
129+
}
130+
131+
@Bean
132+
public JpaItemWriter<CustomerCredit> itemWriter(EntityManagerFactory entityManagerFactory) {
133+
return new JpaItemWriterBuilder<CustomerCredit>().entityManagerFactory(entityManagerFactory).build();
134+
}
135+
136+
// infrastructure beans
137+
138+
@Bean
139+
public DataSource dataSource() throws Exception {
140+
PGSimpleDataSource datasource = new PGSimpleDataSource();
141+
datasource.setURL(postgres.getJdbcUrl());
142+
datasource.setUser(postgres.getUsername());
143+
datasource.setPassword(postgres.getPassword());
144+
return datasource;
145+
}
146+
147+
@Bean
148+
public JpaTransactionManager jpaTransactionManager(EntityManagerFactory entityManagerFactory) {
149+
return new JpaTransactionManager(entityManagerFactory);
150+
}
151+
152+
@Bean
153+
public EntityManagerFactory entityManagerFactory(PersistenceUnitManager persistenceUnitManager,
154+
DataSource dataSource) {
155+
LocalContainerEntityManagerFactoryBean factoryBean = new LocalContainerEntityManagerFactoryBean();
156+
factoryBean.setDataSource(dataSource);
157+
factoryBean.setPersistenceUnitManager(persistenceUnitManager);
158+
factoryBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
159+
factoryBean.afterPropertiesSet();
160+
return factoryBean.getObject();
161+
}
162+
163+
@Bean
164+
public PersistenceUnitManager persistenceUnitManager(DataSource dataSource) {
165+
DefaultPersistenceUnitManager persistenceUnitManager = new DefaultPersistenceUnitManager();
166+
persistenceUnitManager.setDefaultDataSource(dataSource);
167+
persistenceUnitManager.afterPropertiesSet();
168+
return persistenceUnitManager;
169+
}
170+
171+
}
172+
173+
}

0 commit comments

Comments
 (0)