IN PROGRESS - BATCH-354: added an ItemReader implementation for JPA, modified CommonItemReaderTests and subclasses to run as JUnit4 tests

This commit is contained in:
trisberg
2008-08-02 06:12:48 +00:00
parent 5cd83e1bc5
commit b93903602b
18 changed files with 381 additions and 8 deletions

24
pom.xml
View File

@@ -414,7 +414,7 @@
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate</artifactId>
<version>3.2.3.ga</version>
<version>3.2.6.ga</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
@@ -442,6 +442,22 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>3.3.2.GA</version>
<exclusions>
<exclusion>
<groupId>javax.transaction</groupId>
<artifactId>jta</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-annotations</artifactId>
<version>3.3.1.GA</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib-nodep</artifactId>
@@ -521,6 +537,12 @@
<version>2.3.0</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>javax.persistence</groupId>
<artifactId>com.springsource.javax.persistence</artifactId>
<version>1.0.0</version>
<optional>true</optional>
</dependency>
<!-- Spring Dependencies -->
<dependency>
<groupId>org.springframework</groupId>

View File

@@ -179,6 +179,14 @@
<artifactId>hibernate</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-annotations</artifactId>
</dependency>
<dependency>
<!-- Needed by Hibernate if JTA is excluded -->
<groupId>org.apache.geronimo.specs</groupId>
@@ -198,6 +206,12 @@
<version>2.3.0</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>javax.persistence</groupId>
<artifactId>com.springsource.javax.persistence</artifactId>
<version>1.0.0</version>
<optional>true</optional>
</dependency>
<!-- Spring Dependencies -->
<dependency>
<groupId>org.springframework</groupId>

View File

@@ -0,0 +1,154 @@
package org.springframework.batch.item.database;
import org.springframework.batch.item.support.AbstractBufferedItemReaderItemStream;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.ClassUtils;
import org.springframework.util.Assert;
import org.springframework.orm.jpa.EntityManagerFactoryUtils;
import org.springframework.dao.DataAccessResourceFailureException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Query;
import javax.persistence.EntityManager;
import java.util.Map;
import java.util.HashMap;
import java.util.List;
/**
* {@link org.springframework.batch.item.ItemReader} for reading database records built on top of JPA.
*
* It executes the JPQL {@link #setQueryString(String)} to retrieve requested data. The query is
* executed using paged requests of a size specified in {@link #setPageSize(int)}. Additional pages
* are requested when needed as {@link #read()} method is called, returning
* an object corresponding to current position.
*
* The reader must be configured with an {@link javax.persistence.EntityManagerFactory} that is capable
* of participating in SPring managed transactions.
*
* The implementation is *not* thread-safe.
*
* @author Thomas Risberg
*/
public class JpaPagingItemReader<T> extends AbstractBufferedItemReaderItemStream<T> implements InitializingBean {
protected Log logger = LogFactory.getLog(getClass());
private EntityManagerFactory entityManagerFactory;
private final Map jpaPropertyMap = new HashMap();
private String queryString;
private boolean initialized = false;
private int current = 0;
private int page = 0;
private int pageSize = 10;
private List<T> entities;
public JpaPagingItemReader() {
setName(ClassUtils.getShortName(JpaPagingItemReader.class));
}
public void setEntityManagerFactory(EntityManagerFactory entityManagerFactory) {
this.entityManagerFactory = entityManagerFactory;
}
public void afterPropertiesSet() throws Exception {
Assert.notNull(entityManagerFactory);
Assert.hasLength(queryString);
Assert.isTrue(pageSize > 0, "pageSize must be greater than zero");
}
/**
* @param queryString JPQL query string
*/
public void setQueryString(String queryString) {
this.queryString = queryString;
}
@Override
public void mark() {
super.mark();
}
/**
* The number of entities to retreive at a time.
*
* @param pageSize the number of rows to fetch, 10 by default
* @see javax.persistence.Query#setMaxResults(int)
*/
public void setPageSize(int pageSize) {
this.pageSize = pageSize;
}
@Override
@SuppressWarnings("unchecked")
protected T doRead() throws Exception {
if (entities == null || current >= pageSize) {
EntityManager entityManager =
EntityManagerFactoryUtils.getTransactionalEntityManager(entityManagerFactory, jpaPropertyMap);
if (entityManager == null) {
throw new DataAccessResourceFailureException("Unable to obtain a transactional EntityManager");
}
Query query = entityManager.createQuery(queryString)
.setFirstResult(page * pageSize)
.setMaxResults(pageSize);
entities = query.getResultList();
entityManager.flush();
entityManager.clear();
if (current >= pageSize) {
current = 0;
}
page++;
}
if (current < entities.size()) {
return entities.get(current++);
}
else {
return null;
}
}
@Override
protected void doOpen() throws Exception {
Assert.state(!initialized, "Cannot open an already opened ItemReader, call close first");
initialized = true;
}
@Override
protected void doClose() throws Exception {
initialized = false;
}
@Override
protected void jumpToItem(int itemIndex) throws Exception {
page = itemIndex / pageSize;
current = itemIndex % pageSize;
logger.debug("Jumping to page " + page + " and index " + current);
}
}

