Skip to content

Commit 95f11fa

Browse files
akappsfmbenhassine
authored andcommitted
Fix incorrect behaviour of RepositoryItemReader#jumpToItem() on restart
* Corrects unit tests with expected behavior. * Creates integration tests highlighting Hibernate lazy-loading failure if the reader has requested the page on open(). Issue #1074
1 parent 613f425 commit 95f11fa

File tree

10 files changed

+529
-71
lines changed

10 files changed

+529
-71
lines changed

build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,7 @@ project('spring-batch-infrastructure-tests') {
478478
testCompile("org.springframework:spring-oxm:$springVersion") {
479479
exclude group: 'commons-lang', module: 'commons-lang'
480480
}
481+
testCompile "org.springframework.data:spring-data-jpa:$springDataJpaVersion"
481482
testCompile "org.springframework:spring-jdbc:$springVersion"
482483
testCompile "org.springframework:spring-test:$springVersion"
483484
testCompile "org.mockito:mockito-core:$mockitoVersion"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
/*
2+
* Copyright 2020 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+
* http://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.item.database;
17+
18+
import static org.junit.Assert.assertEquals;
19+
20+
import java.util.List;
21+
22+
import org.junit.After;
23+
import org.junit.Test;
24+
import org.junit.runner.RunWith;
25+
26+
import org.springframework.batch.item.ExecutionContext;
27+
import org.springframework.batch.item.data.RepositoryItemReader;
28+
import org.springframework.batch.item.sample.books.Author;
29+
import org.springframework.batch.item.sample.books.Book;
30+
import org.springframework.batch.item.sample.books.data.SimpleService;
31+
import org.springframework.beans.factory.annotation.Autowired;
32+
import org.springframework.test.context.ContextConfiguration;
33+
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
34+
35+
36+
@RunWith(SpringJUnit4ClassRunner.class)
37+
@ContextConfiguration(locations = "RepositoryItemReaderCommonTests-context.xml")
38+
public class RepositoryItemReaderIntegrationTests {
39+
40+
private static final String CONTEXT_KEY = "RepositoryItemReader.read.count";
41+
42+
@Autowired
43+
private SimpleService service;
44+
45+
@Autowired
46+
private RepositoryItemReader<Author> reader;
47+
48+
@After
49+
public void reinitializeReader() {
50+
reader.close();
51+
}
52+
53+
@Test
54+
public void testReadFromFirstPos() throws Exception {
55+
service.openReader(new ExecutionContext());
56+
57+
final List<Book> books = service.nextAuthorBooks();
58+
59+
assertEquals("Books list size", 2, books.size());
60+
assertEquals("First book", "author 1 - book 1", books.get(0).getName());
61+
assertEquals("Second book", "author 1 - book 2", books.get(1).getName());
62+
}
63+
64+
@Test
65+
public void testReadFromWithinPage() throws Exception {
66+
reader.setCurrentItemCount(1);
67+
service.openReader(new ExecutionContext());
68+
69+
final List<Book> books = service.nextAuthorBooks();
70+
71+
assertEquals("Books list size", 2, books.size());
72+
assertEquals("First book", "author 2 - book 1", books.get(0).getName());
73+
assertEquals("Second book", "author 2 - book 2", books.get(1).getName());
74+
}
75+
76+
@Test
77+
public void testReadFromNewPage() throws Exception {
78+
reader.setPageSize(2);
79+
reader.setCurrentItemCount(2); // 3rd item = 1rst of page 2
80+
service.openReader(new ExecutionContext());
81+
82+
final List<Book> books = service.nextAuthorBooks();
83+
84+
assertEquals("Books list size", 2, books.size());
85+
assertEquals("First book", "author 3 - book 1", books.get(0).getName());
86+
assertEquals("Second book", "author 3 - book 2", books.get(1).getName());
87+
}
88+
89+
@Test
90+
public void testReadFromWithinPage_Restart() throws Exception {
91+
final ExecutionContext executionContext = new ExecutionContext();
92+
executionContext.putInt(CONTEXT_KEY, 1);
93+
service.openReader(executionContext);
94+
95+
final List<Book> books = service.nextAuthorBooks();
96+
97+
assertEquals("Books list size", 2, books.size());
98+
assertEquals("First book", "author 2 - book 1", books.get(0).getName());
99+
assertEquals("Second book", "author 2 - book 2", books.get(1).getName());
100+
}
101+
102+
@Test
103+
public void testReadFromNewPage_Restart() throws Exception {
104+
reader.setPageSize(2);
105+
final ExecutionContext executionContext = new ExecutionContext();
106+
executionContext.putInt(CONTEXT_KEY, 2);
107+
service.openReader(executionContext);
108+
109+
final List<Book> books = service.nextAuthorBooks();
110+
111+
assertEquals("Books list size", 2, books.size());
112+
assertEquals("First book", "author 3 - book 1", books.get(0).getName());
113+
assertEquals("Second book", "author 3 - book 2", books.get(1).getName());
114+
}
115+
116+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*
2+
* Copyright 2020 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+
* http://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.item.sample.books;
17+
18+
import javax.persistence.Basic;
19+
import javax.persistence.Entity;
20+
import javax.persistence.Id;
21+
import javax.persistence.JoinColumn;
22+
import javax.persistence.OneToMany;
23+
import javax.persistence.Table;
24+
import java.util.List;
25+
import java.util.Objects;
26+
27+
/**
28+
* Basic domain object with a lazy one-to-many association.
29+
*
30+
* @author Antoine Kapps
31+
*/
32+
@Entity
33+
@Table(name = "T_AUTHORS")
34+
public class Author {
35+
36+
@Id
37+
private int id;
38+
39+
@Basic
40+
private String name;
41+
42+
@OneToMany
43+
@JoinColumn(name = "AUTHOR_ID")
44+
private List<Book> books;
45+
46+
public int getId() {
47+
return id;
48+
}
49+
public String getName() {
50+
return name;
51+
}
52+
public List<Book> getBooks() {
53+
return books;
54+
}
55+
56+
@Override
57+
public boolean equals(Object o) {
58+
if (this == o) return true;
59+
if (o == null || getClass() != o.getClass()) return false;
60+
Author author = (Author) o;
61+
return id == author.id &&
62+
Objects.equals(name, author.name) &&
63+
Objects.equals(books, author.books);
64+
}
65+
66+
@Override
67+
public int hashCode() {
68+
return Objects.hash(id, name, books);
69+
}
70+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
* Copyright 2020 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+
* http://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.item.sample.books;
17+
18+
import javax.persistence.Entity;
19+
import javax.persistence.Id;
20+
import javax.persistence.Table;
21+
import java.util.Objects;
22+
23+
/**
24+
* Simple domain object implied in an association with {@link Author}.
25+
*
26+
* @author Antoine Kapps
27+
*/
28+
@Entity
29+
@Table(name = "T_BOOKS")
30+
public class Book {
31+
32+
@Id
33+
private int id;
34+
private String name;
35+
36+
public int getId() {
37+
return id;
38+
}
39+
public String getName() {
40+
return name;
41+
}
42+
43+
@Override
44+
public boolean equals(Object o) {
45+
if (this == o) return true;
46+
if (o == null || getClass() != o.getClass()) return false;
47+
Book book = (Book) o;
48+
return id == book.id &&
49+
Objects.equals(name, book.name);
50+
}
51+
52+
@Override
53+
public int hashCode() {
54+
return Objects.hash(id, name);
55+
}
56+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Copyright 2020 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+
* http://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.item.sample.books.data;
17+
18+
import org.springframework.batch.item.sample.books.Author;
19+
import org.springframework.data.repository.PagingAndSortingRepository;
20+
import org.springframework.stereotype.Repository;
21+
22+
23+
@Repository
24+
public interface AuthorRepository extends PagingAndSortingRepository<Author, Integer> {
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright 2020 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+
* http://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.item.sample.books.data;
17+
18+
import javax.transaction.Transactional;
19+
import java.util.ArrayList;
20+
import java.util.List;
21+
22+
import org.springframework.batch.item.ExecutionContext;
23+
import org.springframework.batch.item.data.RepositoryItemReader;
24+
import org.springframework.batch.item.sample.books.Author;
25+
import org.springframework.batch.item.sample.books.Book;
26+
27+
/**
28+
* A simple service based upon a {@link RepositoryItemReader}
29+
*/
30+
public class SimpleService {
31+
32+
private final RepositoryItemReader<Author> itemReader;
33+
34+
public SimpleService(RepositoryItemReader<Author> itemReader) {
35+
this.itemReader = itemReader;
36+
}
37+
38+
// Prepare the reader
39+
public void openReader(ExecutionContext executionContext) throws Exception {
40+
itemReader.open(executionContext);
41+
}
42+
43+
// Reads next Author and returns his (lazy-loaded) books, inside a transaction (simulates the chunk's transaction)
44+
@Transactional
45+
public List<Book> nextAuthorBooks() throws Exception {
46+
List<Book> result = new ArrayList<>();
47+
48+
final Author nextAuthor = itemReader.read();
49+
if (nextAuthor != null) {
50+
result.addAll(nextAuthor.getBooks());
51+
}
52+
53+
return result;
54+
}
55+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xmlns:jpa="http://www.springframework.org/schema/data/jpa"
4+
xmlns:tx="http://www.springframework.org/schema/tx"
5+
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
6+
http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd
7+
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
8+
9+
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
10+
<property name="driverClassName" value="org.hsqldb.jdbcDriver" />
11+
<property name="url" value="jdbc:hsqldb:mem:testjpamydb" />
12+
</bean>
13+
14+
<!-- Initialise the database -->
15+
<bean id="dataSourceInitializer" class="test.jdbc.datasource.DataSourceInitializer">
16+
<property name="dataSource" ref="dataSource"/>
17+
<property name="initialize" value="true"/>
18+
<property name="initScripts">
19+
<list>
20+
<value>classpath:org/springframework/batch/item/database/init-books-schema.sql</value>
21+
</list>
22+
</property>
23+
</bean>
24+
25+
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
26+
<property name="entityManagerFactory" ref="entityManagerFactory" />
27+
</bean>
28+
<tx:annotation-driven transaction-manager="transactionManager"/>
29+
30+
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
31+
<property name="dataSource" ref="dataSource"/>
32+
<property name="jpaVendorAdapter">
33+
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
34+
<property name="showSql" value="false"/>
35+
</bean>
36+
</property>
37+
<property name="packagesToScan" value="org.springframework.batch.item.sample.books"/>
38+
</bean>
39+
40+
<jpa:repositories base-package="org.springframework.batch.item.sample.books.data"/>
41+
42+
<bean id="authorRepositoryItemReader" class="org.springframework.batch.item.data.RepositoryItemReader">
43+
<property name="repository" ref="authorRepository"/>
44+
<property name="methodName" value="findAll"/>
45+
<property name="sort">
46+
<map>
47+
<entry key="id" value="ASC"/>
48+
</map>
49+
</property>
50+
</bean>
51+
52+
<bean id="myService" class="org.springframework.batch.item.sample.books.data.SimpleService">
53+
<constructor-arg ref="authorRepositoryItemReader"/>
54+
</bean>
55+
56+
</beans>

0 commit comments

Comments
 (0)