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:
@@ -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() {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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++;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user