diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/JobExecution.java b/spring-batch-core/src/main/java/org/springframework/batch/core/JobExecution.java index 23dceb659..483578f31 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/JobExecution.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/JobExecution.java @@ -42,6 +42,8 @@ public class JobExecution extends Entity { private volatile Date createTime = new Date(System.currentTimeMillis()); private volatile Date endTime = null; + + private volatile Date lastUpdated = null; private volatile ExitStatus exitStatus = ExitStatus.UNKNOWN; @@ -222,4 +224,13 @@ public class JobExecution extends Entity { void addStepExecution(StepExecution stepExecution) { stepExecutions.add(stepExecution); } -} + + public Date getLastUpdated() { + return lastUpdated; + } + + public void setLastUpdated(Date lastUpdated) { + this.lastUpdated = lastUpdated; + } + +} \ No newline at end of file diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JdbcJobExecutionDao.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JdbcJobExecutionDao.java index b0b400f5a..17538ca98 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JdbcJobExecutionDao.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JdbcJobExecutionDao.java @@ -40,28 +40,28 @@ public class JdbcJobExecutionDao extends AbstractJdbcBatchMetadataDao implements private static final int DEFAULT_EXIT_MESSAGE_LENGTH = 2500; private static final String SAVE_JOB_EXECUTION = "INSERT into %PREFIX%JOB_EXECUTION(JOB_EXECUTION_ID, JOB_INSTANCE_ID, START_TIME, " - + "END_TIME, STATUS, CONTINUABLE, EXIT_CODE, EXIT_MESSAGE, VERSION, CREATE_TIME) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; + + "END_TIME, STATUS, CONTINUABLE, EXIT_CODE, EXIT_MESSAGE, VERSION, CREATE_TIME, LAST_UPDATED) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; private static final String CHECK_JOB_EXECUTION_EXISTS = "SELECT COUNT(*) FROM %PREFIX%JOB_EXECUTION WHERE JOB_EXECUTION_ID = ?"; private static final String GET_STATUS = "SELECT STATUS from %PREFIX%JOB_EXECUTION where JOB_EXECUTION_ID = ?"; private static final String UPDATE_JOB_EXECUTION = "UPDATE %PREFIX%JOB_EXECUTION set START_TIME = ?, END_TIME = ?, " - + " STATUS = ?, CONTINUABLE = ?, EXIT_CODE = ?, EXIT_MESSAGE = ?, VERSION = ?, CREATE_TIME = ? where JOB_EXECUTION_ID = ?"; + + " STATUS = ?, CONTINUABLE = ?, EXIT_CODE = ?, EXIT_MESSAGE = ?, VERSION = ?, CREATE_TIME = ?, LAST_UPDATED = ? where JOB_EXECUTION_ID = ?"; - private static final String FIND_JOB_EXECUTIONS = "SELECT JOB_EXECUTION_ID, START_TIME, END_TIME, STATUS, CONTINUABLE, EXIT_CODE, EXIT_MESSAGE, CREATE_TIME from %PREFIX%JOB_EXECUTION" - + " where JOB_INSTANCE_ID = ?"; + private static final String FIND_JOB_EXECUTIONS = "SELECT JOB_EXECUTION_ID, START_TIME, END_TIME, STATUS, CONTINUABLE, EXIT_CODE, EXIT_MESSAGE, CREATE_TIME, LAST_UPDATED" + + " from %PREFIX%JOB_EXECUTION where JOB_INSTANCE_ID = ?"; - private static final String GET_LAST_EXECUTION = "SELECT JOB_EXECUTION_ID, START_TIME, END_TIME, STATUS, CONTINUABLE, EXIT_CODE, EXIT_MESSAGE, CREATE_TIME from %PREFIX%JOB_EXECUTION" - + " where JOB_INSTANCE_ID = ? and CREATE_TIME = (SELECT max(CREATE_TIME) from %PREFIX%JOB_EXECUTION where JOB_INSTANCE_ID = ?)"; + private static final String GET_LAST_EXECUTION = "SELECT JOB_EXECUTION_ID, START_TIME, END_TIME, STATUS, CONTINUABLE, EXIT_CODE, EXIT_MESSAGE, CREATE_TIME, LAST_UPDATED " + + "from %PREFIX%JOB_EXECUTION where JOB_INSTANCE_ID = ? and CREATE_TIME = (SELECT max(CREATE_TIME) from %PREFIX%JOB_EXECUTION where JOB_INSTANCE_ID = ?)"; private static final String GET_INSTANCE_BY_EXECUTION_ID = "SELECT JOB_INSTANCE_ID from %PREFIX%JOB_EXECUTION where JOB_EXECUTION_ID = ?"; - private static final String GET_EXECUTION_BY_ID = "SELECT JOB_EXECUTION_ID, START_TIME, END_TIME, STATUS, CONTINUABLE, EXIT_CODE, EXIT_MESSAGE, CREATE_TIME from %PREFIX%JOB_EXECUTION" - + " where JOB_EXECUTION_ID = ?"; + private static final String GET_EXECUTION_BY_ID = "SELECT JOB_EXECUTION_ID, START_TIME, END_TIME, STATUS, CONTINUABLE, EXIT_CODE, EXIT_MESSAGE, CREATE_TIME, LAST_UPDATED" + + " from %PREFIX%JOB_EXECUTION where JOB_EXECUTION_ID = ?"; - private static final String GET_RUNNING_EXECUTIONS = "SELECT JOB_EXECUTION_ID, START_TIME, END_TIME, STATUS, CONTINUABLE, EXIT_CODE, EXIT_MESSAGE, CREATE_TIME, JOB_INSTANCE_ID from %PREFIX%JOB_EXECUTION" - + " where END_TIME is NULL"; + private static final String GET_RUNNING_EXECUTIONS = "SELECT JOB_EXECUTION_ID, START_TIME, END_TIME, STATUS, CONTINUABLE, EXIT_CODE, EXIT_MESSAGE, CREATE_TIME, LAST_UPDATED, " + + "JOB_INSTANCE_ID from %PREFIX%JOB_EXECUTION where END_TIME is NULL"; private int exitMessageLength = DEFAULT_EXIT_MESSAGE_LENGTH; @@ -142,12 +142,12 @@ public class JdbcJobExecutionDao extends AbstractJdbcBatchMetadataDao implements jobExecution.getStartTime(), jobExecution.getEndTime(), jobExecution.getStatus().toString(), jobExecution.getExitStatus().isContinuable() ? "Y" : "N", jobExecution.getExitStatus().getExitCode(), jobExecution.getExitStatus().getExitDescription(), jobExecution.getVersion(), - jobExecution.getCreateTime() }; + jobExecution.getCreateTime(), jobExecution.getLastUpdated() }; getJdbcTemplate().getJdbcOperations().update( getQuery(SAVE_JOB_EXECUTION), parameters, new int[] { Types.INTEGER, Types.INTEGER, Types.TIMESTAMP, Types.TIMESTAMP, Types.VARCHAR, Types.CHAR, - Types.VARCHAR, Types.VARCHAR, Types.INTEGER, Types.TIMESTAMP }); + Types.VARCHAR, Types.VARCHAR, Types.INTEGER, Types.TIMESTAMP, Types.TIMESTAMP }); } /** @@ -187,7 +187,7 @@ public class JdbcJobExecutionDao extends AbstractJdbcBatchMetadataDao implements Object[] parameters = new Object[] { jobExecution.getStartTime(), jobExecution.getEndTime(), jobExecution.getStatus().toString(), jobExecution.getExitStatus().isContinuable() ? "Y" : "N", jobExecution.getExitStatus().getExitCode(), exitDescription, jobExecution.getVersion(), - jobExecution.getCreateTime(), jobExecution.getId() }; + jobExecution.getCreateTime(), jobExecution.getLastUpdated(), jobExecution.getId() }; if (jobExecution.getId() == null) { throw new IllegalArgumentException("JobExecution ID cannot be null. JobExecution must be saved " @@ -205,7 +205,7 @@ public class JdbcJobExecutionDao extends AbstractJdbcBatchMetadataDao implements getQuery(UPDATE_JOB_EXECUTION), parameters, new int[] { Types.TIMESTAMP, Types.TIMESTAMP, Types.VARCHAR, Types.CHAR, Types.VARCHAR, Types.VARCHAR, - Types.INTEGER, Types.TIMESTAMP, Types.INTEGER }); + Types.INTEGER, Types.TIMESTAMP, Types.TIMESTAMP, Types.INTEGER }); } public JobExecution getLastJobExecution(JobInstance jobInstance) { @@ -299,7 +299,8 @@ public class JdbcJobExecutionDao extends AbstractJdbcBatchMetadataDao implements jobExecution.setEndTime(rs.getTimestamp(3)); jobExecution.setStatus(BatchStatus.valueOf(rs.getString(4))); jobExecution.setExitStatus(new ExitStatus("Y".equals(rs.getString(5)), rs.getString(6), rs.getString(7))); - jobExecution.setCreateTime(rs.getDate(8)); + jobExecution.setCreateTime(rs.getTimestamp(8)); + jobExecution.setLastUpdated(rs.getTimestamp(9)); return 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 3341c972d..55ff0b8f3 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 @@ -188,6 +188,7 @@ public class SimpleJobRepository implements JobRepository { JobExecution jobExecution = new JobExecution(jobInstance); jobExecution.setExecutionContext(executionContext); + jobExecution.setLastUpdated(new Date(System.currentTimeMillis())); // Save the JobExecution so that it picks up an ID (useful for clients // monitoring asynchronous executions): @@ -214,6 +215,7 @@ public class SimpleJobRepository implements JobRepository { Assert.notNull(jobExecution.getJobId(), "JobExecution must have a Job ID set."); Assert.notNull(jobExecution.getId(), "JobExecution must be already saved (have an id assigned)."); + jobExecution.setLastUpdated(new Date(System.currentTimeMillis())); jobExecutionDao.updateJobExecution(jobExecution); } diff --git a/spring-batch-core/src/main/resources/schema-hsqldb.sql b/spring-batch-core/src/main/resources/schema-hsqldb.sql index 174191744..ba855e2ad 100644 --- a/spring-batch-core/src/main/resources/schema-hsqldb.sql +++ b/spring-batch-core/src/main/resources/schema-hsqldb.sql @@ -27,7 +27,8 @@ CREATE TABLE BATCH_JOB_EXECUTION ( STATUS VARCHAR(10) , CONTINUABLE CHAR(1) , EXIT_CODE VARCHAR(20) , - EXIT_MESSAGE VARCHAR(2500) , + EXIT_MESSAGE VARCHAR(2500) , + LAST_UPDATED TIMESTAMP , constraint JOB_INST_EXEC_FK foreign key (JOB_INSTANCE_ID) references BATCH_JOB_INSTANCE(JOB_INSTANCE_ID) ) ; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/AbstractJobExecutionDaoTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/AbstractJobExecutionDaoTests.java index ba07914aa..b0924c23f 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/AbstractJobExecutionDaoTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/AbstractJobExecutionDaoTests.java @@ -50,11 +50,14 @@ public abstract class AbstractJobExecutionDaoTests extends AbstractTransactional @Test public void testSaveAndFind() { + execution.setStartTime(new Date(System.currentTimeMillis())); + execution.setLastUpdated(new Date(System.currentTimeMillis())); dao.saveJobExecution(execution); List executions = dao.findJobExecutions(jobInstance); assertEquals(1, executions.size()); assertEquals(execution, executions.get(0)); + assertExecutionsAreEqual(execution, executions.get(0)); } /** @@ -91,12 +94,14 @@ public abstract class AbstractJobExecutionDaoTests extends AbstractTransactional execution.setStatus(BatchStatus.STARTED); dao.saveJobExecution(execution); + execution.setLastUpdated(new Date(0)); execution.setStatus(BatchStatus.COMPLETED); dao.updateJobExecution(execution); JobExecution updated = dao.findJobExecutions(jobInstance).get(0); assertEquals(execution, updated); assertEquals(BatchStatus.COMPLETED, updated.getStatus()); + assertExecutionsAreEqual(execution, updated); } /** @@ -136,9 +141,11 @@ public abstract class AbstractJobExecutionDaoTests extends AbstractTransactional public void testFindRunningExecutions() { JobExecution exec = new JobExecution(jobInstance); exec.setCreateTime(new Date(0)); - exec.setEndTime(new Date(0)); + exec.setEndTime(new Date(1L)); + exec.setLastUpdated(new Date(5L)); dao.saveJobExecution(exec); exec = new JobExecution(jobInstance); + exec.setLastUpdated(new Date(5L)); exec.createStepExecution(new StepSupport("foo")); dao.saveJobExecution(exec); StepExecutionDao stepExecutionDao = getStepExecutionDao(); @@ -152,6 +159,7 @@ public abstract class AbstractJobExecutionDaoTests extends AbstractTransactional assertEquals(1, values.size()); JobExecution value = values.iterator().next(); assertEquals(exec, value); + assertEquals(5L, value.getLastUpdated().getTime()); assertEquals(1, value.getStepExecutions().size()); } @@ -198,4 +206,19 @@ public abstract class AbstractJobExecutionDaoTests extends AbstractTransactional assertNull(value); } + /* + * Check to make sure the executions are equal. Normally, comparing the id's is + * sufficient. However, for testing purposes, especially of a dao, we need to make + * sure all the fields are being stored/retrieved correctly. + */ + private void assertExecutionsAreEqual(JobExecution lhs, JobExecution rhs){ + + assertEquals(lhs.getId(), rhs.getId()); + assertEquals(lhs.getStartTime(), rhs.getStartTime()); + assertEquals(lhs.getStatus(), rhs.getStatus()); + assertEquals(lhs.getEndTime(), rhs.getEndTime()); + assertEquals(lhs.getCreateTime(), rhs.getCreateTime()); + assertEquals(lhs.getLastUpdated(), rhs.getLastUpdated()); + } + } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/SimpleJobRepositoryIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/SimpleJobRepositoryIntegrationTests.java index 27cde414d..f69cd151a 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/SimpleJobRepositoryIntegrationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/SimpleJobRepositoryIntegrationTests.java @@ -57,6 +57,7 @@ public class SimpleJobRepositoryIntegrationTests { JobExecution firstExecution = jobRepository.createJobExecution(job, jobParams); firstExecution.setStartTime(new Date()); + assertNotNull(firstExecution.getLastUpdated()); assertEquals(job.getName(), firstExecution.getJobInstance().getJobName()); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/SimpleJobRepositoryTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/SimpleJobRepositoryTests.java index e92d2ae60..3f9a5440a 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/SimpleJobRepositoryTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/SimpleJobRepositoryTests.java @@ -132,12 +132,13 @@ public class SimpleJobRepositoryTests { public void testUpdateValidJobExecution() throws Exception { JobExecution jobExecution = new JobExecution(new JobInstance(new Long(1), jobParameters, job.getName()), new Long(1)); - // new execution - call update on job dao jobExecutionDao.updateJobExecution(jobExecution); replay(jobExecutionDao); jobRepository.update(jobExecution); verify(jobExecutionDao); + + assertNotNull(jobExecution.getLastUpdated()); } @Test