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++;
}
}