diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/JobParametersBuilder.java b/spring-batch-core/src/main/java/org/springframework/batch/core/JobParametersBuilder.java index 99a3e42a7..6b194fb64 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/JobParametersBuilder.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/JobParametersBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2018 the original author or authors. + * Copyright 2006-2019 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. @@ -19,7 +19,6 @@ package org.springframework.batch.core; import java.util.Date; import java.util.HashMap; import java.util.LinkedHashMap; -import java.util.List; import java.util.Map; import java.util.Properties; @@ -256,20 +255,19 @@ public class JobParametersBuilder { String name = job.getName(); JobParameters nextParameters; - List lastInstances = this.jobExplorer.getJobInstances(name, 0, 1); + JobInstance lastInstance = this.jobExplorer.getLastJobInstance(name); JobParametersIncrementer incrementer = job.getJobParametersIncrementer(); - if (lastInstances.isEmpty()) { + if (lastInstance == null) { // Start from a completely clean sheet nextParameters = incrementer.getNext(new JobParameters()); } else { - List previousExecutions = this.jobExplorer.getJobExecutions(lastInstances.get(0)); - if (previousExecutions.isEmpty()) { + JobExecution previousExecution = this.jobExplorer.getLastJobExecution(lastInstance); + if (previousExecution == null) { // Normally this will not happen - an instance exists with no executions nextParameters = incrementer.getNext(new JobParameters()); } else { - JobExecution previousExecution = previousExecutions.get(0); nextParameters = incrementer.getNext(previousExecution.getJobParameters()); } } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/explore/JobExplorer.java b/spring-batch-core/src/main/java/org/springframework/batch/core/explore/JobExplorer.java index 7c5e36878..586ad67be 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/explore/JobExplorer.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/explore/JobExplorer.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2018 the original author or authors. + * Copyright 2006-2019 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. @@ -49,6 +49,18 @@ public interface JobExplorer { */ List getJobInstances(String jobName, int start, int count); + /** + * Find the last job instance by Id for the given job. + * @param jobName name of the job + * @return the last job instance by Id if any or null otherwise + * + * @since 4.2 + */ + @Nullable + default JobInstance getLastJobInstance(String jobName) { + throw new UnsupportedOperationException(); + } + /** * Retrieve a {@link JobExecution} by its id. The complete object graph for * this execution should be returned (unless otherwise indicated) including @@ -95,6 +107,20 @@ public interface JobExplorer { */ List getJobExecutions(JobInstance jobInstance); + /** + * Find the last {@link JobExecution} that has been created for a given + * {@link JobInstance}. + * @param jobInstance the {@link JobInstance} + * @return the last {@link JobExecution} that has been created for this instance or + * {@code null} if no job execution is found for the given job instance. + * + * @since 4.2 + */ + @Nullable + default JobExecution getLastJobExecution(JobInstance jobInstance) { + throw new UnsupportedOperationException(); + } + /** * Retrieve running job executions. The corresponding step executions may * not be fully hydrated (e.g. their execution context may be missing), diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/explore/support/SimpleJobExplorer.java b/spring-batch-core/src/main/java/org/springframework/batch/core/explore/support/SimpleJobExplorer.java index f01b55f40..9c5c7b79c 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/explore/support/SimpleJobExplorer.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/explore/support/SimpleJobExplorer.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2018 the original author or authors. + * Copyright 2006-2019 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. @@ -90,6 +90,18 @@ public class SimpleJobExplorer implements JobExplorer { return executions; } + /* + * (non-Javadoc) + * + * @see + * org.springframework.batch.core.explore.JobExplorer#getLastJobExecution( + * org.springframework.batch.core.JobInstance) + */ + @Nullable + public JobExecution getLastJobExecution(JobInstance jobInstance) { + return jobExecutionDao.getLastJobExecution(jobInstance); + } + /* * (non-Javadoc) * @@ -163,6 +175,18 @@ public class SimpleJobExplorer implements JobExplorer { return jobInstanceDao.getJobInstance(instanceId); } + /* + * (non-Javadoc) + * + * @see + * org.springframework.batch.core.explore.JobExplorer#getLastJobInstance(java + * .lang.String) + */ + @Override + public JobInstance getLastJobInstance(String jobName) { + return jobInstanceDao.getLastJobInstance(jobName); + } + /* * (non-Javadoc) * diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JdbcJobInstanceDao.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JdbcJobInstanceDao.java index 2bf447205..d9de3e1a5 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JdbcJobInstanceDao.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JdbcJobInstanceDao.java @@ -81,7 +81,10 @@ JobInstanceDao, InitializingBean { private static final String FIND_JOB_NAMES = "SELECT distinct JOB_NAME from %PREFIX%JOB_INSTANCE order by JOB_NAME"; private static final String FIND_LAST_JOBS_BY_NAME = "SELECT JOB_INSTANCE_ID, JOB_NAME from %PREFIX%JOB_INSTANCE where JOB_NAME = ? order by JOB_INSTANCE_ID desc"; - + + private static final String FIND_LAST_JOB_INSTANCE_BY_JOB_NAME = "SELECT JOB_INSTANCE_ID, JOB_NAME from %PREFIX%JOB_INSTANCE I1 where" + + " I1.JOB_NAME = ? and I1.JOB_INSTANCE_ID in (SELECT max(I2.JOB_INSTANCE_ID) from %PREFIX%JOB_INSTANCE I2 where I2.JOB_NAME = ?)"; + private static final String FIND_LAST_JOBS_LIKE_NAME = "SELECT JOB_INSTANCE_ID, JOB_NAME from %PREFIX%JOB_INSTANCE where JOB_NAME like ? order by JOB_INSTANCE_ID desc"; private DataFieldMaxValueIncrementer jobIncrementer; @@ -237,6 +240,25 @@ JobInstanceDao, InitializingBean { return result; } + /* + * (non-Javadoc) + * + * @see org.springframework.batch.core.repository.dao.JobInstanceDao# + * getLastJobInstance(java.lang.String) + */ + @Override + @Nullable + public JobInstance getLastJobInstance(String jobName) { + try { + return getJdbcTemplate().queryForObject( + getQuery(FIND_LAST_JOB_INSTANCE_BY_JOB_NAME), + new Object[] { jobName, jobName }, + new JobInstanceRowMapper()); + } catch (EmptyResultDataAccessException e) { + return null; + } + } + /* * (non-Javadoc) * diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JobInstanceDao.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JobInstanceDao.java index 9a49b21d1..92af7964e 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JobInstanceDao.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JobInstanceDao.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2018 the original author or authors. + * Copyright 2006-2019 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. @@ -96,6 +96,18 @@ public interface JobInstanceDao { */ List getJobInstances(String jobName, int start, int count); + /** + * Fetch the last job instance by Id for the given job. + * @param jobName name of the job + * @return the last job instance by Id if any or null otherwise + * + * @since 4.2 + */ + @Nullable + default JobInstance getLastJobInstance(String jobName) { + throw new UnsupportedOperationException(); + } + /** * Retrieve the names of all job instances sorted alphabetically - i.e. jobs * that have ever been executed. diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MapJobInstanceDao.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MapJobInstanceDao.java index dcbe4d94e..fa4175084 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MapJobInstanceDao.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MapJobInstanceDao.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2018 the original author or authors. + * Copyright 2006-2019 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. @@ -106,6 +106,13 @@ public class MapJobInstanceDao implements JobInstanceDao { return subset(result, start, count); } + @Override + @Nullable + public JobInstance getLastJobInstance(String jobName) { + List jobInstances = getJobInstances(jobName, 0, 1); + return jobInstances.isEmpty() ? null : jobInstances.get(0); + } + @Override @Nullable public JobInstance getJobInstance(JobExecution jobExecution) { diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/explore/support/SimpleJobExplorerTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/explore/support/SimpleJobExplorerTests.java index c9cd9ef3f..290243acc 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/explore/support/SimpleJobExplorerTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/explore/support/SimpleJobExplorerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2013 the original author or authors. + * Copyright 2006-2019 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. @@ -42,6 +42,7 @@ import org.springframework.batch.core.repository.dao.StepExecutionDao; * @author Dave Syer * @author Will Schipp * @author Michael Minella + * @author Mahmoud Ben Hassine * */ public class SimpleJobExplorerTests { @@ -82,6 +83,13 @@ public class SimpleJobExplorerTests { jobExplorer.getJobExecution(123L); } + @Test + public void testGetLastJobExecution() { + when(jobExecutionDao.getLastJobExecution(jobInstance)).thenReturn(jobExecution); + JobExecution lastJobExecution = jobExplorer.getLastJobExecution(jobInstance); + assertEquals(jobExecution, lastJobExecution); + } + @Test public void testMissingGetJobExecution() throws Exception { when(jobExecutionDao.getJobExecution(123L)).thenReturn(null); @@ -156,6 +164,13 @@ public class SimpleJobExplorerTests { jobExplorer.getJobInstances("foo", 0, 1); } + @Test + public void testGetLastJobInstance() { + when(jobInstanceDao.getLastJobInstance("foo")).thenReturn(jobInstance); + JobInstance lastJobInstance = jobExplorer.getLastJobInstance("foo"); + assertEquals(jobInstance, lastJobInstance); + } + @Test public void testGetJobNames() throws Exception { jobInstanceDao.getJobNames(); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/CommandLineJobRunnerTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/CommandLineJobRunnerTests.java index 977170661..f50b7191d 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/CommandLineJobRunnerTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/CommandLineJobRunnerTests.java @@ -505,6 +505,16 @@ public class CommandLineJobRunnerTests { throw new UnsupportedOperationException(); } + @Override + public JobInstance getLastJobInstance(String jobName) { + return null; + } + + @Override + public JobExecution getLastJobExecution(JobInstance jobInstance) { + return null; + } + @Override public List getJobInstances(String jobName, int start, int count) { if (jobInstances == null) { diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/AbstractJobInstanceDaoTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/AbstractJobInstanceDaoTests.java index 0f6516b9a..87b32711b 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/AbstractJobInstanceDaoTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/AbstractJobInstanceDaoTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2013 the original author or authors. + * Copyright 2008-2019 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. @@ -154,6 +154,33 @@ public abstract class AbstractJobInstanceDaoTests { } + @Transactional + @Test + public void testGetLastInstance() throws Exception { + testCreateAndRetrieve(); + + // unrelated job instance that should be ignored by the query + dao.createJobInstance("anotherJob", new JobParameters()); + + // we need two instances of the same job to check ordering + dao.createJobInstance(fooJob, new JobParameters()); + + List jobInstances = dao.getJobInstances(fooJob, 0, 2); + assertEquals(2, jobInstances.size()); + JobInstance lastJobInstance = dao.getLastJobInstance(fooJob); + assertNotNull(lastJobInstance); + assertEquals(fooJob, lastJobInstance.getJobName()); + assertEquals("Last instance should be first on the list", + jobInstances.get(0), lastJobInstance); + } + + @Transactional + @Test + public void testGetLastInstanceWhenNoInstance() { + JobInstance lastJobInstance = dao.getLastJobInstance("NonExistingJob"); + assertNull(lastJobInstance); + } + /** * Create and retrieve a job instance. */