diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/JobRepository.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/JobRepository.java index c4990b69a..1a16e2927 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/JobRepository.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/JobRepository.java @@ -22,42 +22,64 @@ import org.springframework.batch.core.JobInstance; import org.springframework.batch.core.JobParameters; import org.springframework.batch.core.Step; import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.repository.dao.JobExecutionDao; +import org.springframework.batch.core.repository.dao.JobInstanceDao; import org.springframework.batch.item.ExecutionContext; +import org.springframework.transaction.annotation.Isolation; /** *

- * Repository for storing batch {@link JobExecution} and {@link StepExecution}s. - * Before using any methods, a {@link JobExecution} must first be obtained using - * the createJobExecution method. Once a {@link JobExecution} is obtained, they - * can be updated. + * Repository responsible for persistence of batch metadata entities. *

* + * @see JobInstance + * @see JobExecution + * @see StepExecution + * * @author Lucas Ward * @author Dave Syer - * + * @author Robert Kasanicky */ public interface JobRepository { /** - * Check if an instance of this job already exists with the parameters provided. + * Check if an instance of this job already exists with the parameters + * provided. * * @param jobName the name of the job * @param jobParameters the parameters to match - * @return true if a {@link JobInstance} already exists for this job name and job parameters + * @return true if a {@link JobInstance} already exists for this job name + * and job parameters */ boolean isJobInstanceExists(String jobName, JobParameters jobParameters); /** - * Find or create a {@link JobExecution} for a given {@link Job} and - * {@link JobParameters}. If the {@link Job} was already executed with these - * {@link JobParameters}, its persisted values (including ID) will be - * returned in a new {@link JobInstance}, associated with the - * {@link JobExecution}. If no previous instance is found, the execution - * will be associated with a new {@link JobInstance} - * @param jobName the name of the job that is to be executed + *

+ * Create a {@link JobExecution} for a given {@link Job} and + * {@link JobParameters}. If matching {@link JobInstance} already exists, + * the job must be restartable and it's last JobExecution must *not* be + * completed. If matching {@link JobInstance} does not exist yet it will be + * created. + *

+ * + *

+ * If this method is run in a transaction (as it normally would be) with + * isolation level at {@link Isolation#REPEATABLE_READ} or better, then this + * method should block if another transaction is already executing it (for + * the same {@link JobParameters} and job name). The first transaction to + * complete in this scenario obtains a valid {@link JobExecution}, and + * others throw {@link JobExecutionAlreadyRunningException} (or timeout). + * There are no such guarantees if the {@link JobInstanceDao} and + * {@link JobExecutionDao} do not respect the transaction isolation levels + * (e.g. if using a non-relational data-store, or if the platform does not + * support the higher isolation levels). + *

+ * + * @param jobName the name of the job that is to be executed

+ * * @param jobParameters the runtime parameters for the job * - * @return a valid job {@link JobExecution} for the arguments provided + * @return a valid {@link JobExecution} for the arguments provided * @throws JobExecutionAlreadyRunningException if there is a * {@link JobExecution} already running for the job instance with the * provided job and parameters. @@ -68,11 +90,11 @@ public interface JobRepository { * found and was already completed successfully. * */ - JobExecution createJobExecution(String jobName, JobParameters jobParameters) throws JobExecutionAlreadyRunningException, - JobRestartException, JobInstanceAlreadyCompleteException; + JobExecution createJobExecution(String jobName, JobParameters jobParameters) + throws JobExecutionAlreadyRunningException, JobRestartException, JobInstanceAlreadyCompleteException; /** - * Update the {@link JobExecution}. + * Update the {@link JobExecution} (but not its {@link ExecutionContext}). * * Preconditions: {@link JobExecution} must contain a valid * {@link JobInstance} and be saved (have an id assigned). @@ -82,11 +104,10 @@ public interface JobRepository { void update(JobExecution jobExecution); /** - * Save the {@link StepExecution}. ID will be assigned - it is not permitted - * that an ID be assigned before calling this method. Instead, it should be - * left blank, to be assigned by a {@link JobRepository}. The - * {@link ExecutionContext} of the {@link StepExecution} is not - * saved: see {@link #updateExecutionContext(StepExecution)}. + * Save the {@link StepExecution} and its {@link ExecutionContext}. ID will + * be assigned - it is not permitted that an ID be assigned before calling + * this method. Instead, it should be left blank, to be assigned by a + * {@link JobRepository}. * * Preconditions: {@link StepExecution} must have a valid {@link Step}. * @@ -95,7 +116,7 @@ public interface JobRepository { void add(StepExecution stepExecution); /** - * Update the {@link StepExecution}. + * Update the {@link StepExecution} (but not its {@link ExecutionContext}). * * Preconditions: {@link StepExecution} must be saved (have an id assigned). * @@ -104,11 +125,10 @@ public interface JobRepository { void update(StepExecution stepExecution); /** - * Persist the {@link ExecutionContext} of the given {@link StepExecution} - * and enclosing {@link JobExecution}. + * Persist the updated {@link ExecutionContext}s of the given + * {@link StepExecution} and corresponding {@link JobExecution}. * - * @param stepExecution the {@link StepExecution} containing the - * {@link ExecutionContext} to be saved. + * @param stepExecution */ void updateExecutionContext(StepExecution stepExecution); @@ -125,7 +145,7 @@ public interface JobRepository { int getStepExecutionCount(JobInstance jobInstance, String stepName); /** - * @param jobName the name of the job that might have run + * @param jobName the name of the job that might have run * @param jobParameters parameters identifying the {@link JobInstance} * @return the last execution of job if exists, null otherwise */ diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JdbcExecutionContextDao.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JdbcExecutionContextDao.java index 4e23dbc14..a460e62fb 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JdbcExecutionContextDao.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JdbcExecutionContextDao.java @@ -70,10 +70,6 @@ public class JdbcExecutionContextDao extends AbstractJdbcBatchMetadataDao implem private ExecutionContextStringSerializer serializer; - /** - * @param jobExecution - * @return execution context associated with the given jobExecution. - */ public ExecutionContext getExecutionContext(JobExecution jobExecution) { Long executionId = jobExecution.getId(); Assert.notNull(executionId, "ExecutionId must not be null."); @@ -88,10 +84,6 @@ public class JdbcExecutionContextDao extends AbstractJdbcBatchMetadataDao implem } } - /** - * @param stepExecution - * @return execution context associated with the given stepExecution. - */ public ExecutionContext getExecutionContext(StepExecution stepExecution) { Long executionId = stepExecution.getId(); Assert.notNull(executionId, "ExecutionId must not be null."); @@ -106,11 +98,6 @@ public class JdbcExecutionContextDao extends AbstractJdbcBatchMetadataDao implem } } - /** - * Persist or update the execution context associated with the given - * jobExecution - * @param jobExecution - */ public void updateExecutionContext(final JobExecution jobExecution) { Long executionId = jobExecution.getId(); ExecutionContext executionContext = jobExecution.getExecutionContext(); @@ -122,11 +109,6 @@ public class JdbcExecutionContextDao extends AbstractJdbcBatchMetadataDao implem persistSerializedContext(executionId, serializedContext, UPDATE_JOB_EXECUTION_CONTEXT); } - /** - * Persist or update the execution context associated with the given - * stepExecution - * @param stepExecution - */ public void updateExecutionContext(final StepExecution stepExecution) { Long executionId = stepExecution.getId(); @@ -173,6 +155,11 @@ public class JdbcExecutionContextDao extends AbstractJdbcBatchMetadataDao implem ((XStreamExecutionContextStringSerializer) serializer).afterPropertiesSet(); } + /** + * @param executionId + * @param serializedContext + * @param sql with parameters (shortContext, longContext, executionId) + */ private void persistSerializedContext(final Long executionId, String serializedContext, String sql) { final String shortContext; diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MapExecutionContextDao.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MapExecutionContextDao.java index 32cc1ec21..e6868d77f 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MapExecutionContextDao.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MapExecutionContextDao.java @@ -24,22 +24,29 @@ import org.springframework.batch.core.StepExecution; import org.springframework.batch.item.ExecutionContext; import org.springframework.batch.support.transaction.TransactionAwareProxyFactory; +/** + * In-memory implementation of {@link ExecutionContextDao} backed by static + * maps. + * + * @author Robert Kasanicky + */ public class MapExecutionContextDao implements ExecutionContextDao { - private static Map contextsByStepExecutionId = TransactionAwareProxyFactory.createTransactionalMap(); + private static Map contextsByStepExecutionId = TransactionAwareProxyFactory + .createTransactionalMap(); + + private static Map contextsByJobExecutionId = TransactionAwareProxyFactory + .createTransactionalMap(); - private static Map contextsByJobExecutionId = TransactionAwareProxyFactory.createTransactionalMap(); - public static void clear() { contextsByJobExecutionId.clear(); contextsByStepExecutionId.clear(); } - + private static ExecutionContext copy(ExecutionContext original) { return (ExecutionContext) SerializationUtils.deserialize(SerializationUtils.serialize(original)); } - public ExecutionContext getExecutionContext(StepExecution stepExecution) { return copy(contextsByStepExecutionId.get(stepExecution.getId())); } @@ -47,14 +54,13 @@ public class MapExecutionContextDao implements ExecutionContextDao { public void updateExecutionContext(StepExecution stepExecution) { contextsByStepExecutionId.put(stepExecution.getId(), copy(stepExecution.getExecutionContext())); } - + public ExecutionContext getExecutionContext(JobExecution jobExecution) { return copy(contextsByJobExecutionId.get(jobExecution.getId())); } public void updateExecutionContext(JobExecution jobExecution) { contextsByJobExecutionId.put(jobExecution.getId(), copy(jobExecution.getExecutionContext())); - } public void saveExecutionContext(JobExecution jobExecution) { diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/SimpleJobRepository.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/SimpleJobRepository.java index b3ddc2fa7..70d476573 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/SimpleJobRepository.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/SimpleJobRepository.java @@ -21,7 +21,6 @@ import java.util.Date; import java.util.List; import org.springframework.batch.core.BatchStatus; -import org.springframework.batch.core.Job; import org.springframework.batch.core.JobExecution; import org.springframework.batch.core.JobInstance; import org.springframework.batch.core.JobParameters; @@ -35,7 +34,6 @@ import org.springframework.batch.core.repository.dao.JobExecutionDao; import org.springframework.batch.core.repository.dao.JobInstanceDao; import org.springframework.batch.core.repository.dao.StepExecutionDao; import org.springframework.batch.item.ExecutionContext; -import org.springframework.transaction.annotation.Isolation; import org.springframework.util.Assert; /** @@ -81,71 +79,10 @@ public class SimpleJobRepository implements JobRepository { this.ecDao = ecDao; } - /** - * @see JobRepository#isJobInstanceExists(String, JobParameters) - */ public boolean isJobInstanceExists(String jobName, JobParameters jobParameters) { return jobInstanceDao.getJobInstance(jobName, jobParameters) != null; } - /** - *

- * Create a {@link JobExecution} based on the passed in {@link Job} and - * {@link JobParameters}. However, unique identification of a job can only - * come from the database, and therefore must come from JobDao by either - * creating a new job instance or finding an existing one, which will ensure - * that the id of the job instance is populated with the correct value. - *

- * - *

- * There are two ways in which the method determines if a job should be - * created or an existing one should be returned. The first is - * restartability. The {@link Job} restartable property will be checked - * first. If it is false, a new job will be created, regardless of whether - * or not one exists. If it is true, the {@link JobInstanceDao} will be - * checked to determine if the job already exists, if it does, it's steps - * will be populated (there must be at least 1) and a new - * {@link JobExecution} will be returned. If no job instance is found, a new - * one will be created. - *

- * - *

- * A check is made to see if any job executions are already running, and an - * exception will be thrown if one is detected. To detect a running job - * execution we use the {@link JobExecutionDao}: - *

    - *
  1. First we find all jobs which match the given {@link JobParameters} - * and job name
  2. - *
  3. What happens then depends on how many existing job instances we find: - *
      - *
    • If there are none, or the {@link Job} is marked restartable, then we - * create a new {@link JobInstance}
    • - *
    • If there is more than one and the {@link Job} is not marked as - * restartable, it is an error. This could be caused by a job whose - * restartable flag has changed to be more strict (true not false) - * after it has been executed at least once.
    • - *
    • If there is precisely one existing {@link JobInstance} then we check - * the {@link JobExecution} instances for that job, and if any of them tells - * us it is running (see {@link JobExecution#isRunning()}) then it is an - * error.
    • - *
    - *
  4. - *
- * If this method is run in a transaction (as it normally would be) with - * isolation level at {@link Isolation#REPEATABLE_READ} or better, then this - * method should block if another transaction is already executing it (for - * the same {@link JobParameters} and job name). The first transaction to - * complete in this scenario obtains a valid {@link JobExecution}, and - * others throw {@link JobExecutionAlreadyRunningException} (or timeout). - * There are no such guarantees if the {@link JobInstanceDao} and - * {@link JobExecutionDao} do not respect the transaction isolation levels - * (e.g. if using a non-relational data-store, or if the platform does not - * support the higher isolation levels). - *

- * - * @see JobRepository#createJobExecution(String, JobParameters) - * - */ public JobExecution createJobExecution(String jobName, JobParameters jobParameters) throws JobExecutionAlreadyRunningException, JobRestartException, JobInstanceAlreadyCompleteException { @@ -202,15 +139,6 @@ public class SimpleJobRepository implements JobRepository { } - /** - * Update a JobExecution. A JobExecution is considered one 'execution' of a - * particular job. Therefore, it must have it's jobId field set before it is - * passed into this method. It also has it's own unique identifier, because - * it must be updatable separately. - * - * @param jobExecution to be stored. - * @throws IllegalArgumentException if jobExecution is null. - */ public void update(JobExecution jobExecution) { Assert.notNull(jobExecution, "JobExecution cannot be null."); @@ -221,12 +149,6 @@ public class SimpleJobRepository implements JobRepository { jobExecutionDao.updateJobExecution(jobExecution); } - /** - * Save the {@link StepExecution}. - * - * Preconditions: step name must be given and associated - * {@link JobExecution} must already be saved (have an id assigned). - */ public void add(StepExecution stepExecution) { validateStepExecution(stepExecution); @@ -235,12 +157,6 @@ public class SimpleJobRepository implements JobRepository { ecDao.saveExecutionContext(stepExecution); } - /** - * Update the {@link StepExecution}. - * - * Preconditions: step name must be given and associated - * {@link JobExecution} must already be saved (have an id assigned). - */ public void update(StepExecution stepExecution) { validateStepExecution(stepExecution); Assert.notNull(stepExecution.getId(), "StepExecution must already be saved (have an id assigned)"); @@ -261,9 +177,6 @@ public class SimpleJobRepository implements JobRepository { ecDao.updateExecutionContext(stepExecution); } - /** - * @return the last execution of the step within given job instance - */ public StepExecution getLastStepExecution(JobInstance jobInstance, String stepName) { List jobExecutions = jobExecutionDao.findJobExecutions(jobInstance); List stepExecutions = new ArrayList(jobExecutions.size());