View File

@@ -1,5 +1,9 @@
package org.springframework.batch.item;
import static org.junit.Assert.*;
import org.junit.Test;
import org.junit.Before;
import junit.framework.TestCase;
import org.springframework.batch.item.sample.Foo;
@@ -8,7 +12,7 @@ import org.springframework.batch.item.sample.Foo;
* Common tests for {@link ItemReader} implementations. Expected input is five
* {@link Foo} objects with values 1 to 5.
*/
public abstract class CommonItemReaderTests extends TestCase {
public abstract class CommonItemReaderTests {
protected ItemReader<Foo> tested;
@@ -17,13 +21,15 @@ public abstract class CommonItemReaderTests extends TestCase {
*/
protected abstract ItemReader<Foo> getItemReader() throws Exception;
protected void setUp() throws Exception {
@Before
public void setUp() throws Exception {
tested = getItemReader();
}
/**
* Regular scenario - read the input and eventually return null.
*/
@Test
public void testRead() throws Exception {
Foo foo1 = tested.read();
@@ -48,6 +54,7 @@ public abstract class CommonItemReaderTests extends TestCase {
* Rollback scenario - reader resets to last marked point. Note the commit
* interval can change dynamically.
*/
@Test
public void testReset() throws Exception {
Foo foo1 = tested.read();
assertEquals(1, foo1.getValue());
@@ -92,6 +99,7 @@ public abstract class CommonItemReaderTests extends TestCase {
* Empty input should be handled gracefully - null is returned on first
* read.
*/
@Test
public void testEmptyInput() throws Exception {
pointToEmptyInput(tested);
assertNull(tested.read());

View File

@@ -1,6 +1,11 @@
package org.springframework.batch.item;
import static org.junit.Assert.*;
import org.springframework.batch.item.sample.Foo;
import org.junit.Before;
import org.junit.After;
import org.junit.Test;
/**
* Common tests for readers implementing both {@link ItemReader} and
@@ -18,13 +23,14 @@ public abstract class CommonItemStreamItemReaderTests extends CommonItemReaderTe
return (ItemStream) tested;
}
protected void setUp() throws Exception {
@Before
public void setUp() throws Exception {
super.setUp();
testedAsStream().open(executionContext);
}
protected void tearDown() throws Exception {
super.tearDown();
@After
public void tearDown() throws Exception {
testedAsStream().close(executionContext);
}
@@ -33,6 +39,7 @@ public abstract class CommonItemStreamItemReaderTests extends CommonItemReaderTe
* reader and restore from restart data - the new input source should
* continue where the old one finished.
*/
@Test
public void testRestart() throws Exception {
testedAsStream().update(executionContext);
@@ -59,6 +66,7 @@ public abstract class CommonItemStreamItemReaderTests extends CommonItemReaderTe
* execution context, create new reader and restore from restart data - the
* new input source should continue where the old one finished.
*/
@Test
public void testResetAndRestart() throws Exception {
testedAsStream().update(executionContext);
@@ -87,6 +95,7 @@ public abstract class CommonItemStreamItemReaderTests extends CommonItemReaderTe
assertEquals(3, fooAfterRestart.getValue());
}
@Test
public void testReopen() throws Exception {
testedAsStream().update(executionContext);

View File

@@ -4,17 +4,21 @@ import javax.sql.DataSource;
import org.springframework.batch.item.CommonItemStreamItemReaderTests;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.junit.Before;
import org.junit.After;
public abstract class CommonDatabaseItemStreamItemReaderTests extends CommonItemStreamItemReaderTests {
private ClassPathXmlApplicationContext ctx;
protected void setUp() throws Exception {
@Before
public void setUp() throws Exception {
ctx = new ClassPathXmlApplicationContext("org/springframework/batch/item/database/data-source-context.xml");
super.setUp();
}
protected void tearDown() throws Exception {
@After
public void tearDown() throws Exception {
super.tearDown();
ctx.close();
}

View File

@@ -7,7 +7,10 @@ import org.springframework.batch.item.sample.Foo;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.orm.hibernate3.LocalSessionFactoryBean;
import org.junit.runner.RunWith;
import org.junit.internal.runners.JUnit4ClassRunner;
@RunWith(JUnit4ClassRunner.class)
public class HibernateCursorItemReaderCommonTests extends CommonDatabaseItemStreamItemReaderTests {
protected ItemReader<Foo> getItemReader() throws Exception {

View File

@@ -6,10 +6,13 @@ import org.springframework.batch.item.database.support.IbatisKeyCollector;
import org.springframework.batch.item.sample.Foo;
import org.springframework.core.io.ClassPathResource;
import org.springframework.orm.ibatis.SqlMapClientFactoryBean;
import org.junit.runner.RunWith;
import org.junit.internal.runners.JUnit4ClassRunner;
import com.ibatis.sqlmap.client.SqlMapClient;
@SuppressWarnings("unchecked")
@RunWith(JUnit4ClassRunner.class)
public class IbatisItemReaderCommonTests extends CommonDatabaseItemStreamItemReaderTests {
protected ItemReader<Foo> getItemReader() throws Exception {

View File

@@ -3,7 +3,11 @@ package org.springframework.batch.item.database;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.sample.Foo;
import org.junit.runner.RunWith;
import org.junit.internal.runners.JUnit4ClassRunner;
import org.junit.Test;
@RunWith(JUnit4ClassRunner.class)
public class JdbcCursorItemReaderCommonTests extends CommonDatabaseItemStreamItemReaderTests {
protected ItemReader<Foo> getItemReader() throws Exception {
@@ -24,6 +28,7 @@ public class JdbcCursorItemReaderCommonTests extends CommonDatabaseItemStreamIte
return result;
}
@Test
public void testRestartWithDriverSupportsAbsolute() throws Exception {
tested = getItemReader();
((JdbcCursorItemReader<Foo>) tested).setDriverSupportsAbsolute(true);

View File

@@ -0,0 +1,75 @@
package org.springframework.batch.item.database;
import org.springframework.batch.item.sample.Foo;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.item.CommonItemStreamItemReaderTests;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.transaction.annotation.Transactional;
import org.junit.runner.RunWith;
import org.junit.Test;
import javax.persistence.EntityManagerFactory;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class JpaPagingItemReaderCommonTests extends CommonItemStreamItemReaderTests {
@Autowired
private EntityManagerFactory entityManagerFactory;
protected ItemReader<Foo> getItemReader() throws Exception {
String jpqlQuery = "select f from Foo f";
JpaPagingItemReader<Foo> reader = new JpaPagingItemReader<Foo>();
reader.setQueryString(jpqlQuery);
reader.setEntityManagerFactory(entityManagerFactory);
reader.setPageSize(3);
reader.afterPropertiesSet();
reader.setSaveState(true);
return reader;
}
protected void pointToEmptyInput(ItemReader<Foo> tested) throws Exception {
JpaPagingItemReader<Foo> reader = (JpaPagingItemReader<Foo>) tested;
reader.close(new ExecutionContext());
reader.setQueryString("select f from Foo f where f.id = -1");
reader.afterPropertiesSet();
reader.open(new ExecutionContext());
}
@Transactional @Test
public void testRestart() throws Exception {
super.testRestart();
}
@Transactional @Test
public void testResetAndRestart() throws Exception {
super.testResetAndRestart();
}
@Transactional @Test
public void testReopen() throws Exception {
super.testReopen();
}
@Transactional @Test
public void testRead() throws Exception {
super.testRead();
}
@Transactional @Test
public void testReset() throws Exception {
super.testReset();
}
@Transactional @Test
public void testEmptyInput() throws Exception {
super.testEmptyInput();
}
}

View File

@@ -5,7 +5,10 @@ import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.database.support.SingleColumnJdbcKeyCollector;
import org.springframework.batch.item.sample.Foo;
import org.springframework.jdbc.core.JdbcTemplate;
import org.junit.runner.RunWith;
import org.junit.internal.runners.JUnit4ClassRunner;
@RunWith(JUnit4ClassRunner.class)
public class SingleColumnJdbcDrivingQueryItemReaderCommonTests extends CommonDatabaseItemStreamItemReaderTests {
protected ItemReader<Foo> getItemReader() throws Exception {

View File

@@ -8,7 +8,10 @@ import org.springframework.batch.item.file.mapping.FieldSetMapper;
import org.springframework.batch.item.sample.Foo;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;
import org.junit.runner.RunWith;
import org.junit.internal.runners.JUnit4ClassRunner;
@RunWith(JUnit4ClassRunner.class)
public class FlatFileItemReaderCommonTests extends CommonItemStreamItemReaderTests {
private static final String FOOS = "1 \n 2 \n 3 \n 4 \n 5 \n";

View File

@@ -10,7 +10,10 @@ import org.springframework.batch.item.file.mapping.FieldSetMapper;
import org.springframework.batch.item.sample.Foo;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;
import org.junit.runner.RunWith;
import org.junit.internal.runners.JUnit4ClassRunner;
@RunWith(JUnit4ClassRunner.class)
public class MultiResourceItemReaderFlatFileTests extends
CommonItemStreamItemReaderTests {

View File

@@ -1,5 +1,9 @@
package org.springframework.batch.item.file;
import static org.junit.Assert.*;
import org.junit.runner.RunWith;
import org.junit.internal.runners.JUnit4ClassRunner;
import java.util.Comparator;
import javax.xml.stream.XMLEventReader;
@@ -16,6 +20,7 @@ import org.springframework.batch.item.xml.StaxEventItemReader;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;
@RunWith(JUnit4ClassRunner.class)
public class MultiResourceItemReaderXmlTests extends CommonItemStreamItemReaderTests {
protected ItemReader<Foo> getItemReader() throws Exception {

View File

@@ -3,11 +3,18 @@ package org.springframework.batch.item.sample;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import javax.persistence.Entity;
import javax.persistence.Table;
import javax.persistence.Id;
/**
* Simple domain object for testing purposes.
*/
@Entity
@Table(name = "T_FOOS")
public class Foo {
@Id
private int id;
private String name;
private int value;

View File

@@ -1,5 +1,9 @@
package org.springframework.batch.item.xml;
import static org.junit.Assert.*;
import org.junit.runner.RunWith;
import org.junit.internal.runners.JUnit4ClassRunner;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.Attribute;
@@ -11,6 +15,7 @@ import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.sample.Foo;
import org.springframework.core.io.ByteArrayResource;
@RunWith(JUnit4ClassRunner.class)
public class StaxEventItemReaderCommonTests extends CommonItemStreamItemReaderTests {
private final static String FOOS = "<foos> <foo value=\"1\"/> <foo value=\"2\"/> <foo value=\"3\"/> <foo value=\"4\"/> <foo value=\"5\"/> </foos>";

View File

@@ -0,0 +1,13 @@
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
version="1.0">
<persistence-unit name="bar" transaction-type="RESOURCE_LOCAL">
<class>org.springframework.batch.item.sample.Foo</class>
<exclude-unlisted-classes>true</exclude-unlisted-classes>
</persistence-unit>
</persistence>

View File

@@ -0,0 +1,37 @@
<?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:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.5.xsd">
<bean id="dataSource" class="test.jdbc.datasource.InitializingDataSourceFactoryBean">
<property name="dataSource">
<bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="org.hsqldb.jdbcDriver" />
<property name="url" value="jdbc:hsqldb:mem:testdb" />
</bean>
</property>
<property name="initScripts" value="org/springframework/batch/item/database/init-foo-schema-hsqldb.sql" />
</bean>
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="persistenceUnitName" value="bar"/>
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="showSql" value="false"/>
</bean>
</property>
</bean>
<bean id="incrementerParent" class="org.springframework.jdbc.support.incrementer.HsqlMaxValueIncrementer"
abstract="true">
<property name="dataSource" ref="dataSource" />
<property name="columnName" value="ID" />
</bean>
</beans>