Skip to content

Commit d755812

Browse files
authored
Bael 9136 new (#18291)
* Update CompositeItemReader * Fix deprecated method
1 parent 7b08fcb commit d755812

File tree

6 files changed

+280
-3
lines changed

6 files changed

+280
-3
lines changed

spring-batch-2/pom.xml

+22-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,20 @@
1717
</parent>
1818

1919
<dependencies>
20+
<dependency>
21+
<groupId>org.springframework.batch</groupId>
22+
<artifactId>spring-batch-core</artifactId>
23+
<version>${spring.batch.version}</version>
24+
</dependency>
25+
<dependency>
26+
<groupId>org.springframework.batch</groupId>
27+
<artifactId>spring-batch-infrastructure</artifactId>
28+
<version>${spring.batch.version}</version>
29+
</dependency>
30+
<dependency>
31+
<groupId>org.springframework.boot</groupId>
32+
<artifactId>spring-boot-starter-data-jpa</artifactId>
33+
</dependency>
2034
<dependency>
2135
<groupId>org.springframework.boot</groupId>
2236
<artifactId>spring-boot-starter-batch</artifactId>
@@ -69,6 +83,12 @@
6983
<version>${jettison.version}</version>
7084
<scope>compile</scope>
7185
</dependency>
86+
<dependency>
87+
<groupId>org.assertj</groupId>
88+
<artifactId>assertj-core</artifactId>
89+
<version>${assertj.version}</version>
90+
<scope>test</scope>
91+
</dependency>
7292
<dependency>
7393
<groupId>org.springframework.boot</groupId>
7494
<artifactId>spring-boot-starter-quartz</artifactId>
@@ -100,8 +120,9 @@
100120
<http-client.version>4.5.14</http-client.version>
101121
<jaxb.version>4.0.2</jaxb.version>
102122
<jettison.version>1.5.4</jettison.version>
103-
<spring.batch.version>5.1.2</spring.batch.version>
123+
<spring.batch.version>5.2.0</spring.batch.version>
104124
<awaitility.version>4.2.1</awaitility.version>
125+
<assertj.version>3.24.2</assertj.version>
105126
<start-class>com.baeldung.batch.SpringBootBatchProcessingApplication</start-class>
106127
</properties>
107128

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package com.baeldung.batch;
2+
3+
import org.springframework.batch.core.Job;
4+
import org.springframework.batch.core.Step;
5+
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
6+
import org.springframework.batch.core.job.builder.JobBuilder;
7+
import org.springframework.batch.core.repository.JobRepository;
8+
import org.springframework.batch.core.step.builder.StepBuilder;
9+
import org.springframework.batch.item.ItemReader;
10+
import org.springframework.batch.item.ItemWriter;
11+
import org.springframework.batch.item.database.JdbcCursorItemReader;
12+
import org.springframework.batch.item.database.builder.JdbcCursorItemReaderBuilder;
13+
import org.springframework.batch.item.file.FlatFileItemReader;
14+
import org.springframework.batch.item.file.builder.FlatFileItemReaderBuilder;
15+
import org.springframework.batch.item.support.CompositeItemReader;
16+
import org.springframework.beans.factory.annotation.Autowired;
17+
import org.springframework.beans.factory.annotation.Qualifier;
18+
import org.springframework.boot.jdbc.DataSourceBuilder;
19+
import org.springframework.context.annotation.Bean;
20+
import org.springframework.context.annotation.Configuration;
21+
import org.springframework.core.io.ClassPathResource;
22+
import org.springframework.transaction.PlatformTransactionManager;
23+
24+
import javax.sql.DataSource;
25+
import java.util.Arrays;
26+
27+
import com.baeldung.batch.model.Product;
28+
29+
@Configuration
30+
@EnableBatchProcessing
31+
public class CompositeItemReaderConfig {
32+
33+
@Bean
34+
public DataSource dataSource() {
35+
return DataSourceBuilder.create()
36+
.driverClassName("org.h2.Driver")
37+
.url("jdbc:h2:mem:batchdb;DB_CLOSE_DELAY=-1;")
38+
.username("sa")
39+
.password("")
40+
.build();
41+
}
42+
43+
44+
@Bean
45+
public FlatFileItemReader<Product> fileReader() {
46+
return new FlatFileItemReaderBuilder<Product>()
47+
.name("fileReader")
48+
.resource(new ClassPathResource("products.csv"))
49+
.delimited()
50+
.names("productId", "productName", "stock", "price")
51+
.linesToSkip(1)
52+
.targetType(Product.class)
53+
.build();
54+
}
55+
56+
@Bean
57+
public JdbcCursorItemReader<Product> dbReader() {
58+
return new JdbcCursorItemReaderBuilder<Product>()
59+
.name("dbReader")
60+
.dataSource(dataSource())
61+
.sql("SELECT productid, productname, stock, price FROM products")
62+
.rowMapper((rs, rowNum) -> new Product(
63+
rs.getLong("productid"),
64+
rs.getString("productname"),
65+
rs.getInt("stock"),
66+
rs.getBigDecimal("price")
67+
))
68+
.build();
69+
}
70+
71+
@Bean
72+
public CompositeItemReader<Product> compositeReader() {
73+
return new CompositeItemReader<>(Arrays.asList(fileReader(), dbReader()));
74+
}
75+
76+
@Bean
77+
public ItemWriter<Product> productWriter() {
78+
return items -> {
79+
for (Product product : items) {
80+
System.out.println("Writing product: " + product);
81+
}
82+
};
83+
}
84+
85+
@Bean
86+
public Step step(JobRepository jobRepository, PlatformTransactionManager transactionManager, @Qualifier("compositeReader") ItemReader<Product> compositeReader, ItemWriter<Product> productWriter) {
87+
return new StepBuilder("productStep", jobRepository)
88+
.<Product, Product>chunk(10, transactionManager)
89+
.reader(compositeReader)
90+
.writer(productWriter)
91+
.build();
92+
}
93+
94+
@Bean
95+
public Job productJob(JobRepository jobRepository, Step step) {
96+
return new JobBuilder("productJob", jobRepository)
97+
.start(step)
98+
.build();
99+
}
100+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package com.baeldung.batch.model;
2+
3+
import java.math.BigDecimal;
4+
import jakarta.persistence.*;
5+
6+
@Entity
7+
@Table(name = "products")
8+
public class Product {
9+
10+
@Id
11+
@GeneratedValue(strategy = GenerationType.IDENTITY)
12+
@Column(name = "productid")
13+
private Long productId;
14+
@Column(name = "productname")
15+
private String productName;
16+
@Column(name = "stock")
17+
private Integer stock;
18+
@Column(name = "price")
19+
private BigDecimal price;
20+
21+
public Product() {
22+
23+
}
24+
25+
public Product(Long productId, String productName, Integer stock, BigDecimal price) {
26+
this.productId = productId;
27+
this.productName = productName;
28+
this.stock = stock;
29+
this.price = price;
30+
}
31+
32+
public Long getProductId() {
33+
return productId;
34+
}
35+
36+
public void setProductId(Long productId) {
37+
this.productId = productId;
38+
}
39+
40+
public String getProductName() {
41+
return productName;
42+
}
43+
44+
public void setProductName(String productName) {
45+
this.productName = productName;
46+
}
47+
48+
public Integer getStock() {
49+
return stock;
50+
}
51+
52+
public void setStock(Integer stock) {
53+
this.stock = stock;
54+
}
55+
56+
public BigDecimal getPrice() {
57+
return price;
58+
}
59+
60+
public void setPrice(BigDecimal price) {
61+
this.price = price;
62+
}
63+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
productId,productName,stock,price
2+
101,Apple,50,1.99
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package com.baeldung.batch;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertNotNull;
5+
import static org.junit.jupiter.api.Assertions.assertNull;
6+
import static org.junit.jupiter.api.Assertions.assertTrue;
7+
import static org.mockito.Mockito.mock;
8+
import static org.mockito.Mockito.when;
9+
10+
import java.math.BigDecimal;
11+
import java.util.Arrays;
12+
13+
import org.junit.jupiter.api.BeforeEach;
14+
import org.junit.jupiter.api.Test;
15+
import org.springframework.batch.core.JobExecution;
16+
import org.springframework.batch.core.JobParameters;
17+
import org.springframework.batch.core.StepExecution;
18+
import org.springframework.batch.item.ExecutionContext;
19+
import org.springframework.batch.item.ItemReader;
20+
import org.springframework.batch.item.ItemStreamReader;
21+
import org.springframework.batch.item.support.CompositeItemReader;
22+
import org.springframework.beans.factory.annotation.Autowired;
23+
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
24+
import org.springframework.boot.test.context.SpringBootTest;
25+
import org.springframework.jdbc.core.JdbcTemplate;
26+
import org.springframework.test.context.ContextConfiguration;
27+
28+
import com.baeldung.batch.model.Product;
29+
30+
@SpringBootTest
31+
@EnableAutoConfiguration
32+
@ContextConfiguration(classes = { CompositeItemReaderConfig.class })
33+
public class CompositeItemReaderUnitTest {
34+
@Autowired
35+
private CompositeItemReader<Product> compositeReader;
36+
37+
@Autowired
38+
private JdbcTemplate jdbcTemplate;
39+
40+
@BeforeEach
41+
public void setUp() {
42+
jdbcTemplate.update("DELETE FROM products");
43+
jdbcTemplate.update("INSERT INTO products (productid, productname, stock, price) VALUES (?, ?, ?, ?)",
44+
102, "Banana", 30, 1.49);
45+
}
46+
47+
@Test
48+
public void givenTwoReaders_whenRead_thenProcessProductsInOrder() throws Exception {
49+
StepExecution stepExecution = new StepExecution("testStep", new JobExecution(1L, new JobParameters()), 1L);
50+
ExecutionContext executionContext = stepExecution.getExecutionContext();
51+
compositeReader.open(executionContext);
52+
53+
Product product1 = compositeReader.read();
54+
assertNotNull(product1);
55+
assertEquals(101, product1.getProductId());
56+
assertEquals("Apple", product1.getProductName());
57+
58+
Product product2 = compositeReader.read();
59+
assertNotNull(product2);
60+
assertEquals(102, product2.getProductId());
61+
assertEquals("Banana", product2.getProductName());
62+
}
63+
64+
@Test
65+
public void givenMultipleReader_whenOneReaderReturnNull_thenProcessDataFromNextReader() throws Exception {
66+
ItemStreamReader<Product> emptyReader = mock(ItemStreamReader.class);
67+
when(emptyReader.read()).thenReturn(null);
68+
69+
ItemStreamReader<Product> validReader = mock(ItemStreamReader.class);
70+
when(validReader.read()).thenReturn(new Product(103L, "Cherry", 20, BigDecimal.valueOf(2.99)), null);
71+
72+
CompositeItemReader<Product> compositeReader = new CompositeItemReader<>(Arrays.asList(emptyReader, validReader));
73+
74+
Product product = compositeReader.read();
75+
assertNotNull(product);
76+
assertEquals(103, product.getProductId());
77+
assertEquals("Cherry", product.getProductName());
78+
}
79+
80+
81+
@Test
82+
public void givenEmptyReaders_whenRead_thenReturnNull() throws Exception {
83+
ItemStreamReader<Product> emptyReader = mock(ItemStreamReader.class);
84+
when(emptyReader.read()).thenReturn(null);
85+
86+
CompositeItemReader<Product> compositeReader = new CompositeItemReader<>(Arrays.asList(emptyReader, emptyReader));
87+
88+
Product product = compositeReader.read();
89+
assertNull(product);
90+
}
91+
}

spring-batch-2/src/test/java/com/baeldung/batch/SpringBatchRetryIntegrationTest.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@
55
import static org.junit.jupiter.api.Assertions.assertEquals;
66
import static org.mockito.ArgumentMatchers.any;
77
import static org.mockito.Mockito.when;
8-
import static org.springframework.batch.test.AssertFile.assertFileEquals;
98

109
import org.apache.http.client.methods.CloseableHttpResponse;
1110
import org.apache.http.conn.ConnectTimeoutException;
1211
import org.apache.http.entity.StringEntity;
1312
import org.apache.http.impl.client.CloseableHttpClient;
13+
import org.junit.jupiter.api.Assertions;
1414
import org.junit.jupiter.api.Test;
1515
import org.mockito.Mock;
1616
import org.springframework.batch.core.ExitStatus;
@@ -76,7 +76,7 @@ public void whenEndpointFailsTwicePasses3rdTime_thenSuccess() throws Exception {
7676

7777
assertEquals("retryBatchJob", actualJobInstance.getJobName());
7878
assertEquals("COMPLETED", actualJobExitStatus.getExitCode());
79-
assertFileEquals(expectedResult, actualResult);
79+
org.assertj.core.api.Assertions.assertThat(actualResult.getFile()).hasSameTextualContentAs(expectedResult.getFile());
8080
}
8181

8282
private JobParameters defaultJobParameters() {

0 commit comments

Comments
 (0)