Bug Description
MongoStepExecutionDao.countStepExecutions(JobInstance jobInstance, String stepName) does not use the stepName parameter in the query.
- Current behavior: Returns count of all StepExecutions for the JobInstance
- Expected behavior: Returns count of StepExecutions filtered by
stepName
Affected Code
// MongoStepExecutionDao.java (Line 165-177)
@Override
public long countStepExecutions(JobInstance jobInstance, String stepName) {
Query query = query(where("jobInstanceId").is(jobInstance.getId()));
List jobExecutions = this.mongoOperations.find(...);
return this.mongoOperations.count(
query(where("jobExecutionId").in(jobExecutions.stream()
.map(JobExecution::getJobExecutionId)
.toList())), // stepName parameter is ignored
StepExecution.class, STEP_EXECUTIONS_COLLECTION_NAME);
}
Comparison with JdbcStepExecutionDao
JdbcStepExecutionDao correctly filters by stepName:
-- JdbcStepExecutionDao.java (Line 106-111)
SELECT COUNT(*)
FROM BATCH_JOB_EXECUTION JE
JOIN BATCH_STEP_EXECUTION SE ON SE.JOB_EXECUTION_ID = JE.JOB_EXECUTION_ID
WHERE JE.JOB_INSTANCE_ID = ? AND SE.STEP_NAME = ?
Impact
This bug affects startLimit functionality. SimpleStepHandler.shouldStart() calls countStepExecutions() to validate startLimit:
// SimpleStepHandler.java (Line 205-221)
long stepExecutionCount = jobRepository.getStepExecutionCount(jobInstance, step.getName());
if (stepExecutionCount < step.getStartLimit()) {
return true;
} else {
throw new StartLimitExceededException(...);
}
Example:
| Step |
Actual Executions |
startLimit |
Bug Returns |
Result |
| stepA |
2 |
3 |
7 (total count) |
StartLimitExceededException (incorrect) |
| stepB |
5 |
10 |
7 (total count) |
Pass (accidentally correct) |
Test to Reproduce
@Test
void testCountStepExecutionsFiltersByStepName() {
// given
dao.createStepExecution("stepA", jobExecution);
dao.createStepExecution("stepA", jobExecution);
dao.createStepExecution("stepB", jobExecution);
dao.createStepExecution("stepC", jobExecution);
// when
long countA = dao.countStepExecutions(jobInstance, "stepA");
long countB = dao.countStepExecutions(jobInstance, "stepB");
long countC = dao.countStepExecutions(jobInstance, "stepC");
long countNonExistent = dao.countStepExecutions(jobInstance, "nonExistentStep");
// then
assertEquals(2, countA); // Bug: returns 4
assertEquals(1, countB); // Bug: returns 4
assertEquals(1, countC); // Bug: returns 4
assertEquals(0, countNonExistent); // Bug: returns 4
}
Suggested Fix
Add .and("name").is(stepName) to the query:
return this.mongoOperations.count(
query(where("jobExecutionId").in(jobExecutions.stream()
.map(JobExecution::getJobExecutionId)
.toList())
.and("name").is(stepName)), // Added stepName filter
StepExecution.class, STEP_EXECUTIONS_COLLECTION_NAME);
Additional Note
While investigating this issue, I noticed that there seem to be several areas where the MongoDB implementation is not fully synchronized with the JDBC implementation. For example:
- Optimistic locking behavior
- Other DAO method implementations
Would it be helpful if I do a broader review of the MongoDB module to identify other inconsistencies with the JDBC implementation? I'd be happy to contribute fixes for any issues found.
Bug Description
MongoStepExecutionDao.countStepExecutions(JobInstance jobInstance, String stepName)does not use thestepNameparameter in the query.stepNameAffected Code
Comparison with JdbcStepExecutionDao
JdbcStepExecutionDaocorrectly filters bystepName:Impact
This bug affects
startLimitfunctionality.SimpleStepHandler.shouldStart()callscountStepExecutions()to validatestartLimit:Example:
StartLimitExceededException(incorrect)Test to Reproduce
Suggested Fix
Add
.and("name").is(stepName)to the query:Additional Note
While investigating this issue, I noticed that there seem to be several areas where the MongoDB implementation is not fully synchronized with the JDBC implementation. For example:
Would it be helpful if I do a broader review of the MongoDB module to identify other inconsistencies with the JDBC implementation? I'd be happy to contribute fixes for any issues found.