Add parallel processing sample (parallel version of football job)
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
package org.springframework.batch.sample.domain;
|
||||
|
||||
public class Player {
|
||||
import java.io.Serializable;
|
||||
|
||||
public class Player implements Serializable {
|
||||
|
||||
private String ID;
|
||||
private String lastName;
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
package org.springframework.batch.sample.item.processor;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.sql.Types;
|
||||
|
||||
import org.apache.commons.lang.SerializationUtils;
|
||||
import org.springframework.batch.execution.scope.StepContext;
|
||||
import org.springframework.batch.execution.scope.StepContextAware;
|
||||
import org.springframework.batch.item.ItemProcessor;
|
||||
import org.springframework.jdbc.core.support.JdbcDaoSupport;
|
||||
import org.springframework.jdbc.support.incrementer.DataFieldMaxValueIncrementer;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
public class StagingItemProcessor extends JdbcDaoSupport implements
|
||||
StepContextAware, ItemProcessor {
|
||||
|
||||
public static final String NEW = "N";
|
||||
public static final String DONE = "Y";
|
||||
public static final Object WORKING = "W";
|
||||
|
||||
private DataFieldMaxValueIncrementer incrementer;
|
||||
private StepContext stepContext;
|
||||
|
||||
/**
|
||||
* Check mandatory properties.
|
||||
*
|
||||
* @see org.springframework.dao.support.DaoSupport#initDao()
|
||||
*/
|
||||
protected void initDao() throws Exception {
|
||||
super.initDao();
|
||||
Assert
|
||||
.notNull(
|
||||
incrementer,
|
||||
"DataFieldMaxValueIncrementer is required - set the incrementer property in the "
|
||||
+ ClassUtils
|
||||
.getShortName(StagingItemProcessor.class));
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for injection of the step context.
|
||||
*
|
||||
* @param stepContext
|
||||
* the stepContext to set
|
||||
*/
|
||||
public void setStepContext(StepContext stepContext) {
|
||||
this.stepContext = stepContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for the key generator for the staging table.
|
||||
*
|
||||
* @param incrementer
|
||||
* the {@link DataFieldMaxValueIncrementer} to set
|
||||
*/
|
||||
public void setIncrementer(DataFieldMaxValueIncrementer incrementer) {
|
||||
this.incrementer = incrementer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize the item to the staging table, and add a NEW processed flag.
|
||||
*
|
||||
* @see org.springframework.batch.item.ItemProcessor#process(java.lang.Object)
|
||||
*/
|
||||
public void process(Object data) throws Exception {
|
||||
Long id = new Long(incrementer.nextLongValue());
|
||||
Long jobId = stepContext.getStepExecution().getJobExecution().getJobId();
|
||||
byte[] blob = SerializationUtils.serialize((Serializable) data);
|
||||
getJdbcTemplate()
|
||||
.update(
|
||||
"INSERT into BATCH_STAGING (ID, JOB_ID, VALUE, PROCESSED) values (?,?,?,?)",
|
||||
new Object[] { id, jobId, blob, NEW },
|
||||
new int[] { Types.BIGINT, Types.BIGINT, Types.BLOB, Types.CHAR});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,263 @@
|
||||
package org.springframework.batch.sample.item.provider;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.lang.SerializationUtils;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.batch.execution.scope.StepContext;
|
||||
import org.springframework.batch.execution.scope.StepContextAware;
|
||||
import org.springframework.batch.item.ItemProvider;
|
||||
import org.springframework.batch.item.ResourceLifecycle;
|
||||
import org.springframework.batch.repeat.synch.BatchTransactionSynchronizationManager;
|
||||
import org.springframework.batch.sample.item.processor.StagingItemProcessor;
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.dao.OptimisticLockingFailureException;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
import org.springframework.jdbc.core.support.JdbcDaoSupport;
|
||||
import org.springframework.jdbc.support.lob.DefaultLobHandler;
|
||||
import org.springframework.jdbc.support.lob.LobHandler;
|
||||
import org.springframework.transaction.support.TransactionSynchronization;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationAdapter;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
public class StagingItemProvider extends JdbcDaoSupport implements
|
||||
ItemProvider, ResourceLifecycle, DisposableBean,
|
||||
StepContextAware {
|
||||
|
||||
// Key for buffer in transaction synchronization manager
|
||||
private static final String BUFFER_KEY = StagingItemProvider.class.getName()+".BUFFER";
|
||||
|
||||
private static Log logger = LogFactory.getLog(StagingItemProvider.class);
|
||||
|
||||
private StepContext stepContext;
|
||||
|
||||
private LobHandler lobHandler = new DefaultLobHandler();
|
||||
|
||||
private Object lock = new Object();
|
||||
|
||||
private boolean initialized = false;
|
||||
|
||||
private volatile Iterator keys;
|
||||
|
||||
private final TransactionSynchronization synchronization = new StagingInputTransactionSynchronization();
|
||||
|
||||
/**
|
||||
*
|
||||
* @see org.springframework.batch.io.driving.DrivingQueryInputSource#close()
|
||||
*/
|
||||
public void close() {
|
||||
initialized = false;
|
||||
keys = null;
|
||||
if (TransactionSynchronizationManager.hasResource(BUFFER_KEY)) {
|
||||
TransactionSynchronizationManager.unbindResource(BUFFER_KEY);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
* @see org.springframework.batch.io.driving.DrivingQueryInputSource#destroy()
|
||||
*/
|
||||
public void destroy() throws Exception {
|
||||
close();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @see org.springframework.batch.io.driving.DrivingQueryInputSource#open()
|
||||
*/
|
||||
public void open() {
|
||||
Assert.state(keys == null || initialized,
|
||||
"Cannot open an already open StagingItemProvider"
|
||||
+ ", call close() first.");
|
||||
keys = retrieveKeys().iterator();
|
||||
logger.info("keys: " + keys);
|
||||
registerSynchronization();
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for injection of the step context.
|
||||
*
|
||||
* @param stepContext
|
||||
* the stepContext to set
|
||||
*/
|
||||
public void setStepContext(StepContext stepContext) {
|
||||
this.stepContext = stepContext;
|
||||
}
|
||||
|
||||
private List retrieveKeys() {
|
||||
|
||||
synchronized (lock) {
|
||||
|
||||
return getJdbcTemplate()
|
||||
.query(
|
||||
|
||||
"SELECT ID FROM BATCH_STAGING WHERE JOB_ID=? AND PROCESSED=? ORDER BY ID",
|
||||
|
||||
new Object[] {
|
||||
stepContext.getStepExecution()
|
||||
.getJobExecution().getJobId(),
|
||||
StagingItemProcessor.NEW },
|
||||
|
||||
new RowMapper() {
|
||||
public Object mapRow(ResultSet rs, int rowNum)
|
||||
throws SQLException {
|
||||
return new Long(rs.getLong(1));
|
||||
}
|
||||
}
|
||||
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public Object getKey(Object item) {
|
||||
return item;
|
||||
}
|
||||
|
||||
public Object next() throws Exception {
|
||||
Long id = read();
|
||||
|
||||
if (id == null) {
|
||||
return null;
|
||||
}
|
||||
Object result = getJdbcTemplate().queryForObject(
|
||||
"SELECT VALUE FROM BATCH_STAGING WHERE ID=?",
|
||||
new Object[] { id }, new RowMapper() {
|
||||
public Object mapRow(ResultSet rs, int rowNum)
|
||||
throws SQLException {
|
||||
byte[] blob = lobHandler.getBlobAsBytes(rs, 1);
|
||||
return SerializationUtils.deserialize(blob);
|
||||
}
|
||||
});
|
||||
// Update now - changes will rollback if there is a problem later.
|
||||
int count = getJdbcTemplate()
|
||||
.update(
|
||||
"UPDATE BATCH_STAGING SET PROCESSED=? WHERE ID=? AND PROCESSED=?",
|
||||
new Object[] { StagingItemProcessor.DONE, id,
|
||||
StagingItemProcessor.NEW });
|
||||
if (count != 1) {
|
||||
throw new OptimisticLockingFailureException(
|
||||
"The staging record with ID="
|
||||
+ id
|
||||
+ " was updated concurrently when trying to mark as complete.");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private Long read() {
|
||||
if (!initialized) {
|
||||
open();
|
||||
}
|
||||
|
||||
Long key = getBuffer().next();
|
||||
if (key == null) {
|
||||
synchronized (lock) {
|
||||
if (keys.hasNext()) {
|
||||
Long next = (Long) keys.next();
|
||||
getBuffer().add(next);
|
||||
key = next;
|
||||
logger.debug("Retrieved key from list: " + key);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logger.debug("Retrieved key from buffer: " + key);
|
||||
}
|
||||
return key;
|
||||
|
||||
}
|
||||
|
||||
private StagingBuffer getBuffer() {
|
||||
if (!TransactionSynchronizationManager.hasResource(BUFFER_KEY)) {
|
||||
TransactionSynchronizationManager.bindResource(BUFFER_KEY,
|
||||
new StagingBuffer());
|
||||
}
|
||||
return (StagingBuffer) TransactionSynchronizationManager
|
||||
.getResource(BUFFER_KEY);
|
||||
}
|
||||
|
||||
public boolean recover(Object data, Throwable cause) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register for Synchronization. This method is left protected because
|
||||
* clients of this class should not be registering for synchronization, but
|
||||
* rather only subclasses, at the appropriate time, i.e. when they are not
|
||||
* initialized.
|
||||
*/
|
||||
protected void registerSynchronization() {
|
||||
BatchTransactionSynchronizationManager
|
||||
.registerSynchronization(synchronization);
|
||||
}
|
||||
|
||||
/*
|
||||
* Called when a transaction has been committed.
|
||||
*
|
||||
* @see TransactionSynchronization#afterCompletion
|
||||
*/
|
||||
protected void transactionCommitted() {
|
||||
getBuffer().commit();
|
||||
}
|
||||
|
||||
/*
|
||||
* Called when a transaction has been rolled back.
|
||||
*
|
||||
* @see TransactionSynchronization#afterCompletion
|
||||
*/
|
||||
protected void transactionRolledBack() {
|
||||
getBuffer().rollback();
|
||||
}
|
||||
|
||||
/**
|
||||
* Encapsulates transaction events handling.
|
||||
*/
|
||||
private class StagingInputTransactionSynchronization extends
|
||||
TransactionSynchronizationAdapter {
|
||||
public void afterCompletion(int status) {
|
||||
if (status == TransactionSynchronization.STATUS_ROLLED_BACK) {
|
||||
transactionRolledBack();
|
||||
} else if (status == TransactionSynchronization.STATUS_COMMITTED) {
|
||||
transactionCommitted();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class StagingBuffer {
|
||||
|
||||
private List list = new ArrayList();
|
||||
private Iterator iter = new ArrayList().iterator();
|
||||
|
||||
public Long next() {
|
||||
if (iter.hasNext()) {
|
||||
return (Long) iter.next();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void add(Long next) {
|
||||
list.add(next);
|
||||
}
|
||||
|
||||
public void rollback() {
|
||||
iter = new ArrayList(list).iterator();
|
||||
}
|
||||
|
||||
public void commit() {
|
||||
list.clear();
|
||||
iter = new ArrayList().iterator();
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "list=" + list + "; iter.hasNext()=" + iter.hasNext();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
# Placeholders batch.*
|
||||
# for HSQLDB:
|
||||
batch.jdbc.driver=org.hsqldb.jdbcDriver
|
||||
batch.jdbc.url=jdbc:hsqldb:mem:testdb;sql.enforce_strict_size=true
|
||||
# batch.jdbc.url=jdbc:hsqldb:mem:testdb;sql.enforce_strict_size=true
|
||||
# use this one for a separate server process (so you can inspect the results)
|
||||
# batch.jdbc.url=jdbc:hsqldb:hsql://localhost:9005/samples
|
||||
batch.jdbc.url=jdbc:hsqldb:hsql://localhost:9005/samples
|
||||
batch.jdbc.user=sa
|
||||
batch.jdbc.password=
|
||||
batch.schema=
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
<!-- The tasklet used in this job will run in an infinite loop. This is useful for testing graceful shutdown from
|
||||
multiple environments. -->
|
||||
|
||||
<bean parent="stepScope"/>
|
||||
<bean parent="jobConfigurationRegistryBeanPostProcessor"/>
|
||||
<bean parent="stepScope" />
|
||||
<bean parent="jobConfigurationRegistryBeanPostProcessor" />
|
||||
|
||||
<bean id="loopJob" parent="simpleJob">
|
||||
<property name="steps">
|
||||
@@ -44,19 +44,40 @@
|
||||
<bean class="org.springframework.jmx.export.MBeanExporter">
|
||||
<property name="beans">
|
||||
<map>
|
||||
<entry
|
||||
key="spring:service=batch,bean=jobLauncher">
|
||||
<bean class="org.springframework.aop.framework.ProxyFactoryBean">
|
||||
<property name="target" ref="jobLauncher"/>
|
||||
<entry key="spring:service=batch,bean=jobLauncher">
|
||||
<bean
|
||||
class="org.springframework.aop.framework.ProxyFactoryBean">
|
||||
<property name="target" ref="jobLauncher" />
|
||||
<property name="interfaces">
|
||||
<list>
|
||||
<value>org.springframework.batch.execution.bootstrap.support.ExportedJobLauncher</value>
|
||||
<value>
|
||||
org.springframework.batch.execution.bootstrap.support.ExportedJobLauncher
|
||||
</value>
|
||||
</list>
|
||||
</property>
|
||||
<property name="interceptorNames" value="convertingMethodInterceptor"/>
|
||||
<property name="interceptorNames"
|
||||
value="convertingMethodInterceptor" />
|
||||
</bean>
|
||||
</entry>
|
||||
<entry
|
||||
key="spring:service=batch,bean=notificationPublisher"
|
||||
value-ref="notificationPublisher" />
|
||||
<entry
|
||||
key="spring:service=batch,bean=configurationLoader">
|
||||
<bean
|
||||
class="org.springframework.aop.framework.ProxyFactoryBean">
|
||||
<property name="target" ref="loader" />
|
||||
<property name="interfaces">
|
||||
<list>
|
||||
<value>
|
||||
org.springframework.batch.sample.ExportedJobConfigurationLoader
|
||||
</value>
|
||||
</list>
|
||||
</property>
|
||||
<property name="interceptorNames"
|
||||
value="convertingMethodInterceptor" />
|
||||
</bean>
|
||||
</entry>
|
||||
<entry key="spring:service=batch,bean=notificationPublisher" value-ref="notificationPublisher"/>
|
||||
</map>
|
||||
</property>
|
||||
<property name="assembler">
|
||||
@@ -67,15 +88,22 @@
|
||||
<entry
|
||||
key="spring:service=batch,bean=jobLauncher"
|
||||
value="org.springframework.batch.execution.bootstrap.support.ExportedJobLauncher" />
|
||||
<entry
|
||||
key="spring:service=batch,bean=configurationLoader"
|
||||
value="org.springframework.batch.sample.JobConfigurationLoader" />
|
||||
</map>
|
||||
</property>
|
||||
</bean>
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
<bean id="notificationPublisher" class="org.springframework.batch.execution.bootstrap.JobExecutionNotificationPublisher"/>
|
||||
|
||||
<bean id="convertingMethodInterceptor" class="org.springframework.batch.execution.bootstrap.support.TypeConverterMethodInterceptor"/>
|
||||
|
||||
<bean id="notificationPublisher"
|
||||
class="org.springframework.batch.execution.bootstrap.JobExecutionNotificationPublisher" />
|
||||
|
||||
<bean id="convertingMethodInterceptor"
|
||||
class="org.springframework.batch.execution.bootstrap.support.TypeConverterMethodInterceptor">
|
||||
<property name="convertException" value="true" />
|
||||
</bean>
|
||||
|
||||
<bean id="logAdvice"
|
||||
class="org.springframework.batch.sample.advice.MethodExecutionLogAdvice" />
|
||||
@@ -93,7 +121,8 @@
|
||||
<bean id="jobLauncher"
|
||||
class="org.springframework.batch.execution.launch.SimpleJobLauncher">
|
||||
<property name="jobRepository" ref="simpleJobRepository" />
|
||||
<property name="jobConfigurationLocator" ref="jobConfigurationRegistry"/>
|
||||
<property name="jobConfigurationLocator"
|
||||
ref="jobConfigurationRegistry" />
|
||||
<property name="jobExecutor">
|
||||
<bean parent="jobExecutor">
|
||||
<property name="stepExecutorFactory">
|
||||
@@ -110,7 +139,12 @@
|
||||
class="org.springframework.core.task.SimpleAsyncTaskExecutor" />
|
||||
</property>
|
||||
<property name="autoStart" value="false" />
|
||||
<property name="jobConfigurationName" value="loopJob"/>
|
||||
<property name="jobConfigurationName" value="loopJob" />
|
||||
</bean>
|
||||
|
||||
|
||||
<bean id="loader"
|
||||
class="org.springframework.batch.sample.DefaultJobConfigurationLoader">
|
||||
<property name="registry" ref="jobConfigurationRegistry" />
|
||||
</bean>
|
||||
|
||||
</beans>
|
||||
238
spring-batch-samples/src/main/resources/jobs/parallelJob.xml
Normal file
238
spring-batch-samples/src/main/resources/jobs/parallelJob.xml
Normal file
@@ -0,0 +1,238 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:aop="http://www.springframework.org/schema/aop"
|
||||
xmlns:tx="http://www.springframework.org/schema/tx"
|
||||
xmlns:p="http://www.springframework.org/schema/p"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
|
||||
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
|
||||
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">
|
||||
|
||||
<bean parent="stepScope" />
|
||||
<bean parent="jobConfigurationRegistryBeanPostProcessor" />
|
||||
|
||||
<bean id="parallelJob"
|
||||
class="org.springframework.batch.core.configuration.JobConfiguration">
|
||||
<property name="restartable" value="true" />
|
||||
<property name="startLimit" value="100" />
|
||||
<property name="steps">
|
||||
<list>
|
||||
<bean id="staging" parent="simpleStep">
|
||||
<property name="tasklet">
|
||||
<bean
|
||||
class="org.springframework.batch.execution.tasklet.RestartableItemProviderTasklet">
|
||||
<property name="itemProvider">
|
||||
<bean
|
||||
class="org.springframework.batch.item.provider.InputSourceItemProvider">
|
||||
<property name="inputSource"
|
||||
ref="playerFileInputSource" />
|
||||
</bean>
|
||||
</property>
|
||||
<property name="itemProcessor">
|
||||
<bean
|
||||
class="org.springframework.batch.sample.item.processor.StagingItemProcessor"
|
||||
scope="step">
|
||||
<aop:scoped-proxy />
|
||||
<property name="dataSource"
|
||||
ref="dataSource" />
|
||||
<property name="incrementer">
|
||||
<bean
|
||||
parent="incrementerParent">
|
||||
<property
|
||||
name="incrementerName" value="BATCH_STAGING_SEQ" />
|
||||
</bean>
|
||||
</property>
|
||||
</bean>
|
||||
</property>
|
||||
</bean>
|
||||
</property>
|
||||
<property name="commitInterval" value="10000"></property>
|
||||
<property name="startLimit" value="100" />
|
||||
<property name="saveRestartData" value="true" />
|
||||
<property name="allowStartIfComplete" value="false" />
|
||||
</bean>
|
||||
<bean id="playerload"
|
||||
class="org.springframework.batch.execution.step.RepeatOperationsStepConfiguration">
|
||||
<property name="tasklet">
|
||||
<bean
|
||||
class="org.springframework.batch.execution.tasklet.ItemProviderProcessTasklet">
|
||||
<property name="itemProvider">
|
||||
<bean
|
||||
class="org.springframework.batch.sample.item.provider.StagingItemProvider"
|
||||
scope="step">
|
||||
<aop:scoped-proxy />
|
||||
<property name="dataSource"
|
||||
ref="dataSource" />
|
||||
</bean>
|
||||
</property>
|
||||
<property name="itemProcessor">
|
||||
<bean
|
||||
class="org.springframework.batch.sample.item.processor.PlayerItemProcessor">
|
||||
<property name="playerDao">
|
||||
<bean
|
||||
class="org.springframework.batch.sample.dao.SqlPlayerDao">
|
||||
<property name="dataSource"
|
||||
ref="dataSource" />
|
||||
</bean>
|
||||
</property>
|
||||
</bean>
|
||||
</property>
|
||||
</bean>
|
||||
</property>
|
||||
<property name="chunkOperations">
|
||||
<bean
|
||||
class="org.springframework.batch.repeat.support.RepeatTemplate">
|
||||
<property name="completionPolicy">
|
||||
<bean
|
||||
class="org.springframework.batch.repeat.policy.SimpleCompletionPolicy">
|
||||
<property name="chunkSize"
|
||||
value="10" />
|
||||
</bean>
|
||||
</property>
|
||||
</bean>
|
||||
</property>
|
||||
<property name="stepOperations">
|
||||
<bean
|
||||
class="org.springframework.batch.repeat.support.TaskExecutorRepeatTemplate">
|
||||
<property name="taskExecutor">
|
||||
<bean
|
||||
class="org.springframework.core.task.SimpleAsyncTaskExecutor" />
|
||||
</property>
|
||||
</bean>
|
||||
</property>
|
||||
</bean>
|
||||
<bean id="gameLoad"
|
||||
class="org.springframework.batch.execution.step.SimpleStepConfiguration">
|
||||
<property name="commitInterval" value="1000" />
|
||||
<property name="startLimit" value="100" />
|
||||
<property name="saveRestartData" value="true" />
|
||||
<property name="tasklet">
|
||||
<bean
|
||||
class="org.springframework.batch.execution.tasklet.RestartableItemProviderTasklet">
|
||||
<property name="itemProvider">
|
||||
<bean
|
||||
class="org.springframework.batch.item.provider.InputSourceItemProvider">
|
||||
<property name="inputSource"
|
||||
ref="gameFileInputSource" />
|
||||
</bean>
|
||||
</property>
|
||||
<property name="itemProcessor">
|
||||
<bean
|
||||
class="org.springframework.batch.item.processor.ItemWriterItemProcessor">
|
||||
<property name="itemWriter">
|
||||
<bean
|
||||
class="org.springframework.batch.sample.dao.SqlGameDao">
|
||||
<property name="dataSource"
|
||||
ref="dataSource" />
|
||||
</bean>
|
||||
</property>
|
||||
</bean>
|
||||
</property>
|
||||
</bean>
|
||||
</property>
|
||||
</bean>
|
||||
<bean id="playerSummarization"
|
||||
class="org.springframework.batch.execution.step.SimpleStepConfiguration">
|
||||
<property name="commitInterval" value="100" />
|
||||
<property name="startLimit" value="100" />
|
||||
<property name="saveRestartData" value="true" />
|
||||
<property name="tasklet">
|
||||
<bean
|
||||
class="org.springframework.batch.execution.tasklet.RestartableItemProviderTasklet">
|
||||
<property name="itemProvider">
|
||||
<bean
|
||||
class="org.springframework.batch.item.provider.InputSourceItemProvider">
|
||||
<property name="inputSource"
|
||||
ref="playerSummarizationSource" />
|
||||
</bean>
|
||||
</property>
|
||||
<property name="itemProcessor">
|
||||
<bean
|
||||
class="org.springframework.batch.item.processor.ItemWriterItemProcessor">
|
||||
<property name="itemWriter">
|
||||
<bean
|
||||
class="org.springframework.batch.sample.dao.SqlPlayerSummaryDao">
|
||||
<property name="dataSource"
|
||||
ref="dataSource" />
|
||||
</bean>
|
||||
</property>
|
||||
</bean>
|
||||
</property>
|
||||
</bean>
|
||||
</property>
|
||||
</bean>
|
||||
</list>
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
<bean id="playerFileInputSource"
|
||||
class="org.springframework.batch.io.file.support.DefaultFlatFileInputSource"
|
||||
scope="step">
|
||||
<aop:scoped-proxy />
|
||||
<property name="resource"
|
||||
value="classpath:data/footballjob/input/player.csv" />
|
||||
<property name="tokenizer">
|
||||
<bean
|
||||
class="org.springframework.batch.io.file.support.transform.DelimitedLineTokenizer">
|
||||
<property name="names"
|
||||
value="ID,lastName,firstName,position,birthYear,debutYear" />
|
||||
</bean>
|
||||
</property>
|
||||
<property name="fieldSetMapper">
|
||||
<bean
|
||||
class="org.springframework.batch.sample.mapping.PlayerMapper" />
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
<bean id="gameFileInputSource"
|
||||
class="org.springframework.batch.io.file.support.DefaultFlatFileInputSource"
|
||||
scope="step">
|
||||
<aop:scoped-proxy />
|
||||
<property name="resource"
|
||||
value="classpath:data/footballjob/input/games.csv" />
|
||||
<property name="tokenizer">
|
||||
<bean
|
||||
class="org.springframework.batch.io.file.support.transform.DelimitedLineTokenizer">
|
||||
<property name="names"
|
||||
value="id,year,team,week,opponent,completes,attempts,passingYards,passingTd,interceptions,rushes,rushYards,receptions,receptionYards,totalTd" />
|
||||
</bean>
|
||||
</property>
|
||||
<property name="fieldSetMapper">
|
||||
<bean
|
||||
class="org.springframework.batch.sample.mapping.GameMapper" />
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
<bean id="playerSummarizationSource"
|
||||
class="org.springframework.batch.io.cursor.JdbcCursorInputSource"
|
||||
scope="step">
|
||||
<aop:scoped-proxy />
|
||||
<property name="dataSource" ref="dataSource" />
|
||||
<property name="mapper">
|
||||
<bean
|
||||
class="org.springframework.batch.sample.mapping.PlayerSummaryMapper" />
|
||||
</property>
|
||||
<property name="sql">
|
||||
<value>
|
||||
SELECT games.player_id, games.year_no, SUM(COMPLETES),
|
||||
SUM(ATTEMPTS), SUM(PASSING_YARDS), SUM(PASSING_TD),
|
||||
SUM(INTERCEPTIONS), SUM(RUSHES), SUM(RUSH_YARDS),
|
||||
SUM(RECEPTIONS), SUM(RECEPTIONS_YARDS), SUM(TOTAL_TD)
|
||||
from games, players where players.player_id =
|
||||
games.player_id group by games.player_id, games.year_no
|
||||
</value>
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
<aop:config>
|
||||
<aop:aspect id="moduleLogging" ref="itemProcessorLogAdvice">
|
||||
|
||||
<aop:after
|
||||
pointcut="execution( * org.springframework.batch.item.ItemProcessor+.process(Object)) and args(item)"
|
||||
method="doStronglyTypedLogging" />
|
||||
|
||||
</aop:aspect>
|
||||
</aop:config>
|
||||
|
||||
</beans>
|
||||
@@ -1,14 +1,36 @@
|
||||
package org.springframework.batch.sample;
|
||||
|
||||
public class FootballJobFunctionalTests extends AbstractValidatingBatchLauncherTests {
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.springframework.batch.sample.item.processor.StagingItemProcessor;
|
||||
import org.springframework.jdbc.core.JdbcOperations;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
|
||||
public class FootballJobFunctionalTests extends
|
||||
AbstractValidatingBatchLauncherTests {
|
||||
|
||||
private JdbcOperations jdbcTemplate;
|
||||
|
||||
public void setDataSource(DataSource dataSource) {
|
||||
this.jdbcTemplate = new JdbcTemplate(dataSource);
|
||||
}
|
||||
|
||||
protected String[] getConfigLocations() {
|
||||
return new String[] {"jobs/footballJob.xml###"};
|
||||
return new String[] { "jobs/parallelJob.xml" };
|
||||
}
|
||||
|
||||
protected void validatePostConditions() throws Exception {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
protected void validatePostConditions() throws Exception {
|
||||
int count;
|
||||
count = jdbcTemplate.queryForInt(
|
||||
"SELECT COUNT(*) from BATCH_STAGING where PROCESSED=?",
|
||||
new Object[] {StagingItemProcessor.NEW});
|
||||
assertEquals(0, count);
|
||||
int total = jdbcTemplate.queryForInt(
|
||||
"SELECT COUNT(*) from BATCH_STAGING");
|
||||
count = jdbcTemplate.queryForInt(
|
||||
"SELECT COUNT(*) from BATCH_STAGING where PROCESSED=?",
|
||||
new Object[] {StagingItemProcessor.DONE});
|
||||
assertEquals(total, count);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package org.springframework.batch.sample.item.processor;
|
||||
|
||||
import org.springframework.batch.core.domain.JobExecution;
|
||||
import org.springframework.batch.core.domain.JobInstance;
|
||||
import org.springframework.batch.core.domain.StepExecution;
|
||||
import org.springframework.batch.core.domain.StepInstance;
|
||||
import org.springframework.batch.core.runtime.SimpleJobIdentifier;
|
||||
import org.springframework.batch.execution.scope.SimpleStepContext;
|
||||
import org.springframework.batch.execution.scope.StepSynchronizationManager;
|
||||
import org.springframework.test.AbstractTransactionalDataSourceSpringContextTests;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
public class StagingItemProcessorTests extends
|
||||
AbstractTransactionalDataSourceSpringContextTests {
|
||||
|
||||
private StagingItemProcessor processor;
|
||||
|
||||
public void setProcessor(StagingItemProcessor processor) {
|
||||
this.processor = processor;
|
||||
}
|
||||
|
||||
protected String[] getConfigLocations() {
|
||||
return new String[] { ClassUtils.addResourcePathToPackagePath(
|
||||
StagingItemProcessor.class, "staging-test-context.xml") };
|
||||
}
|
||||
|
||||
protected void prepareTestInstance() throws Exception {
|
||||
SimpleStepContext stepScopeContext = StepSynchronizationManager
|
||||
.open();
|
||||
stepScopeContext.setStepExecution(new StepExecution(new StepInstance(
|
||||
new Long(11)), new JobExecution(new JobInstance(
|
||||
new SimpleJobIdentifier("job"), new Long(12)))));
|
||||
super.prepareTestInstance();
|
||||
}
|
||||
|
||||
public void testProcessInsertsNewItem() throws Exception {
|
||||
int before = getJdbcTemplate().queryForInt(
|
||||
"SELECT COUNT(*) from BATCH_STAGING");
|
||||
processor.process("FOO");
|
||||
int after = getJdbcTemplate().queryForInt(
|
||||
"SELECT COUNT(*) from BATCH_STAGING");
|
||||
assertEquals(before + 1, after);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
package org.springframework.batch.sample.item.provider;
|
||||
|
||||
import org.springframework.batch.core.domain.JobExecution;
|
||||
import org.springframework.batch.core.domain.JobInstance;
|
||||
import org.springframework.batch.core.domain.StepExecution;
|
||||
import org.springframework.batch.core.domain.StepInstance;
|
||||
import org.springframework.batch.core.runtime.SimpleJobIdentifier;
|
||||
import org.springframework.batch.execution.scope.SimpleStepContext;
|
||||
import org.springframework.batch.execution.scope.StepSynchronizationManager;
|
||||
import org.springframework.batch.repeat.context.RepeatContextSupport;
|
||||
import org.springframework.batch.repeat.synch.BatchTransactionSynchronizationManager;
|
||||
import org.springframework.batch.repeat.synch.RepeatSynchronizationManager;
|
||||
import org.springframework.batch.sample.item.processor.StagingItemProcessor;
|
||||
import org.springframework.test.AbstractTransactionalDataSourceSpringContextTests;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
public class StagingItemProviderTests extends
|
||||
AbstractTransactionalDataSourceSpringContextTests {
|
||||
|
||||
private StagingItemProcessor processor;
|
||||
private StagingItemProvider provider;
|
||||
private Long jobId;
|
||||
|
||||
public void setProcessor(StagingItemProcessor processor) {
|
||||
this.processor = processor;
|
||||
}
|
||||
|
||||
public void setProvider(StagingItemProvider provider) {
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
protected String[] getConfigLocations() {
|
||||
return new String[] { ClassUtils.addResourcePathToPackagePath(
|
||||
StagingItemProcessor.class, "staging-test-context.xml") };
|
||||
}
|
||||
|
||||
protected void prepareTestInstance() throws Exception {
|
||||
SimpleStepContext stepScopeContext = StepSynchronizationManager.open();
|
||||
jobId = new Long(11);
|
||||
stepScopeContext.setStepExecution(new StepExecution(new StepInstance(
|
||||
new Long(12)), new JobExecution(new JobInstance(
|
||||
new SimpleJobIdentifier("job"), jobId))));
|
||||
RepeatSynchronizationManager.register(new RepeatContextSupport(null));
|
||||
super.prepareTestInstance();
|
||||
}
|
||||
|
||||
protected void onSetUpInTransaction() throws Exception {
|
||||
processor.process("FOO");
|
||||
processor.process("BAR");
|
||||
processor.process("SPAM");
|
||||
processor.process("BUCKET");
|
||||
}
|
||||
|
||||
protected void onTearDownAfterTransaction() throws Exception {
|
||||
provider.close();
|
||||
getJdbcTemplate().update("DELETE FROM BATCH_STAGING");
|
||||
}
|
||||
|
||||
public void testProviderUpdatesProcessIndicator() throws Exception {
|
||||
|
||||
long id = getJdbcTemplate().queryForLong(
|
||||
"SELECT MIN(ID) from BATCH_STAGING where JOB_ID=?",
|
||||
new Object[] { jobId });
|
||||
String before = (String) getJdbcTemplate().queryForObject(
|
||||
"SELECT PROCESSED from BATCH_STAGING where ID=?",
|
||||
new Object[] { new Long(id) }, String.class);
|
||||
assertEquals(StagingItemProcessor.NEW, before);
|
||||
|
||||
Object item = provider.next();
|
||||
assertEquals("FOO", item);
|
||||
|
||||
String after = (String) getJdbcTemplate().queryForObject(
|
||||
"SELECT PROCESSED from BATCH_STAGING where ID=?",
|
||||
new Object[] { new Long(id) }, String.class);
|
||||
assertEquals(StagingItemProcessor.DONE, after);
|
||||
|
||||
}
|
||||
|
||||
public void testUpdateProcessIndicatorAfterCommit() throws Exception {
|
||||
testProviderUpdatesProcessIndicator();
|
||||
setComplete();
|
||||
endTransaction();
|
||||
startNewTransaction();
|
||||
long id = getJdbcTemplate().queryForLong(
|
||||
"SELECT MIN(ID) from BATCH_STAGING where JOB_ID=?",
|
||||
new Object[] { jobId });
|
||||
String before = (String) getJdbcTemplate().queryForObject(
|
||||
"SELECT PROCESSED from BATCH_STAGING where ID=?",
|
||||
new Object[] { new Long(id) }, String.class);
|
||||
assertEquals(StagingItemProcessor.DONE, before);
|
||||
}
|
||||
|
||||
public void testProviderRollsBackMultipleTimes() throws Exception {
|
||||
|
||||
setComplete();
|
||||
endTransaction();
|
||||
startNewTransaction();
|
||||
// After a rollback we have to resynchronize the TX to simulate a real batch
|
||||
BatchTransactionSynchronizationManager.resynchronize();
|
||||
|
||||
int count = getJdbcTemplate().queryForInt(
|
||||
"SELECT COUNT(*) from BATCH_STAGING where JOB_ID=? AND PROCESSED=?",
|
||||
new Object[] { jobId, StagingItemProcessor.NEW });
|
||||
assertEquals(4, count);
|
||||
|
||||
Object item = provider.next();
|
||||
assertEquals("FOO", item);
|
||||
item = provider.next();
|
||||
assertEquals("BAR", item);
|
||||
|
||||
endTransaction();
|
||||
startNewTransaction();
|
||||
BatchTransactionSynchronizationManager.resynchronize();
|
||||
|
||||
item = provider.next();
|
||||
assertEquals("FOO", item);
|
||||
item = provider.next();
|
||||
assertEquals("BAR", item);
|
||||
item = provider.next();
|
||||
assertEquals("SPAM", item);
|
||||
|
||||
endTransaction();
|
||||
startNewTransaction();
|
||||
BatchTransactionSynchronizationManager.resynchronize();
|
||||
|
||||
item = provider.next();
|
||||
assertEquals("FOO", item);
|
||||
|
||||
}
|
||||
|
||||
public void testProviderRollsBackProcessIndicator() throws Exception {
|
||||
|
||||
setComplete();
|
||||
endTransaction();
|
||||
startNewTransaction();
|
||||
// After a rollback we have to resynchronize the TX to simulate a real batch
|
||||
BatchTransactionSynchronizationManager.resynchronize();
|
||||
|
||||
long id = getJdbcTemplate().queryForLong(
|
||||
"SELECT MIN(ID) from BATCH_STAGING where JOB_ID=?",
|
||||
new Object[] { jobId });
|
||||
String before = (String) getJdbcTemplate().queryForObject(
|
||||
"SELECT PROCESSED from BATCH_STAGING where ID=?",
|
||||
new Object[] { new Long(id) }, String.class);
|
||||
assertEquals(StagingItemProcessor.NEW, before);
|
||||
|
||||
Object item = provider.next();
|
||||
assertEquals("FOO", item);
|
||||
|
||||
endTransaction();
|
||||
startNewTransaction();
|
||||
// After a rollback we have to resynchronize the TX to simulate a real batch
|
||||
BatchTransactionSynchronizationManager.resynchronize();
|
||||
|
||||
String after = (String) getJdbcTemplate().queryForObject(
|
||||
"SELECT PROCESSED from BATCH_STAGING where ID=?",
|
||||
new Object[] { new Long(id) }, String.class);
|
||||
assertEquals(StagingItemProcessor.NEW, after);
|
||||
|
||||
item = provider.next();
|
||||
assertEquals("FOO", item);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package test.jdbc.datasource;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.apache.derby.jdbc.EmbeddedDataSource;
|
||||
import org.springframework.beans.factory.config.AbstractFactoryBean;
|
||||
|
||||
public class DerbyDataSourceFactoryBean extends AbstractFactoryBean {
|
||||
|
||||
private String dataDirectory = "derby-home";
|
||||
|
||||
DataSource dataSource;
|
||||
|
||||
public void setDataDirectory(String dataDirectory) {
|
||||
this.dataDirectory = dataDirectory;
|
||||
}
|
||||
|
||||
protected Object createInstance() throws Exception {
|
||||
File directory = new File(dataDirectory);
|
||||
System.setProperty("derby.system.home", directory.getCanonicalPath());
|
||||
System.setProperty("derby.storage.fileSyncTransactionLog", "true");
|
||||
System.setProperty("derby.storage.pageCacheSize", "100");
|
||||
|
||||
final EmbeddedDataSource ds = new EmbeddedDataSource();
|
||||
ds.setDatabaseName("derbydb");
|
||||
ds.setCreateDatabase("create");
|
||||
dataSource = ds;
|
||||
|
||||
return ds;
|
||||
}
|
||||
|
||||
public Class getObjectType() {
|
||||
return DataSource.class;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?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.0.xsd">
|
||||
|
||||
<import resource="classpath:data-source-context.xml" />
|
||||
|
||||
<!-- register the step scope with the application context -->
|
||||
<bean id="stepScope"
|
||||
class="org.springframework.batch.execution.scope.StepScope" />
|
||||
|
||||
<bean id="processor"
|
||||
class="org.springframework.batch.sample.item.processor.StagingItemProcessor"
|
||||
scope="step">
|
||||
<property name="incrementer">
|
||||
<bean id="jobIncrementer" parent="incrementerParent">
|
||||
<property name="incrementerName"
|
||||
value="BATCH_STAGING_SEQ" />
|
||||
</bean>
|
||||
</property>
|
||||
<property name="dataSource" ref="dataSource" />
|
||||
</bean>
|
||||
|
||||
<bean id="provider"
|
||||
class="org.springframework.batch.sample.item.provider.StagingItemProvider"
|
||||
scope="step">
|
||||
<property name="dataSource" ref="dataSource" />
|
||||
</bean>
|
||||
|
||||
</beans>
|
||||
Reference in New Issue
Block a user