OPEN - issue BATCH-170: Concurrent modification of StepExecution when running an asynchrounous step operation

http://opensource.atlassian.com/projects/spring/browse/BATCH-170

Synchronized and added OptimisticLockingException to SqlStepDao.  The parallel job now runs cleanly (tried it a few times).
This commit is contained in:
dsyer
2007-12-19 13:06:31 +00:00
parent 7f71dc55ce
commit 37696b45f4
21 changed files with 431 additions and 297 deletions

View File

@@ -34,7 +34,7 @@ public class Entity implements Serializable {
private Long id;
private Integer version;
private Integer version = new Integer(0);
public Entity() {
super();
@@ -59,6 +59,13 @@ public class Entity implements Serializable {
public Integer getVersion() {
return version;
}
/**
*
*/
public void incrementVersion() {
version = new Integer(version.intValue()+1);
}
// @Override
public String toString() {

View File

@@ -101,7 +101,7 @@ public class JobInstance extends Entity {
return identifier==null ? null : identifier.getName();
}
public JobExecution createNewJobExecution() {
public JobExecution createJobExecution() {
return new JobExecution(this);
}

View File

@@ -0,0 +1,128 @@
/*
* Copyright 2006-2007 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.core.domain;
import java.util.Properties;
import org.springframework.batch.repeat.RepeatContext;
/**
* Represents a contribution to a {@link StepExecution}, buffering changes
* until they can be applied at a chunk boundary.
*
* @author Dave Syer
*
*/
public class StepContribution {
/**
* Context attribute key for step execution. Used by monitoring and managing
* clients to inspect current step execution.
*/
private static final String STEP_EXECUTION_KEY = "STEP_EXECUTION";
private int taskCount = 0;
private StepExecution execution;
private Properties statistics;
private int commitCount;
/**
* @param execution
*/
public StepContribution(StepExecution execution) {
this.execution = execution;
}
/**
* Increment the counter for the number of tasks executed.
*/
public void incrementTaskCount() {
taskCount++;
}
/**
* Public access to the task execution counter.
*
* @return the task execution counter.
*/
public int getTaskCount() {
return taskCount;
}
/**
* @param context
*/
public void registerChunkContext(final RepeatContext context) {
execution.getJobExecution().registerChunkContext(context);
context.registerDestructionCallback("CHUNK_EXECUTION_CONTEXT_CALLBACK", new Runnable() {
public void run() {
execution.getJobExecution().unregisterStepContext(context);
}
});
}
/**
* @param context
*/
public void registerStepContext(final RepeatContext context) {
execution.getJobExecution().registerStepContext(context);
context.registerDestructionCallback("STEP_EXECUTION_CONTEXT_CALLBACK", new Runnable() {
public void run() {
execution.getJobExecution().unregisterStepContext(context);
}
});
// Add the step execution as an attribute so monitoring
// clients can see it.
context.setAttribute(STEP_EXECUTION_KEY, execution);
}
/**
* Set the statistics properties.
*
* @param statistics
*/
public void setStatistics(Properties statistics) {
this.statistics = statistics;
}
/**
* Increment the commit counter.
*/
public void incrementCommitCount() {
commitCount++;
}
/**
* Public getter for the statistics.
* @return the statistics
*/
public Properties getStatistics() {
return statistics;
}
/**
* Public getter for the commit counter.
* @return the commitCount
*/
public int getCommitCount() {
return commitCount;
}
}

View File

@@ -54,7 +54,7 @@ public class StepExecution extends Entity {
private Properties statistics = new Properties();
private ExitStatus exitStatus = ExitStatus.UNKNOWN;
/**
* Package private constructor for Hibernate
*/
@@ -66,11 +66,11 @@ public class StepExecution extends Entity {
* Constructor with mandatory properties.
*
* @param step the step to which this execution belongs
* @param jobExecution the current job execution
*/
* @param jobExecution the current job execution
*/
public StepExecution(StepInstance step, JobExecution jobExecution, Long id) {
this();
this.step= step;
this.step = step;
this.jobExecution = jobExecution;
setId(id);
}
@@ -87,10 +87,6 @@ public class StepExecution extends Entity {
taskCount++;
}
public void incrementRollbackCount() {
rollbackCount++;
}
public Properties getStatistics() {
return statistics;
}
@@ -148,7 +144,7 @@ public class StepExecution extends Entity {
}
public Long getStepId() {
if (step!=null) {
if (step != null) {
return step.getId();
}
return null;
@@ -159,45 +155,48 @@ public class StepExecution extends Entity {
* @return the jobExecutionId
*/
public Long getJobExecutionId() {
if (jobExecution!=null) {
if (jobExecution != null) {
return jobExecution.getId();
}
return null;
}
/* (non-Javadoc)
/*
* (non-Javadoc)
* @see org.springframework.batch.container.common.domain.Entity#equals(java.lang.Object)
*/
public boolean equals(Object obj) {
Object stepId = getStepId();
Object jobExecutionId = getJobExecutionId();
if (stepId==null && jobExecutionId==null || !(obj instanceof StepExecution) || getId()!=null) {
if (stepId == null && jobExecutionId == null || !(obj instanceof StepExecution) || getId() != null) {
return super.equals(obj);
}
StepExecution other = (StepExecution) obj;
if (stepId==null) {
if (stepId == null) {
return jobExecutionId.equals(other.getJobExecutionId());
}
return stepId.equals(other.getStepId()) && (jobExecutionId==null || jobExecutionId.equals(other.getJobExecutionId()));
return stepId.equals(other.getStepId())
&& (jobExecutionId == null || jobExecutionId.equals(other.getJobExecutionId()));
}
/* (non-Javadoc)
/*
* (non-Javadoc)
* @see org.springframework.batch.container.common.domain.Entity#hashCode()
*/
public int hashCode() {
Object stepId = getStepId();
Object jobExecutionId = getJobExecutionId();
return super.hashCode() + 31*(stepId!=null ? stepId.hashCode() : 0) + 91*(jobExecutionId!=null ? jobExecutionId.hashCode() : 0);
}
public String toString() {
return super.toString() + ", name=" + getName() + ", taskCount=" + taskCount + ", commitCount=" + commitCount + ", rollbackCount="
+ rollbackCount;
return super.hashCode() + 31 * (stepId != null ? stepId.hashCode() : 0) + 91
* (jobExecutionId != null ? jobExecutionId.hashCode() : 0);
}
public String toString() {
return super.toString() + ", name=" + getName() + ", taskCount=" + taskCount + ", commitCount=" + commitCount
+ ", rollbackCount=" + rollbackCount;
}
private String getName() {
return step==null ? null : step.getName();
return step == null ? null : step.getName();
}
/**
@@ -231,4 +230,34 @@ public class StepExecution extends Entity {
return jobExecution;
}
/**
* Factory method for {@link StepContribution}.
*
* @return a new {@link StepContribution}
*/
public StepContribution createStepContribution() {
return new StepContribution(this);
}
/**
* On successful execution just before a chunk commit, this method should be
* called. Synchronizes access to the {@link StepExecution} so that changes
* are atomic.
*
* @param contribution
*/
public synchronized void apply(StepContribution contribution) {
taskCount += contribution.getTaskCount();
statistics = contribution.getStatistics();
commitCount += contribution.getCommitCount();
}
/**
* On unsuccessful execution after a chunk has rolled back. Synchronizes
* access to the {@link StepExecution} so that changes are atomic.
*/
public synchronized void rollback() {
rollbackCount++;
}
}

View File

@@ -111,11 +111,11 @@ public class StepExecutionTests extends TestCase {
/**
* Test method for
* {@link org.springframework.batch.core.domain.StepExecution#incrementRollbackCount()}.
* {@link org.springframework.batch.core.domain.StepExecution#rollback()}.
*/
public void testIncrementRollbackCount() {
int before = execution.getRollbackCount().intValue();
execution.incrementRollbackCount();
execution.rollback();
int after = execution.getRollbackCount().intValue();
assertEquals(before + 1, after);
}

View File

@@ -185,7 +185,7 @@ public class SimpleJobRepository implements JobRepository {
}
private JobExecution generateJobExecution(JobInstance job) {
JobExecution execution = job.createNewJobExecution();
JobExecution execution = job.createJobExecution();
// Save the JobExecution so that it picks up an ID (useful for clients
// monitoring asynchronous executions):
saveOrUpdate(execution);

View File

@@ -20,7 +20,6 @@ import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.List;
import java.util.Properties;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -36,6 +35,7 @@ import org.springframework.batch.restart.RestartData;
import org.springframework.batch.support.PropertiesConverter;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.support.incrementer.DataFieldMaxValueIncrementer;
@@ -46,11 +46,11 @@ import org.springframework.util.StringUtils;
* Sql implementation of {@link StepDao}. Uses Sequences (via Spring's
*
* @link DataFieldMaxValueIncrementer abstraction) to create all Step and
* StepExecution primary keys before inserting a new row. All objects are
* checked to ensure all fields to be stored are not null. If any are
* found to be null, an IllegalArgumentException will be thrown. This
* could be left to JdbcTemplate, however, the exception will be fairly
* vague, and fails to highlight which field caused the exception.
* StepExecution primary keys before inserting a new row. All objects are
* checked to ensure all fields to be stored are not null. If any are found to
* be null, an IllegalArgumentException will be thrown. This could be left to
* JdbcTemplate, however, the exception will be fairly vague, and fails to
* highlight which field caused the exception.
*
* TODO: JavaDoc should be geared more towards usability, the comments above are
* useful information, and should be there, but needs usability stuff. Depends
@@ -66,6 +66,8 @@ public class SqlStepDao implements StepDao, InitializingBean {
private static final int EXIT_MESSAGE_LENGTH = 250;
private static final int RESTART_DATA_LENGTH = 1000;
private static final String FIND_STEP = "SELECT ID, STATUS, RESTART_DATA from %PREFIX%STEP where JOB_ID = ? "
+ "and STEP_NAME = ?";
@@ -89,7 +91,7 @@ public class SqlStepDao implements StepDao, InitializingBean {
private static final String UPDATE_STEP_EXECUTION = "UPDATE %PREFIX%STEP_EXECUTION set START_TIME = ?, END_TIME = ?, "
+ "STATUS = ?, COMMIT_COUNT = ?, TASK_COUNT = ?, TASK_STATISTICS = ?, CONTINUABLE = ? , EXIT_CODE = ?, "
+ "EXIT_MESSAGE = ? where ID = ?";
+ "EXIT_MESSAGE = ?, VERSION=? where ID = ? and VERSION = ?";
private JdbcOperations jdbcTemplate;
@@ -104,8 +106,7 @@ public class SqlStepDao implements StepDao, InitializingBean {
public void afterPropertiesSet() throws Exception {
Assert.notNull(jdbcTemplate, "JdbcTemplate cannot be null.");
Assert.notNull(stepIncrementer, "StepIncrementer cannot be null.");
Assert.notNull(stepExecutionIncrementer,
"StepExecutionIncrementer canot be null.");
Assert.notNull(stepExecutionIncrementer, "StepExecutionIncrementer canot be null.");
}
private void cascadeJobExecution(JobExecution jobExecution) {
@@ -122,8 +123,7 @@ public class SqlStepDao implements StepDao, InitializingBean {
* DataFieldMaxValueIncrementer)
*
* @see StepDao#createStep(JobInstance, String)
* @throws IllegalArgumentException
* if job or stepName is null.
* @throws IllegalArgumentException if job or stepName is null.
*/
public StepInstance createStep(JobInstance job, String stepName) {
@@ -145,10 +145,9 @@ public class SqlStepDao implements StepDao, InitializingBean {
* anymore than one step is found, an exception is thrown.
*
* @see StepDao#findStep(Long, String)
* @throws IllegalArgumentException
* if job, stepName, or job.id is null.
* @throws IncorrectResultSizeDataAccessException
* if more than one step is found.
* @throws IllegalArgumentException if job, stepName, or job.id is null.
* @throws IncorrectResultSizeDataAccessException if more than one step is
* found.
*/
public StepInstance findStep(JobInstance job, String stepName) {
@@ -164,30 +163,28 @@ public class SqlStepDao implements StepDao, InitializingBean {
StepInstance step = new StepInstance(new Long(rs.getLong(1)));
step.setStatus(BatchStatus.getStatus(rs.getString(2)));
step.setRestartData(new GenericRestartData(PropertiesConverter
.stringToProperties(rs.getString(3))));
step.setRestartData(new GenericRestartData(PropertiesConverter.stringToProperties(rs.getString(3))));
return step;
}
};
List steps = jdbcTemplate.query(getFindStepQuery(), parameters,
rowMapper);
List steps = jdbcTemplate.query(getFindStepQuery(), parameters, rowMapper);
if (steps.size() == 0) {
// No step found
return null;
} else if (steps.size() == 1) {
}
else if (steps.size() == 1) {
StepInstance step = (StepInstance) steps.get(0);
return step;
} else {
}
else {
// This error will likely never be thrown, because there should
// never be two steps with the same name and Job_ID due to database
// constraints.
throw new IncorrectResultSizeDataAccessException(
"Step Invalid, multiple steps found for StepName:"
+ stepName + " and JobId:" + job.getId(), 1, steps
.size());
throw new IncorrectResultSizeDataAccessException("Step Invalid, multiple steps found for StepName:"
+ stepName + " and JobId:" + job.getId(), 1, steps.size());
}
}
@@ -197,8 +194,7 @@ public class SqlStepDao implements StepDao, InitializingBean {
* they will not be returned with reconstituted object.
*
* @see StepDao#getStepExecution(Long)
* @throws IllegalArgumentException
* if id is null.
* @throws IllegalArgumentException if id is null.
*/
public List findStepExecutions(final StepInstance step) {
@@ -208,28 +204,23 @@ public class SqlStepDao implements StepDao, InitializingBean {
RowMapper rowMapper = new RowMapper() {
public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
JobExecution jobExecution = (JobExecution) jdbcTemplate
.queryForObject(
getQuery(JobExecutionRowMapper.GET_JOB_EXECUTION),
new Object[] { new Long(rs.getLong(2)) },
new JobExecutionRowMapper(step.getJob()));
StepExecution stepExecution = new StepExecution(step,
jobExecution, new Long(rs.getLong(1)));
JobExecution jobExecution = (JobExecution) jdbcTemplate.queryForObject(
getQuery(JobExecutionRowMapper.GET_JOB_EXECUTION), new Object[] { new Long(rs.getLong(2)) },
new JobExecutionRowMapper(step.getJob()));
StepExecution stepExecution = new StepExecution(step, jobExecution, new Long(rs.getLong(1)));
stepExecution.setStartTime(rs.getTimestamp(3));
stepExecution.setEndTime(rs.getTimestamp(4));
stepExecution.setStatus(BatchStatus.getStatus(rs.getString(5)));
stepExecution.setCommitCount(rs.getInt(6));
stepExecution.setTaskCount(rs.getInt(7));
stepExecution.setStatistics(PropertiesConverter
.stringToProperties(rs.getString(8)));
stepExecution.setExitStatus(new ExitStatus("Y".equals(rs
.getString(9)), rs.getString(10), rs.getString(11)));
stepExecution.setStatistics(PropertiesConverter.stringToProperties(rs.getString(8)));
stepExecution.setExitStatus(new ExitStatus("Y".equals(rs.getString(9)), rs.getString(10), rs
.getString(11)));
return stepExecution;
}
};
return jdbcTemplate.query(getFindStepExecutionsQuery(),
new Object[] { step.getId() }, rowMapper);
return jdbcTemplate.query(getFindStepExecutionsQuery(), new Object[] { step.getId() }, rowMapper);
}
@@ -239,8 +230,7 @@ public class SqlStepDao implements StepDao, InitializingBean {
* Sql implementation which uses a RowMapper to populate a list of all rows
* in the step table with the same JOB_ID.
*
* @throws IllegalArgumentException
* if jobId is null.
* @throws IllegalArgumentException if jobId is null.
*/
public List findSteps(final JobInstance job) {
@@ -252,12 +242,10 @@ public class SqlStepDao implements StepDao, InitializingBean {
public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
StepInstance step = new StepInstance(job, rs.getString(2),
new Long(rs.getLong(1)));
StepInstance step = new StepInstance(job, rs.getString(2), new Long(rs.getLong(1)));
String status = rs.getString(3);
step.setStatus(BatchStatus.getStatus(status));
step.setRestartData(new GenericRestartData(PropertiesConverter
.stringToProperties(rs.getString(3))));
step.setRestartData(new GenericRestartData(PropertiesConverter.stringToProperties(rs.getString(3))));
return step;
}
};
@@ -293,8 +281,7 @@ public class SqlStepDao implements StepDao, InitializingBean {
Object[] parameters = new Object[] { stepId };
return jdbcTemplate.queryForInt(getStepExecutionCountQuery(),
parameters);
return jdbcTemplate.queryForInt(getStepExecutionCountQuery(), parameters);
}
private String getStepExecutionCountQuery() {
@@ -323,26 +310,15 @@ public class SqlStepDao implements StepDao, InitializingBean {
cascadeJobExecution(stepExecution.getJobExecution());
stepExecution.setId(new Long(stepExecutionIncrementer.nextLongValue()));
Object[] parameters = new Object[] {
stepExecution.getId(),
new Long(0),
stepExecution.getStepId(),
stepExecution.getJobExecutionId(),
stepExecution.getStartTime(),
stepExecution.getEndTime(),
stepExecution.getStatus().toString(),
stepExecution.getCommitCount(),
stepExecution.getTaskCount(),
PropertiesConverter.propertiesToString(stepExecution
.getStatistics()),
stepExecution.getExitStatus().isContinuable() ? "Y" : "N",
stepExecution.getExitStatus().getExitCode(),
Object[] parameters = new Object[] { stepExecution.getId(), new Long(0), stepExecution.getStepId(),
stepExecution.getJobExecutionId(), stepExecution.getStartTime(), stepExecution.getEndTime(),
stepExecution.getStatus().toString(), stepExecution.getCommitCount(), stepExecution.getTaskCount(),
PropertiesConverter.propertiesToString(stepExecution.getStatistics()),
stepExecution.getExitStatus().isContinuable() ? "Y" : "N", stepExecution.getExitStatus().getExitCode(),
stepExecution.getExitStatus().getExitDescription() };
jdbcTemplate.update(getSaveStepExecutionQuery(), parameters, new int[] {
Types.INTEGER, Types.INTEGER, Types.INTEGER, Types.INTEGER,
Types.TIMESTAMP, Types.TIMESTAMP, Types.VARCHAR, Types.INTEGER,
Types.INTEGER, Types.VARCHAR, Types.CHAR, Types.VARCHAR,
Types.VARCHAR });
jdbcTemplate.update(getSaveStepExecutionQuery(), parameters, new int[] { Types.INTEGER, Types.INTEGER,
Types.INTEGER, Types.INTEGER, Types.TIMESTAMP, Types.TIMESTAMP, Types.VARCHAR, Types.INTEGER,
Types.INTEGER, Types.VARCHAR, Types.CHAR, Types.VARCHAR, Types.VARCHAR });
}
@@ -354,8 +330,7 @@ public class SqlStepDao implements StepDao, InitializingBean {
* Injection setter for job dao. Used to save {@link JobExecution}
* instances.
*
* @param jobDao
* a {@link JobDao}
* @param jobDao a {@link JobDao}
*/
public void setJobDao(JobDao jobDao) {
this.jobDao = jobDao;
@@ -367,8 +342,7 @@ public class SqlStepDao implements StepDao, InitializingBean {
*
* @param stepExecutionIncrementer a {@link DataFieldMaxValueIncrementer}
*/
public void setStepExecutionIncrementer(
DataFieldMaxValueIncrementer stepExecutionIncrementer) {
public void setStepExecutionIncrementer(DataFieldMaxValueIncrementer stepExecutionIncrementer) {
this.stepExecutionIncrementer = stepExecutionIncrementer;
}
@@ -388,65 +362,65 @@ public class SqlStepDao implements StepDao, InitializingBean {
* are overridden with the set*Query methods). Defaults to
* {@value #DEFAULT_TABLE_PREFIX}.
*
* @param tablePrefix
* the tablePrefix to set
* @param tablePrefix the tablePrefix to set
*/
public void setTablePrefix(String tablePrefix) {
this.tablePrefix = tablePrefix;
}
/**
* Update the {@link StepExecution}, truncating the exit description. Also
* checks for optimistic locking failure where another agent has updated the
* {@link StepExecution}.<br/>
*
* N.B. locks the {@link StepExecution} to prevent multi-threaded access.
*
* @throws OptimisticLockingFailureException if the {@link StepExecution}
* version does not match the value in the data base.
* @see StepDao#update(StepExecution)
*/
public void update(StepExecution stepExecution) {
validateStepExecution(stepExecution);
Assert.notNull(stepExecution.getId(),
"StepExecution Id cannot be null. StepExecution must saved"
+ " before it can be updated.");
Assert.notNull(stepExecution.getId(), "StepExecution Id cannot be null. StepExecution must saved"
+ " before it can be updated.");
// TODO: Not sure if this is a good idea on step execution considering
// it is saved at every commit
// point.
// if (jdbcTemplate.queryForInt(CHECK_STEP_EXECUTION_EXISTS, new
// Object[] { stepExecution.getId() }) != 1) {
// return; // throw exception?
// }
String exitDescription = stepExecution.getExitStatus()
.getExitDescription();
if (exitDescription != null
&& exitDescription.length() > EXIT_MESSAGE_LENGTH) {
String exitDescription = stepExecution.getExitStatus().getExitDescription();
if (exitDescription != null && exitDescription.length() > EXIT_MESSAGE_LENGTH) {
exitDescription = exitDescription.substring(0, EXIT_MESSAGE_LENGTH);
logger
.debug("Truncating long message before update of StepExecution: "
+ stepExecution);
logger.debug("Truncating long message before update of StepExecution: " + stepExecution);
}
Object[] parameters = new Object[] {
stepExecution.getStartTime(),
stepExecution.getEndTime(),
stepExecution.getStatus().toString(),
stepExecution.getCommitCount(),
stepExecution.getTaskCount(),
PropertiesConverter.propertiesToString(stepExecution
.getStatistics()),
stepExecution.getExitStatus().isContinuable() ? "Y" : "N",
stepExecution.getExitStatus().getExitCode(), exitDescription,
stepExecution.getId() };
jdbcTemplate
.update(getUpdateStepExecutionQuery(), parameters,
new int[] { Types.TIMESTAMP, Types.TIMESTAMP,
Types.VARCHAR, Types.INTEGER, Types.INTEGER,
Types.VARCHAR, Types.CHAR, Types.VARCHAR,
Types.VARCHAR, Types.INTEGER });
// Attempt to prevent concurrent modification errors by blocking here if
// someone is already trying to do it.
synchronized (stepExecution) {
Integer version = new Integer(stepExecution.getVersion().intValue() + 1);
Object[] parameters = new Object[] { stepExecution.getStartTime(), stepExecution.getEndTime(),
stepExecution.getStatus().toString(), stepExecution.getCommitCount(), stepExecution.getTaskCount(),
PropertiesConverter.propertiesToString(stepExecution.getStatistics()),
stepExecution.getExitStatus().isContinuable() ? "Y" : "N",
stepExecution.getExitStatus().getExitCode(), exitDescription, version, stepExecution.getId(),
stepExecution.getVersion() };
int count = jdbcTemplate.update(getUpdateStepExecutionQuery(), parameters, new int[] { Types.TIMESTAMP,
Types.TIMESTAMP, Types.VARCHAR, Types.INTEGER, Types.INTEGER, Types.VARCHAR, Types.CHAR,
Types.VARCHAR, Types.VARCHAR, Types.INTEGER, Types.INTEGER, Types.INTEGER });
// Avoid concurrent modifications...
if (count == 0) {
throw new OptimisticLockingFailureException("Attempt to update step execution id="
+ stepExecution.getId() + " with out of date version (" + stepExecution.getVersion() + ")");
}
stepExecution.incrementVersion();
}
}
/**
* @see StepDao#update(StepInstance)
* @throws IllegalArgumentException
* if step, or it's status and id is null.
* @throws IllegalArgumentException if step, or it's status and id is null.
*/
public void update(final StepInstance step) {
@@ -454,17 +428,21 @@ public class SqlStepDao implements StepDao, InitializingBean {
Assert.notNull(step.getStatus(), "Step status cannot be null.");
Assert.notNull(step.getId(), "Step Id cannot be null.");
Properties restartProps = null;
String restartString = "";
RestartData restartData = step.getRestartData();
if (restartData != null) {
restartProps = restartData.getProperties();
restartString = PropertiesConverter.propertiesToString(restartData.getProperties());
}
Object[] parameters = new Object[] { step.getStatus().toString(),
PropertiesConverter.propertiesToString(restartProps),
step.getId() };
if (restartString.length() >= RESTART_DATA_LENGTH) {
logger.error("Restart data too long to persist (max length=" + RESTART_DATA_LENGTH + "): " + restartString);
throw new IllegalStateException("Restart exceeded allowed length (" + RESTART_DATA_LENGTH + ")");
}
Object[] parameters = new Object[] { step.getStatus().toString(), restartString, step.getId() };
jdbcTemplate.update(getUpdateStepQuery(), parameters);
}
/*
@@ -476,12 +454,9 @@ public class SqlStepDao implements StepDao, InitializingBean {
private void validateStepExecution(StepExecution stepExecution) {
Assert.notNull(stepExecution);
Assert.notNull(stepExecution.getStepId(),
"StepExecution Step-Id cannot be null.");
Assert.notNull(stepExecution.getStartTime(),
"StepExecution start time cannot be null.");
Assert.notNull(stepExecution.getStatus(),
"StepExecution status cannot be null.");
Assert.notNull(stepExecution.getStepId(), "StepExecution Step-Id cannot be null.");
Assert.notNull(stepExecution.getStartTime(), "StepExecution start time cannot be null.");
Assert.notNull(stepExecution.getStatus(), "StepExecution status cannot be null.");
}
}

View File

@@ -22,6 +22,7 @@ import java.util.Properties;
import org.springframework.batch.core.configuration.StepConfiguration;
import org.springframework.batch.core.domain.BatchStatus;
import org.springframework.batch.core.domain.StepContribution;
import org.springframework.batch.core.domain.StepExecution;
import org.springframework.batch.core.domain.StepInstance;
import org.springframework.batch.core.executor.ExitCodeExceptionClassifier;
@@ -77,12 +78,6 @@ import org.springframework.util.Assert;
*/
public class SimpleStepExecutor implements StepExecutor {
/**
* Context attribute key for step execution. Used by monitoring and managing
* clients to inspect current step execution.
*/
private static final String STEP_EXECUTION_KEY = "STEP_EXECUTION";
private RepeatOperations chunkOperations = new RepeatTemplate();
private RepeatOperations stepOperations = new RepeatTemplate();
@@ -97,8 +92,7 @@ public class SimpleStepExecutor implements StepExecutor {
// Not for production use...
protected PlatformTransactionManager transactionManager = new ResourcelessTransactionManager();
public void setTransactionManager(
PlatformTransactionManager transactionManager) {
public void setTransactionManager(PlatformTransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
@@ -117,8 +111,7 @@ public class SimpleStepExecutor implements StepExecutor {
* processing. Should be set up by the caller through a factory. Defaults to
* a plain {@link RepeatTemplate}.
*
* @param stepOperations
* a {@link RepeatOperations} instance.
* @param stepOperations a {@link RepeatOperations} instance.
*/
public void setStepOperations(RepeatOperations stepOperations) {
this.stepOperations = stepOperations;
@@ -129,8 +122,7 @@ public class SimpleStepExecutor implements StepExecutor {
* processing. Should be set up by the caller through a factory. Defaults to
* a plain {@link RepeatTemplate}.
*
* @param chunkOperations
* a {@link RepeatOperations} instance.
* @param chunkOperations a {@link RepeatOperations} instance.
*/
public void setChunkOperations(RepeatOperations chunkOperations) {
this.chunkOperations = chunkOperations;
@@ -146,15 +138,13 @@ public class SimpleStepExecutor implements StepExecutor {
* execution, which would normally be available to the caller somehow
* through the step's {@link JobExecutionContext}.<br/>
*
* @throws StepInterruptedException
* if the step or a chunk is interrupted
* @throws RuntimeException
* if there is an exception during a chunk execution
* @throws StepInterruptedException if the step or a chunk is interrupted
* @throws RuntimeException if there is an exception during a chunk
* execution
* @see StepExecutor#process(StepConfiguration, StepExecution)
*/
public ExitStatus process(final StepConfiguration configuration,
final StepExecution stepExecution) throws BatchCriticalException,
StepInterruptedException {
public ExitStatus process(final StepConfiguration configuration, final StepExecution stepExecution)
throws BatchCriticalException, StepInterruptedException {
final StepInstance step = stepExecution.getStep();
boolean isRestart = step.getStepExecutionCount() > 0 ? true : false;
@@ -164,17 +154,14 @@ public class SimpleStepExecutor implements StepExecutor {
ExitStatus status = ExitStatus.FAILED;
final SimpleStepContext stepScopeContext = StepSynchronizationManager
.open();
final SimpleStepContext stepScopeContext = StepSynchronizationManager.open();
stepScopeContext.setStepExecution(stepExecution);
// Add the job identifier so that it can be used to identify
// the conversation in StepScope
stepScopeContext.setAttribute(StepScope.ID_KEY, stepExecution
.getJobExecution().getJob().getIdentifier());
stepScopeContext.setAttribute(StepScope.ID_KEY, stepExecution.getJobExecution().getJob().getIdentifier());
try {
stepExecution
.setStartTime(new Timestamp(System.currentTimeMillis()));
stepExecution.setStartTime(new Timestamp(System.currentTimeMillis()));
updateStatus(stepExecution, BatchStatus.STARTED);
final boolean saveRestartData = configuration.isSaveRestartData();
@@ -185,56 +172,53 @@ public class SimpleStepExecutor implements StepExecutor {
status = stepOperations.iterate(new RepeatCallback() {
public ExitStatus doInIteration(final RepeatContext context)
throws Exception {
public ExitStatus doInIteration(final RepeatContext context) throws Exception {
final StepContribution contribution = stepExecution.createStepContribution();
contribution.registerStepContext(context);
stepExecution.getJobExecution()
.registerStepContext(context);
context.registerDestructionCallback(
"STEP_EXECUTION_CONTEXT_CALLBACK", new Runnable() {
public void run() {
stepExecution.getJobExecution()
.unregisterStepContext(context);
}
});
// Add the step execution as an attribute so monitoring
// clients can see it.
context.setAttribute(STEP_EXECUTION_KEY, stepExecution);
// Before starting a new transaction, check for
// interruption.
interruptionPolicy.checkInterrupted(context);
ExitStatus result;
try {
result = (ExitStatus) new TransactionTemplate(
transactionManager)
result = (ExitStatus) new TransactionTemplate(transactionManager)
.execute(new TransactionCallback() {
public Object doInTransaction(
TransactionStatus status) {
// New transaction obtained,
// resynchronize
// TransactionSyncrhonization objects
BatchTransactionSynchronizationManager
.resynchronize();
public Object doInTransaction(TransactionStatus status) {
/*
* New transaction obtained,
* resynchronize
* TransactionSynchronization objects
*/
BatchTransactionSynchronizationManager.resynchronize();
ExitStatus result;
result = processChunk(configuration,
stepExecution);
result = processChunk(configuration, contribution);
// TODO: Statistics are not thread safe
// - we cannot guarantee that they are
// up to date. (Maybe we never can?)
Properties statistics = getStatistics(module);
contribution.setStatistics(statistics);
contribution.incrementCommitCount();
// Apply the contribution to the step
// only if chunk was successful
stepExecution.apply(contribution);
if (saveRestartData) {
step
.setRestartData(getRestartData(module));
step.setRestartData(getRestartData(module));
jobRepository.update(step);
}
Properties statistics = getStatistics(module);
stepExecution.setStatistics(statistics);
stepExecution.incrementCommitCount();
jobRepository
.saveOrUpdate(stepExecution);
jobRepository.saveOrUpdate(stepExecution);
return result;
}
});
} catch (Throwable t) {
}
catch (Throwable t) {
/*
* Any exception thrown within the transaction template
* will automatically cause the transaction to rollback.
@@ -242,10 +226,11 @@ public class SimpleStepExecutor implements StepExecutor {
* commit (e.g. Hibernate flush) so this catch block
* comes outside the transaction.
*/
stepExecution.incrementRollbackCount();
stepExecution.rollback();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
}
else {
throw new RuntimeException(t);
}
}
@@ -263,30 +248,34 @@ public class SimpleStepExecutor implements StepExecutor {
updateStatus(stepExecution, BatchStatus.COMPLETED);
return status;
} catch (RuntimeException e) {
}
catch (RuntimeException e) {
// classify exception so an exit code can be stored.
status = exceptionClassifier.classifyForExitCode(e);
if (e.getCause() instanceof StepInterruptedException) {
updateStatus(stepExecution, BatchStatus.STOPPED);
throw (StepInterruptedException) e.getCause();
} else {
}
else {
updateStatus(stepExecution, BatchStatus.FAILED);
throw e;
}
} finally {
}
finally {
stepExecution.setExitStatus(status);
stepExecution.setEndTime(new Timestamp(System.currentTimeMillis()));
try {
jobRepository.saveOrUpdate(stepExecution);
} finally {
}
finally {
// clear any registered synchronizations
try {
StepSynchronizationManager.close();
} finally {
BatchTransactionSynchronizationManager
.clearSynchronizations();
}
finally {
BatchTransactionSynchronizationManager.clearSynchronizations();
}
}
}
@@ -296,12 +285,9 @@ public class SimpleStepExecutor implements StepExecutor {
/**
* Convenience method to update the status in all relevant places.
*
* @param step
* the current step
* @param stepExecution
* the current stepExecution
* @param status
* the status to set
* @param step the current step
* @param stepExecution the current stepExecution
* @param status the status to set
*/
private void updateStatus(StepExecution stepExecution, BatchStatus status) {
StepInstance step = stepExecution.getStep();
@@ -309,8 +295,7 @@ public class SimpleStepExecutor implements StepExecutor {
step.setStatus(status);
jobRepository.update(step);
jobRepository.saveOrUpdate(stepExecution);
for (Iterator iter = stepExecution.getJobExecution().getStepContexts()
.iterator(); iter.hasNext();) {
for (Iterator iter = stepExecution.getJobExecution().getStepContexts().iterator(); iter.hasNext();) {
RepeatContext context = (RepeatContext) iter.next();
context.setAttribute("JOB_STATUS", status);
}
@@ -322,37 +307,25 @@ public class SimpleStepExecutor implements StepExecutor {
* outside this method, so subclasses that override do not need to create a
* transaction.
*
* @param configuration
* the current step configuration
* @param stepExecution
* the current step, containing the {@link Tasklet} with the
* business logic.
* @param configuration the current step configuration
* @param stepExecution the current step, containing the {@link Tasklet}
* with the business logic.
* @return true if there is more data to process.
*/
protected final ExitStatus processChunk(
final StepConfiguration configuration,
final StepExecution stepExecution) {
return chunkOperations.iterate(new RepeatCallback() {
public ExitStatus doInIteration(final RepeatContext context)
throws Exception {
stepExecution.getJobExecution().registerChunkContext(context);
context.registerDestructionCallback(
"CHUNK_EXECUTION_CONTEXT_CALLBACK", new Runnable() {
public void run() {
stepExecution.getJobExecution()
.unregisterStepContext(context);
}
});
protected final ExitStatus processChunk(final StepConfiguration configuration, final StepContribution contribution) {
ExitStatus result = chunkOperations.iterate(new RepeatCallback() {
public ExitStatus doInIteration(final RepeatContext context) throws Exception {
contribution.registerChunkContext(context);
// check for interruption before each item as well
interruptionPolicy.checkInterrupted(context);
ExitStatus exitStatus = doTaskletProcessing(configuration
.getTasklet(), stepExecution);
stepExecution.incrementTaskCount();
ExitStatus exitStatus = doTaskletProcessing(configuration.getTasklet(), contribution);
contribution.incrementTaskCount();
// check for interruption after each item as well
interruptionPolicy.checkInterrupted(context);
return exitStatus;
}
});
return result;
}
/**
@@ -363,23 +336,20 @@ public class SimpleStepExecutor implements StepExecutor {
* If there is an exception and the {@link Tasklet} implements
* {@link Skippable} then the skip method is called.
*
* @param tasklet
* the unit of business logic to execute
* @param stepExecution
* the current step
* @param tasklet the unit of business logic to execute
* @param contribution the current step
* @return boolean if there is more processing to do
* @throws Exception
* if there is an error
* @throws Exception if there is an error
*/
protected ExitStatus doTaskletProcessing(Tasklet tasklet,
StepExecution stepExecution) throws Exception {
protected ExitStatus doTaskletProcessing(Tasklet tasklet, StepContribution contribution) throws Exception {
ExitStatus exitStatus = ExitStatus.CONTINUABLE;
try {
exitStatus = tasklet.execute();
} catch (Exception e) {
}
catch (Exception e) {
if (tasklet instanceof Skippable) {
((Skippable) tasklet).skip();
@@ -396,12 +366,13 @@ public class SimpleStepExecutor implements StepExecutor {
/**
* @param tasklet
* @return restart data from the {@link Tasklet} if it is
* {@link Restartable}
* {@link Restartable}
*/
private RestartData getRestartData(Tasklet tasklet) {
if (tasklet instanceof Restartable) {
return ((Restartable) tasklet).getRestartData();
} else {
}
else {
return null;
}
}
@@ -415,7 +386,8 @@ public class SimpleStepExecutor implements StepExecutor {
private Properties getStatistics(Tasklet tasklet) {
if (tasklet instanceof StatisticsProvider) {
return ((StatisticsProvider) tasklet).getStatistics();
} else {
}
else {
return null;
}
}
@@ -425,8 +397,7 @@ public class SimpleStepExecutor implements StepExecutor {
* check whether an external request has been made to interrupt the job
* execution.
*
* @param interruptionPolicy
* a {@link StepInterruptionPolicy}
* @param interruptionPolicy a {@link StepInterruptionPolicy}
*/
public void setInterruptionPolicy(StepInterruptionPolicy interruptionPolicy) {
this.interruptionPolicy = interruptionPolicy;
@@ -438,8 +409,7 @@ public class SimpleStepExecutor implements StepExecutor {
*
* @param exceptionClassifier
*/
public void setExceptionClassifier(
ExitCodeExceptionClassifier exceptionClassifier) {
public void setExceptionClassifier(ExitCodeExceptionClassifier exceptionClassifier) {
this.exceptionClassifier = exceptionClassifier;
}
@@ -457,8 +427,7 @@ public class SimpleStepExecutor implements StepExecutor {
* {@link SimpleLimitExceptionHandler} with that limit.</li>
* </ul>
*
* @param configuration
* a step configuration
* @param configuration a step configuration
*/
public void applyConfiguration(StepConfiguration configuration) {
@@ -478,27 +447,24 @@ public class SimpleStepExecutor implements StepExecutor {
setStepOperations(stepOperations);
}
} else if (configuration instanceof SimpleStepConfiguration) {
}
else if (configuration instanceof SimpleStepConfiguration) {
SimpleStepConfiguration simpleConfiguation = (SimpleStepConfiguration) configuration;
if (this.chunkOperations instanceof RepeatTemplate) {
RepeatTemplate template = (RepeatTemplate) this.chunkOperations;
template.setCompletionPolicy(new SimpleCompletionPolicy(
simpleConfiguation.getCommitInterval()));
template.setCompletionPolicy(new SimpleCompletionPolicy(simpleConfiguation.getCommitInterval()));
}
ExceptionHandler exceptionHandler = simpleConfiguation
.getExceptionHandler();
ExceptionHandler exceptionHandler = simpleConfiguation.getExceptionHandler();
if (simpleConfiguation.getSkipLimit() > 0
&& exceptionHandler == null) {
if (simpleConfiguation.getSkipLimit() > 0 && exceptionHandler == null) {
SimpleLimitExceptionHandler handler = new SimpleLimitExceptionHandler();
handler.setLimit(simpleConfiguation.getSkipLimit());
exceptionHandler = handler;
}
if (this.stepOperations instanceof RepeatTemplate
&& exceptionHandler != null) {
if (this.stepOperations instanceof RepeatTemplate && exceptionHandler != null) {
RepeatTemplate template = (RepeatTemplate) this.stepOperations;
template.setExceptionHandler(exceptionHandler);
}

View File

@@ -35,7 +35,7 @@ CREATE TABLE BATCH_STEP (
JOB_ID BIGINT NOT NULL,
STEP_NAME VARCHAR(100) NOT NULL,
STATUS VARCHAR(10),
RESTART_DATA VARCHAR(200));
RESTART_DATA VARCHAR(1000));
CREATE TABLE BATCH_STEP_EXECUTION (
ID BIGINT PRIMARY KEY ,
@@ -47,7 +47,7 @@ CREATE TABLE BATCH_STEP_EXECUTION (
STATUS VARCHAR(10),
COMMIT_COUNT BIGINT ,
TASK_COUNT BIGINT ,
TASK_STATISTICS VARCHAR(250),
TASK_STATISTICS VARCHAR(1000),
CONTINUABLE CHAR(1),
EXIT_CODE VARCHAR(20),
EXIT_MESSAGE VARCHAR(250));

View File

@@ -35,7 +35,7 @@ CREATE TABLE BATCH_STEP (
JOB_ID BIGINT NOT NULL,
STEP_NAME VARCHAR(100) NOT NULL,
STATUS VARCHAR(10),
RESTART_DATA VARCHAR(200));
RESTART_DATA VARCHAR(1000));
CREATE TABLE BATCH_STEP_EXECUTION (
ID BIGINT PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
@@ -47,7 +47,7 @@ CREATE TABLE BATCH_STEP_EXECUTION (
STATUS VARCHAR(10),
COMMIT_COUNT BIGINT ,
TASK_COUNT BIGINT ,
TASK_STATISTICS VARCHAR(250),
TASK_STATISTICS VARCHAR(1000),
CONTINUABLE CHAR(1),
EXIT_CODE VARCHAR(20),
EXIT_MESSAGE VARCHAR(250));

View File

@@ -35,7 +35,7 @@ CREATE TABLE BATCH_STEP (
JOB_ID BIGINT NOT NULL,
STEP_NAME VARCHAR(100) NOT NULL,
STATUS VARCHAR(10),
RESTART_DATA VARCHAR(200));
RESTART_DATA VARCHAR(1000));
CREATE TABLE BATCH_STEP_EXECUTION (
ID BIGINT IDENTITY PRIMARY KEY ,
@@ -47,7 +47,7 @@ CREATE TABLE BATCH_STEP_EXECUTION (
STATUS VARCHAR(10),
COMMIT_COUNT BIGINT ,
TASK_COUNT BIGINT ,
TASK_STATISTICS VARCHAR(250),
TASK_STATISTICS VARCHAR(1000),
CONTINUABLE CHAR(1),
EXIT_CODE VARCHAR(20),
EXIT_MESSAGE VARCHAR(250));

View File

@@ -35,7 +35,7 @@ CREATE TABLE BATCH_STEP (
JOB_ID NUMBER(38) NOT NULL,
STEP_NAME VARCHAR(100) NOT NULL,
STATUS VARCHAR(10),
RESTART_DATA VARCHAR(200));
RESTART_DATA VARCHAR(1000));
CREATE TABLE BATCH_STEP_EXECUTION (
ID NUMBER(38) PRIMARY KEY ,
@@ -47,7 +47,7 @@ CREATE TABLE BATCH_STEP_EXECUTION (
STATUS VARCHAR(10),
COMMIT_COUNT NUMBER(38) ,
TASK_COUNT NUMBER(38) ,
TASK_STATISTICS VARCHAR(250),
TASK_STATISTICS VARCHAR(1000),
CONTINUABLE CHAR(1),
EXIT_CODE VARCHAR(20),
EXIT_MESSAGE VARCHAR(250));

View File

@@ -35,7 +35,7 @@ CREATE TABLE BATCH_STEP (
JOB_ID BIGINT NOT NULL,
STEP_NAME VARCHAR(100) NOT NULL,
STATUS VARCHAR(10),
RESTART_DATA VARCHAR(200));
RESTART_DATA VARCHAR(1000));
CREATE TABLE BATCH_STEP_EXECUTION (
ID BIGINT PRIMARY KEY ,
@@ -47,7 +47,7 @@ CREATE TABLE BATCH_STEP_EXECUTION (
STATUS VARCHAR(10),
COMMIT_COUNT BIGINT ,
TASK_COUNT BIGINT ,
TASK_STATISTICS VARCHAR(250),
TASK_STATISTICS VARCHAR(1000),
CONTINUABLE CHAR(1),
EXIT_CODE VARCHAR(20),
EXIT_MESSAGE VARCHAR(250));

View File

@@ -24,7 +24,7 @@ CREATE TABLE BATCH_STEP (
JOB_ID ${BIGINT} NOT NULL,
STEP_NAME VARCHAR(100) NOT NULL,
STATUS VARCHAR(10),
RESTART_DATA VARCHAR(200));
RESTART_DATA VARCHAR(1000));
CREATE TABLE BATCH_STEP_EXECUTION (
ID ${BIGINT} $!{IDENTITY} PRIMARY KEY $!{GENERATED},
@@ -36,7 +36,7 @@ CREATE TABLE BATCH_STEP_EXECUTION (
STATUS VARCHAR(10),
COMMIT_COUNT ${BIGINT} ,
TASK_COUNT ${BIGINT} ,
TASK_STATISTICS VARCHAR(250),
TASK_STATISTICS VARCHAR(1000),
CONTINUABLE CHAR(1),
EXIT_CODE VARCHAR(20),
EXIT_MESSAGE VARCHAR(250));

View File

@@ -102,8 +102,8 @@ public class SimpleJobRepositoryTests extends TestCase {
jobConfiguration.setSteps(stepConfigurations);
databaseJob = new JobInstance(jobRuntimeInformation, new Long(1)) {
public JobExecution createNewJobExecution() {
jobExecution = super.createNewJobExecution();
public JobExecution createJobExecution() {
jobExecution = super.createJobExecution();
return jobExecution;
}
};
@@ -170,7 +170,7 @@ public class SimpleJobRepositoryTests extends TestCase {
jobDaoControl.setReturnValue(1);
jobDao.findJobExecutions(databaseJob);
final List executions = new ArrayList();
JobExecution execution =databaseJob.createNewJobExecution();
JobExecution execution =databaseJob.createJobExecution();
executions.add(execution);
// For this test it is important that the execution is finished
// and the executions in the list contain one with an end date

View File

@@ -31,6 +31,7 @@ import org.springframework.batch.execution.runtime.ScheduledJobIdentifier;
import org.springframework.batch.repeat.ExitStatus;
import org.springframework.batch.restart.GenericRestartData;
import org.springframework.batch.restart.RestartData;
import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.test.AbstractTransactionalDataSourceSpringContextTests;
import org.springframework.util.ClassUtils;
@@ -196,7 +197,7 @@ public abstract class AbstractStepDaoTests extends AbstractTransactionalDataSour
StepExecution stepExecution = new StepExecution(null, null);
try{
stepDao.update(stepExecution);
fail();
fail("Expected IllegalArgumentException");
}catch(IllegalArgumentException ex){
//expected
}
@@ -215,4 +216,25 @@ public abstract class AbstractStepDaoTests extends AbstractTransactionalDataSour
stepDao.save(execution);
assertEquals(2, stepDao.getStepExecutionCount(step1.getId()));
}
public void testUpdateStepExecutionVersion() throws Exception {
int before = stepExecution.getVersion().intValue();
stepDao.update(stepExecution);
int after = stepExecution.getVersion().intValue();
assertEquals("StepExecution version not updated", before+1, after);
}
public void testUpdateStepExecutionOptimisticLocking() throws Exception {
stepExecution.incrementVersion(); // not really allowed outside dao code
try {
stepDao.update(stepExecution);
fail("Expected OptimisticLockingFailureException");
}
catch (OptimisticLockingFailureException e) {
// expected
assertTrue("Exception message should contain step execution id: "+e.getMessage(), e.getMessage().indexOf(""+stepExecution.getId())>=0);
assertTrue("Exception message should contain step execution version: "+e.getMessage(), e.getMessage().indexOf(""+stepExecution.getVersion())>=0);
}
}
}

View File

@@ -66,7 +66,7 @@ public class SqlJobDaoQueryTests extends TestCase {
return 1;
}
});
sqlDao.save(new JobInstance(new SimpleJobIdentifier("foo"), new Long(11)).createNewJobExecution());
sqlDao.save(new JobInstance(new SimpleJobIdentifier("foo"), new Long(11)).createJobExecution());
assertEquals(1, list.size());
String query = (String) list.get(0);
assertTrue("Query did not contain FOO_:"+query, query.indexOf("FOO_")>=0);

View File

@@ -37,5 +37,5 @@ public class SqlStepDaoTests extends AbstractStepDaoTests {
assertEquals(LONG_STRING.substring(0, 250), ((Map) executions.get(0))
.get("EXIT_MESSAGE"));
}
}

View File

@@ -25,6 +25,7 @@ import junit.framework.TestCase;
import org.springframework.batch.core.configuration.StepConfigurationSupport;
import org.springframework.batch.core.domain.JobExecution;
import org.springframework.batch.core.domain.JobInstance;
import org.springframework.batch.core.domain.StepContribution;
import org.springframework.batch.core.domain.StepExecution;
import org.springframework.batch.core.domain.StepInstance;
import org.springframework.batch.core.executor.ExitCodeExceptionClassifier;
@@ -115,6 +116,7 @@ public class DefaultStepExecutorTests extends TestCase {
stepExecutor.process(stepConfiguration, stepExecution);
assertEquals(1, processed.size());
assertEquals(1, stepExecution.getTaskCount().intValue());
}
public void testChunkExecutor() throws Exception {
@@ -131,8 +133,11 @@ public class DefaultStepExecutorTests extends TestCase {
jobIdentifier, new Long(1)));
StepExecution stepExecution = new StepExecution(step, jobExecution);
stepExecutor.processChunk(stepConfiguration, stepExecution);
StepContribution contribution = stepExecution.createStepContribution();
stepExecutor.processChunk(stepConfiguration, contribution);
assertEquals(1, processed.size());
assertEquals(0, stepExecution.getTaskCount().intValue());
assertEquals(1, contribution.getTaskCount());
}

View File

@@ -24,7 +24,7 @@ CREATE TABLE BATCH_STEP (
JOB_ID BIGINT NOT NULL,
STEP_NAME VARCHAR(100) NOT NULL,
STATUS VARCHAR(10),
RESTART_DATA VARCHAR(200));
RESTART_DATA VARCHAR(1000));
CREATE TABLE BATCH_STEP_EXECUTION (
ID BIGINT IDENTITY PRIMARY KEY ,
@@ -36,7 +36,7 @@ CREATE TABLE BATCH_STEP_EXECUTION (
STATUS VARCHAR(10),
COMMIT_COUNT BIGINT ,
TASK_COUNT BIGINT ,
TASK_STATISTICS VARCHAR(250),
TASK_STATISTICS VARCHAR(1000),
CONTINUABLE CHAR(1),
EXIT_CODE VARCHAR(20),
EXIT_MESSAGE VARCHAR(250));

View File

@@ -75,7 +75,9 @@ public class StagingItemReader extends JdbcDaoSupport implements
Assert.state(keys == null || initialized,
"Cannot open an already open StagingItemProvider"
+ ", call close() first.");
keys = retrieveKeys().iterator();
synchronized (lock) {
keys = retrieveKeys().iterator();
}
logger.info("keys: " + keys);
registerSynchronization();
initialized = true;
@@ -147,7 +149,7 @@ public class StagingItemReader extends JdbcDaoSupport implements
throw new OptimisticLockingFailureException(
"The staging record with ID="
+ id
+ " was updated concurrently when trying to mark as complete.");
+ " was updated concurrently when trying to mark as complete (updated "+count+" records.");
}
return result;
}