Add new methods to get the last job instance/execution in JobInstance/JobExecution DAOs

Before this commit, getting the last job instance/execution required to
load all job instances/executions from the database and filter them
on the client side in memory.

This commit introduces new methods that use database queries to get
the last job instance/execution without the need to load all job
instances/executions. This change improves memory consumption as well as
the performance of starting the next instance of a job.

Resolves BATCH-1784
This commit is contained in:
Mahmoud Ben Hassine
2019-06-17 15:32:25 +02:00
committed by Michael Minella
parent afc25dc873
commit 7c1fbede1b
9 changed files with 155 additions and 14 deletions

View File

@@ -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<JobInstance> 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<JobExecution> 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());
}
}

View File

@@ -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<JobInstance> 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<JobExecution> 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),

View File

@@ -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)
*

View File

@@ -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)
*

View File

@@ -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<JobInstance> 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.

View File

@@ -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<JobInstance> jobInstances = getJobInstances(jobName, 0, 1);
return jobInstances.isEmpty() ? null : jobInstances.get(0);
}
@Override
@Nullable
public JobInstance getJobInstance(JobExecution jobExecution) {

View File

@@ -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();

View File

@@ -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<JobInstance> getJobInstances(String jobName, int start, int count) {
if (jobInstances == null) {

View File

@@ -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<JobInstance> 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.
*/