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
This commit is contained in:
Antoine Kapps
2016-12-20 00:04:29 +01:00
committed by Mahmoud Ben Hassine
parent 613f425336
commit 95f11facf4
10 changed files with 529 additions and 71 deletions

View File

@@ -0,0 +1,116 @@
/*
* Copyright 2020 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
*
* http://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.item.database;
import static org.junit.Assert.assertEquals;
import java.util.List;
import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.item.data.RepositoryItemReader;
import org.springframework.batch.item.sample.books.Author;
import org.springframework.batch.item.sample.books.Book;
import org.springframework.batch.item.sample.books.data.SimpleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "RepositoryItemReaderCommonTests-context.xml")
public class RepositoryItemReaderIntegrationTests {
private static final String CONTEXT_KEY = "RepositoryItemReader.read.count";
@Autowired
private SimpleService service;
@Autowired
private RepositoryItemReader<Author> reader;
@After
public void reinitializeReader() {
reader.close();
}
@Test
public void testReadFromFirstPos() throws Exception {
service.openReader(new ExecutionContext());
final List<Book> books = service.nextAuthorBooks();
assertEquals("Books list size", 2, books.size());
assertEquals("First book", "author 1 - book 1", books.get(0).getName());
assertEquals("Second book", "author 1 - book 2", books.get(1).getName());
}
@Test
public void testReadFromWithinPage() throws Exception {
reader.setCurrentItemCount(1);
service.openReader(new ExecutionContext());
final List<Book> books = service.nextAuthorBooks();
assertEquals("Books list size", 2, books.size());
assertEquals("First book", "author 2 - book 1", books.get(0).getName());
assertEquals("Second book", "author 2 - book 2", books.get(1).getName());
}
@Test
public void testReadFromNewPage() throws Exception {
reader.setPageSize(2);
reader.setCurrentItemCount(2); // 3rd item = 1rst of page 2
service.openReader(new ExecutionContext());
final List<Book> books = service.nextAuthorBooks();
assertEquals("Books list size", 2, books.size());
assertEquals("First book", "author 3 - book 1", books.get(0).getName());
assertEquals("Second book", "author 3 - book 2", books.get(1).getName());
}
@Test
public void testReadFromWithinPage_Restart() throws Exception {
final ExecutionContext executionContext = new ExecutionContext();
executionContext.putInt(CONTEXT_KEY, 1);
service.openReader(executionContext);
final List<Book> books = service.nextAuthorBooks();
assertEquals("Books list size", 2, books.size());
assertEquals("First book", "author 2 - book 1", books.get(0).getName());
assertEquals("Second book", "author 2 - book 2", books.get(1).getName());
}
@Test
public void testReadFromNewPage_Restart() throws Exception {
reader.setPageSize(2);
final ExecutionContext executionContext = new ExecutionContext();
executionContext.putInt(CONTEXT_KEY, 2);
service.openReader(executionContext);
final List<Book> books = service.nextAuthorBooks();
assertEquals("Books list size", 2, books.size());
assertEquals("First book", "author 3 - book 1", books.get(0).getName());
assertEquals("Second book", "author 3 - book 2", books.get(1).getName());
}
}

View File

@@ -0,0 +1,70 @@
/*
* Copyright 2020 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
*
* http://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.item.sample.books;
import javax.persistence.Basic;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import java.util.List;
import java.util.Objects;
/**
* Basic domain object with a lazy one-to-many association.
*
* @author Antoine Kapps
*/
@Entity
@Table(name = "T_AUTHORS")
public class Author {
@Id
private int id;
@Basic
private String name;
@OneToMany
@JoinColumn(name = "AUTHOR_ID")
private List<Book> books;
public int getId() {
return id;
}
public String getName() {
return name;
}
public List<Book> getBooks() {
return books;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Author author = (Author) o;
return id == author.id &&
Objects.equals(name, author.name) &&
Objects.equals(books, author.books);
}
@Override
public int hashCode() {
return Objects.hash(id, name, books);
}
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright 2020 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
*
* http://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.item.sample.books;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import java.util.Objects;
/**
* Simple domain object implied in an association with {@link Author}.
*
* @author Antoine Kapps
*/
@Entity
@Table(name = "T_BOOKS")
public class Book {
@Id
private int id;
private String name;
public int getId() {
return id;
}
public String getName() {
return name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Book book = (Book) o;
return id == book.id &&
Objects.equals(name, book.name);
}
@Override
public int hashCode() {
return Objects.hash(id, name);
}
}

View File

@@ -0,0 +1,25 @@
/*
* Copyright 2020 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
*
* http://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.item.sample.books.data;
import org.springframework.batch.item.sample.books.Author;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface AuthorRepository extends PagingAndSortingRepository<Author, Integer> {
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright 2020 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
*
* http://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.item.sample.books.data;
import javax.transaction.Transactional;
import java.util.ArrayList;
import java.util.List;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.item.data.RepositoryItemReader;
import org.springframework.batch.item.sample.books.Author;
import org.springframework.batch.item.sample.books.Book;
/**
* A simple service based upon a {@link RepositoryItemReader}
*/
public class SimpleService {
private final RepositoryItemReader<Author> itemReader;
public SimpleService(RepositoryItemReader<Author> itemReader) {
this.itemReader = itemReader;
}
// Prepare the reader
public void openReader(ExecutionContext executionContext) throws Exception {
itemReader.open(executionContext);
}
// Reads next Author and returns his (lazy-loaded) books, inside a transaction (simulates the chunk's transaction)
@Transactional
public List<Book> nextAuthorBooks() throws Exception {
List<Book> result = new ArrayList<>();
final Author nextAuthor = itemReader.read();
if (nextAuthor != null) {
result.addAll(nextAuthor.getBooks());
}
return result;
}
}

View File

@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jpa="http://www.springframework.org/schema/data/jpa"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="org.hsqldb.jdbcDriver" />
<property name="url" value="jdbc:hsqldb:mem:testjpamydb" />
</bean>
<!-- Initialise the database -->
<bean id="dataSourceInitializer" class="test.jdbc.datasource.DataSourceInitializer">
<property name="dataSource" ref="dataSource"/>
<property name="initialize" value="true"/>
<property name="initScripts">
<list>
<value>classpath:org/springframework/batch/item/database/init-books-schema.sql</value>
</list>
</property>
</bean>
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
<tx:annotation-driven transaction-manager="transactionManager"/>
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="showSql" value="false"/>
</bean>
</property>
<property name="packagesToScan" value="org.springframework.batch.item.sample.books"/>
</bean>
<jpa:repositories base-package="org.springframework.batch.item.sample.books.data"/>
<bean id="authorRepositoryItemReader" class="org.springframework.batch.item.data.RepositoryItemReader">
<property name="repository" ref="authorRepository"/>
<property name="methodName" value="findAll"/>
<property name="sort">
<map>
<entry key="id" value="ASC"/>
</map>
</property>
</bean>
<bean id="myService" class="org.springframework.batch.item.sample.books.data.SimpleService">
<constructor-arg ref="authorRepositoryItemReader"/>
</bean>
</beans>

View File

@@ -0,0 +1,24 @@
DROP TABLE T_BOOKS if exists;
DROP TABLE T_AUTHORS if exists;
CREATE TABLE T_AUTHORS (
ID BIGINT NOT NULL PRIMARY KEY,
NAME VARCHAR(45)
);
CREATE TABLE T_BOOKS(
ID BIGINT NOT NULL PRIMARY KEY,
NAME VARCHAR(45),
AUTHOR_ID BIGINT NOT NULL
);
INSERT INTO T_AUTHORS (id, name) VALUES (1, 'author 1');
INSERT INTO T_AUTHORS (id, name) VALUES (2, 'author 2');
INSERT INTO T_AUTHORS (id, name) VALUES (3, 'author 3');
INSERT INTO T_BOOKS (id, name, author_id) VALUES (1, 'author 1 - book 1', 1);
INSERT INTO T_BOOKS (id, name, author_id) VALUES (2, 'author 1 - book 2', 1);
INSERT INTO T_BOOKS (id, name, author_id) VALUES (3, 'author 2 - book 1', 2);
INSERT INTO T_BOOKS (id, name, author_id) VALUES (4, 'author 2 - book 2', 2);
INSERT INTO T_BOOKS (id, name, author_id) VALUES (5, 'author 3 - book 1', 3);
INSERT INTO T_BOOKS (id, name, author_id) VALUES (6, 'author 3 - book 2', 3);