From a16402277511a48e6aba57c73e627a4d7804354f Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Sat, 12 Oct 2024 00:47:02 +0200 Subject: [PATCH 001/266] Next development version --- pom.xml | 2 +- spring-batch-bom/pom.xml | 2 +- spring-batch-core/pom.xml | 2 +- spring-batch-docs/pom.xml | 2 +- spring-batch-infrastructure/pom.xml | 2 +- spring-batch-integration/pom.xml | 2 +- spring-batch-samples/pom.xml | 2 +- spring-batch-test/pom.xml | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index eb854c35f1..b98989a2ec 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ designed to enable the development of robust batch applications vital for the daily operations of enterprise systems. Spring Batch is part of the Spring Portfolio. - 5.2.0-M2 + 5.2.0-SNAPSHOT pom https://projects.spring.io/spring-batch diff --git a/spring-batch-bom/pom.xml b/spring-batch-bom/pom.xml index ea12c3c9aa..7ad96c8a70 100644 --- a/spring-batch-bom/pom.xml +++ b/spring-batch-bom/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0-M2 + 5.2.0-SNAPSHOT spring-batch-bom pom diff --git a/spring-batch-core/pom.xml b/spring-batch-core/pom.xml index bf804530b2..6ef5cfa1a6 100644 --- a/spring-batch-core/pom.xml +++ b/spring-batch-core/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0-M2 + 5.2.0-SNAPSHOT spring-batch-core jar diff --git a/spring-batch-docs/pom.xml b/spring-batch-docs/pom.xml index cb4ed9387d..c0e835ea10 100644 --- a/spring-batch-docs/pom.xml +++ b/spring-batch-docs/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0-M2 + 5.2.0-SNAPSHOT spring-batch-docs Spring Batch Docs diff --git a/spring-batch-infrastructure/pom.xml b/spring-batch-infrastructure/pom.xml index 2f924ba9d6..f980ee0bd1 100644 --- a/spring-batch-infrastructure/pom.xml +++ b/spring-batch-infrastructure/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0-M2 + 5.2.0-SNAPSHOT spring-batch-infrastructure jar diff --git a/spring-batch-integration/pom.xml b/spring-batch-integration/pom.xml index 121eaeb8fc..bfc85952bd 100644 --- a/spring-batch-integration/pom.xml +++ b/spring-batch-integration/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0-M2 + 5.2.0-SNAPSHOT spring-batch-integration Spring Batch Integration diff --git a/spring-batch-samples/pom.xml b/spring-batch-samples/pom.xml index 250a969e0c..9d433e897b 100644 --- a/spring-batch-samples/pom.xml +++ b/spring-batch-samples/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0-M2 + 5.2.0-SNAPSHOT spring-batch-samples jar diff --git a/spring-batch-test/pom.xml b/spring-batch-test/pom.xml index 9aefd49260..15bbf21f7b 100644 --- a/spring-batch-test/pom.xml +++ b/spring-batch-test/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0-M2 + 5.2.0-SNAPSHOT spring-batch-test Spring Batch Test From 8a51712e41c4139426992f780c260602409766b2 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Sat, 12 Oct 2024 01:31:48 +0200 Subject: [PATCH 002/266] Update Spring dependencies to latest snapshots --- pom.xml | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/pom.xml b/pom.xml index b98989a2ec..6e0983da71 100644 --- a/pom.xml +++ b/pom.xml @@ -61,19 +61,19 @@ 17 - 6.2.0-RC1 - 2.0.9 - 6.4.0-M3 - 1.14.0-M3 + 6.2.0-SNAPSHOT + 2.0.10-SNAPSHOT + 6.4.0-SNAPSHOT + 1.14.0-SNAPSHOT - 3.4.0-M1 - 3.4.0-M1 - 3.4.0-M1 - 4.4.0-M1 - 3.3.0-M3 - 3.2.0-M3 - 3.2.6 + 3.4.0-SNAPSHOT + 3.4.0-SNAPSHOT + 3.4.0-SNAPSHOT + 4.4.0-SNAPSHOT + 3.3.0-SNAPSHOT + 3.2.0-SNAPSHOT + 3.2.7-SNAPSHOT 2.18.0 1.12.0 @@ -92,7 +92,7 @@ 3.0.2 - 1.4.0-M3 + 1.4.0-SNAPSHOT 1.4.20 4.13.2 From ffe3687c94412c3a9c156c1cc85e78bf974349b1 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Sat, 12 Oct 2024 01:42:56 +0200 Subject: [PATCH 003/266] Add latest news in README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 3dd6c56f63..857ade356a 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +# Latest news: [Spring Batch 5.2.0-M2 is available now!](https://spring.io/blog/2024/10/11/spring-batch-5-2-0-m2-is-available-now) + # Spring Batch [![build status](https://github.com/spring-projects/spring-batch/actions/workflows/continuous-integration.yml/badge.svg)](https://github.com/spring-projects/spring-batch/actions/workflows/continuous-integration.yml) From c2a4bf61461d4e00118820fddd3355c27e6e33e7 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Tue, 22 Oct 2024 11:22:39 +0200 Subject: [PATCH 004/266] Fix last step execution retrieval in MongoStepExecutionDao Resolves https://github.com/spring-projects-experimental/spring-batch-experimental/issues/3 --- .../batch/core/repository/dao/MongoStepExecutionDao.java | 1 + 1 file changed, 1 insertion(+) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoStepExecutionDao.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoStepExecutionDao.java index 44215babd7..9b889c1d81 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoStepExecutionDao.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoStepExecutionDao.java @@ -111,6 +111,7 @@ public StepExecution getLastStepExecution(JobInstance jobInstance, String stepNa // first one Optional lastStepExecution = stepExecutions .stream() + .filter(stepExecution -> stepExecution.getName().equals(stepName)) .min(Comparator .comparing(org.springframework.batch.core.repository.persistence.StepExecution::getCreateTime) .thenComparing(org.springframework.batch.core.repository.persistence.StepExecution::getId)); From fc9460b14a0c4029cf757389f2615d146bb3d9da Mon Sep 17 00:00:00 2001 From: hyejinggu <118355536+hyejinggu@users.noreply.github.com> Date: Sat, 5 Oct 2024 18:52:24 +0900 Subject: [PATCH 005/266] Fix column types in JdbcStepExecutionDao Resolves #4648 --- .../repository/dao/JdbcStepExecutionDao.java | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JdbcStepExecutionDao.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JdbcStepExecutionDao.java index 594e2e7eef..5782dd2277 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JdbcStepExecutionDao.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JdbcStepExecutionDao.java @@ -281,9 +281,9 @@ public void updateStepExecution(StepExecution stepExecution) { stepExecution.getWriteSkipCount(), stepExecution.getRollbackCount(), lastUpdated, stepExecution.getId(), stepExecution.getVersion() }; int count = getJdbcTemplate().update(getQuery(UPDATE_STEP_EXECUTION), parameters, - new int[] { Types.TIMESTAMP, Types.TIMESTAMP, Types.VARCHAR, Types.INTEGER, Types.INTEGER, - Types.INTEGER, Types.INTEGER, Types.VARCHAR, Types.VARCHAR, Types.INTEGER, Types.INTEGER, - Types.INTEGER, Types.INTEGER, Types.INTEGER, Types.TIMESTAMP, Types.BIGINT, + new int[] { Types.TIMESTAMP, Types.TIMESTAMP, Types.VARCHAR, Types.BIGINT, Types.BIGINT, + Types.BIGINT, Types.BIGINT, Types.VARCHAR, Types.VARCHAR, Types.INTEGER, Types.BIGINT, + Types.BIGINT, Types.BIGINT, Types.BIGINT, Types.TIMESTAMP, Types.BIGINT, Types.INTEGER }); // Avoid concurrent modifications... @@ -396,15 +396,15 @@ public StepExecution mapRow(ResultSet rs, int rowNum) throws SQLException { stepExecution.setStartTime(rs.getTimestamp(3) == null ? null : rs.getTimestamp(3).toLocalDateTime()); stepExecution.setEndTime(rs.getTimestamp(4) == null ? null : rs.getTimestamp(4).toLocalDateTime()); stepExecution.setStatus(BatchStatus.valueOf(rs.getString(5))); - stepExecution.setCommitCount(rs.getInt(6)); - stepExecution.setReadCount(rs.getInt(7)); - stepExecution.setFilterCount(rs.getInt(8)); - stepExecution.setWriteCount(rs.getInt(9)); + stepExecution.setCommitCount(rs.getLong(6)); + stepExecution.setReadCount(rs.getLong(7)); + stepExecution.setFilterCount(rs.getLong(8)); + stepExecution.setWriteCount(rs.getLong(9)); stepExecution.setExitStatus(new ExitStatus(rs.getString(10), rs.getString(11))); - stepExecution.setReadSkipCount(rs.getInt(12)); - stepExecution.setWriteSkipCount(rs.getInt(13)); - stepExecution.setProcessSkipCount(rs.getInt(14)); - stepExecution.setRollbackCount(rs.getInt(15)); + stepExecution.setReadSkipCount(rs.getLong(12)); + stepExecution.setWriteSkipCount(rs.getLong(13)); + stepExecution.setProcessSkipCount(rs.getLong(14)); + stepExecution.setRollbackCount(rs.getLong(15)); stepExecution.setLastUpdated(rs.getTimestamp(16) == null ? null : rs.getTimestamp(16).toLocalDateTime()); stepExecution.setVersion(rs.getInt(17)); stepExecution.setCreateTime(rs.getTimestamp(18) == null ? null : rs.getTimestamp(18).toLocalDateTime()); From f40c415e38f65b0c08575da389a94136742138fe Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Tue, 22 Oct 2024 22:48:00 +0200 Subject: [PATCH 006/266] Fix code formatting --- .../batch/core/repository/dao/JdbcStepExecutionDao.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JdbcStepExecutionDao.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JdbcStepExecutionDao.java index 5782dd2277..b1e46e0c23 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JdbcStepExecutionDao.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JdbcStepExecutionDao.java @@ -283,8 +283,7 @@ public void updateStepExecution(StepExecution stepExecution) { int count = getJdbcTemplate().update(getQuery(UPDATE_STEP_EXECUTION), parameters, new int[] { Types.TIMESTAMP, Types.TIMESTAMP, Types.VARCHAR, Types.BIGINT, Types.BIGINT, Types.BIGINT, Types.BIGINT, Types.VARCHAR, Types.VARCHAR, Types.INTEGER, Types.BIGINT, - Types.BIGINT, Types.BIGINT, Types.BIGINT, Types.TIMESTAMP, Types.BIGINT, - Types.INTEGER }); + Types.BIGINT, Types.BIGINT, Types.BIGINT, Types.TIMESTAMP, Types.BIGINT, Types.INTEGER }); // Avoid concurrent modifications... if (count == 0) { From 050fce59231c4271fde2760da23b906e20f22059 Mon Sep 17 00:00:00 2001 From: pxzxj Date: Wed, 4 Sep 2024 15:39:37 +0800 Subject: [PATCH 007/266] Fix typo in word AsynchItemWriter Resolves #4649 --- .../ROOT/pages/spring-batch-integration/sub-elements.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-batch-docs/modules/ROOT/pages/spring-batch-integration/sub-elements.adoc b/spring-batch-docs/modules/ROOT/pages/spring-batch-integration/sub-elements.adoc index eac14f4e7c..205a8669e2 100644 --- a/spring-batch-docs/modules/ROOT/pages/spring-batch-integration/sub-elements.adoc +++ b/spring-batch-docs/modules/ROOT/pages/spring-batch-integration/sub-elements.adoc @@ -182,7 +182,7 @@ The following example shows the how to add a step-level listener in XML: Asynchronous Processors help you scale the processing of items. In the asynchronous processor use case, an `AsyncItemProcessor` serves as a dispatcher, executing the logic of the `ItemProcessor` for an item on a new thread. Once the item completes, the `Future` is -passed to the `AsynchItemWriter` to be written. +passed to the `AsyncItemWriter` to be written. Therefore, you can increase performance by using asynchronous item processing, basically letting you implement fork-join scenarios. The `AsyncItemWriter` gathers the results and From c6f502909ccb69c029314ee7518dbe134d3f4550 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Wed, 23 Oct 2024 05:57:09 +0200 Subject: [PATCH 008/266] Clarify the behaviour of MultiResourceItemWriter with regard to file creation Resolves #4645 --- .../batch/item/file/MultiResourceItemWriter.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/MultiResourceItemWriter.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/MultiResourceItemWriter.java index 1480cba407..835abb3527 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/MultiResourceItemWriter.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/MultiResourceItemWriter.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2023 the original author or authors. + * Copyright 2006-2024 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. @@ -36,7 +36,11 @@ *

* Note that new resources are created only at chunk boundaries i.e. the number of items * written into one resource is between the limit set by - * {@link #setItemCountLimitPerResource(int)} and (limit + chunk size). + *

+ * This writer will create an output file only when there are items to write, which means + * there would be no empty file created if no items are passed (for example when all items + * are filtered or skipped during the processing phase). + *

* * @param item type * @author Robert Kasanicky From 7c10a0f8131c76decc9cb6c7cc07495294b98b79 Mon Sep 17 00:00:00 2001 From: Tobias Berse Date: Mon, 14 Oct 2024 15:13:17 +0200 Subject: [PATCH 009/266] Allow subclasses of items in CompositeItemReader's generics Resolves #https://github.com/spring-projects-experimental/spring-batch-experimental/issues/2 --- .../batch/item/support/CompositeItemReader.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/support/CompositeItemReader.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/support/CompositeItemReader.java index e9b5a72d07..06148a346c 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/support/CompositeItemReader.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/support/CompositeItemReader.java @@ -32,17 +32,17 @@ */ public class CompositeItemReader implements ItemStreamReader { - private final List> delegates; + private final List> delegates; - private final Iterator> delegatesIterator; + private final Iterator> delegatesIterator; - private ItemStreamReader currentDelegate; + private ItemStreamReader currentDelegate; /** * Create a new {@link CompositeItemReader}. * @param delegates the delegate readers to read data */ - public CompositeItemReader(List> delegates) { + public CompositeItemReader(List> delegates) { this.delegates = delegates; this.delegatesIterator = this.delegates.iterator(); this.currentDelegate = this.delegatesIterator.hasNext() ? this.delegatesIterator.next() : null; @@ -52,7 +52,7 @@ public CompositeItemReader(List> delegates) { // opening resources early for a long time @Override public void open(ExecutionContext executionContext) throws ItemStreamException { - for (ItemStreamReader delegate : delegates) { + for (ItemStreamReader delegate : delegates) { delegate.open(executionContext); } } @@ -79,7 +79,7 @@ public void update(ExecutionContext executionContext) throws ItemStreamException @Override public void close() throws ItemStreamException { - for (ItemStreamReader delegate : delegates) { + for (ItemStreamReader delegate : delegates) { delegate.close(); } } From dd1052510e6e792c462aa48334b57a39d99ddae8 Mon Sep 17 00:00:00 2001 From: Taeik Lim Date: Sun, 9 Jun 2024 22:22:35 +0900 Subject: [PATCH 010/266] Add '@FunctionalInterface' to JobKeyGenerator Resolves #4613 Signed-off-by: Taeik Lim --- .../java/org/springframework/batch/core/JobKeyGenerator.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/JobKeyGenerator.java b/spring-batch-core/src/main/java/org/springframework/batch/core/JobKeyGenerator.java index 589434b97f..147a26a37c 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/JobKeyGenerator.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/JobKeyGenerator.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2022 the original author or authors. + * Copyright 2013-2024 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. @@ -21,9 +21,11 @@ * * @author Michael Minella * @author Mahmoud Ben Hassine + * @author Taeik Lim * @param The type of the source data used to calculate the key. * @since 2.2 */ +@FunctionalInterface public interface JobKeyGenerator { /** From b43b35bb6d67bd5d726e335ab624a61c5c21c1f1 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Wed, 23 Oct 2024 19:49:01 +0200 Subject: [PATCH 011/266] Prepare release 5.2.0-RC1 --- pom.xml | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/pom.xml b/pom.xml index 6e0983da71..a35392c05a 100644 --- a/pom.xml +++ b/pom.xml @@ -61,19 +61,19 @@ 17 - 6.2.0-SNAPSHOT - 2.0.10-SNAPSHOT - 6.4.0-SNAPSHOT - 1.14.0-SNAPSHOT + 6.2.0-RC2 + 2.0.10 + 6.4.0-RC1 + 1.14.0-RC1 - 3.4.0-SNAPSHOT - 3.4.0-SNAPSHOT - 3.4.0-SNAPSHOT - 4.4.0-SNAPSHOT - 3.3.0-SNAPSHOT - 3.2.0-SNAPSHOT - 3.2.7-SNAPSHOT + 3.4.0-RC1 + 3.4.0-RC1 + 3.4.0-RC1 + 4.4.0-RC1 + 3.3.0-RC1 + 3.2.0-RC1 + 3.2.7 2.18.0 1.12.0 @@ -92,7 +92,7 @@ 3.0.2 - 1.4.0-SNAPSHOT + 1.4.0-RC1 1.4.20 4.13.2 From 0911e8950ea74a96c00c49b415a989ff8d4591a6 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Wed, 23 Oct 2024 19:54:39 +0200 Subject: [PATCH 012/266] Release version 5.2.0-RC1 --- pom.xml | 2 +- spring-batch-bom/pom.xml | 2 +- spring-batch-core/pom.xml | 2 +- spring-batch-docs/pom.xml | 2 +- spring-batch-infrastructure/pom.xml | 2 +- spring-batch-integration/pom.xml | 2 +- spring-batch-samples/pom.xml | 2 +- spring-batch-test/pom.xml | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index a35392c05a..c3c6efd3eb 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ designed to enable the development of robust batch applications vital for the daily operations of enterprise systems. Spring Batch is part of the Spring Portfolio. - 5.2.0-SNAPSHOT + 5.2.0-RC1 pom https://projects.spring.io/spring-batch diff --git a/spring-batch-bom/pom.xml b/spring-batch-bom/pom.xml index 7ad96c8a70..c28427da02 100644 --- a/spring-batch-bom/pom.xml +++ b/spring-batch-bom/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0-SNAPSHOT + 5.2.0-RC1 spring-batch-bom pom diff --git a/spring-batch-core/pom.xml b/spring-batch-core/pom.xml index 6ef5cfa1a6..07212da607 100644 --- a/spring-batch-core/pom.xml +++ b/spring-batch-core/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0-SNAPSHOT + 5.2.0-RC1 spring-batch-core jar diff --git a/spring-batch-docs/pom.xml b/spring-batch-docs/pom.xml index c0e835ea10..11691e23de 100644 --- a/spring-batch-docs/pom.xml +++ b/spring-batch-docs/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0-SNAPSHOT + 5.2.0-RC1 spring-batch-docs Spring Batch Docs diff --git a/spring-batch-infrastructure/pom.xml b/spring-batch-infrastructure/pom.xml index f980ee0bd1..cf4b1c7231 100644 --- a/spring-batch-infrastructure/pom.xml +++ b/spring-batch-infrastructure/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0-SNAPSHOT + 5.2.0-RC1 spring-batch-infrastructure jar diff --git a/spring-batch-integration/pom.xml b/spring-batch-integration/pom.xml index bfc85952bd..8897577026 100644 --- a/spring-batch-integration/pom.xml +++ b/spring-batch-integration/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0-SNAPSHOT + 5.2.0-RC1 spring-batch-integration Spring Batch Integration diff --git a/spring-batch-samples/pom.xml b/spring-batch-samples/pom.xml index 9d433e897b..c64de58a34 100644 --- a/spring-batch-samples/pom.xml +++ b/spring-batch-samples/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0-SNAPSHOT + 5.2.0-RC1 spring-batch-samples jar diff --git a/spring-batch-test/pom.xml b/spring-batch-test/pom.xml index 15bbf21f7b..a843c3cf50 100644 --- a/spring-batch-test/pom.xml +++ b/spring-batch-test/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0-SNAPSHOT + 5.2.0-RC1 spring-batch-test Spring Batch Test From cf44ca834b94de1cec9d82263da1564b44956017 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Wed, 23 Oct 2024 19:54:55 +0200 Subject: [PATCH 013/266] Next development version --- pom.xml | 2 +- spring-batch-bom/pom.xml | 2 +- spring-batch-core/pom.xml | 2 +- spring-batch-docs/pom.xml | 2 +- spring-batch-infrastructure/pom.xml | 2 +- spring-batch-integration/pom.xml | 2 +- spring-batch-samples/pom.xml | 2 +- spring-batch-test/pom.xml | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index c3c6efd3eb..a35392c05a 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ designed to enable the development of robust batch applications vital for the daily operations of enterprise systems. Spring Batch is part of the Spring Portfolio. - 5.2.0-RC1 + 5.2.0-SNAPSHOT pom https://projects.spring.io/spring-batch diff --git a/spring-batch-bom/pom.xml b/spring-batch-bom/pom.xml index c28427da02..7ad96c8a70 100644 --- a/spring-batch-bom/pom.xml +++ b/spring-batch-bom/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0-RC1 + 5.2.0-SNAPSHOT spring-batch-bom pom diff --git a/spring-batch-core/pom.xml b/spring-batch-core/pom.xml index 07212da607..6ef5cfa1a6 100644 --- a/spring-batch-core/pom.xml +++ b/spring-batch-core/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0-RC1 + 5.2.0-SNAPSHOT spring-batch-core jar diff --git a/spring-batch-docs/pom.xml b/spring-batch-docs/pom.xml index 11691e23de..c0e835ea10 100644 --- a/spring-batch-docs/pom.xml +++ b/spring-batch-docs/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0-RC1 + 5.2.0-SNAPSHOT spring-batch-docs Spring Batch Docs diff --git a/spring-batch-infrastructure/pom.xml b/spring-batch-infrastructure/pom.xml index cf4b1c7231..f980ee0bd1 100644 --- a/spring-batch-infrastructure/pom.xml +++ b/spring-batch-infrastructure/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0-RC1 + 5.2.0-SNAPSHOT spring-batch-infrastructure jar diff --git a/spring-batch-integration/pom.xml b/spring-batch-integration/pom.xml index 8897577026..bfc85952bd 100644 --- a/spring-batch-integration/pom.xml +++ b/spring-batch-integration/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0-RC1 + 5.2.0-SNAPSHOT spring-batch-integration Spring Batch Integration diff --git a/spring-batch-samples/pom.xml b/spring-batch-samples/pom.xml index c64de58a34..9d433e897b 100644 --- a/spring-batch-samples/pom.xml +++ b/spring-batch-samples/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0-RC1 + 5.2.0-SNAPSHOT spring-batch-samples jar diff --git a/spring-batch-test/pom.xml b/spring-batch-test/pom.xml index a843c3cf50..15bbf21f7b 100644 --- a/spring-batch-test/pom.xml +++ b/spring-batch-test/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0-RC1 + 5.2.0-SNAPSHOT spring-batch-test Spring Batch Test From fc1f3fcfc791196273b1249157c4e860b1df9025 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Mon, 28 Oct 2024 14:36:40 +0100 Subject: [PATCH 014/266] Update latest news in README.md --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 857ade356a..ceaece65f9 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,7 @@ -# Latest news: [Spring Batch 5.2.0-M2 is available now!](https://spring.io/blog/2024/10/11/spring-batch-5-2-0-m2-is-available-now) +# Latest news + +* October 25, 2024: [Spring Batch 5.2.0-RC1 is out!](https://spring.io/blog/2024/10/25/spring-batch-5-2-0-rc1-is-out) +* October 11, 2024: [Spring Batch 5.2.0-M2 is available now!](https://spring.io/blog/2024/10/11/spring-batch-5-2-0-m2-is-available-now) From fd45d32239c65892c7e2e1fb2e67414bcb5eadb4 Mon Sep 17 00:00:00 2001 From: Ian Date: Sun, 22 Sep 2024 15:26:09 +0900 Subject: [PATCH 015/266] Update skipLimit default value to 10 Resolves #4661 --- .../step/builder/FaultTolerantStepBuilder.java | 5 +++-- .../factory/FaultTolerantStepFactoryBean.java | 7 ++++--- .../builder/FaultTolerantStepBuilderTests.java | 15 ++++++++++++++- .../configuring-skip.adoc | 4 ++++ 4 files changed, 25 insertions(+), 6 deletions(-) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/FaultTolerantStepBuilder.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/FaultTolerantStepBuilder.java index 635c54550a..d1c91ce237 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/FaultTolerantStepBuilder.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/FaultTolerantStepBuilder.java @@ -90,6 +90,7 @@ * @author Chris Schaefer * @author Michael Minella * @author Mahmoud Ben Hassine + * @author Ian Choi * @since 2.2 */ public class FaultTolerantStepBuilder extends SimpleStepBuilder { @@ -122,7 +123,7 @@ public class FaultTolerantStepBuilder extends SimpleStepBuilder { private final Set> skipListeners = new LinkedHashSet<>(); - private int skipLimit = 0; + private int skipLimit = 10; private SkipPolicy skipPolicy; @@ -306,7 +307,7 @@ public FaultTolerantStepBuilder retryContextCache(RetryContextCache retryC /** * Sets the maximum number of failed items to skip before the step fails. Ignored if * an explicit {@link #skipPolicy(SkipPolicy)} is provided. - * @param skipLimit the skip limit to set + * @param skipLimit the skip limit to set. Default is 10. * @return this for fluent chaining */ public FaultTolerantStepBuilder skipLimit(int skipLimit) { diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/factory/FaultTolerantStepFactoryBean.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/factory/FaultTolerantStepFactoryBean.java index 48aed1eae5..cec322ccd4 100755 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/factory/FaultTolerantStepFactoryBean.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/factory/FaultTolerantStepFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2023 the original author or authors. + * Copyright 2006-2024 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. @@ -47,6 +47,7 @@ * @author Dave Syer * @author Robert Kasanicky * @author Morten Andersen-Gott + * @author Ian Choi * */ public class FaultTolerantStepFactoryBean extends SimpleStepFactoryBean { @@ -61,7 +62,7 @@ public class FaultTolerantStepFactoryBean extends SimpleStepFactoryBean stepBuilder = new FaultTolerantStepBuilder<>( + new StepBuilder("step", new DummyJobRepository())); + + Field field = stepBuilder.getClass().getDeclaredField("skipLimit"); + field.setAccessible(true); + int skipLimit = (int) field.get(stepBuilder); + + assertEquals(10, skipLimit); + } + } diff --git a/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/configuring-skip.adoc b/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/configuring-skip.adoc index 16a08cb719..5c5136c825 100644 --- a/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/configuring-skip.adoc +++ b/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/configuring-skip.adoc @@ -31,6 +31,8 @@ public Step step1(JobRepository jobRepository, PlatformTransactionManager transa .build(); } ---- ++ +Note: The `skipLimit` can be explicitly set using the `skipLimit()` method. If not specified, the default skip limit is set to 10. XML:: + @@ -91,6 +93,8 @@ public Step step1(JobRepository jobRepository, PlatformTransactionManager transa .build(); } ---- ++ +Note: The `skipLimit` can be explicitly set using the `skipLimit()` method. If not specified, the default skip limit is set to 10. XML:: + From 6c330d6bd13dab495cd26173061a3ea76cfba962 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Wed, 20 Nov 2024 06:29:57 +0100 Subject: [PATCH 016/266] Refine contribution #4668 Before this commit, an assertion was enforcing that when a skip limit is provided, then at least one skippable exception is defined. Since the default value of skip limit was changed to 10 in fd45d322, that assertion is now replaced with a log message at debug level. Related to #4661 --- .../batch/core/step/builder/FaultTolerantStepBuilder.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/FaultTolerantStepBuilder.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/FaultTolerantStepBuilder.java index d1c91ce237..e4c24fb3b0 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/FaultTolerantStepBuilder.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/FaultTolerantStepBuilder.java @@ -555,8 +555,11 @@ protected SkipPolicy createSkipPolicy() { map.put(ForceRollbackForWriteSkipException.class, true); LimitCheckingItemSkipPolicy limitCheckingItemSkipPolicy = new LimitCheckingItemSkipPolicy(skipLimit, map); if (skipPolicy == null) { - Assert.state(!(skippableExceptionClasses.isEmpty() && skipLimit > 0), - "If a skip limit is provided then skippable exceptions must also be specified"); + if (skippableExceptionClasses.isEmpty() && skipLimit > 0) { + logger.debug(String.format( + "A skip limit of %s is set but no skippable exceptions are defined. Consider defining skippable exceptions.", + skipLimit)); + } skipPolicy = limitCheckingItemSkipPolicy; } else if (limitCheckingItemSkipPolicy != null) { From 50a41a3cb024e2b24a496dcf32e3d53ffb3921ba Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Thu, 21 Nov 2024 02:18:34 +0100 Subject: [PATCH 017/266] Optimize sequence handling in MongoDB job repository --- .../dao/MongoSequenceIncrementer.java | 51 ++++--------------- .../batch/core/schema-drop-mongodb.js | 4 +- .../batch/core/schema-mongodb.js | 20 +++++--- .../MongoDBJobRepositoryIntegrationTests.java | 14 ++--- 4 files changed, 33 insertions(+), 56 deletions(-) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoSequenceIncrementer.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoSequenceIncrementer.java index 683d2ad69e..db78dc343a 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoSequenceIncrementer.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoSequenceIncrementer.java @@ -15,20 +15,20 @@ */ package org.springframework.batch.core.repository.dao; +import com.mongodb.client.model.FindOneAndUpdateOptions; +import com.mongodb.client.model.ReturnDocument; +import org.bson.Document; + import org.springframework.dao.DataAccessException; import org.springframework.data.mongodb.core.MongoOperations; -import org.springframework.data.mongodb.core.query.Query; -import org.springframework.data.mongodb.core.query.Update; import org.springframework.jdbc.support.incrementer.DataFieldMaxValueIncrementer; -import static org.springframework.data.mongodb.core.query.Criteria.where; -import static org.springframework.data.mongodb.core.query.Query.query; - // Based on https://www.mongodb.com/blog/post/generating-globally-unique-identifiers-for-use-with-mongodb // Section: Use a single counter document to generate unique identifiers one at a time /** * @author Mahmoud Ben Hassine + * @author Christoph Strobl * @since 5.2.0 */ public class MongoSequenceIncrementer implements DataFieldMaxValueIncrementer { @@ -44,13 +44,11 @@ public MongoSequenceIncrementer(MongoOperations mongoTemplate, String sequenceNa @Override public long nextLongValue() throws DataAccessException { - // TODO optimize - MongoSequence sequence = mongoTemplate.findOne(new Query(), MongoSequence.class, sequenceName); - Query query = query(where("_id").is(sequence.getId())); - Update update = new Update().inc("count", 1); - // The following does not return the modified document - mongoTemplate.findAndModify(query, update, MongoSequence.class, sequenceName); - return mongoTemplate.findOne(new Query(), MongoSequence.class, sequenceName).getCount(); + return mongoTemplate.execute("BATCH_SEQUENCES", + collection -> collection + .findOneAndUpdate(new Document("_id", sequenceName), new Document("$inc", new Document("count", 1)), + new FindOneAndUpdateOptions().returnDocument(ReturnDocument.AFTER)) + .getLong("count")); } @Override @@ -63,33 +61,4 @@ public String nextStringValue() throws DataAccessException { throw new UnsupportedOperationException(); } - public static final class MongoSequence { - - private String id; - - private long count; - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public long getCount() { - return count; - } - - public void setCount(long count) { - this.count = count; - } - - @Override - public String toString() { - return "MongoSequence{" + "id='" + id + '\'' + ", count=" + count + '}'; - } - - } - } diff --git a/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-drop-mongodb.js b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-drop-mongodb.js index 6a4d05c67f..0213a39df0 100644 --- a/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-drop-mongodb.js +++ b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-drop-mongodb.js @@ -2,6 +2,4 @@ db.getCollection("BATCH_JOB_INSTANCE").drop(); db.getCollection("BATCH_JOB_EXECUTION").drop(); db.getCollection("BATCH_STEP_EXECUTION").drop(); -db.getCollection("BATCH_JOB_INSTANCE_SEQ").drop(); -db.getCollection("BATCH_JOB_EXECUTION_SEQ").drop(); -db.getCollection("BATCH_STEP_EXECUTION_SEQ").drop(); +db.getCollection("BATCH_SEQUENCES").drop(); diff --git a/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-mongodb.js b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-mongodb.js index d8a3d25715..e3a971ad8a 100644 --- a/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-mongodb.js +++ b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-mongodb.js @@ -2,9 +2,17 @@ db.createCollection("BATCH_JOB_INSTANCE"); db.createCollection("BATCH_JOB_EXECUTION"); db.createCollection("BATCH_STEP_EXECUTION"); -db.createCollection("BATCH_JOB_INSTANCE_SEQ"); -db.createCollection("BATCH_JOB_EXECUTION_SEQ"); -db.createCollection("BATCH_STEP_EXECUTION_SEQ"); -db.getCollection("BATCH_JOB_INSTANCE_SEQ").insertOne({count : 0}); -db.getCollection("BATCH_JOB_EXECUTION_SEQ").insertOne({count : 0}); -db.getCollection("BATCH_STEP_EXECUTION_SEQ").insertOne({count : 0}); + +// SEQUENCES +db.createCollection("BATCH_SEQUENCES"); +db.getCollection("BATCH_SEQUENCES").insertOne({_id: "BATCH_JOB_INSTANCE_SEQ", count: Long(0)}); +db.getCollection("BATCH_SEQUENCES").insertOne({_id: "BATCH_JOB_EXECUTION_SEQ", count: Long(0)}); +db.getCollection("BATCH_SEQUENCES").insertOne({_id: "BATCH_STEP_EXECUTION_SEQ", count: Long(0)}); + +// INDICES +db.getCollection("BATCH_JOB_INSTANCE").createIndex("job_name_idx", {"jobName": 1}, {}); +db.getCollection("BATCH_JOB_INSTANCE").createIndex("job_name_key_idx", {"jobName": 1, "jobKey": 1}, {}); +db.getCollection("BATCH_JOB_INSTANCE").createIndex("job_instance_idx", {"jobInstanceId": -1}, {}); +db.getCollection("BATCH_JOB_EXECUTION").createIndex("job_instance_idx", {"jobInstanceId": 1}, {}); +db.getCollection("BATCH_JOB_EXECUTION").createIndex("job_instance_idx", {"jobInstanceId": 1, "status": 1}, {}); +db.getCollection("BATCH_STEP_EXECUTION").createIndex("step_execution_idx", {"stepExecutionId": 1}, {}); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoDBJobRepositoryIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoDBJobRepositoryIntegrationTests.java index 3499b51939..6be4001369 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoDBJobRepositoryIntegrationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoDBJobRepositoryIntegrationTests.java @@ -16,6 +16,7 @@ package org.springframework.batch.core.repository.support; import java.time.LocalDateTime; +import java.util.Map; import com.mongodb.client.MongoClient; import com.mongodb.client.MongoClients; @@ -75,12 +76,13 @@ public void setUp() { mongoTemplate.createCollection("BATCH_JOB_INSTANCE"); mongoTemplate.createCollection("BATCH_JOB_EXECUTION"); mongoTemplate.createCollection("BATCH_STEP_EXECUTION"); - mongoTemplate.createCollection("BATCH_JOB_INSTANCE_SEQ"); - mongoTemplate.createCollection("BATCH_JOB_EXECUTION_SEQ"); - mongoTemplate.createCollection("BATCH_STEP_EXECUTION_SEQ"); - mongoTemplate.getCollection("BATCH_JOB_INSTANCE_SEQ").insertOne(new Document("count", 0)); - mongoTemplate.getCollection("BATCH_JOB_EXECUTION_SEQ").insertOne(new Document("count", 0)); - mongoTemplate.getCollection("BATCH_STEP_EXECUTION_SEQ").insertOne(new Document("count", 0)); + mongoTemplate.createCollection("BATCH_SEQUENCES"); + mongoTemplate.getCollection("BATCH_SEQUENCES") + .insertOne(new Document(Map.of("_id", "BATCH_JOB_INSTANCE_SEQ", "count", 0L))); + mongoTemplate.getCollection("BATCH_SEQUENCES") + .insertOne(new Document(Map.of("_id", "BATCH_JOB_EXECUTION_SEQ", "count", 0L))); + mongoTemplate.getCollection("BATCH_SEQUENCES") + .insertOne(new Document(Map.of("_id", "BATCH_STEP_EXECUTION_SEQ", "count", 0L))); } @Test From 69310604b34075025f70bdc46e513675bb69d065 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Thu, 21 Nov 2024 02:20:50 +0100 Subject: [PATCH 018/266] Update MongoDB sample --- .../mongodb/DeletionJobConfiguration.java | 18 +++++----- .../mongodb/InsertionJobConfiguration.java | 16 ++++----- .../samples/mongodb/MongoDBConfiguration.java | 34 +++++++++++++++++-- .../samples/mongodb/MongoDBSampleApp.java | 15 +++++++- 4 files changed, 61 insertions(+), 22 deletions(-) diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/mongodb/DeletionJobConfiguration.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/mongodb/DeletionJobConfiguration.java index 42dcd42d9f..ca50af5af3 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/mongodb/DeletionJobConfiguration.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/mongodb/DeletionJobConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2022 the original author or authors. + * Copyright 2020-2024 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. @@ -20,14 +20,13 @@ import org.springframework.batch.core.Job; import org.springframework.batch.core.Step; -import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; import org.springframework.batch.core.job.builder.JobBuilder; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.step.builder.StepBuilder; -import org.springframework.batch.item.data.MongoItemReader; import org.springframework.batch.item.data.MongoItemWriter; -import org.springframework.batch.item.data.builder.MongoItemReaderBuilder; +import org.springframework.batch.item.data.MongoPagingItemReader; import org.springframework.batch.item.data.builder.MongoItemWriterBuilder; +import org.springframework.batch.item.data.builder.MongoPagingItemReaderBuilder; import org.springframework.context.annotation.Bean; import org.springframework.data.domain.Sort; import org.springframework.data.mongodb.core.MongoTemplate; @@ -38,18 +37,17 @@ /** * This job will remove document "foo3" from collection "person_out" using - * {@link MongoItemWriter#setDelete(boolean)}. + * {@link MongoItemWriter#setMode(MongoItemWriter.Mode)}}. * * @author Mahmoud Ben Hassine */ -@EnableBatchProcessing public class DeletionJobConfiguration { @Bean - public MongoItemReader mongoPersonReader(MongoTemplate mongoTemplate) { + public MongoPagingItemReader mongoPersonReader(MongoTemplate mongoTemplate) { Map sortOptions = new HashMap<>(); sortOptions.put("name", Sort.Direction.DESC); - return new MongoItemReaderBuilder().name("personItemReader") + return new MongoPagingItemReaderBuilder().name("personItemReader") .collection("person_out") .targetType(Person.class) .template(mongoTemplate) @@ -61,14 +59,14 @@ public MongoItemReader mongoPersonReader(MongoTemplate mongoTemplate) { @Bean public MongoItemWriter mongoPersonRemover(MongoTemplate mongoTemplate) { return new MongoItemWriterBuilder().template(mongoTemplate) - .delete(true) + .mode(MongoItemWriter.Mode.REMOVE) .collection("person_out") .build(); } @Bean public Step deletionStep(JobRepository jobRepository, PlatformTransactionManager transactionManager, - MongoItemReader mongoPersonReader, MongoItemWriter mongoPersonRemover) { + MongoPagingItemReader mongoPersonReader, MongoItemWriter mongoPersonRemover) { return new StepBuilder("step", jobRepository).chunk(2, transactionManager) .reader(mongoPersonReader) .writer(mongoPersonRemover) diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/mongodb/InsertionJobConfiguration.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/mongodb/InsertionJobConfiguration.java index 1e1488d50b..8bbf2b0932 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/mongodb/InsertionJobConfiguration.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/mongodb/InsertionJobConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2022 the original author or authors. + * Copyright 2020-2024 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. @@ -20,14 +20,13 @@ import org.springframework.batch.core.Job; import org.springframework.batch.core.Step; -import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; import org.springframework.batch.core.job.builder.JobBuilder; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.step.builder.StepBuilder; -import org.springframework.batch.item.data.MongoItemReader; +import org.springframework.batch.item.data.MongoPagingItemReader; import org.springframework.batch.item.data.MongoItemWriter; -import org.springframework.batch.item.data.builder.MongoItemReaderBuilder; import org.springframework.batch.item.data.builder.MongoItemWriterBuilder; +import org.springframework.batch.item.data.builder.MongoPagingItemReaderBuilder; import org.springframework.context.annotation.Bean; import org.springframework.data.domain.Sort; import org.springframework.data.mongodb.core.MongoTemplate; @@ -35,18 +34,17 @@ /** * This job will copy documents from collection "person_in" into collection "person_out" - * using {@link MongoItemReader} and {@link MongoItemWriter}. + * using {@link MongoPagingItemReader} and {@link MongoItemWriter}. * * @author Mahmoud Ben Hassine */ -@EnableBatchProcessing public class InsertionJobConfiguration { @Bean - public MongoItemReader mongoItemReader(MongoTemplate mongoTemplate) { + public MongoPagingItemReader mongoItemReader(MongoTemplate mongoTemplate) { Map sortOptions = new HashMap<>(); sortOptions.put("name", Sort.Direction.DESC); - return new MongoItemReaderBuilder().name("personItemReader") + return new MongoPagingItemReaderBuilder().name("personItemReader") .collection("person_in") .targetType(Person.class) .template(mongoTemplate) @@ -62,7 +60,7 @@ public MongoItemWriter mongoItemWriter(MongoTemplate mongoTemplate) { @Bean public Step step(JobRepository jobRepository, PlatformTransactionManager transactionManager, - MongoItemReader mongoItemReader, MongoItemWriter mongoItemWriter) { + MongoPagingItemReader mongoItemReader, MongoItemWriter mongoItemWriter) { return new StepBuilder("step", jobRepository).chunk(2, transactionManager) .reader(mongoItemReader) .writer(mongoItemWriter) diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/mongodb/MongoDBConfiguration.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/mongodb/MongoDBConfiguration.java index 4695a21fd7..45b2994f3a 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/mongodb/MongoDBConfiguration.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/mongodb/MongoDBConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2022 the original author or authors. + * Copyright 2020-2024 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. @@ -18,6 +18,11 @@ import com.mongodb.client.MongoClient; import com.mongodb.client.MongoClients; +import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; +import org.springframework.batch.core.explore.JobExplorer; +import org.springframework.batch.core.explore.support.MongoJobExplorerFactoryBean; +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.core.repository.support.MongoJobRepositoryFactoryBean; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -26,9 +31,11 @@ import org.springframework.data.mongodb.MongoTransactionManager; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.SimpleMongoClientDatabaseFactory; +import org.springframework.data.mongodb.core.convert.MappingMongoConverter; @Configuration @PropertySource("classpath:/org/springframework/batch/samples/mongodb/mongodb-sample.properties") +@EnableBatchProcessing public class MongoDBConfiguration { @Value("${mongodb.host}") @@ -48,7 +55,10 @@ public MongoClient mongoClient() { @Bean public MongoTemplate mongoTemplate(MongoClient mongoClient) { - return new MongoTemplate(mongoClient, "test"); + MongoTemplate mongoTemplate = new MongoTemplate(mongoClient, "test"); + MappingMongoConverter converter = (MappingMongoConverter) mongoTemplate.getConverter(); + converter.setMapKeyDotReplacement("."); + return mongoTemplate; } @Bean @@ -61,4 +71,24 @@ public MongoTransactionManager transactionManager(MongoDatabaseFactory mongoData return new MongoTransactionManager(mongoDatabaseFactory); } + @Bean + public JobRepository jobRepository(MongoTemplate mongoTemplate, MongoTransactionManager transactionManager) + throws Exception { + MongoJobRepositoryFactoryBean jobRepositoryFactoryBean = new MongoJobRepositoryFactoryBean(); + jobRepositoryFactoryBean.setMongoOperations(mongoTemplate); + jobRepositoryFactoryBean.setTransactionManager(transactionManager); + jobRepositoryFactoryBean.afterPropertiesSet(); + return jobRepositoryFactoryBean.getObject(); + } + + @Bean + public JobExplorer jobExplorer(MongoTemplate mongoTemplate, MongoTransactionManager transactionManager) + throws Exception { + MongoJobExplorerFactoryBean jobExplorerFactoryBean = new MongoJobExplorerFactoryBean(); + jobExplorerFactoryBean.setMongoOperations(mongoTemplate); + jobExplorerFactoryBean.setTransactionManager(transactionManager); + jobExplorerFactoryBean.afterPropertiesSet(); + return jobExplorerFactoryBean.getObject(); + } + } diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/mongodb/MongoDBSampleApp.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/mongodb/MongoDBSampleApp.java index ae7b53bbb2..7fc8e52f5d 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/mongodb/MongoDBSampleApp.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/mongodb/MongoDBSampleApp.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2023 the original author or authors. + * Copyright 2020-2024 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. @@ -17,6 +17,7 @@ import java.util.Arrays; import java.util.List; +import java.util.Map; import com.mongodb.client.MongoCollection; import org.bson.Document; @@ -45,6 +46,18 @@ public static void main(String[] args) throws Exception { ApplicationContext context = new AnnotationConfigApplicationContext(configurationClasses); MongoTemplate mongoTemplate = context.getBean(MongoTemplate.class); + // create meta-data collections and sequences + mongoTemplate.createCollection("BATCH_JOB_INSTANCE"); + mongoTemplate.createCollection("BATCH_JOB_EXECUTION"); + mongoTemplate.createCollection("BATCH_STEP_EXECUTION"); + mongoTemplate.createCollection("BATCH_SEQUENCES"); + mongoTemplate.getCollection("BATCH_SEQUENCES") + .insertOne(new Document(Map.of("_id", "BATCH_JOB_INSTANCE_SEQ", "count", 0L))); + mongoTemplate.getCollection("BATCH_SEQUENCES") + .insertOne(new Document(Map.of("_id", "BATCH_JOB_EXECUTION_SEQ", "count", 0L))); + mongoTemplate.getCollection("BATCH_SEQUENCES") + .insertOne(new Document(Map.of("_id", "BATCH_STEP_EXECUTION_SEQ", "count", 0L))); + // clear collections and insert some documents in "person_in" MongoCollection personsIn = mongoTemplate.getCollection("person_in"); MongoCollection personsOut = mongoTemplate.getCollection("person_out"); From 0d425690ff6ab92b9aa33fecc442fb44a5e4fa72 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Thu, 21 Nov 2024 03:10:45 +0100 Subject: [PATCH 019/266] Update what is new section --- spring-batch-docs/modules/ROOT/pages/whatsnew.adoc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spring-batch-docs/modules/ROOT/pages/whatsnew.adoc b/spring-batch-docs/modules/ROOT/pages/whatsnew.adoc index d472af579f..5c94f12831 100644 --- a/spring-batch-docs/modules/ROOT/pages/whatsnew.adoc +++ b/spring-batch-docs/modules/ROOT/pages/whatsnew.adoc @@ -24,11 +24,11 @@ In this release, the Spring dependencies are upgraded to the following versions: * Spring Framework 6.2.0 * Spring Integration 6.4.0 * Spring Data 3.4.0 -* Spring Retry 2.0.9 -* Spring LDAP 3.2.7 +* Spring Retry 2.0.10 +* Spring LDAP 3.2.8 * Spring AMQP 3.2.0 * Spring Kafka 3.3.0 -* Micrometer 1.14.0 +* Micrometer 1.14.1 [[mongodb-job-repository-support]] == MongoDB job repository support From 1105d22916e00b6975a7dd6b69c41836648931c8 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Thu, 21 Nov 2024 03:10:55 +0100 Subject: [PATCH 020/266] Update javadocs --- .../core/explore/support/MongoJobExplorerFactoryBean.java | 8 ++++++++ .../repository/support/MongoJobRepositoryFactoryBean.java | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/explore/support/MongoJobExplorerFactoryBean.java b/spring-batch-core/src/main/java/org/springframework/batch/core/explore/support/MongoJobExplorerFactoryBean.java index 8b24b7febb..c9e38e76f8 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/explore/support/MongoJobExplorerFactoryBean.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/explore/support/MongoJobExplorerFactoryBean.java @@ -25,9 +25,17 @@ import org.springframework.batch.core.repository.dao.MongoStepExecutionDao; import org.springframework.beans.factory.InitializingBean; import org.springframework.data.mongodb.core.MongoOperations; +import org.springframework.data.mongodb.core.convert.MappingMongoConverter; import org.springframework.util.Assert; /** + * This factory bean creates a job explorer backed by MongoDB. It requires a mongo + * template and a mongo transaction manager. The mongo template must be configured + * with a {@link MappingMongoConverter} having a {@code MapKeyDotReplacement} set to a non + * null value. See {@code MongoDBJobRepositoryIntegrationTests} for an example. This is + * required to support execution context keys containing dots (like "step.type" or + * "batch.version") + * * @author Mahmoud Ben Hassine * @since 5.2.0 */ diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/MongoJobRepositoryFactoryBean.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/MongoJobRepositoryFactoryBean.java index 51030cd957..721272dde4 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/MongoJobRepositoryFactoryBean.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/MongoJobRepositoryFactoryBean.java @@ -25,9 +25,17 @@ import org.springframework.batch.core.repository.dao.MongoStepExecutionDao; import org.springframework.beans.factory.InitializingBean; import org.springframework.data.mongodb.core.MongoOperations; +import org.springframework.data.mongodb.core.convert.MappingMongoConverter; import org.springframework.util.Assert; /** + * This factory bean creates a job repository backed by MongoDB. It requires a mongo + * template and a mongo transaction manager. The mongo template must be configured + * with a {@link MappingMongoConverter} having a {@code MapKeyDotReplacement} set to a non + * null value. See {@code MongoDBJobRepositoryIntegrationTests} for an example. This is + * required to support execution context keys containing dots (like "step.type" or + * "batch.version") + * * @author Mahmoud Ben Hassine * @since 5.2.0 */ From 6af3d7feaf76cbee3544c6cdc174378ded6f376d Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Thu, 21 Nov 2024 03:24:20 +0100 Subject: [PATCH 021/266] Prepare release 5.2.0 --- pom.xml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/pom.xml b/pom.xml index a35392c05a..9bfe440888 100644 --- a/pom.xml +++ b/pom.xml @@ -61,19 +61,19 @@ 17 - 6.2.0-RC2 + 6.2.0 2.0.10 - 6.4.0-RC1 - 1.14.0-RC1 + 6.4.0 + 1.14.1 - 3.4.0-RC1 - 3.4.0-RC1 - 3.4.0-RC1 - 4.4.0-RC1 - 3.3.0-RC1 - 3.2.0-RC1 - 3.2.7 + 3.4.0 + 3.4.0 + 3.4.0 + 4.4.0 + 3.3.0 + 3.2.0 + 3.2.8 2.18.0 1.12.0 @@ -92,7 +92,7 @@ 3.0.2 - 1.4.0-RC1 + 1.4.0 1.4.20 4.13.2 From ade546bf0eff6fc6e8c39f4e7a774f4e865b0d2c Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Thu, 21 Nov 2024 09:32:29 +0100 Subject: [PATCH 022/266] Release version 5.2.0 --- .github/workflows/maven-central-release.yml | 5 +---- pom.xml | 2 +- spring-batch-bom/pom.xml | 2 +- spring-batch-core/pom.xml | 2 +- spring-batch-docs/pom.xml | 2 +- spring-batch-infrastructure/pom.xml | 2 +- spring-batch-integration/pom.xml | 2 +- spring-batch-samples/pom.xml | 2 +- spring-batch-test/pom.xml | 2 +- 9 files changed, 9 insertions(+), 12 deletions(-) diff --git a/.github/workflows/maven-central-release.yml b/.github/workflows/maven-central-release.yml index bce60ac9ea..ff04db2c99 100644 --- a/.github/workflows/maven-central-release.yml +++ b/.github/workflows/maven-central-release.yml @@ -66,7 +66,7 @@ jobs: wget --user="$ARTIFACTORY_USERNAME" --password="$ARTIFACTORY_PASSWORD" $ARTIFACTORY_URL/spring-batch-integration/$RELEASE_VERSION/spring-batch-integration-$RELEASE_VERSION-sources.jar - name: Sign artifacts and release them to Maven Central - uses: jvalkeal/nexus-sync@v0 + uses: spring-io/nexus-sync-action@main id: nexus with: url: ${{ secrets.OSSRH_URL }} @@ -78,6 +78,3 @@ jobs: close: true release: true generate-checksums: true - pgp-sign: true - pgp-sign-passphrase: ${{ secrets.GPG_PASSPHRASE }} - pgp-sign-private-key: ${{ secrets.GPG_PRIVATE_KEY }} diff --git a/pom.xml b/pom.xml index 9bfe440888..9e6f9ec5be 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ designed to enable the development of robust batch applications vital for the daily operations of enterprise systems. Spring Batch is part of the Spring Portfolio. - 5.2.0-SNAPSHOT + 5.2.0 pom https://projects.spring.io/spring-batch diff --git a/spring-batch-bom/pom.xml b/spring-batch-bom/pom.xml index 7ad96c8a70..3d62534774 100644 --- a/spring-batch-bom/pom.xml +++ b/spring-batch-bom/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0-SNAPSHOT + 5.2.0 spring-batch-bom pom diff --git a/spring-batch-core/pom.xml b/spring-batch-core/pom.xml index 6ef5cfa1a6..c48672fbd1 100644 --- a/spring-batch-core/pom.xml +++ b/spring-batch-core/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0-SNAPSHOT + 5.2.0 spring-batch-core jar diff --git a/spring-batch-docs/pom.xml b/spring-batch-docs/pom.xml index c0e835ea10..a3bc3d1d0a 100644 --- a/spring-batch-docs/pom.xml +++ b/spring-batch-docs/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0-SNAPSHOT + 5.2.0 spring-batch-docs Spring Batch Docs diff --git a/spring-batch-infrastructure/pom.xml b/spring-batch-infrastructure/pom.xml index f980ee0bd1..c308dc2da8 100644 --- a/spring-batch-infrastructure/pom.xml +++ b/spring-batch-infrastructure/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0-SNAPSHOT + 5.2.0 spring-batch-infrastructure jar diff --git a/spring-batch-integration/pom.xml b/spring-batch-integration/pom.xml index bfc85952bd..43a25e9d34 100644 --- a/spring-batch-integration/pom.xml +++ b/spring-batch-integration/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0-SNAPSHOT + 5.2.0 spring-batch-integration Spring Batch Integration diff --git a/spring-batch-samples/pom.xml b/spring-batch-samples/pom.xml index 9d433e897b..4631c458f9 100644 --- a/spring-batch-samples/pom.xml +++ b/spring-batch-samples/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0-SNAPSHOT + 5.2.0 spring-batch-samples jar diff --git a/spring-batch-test/pom.xml b/spring-batch-test/pom.xml index 15bbf21f7b..100026ee21 100644 --- a/spring-batch-test/pom.xml +++ b/spring-batch-test/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0-SNAPSHOT + 5.2.0 spring-batch-test Spring Batch Test From 2a223b0f3b1a725e3513e2bab4893ad350a013ba Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Thu, 21 Nov 2024 09:32:57 +0100 Subject: [PATCH 023/266] Next development version --- pom.xml | 2 +- spring-batch-bom/pom.xml | 2 +- spring-batch-core/pom.xml | 2 +- spring-batch-docs/pom.xml | 2 +- spring-batch-infrastructure/pom.xml | 2 +- spring-batch-integration/pom.xml | 2 +- spring-batch-samples/pom.xml | 2 +- spring-batch-test/pom.xml | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 9e6f9ec5be..0ab49a219c 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ designed to enable the development of robust batch applications vital for the daily operations of enterprise systems. Spring Batch is part of the Spring Portfolio. - 5.2.0 + 5.2.1-SNAPSHOT pom https://projects.spring.io/spring-batch diff --git a/spring-batch-bom/pom.xml b/spring-batch-bom/pom.xml index 3d62534774..913818fe6e 100644 --- a/spring-batch-bom/pom.xml +++ b/spring-batch-bom/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0 + 5.2.1-SNAPSHOT spring-batch-bom pom diff --git a/spring-batch-core/pom.xml b/spring-batch-core/pom.xml index c48672fbd1..33d0eb9890 100644 --- a/spring-batch-core/pom.xml +++ b/spring-batch-core/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0 + 5.2.1-SNAPSHOT spring-batch-core jar diff --git a/spring-batch-docs/pom.xml b/spring-batch-docs/pom.xml index a3bc3d1d0a..5f6d82fd17 100644 --- a/spring-batch-docs/pom.xml +++ b/spring-batch-docs/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0 + 5.2.1-SNAPSHOT spring-batch-docs Spring Batch Docs diff --git a/spring-batch-infrastructure/pom.xml b/spring-batch-infrastructure/pom.xml index c308dc2da8..e37bec53b2 100644 --- a/spring-batch-infrastructure/pom.xml +++ b/spring-batch-infrastructure/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0 + 5.2.1-SNAPSHOT spring-batch-infrastructure jar diff --git a/spring-batch-integration/pom.xml b/spring-batch-integration/pom.xml index 43a25e9d34..dbbc1e7c87 100644 --- a/spring-batch-integration/pom.xml +++ b/spring-batch-integration/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0 + 5.2.1-SNAPSHOT spring-batch-integration Spring Batch Integration diff --git a/spring-batch-samples/pom.xml b/spring-batch-samples/pom.xml index 4631c458f9..7bb4812ccc 100644 --- a/spring-batch-samples/pom.xml +++ b/spring-batch-samples/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0 + 5.2.1-SNAPSHOT spring-batch-samples jar diff --git a/spring-batch-test/pom.xml b/spring-batch-test/pom.xml index 100026ee21..8ac987b12f 100644 --- a/spring-batch-test/pom.xml +++ b/spring-batch-test/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0 + 5.2.1-SNAPSHOT spring-batch-test Spring Batch Test From e8f0aada1f71fc6343fa7ee84b73a428de95783b Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Sat, 23 Nov 2024 14:29:22 +0100 Subject: [PATCH 024/266] Update latest news in README.md --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ceaece65f9..3c0d3fdc22 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@ # Latest news +* November 20, 2024: [Spring Batch 5.2.0 goes GA!](https://spring.io/blog/2024/11/20/spring-batch-5-2-0-goes-ga) * October 25, 2024: [Spring Batch 5.2.0-RC1 is out!](https://spring.io/blog/2024/10/25/spring-batch-5-2-0-rc1-is-out) * October 11, 2024: [Spring Batch 5.2.0-M2 is available now!](https://spring.io/blog/2024/10/11/spring-batch-5-2-0-m2-is-available-now) +* September 18, 2024: [Spring Batch 5.2.0-M1 is out!](https://spring.io/blog/2024/09/18/spring-batch-5-2-0-m1-is-out) @@ -227,4 +229,4 @@ Please see our [code of conduct](https://github.com/spring-projects/.github/blob # License -Spring Batch is Open Source software released under the [Apache 2.0 license](https://www.apache.org/licenses/LICENSE-2.0.html). \ No newline at end of file +Spring Batch is Open Source software released under the [Apache 2.0 license](https://www.apache.org/licenses/LICENSE-2.0.html). From 18fe8c72d63e71d8f556521aed7bdba6ac91d6ec Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Tue, 26 Nov 2024 11:46:04 +0100 Subject: [PATCH 025/266] Add link to latest blog post to the latest news section in README.md Signed-off-by: Mahmoud Ben Hassine --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 3c0d3fdc22..7110ad6524 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ # Latest news +* November 24, 2024: [Bootiful Spring Boot 3.4: Spring Batch](https://spring.io/blog/2024/11/24/bootiful-34-batch) * November 20, 2024: [Spring Batch 5.2.0 goes GA!](https://spring.io/blog/2024/11/20/spring-batch-5-2-0-goes-ga) * October 25, 2024: [Spring Batch 5.2.0-RC1 is out!](https://spring.io/blog/2024/10/25/spring-batch-5-2-0-rc1-is-out) * October 11, 2024: [Spring Batch 5.2.0-M2 is available now!](https://spring.io/blog/2024/10/11/spring-batch-5-2-0-m2-is-available-now) From 8f34e0c1043b7ff7f5e5e263525bf73bde607160 Mon Sep 17 00:00:00 2001 From: Uli <98144814+uli-f@users.noreply.github.com> Date: Mon, 25 Nov 2024 15:23:08 +1100 Subject: [PATCH 026/266] fixed class name and grammatical error in docs whatsnew --- spring-batch-docs/modules/ROOT/pages/whatsnew.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spring-batch-docs/modules/ROOT/pages/whatsnew.adoc b/spring-batch-docs/modules/ROOT/pages/whatsnew.adoc index 5c94f12831..22635de973 100644 --- a/spring-batch-docs/modules/ROOT/pages/whatsnew.adoc +++ b/spring-batch-docs/modules/ROOT/pages/whatsnew.adoc @@ -39,7 +39,7 @@ necessary collections in MongoDB in order to save and retrieve batch meta-data. This implementation requires MongoDB version 4 or later and is based on Spring Data MongoDB. In order to use this job repository, all you need to do is define a `MongoTemplate` and a -`MongoTransactionManager` which are required by the newly added `MongoDBJobRepositoryFactoryBean`: +`MongoTransactionManager` which are required by the newly added `MongoJobRepositoryFactoryBean`: ``` @Bean @@ -130,7 +130,7 @@ The https://en.wikipedia.org/wiki/Staged_event-driven_architecture[staged event- powerful architecture style to process data in stages connected by queues. This style is directly applicable to data pipelines and easily implemented in Spring Batch thanks to the ability to design jobs as a sequence of steps. -The only missing piece here is how to read and write data to intermediate queues. This release introduces an item reader +The only missing piece here is how to read data from and write data to intermediate queues. This release introduces an item reader and item writer to read data from and write it to a `BlockingQueue`. With these two new classes, one can design a first step that prepares data in a queue and a second step that consumes data from the same queue. This way, both steps can run concurrently to process data efficiently in a non-blocking, event-driven fashion. From bf6c72093b4101a47a92162a0b9ed2f3ec6836fa Mon Sep 17 00:00:00 2001 From: Tran Ngoc Nhan Date: Tue, 29 Oct 2024 23:44:14 +0700 Subject: [PATCH 027/266] Update reference link --- .../modules/ROOT/pages/spring-batch-integration.adoc | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/spring-batch-docs/modules/ROOT/pages/spring-batch-integration.adoc b/spring-batch-docs/modules/ROOT/pages/spring-batch-integration.adoc index 45a9fe3cd8..e47243c999 100644 --- a/spring-batch-docs/modules/ROOT/pages/spring-batch-integration.adoc +++ b/spring-batch-docs/modules/ROOT/pages/spring-batch-integration.adoc @@ -32,11 +32,8 @@ provide methods to distribute workloads over a number of workers. This section covers the following key concepts: [role="xmlContent"] -* <> +* xref:spring-batch-integration/namespace-support.adoc[Namespace Support] * xref:spring-batch-integration/launching-jobs-through-messages.adoc[Launching Batch Jobs through Messages] * xref:spring-batch-integration/sub-elements.adoc#providing-feedback-with-informational-messages[Providing Feedback with Informational Messages] * xref:spring-batch-integration/sub-elements.adoc#asynchronous-processors[Asynchronous Processors] -* xref:spring-batch-integration/sub-elements.adoc#externalizing-batch-process-execution[Externalizing Batch Process Execution] - -[[namespace-support]] -[role="xmlContent"] +* xref:spring-batch-integration/sub-elements.adoc#externalizing-batch-process-execution[Externalizing Batch Process Execution] \ No newline at end of file From ba2202a540364625fd377010d67d684dc18f997e Mon Sep 17 00:00:00 2001 From: Taeik Lim Date: Sun, 9 Jun 2024 22:41:06 +0900 Subject: [PATCH 028/266] Remove repeating 'the the' in docs Signed-off-by: Taeik Lim --- .../core/configuration/support/DefaultBatchConfiguration.java | 4 ++-- .../core/configuration/xml/StepParserStepFactoryBean.java | 2 +- .../batch/core/configuration/xml/spring-batch-2.0.xsd | 2 +- .../batch/core/configuration/xml/spring-batch-2.1.xsd | 2 +- .../batch/core/configuration/xml/spring-batch-2.2.xsd | 2 +- .../batch/core/configuration/xml/spring-batch-3.0.xsd | 2 +- .../batch/core/configuration/xml/spring-batch.xsd | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/DefaultBatchConfiguration.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/DefaultBatchConfiguration.java index 365af8487c..67df9fd41f 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/DefaultBatchConfiguration.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/DefaultBatchConfiguration.java @@ -461,9 +461,9 @@ protected String getDatabaseType() throws MetaDataAccessException { } /** - * Return the {@link TaskExecutor} to use in the the job launcher. Defaults to + * Return the {@link TaskExecutor} to use in the job launcher. Defaults to * {@link SyncTaskExecutor}. - * @return the {@link TaskExecutor} to use in the the job launcher. + * @return the {@link TaskExecutor} to use in the job launcher. */ protected TaskExecutor getTaskExecutor() { return new SyncTaskExecutor(); diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/StepParserStepFactoryBean.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/StepParserStepFactoryBean.java index cbad5b1cee..7b18458ee7 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/StepParserStepFactoryBean.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/StepParserStepFactoryBean.java @@ -893,7 +893,7 @@ public void setKeyGenerator(KeyGenerator keyGenerator) { /** * * Public setter for the capacity of the cache in the retry policy. If there are more - * items than the specified capacity, the the step fails without being skipped or + * items than the specified capacity, the step fails without being skipped or * recovered, and an exception is thrown. This guards against inadvertent infinite * loops generated by item identity problems.
*
diff --git a/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch-2.0.xsd b/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch-2.0.xsd index 9f8241f3d1..07f613bed1 100644 --- a/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch-2.0.xsd +++ b/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch-2.0.xsd @@ -182,7 +182,7 @@ diff --git a/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch-2.1.xsd b/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch-2.1.xsd index 559c74a748..7f0b739f15 100644 --- a/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch-2.1.xsd +++ b/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch-2.1.xsd @@ -230,7 +230,7 @@ ref" is not required, and only needs to be specified explicitly diff --git a/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch-2.2.xsd b/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch-2.2.xsd index df341d1b29..8871bfbb51 100644 --- a/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch-2.2.xsd +++ b/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch-2.2.xsd @@ -230,7 +230,7 @@ ref" is not required, and only needs to be specified explicitly diff --git a/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch-3.0.xsd b/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch-3.0.xsd index 3857e27962..2946e125cb 100644 --- a/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch-3.0.xsd +++ b/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch-3.0.xsd @@ -245,7 +245,7 @@ diff --git a/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch.xsd b/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch.xsd index 5cf435ab68..1c5b20f37c 100644 --- a/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch.xsd +++ b/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch.xsd @@ -246,7 +246,7 @@ From 82d9c15b5ad6387454d1e08476b1e48f7e2976a9 Mon Sep 17 00:00:00 2001 From: Henning Poettker Date: Fri, 29 Nov 2024 10:52:01 +0100 Subject: [PATCH 029/266] Fix job execution retrieval by id for MongoDB Resolves #4722 Signed-off-by: Mahmoud Ben Hassine --- .../dao/MongoExecutionContextDao.java | 15 +- .../repository/dao/MongoJobExecutionDao.java | 12 +- .../repository/dao/MongoStepExecutionDao.java | 3 +- .../MongoDBIntegrationTestConfiguration.java | 98 +++++++++++ .../MongoDBJobExplorerIntegrationTests.java | 114 +++++++++++++ .../MongoDBJobRepositoryIntegrationTests.java | 89 +--------- ...goExecutionContextDaoIntegrationTests.java | 158 ++++++++++++++++++ 7 files changed, 396 insertions(+), 93 deletions(-) create mode 100644 spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoDBIntegrationTestConfiguration.java create mode 100644 spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoDBJobExplorerIntegrationTests.java create mode 100644 spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoExecutionContextDaoIntegrationTests.java diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoExecutionContextDao.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoExecutionContextDao.java index 485882a163..7b3e80294b 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoExecutionContextDao.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoExecutionContextDao.java @@ -16,7 +16,6 @@ package org.springframework.batch.core.repository.dao; import java.util.Collection; -import java.util.Map; import org.springframework.batch.core.JobExecution; import org.springframework.batch.core.StepExecution; @@ -46,8 +45,9 @@ public MongoExecutionContextDao(MongoOperations mongoOperations) { @Override public ExecutionContext getExecutionContext(JobExecution jobExecution) { - org.springframework.batch.core.repository.persistence.JobExecution execution = this.mongoOperations.findById( - jobExecution.getId(), org.springframework.batch.core.repository.persistence.JobExecution.class, + Query query = query(where("jobExecutionId").is(jobExecution.getId())); + org.springframework.batch.core.repository.persistence.JobExecution execution = this.mongoOperations.findOne( + query, org.springframework.batch.core.repository.persistence.JobExecution.class, JOB_EXECUTIONS_COLLECTION_NAME); if (execution == null) { return new ExecutionContext(); @@ -57,8 +57,9 @@ public ExecutionContext getExecutionContext(JobExecution jobExecution) { @Override public ExecutionContext getExecutionContext(StepExecution stepExecution) { - org.springframework.batch.core.repository.persistence.StepExecution execution = this.mongoOperations.findById( - stepExecution.getId(), org.springframework.batch.core.repository.persistence.StepExecution.class, + Query query = query(where("stepExecutionId").is(stepExecution.getId())); + org.springframework.batch.core.repository.persistence.StepExecution execution = this.mongoOperations.findOne( + query, org.springframework.batch.core.repository.persistence.StepExecution.class, STEP_EXECUTIONS_COLLECTION_NAME); if (execution == null) { return new ExecutionContext(); @@ -69,7 +70,7 @@ public ExecutionContext getExecutionContext(StepExecution stepExecution) { @Override public void saveExecutionContext(JobExecution jobExecution) { ExecutionContext executionContext = jobExecution.getExecutionContext(); - Query query = query(where("_id").is(jobExecution.getId())); + Query query = query(where("jobExecutionId").is(jobExecution.getId())); Update update = Update.update("executionContext", new org.springframework.batch.core.repository.persistence.ExecutionContext(executionContext.toMap(), @@ -82,7 +83,7 @@ public void saveExecutionContext(JobExecution jobExecution) { @Override public void saveExecutionContext(StepExecution stepExecution) { ExecutionContext executionContext = stepExecution.getExecutionContext(); - Query query = query(where("_id").is(stepExecution.getId())); + Query query = query(where("stepExecutionId").is(stepExecution.getId())); Update update = Update.update("executionContext", new org.springframework.batch.core.repository.persistence.ExecutionContext(executionContext.toMap(), diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoJobExecutionDao.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoJobExecutionDao.java index c4525970d7..90d3326a9a 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoJobExecutionDao.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoJobExecutionDao.java @@ -126,15 +126,17 @@ public Set findRunningJobExecutions(String jobName) { @Override public JobExecution getJobExecution(Long executionId) { - org.springframework.batch.core.repository.persistence.JobExecution jobExecution = this.mongoOperations.findById( - executionId, org.springframework.batch.core.repository.persistence.JobExecution.class, + Query jobExecutionQuery = query(where("jobExecutionId").is(executionId)); + org.springframework.batch.core.repository.persistence.JobExecution jobExecution = this.mongoOperations.findOne( + jobExecutionQuery, org.springframework.batch.core.repository.persistence.JobExecution.class, JOB_EXECUTIONS_COLLECTION_NAME); if (jobExecution == null) { return null; } - org.springframework.batch.core.repository.persistence.JobInstance jobInstance = this.mongoOperations.findById( - jobExecution.getJobInstanceId(), - org.springframework.batch.core.repository.persistence.JobInstance.class, JOB_INSTANCES_COLLECTION_NAME); + Query jobInstanceQuery = query(where("jobInstanceId").is(jobExecution.getJobInstanceId())); + org.springframework.batch.core.repository.persistence.JobInstance jobInstance = this.mongoOperations.findOne( + jobInstanceQuery, org.springframework.batch.core.repository.persistence.JobInstance.class, + JOB_INSTANCES_COLLECTION_NAME); return this.jobExecutionConverter.toJobExecution(jobExecution, this.jobInstanceConverter.toJobInstance(jobInstance)); } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoStepExecutionDao.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoStepExecutionDao.java index 9b889c1d81..ec9067fe61 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoStepExecutionDao.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoStepExecutionDao.java @@ -89,8 +89,9 @@ public void updateStepExecution(StepExecution stepExecution) { @Override public StepExecution getStepExecution(JobExecution jobExecution, Long stepExecutionId) { + Query query = query(where("stepExecutionId").is(stepExecutionId)); org.springframework.batch.core.repository.persistence.StepExecution stepExecution = this.mongoOperations - .findById(stepExecutionId, org.springframework.batch.core.repository.persistence.StepExecution.class, + .findOne(query, org.springframework.batch.core.repository.persistence.StepExecution.class, STEP_EXECUTIONS_COLLECTION_NAME); return stepExecution != null ? this.stepExecutionConverter.toStepExecution(stepExecution, jobExecution) : null; } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoDBIntegrationTestConfiguration.java b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoDBIntegrationTestConfiguration.java new file mode 100644 index 0000000000..015a90e034 --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoDBIntegrationTestConfiguration.java @@ -0,0 +1,98 @@ +/* + * Copyright 2024 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.core.repository.support; + +import com.mongodb.client.MongoClient; +import com.mongodb.client.MongoClients; +import org.springframework.batch.core.Job; +import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; +import org.springframework.batch.core.explore.JobExplorer; +import org.springframework.batch.core.explore.support.MongoJobExplorerFactoryBean; +import org.springframework.batch.core.job.builder.JobBuilder; +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.core.step.builder.StepBuilder; +import org.springframework.batch.repeat.RepeatStatus; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.mongodb.MongoDatabaseFactory; +import org.springframework.data.mongodb.MongoTransactionManager; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.SimpleMongoClientDatabaseFactory; +import org.springframework.data.mongodb.core.convert.MappingMongoConverter; + +/** + * @author Mahmoud Ben Hassine + */ +@Configuration +@EnableBatchProcessing +class MongoDBIntegrationTestConfiguration { + + @Bean + public JobRepository jobRepository(MongoTemplate mongoTemplate, MongoTransactionManager transactionManager) + throws Exception { + MongoJobRepositoryFactoryBean jobRepositoryFactoryBean = new MongoJobRepositoryFactoryBean(); + jobRepositoryFactoryBean.setMongoOperations(mongoTemplate); + jobRepositoryFactoryBean.setTransactionManager(transactionManager); + jobRepositoryFactoryBean.afterPropertiesSet(); + return jobRepositoryFactoryBean.getObject(); + } + + @Bean + public JobExplorer jobExplorer(MongoTemplate mongoTemplate, MongoTransactionManager transactionManager) + throws Exception { + MongoJobExplorerFactoryBean jobExplorerFactoryBean = new MongoJobExplorerFactoryBean(); + jobExplorerFactoryBean.setMongoOperations(mongoTemplate); + jobExplorerFactoryBean.setTransactionManager(transactionManager); + jobExplorerFactoryBean.afterPropertiesSet(); + return jobExplorerFactoryBean.getObject(); + } + + @Bean + public MongoDatabaseFactory mongoDatabaseFactory(@Value("${mongo.connectionString}") String connectionString) { + MongoClient mongoClient = MongoClients.create(connectionString); + return new SimpleMongoClientDatabaseFactory(mongoClient, "test"); + } + + @Bean + public MongoTemplate mongoTemplate(MongoDatabaseFactory mongoDatabaseFactory) { + MongoTemplate template = new MongoTemplate(mongoDatabaseFactory); + MappingMongoConverter converter = (MappingMongoConverter) template.getConverter(); + converter.setMapKeyDotReplacement("."); + return template; + } + + @Bean + public MongoTransactionManager transactionManager(MongoDatabaseFactory mongoDatabaseFactory) { + MongoTransactionManager mongoTransactionManager = new MongoTransactionManager(); + mongoTransactionManager.setDatabaseFactory(mongoDatabaseFactory); + mongoTransactionManager.afterPropertiesSet(); + return mongoTransactionManager; + } + + @Bean + public Job job(JobRepository jobRepository, MongoTransactionManager transactionManager) { + return new JobBuilder("job", jobRepository) + .start(new StepBuilder("step1", jobRepository) + .tasklet((contribution, chunkContext) -> RepeatStatus.FINISHED, transactionManager) + .build()) + .next(new StepBuilder("step2", jobRepository) + .tasklet((contribution, chunkContext) -> RepeatStatus.FINISHED, transactionManager) + .build()) + .build(); + } + +} diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoDBJobExplorerIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoDBJobExplorerIntegrationTests.java new file mode 100644 index 0000000000..a6ed1c9bb9 --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoDBJobExplorerIntegrationTests.java @@ -0,0 +1,114 @@ +/* + * Copyright 2024 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.core.repository.support; + +import java.time.LocalDateTime; +import java.util.Map; + +import org.bson.Document; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.springframework.batch.core.Job; +import org.springframework.batch.core.JobExecution; +import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.JobParametersBuilder; +import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.explore.JobExplorer; +import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; +import org.testcontainers.containers.MongoDBContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.DockerImageName; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +/** + * @author Henning Pöttker + */ +@Testcontainers(disabledWithoutDocker = true) +@SpringJUnitConfig(MongoDBIntegrationTestConfiguration.class) +public class MongoDBJobExplorerIntegrationTests { + + private static final DockerImageName MONGODB_IMAGE = DockerImageName.parse("mongo:8.0.1"); + + @Container + public static MongoDBContainer mongodb = new MongoDBContainer(MONGODB_IMAGE); + + @DynamicPropertySource + static void setMongoDbConnectionString(DynamicPropertyRegistry registry) { + registry.add("mongo.connectionString", mongodb::getConnectionString); + } + + @BeforeAll + static void setUp(@Autowired MongoTemplate mongoTemplate) { + mongoTemplate.createCollection("BATCH_JOB_INSTANCE"); + mongoTemplate.createCollection("BATCH_JOB_EXECUTION"); + mongoTemplate.createCollection("BATCH_STEP_EXECUTION"); + mongoTemplate.createCollection("BATCH_SEQUENCES"); + mongoTemplate.getCollection("BATCH_SEQUENCES") + .insertOne(new Document(Map.of("_id", "BATCH_JOB_INSTANCE_SEQ", "count", 0L))); + mongoTemplate.getCollection("BATCH_SEQUENCES") + .insertOne(new Document(Map.of("_id", "BATCH_JOB_EXECUTION_SEQ", "count", 0L))); + mongoTemplate.getCollection("BATCH_SEQUENCES") + .insertOne(new Document(Map.of("_id", "BATCH_STEP_EXECUTION_SEQ", "count", 0L))); + } + + @Test + void testGetJobExecutionById(@Autowired JobLauncher jobLauncher, @Autowired Job job, + @Autowired JobExplorer jobExplorer) throws Exception { + // given + JobParameters jobParameters = new JobParametersBuilder().addString("name", "testGetJobExecutionById") + .addLocalDateTime("runtime", LocalDateTime.now()) + .toJobParameters(); + JobExecution jobExecution = jobLauncher.run(job, jobParameters); + + // when + JobExecution actual = jobExplorer.getJobExecution(jobExecution.getId()); + + // then + assertNotNull(actual); + assertNotNull(actual.getJobInstance()); + assertEquals(jobExecution.getJobId(), actual.getJobId()); + assertFalse(actual.getExecutionContext().isEmpty()); + } + + @Test + void testGetStepExecutionByIds(@Autowired JobLauncher jobLauncher, @Autowired Job job, + @Autowired JobExplorer jobExplorer) throws Exception { + // given + JobParameters jobParameters = new JobParametersBuilder().addString("name", "testGetStepExecutionByIds") + .addLocalDateTime("runtime", LocalDateTime.now()) + .toJobParameters(); + JobExecution jobExecution = jobLauncher.run(job, jobParameters); + StepExecution stepExecution = jobExecution.getStepExecutions().stream().findFirst().orElseThrow(); + + // when + StepExecution actual = jobExplorer.getStepExecution(jobExecution.getId(), stepExecution.getId()); + + // then + assertNotNull(actual); + assertEquals(stepExecution.getId(), actual.getId()); + assertFalse(actual.getExecutionContext().isEmpty()); + } + +} diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoDBJobRepositoryIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoDBJobRepositoryIntegrationTests.java index 6be4001369..b45aa7bd19 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoDBJobRepositoryIntegrationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoDBJobRepositoryIntegrationTests.java @@ -18,14 +18,14 @@ import java.time.LocalDateTime; import java.util.Map; -import com.mongodb.client.MongoClient; -import com.mongodb.client.MongoClients; import com.mongodb.client.MongoCollection; import org.bson.Document; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; import org.testcontainers.containers.MongoDBContainer; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; @@ -36,31 +36,15 @@ import org.springframework.batch.core.JobExecution; import org.springframework.batch.core.JobParameters; import org.springframework.batch.core.JobParametersBuilder; -import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; -import org.springframework.batch.core.explore.JobExplorer; -import org.springframework.batch.core.job.builder.JobBuilder; import org.springframework.batch.core.launch.JobLauncher; -import org.springframework.batch.core.repository.JobRepository; -import org.springframework.batch.core.step.builder.StepBuilder; -import org.springframework.batch.core.explore.support.MongoJobExplorerFactoryBean; -import org.springframework.batch.repeat.RepeatStatus; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.data.mongodb.MongoDatabaseFactory; -import org.springframework.data.mongodb.MongoTransactionManager; import org.springframework.data.mongodb.core.MongoTemplate; -import org.springframework.data.mongodb.core.SimpleMongoClientDatabaseFactory; -import org.springframework.data.mongodb.core.convert.MappingMongoConverter; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit.jupiter.SpringExtension; /** * @author Mahmoud Ben Hassine */ @Testcontainers(disabledWithoutDocker = true) -@ExtendWith(SpringExtension.class) -@ContextConfiguration +@SpringJUnitConfig(MongoDBIntegrationTestConfiguration.class) public class MongoDBJobRepositoryIntegrationTests { private static final DockerImageName MONGODB_IMAGE = DockerImageName.parse("mongo:8.0.1"); @@ -68,6 +52,11 @@ public class MongoDBJobRepositoryIntegrationTests { @Container public static MongoDBContainer mongodb = new MongoDBContainer(MONGODB_IMAGE); + @DynamicPropertySource + static void setMongoDbConnectionString(DynamicPropertyRegistry registry) { + registry.add("mongo.connectionString", mongodb::getConnectionString); + } + @Autowired private MongoTemplate mongoTemplate; @@ -119,64 +108,4 @@ private static void dump(MongoCollection collection, String prefix) { } } - @Configuration - @EnableBatchProcessing - static class TestConfiguration { - - @Bean - public JobRepository jobRepository(MongoTemplate mongoTemplate, MongoTransactionManager transactionManager) - throws Exception { - MongoJobRepositoryFactoryBean jobRepositoryFactoryBean = new MongoJobRepositoryFactoryBean(); - jobRepositoryFactoryBean.setMongoOperations(mongoTemplate); - jobRepositoryFactoryBean.setTransactionManager(transactionManager); - jobRepositoryFactoryBean.afterPropertiesSet(); - return jobRepositoryFactoryBean.getObject(); - } - - @Bean - public JobExplorer jobExplorer(MongoTemplate mongoTemplate, MongoTransactionManager transactionManager) - throws Exception { - MongoJobExplorerFactoryBean jobExplorerFactoryBean = new MongoJobExplorerFactoryBean(); - jobExplorerFactoryBean.setMongoOperations(mongoTemplate); - jobExplorerFactoryBean.setTransactionManager(transactionManager); - jobExplorerFactoryBean.afterPropertiesSet(); - return jobExplorerFactoryBean.getObject(); - } - - @Bean - public MongoDatabaseFactory mongoDatabaseFactory() { - MongoClient mongoClient = MongoClients.create(mongodb.getConnectionString()); - return new SimpleMongoClientDatabaseFactory(mongoClient, "test"); - } - - @Bean - public MongoTemplate mongoTemplate(MongoDatabaseFactory mongoDatabaseFactory) { - MongoTemplate template = new MongoTemplate(mongoDatabaseFactory); - MappingMongoConverter converter = (MappingMongoConverter) template.getConverter(); - converter.setMapKeyDotReplacement("."); - return template; - } - - @Bean - public MongoTransactionManager transactionManager(MongoDatabaseFactory mongoDatabaseFactory) { - MongoTransactionManager mongoTransactionManager = new MongoTransactionManager(); - mongoTransactionManager.setDatabaseFactory(mongoDatabaseFactory); - mongoTransactionManager.afterPropertiesSet(); - return mongoTransactionManager; - } - - @Bean - public Job job(JobRepository jobRepository, MongoTransactionManager transactionManager) { - return new JobBuilder("job", jobRepository) - .start(new StepBuilder("step1", jobRepository) - .tasklet((contribution, chunkContext) -> RepeatStatus.FINISHED, transactionManager) - .build()) - .next(new StepBuilder("step2", jobRepository) - .tasklet((contribution, chunkContext) -> RepeatStatus.FINISHED, transactionManager) - .build()) - .build(); - } - - } - } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoExecutionContextDaoIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoExecutionContextDaoIntegrationTests.java new file mode 100644 index 0000000000..7b71ca8505 --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoExecutionContextDaoIntegrationTests.java @@ -0,0 +1,158 @@ +/* + * Copyright 2024 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.core.repository.support; + +import java.time.LocalDateTime; +import java.util.Map; + +import org.bson.Document; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.springframework.batch.core.Job; +import org.springframework.batch.core.JobExecution; +import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.JobParametersBuilder; +import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.repository.dao.ExecutionContextDao; +import org.springframework.batch.core.repository.dao.MongoExecutionContextDao; +import org.springframework.batch.core.repository.support.MongoExecutionContextDaoIntegrationTests.ExecutionContextDaoConfiguration; +import org.springframework.batch.item.ExecutionContext; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.mongodb.core.MongoOperations; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; +import org.testcontainers.containers.MongoDBContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.DockerImageName; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +/** + * @author Henning Pöttker + */ +@Testcontainers(disabledWithoutDocker = true) +@SpringJUnitConfig({ MongoDBIntegrationTestConfiguration.class, ExecutionContextDaoConfiguration.class }) +public class MongoExecutionContextDaoIntegrationTests { + + private static final DockerImageName MONGODB_IMAGE = DockerImageName.parse("mongo:8.0.1"); + + @Container + public static MongoDBContainer mongodb = new MongoDBContainer(MONGODB_IMAGE); + + @DynamicPropertySource + static void setMongoDbConnectionString(DynamicPropertyRegistry registry) { + registry.add("mongo.connectionString", mongodb::getConnectionString); + } + + @BeforeAll + static void setUp(@Autowired MongoTemplate mongoTemplate) { + mongoTemplate.createCollection("BATCH_JOB_INSTANCE"); + mongoTemplate.createCollection("BATCH_JOB_EXECUTION"); + mongoTemplate.createCollection("BATCH_STEP_EXECUTION"); + mongoTemplate.createCollection("BATCH_SEQUENCES"); + mongoTemplate.getCollection("BATCH_SEQUENCES") + .insertOne(new Document(Map.of("_id", "BATCH_JOB_INSTANCE_SEQ", "count", 0L))); + mongoTemplate.getCollection("BATCH_SEQUENCES") + .insertOne(new Document(Map.of("_id", "BATCH_JOB_EXECUTION_SEQ", "count", 0L))); + mongoTemplate.getCollection("BATCH_SEQUENCES") + .insertOne(new Document(Map.of("_id", "BATCH_STEP_EXECUTION_SEQ", "count", 0L))); + } + + @Test + void testGetJobExecutionWithEmptyResult(@Autowired ExecutionContextDao executionContextDao) { + // given + JobExecution jobExecution = new JobExecution(12345678L); + + // when + ExecutionContext actual = executionContextDao.getExecutionContext(jobExecution); + + // then + assertNotNull(actual); + assertTrue(actual.isEmpty()); + } + + @Test + void testSaveJobExecution(@Autowired JobLauncher jobLauncher, @Autowired Job job, + @Autowired ExecutionContextDao executionContextDao) throws Exception { + // given + JobParameters jobParameters = new JobParametersBuilder().addString("name", "testSaveJobExecution") + .addLocalDateTime("runtime", LocalDateTime.now()) + .toJobParameters(); + JobExecution jobExecution = jobLauncher.run(job, jobParameters); + + // when + jobExecution.getExecutionContext().putString("foo", "bar"); + executionContextDao.saveExecutionContext(jobExecution); + ExecutionContext actual = executionContextDao.getExecutionContext(jobExecution); + + // then + assertTrue(actual.containsKey("foo")); + assertEquals("bar", actual.get("foo")); + } + + @Test + void testGetStepExecutionWithEmptyResult(@Autowired ExecutionContextDao executionContextDao) { + // given + JobExecution jobExecution = new JobExecution(12345678L); + StepExecution stepExecution = new StepExecution("step", jobExecution, 23456789L); + + // when + ExecutionContext actual = executionContextDao.getExecutionContext(stepExecution); + + // then + assertNotNull(actual); + assertTrue(actual.isEmpty()); + } + + @Test + void testSaveStepExecution(@Autowired JobLauncher jobLauncher, @Autowired Job job, + @Autowired ExecutionContextDao executionContextDao) throws Exception { + // given + JobParameters jobParameters = new JobParametersBuilder().addString("name", "testSaveJobExecution") + .addLocalDateTime("runtime", LocalDateTime.now()) + .toJobParameters(); + JobExecution jobExecution = jobLauncher.run(job, jobParameters); + StepExecution stepExecution = jobExecution.getStepExecutions().stream().findFirst().orElseThrow(); + + // when + stepExecution.getExecutionContext().putString("foo", "bar"); + executionContextDao.saveExecutionContext(stepExecution); + ExecutionContext actual = executionContextDao.getExecutionContext(stepExecution); + + // then + assertTrue(actual.containsKey("foo")); + assertEquals("bar", actual.get("foo")); + } + + @Configuration + static class ExecutionContextDaoConfiguration { + + @Bean + ExecutionContextDao executionContextDao(MongoOperations mongoOperations) { + return new MongoExecutionContextDao(mongoOperations); + } + + } + +} From 39c593e150d0e86094d14561fd57471358d58499 Mon Sep 17 00:00:00 2001 From: Henning Poettker Date: Sun, 24 Nov 2024 03:24:30 +0100 Subject: [PATCH 030/266] Fix parsing of the `stop` element in step XML Previously, users were forced to set the attribute `exit-code` of the element to `""` as a work-around to prevent failing restarts. Resolves #1287 --- .../configuration/xml/AbstractFlowParser.java | 5 +- .../StopAndRestartFailedJobParserTests.java | 4 +- .../xml/StopAndRestartJobParserTests.java | 4 +- ...startWithCustomExitCodeJobParserTests.java | 69 +++++++++++++++++++ .../xml/StopCustomStatusJobParserTests.java | 4 +- .../xml/StopIncompleteJobParserTests.java | 4 +- .../configuration/xml/StopJobParserTests.java | 4 +- ...AndRestartFailedJobParserTests-context.xml | 2 +- .../StopAndRestartJobParserTests-context.xml | 2 +- ...thCustomExitCodeJobParserTests-context.xml | 16 +++++ ...StopCustomStatusJobParserTests-context.xml | 2 +- .../StopIncompleteJobParserTests-context.xml | 2 +- .../xml/StopJobParserTests-context.xml | 2 +- 13 files changed, 97 insertions(+), 23 deletions(-) create mode 100644 spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StopAndRestartWithCustomExitCodeJobParserTests.java create mode 100644 spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StopAndRestartWithCustomExitCodeJobParserTests-context.xml diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/AbstractFlowParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/AbstractFlowParser.java index 03ec94b23e..876a9ed2ce 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/AbstractFlowParser.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/AbstractFlowParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2023 the original author or authors. + * Copyright 2006-2024 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. @@ -409,8 +409,7 @@ protected static Collection createTransition(FlowExecutionStatus endBuilder.addConstructorArgValue(abandon); - String nextOnEnd = exitCodeExists ? null : next; - endState = getStateTransitionReference(parserContext, endBuilder.getBeanDefinition(), null, nextOnEnd); + endState = getStateTransitionReference(parserContext, endBuilder.getBeanDefinition(), null, next); next = endName; } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StopAndRestartFailedJobParserTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StopAndRestartFailedJobParserTests.java index b8fe6dedfb..525e2ba2a1 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StopAndRestartFailedJobParserTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StopAndRestartFailedJobParserTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2022 the original author or authors. + * Copyright 2006-2024 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. @@ -33,8 +33,6 @@ * */ @SpringJUnitConfig -// FIXME this test fails when upgrading the batch xsd from 2.2 to 3.0: -// https://github.com/spring-projects/spring-batch/issues/1287 class StopAndRestartFailedJobParserTests extends AbstractJobParserTests { @Test diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StopAndRestartJobParserTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StopAndRestartJobParserTests.java index 1702b6f1a3..6f30120f30 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StopAndRestartJobParserTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StopAndRestartJobParserTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2022 the original author or authors. + * Copyright 2006-2024 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. @@ -29,8 +29,6 @@ * */ @SpringJUnitConfig -// FIXME this test fails when upgrading the batch xsd from 2.2 to 3.0: -// https://github.com/spring-projects/spring-batch/issues/1287 class StopAndRestartJobParserTests extends AbstractJobParserTests { @Test diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StopAndRestartWithCustomExitCodeJobParserTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StopAndRestartWithCustomExitCodeJobParserTests.java new file mode 100644 index 0000000000..375e21bb27 --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StopAndRestartWithCustomExitCodeJobParserTests.java @@ -0,0 +1,69 @@ +/* + * Copyright 2024 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.core.configuration.xml; + +import org.junit.jupiter.api.Test; +import org.springframework.batch.core.BatchStatus; +import org.springframework.batch.core.ExitStatus; +import org.springframework.batch.core.JobExecution; +import org.springframework.batch.core.StepExecution; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Henning Pöttker + */ +@SpringJUnitConfig +class StopAndRestartWithCustomExitCodeJobParserTests extends AbstractJobParserTests { + + @Test + void testStopIncomplete() throws Exception { + + // + // First Launch + // + JobExecution jobExecution = createJobExecution(); + job.execute(jobExecution); + assertEquals(1, stepNamesList.size()); + assertEquals("[s1]", stepNamesList.toString()); + + assertEquals(BatchStatus.STOPPED, jobExecution.getStatus()); + assertEquals("CUSTOM", jobExecution.getExitStatus().getExitCode()); + + StepExecution stepExecution1 = getStepExecution(jobExecution, "s1"); + assertEquals(BatchStatus.COMPLETED, stepExecution1.getStatus()); + assertEquals(ExitStatus.COMPLETED.getExitCode(), stepExecution1.getExitStatus().getExitCode()); + + // + // Second Launch + // + stepNamesList.clear(); + jobExecution = createJobExecution(); + job.execute(jobExecution); + assertEquals(1, stepNamesList.size()); // step1 is not executed + assertEquals("[s2]", stepNamesList.toString()); + + assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus()); + assertEquals(ExitStatus.COMPLETED, jobExecution.getExitStatus()); + + StepExecution stepExecution2 = getStepExecution(jobExecution, "s2"); + assertEquals(BatchStatus.COMPLETED, stepExecution2.getStatus()); + assertEquals(ExitStatus.COMPLETED, stepExecution2.getExitStatus()); + + } + +} diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StopCustomStatusJobParserTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StopCustomStatusJobParserTests.java index f0fe245e14..e824cd75d9 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StopCustomStatusJobParserTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StopCustomStatusJobParserTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2022 the original author or authors. + * Copyright 2006-2024 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. @@ -29,8 +29,6 @@ * */ @SpringJUnitConfig -// FIXME this test fails when upgrading the batch xsd from 2.2 to 3.0: -// https://github.com/spring-projects/spring-batch/issues/1287 class StopCustomStatusJobParserTests extends AbstractJobParserTests { @Test diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StopIncompleteJobParserTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StopIncompleteJobParserTests.java index e6ddfee766..b5e4b8183a 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StopIncompleteJobParserTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StopIncompleteJobParserTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2022 the original author or authors. + * Copyright 2006-2024 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. @@ -29,8 +29,6 @@ * */ @SpringJUnitConfig -// FIXME this test fails when upgrading the batch xsd from 2.2 to 3.0: -// https://github.com/spring-projects/spring-batch/issues/1287 class StopIncompleteJobParserTests extends AbstractJobParserTests { @Test diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StopJobParserTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StopJobParserTests.java index b2f0d75c71..f05c0b6cba 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StopJobParserTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StopJobParserTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2022 the original author or authors. + * Copyright 2006-2024 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. @@ -34,8 +34,6 @@ * */ @SpringJUnitConfig -// FIXME this test fails when upgrading the batch xsd from 2.2 to 3.0: -// https://github.com/spring-projects/spring-batch/issues/1287 class StopJobParserTests extends AbstractJobParserTests { @Test diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StopAndRestartFailedJobParserTests-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StopAndRestartFailedJobParserTests-context.xml index 189d63ce13..97df1bef5e 100644 --- a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StopAndRestartFailedJobParserTests-context.xml +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StopAndRestartFailedJobParserTests-context.xml @@ -2,7 +2,7 @@ diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StopAndRestartJobParserTests-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StopAndRestartJobParserTests-context.xml index fe7ed075ed..de0face964 100644 --- a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StopAndRestartJobParserTests-context.xml +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StopAndRestartJobParserTests-context.xml @@ -1,7 +1,7 @@ diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StopAndRestartWithCustomExitCodeJobParserTests-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StopAndRestartWithCustomExitCodeJobParserTests-context.xml new file mode 100644 index 0000000000..dba05231c4 --- /dev/null +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StopAndRestartWithCustomExitCodeJobParserTests-context.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StopCustomStatusJobParserTests-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StopCustomStatusJobParserTests-context.xml index fbb7f4c6a0..93b0a1b4ea 100644 --- a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StopCustomStatusJobParserTests-context.xml +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StopCustomStatusJobParserTests-context.xml @@ -1,7 +1,7 @@ diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StopIncompleteJobParserTests-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StopIncompleteJobParserTests-context.xml index ca269dec17..080f44a374 100644 --- a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StopIncompleteJobParserTests-context.xml +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StopIncompleteJobParserTests-context.xml @@ -1,7 +1,7 @@ diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StopJobParserTests-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StopJobParserTests-context.xml index 0f67bf801d..5be5d43f6b 100644 --- a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StopJobParserTests-context.xml +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StopJobParserTests-context.xml @@ -1,7 +1,7 @@ From e86bd652bbce3391c1b4b76a2a14559de211cd4d Mon Sep 17 00:00:00 2001 From: xeounxzxu Date: Sat, 26 Oct 2024 11:12:52 +0900 Subject: [PATCH 031/266] Add test coverage of `isNonDefaultExitStatus` in ExitStatusTests Issue #4690 Signed-off-by: Mahmoud Ben Hassine --- .../batch/core/ExitStatusTests.java | 40 +++++++++++++++++-- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/ExitStatusTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/ExitStatusTests.java index 3a450aa993..907ea62ff8 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/ExitStatusTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/ExitStatusTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2023 the original author or authors. + * Copyright 2006-2024 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. @@ -15,14 +15,21 @@ */ package org.springframework.batch.core; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import org.springframework.util.SerializationUtils; + import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotSame; import static org.junit.jupiter.api.Assertions.assertTrue; -import org.junit.jupiter.api.Test; -import org.springframework.util.SerializationUtils; - /** * @author Dave Syer * @author Mahmoud Ben Hassine @@ -186,4 +193,29 @@ void testSerializable() { assertEquals(status.getExitCode(), clone.getExitCode()); } + @ParameterizedTest + @MethodSource("provideKnownExitStatuses") + public void testIsNonDefaultExitStatusShouldReturnTrue(ExitStatus status) { + boolean result = ExitStatus.isNonDefaultExitStatus(status); + assertTrue(result); + } + + @ParameterizedTest + @MethodSource("provideCustomExitStatuses") + public void testIsNonDefaultExitStatusShouldReturnFalse(ExitStatus status) { + boolean result = ExitStatus.isNonDefaultExitStatus(status); + assertFalse(result); + } + + private static Stream provideKnownExitStatuses() { + return Stream.of(Arguments.of((ExitStatus) null), Arguments.of(new ExitStatus(null)), + Arguments.of(ExitStatus.COMPLETED), Arguments.of(ExitStatus.EXECUTING), Arguments.of(ExitStatus.FAILED), + Arguments.of(ExitStatus.NOOP), Arguments.of(ExitStatus.STOPPED), Arguments.of(ExitStatus.UNKNOWN)); + } + + private static Stream provideCustomExitStatuses() { + return Stream.of(Arguments.of(new ExitStatus("CUSTOM")), Arguments.of(new ExitStatus("SUCCESS")), + Arguments.of(new ExitStatus("DONE"))); + } + } From 8f1b24a462327d1edebe6c38f78c8ebce7602f15 Mon Sep 17 00:00:00 2001 From: Henning Poettker Date: Sat, 23 Nov 2024 23:42:46 +0100 Subject: [PATCH 032/266] Update implementations of `PagingQueryProvider` Replaces the implementation of `DerbyPagingQueryProvider` with that corresponding to DB2 and adds an integration test that failed with the previous implementation. Deprecates `SqlWindowingPagingQueryProvider` for removal, which was effectively only used by `DerbyPagingQueryProvider`. Resolves #4733 --- .../support/Db2PagingQueryProvider.java | 9 +- .../support/DerbyPagingQueryProvider.java | 71 +++----------- .../support/SqlServerPagingQueryProvider.java | 7 +- .../SqlWindowingPagingQueryProvider.java | 4 +- .../support/SybasePagingQueryProvider.java | 7 +- ...byPagingQueryProviderIntegrationTests.java | 98 +++++++++++++++++++ .../DerbyPagingQueryProviderTests.java | 83 ++-------------- .../SqlWindowingPagingQueryProviderTests.java | 3 +- .../support/query-provider-fixture.sql | 20 ++++ 9 files changed, 151 insertions(+), 151 deletions(-) create mode 100644 spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/DerbyPagingQueryProviderIntegrationTests.java create mode 100644 spring-batch-infrastructure/src/test/resources/org/springframework/batch/item/database/support/query-provider-fixture.sql diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/Db2PagingQueryProvider.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/Db2PagingQueryProvider.java index f29f868190..660eb430b9 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/Db2PagingQueryProvider.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/Db2PagingQueryProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2021 the original author or authors. + * Copyright 2006-2024 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. @@ -27,7 +27,7 @@ * @author Mahmoud Ben Hassine * @since 2.0 */ -public class Db2PagingQueryProvider extends SqlWindowingPagingQueryProvider { +public class Db2PagingQueryProvider extends AbstractSqlPagingQueryProvider { @Override public String generateFirstPageQuery(int pageSize) { @@ -44,11 +44,6 @@ public String generateRemainingPagesQuery(int pageSize) { } } - @Override - protected Object getSubQueryAlias() { - return "AS TMP_SUB "; - } - private String buildLimitClause(int pageSize) { return new StringBuilder().append("FETCH FIRST ").append(pageSize).append(" ROWS ONLY").toString(); } diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/DerbyPagingQueryProvider.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/DerbyPagingQueryProvider.java index b2f4ab422c..015454f90e 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/DerbyPagingQueryProvider.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/DerbyPagingQueryProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2023 the original author or authors. + * Copyright 2006-2024 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. @@ -16,76 +16,37 @@ package org.springframework.batch.item.database.support; -import java.sql.DatabaseMetaData; -import javax.sql.DataSource; - import org.springframework.batch.item.database.PagingQueryProvider; -import org.springframework.dao.InvalidDataAccessResourceUsageException; -import org.springframework.jdbc.support.JdbcUtils; +import org.springframework.util.StringUtils; /** - * Derby implementation of a {@link PagingQueryProvider} using standard SQL:2003 windowing - * functions. These features are supported starting with Apache Derby version 10.4.1.3. - *

- * As the OVER() function does not support the ORDER BY clause a sub query is instead used - * to order the results before the ROW_NUM restriction is applied + * Derby implementation of a {@link PagingQueryProvider} using database specific features. * * @author Thomas Risberg * @author David Thexton * @author Michael Minella + * @author Henning Pöttker * @since 2.0 */ -public class DerbyPagingQueryProvider extends SqlWindowingPagingQueryProvider { - - private static final String MINIMAL_DERBY_VERSION = "10.4.1.3"; +public class DerbyPagingQueryProvider extends AbstractSqlPagingQueryProvider { @Override - public void init(DataSource dataSource) throws Exception { - super.init(dataSource); - String version = JdbcUtils.extractDatabaseMetaData(dataSource, DatabaseMetaData::getDatabaseProductVersion); - if (!isDerbyVersionSupported(version)) { - throw new InvalidDataAccessResourceUsageException( - "Apache Derby version " + version + " is not supported by this class, Only version " - + MINIMAL_DERBY_VERSION + " or later is supported"); - } - } - - // derby version numbering is M.m.f.p [ {alpha|beta} ] see - // https://db.apache.org/derby/papers/versionupgrade.html#Basic+Numbering+Scheme - private boolean isDerbyVersionSupported(String version) { - String[] minimalVersionParts = MINIMAL_DERBY_VERSION.split("\\."); - String[] versionParts = version.split("[\\. ]"); - for (int i = 0; i < minimalVersionParts.length; i++) { - int minimalVersionPart = Integer.parseInt(minimalVersionParts[i]); - int versionPart = Integer.parseInt(versionParts[i]); - if (versionPart < minimalVersionPart) { - return false; - } - else if (versionPart > minimalVersionPart) { - return true; - } - } - return true; + public String generateFirstPageQuery(int pageSize) { + return SqlPagingQueryUtils.generateLimitSqlQuery(this, false, buildLimitClause(pageSize)); } @Override - protected String getOrderedQueryAlias() { - return "TMP_ORDERED"; - } - - @Override - protected String getOverClause() { - return ""; - } - - @Override - protected String getOverSubstituteClauseStart() { - return " FROM (SELECT " + getSelectClause(); + public String generateRemainingPagesQuery(int pageSize) { + if (StringUtils.hasText(getGroupClause())) { + return SqlPagingQueryUtils.generateLimitGroupedSqlQuery(this, buildLimitClause(pageSize)); + } + else { + return SqlPagingQueryUtils.generateLimitSqlQuery(this, true, buildLimitClause(pageSize)); + } } - @Override - protected String getOverSubstituteClauseEnd() { - return " ) AS " + getOrderedQueryAlias(); + private String buildLimitClause(int pageSize) { + return new StringBuilder("FETCH FIRST ").append(pageSize).append(" ROWS ONLY").toString(); } } diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SqlServerPagingQueryProvider.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SqlServerPagingQueryProvider.java index 51b879ed2a..b1c79763b1 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SqlServerPagingQueryProvider.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SqlServerPagingQueryProvider.java @@ -28,7 +28,7 @@ * @author Mahmoud Ben Hassine * @since 2.0 */ -public class SqlServerPagingQueryProvider extends SqlWindowingPagingQueryProvider { +public class SqlServerPagingQueryProvider extends AbstractSqlPagingQueryProvider { @Override public String generateFirstPageQuery(int pageSize) { @@ -45,11 +45,6 @@ public String generateRemainingPagesQuery(int pageSize) { } } - @Override - protected Object getSubQueryAlias() { - return "AS TMP_SUB "; - } - private String buildTopClause(int pageSize) { return new StringBuilder().append("TOP ").append(pageSize).toString(); } diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SqlWindowingPagingQueryProvider.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SqlWindowingPagingQueryProvider.java index 1f75726aaf..00e0d04711 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SqlWindowingPagingQueryProvider.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SqlWindowingPagingQueryProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2023 the original author or authors. + * Copyright 2006-2024 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. @@ -26,7 +26,9 @@ * @author Thomas Risberg * @author Michael Minella * @since 2.0 + * @deprecated since 5.2.1 with no replacement. Scheduled for removal in 6.0. */ +@Deprecated(forRemoval = true) public class SqlWindowingPagingQueryProvider extends AbstractSqlPagingQueryProvider { @Override diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SybasePagingQueryProvider.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SybasePagingQueryProvider.java index af69169139..ade0af5266 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SybasePagingQueryProvider.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SybasePagingQueryProvider.java @@ -28,7 +28,7 @@ * @author Mahmoud Ben Hassine * @since 2.0 */ -public class SybasePagingQueryProvider extends SqlWindowingPagingQueryProvider { +public class SybasePagingQueryProvider extends AbstractSqlPagingQueryProvider { @Override public String generateFirstPageQuery(int pageSize) { @@ -45,11 +45,6 @@ public String generateRemainingPagesQuery(int pageSize) { } } - @Override - protected Object getSubQueryAlias() { - return ""; - } - private String buildTopClause(int pageSize) { return new StringBuilder().append("TOP ").append(pageSize).toString(); } diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/DerbyPagingQueryProviderIntegrationTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/DerbyPagingQueryProviderIntegrationTests.java new file mode 100644 index 0000000000..fb3820c61c --- /dev/null +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/DerbyPagingQueryProviderIntegrationTests.java @@ -0,0 +1,98 @@ +/* + * Copyright 2024 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.item.database.support; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.springframework.batch.item.database.Order; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Henning Pöttker + */ +class DerbyPagingQueryProviderIntegrationTests { + + private static EmbeddedDatabase embeddedDatabase; + + private static JdbcTemplate jdbcTemplate; + + @BeforeAll + static void setUp() { + embeddedDatabase = new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.DERBY) + .addScript("/org/springframework/batch/item/database/support/query-provider-fixture.sql") + .generateUniqueName(true) + .build(); + jdbcTemplate = new JdbcTemplate(embeddedDatabase); + } + + @AfterAll + static void tearDown() { + if (embeddedDatabase != null) { + embeddedDatabase.shutdown(); + } + } + + @Test + void testWithoutGrouping() { + var queryProvider = new DerbyPagingQueryProvider(); + queryProvider.setSelectClause("ID, STRING"); + queryProvider.setFromClause("TEST_TABLE"); + Map sortKeys = new HashMap<>(); + sortKeys.put("ID", Order.ASCENDING); + queryProvider.setSortKeys(sortKeys); + + List firstPage = jdbcTemplate.query(queryProvider.generateFirstPageQuery(2), MAPPER); + assertEquals(List.of(new Item(1, "Spring"), new Item(2, "Batch")), firstPage); + + List secondPage = jdbcTemplate.query(queryProvider.generateRemainingPagesQuery(2), MAPPER, 2); + assertEquals(List.of(new Item(3, "Infrastructure")), secondPage); + } + + @Test + void testWithGrouping() { + var queryProvider = new DerbyPagingQueryProvider(); + queryProvider.setSelectClause("STRING"); + queryProvider.setFromClause("GROUPING_TEST_TABLE"); + queryProvider.setGroupClause("STRING"); + Map sortKeys = new HashMap<>(); + sortKeys.put("STRING", Order.ASCENDING); + queryProvider.setSortKeys(sortKeys); + + List firstPage = jdbcTemplate.queryForList(queryProvider.generateFirstPageQuery(2), String.class); + assertEquals(List.of("Batch", "Infrastructure"), firstPage); + + List secondPage = jdbcTemplate.queryForList(queryProvider.generateRemainingPagesQuery(2), String.class, + "Infrastructure"); + assertEquals(List.of("Spring"), secondPage); + } + + private record Item(Integer id, String string) { + } + + private static final RowMapper MAPPER = (rs, rowNum) -> new Item(rs.getInt("id"), rs.getString("string")); + +} diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/DerbyPagingQueryProviderTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/DerbyPagingQueryProviderTests.java index 5bd891ddfa..c93f979c9b 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/DerbyPagingQueryProviderTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/DerbyPagingQueryProviderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2022 the original author or authors. + * Copyright 2006-2024 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. @@ -15,20 +15,9 @@ */ package org.springframework.batch.item.database.support; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.sql.Connection; -import java.sql.DatabaseMetaData; - -import javax.sql.DataSource; import org.junit.jupiter.api.Test; -import org.springframework.batch.item.database.Order; -import org.springframework.dao.InvalidDataAccessResourceUsageException; /** * @author Thomas Risberg @@ -41,43 +30,10 @@ class DerbyPagingQueryProviderTests extends AbstractSqlPagingQueryProviderTests pagingQueryProvider = new DerbyPagingQueryProvider(); } - @Test - void testInit() throws Exception { - DataSource ds = mock(); - Connection con = mock(); - DatabaseMetaData dmd = mock(); - when(dmd.getDatabaseProductVersion()).thenReturn("10.4.1.3"); - when(con.getMetaData()).thenReturn(dmd); - when(ds.getConnection()).thenReturn(con); - pagingQueryProvider.init(ds); - } - - @Test - void testInitWithRecentVersion() throws Exception { - DataSource ds = mock(); - Connection con = mock(); - DatabaseMetaData dmd = mock(); - when(dmd.getDatabaseProductVersion()).thenReturn("10.10.1.1"); - when(con.getMetaData()).thenReturn(dmd); - when(ds.getConnection()).thenReturn(con); - pagingQueryProvider.init(ds); - } - - @Test - void testInitWithUnsupportedVersion() throws Exception { - DataSource ds = mock(); - Connection con = mock(); - DatabaseMetaData dmd = mock(); - when(dmd.getDatabaseProductVersion()).thenReturn("10.2.9.9"); - when(con.getMetaData()).thenReturn(dmd); - when(ds.getConnection()).thenReturn(con); - assertThrows(InvalidDataAccessResourceUsageException.class, () -> pagingQueryProvider.init(ds)); - } - @Test @Override void testGenerateFirstPageQuery() { - String sql = "SELECT * FROM ( SELECT TMP_ORDERED.*, ROW_NUMBER() OVER () AS ROW_NUMBER FROM (SELECT id, name, age FROM foo WHERE bar = 1 ) AS TMP_ORDERED) AS TMP_SUB WHERE TMP_SUB.ROW_NUMBER <= 100 ORDER BY id ASC"; + String sql = "SELECT id, name, age FROM foo WHERE bar = 1 ORDER BY id ASC FETCH FIRST 100 ROWS ONLY"; String s = pagingQueryProvider.generateFirstPageQuery(pageSize); assertEquals(sql, s); } @@ -85,60 +41,37 @@ void testGenerateFirstPageQuery() { @Test @Override void testGenerateRemainingPagesQuery() { - String sql = "SELECT * FROM ( SELECT TMP_ORDERED.*, ROW_NUMBER() OVER () AS ROW_NUMBER FROM (SELECT id, name, age FROM foo WHERE bar = 1 ) AS TMP_ORDERED) AS TMP_SUB WHERE TMP_SUB.ROW_NUMBER <= 100 AND ((id > ?)) ORDER BY id ASC"; + String sql = "SELECT id, name, age FROM foo WHERE (bar = 1) AND ((id > ?)) ORDER BY id ASC FETCH FIRST 100 ROWS ONLY"; String s = pagingQueryProvider.generateRemainingPagesQuery(pageSize); assertEquals(sql, s); } - /** - * Older versions of Derby don't allow order by in the sub select. This should work - * with 10.6.1 and above. - */ @Test @Override - void testQueryContainsSortKey() { - String s = pagingQueryProvider.generateFirstPageQuery(pageSize).toLowerCase(); - assertTrue(s.contains("id asc"), "Wrong query: " + s); - } - - /** - * Older versions of Derby don't allow order by in the sub select. This should work - * with 10.6.1 and above. - */ - @Test - @Override - void testQueryContainsSortKeyDesc() { - pagingQueryProvider.getSortKeys().put("id", Order.DESCENDING); - String s = pagingQueryProvider.generateFirstPageQuery(pageSize).toLowerCase(); - assertTrue(s.contains("id desc"), "Wrong query: " + s); - } - - @Override - @Test void testGenerateFirstPageQueryWithGroupBy() { pagingQueryProvider.setGroupClause("dep"); - String sql = "SELECT * FROM ( SELECT TMP_ORDERED.*, ROW_NUMBER() OVER () AS ROW_NUMBER FROM (SELECT id, name, age FROM foo WHERE bar = 1 GROUP BY dep ) AS TMP_ORDERED) AS TMP_SUB WHERE TMP_SUB.ROW_NUMBER <= 100 ORDER BY id ASC"; + String sql = "SELECT id, name, age FROM foo WHERE bar = 1 GROUP BY dep ORDER BY id ASC FETCH FIRST 100 ROWS ONLY"; String s = pagingQueryProvider.generateFirstPageQuery(pageSize); assertEquals(sql, s); } - @Override @Test + @Override void testGenerateRemainingPagesQueryWithGroupBy() { pagingQueryProvider.setGroupClause("dep"); - String sql = "SELECT * FROM ( SELECT TMP_ORDERED.*, ROW_NUMBER() OVER () AS ROW_NUMBER FROM (SELECT id, name, age FROM foo WHERE bar = 1 GROUP BY dep ) AS TMP_ORDERED) AS TMP_SUB WHERE TMP_SUB.ROW_NUMBER <= 100 AND ((id > ?)) ORDER BY id ASC"; + String sql = "SELECT * FROM (SELECT id, name, age FROM foo WHERE bar = 1 GROUP BY dep) AS MAIN_QRY WHERE ((id > ?)) ORDER BY id ASC FETCH FIRST 100 ROWS ONLY"; String s = pagingQueryProvider.generateRemainingPagesQuery(pageSize); assertEquals(sql, s); } @Override String getFirstPageSqlWithMultipleSortKeys() { - return "SELECT * FROM ( SELECT TMP_ORDERED.*, ROW_NUMBER() OVER () AS ROW_NUMBER FROM (SELECT id, name, age FROM foo WHERE bar = 1 ) AS TMP_ORDERED) AS TMP_SUB WHERE TMP_SUB.ROW_NUMBER <= 100 ORDER BY name ASC, id DESC"; + return "SELECT id, name, age FROM foo WHERE bar = 1 ORDER BY name ASC, id DESC FETCH FIRST 100 ROWS ONLY"; } @Override String getRemainingSqlWithMultipleSortKeys() { - return "SELECT * FROM ( SELECT TMP_ORDERED.*, ROW_NUMBER() OVER () AS ROW_NUMBER FROM (SELECT id, name, age FROM foo WHERE bar = 1 ) AS TMP_ORDERED) AS TMP_SUB WHERE TMP_SUB.ROW_NUMBER <= 100 AND ((name > ?) OR (name = ? AND id < ?)) ORDER BY name ASC, id DESC"; + return "SELECT id, name, age FROM foo WHERE (bar = 1) AND ((name > ?) OR (name = ? AND id < ?)) ORDER BY name ASC, id DESC FETCH FIRST 100 ROWS ONLY"; } } diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/SqlWindowingPagingQueryProviderTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/SqlWindowingPagingQueryProviderTests.java index bac04788f5..cd58aecfb9 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/SqlWindowingPagingQueryProviderTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/SqlWindowingPagingQueryProviderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2022 the original author or authors. + * Copyright 2006-2024 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. @@ -25,6 +25,7 @@ */ class SqlWindowingPagingQueryProviderTests extends AbstractSqlPagingQueryProviderTests { + @SuppressWarnings("removal") SqlWindowingPagingQueryProviderTests() { pagingQueryProvider = new SqlWindowingPagingQueryProvider(); } diff --git a/spring-batch-infrastructure/src/test/resources/org/springframework/batch/item/database/support/query-provider-fixture.sql b/spring-batch-infrastructure/src/test/resources/org/springframework/batch/item/database/support/query-provider-fixture.sql new file mode 100644 index 0000000000..f320010978 --- /dev/null +++ b/spring-batch-infrastructure/src/test/resources/org/springframework/batch/item/database/support/query-provider-fixture.sql @@ -0,0 +1,20 @@ +CREATE TABLE TEST_TABLE ( + ID INTEGER NOT NULL, + STRING VARCHAR(16) NOT NULL +); + +INSERT INTO TEST_TABLE (ID, STRING) VALUES (1, 'Spring'); +INSERT INTO TEST_TABLE (ID, STRING) VALUES (2, 'Batch'); +INSERT INTO TEST_TABLE (ID, STRING) VALUES (3, 'Infrastructure'); + +CREATE TABLE GROUPING_TEST_TABLE ( + ID INTEGER NOT NULL, + STRING VARCHAR(16) NOT NULL +); + +INSERT INTO GROUPING_TEST_TABLE (ID, STRING) VALUES (1, 'Spring'); +INSERT INTO GROUPING_TEST_TABLE (ID, STRING) VALUES (2, 'Batch'); +INSERT INTO GROUPING_TEST_TABLE (ID, STRING) VALUES (3, 'Batch'); +INSERT INTO GROUPING_TEST_TABLE (ID, STRING) VALUES (4, 'Infrastructure'); +INSERT INTO GROUPING_TEST_TABLE (ID, STRING) VALUES (5, 'Infrastructure'); +INSERT INTO GROUPING_TEST_TABLE (ID, STRING) VALUES (6, 'Infrastructure'); \ No newline at end of file From b831a7d79210defea4f6b6ef5aae0ef502bb875c Mon Sep 17 00:00:00 2001 From: Henning Poettker Date: Sun, 24 Nov 2024 18:19:19 +0100 Subject: [PATCH 033/266] Add integration tests for `PagingQueryProvider` Adds testcontainers based tests for DB2, MySQL, MariaDB, Postgres, Sql Server and Oracle Database, as well as standard tests for HSQL and SQLite. --- spring-batch-infrastructure/pom.xml | 78 +++++++++++++++++ ...ctPagingQueryProviderIntegrationTests.java | 81 ++++++++++++++++++ ...b2PagingQueryProviderIntegrationTests.java | 69 +++++++++++++++ ...byPagingQueryProviderIntegrationTests.java | 84 ++++--------------- ...qlPagingQueryProviderIntegrationTests.java | 50 +++++++++++ ...DBPagingQueryProviderIntegrationTests.java | 65 ++++++++++++++ ...qlPagingQueryProviderIntegrationTests.java | 66 +++++++++++++++ ...lePagingQueryProviderIntegrationTests.java | 75 +++++++++++++++++ ...esPagingQueryProviderIntegrationTests.java | 65 ++++++++++++++ ...erPagingQueryProviderIntegrationTests.java | 66 +++++++++++++++ ...tePagingQueryProviderIntegrationTests.java | 57 +++++++++++++ 11 files changed, 690 insertions(+), 66 deletions(-) create mode 100644 spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/AbstractPagingQueryProviderIntegrationTests.java create mode 100644 spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/Db2PagingQueryProviderIntegrationTests.java create mode 100644 spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/HsqlPagingQueryProviderIntegrationTests.java create mode 100644 spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/MariaDBPagingQueryProviderIntegrationTests.java create mode 100644 spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/MySqlPagingQueryProviderIntegrationTests.java create mode 100644 spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/OraclePagingQueryProviderIntegrationTests.java create mode 100644 spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/PostgresPagingQueryProviderIntegrationTests.java create mode 100644 spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/SqlServerPagingQueryProviderIntegrationTests.java create mode 100644 spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/SqlitePagingQueryProviderIntegrationTests.java diff --git a/spring-batch-infrastructure/pom.xml b/spring-batch-infrastructure/pom.xml index e37bec53b2..1823e8b1ff 100644 --- a/spring-batch-infrastructure/pom.xml +++ b/spring-batch-infrastructure/pom.xml @@ -347,6 +347,84 @@ ${derby.version} test + + org.testcontainers + junit-jupiter + ${testcontainers.version} + test + + + com.mysql + mysql-connector-j + ${mysql-connector-j.version} + test + + + org.testcontainers + mysql + ${testcontainers.version} + test + + + org.testcontainers + oracle-xe + ${testcontainers.version} + test + + + com.oracle.database.jdbc + ojdbc10 + ${oracle.version} + test + + + org.mariadb.jdbc + mariadb-java-client + ${mariadb-java-client.version} + test + + + org.testcontainers + mariadb + ${testcontainers.version} + test + + + org.postgresql + postgresql + ${postgresql.version} + test + + + org.testcontainers + postgresql + ${testcontainers.version} + test + + + com.ibm.db2 + jcc + ${db2.version} + test + + + org.testcontainers + db2 + ${testcontainers.version} + test + + + org.testcontainers + mssqlserver + ${testcontainers.version} + test + + + com.microsoft.sqlserver + mssql-jdbc + ${sqlserver.version} + test + com.thoughtworks.xstream xstream diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/AbstractPagingQueryProviderIntegrationTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/AbstractPagingQueryProviderIntegrationTests.java new file mode 100644 index 0000000000..15f3ced073 --- /dev/null +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/AbstractPagingQueryProviderIntegrationTests.java @@ -0,0 +1,81 @@ +/* + * Copyright 2024 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.item.database.support; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.sql.DataSource; + +import org.junit.jupiter.api.Test; +import org.springframework.batch.item.database.Order; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Henning Pöttker + */ +abstract class AbstractPagingQueryProviderIntegrationTests { + + private final JdbcTemplate jdbcTemplate; + + private final AbstractSqlPagingQueryProvider queryProvider; + + AbstractPagingQueryProviderIntegrationTests(DataSource dataSource, AbstractSqlPagingQueryProvider queryProvider) { + this.jdbcTemplate = new JdbcTemplate(dataSource); + this.queryProvider = queryProvider; + } + + @Test + void testWithoutGrouping() { + queryProvider.setSelectClause("ID, STRING"); + queryProvider.setFromClause("TEST_TABLE"); + Map sortKeys = new HashMap<>(); + sortKeys.put("ID", Order.ASCENDING); + queryProvider.setSortKeys(sortKeys); + + List firstPage = jdbcTemplate.query(queryProvider.generateFirstPageQuery(2), MAPPER); + assertEquals(List.of(new Item(1, "Spring"), new Item(2, "Batch")), firstPage); + + List secondPage = jdbcTemplate.query(queryProvider.generateRemainingPagesQuery(2), MAPPER, 2); + assertEquals(List.of(new Item(3, "Infrastructure")), secondPage); + } + + @Test + void testWithGrouping() { + queryProvider.setSelectClause("STRING"); + queryProvider.setFromClause("GROUPING_TEST_TABLE"); + queryProvider.setGroupClause("STRING"); + Map sortKeys = new HashMap<>(); + sortKeys.put("STRING", Order.ASCENDING); + queryProvider.setSortKeys(sortKeys); + + List firstPage = jdbcTemplate.queryForList(queryProvider.generateFirstPageQuery(2), String.class); + assertEquals(List.of("Batch", "Infrastructure"), firstPage); + + List secondPage = jdbcTemplate.queryForList(queryProvider.generateRemainingPagesQuery(2), String.class, + "Infrastructure"); + assertEquals(List.of("Spring"), secondPage); + } + + private record Item(Integer id, String string) { + } + + private static final RowMapper MAPPER = (rs, rowNum) -> new Item(rs.getInt("id"), rs.getString("string")); + +} diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/Db2PagingQueryProviderIntegrationTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/Db2PagingQueryProviderIntegrationTests.java new file mode 100644 index 0000000000..19d876b9d1 --- /dev/null +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/Db2PagingQueryProviderIntegrationTests.java @@ -0,0 +1,69 @@ +/* + * Copyright 2024 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.item.database.support; + +import javax.sql.DataSource; + +import com.ibm.db2.jcc.DB2SimpleDataSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; +import org.testcontainers.containers.Db2Container; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.DockerImageName; + +import static org.springframework.test.context.jdbc.Sql.ExecutionPhase.BEFORE_TEST_CLASS; + +/** + * @author Henning Pöttker + */ +@Testcontainers(disabledWithoutDocker = true) +@SpringJUnitConfig +@Sql(scripts = "query-provider-fixture.sql", executionPhase = BEFORE_TEST_CLASS) +class Db2PagingQueryProviderIntegrationTests extends AbstractPagingQueryProviderIntegrationTests { + + // TODO find the best way to externalize and manage image versions + private static final DockerImageName DB2_IMAGE = DockerImageName.parse("ibmcom/db2:11.5.5.1"); + + @Container + public static Db2Container db2 = new Db2Container(DB2_IMAGE).acceptLicense(); + + Db2PagingQueryProviderIntegrationTests(@Autowired DataSource dataSource) { + super(dataSource, new Db2PagingQueryProvider()); + } + + @Configuration + static class TestConfiguration { + + @Bean + public DataSource dataSource() throws Exception { + DB2SimpleDataSource dataSource = new DB2SimpleDataSource(); + dataSource.setDatabaseName(db2.getDatabaseName()); + dataSource.setUser(db2.getUsername()); + dataSource.setPassword(db2.getPassword()); + dataSource.setDriverType(4); + dataSource.setServerName(db2.getHost()); + dataSource.setPortNumber(db2.getMappedPort(Db2Container.DB2_PORT)); + dataSource.setSslConnection(false); + return dataSource; + } + + } + +} diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/DerbyPagingQueryProviderIntegrationTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/DerbyPagingQueryProviderIntegrationTests.java index fb3820c61c..9a06de9369 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/DerbyPagingQueryProviderIntegrationTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/DerbyPagingQueryProviderIntegrationTests.java @@ -15,84 +15,36 @@ */ package org.springframework.batch.item.database.support; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import javax.sql.DataSource; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import org.springframework.batch.item.database.Order; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.core.RowMapper; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; - -import static org.junit.jupiter.api.Assertions.assertEquals; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; /** * @author Henning Pöttker */ -class DerbyPagingQueryProviderIntegrationTests { - - private static EmbeddedDatabase embeddedDatabase; - - private static JdbcTemplate jdbcTemplate; - - @BeforeAll - static void setUp() { - embeddedDatabase = new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.DERBY) - .addScript("/org/springframework/batch/item/database/support/query-provider-fixture.sql") - .generateUniqueName(true) - .build(); - jdbcTemplate = new JdbcTemplate(embeddedDatabase); - } - - @AfterAll - static void tearDown() { - if (embeddedDatabase != null) { - embeddedDatabase.shutdown(); - } - } - - @Test - void testWithoutGrouping() { - var queryProvider = new DerbyPagingQueryProvider(); - queryProvider.setSelectClause("ID, STRING"); - queryProvider.setFromClause("TEST_TABLE"); - Map sortKeys = new HashMap<>(); - sortKeys.put("ID", Order.ASCENDING); - queryProvider.setSortKeys(sortKeys); +@SpringJUnitConfig +class DerbyPagingQueryProviderIntegrationTests extends AbstractPagingQueryProviderIntegrationTests { - List firstPage = jdbcTemplate.query(queryProvider.generateFirstPageQuery(2), MAPPER); - assertEquals(List.of(new Item(1, "Spring"), new Item(2, "Batch")), firstPage); - - List secondPage = jdbcTemplate.query(queryProvider.generateRemainingPagesQuery(2), MAPPER, 2); - assertEquals(List.of(new Item(3, "Infrastructure")), secondPage); + DerbyPagingQueryProviderIntegrationTests(@Autowired DataSource dataSource) { + super(dataSource, new DerbyPagingQueryProvider()); } - @Test - void testWithGrouping() { - var queryProvider = new DerbyPagingQueryProvider(); - queryProvider.setSelectClause("STRING"); - queryProvider.setFromClause("GROUPING_TEST_TABLE"); - queryProvider.setGroupClause("STRING"); - Map sortKeys = new HashMap<>(); - sortKeys.put("STRING", Order.ASCENDING); - queryProvider.setSortKeys(sortKeys); - - List firstPage = jdbcTemplate.queryForList(queryProvider.generateFirstPageQuery(2), String.class); - assertEquals(List.of("Batch", "Infrastructure"), firstPage); + @Configuration + static class TestConfiguration { - List secondPage = jdbcTemplate.queryForList(queryProvider.generateRemainingPagesQuery(2), String.class, - "Infrastructure"); - assertEquals(List.of("Spring"), secondPage); - } + @Bean + public DataSource dataSource() throws Exception { + return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.DERBY) + .addScript("/org/springframework/batch/item/database/support/query-provider-fixture.sql") + .generateUniqueName(true) + .build(); + } - private record Item(Integer id, String string) { } - private static final RowMapper MAPPER = (rs, rowNum) -> new Item(rs.getInt("id"), rs.getString("string")); - } diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/HsqlPagingQueryProviderIntegrationTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/HsqlPagingQueryProviderIntegrationTests.java new file mode 100644 index 0000000000..f0ce2f3821 --- /dev/null +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/HsqlPagingQueryProviderIntegrationTests.java @@ -0,0 +1,50 @@ +/* + * Copyright 2024 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.item.database.support; + +import javax.sql.DataSource; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +/** + * @author Henning Pöttker + */ +@SpringJUnitConfig +class HsqlPagingQueryProviderIntegrationTests extends AbstractPagingQueryProviderIntegrationTests { + + HsqlPagingQueryProviderIntegrationTests(@Autowired DataSource dataSource) { + super(dataSource, new HsqlPagingQueryProvider()); + } + + @Configuration + static class TestConfiguration { + + @Bean + public DataSource dataSource() throws Exception { + return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.HSQL) + .addScript("/org/springframework/batch/item/database/support/query-provider-fixture.sql") + .generateUniqueName(true) + .build(); + } + + } + +} diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/MariaDBPagingQueryProviderIntegrationTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/MariaDBPagingQueryProviderIntegrationTests.java new file mode 100644 index 0000000000..e96aeb1242 --- /dev/null +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/MariaDBPagingQueryProviderIntegrationTests.java @@ -0,0 +1,65 @@ +/* + * Copyright 2024 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.item.database.support; + +import javax.sql.DataSource; + +import org.mariadb.jdbc.MariaDbDataSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; +import org.testcontainers.containers.MariaDBContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.DockerImageName; + +import static org.springframework.test.context.jdbc.Sql.ExecutionPhase.BEFORE_TEST_CLASS; + +/** + * @author Henning Pöttker + */ +@Testcontainers(disabledWithoutDocker = true) +@SpringJUnitConfig +@Sql(scripts = "query-provider-fixture.sql", executionPhase = BEFORE_TEST_CLASS) +class MariaDBPagingQueryProviderIntegrationTests extends AbstractPagingQueryProviderIntegrationTests { + + // TODO find the best way to externalize and manage image versions + private static final DockerImageName MARIADB_IMAGE = DockerImageName.parse("mariadb:10.9.3"); + + @Container + public static MariaDBContainer mariaDBContainer = new MariaDBContainer<>(MARIADB_IMAGE); + + MariaDBPagingQueryProviderIntegrationTests(@Autowired DataSource dataSource) { + super(dataSource, new MySqlPagingQueryProvider()); + } + + @Configuration + static class TestConfiguration { + + @Bean + public DataSource dataSource() throws Exception { + MariaDbDataSource datasource = new MariaDbDataSource(); + datasource.setUrl(mariaDBContainer.getJdbcUrl()); + datasource.setUser(mariaDBContainer.getUsername()); + datasource.setPassword(mariaDBContainer.getPassword()); + return datasource; + } + + } + +} diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/MySqlPagingQueryProviderIntegrationTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/MySqlPagingQueryProviderIntegrationTests.java new file mode 100644 index 0000000000..4b1da2044b --- /dev/null +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/MySqlPagingQueryProviderIntegrationTests.java @@ -0,0 +1,66 @@ +/* + * Copyright 2024 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.item.database.support; + +import javax.sql.DataSource; + +import com.mysql.cj.jdbc.MysqlDataSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; +import org.testcontainers.containers.MySQLContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.DockerImageName; + +import static org.springframework.test.context.jdbc.Sql.ExecutionPhase.BEFORE_TEST_CLASS; + +/** + * @author Henning Pöttker + */ +@Testcontainers(disabledWithoutDocker = true) +@SpringJUnitConfig +@Sql(scripts = "query-provider-fixture.sql", executionPhase = BEFORE_TEST_CLASS) +class MySqlPagingQueryProviderIntegrationTests extends AbstractPagingQueryProviderIntegrationTests { + + // TODO find the best way to externalize and manage image versions + private static final DockerImageName MYSQL_IMAGE = DockerImageName.parse("mysql:8.0.31"); + + @Container + public static MySQLContainer mysql = new MySQLContainer<>(MYSQL_IMAGE); + + MySqlPagingQueryProviderIntegrationTests(@Autowired DataSource dataSource) { + super(dataSource, new MySqlPagingQueryProvider()); + } + + @Configuration + static class TestConfiguration { + + @Bean + public DataSource dataSource() throws Exception { + MysqlDataSource datasource = new MysqlDataSource(); + datasource.setURL(mysql.getJdbcUrl()); + datasource.setUser(mysql.getUsername()); + datasource.setPassword(mysql.getPassword()); + datasource.setUseSSL(false); + return datasource; + } + + } + +} diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/OraclePagingQueryProviderIntegrationTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/OraclePagingQueryProviderIntegrationTests.java new file mode 100644 index 0000000000..23d767c384 --- /dev/null +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/OraclePagingQueryProviderIntegrationTests.java @@ -0,0 +1,75 @@ +/* + * Copyright 2024 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.item.database.support; + +import javax.sql.DataSource; + +import oracle.jdbc.pool.OracleDataSource; +import org.junit.jupiter.api.Disabled; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; +import org.testcontainers.containers.OracleContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.DockerImageName; + +import static org.springframework.test.context.jdbc.Sql.ExecutionPhase.BEFORE_TEST_CLASS; + +/** + * Official Docker images for Oracle are not publicly available. Oracle support is tested + * semi-manually for the moment: 1. Build a docker image for oracle/database:11.2.0.2-xe: + * ... + * 2. Run the test `testJobExecution` + * + * @author Henning Pöttker + */ +@Testcontainers(disabledWithoutDocker = true) +@SpringJUnitConfig +@Sql(scripts = "query-provider-fixture.sql", executionPhase = BEFORE_TEST_CLASS) +@Disabled("Official Docker images for Oracle are not publicly available") +class OraclePagingQueryProviderIntegrationTests extends AbstractPagingQueryProviderIntegrationTests { + + // TODO find the best way to externalize and manage image versions + private static final DockerImageName ORACLE_IMAGE = DockerImageName.parse("oracle/database:11.2.0.2-xe"); + + @Container + public static OracleContainer oracle = new OracleContainer(ORACLE_IMAGE); + + OraclePagingQueryProviderIntegrationTests(@Autowired DataSource dataSource) { + super(dataSource, new OraclePagingQueryProvider()); + } + + @Configuration + static class TestConfiguration { + + @Bean + public DataSource dataSource() throws Exception { + OracleDataSource oracleDataSource = new OracleDataSource(); + oracleDataSource.setUser(oracle.getUsername()); + oracleDataSource.setPassword(oracle.getPassword()); + oracleDataSource.setDatabaseName(oracle.getDatabaseName()); + oracleDataSource.setServerName(oracle.getHost()); + oracleDataSource.setPortNumber(oracle.getOraclePort()); + return oracleDataSource; + } + + } + +} diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/PostgresPagingQueryProviderIntegrationTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/PostgresPagingQueryProviderIntegrationTests.java new file mode 100644 index 0000000000..44798f79fa --- /dev/null +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/PostgresPagingQueryProviderIntegrationTests.java @@ -0,0 +1,65 @@ +/* + * Copyright 2024 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.item.database.support; + +import javax.sql.DataSource; + +import org.postgresql.ds.PGSimpleDataSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; +import org.testcontainers.containers.PostgreSQLContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.DockerImageName; + +import static org.springframework.test.context.jdbc.Sql.ExecutionPhase.BEFORE_TEST_CLASS; + +/** + * @author Henning Pöttker + */ +@Testcontainers(disabledWithoutDocker = true) +@SpringJUnitConfig +@Sql(scripts = "query-provider-fixture.sql", executionPhase = BEFORE_TEST_CLASS) +class PostgresPagingQueryProviderIntegrationTests extends AbstractPagingQueryProviderIntegrationTests { + + // TODO find the best way to externalize and manage image versions + private static final DockerImageName POSTGRESQL_IMAGE = DockerImageName.parse("postgres:13.3"); + + @Container + public static PostgreSQLContainer postgres = new PostgreSQLContainer<>(POSTGRESQL_IMAGE); + + PostgresPagingQueryProviderIntegrationTests(@Autowired DataSource dataSource) { + super(dataSource, new PostgresPagingQueryProvider()); + } + + @Configuration + static class TestConfiguration { + + @Bean + public DataSource dataSource() throws Exception { + PGSimpleDataSource datasource = new PGSimpleDataSource(); + datasource.setURL(postgres.getJdbcUrl()); + datasource.setUser(postgres.getUsername()); + datasource.setPassword(postgres.getPassword()); + return datasource; + } + + } + +} diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/SqlServerPagingQueryProviderIntegrationTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/SqlServerPagingQueryProviderIntegrationTests.java new file mode 100644 index 0000000000..21bc1eede6 --- /dev/null +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/SqlServerPagingQueryProviderIntegrationTests.java @@ -0,0 +1,66 @@ +/* + * Copyright 2024 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.item.database.support; + +import javax.sql.DataSource; + +import com.microsoft.sqlserver.jdbc.SQLServerDataSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; +import org.testcontainers.containers.MSSQLServerContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.DockerImageName; + +import static org.springframework.test.context.jdbc.Sql.ExecutionPhase.BEFORE_TEST_CLASS; + +/** + * @author Henning Pöttker + */ +@Testcontainers(disabledWithoutDocker = true) +@SpringJUnitConfig +@Sql(scripts = "query-provider-fixture.sql", executionPhase = BEFORE_TEST_CLASS) +class SqlServerPagingQueryProviderIntegrationTests extends AbstractPagingQueryProviderIntegrationTests { + + // TODO find the best way to externalize and manage image versions + private static final DockerImageName SQLSERVER_IMAGE = DockerImageName + .parse("mcr.microsoft.com/mssql/server:2022-CU14-ubuntu-22.04"); + + @Container + public static MSSQLServerContainer sqlserver = new MSSQLServerContainer<>(SQLSERVER_IMAGE).acceptLicense(); + + SqlServerPagingQueryProviderIntegrationTests(@Autowired DataSource dataSource) { + super(dataSource, new SqlServerPagingQueryProvider()); + } + + @Configuration + static class TestConfiguration { + + @Bean + public DataSource dataSource() throws Exception { + SQLServerDataSource dataSource = new SQLServerDataSource(); + dataSource.setUser(sqlserver.getUsername()); + dataSource.setPassword(sqlserver.getPassword()); + dataSource.setURL(sqlserver.getJdbcUrl()); + return dataSource; + } + + } + +} diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/SqlitePagingQueryProviderIntegrationTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/SqlitePagingQueryProviderIntegrationTests.java new file mode 100644 index 0000000000..db6826c832 --- /dev/null +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/SqlitePagingQueryProviderIntegrationTests.java @@ -0,0 +1,57 @@ +/* + * Copyright 2024 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.item.database.support; + +import java.nio.file.Path; +import javax.sql.DataSource; + +import org.junit.jupiter.api.io.TempDir; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; +import org.sqlite.SQLiteDataSource; + +import static org.springframework.test.context.jdbc.Sql.ExecutionPhase.BEFORE_TEST_CLASS; + +/** + * @author Henning Pöttker + */ +@SpringJUnitConfig +@Sql(scripts = "query-provider-fixture.sql", executionPhase = BEFORE_TEST_CLASS) +class SqlitePagingQueryProviderIntegrationTests extends AbstractPagingQueryProviderIntegrationTests { + + @TempDir + private static Path TEMP_DIR; + + SqlitePagingQueryProviderIntegrationTests(@Autowired DataSource dataSource) { + super(dataSource, new SqlitePagingQueryProvider()); + } + + @Configuration + static class TestConfiguration { + + @Bean + public DataSource dataSource() throws Exception { + SQLiteDataSource dataSource = new SQLiteDataSource(); + dataSource.setUrl("jdbc:sqlite:" + TEMP_DIR.resolve("spring-batch.sqlite")); + return dataSource; + } + + } + +} From 76789494d7be2f6d658763039046c0cea80a1fe1 Mon Sep 17 00:00:00 2001 From: doontagi Date: Sun, 24 Mar 2024 22:55:09 +0900 Subject: [PATCH 034/266] Fix unfinished step in parallel flow Resolves #3939 Signed-off-by: Mahmoud Ben Hassine --- .../job/flow/support/state/SplitState.java | 13 ++++-- .../core/job/builder/FlowJobBuilderTests.java | 41 ++++++++++++++++++- 2 files changed, 49 insertions(+), 5 deletions(-) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/support/state/SplitState.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/support/state/SplitState.java index 1caef3c1a0..14f256adc8 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/support/state/SplitState.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/support/state/SplitState.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2023 the original author or authors. + * Copyright 2006-2024 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,6 +19,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.List; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.FutureTask; @@ -119,7 +120,7 @@ public FlowExecutionStatus handle(final FlowExecutor executor) throws Exception FlowExecutionStatus parentSplitStatus = parentSplit == null ? null : parentSplit.handle(executor); Collection results = new ArrayList<>(); - + List exceptions = new ArrayList<>(); // Could use a CompletionService here? for (Future task : tasks) { try { @@ -129,14 +130,18 @@ public FlowExecutionStatus handle(final FlowExecutor executor) throws Exception // Unwrap the expected exceptions Throwable cause = e.getCause(); if (cause instanceof Exception) { - throw (Exception) cause; + exceptions.add((Exception) cause); } else { - throw e; + exceptions.add(e); } } } + if (!exceptions.isEmpty()) { + throw exceptions.get(0); + } + FlowExecutionStatus flowExecutionStatus = doAggregation(results, executor); if (parentSplitStatus != null) { return Collections.max(Arrays.asList(flowExecutionStatus, parentSplitStatus)); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/job/builder/FlowJobBuilderTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/job/builder/FlowJobBuilderTests.java index dcff2e0eb3..909c04c0d1 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/job/builder/FlowJobBuilderTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/job/builder/FlowJobBuilderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 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. @@ -16,11 +16,14 @@ package org.springframework.batch.core.job.builder; import java.util.Arrays; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; import javax.sql.DataSource; import static org.junit.jupiter.api.Assertions.assertEquals; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.batch.core.BatchStatus; @@ -45,6 +48,8 @@ import org.springframework.batch.core.step.StepSupport; import org.springframework.batch.core.step.builder.StepBuilder; import org.springframework.batch.item.support.ListItemReader; +import org.springframework.batch.repeat.RepeatStatus; +import org.springframework.batch.support.transaction.ResourcelessTransactionManager; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; @@ -419,4 +424,38 @@ public JdbcTransactionManager transactionManager(DataSource dataSource) { } + @Test + public void testBuildSplitWithParallelFlow() throws InterruptedException { + CountDownLatch countDownLatch = new CountDownLatch(1); + Step longExecutingStep = new StepBuilder("longExecutingStep", jobRepository).tasklet((stepContribution, b) -> { + Thread.sleep(500L); + return RepeatStatus.FINISHED; + }, new ResourcelessTransactionManager()).build(); + + Step interruptedStep = new StepBuilder("interruptedStep", jobRepository).tasklet((stepContribution, b) -> { + stepContribution.getStepExecution().setTerminateOnly(); + return RepeatStatus.FINISHED; + }, new ResourcelessTransactionManager()).build(); + + Step nonExecutableStep = new StepBuilder("nonExecutableStep", jobRepository).tasklet((stepContribution, b) -> { + countDownLatch.countDown(); + return RepeatStatus.FINISHED; + }, new ResourcelessTransactionManager()).build(); + + Flow twoStepFlow = new FlowBuilder("twoStepFlow").start(longExecutingStep) + .next(nonExecutableStep) + .build(); + Flow interruptedFlow = new FlowBuilder("interruptedFlow").start(interruptedStep).build(); + + Flow splitFlow = new FlowBuilder("splitFlow").split(new SimpleAsyncTaskExecutor()) + .add(interruptedFlow, twoStepFlow) + .build(); + FlowJobBuilder jobBuilder = new JobBuilder("job", jobRepository).start(splitFlow).build(); + jobBuilder.preventRestart().build().execute(execution); + + boolean isExecutedNonExecutableStep = countDownLatch.await(1, TimeUnit.SECONDS); + assertEquals(BatchStatus.STOPPED, execution.getStatus()); + Assertions.assertFalse(isExecutedNonExecutableStep); + } + } From 2bd3c15587c1dab5a31ffc1f235eede248b61244 Mon Sep 17 00:00:00 2001 From: Henning Poettker Date: Fri, 6 Dec 2024 17:41:00 +0100 Subject: [PATCH 035/266] Add state reset on close of `AbstractPaginatedDataItemReader` Resolves #1086 --- .../data/AbstractPaginatedDataItemReader.java | 10 +++++++++- .../item/data/MongoPagingItemReaderTests.java | 18 +++++++++++++++++- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/AbstractPaginatedDataItemReader.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/AbstractPaginatedDataItemReader.java index c7982e506d..043e54b7ba 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/AbstractPaginatedDataItemReader.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/AbstractPaginatedDataItemReader.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 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. @@ -103,6 +103,14 @@ protected void doOpen() throws Exception { @Override protected void doClose() throws Exception { + this.lock.lock(); + try { + this.page = 0; + this.results = null; + } + finally { + this.lock.unlock(); + } } @Override diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/data/MongoPagingItemReaderTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/data/MongoPagingItemReaderTests.java index 16552fd947..3593ab49cf 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/data/MongoPagingItemReaderTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/data/MongoPagingItemReaderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 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. @@ -18,6 +18,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import org.junit.jupiter.api.BeforeEach; @@ -34,6 +35,7 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; @@ -347,4 +349,18 @@ void testSortThrowsExceptionWhenInvokedWithNull() { .withMessage("Sorts must not be null"); } + @Test + void testClose() throws Exception { + // given + when(template.find(any(), any())).thenReturn(List.of("string")); + reader.read(); + + // when + reader.close(); + + // then + assertEquals(0, reader.page); + assertNull(reader.results); + } + } From 7481cf8a8354ba309b6cbed26897f9617b923e93 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Wed, 18 Dec 2024 10:34:42 +0100 Subject: [PATCH 036/266] Add note about not scoping step beans with job scope Resolves #3900 Signed-off-by: Mahmoud Ben Hassine --- spring-batch-docs/modules/ROOT/pages/step/late-binding.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spring-batch-docs/modules/ROOT/pages/step/late-binding.adoc b/spring-batch-docs/modules/ROOT/pages/step/late-binding.adoc index ceb0d390aa..879464ef21 100644 --- a/spring-batch-docs/modules/ROOT/pages/step/late-binding.adoc +++ b/spring-batch-docs/modules/ROOT/pages/step/late-binding.adoc @@ -201,8 +201,8 @@ The following example shows how to access the `ExecutionContext` in XML: NOTE: Any bean that uses late binding must be declared with `scope="step"`. See xref:step/late-binding.adoc#step-scope[Step Scope] for more information. -A `Step` bean should not be step-scoped. If late binding is needed in a step -definition, the components of that step (tasklet, item reader or writer, and so on) +A `Step` bean should not be step-scoped or job-scoped. If late binding is needed in a step +definition, then the components of that step (tasklet, item reade/writer, completion policy, and so on) are the ones that should be scoped instead. NOTE: If you use Spring 3.0 (or above), the expressions in step-scoped beans are in the From 27adb97bf916a7b0861d9c5f7fb393a2db575d7d Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Wed, 18 Dec 2024 12:03:46 +0100 Subject: [PATCH 037/266] Clarify wildcard usage to select input files with Java configuration Resolves #4707 Signed-off-by: Mahmoud Ben Hassine --- .../ROOT/pages/readers-and-writers/multi-file-input.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spring-batch-docs/modules/ROOT/pages/readers-and-writers/multi-file-input.adoc b/spring-batch-docs/modules/ROOT/pages/readers-and-writers/multi-file-input.adoc index 08307e720d..cf81b7a417 100644 --- a/spring-batch-docs/modules/ROOT/pages/readers-and-writers/multi-file-input.adoc +++ b/spring-batch-docs/modules/ROOT/pages/readers-and-writers/multi-file-input.adoc @@ -24,10 +24,10 @@ The following example shows how to read files with wildcards in Java: [source, java] ---- @Bean -public MultiResourceItemReader multiResourceReader() { +public MultiResourceItemReader multiResourceReader(@Value("classpath:data/input/file-*.txt") Resource[] resources) { return new MultiResourceItemReaderBuilder() .delegate(flatFileItemReader()) - .resources(resources()) + .resources(resources) .build(); } ---- From a64b44efc18dd3621ce45ab4cded9fb0fe5109ce Mon Sep 17 00:00:00 2001 From: Yizheng Wang Date: Tue, 24 Sep 2024 12:44:26 +0800 Subject: [PATCH 038/266] Add unit tests for ExecutionContext#get methods Related to #718 Signed-off-by: Mahmoud Ben Hassine --- .../batch/item/ExecutionContextTests.java | 76 +++++++++++++++++-- 1 file changed, 70 insertions(+), 6 deletions(-) diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/ExecutionContextTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/ExecutionContextTests.java index fe4fd5bb76..96e19dfc43 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/ExecutionContextTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/ExecutionContextTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2023 the original author or authors. + * Copyright 2006-2024 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. @@ -15,18 +15,24 @@ */ package org.springframework.batch.item; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import org.springframework.util.SerializationUtils; + import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import java.io.Serializable; - -import org.junit.jupiter.api.Test; -import org.springframework.util.SerializationUtils; - /** * @author Lucas Ward * @author Mahmoud Ben Hassine @@ -196,4 +202,62 @@ public boolean equals(Object obj) { } + @DisplayName("testGetByType") + @Test + void givenAList_whenGettingAccordingToListType_thenReturnCorrectObject() { + // given - a list + String key = "aListObject"; + List value = List.of("value1", "value2"); + context.put(key, value); + // when - getting according to list type + @SuppressWarnings("unchecked") + List result = (List) context.get(key, List.class); + // then - return the correct list + assertEquals(result, value); + assertEquals(result.get(0), value.get(0)); + assertEquals(result.get(1), value.get(1)); + } + + @DisplayName("testGetNullByDefaultParam") + @Test + void givenANonExistingKey_whenGettingTheNullList_thenReturnNull() { + // given - a non existing key + String key = "aListObjectButNull"; + // when - getting according to the key + @SuppressWarnings("unchecked") + List result = (List) context.get(key, List.class, null); + List result2 = (List) context.get(key, List.class); + // then - return the defined null list + assertNull(result); + assertNull(result2); + } + + @DisplayName("testGetNullByNotNullDefaultParam") + @Test + void givenAnNullList_whenGettingNullWithNonNullDefault_thenReturnDefinedDefaultValue() { + // given - a non existing key + String key = "aListObjectButNull"; + List defaultValue = new ArrayList<>(); + defaultValue.add("value1"); + @SuppressWarnings("unchecked") + // when - getting according to list type and default value + List result = (List) context.get(key, List.class, defaultValue); + // then - return defined default value + assertNotNull(result); + assertEquals(result, defaultValue); + assertEquals(result.get(0), defaultValue.get(0)); + } + + @DisplayName("testGetWithWrongType") + @Test + void givenAList_whenGettingWithWrongType_thenThrowClassCastException() { + // given - another normal list + String key = "anotherListObject"; + List value = List.of("value1", "value2", "value3"); + context.put(key, value); + // when - getting according to map type + // then - throw exception + assertThrows(ClassCastException.class, () -> context.get(key, Map.class)); + } + } From 72ab701ac01a3b21246e21763f3871d7cfb41525 Mon Sep 17 00:00:00 2001 From: Seungrae Date: Tue, 4 Jun 2024 23:43:19 +0900 Subject: [PATCH 039/266] Fix closing tag for listeners in documentation --- spring-batch-docs/modules/ROOT/pages/job/configuring.adoc | 4 ++-- .../chunk-oriented-processing/inheriting-from-parent.adoc | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/spring-batch-docs/modules/ROOT/pages/job/configuring.adoc b/spring-batch-docs/modules/ROOT/pages/job/configuring.adoc index 57f9bbb48c..b6916acafe 100644 --- a/spring-batch-docs/modules/ROOT/pages/job/configuring.adoc +++ b/spring-batch-docs/modules/ROOT/pages/job/configuring.adoc @@ -251,7 +251,7 @@ it with its own list of listeners to produce a - + @@ -259,7 +259,7 @@ it with its own list of listeners to produce a - + ---- diff --git a/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/inheriting-from-parent.adoc b/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/inheriting-from-parent.adoc index 00b59bf54a..fd56acbfb9 100644 --- a/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/inheriting-from-parent.adoc +++ b/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/inheriting-from-parent.adoc @@ -93,7 +93,7 @@ In the following example, the `Step` "concreteStep3", is created with two listen - + @@ -102,7 +102,7 @@ In the following example, the `Step` "concreteStep3", is created with two listen - + ---- From 9921c54ba60d6860c79b478dd4bc86a40c37ddfa Mon Sep 17 00:00:00 2001 From: Seungrae Date: Tue, 4 Jun 2024 23:52:21 +0900 Subject: [PATCH 040/266] Fix incorrect link --- spring-batch-docs/modules/ROOT/pages/job/configuring.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-batch-docs/modules/ROOT/pages/job/configuring.adoc b/spring-batch-docs/modules/ROOT/pages/job/configuring.adoc index b6916acafe..c7aaa78828 100644 --- a/spring-batch-docs/modules/ROOT/pages/job/configuring.adoc +++ b/spring-batch-docs/modules/ROOT/pages/job/configuring.adoc @@ -264,7 +264,7 @@ it with its own list of listeners to produce a ---- [role="xmlContent"] -See the section on <> +See the section on xref:step/chunk-oriented-processing/inheriting-from-parent.adoc[Inheriting from a Parent Step] for more detailed information. [[jobparametersvalidator]] From 45fdc2dc24aeb78a47e02eb15e2cad104dd4378c Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Wed, 18 Dec 2024 20:18:41 +0100 Subject: [PATCH 041/266] Prepare release 5.2.1 Signed-off-by: Mahmoud Ben Hassine --- pom.xml | 58 ++++++++++++++++++++++++++++----------------------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/pom.xml b/pom.xml index 0ab49a219c..8f93848f7b 100644 --- a/pom.xml +++ b/pom.xml @@ -61,73 +61,73 @@ 17 - 6.2.0 - 2.0.10 - 6.4.0 - 1.14.1 + 6.2.1 + 2.0.11 + 6.4.1 + 1.14.2 - 3.4.0 - 3.4.0 - 3.4.0 - 4.4.0 - 3.3.0 - 3.2.0 - 3.2.8 + 3.4.1 + 3.4.1 + 3.4.1 + 4.4.1 + 3.3.1 + 3.2.1 + 3.2.9 - 2.18.0 + 2.18.2 1.12.0 2.11.0 - 6.6.0.Final + 6.6.3.Final 3.0.0 2.1.3 3.1.0 3.1.0 3.1.0 - 4.0.11 - 5.2.0 - 5.11.1 + 4.0.13 + 5.2.1 + 5.11.4 3.0.2 - 1.4.0 + 1.4.1 1.4.20 4.13.2 ${junit-jupiter.version} 3.0 3.26.3 - 5.14.1 + 5.14.2 2.10.0 - 2.17.0 - 2.12.0 + 2.18.0 + 2.13.0 2.0.16 - 2.7.3 + 2.7.4 2.3.232 - 3.46.1.3 + 3.47.1.0 10.16.1.1 2.21.11 - 2.37.0 + 2.38.0 4.0.5 - 2.23.1 - 8.0.1.Final + 2.24.3 + 8.0.2.Final 6.0.1 4.0.2 2.0.1 4.0.2 2.0.3 - 7.0.0 + 7.1.0 1.9.22.1 - 9.0.0 - 3.4.1 + 9.1.0 + 3.5.1 42.7.4 11.5.9.0 19.24.0.0 11.2.3.jre17 1.3.1 - 1.20.1 + 1.20.4 1.5.3 From 659171906378df8d9a2da26b4e6e57f8f54e109b Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Wed, 18 Dec 2024 21:25:24 +0100 Subject: [PATCH 042/266] Update nexus sync action in maven central release process Signed-off-by: Mahmoud Ben Hassine --- .github/workflows/maven-central-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/maven-central-release.yml b/.github/workflows/maven-central-release.yml index ff04db2c99..32c08fd2f2 100644 --- a/.github/workflows/maven-central-release.yml +++ b/.github/workflows/maven-central-release.yml @@ -66,7 +66,7 @@ jobs: wget --user="$ARTIFACTORY_USERNAME" --password="$ARTIFACTORY_PASSWORD" $ARTIFACTORY_URL/spring-batch-integration/$RELEASE_VERSION/spring-batch-integration-$RELEASE_VERSION-sources.jar - name: Sign artifacts and release them to Maven Central - uses: spring-io/nexus-sync-action@main + uses: jvalkeal/nexus-sync@v0.0.2 id: nexus with: url: ${{ secrets.OSSRH_URL }} From 1b773ef7d7481af1e50517f1ea658a04e08e1a3b Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Wed, 18 Dec 2024 21:28:41 +0100 Subject: [PATCH 043/266] Update maven-central-release.yml Signed-off-by: Mahmoud Ben Hassine --- .github/workflows/maven-central-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/maven-central-release.yml b/.github/workflows/maven-central-release.yml index 32c08fd2f2..9d39900983 100644 --- a/.github/workflows/maven-central-release.yml +++ b/.github/workflows/maven-central-release.yml @@ -66,7 +66,7 @@ jobs: wget --user="$ARTIFACTORY_USERNAME" --password="$ARTIFACTORY_PASSWORD" $ARTIFACTORY_URL/spring-batch-integration/$RELEASE_VERSION/spring-batch-integration-$RELEASE_VERSION-sources.jar - name: Sign artifacts and release them to Maven Central - uses: jvalkeal/nexus-sync@v0.0.2 + uses: jvalkeal/nexus-sync@v0 id: nexus with: url: ${{ secrets.OSSRH_URL }} From f4f01c56df3564cc6ee75ac9bac0013b04aa2a15 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Wed, 18 Dec 2024 21:34:31 +0100 Subject: [PATCH 044/266] Add pgp signing in maven-central-release.yml Signed-off-by: Mahmoud Ben Hassine --- .github/workflows/maven-central-release.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/maven-central-release.yml b/.github/workflows/maven-central-release.yml index 9d39900983..bce60ac9ea 100644 --- a/.github/workflows/maven-central-release.yml +++ b/.github/workflows/maven-central-release.yml @@ -78,3 +78,6 @@ jobs: close: true release: true generate-checksums: true + pgp-sign: true + pgp-sign-passphrase: ${{ secrets.GPG_PASSPHRASE }} + pgp-sign-private-key: ${{ secrets.GPG_PRIVATE_KEY }} From a8a458f6e1319858154e38fc835b5712756cabf5 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Wed, 18 Dec 2024 21:38:26 +0100 Subject: [PATCH 045/266] Release version 5.2.1 --- pom.xml | 2 +- spring-batch-bom/pom.xml | 2 +- spring-batch-core/pom.xml | 2 +- spring-batch-docs/pom.xml | 2 +- spring-batch-infrastructure/pom.xml | 2 +- spring-batch-integration/pom.xml | 2 +- spring-batch-samples/pom.xml | 2 +- spring-batch-test/pom.xml | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 8f93848f7b..2cef3d05b8 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ designed to enable the development of robust batch applications vital for the daily operations of enterprise systems. Spring Batch is part of the Spring Portfolio. - 5.2.1-SNAPSHOT + 5.2.1 pom https://projects.spring.io/spring-batch diff --git a/spring-batch-bom/pom.xml b/spring-batch-bom/pom.xml index 913818fe6e..d5a013c25d 100644 --- a/spring-batch-bom/pom.xml +++ b/spring-batch-bom/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.1-SNAPSHOT + 5.2.1 spring-batch-bom pom diff --git a/spring-batch-core/pom.xml b/spring-batch-core/pom.xml index 33d0eb9890..447807636d 100644 --- a/spring-batch-core/pom.xml +++ b/spring-batch-core/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.1-SNAPSHOT + 5.2.1 spring-batch-core jar diff --git a/spring-batch-docs/pom.xml b/spring-batch-docs/pom.xml index 5f6d82fd17..ccaeb1ae84 100644 --- a/spring-batch-docs/pom.xml +++ b/spring-batch-docs/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.1-SNAPSHOT + 5.2.1 spring-batch-docs Spring Batch Docs diff --git a/spring-batch-infrastructure/pom.xml b/spring-batch-infrastructure/pom.xml index 1823e8b1ff..fc411fe428 100644 --- a/spring-batch-infrastructure/pom.xml +++ b/spring-batch-infrastructure/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.1-SNAPSHOT + 5.2.1 spring-batch-infrastructure jar diff --git a/spring-batch-integration/pom.xml b/spring-batch-integration/pom.xml index dbbc1e7c87..05649454c0 100644 --- a/spring-batch-integration/pom.xml +++ b/spring-batch-integration/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.1-SNAPSHOT + 5.2.1 spring-batch-integration Spring Batch Integration diff --git a/spring-batch-samples/pom.xml b/spring-batch-samples/pom.xml index 7bb4812ccc..3fefe28a8c 100644 --- a/spring-batch-samples/pom.xml +++ b/spring-batch-samples/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.1-SNAPSHOT + 5.2.1 spring-batch-samples jar diff --git a/spring-batch-test/pom.xml b/spring-batch-test/pom.xml index 8ac987b12f..cf8b468ccd 100644 --- a/spring-batch-test/pom.xml +++ b/spring-batch-test/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.1-SNAPSHOT + 5.2.1 spring-batch-test Spring Batch Test From 70b85764267fbdc253abd44f06fedb1c0be77b1c Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Wed, 18 Dec 2024 21:38:44 +0100 Subject: [PATCH 046/266] Next development version --- pom.xml | 2 +- spring-batch-bom/pom.xml | 2 +- spring-batch-core/pom.xml | 2 +- spring-batch-docs/pom.xml | 2 +- spring-batch-infrastructure/pom.xml | 2 +- spring-batch-integration/pom.xml | 2 +- spring-batch-samples/pom.xml | 2 +- spring-batch-test/pom.xml | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 2cef3d05b8..eb01fbdcc5 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ designed to enable the development of robust batch applications vital for the daily operations of enterprise systems. Spring Batch is part of the Spring Portfolio. - 5.2.1 + 5.2.2-SNAPSHOT pom https://projects.spring.io/spring-batch diff --git a/spring-batch-bom/pom.xml b/spring-batch-bom/pom.xml index d5a013c25d..a9680681b6 100644 --- a/spring-batch-bom/pom.xml +++ b/spring-batch-bom/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.1 + 5.2.2-SNAPSHOT spring-batch-bom pom diff --git a/spring-batch-core/pom.xml b/spring-batch-core/pom.xml index 447807636d..5a4187bb5d 100644 --- a/spring-batch-core/pom.xml +++ b/spring-batch-core/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.1 + 5.2.2-SNAPSHOT spring-batch-core jar diff --git a/spring-batch-docs/pom.xml b/spring-batch-docs/pom.xml index ccaeb1ae84..d31d9dd8cd 100644 --- a/spring-batch-docs/pom.xml +++ b/spring-batch-docs/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.1 + 5.2.2-SNAPSHOT spring-batch-docs Spring Batch Docs diff --git a/spring-batch-infrastructure/pom.xml b/spring-batch-infrastructure/pom.xml index fc411fe428..dd208f8379 100644 --- a/spring-batch-infrastructure/pom.xml +++ b/spring-batch-infrastructure/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.1 + 5.2.2-SNAPSHOT spring-batch-infrastructure jar diff --git a/spring-batch-integration/pom.xml b/spring-batch-integration/pom.xml index 05649454c0..a57e5f58c5 100644 --- a/spring-batch-integration/pom.xml +++ b/spring-batch-integration/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.1 + 5.2.2-SNAPSHOT spring-batch-integration Spring Batch Integration diff --git a/spring-batch-samples/pom.xml b/spring-batch-samples/pom.xml index 3fefe28a8c..9bf671940f 100644 --- a/spring-batch-samples/pom.xml +++ b/spring-batch-samples/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.1 + 5.2.2-SNAPSHOT spring-batch-samples jar diff --git a/spring-batch-test/pom.xml b/spring-batch-test/pom.xml index cf8b468ccd..ab9e8b20ba 100644 --- a/spring-batch-test/pom.xml +++ b/spring-batch-test/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.1 + 5.2.2-SNAPSHOT spring-batch-test Spring Batch Test From aa8fb75f1c2a961414172341439d5736e74ea73f Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Thu, 19 Dec 2024 05:59:30 +0100 Subject: [PATCH 047/266] Update latest news in README.md Signed-off-by: Mahmoud Ben Hassine --- README.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 7110ad6524..5cc775db30 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,8 @@ # Latest news +* December 18, 2024: [Spring Batch 5.1.3 and 5.2.1 available now](https://spring.io/blog/2024/12/18/spring-batch-5-1-3-and-5-2-1-available-now) * November 24, 2024: [Bootiful Spring Boot 3.4: Spring Batch](https://spring.io/blog/2024/11/24/bootiful-34-batch) -* November 20, 2024: [Spring Batch 5.2.0 goes GA!](https://spring.io/blog/2024/11/20/spring-batch-5-2-0-goes-ga) -* October 25, 2024: [Spring Batch 5.2.0-RC1 is out!](https://spring.io/blog/2024/10/25/spring-batch-5-2-0-rc1-is-out) -* October 11, 2024: [Spring Batch 5.2.0-M2 is available now!](https://spring.io/blog/2024/10/11/spring-batch-5-2-0-m2-is-available-now) -* September 18, 2024: [Spring Batch 5.2.0-M1 is out!](https://spring.io/blog/2024/09/18/spring-batch-5-2-0-m1-is-out) +* November 20, 2024: [Spring Batch 5.2.0 goes GA!](https://spring.io/blog/2024/11/20/spring-batch-5-2-0-goes-ga) From 90e7e9fefacfe8cae1ee188e114d402ef7e7e3f1 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Tue, 7 Jan 2025 12:07:32 +0100 Subject: [PATCH 048/266] Add sample job based on PetClinic application Signed-off-by: Mahmoud Ben Hassine --- spring-batch-samples/README.md | 11 +++ .../batch/samples/petclinic/Owner.java | 19 +++++ .../OwnersExportJobConfiguration.java | 71 ++++++++++++++++++ .../batch/samples/petclinic/README.md | 21 ++++++ .../samples/common/business-schema-hsqldb.sql | 23 ++++++ .../samples/petclinic/job/ownersExportJob.xml | 42 +++++++++++ .../PetClinicJobFunctionalTests.java | 73 +++++++++++++++++++ 7 files changed, 260 insertions(+) create mode 100644 spring-batch-samples/src/main/java/org/springframework/batch/samples/petclinic/Owner.java create mode 100644 spring-batch-samples/src/main/java/org/springframework/batch/samples/petclinic/OwnersExportJobConfiguration.java create mode 100644 spring-batch-samples/src/main/java/org/springframework/batch/samples/petclinic/README.md create mode 100644 spring-batch-samples/src/main/resources/org/springframework/batch/samples/petclinic/job/ownersExportJob.xml create mode 100644 spring-batch-samples/src/test/java/org/springframework/batch/samples/petclinic/PetClinicJobFunctionalTests.java diff --git a/spring-batch-samples/README.md b/spring-batch-samples/README.md index eb26857fc5..770c7fd938 100644 --- a/spring-batch-samples/README.md +++ b/spring-batch-samples/README.md @@ -61,6 +61,7 @@ The IO Sample Job has a number of special instances that show different IO featu | [multiResource Sample](#multiresource-input-output-job) | x | | | | | | | x | | x | | x | | [XML Input Output Sample](#xml-input-output) | | | x | | | | | | | | | | | [MongoDB sample](#mongodb-sample) | | | | | x | | | | x | | | | +| [PetClinic sample](#petclinic-sample) | | | | | x | x | | | | | | | ### Common Sample Source Structures @@ -615,6 +616,16 @@ $>docker run --name mongodb --rm -d -p 27017:27017 mongo Once MongoDB is up and running, run the `org.springframework.batch.samples.mongodb.MongoDBSampleApp` class without any argument to start the sample. +### PetClinic sample + +This sample uses the [PetClinic Spring application](https://github.com/spring-projects/spring-petclinic) to show how to use +Spring Batch to export data from a relational database table to a flat file. + +The job in this sample is a single-step job that exports data from the `owners` table +to a flat file named `owners.csv`. + +[PetClinic Sample](src/main/java/org/springframework/batch/samples/petclinic/README.md) + ### Adhoc Loop and JMX Sample This job is simply an infinite loop. It runs forever so it is diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/petclinic/Owner.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/petclinic/Owner.java new file mode 100644 index 0000000000..7a66d7d296 --- /dev/null +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/petclinic/Owner.java @@ -0,0 +1,19 @@ +/* + * Copyright 2025 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.samples.petclinic; + +public record Owner(int id, String firstname, String lastname, String address, String city, String telephone) { +} diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/petclinic/OwnersExportJobConfiguration.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/petclinic/OwnersExportJobConfiguration.java new file mode 100644 index 0000000000..4a27ffb23f --- /dev/null +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/petclinic/OwnersExportJobConfiguration.java @@ -0,0 +1,71 @@ +/* + * Copyright 2025 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.samples.petclinic; + +import javax.sql.DataSource; + +import org.springframework.batch.core.Job; +import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; +import org.springframework.batch.core.job.builder.JobBuilder; +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.core.step.builder.StepBuilder; +import org.springframework.batch.item.database.JdbcCursorItemReader; +import org.springframework.batch.item.database.builder.JdbcCursorItemReaderBuilder; +import org.springframework.batch.item.file.FlatFileItemWriter; +import org.springframework.batch.item.file.builder.FlatFileItemWriterBuilder; +import org.springframework.batch.samples.common.DataSourceConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.core.io.FileSystemResource; +import org.springframework.jdbc.core.DataClassRowMapper; +import org.springframework.jdbc.support.JdbcTransactionManager; + +@Configuration +@EnableBatchProcessing +@Import(DataSourceConfiguration.class) +public class OwnersExportJobConfiguration { + + @Bean + public JdbcCursorItemReader ownersReader(DataSource dataSource) { + return new JdbcCursorItemReaderBuilder().name("ownersReader") + .sql("SELECT * FROM OWNERS") + .dataSource(dataSource) + .rowMapper(new DataClassRowMapper<>(Owner.class)) + .build(); + } + + @Bean + public FlatFileItemWriter ownersWriter() { + return new FlatFileItemWriterBuilder().name("ownersWriter") + .resource(new FileSystemResource("owners.csv")) + .delimited() + .names("id", "firstname", "lastname", "address", "city", "telephone") + .build(); + } + + @Bean + public Job job(JobRepository jobRepository, JdbcTransactionManager transactionManager, + JdbcCursorItemReader ownersReader, FlatFileItemWriter ownersWriter) { + return new JobBuilder("ownersExportJob", jobRepository) + .start(new StepBuilder("ownersExportStep", jobRepository).chunk(5, transactionManager) + .reader(ownersReader) + .writer(ownersWriter) + .build()) + .build(); + } + +} diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/petclinic/README.md b/spring-batch-samples/src/main/java/org/springframework/batch/samples/petclinic/README.md new file mode 100644 index 0000000000..12be08e09b --- /dev/null +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/petclinic/README.md @@ -0,0 +1,21 @@ +# PetClinic Job + +## About the sample + +This sample uses the [PetClinic Spring application](https://github.com/spring-projects/spring-petclinic) to show how to use +Spring Batch to export data from a relational database table to a flat file. + +The job in this sample is a single-step job that exports data from the `owners` table +to a flat file named `owners.csv`. + +## Run the sample + +You can run the sample from the command line as following: + +``` +$>cd spring-batch-samples +# Launch the sample using the XML configuration +$>../mvnw -Dtest=PetClinicJobFunctionalTests#testLaunchJobWithXmlConfiguration test +# Launch the sample using the Java configuration +$>../mvnw -Dtest=PetClinicJobFunctionalTests#testLaunchJobWithJavaConfiguration test +``` \ No newline at end of file diff --git a/spring-batch-samples/src/main/resources/org/springframework/batch/samples/common/business-schema-hsqldb.sql b/spring-batch-samples/src/main/resources/org/springframework/batch/samples/common/business-schema-hsqldb.sql index b02b0b89a5..ffd6823049 100644 --- a/spring-batch-samples/src/main/resources/org/springframework/batch/samples/common/business-schema-hsqldb.sql +++ b/spring-batch-samples/src/main/resources/org/springframework/batch/samples/common/business-schema-hsqldb.sql @@ -100,3 +100,26 @@ CREATE TABLE ERROR_LOG ( STEP_NAME CHAR(20) , MESSAGE VARCHAR(300) NOT NULL ) ; + +-- PetClinic sample tables + +CREATE TABLE OWNERS ( + ID INTEGER IDENTITY PRIMARY KEY, + FIRSTNAME VARCHAR(30), + LASTNAME VARCHAR(30), + ADDRESS VARCHAR(255), + CITY VARCHAR(80), + TELEPHONE VARCHAR(20) +); + +INSERT INTO OWNERS VALUES (1, 'George', 'Franklin', '110 W. Liberty St.', 'Madison', '6085551023'); +INSERT INTO OWNERS VALUES (2, 'Betty', 'Davis', '638 Cardinal Ave.', 'Sun Prairie', '6085551749'); +INSERT INTO OWNERS VALUES (3, 'Eduardo', 'Rodriquez', '2693 Commerce St.', 'McFarland', '6085558763'); +INSERT INTO OWNERS VALUES (4, 'Harold', 'Davis', '563 Friendly St.', 'Windsor', '6085553198'); +INSERT INTO OWNERS VALUES (5, 'Peter', 'McTavish', '2387 S. Fair Way', 'Madison', '6085552765'); +INSERT INTO OWNERS VALUES (6, 'Jean', 'Coleman', '105 N. Lake St.', 'Monona', '6085552654'); +INSERT INTO OWNERS VALUES (7, 'Jeff', 'Black', '1450 Oak Blvd.', 'Monona', '6085555387'); +INSERT INTO OWNERS VALUES (8, 'Maria', 'Escobito', '345 Maple St.', 'Madison', '6085557683'); +INSERT INTO OWNERS VALUES (9, 'David', 'Schroeder', '2749 Blackhawk Trail', 'Madison', '6085559435'); +INSERT INTO OWNERS VALUES (10, 'Carlos', 'Estaban', '2335 Independence La.', 'Waunakee', '6085555487'); + diff --git a/spring-batch-samples/src/main/resources/org/springframework/batch/samples/petclinic/job/ownersExportJob.xml b/spring-batch-samples/src/main/resources/org/springframework/batch/samples/petclinic/job/ownersExportJob.xml new file mode 100644 index 0000000000..0247f5511f --- /dev/null +++ b/spring-batch-samples/src/main/resources/org/springframework/batch/samples/petclinic/job/ownersExportJob.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/samples/petclinic/PetClinicJobFunctionalTests.java b/spring-batch-samples/src/test/java/org/springframework/batch/samples/petclinic/PetClinicJobFunctionalTests.java new file mode 100644 index 0000000000..5f790cba9f --- /dev/null +++ b/spring-batch-samples/src/test/java/org/springframework/batch/samples/petclinic/PetClinicJobFunctionalTests.java @@ -0,0 +1,73 @@ +/* + * Copyright 2025 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.samples.petclinic; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.springframework.batch.core.BatchStatus; +import org.springframework.batch.core.Job; +import org.springframework.batch.core.JobExecution; +import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.test.JobLauncherTestUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +@SpringJUnitConfig(locations = { "/simple-job-launcher-context.xml", + "/org/springframework/batch/samples/petclinic/job/ownersExportJob.xml" }) +class PetClinicJobFunctionalTests { + + @Autowired + private JobLauncherTestUtils jobLauncherTestUtils; + + @BeforeEach + public void setup() throws IOException { + Files.deleteIfExists(Paths.get("owners.csv")); + } + + @Test + void testLaunchJobWithXmlConfiguration() throws Exception { + // when + JobExecution jobExecution = jobLauncherTestUtils.launchJob(); + + // then + assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus()); + } + + @Test + void testLaunchJobWithJavaConfiguration() throws Exception { + // given + ApplicationContext context = new AnnotationConfigApplicationContext(OwnersExportJobConfiguration.class); + JobLauncher jobLauncher = context.getBean(JobLauncher.class); + Job job = context.getBean(Job.class); + + // when + JobExecution jobExecution = jobLauncher.run(job, new JobParameters()); + + // then + assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus()); + } + +} From e706957eb7fd2687682d2850d4685b9de96f844f Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Tue, 7 Jan 2025 13:20:22 +0100 Subject: [PATCH 049/266] Fix tests Signed-off-by: Mahmoud Ben Hassine --- .../batch/samples/common/business-schema-hsqldb.sql | 1 + .../batch/samples/petclinic/PetClinicJobFunctionalTests.java | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/spring-batch-samples/src/main/resources/org/springframework/batch/samples/common/business-schema-hsqldb.sql b/spring-batch-samples/src/main/resources/org/springframework/batch/samples/common/business-schema-hsqldb.sql index ffd6823049..52a8c890f0 100644 --- a/spring-batch-samples/src/main/resources/org/springframework/batch/samples/common/business-schema-hsqldb.sql +++ b/spring-batch-samples/src/main/resources/org/springframework/batch/samples/common/business-schema-hsqldb.sql @@ -9,6 +9,7 @@ DROP TABLE PLAYERS IF EXISTS; DROP TABLE GAMES IF EXISTS; DROP TABLE PLAYER_SUMMARY IF EXISTS; DROP TABLE ERROR_LOG IF EXISTS; +DROP TABLE OWNERS IF EXISTS; -- Autogenerated: do not edit this file diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/samples/petclinic/PetClinicJobFunctionalTests.java b/spring-batch-samples/src/test/java/org/springframework/batch/samples/petclinic/PetClinicJobFunctionalTests.java index 5f790cba9f..dc8bfce26b 100644 --- a/spring-batch-samples/src/test/java/org/springframework/batch/samples/petclinic/PetClinicJobFunctionalTests.java +++ b/spring-batch-samples/src/test/java/org/springframework/batch/samples/petclinic/PetClinicJobFunctionalTests.java @@ -19,6 +19,7 @@ import java.nio.file.Files; import java.nio.file.Paths; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -43,7 +44,8 @@ class PetClinicJobFunctionalTests { private JobLauncherTestUtils jobLauncherTestUtils; @BeforeEach - public void setup() throws IOException { + @AfterEach + public void deleteOwnersFile() throws IOException { Files.deleteIfExists(Paths.get("owners.csv")); } From 60f490556dcae73f35fe4ba7a3a12ddbe0c69ddb Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Fri, 24 Jan 2025 17:24:43 +0100 Subject: [PATCH 050/266] Add dco.yml Signed-off-by: Mahmoud Ben Hassine --- .github/dco.yml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .github/dco.yml diff --git a/.github/dco.yml b/.github/dco.yml new file mode 100644 index 0000000000..0c4b142e9a --- /dev/null +++ b/.github/dco.yml @@ -0,0 +1,2 @@ +require: + members: false From 8e91adb9ab95d66a1dc127f0b52134603517b589 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Fri, 24 Jan 2025 17:31:48 +0100 Subject: [PATCH 051/266] Update CONTRIBUTING.md to include details about the DCO Signed-off-by: Mahmoud Ben Hassine --- CONTRIBUTING.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 779b711d58..c6ad7d3a70 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -26,9 +26,11 @@ about how to report issues. Not sure what a *pull request* is, or how to submit one? Take a look at the excellent [GitHub help documentation][] first. Please create a new issue *before* submitting a pull request unless the change is truly trivial, e.g. typo fixes, removing compiler warnings, etc. -### Sign the contributor license agreement +### Sign-off commits according to the Developer Certificate of Origin -If you have not previously done so, please fill out and submit the [Contributor License Agreement](https://cla.pivotal.io/sign/spring). +All commits must include a Signed-off-by trailer at the end of each commit message to indicate that the contributor agrees to the [Developer Certificate of Origin](https://developercertificate.org). + +For additional details, please refer to the blog post [Hello DCO, Goodbye CLA: Simplifying Contributions to Spring](https://spring.io/blog/2025/01/06/hello-dco-goodbye-cla-simplifying-contributions-to-spring). ### Fork the Repository From f888ebb43f70d925c028721db0b3d71306089038 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Fri, 31 Jan 2025 12:39:36 +0100 Subject: [PATCH 052/266] Add test cases to cover key generation for empty identifying job parameters set Related to #4755 --- .../core/DefaultJobKeyGeneratorTests.java | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/DefaultJobKeyGeneratorTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/DefaultJobKeyGeneratorTests.java index 74e280f1ef..f5e9983011 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/DefaultJobKeyGeneratorTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/DefaultJobKeyGeneratorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2022 the original author or authors. + * Copyright 2013-2025 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. @@ -65,4 +65,22 @@ void testCreateJobKeyOrdering() { assertEquals(key1, key2); } + @Test + public void testCreateJobKeyForEmptyParameters() { + JobParameters jobParameters1 = new JobParameters(); + JobParameters jobParameters2 = new JobParameters(); + String key1 = jobKeyGenerator.generateKey(jobParameters1); + String key2 = jobKeyGenerator.generateKey(jobParameters2); + assertEquals(key1, key2); + } + + @Test + public void testCreateJobKeyForEmptyParametersAndNonIdentifying() { + JobParameters jobParameters1 = new JobParameters(); + JobParameters jobParameters2 = new JobParametersBuilder().addString("name", "foo", false).toJobParameters(); + String key1 = jobKeyGenerator.generateKey(jobParameters1); + String key2 = jobKeyGenerator.generateKey(jobParameters2); + assertEquals(key1, key2); + } + } From 3b0868db5d76927e2a09a57f157b8f9e90f162ea Mon Sep 17 00:00:00 2001 From: kimjg Date: Sun, 9 Feb 2025 17:08:42 +0900 Subject: [PATCH 053/266] Extract bean name string literals to constants - Extract hardcoded bean name strings to static final constants - Improve code maintainability and reduce the risk of typos - No functional changes Signed-off-by: kimjg --- .../annotation/BatchRegistrar.java | 46 +++++++++++-------- 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/BatchRegistrar.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/BatchRegistrar.java index d261384ef0..3d23f6bcf7 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/BatchRegistrar.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/BatchRegistrar.java @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 the original author or authors. + * Copyright 2022-2025 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. @@ -51,6 +51,16 @@ class BatchRegistrar implements ImportBeanDefinitionRegistrar { private static final String MISSING_ANNOTATION_ERROR_MESSAGE = "EnableBatchProcessing is not present on importing class '%s' as expected"; + private static final String JOB_REPOSITORY = "jobRepository"; + + private static final String JOB_EXPLORER = "jobExplorer"; + + private static final String JOB_LAUNCHER = "jobLauncher"; + + private static final String JOB_REGISTRY = "jobRegistry"; + + private static final String JOB_LOADER = "jobLoader"; + @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { StopWatch watch = new StopWatch(); @@ -80,7 +90,7 @@ private void validateState(AnnotationMetadata importingClassMetadata) { } private void registerJobRepository(BeanDefinitionRegistry registry, EnableBatchProcessing batchAnnotation) { - if (registry.containsBeanDefinition("jobRepository")) { + if (registry.containsBeanDefinition(JOB_REPOSITORY)) { LOGGER.info("Bean jobRepository already defined in the application context, skipping" + " the registration of a jobRepository"); return; @@ -143,11 +153,11 @@ private void registerJobRepository(BeanDefinitionRegistry registry, EnableBatchP beanDefinitionBuilder.addPropertyValue("maxVarCharLength", batchAnnotation.maxVarCharLength()); beanDefinitionBuilder.addPropertyValue("clobType", batchAnnotation.clobType()); - registry.registerBeanDefinition("jobRepository", beanDefinitionBuilder.getBeanDefinition()); + registry.registerBeanDefinition(JOB_REPOSITORY, beanDefinitionBuilder.getBeanDefinition()); } private void registerJobExplorer(BeanDefinitionRegistry registry, EnableBatchProcessing batchAnnotation) { - if (registry.containsBeanDefinition("jobExplorer")) { + if (registry.containsBeanDefinition(JOB_EXPLORER)) { LOGGER.info("Bean jobExplorer already defined in the application context, skipping" + " the registration of a jobExplorer"); return; @@ -192,11 +202,11 @@ private void registerJobExplorer(BeanDefinitionRegistry registry, EnableBatchPro if (tablePrefix != null) { beanDefinitionBuilder.addPropertyValue("tablePrefix", tablePrefix); } - registry.registerBeanDefinition("jobExplorer", beanDefinitionBuilder.getBeanDefinition()); + registry.registerBeanDefinition(JOB_EXPLORER, beanDefinitionBuilder.getBeanDefinition()); } private void registerJobLauncher(BeanDefinitionRegistry registry, EnableBatchProcessing batchAnnotation) { - if (registry.containsBeanDefinition("jobLauncher")) { + if (registry.containsBeanDefinition(JOB_LAUNCHER)) { LOGGER.info("Bean jobLauncher already defined in the application context, skipping" + " the registration of a jobLauncher"); return; @@ -204,25 +214,25 @@ private void registerJobLauncher(BeanDefinitionRegistry registry, EnableBatchPro BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder .genericBeanDefinition(TaskExecutorJobLauncher.class); // set mandatory properties - beanDefinitionBuilder.addPropertyReference("jobRepository", "jobRepository"); + beanDefinitionBuilder.addPropertyReference(JOB_REPOSITORY, JOB_REPOSITORY); // set optional properties String taskExecutorRef = batchAnnotation.taskExecutorRef(); if (registry.containsBeanDefinition(taskExecutorRef)) { beanDefinitionBuilder.addPropertyReference("taskExecutor", taskExecutorRef); } - registry.registerBeanDefinition("jobLauncher", beanDefinitionBuilder.getBeanDefinition()); + registry.registerBeanDefinition(JOB_LAUNCHER, beanDefinitionBuilder.getBeanDefinition()); } private void registerJobRegistry(BeanDefinitionRegistry registry) { - if (registry.containsBeanDefinition("jobRegistry")) { + if (registry.containsBeanDefinition(JOB_REGISTRY)) { LOGGER.info("Bean jobRegistry already defined in the application context, skipping" + " the registration of a jobRegistry"); return; } BeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(MapJobRegistry.class) .getBeanDefinition(); - registry.registerBeanDefinition("jobRegistry", beanDefinition); + registry.registerBeanDefinition(JOB_REGISTRY, beanDefinition); } private void registerJobRegistrySmartInitializingSingleton(BeanDefinitionRegistry registry) { @@ -234,7 +244,7 @@ private void registerJobRegistrySmartInitializingSingleton(BeanDefinitionRegistr } BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder .genericBeanDefinition(JobRegistrySmartInitializingSingleton.class); - beanDefinitionBuilder.addPropertyReference("jobRegistry", "jobRegistry"); + beanDefinitionBuilder.addPropertyReference(JOB_REGISTRY, JOB_REGISTRY); registry.registerBeanDefinition("jobRegistrySmartInitializingSingleton", beanDefinitionBuilder.getBeanDefinition()); @@ -252,10 +262,10 @@ private void registerJobOperator(BeanDefinitionRegistry registry, EnableBatchPro String transactionManagerRef = batchAnnotation.transactionManagerRef(); beanDefinitionBuilder.addPropertyReference("transactionManager", transactionManagerRef); - beanDefinitionBuilder.addPropertyReference("jobRepository", "jobRepository"); - beanDefinitionBuilder.addPropertyReference("jobLauncher", "jobLauncher"); - beanDefinitionBuilder.addPropertyReference("jobExplorer", "jobExplorer"); - beanDefinitionBuilder.addPropertyReference("jobRegistry", "jobRegistry"); + beanDefinitionBuilder.addPropertyReference(JOB_REPOSITORY, JOB_REPOSITORY); + beanDefinitionBuilder.addPropertyReference(JOB_LAUNCHER, JOB_LAUNCHER); + beanDefinitionBuilder.addPropertyReference(JOB_EXPLORER, JOB_EXPLORER); + beanDefinitionBuilder.addPropertyReference(JOB_REGISTRY, JOB_REGISTRY); // set optional properties String jobParametersConverterRef = batchAnnotation.jobParametersConverterRef(); @@ -276,12 +286,12 @@ private void registerAutomaticJobRegistrar(BeanDefinitionRegistry registry, Enab return; } BeanDefinition jobLoaderBeanDefinition = BeanDefinitionBuilder.genericBeanDefinition(DefaultJobLoader.class) - .addPropertyReference("jobRegistry", "jobRegistry") + .addPropertyReference(JOB_REGISTRY, JOB_REGISTRY) .getBeanDefinition(); - registry.registerBeanDefinition("jobLoader", jobLoaderBeanDefinition); + registry.registerBeanDefinition(JOB_LOADER, jobLoaderBeanDefinition); BeanDefinition jobRegistrarBeanDefinition = BeanDefinitionBuilder .genericBeanDefinition(AutomaticJobRegistrar.class) - .addPropertyReference("jobLoader", "jobLoader") + .addPropertyReference(JOB_LOADER, JOB_LOADER) .getBeanDefinition(); registry.registerBeanDefinition("jobRegistrar", jobRegistrarBeanDefinition); } From f758d14ee6b704a5a59e6f9ab8cfe1f4d62a05d0 Mon Sep 17 00:00:00 2001 From: Henning Poettker Date: Tue, 7 Jan 2025 22:16:43 +0100 Subject: [PATCH 054/266] Refactor `MultiResourceItemWriter` The `MultiResourceItemWriter` now writes at most `itemCountLimitPerResource` items per resource where it previously allowed more items when they were written within the same chunk. Resolves #1722 --- .../item/file/MultiResourceItemWriter.java | 46 ++++--- .../MultiResourceItemWriterFlatFileTests.java | 129 +++++++++--------- .../file/MultiResourceItemWriterXmlTests.java | 19 ++- .../MultiResourceItemWriterBuilderTests.java | 93 ++++++------- 4 files changed, 150 insertions(+), 137 deletions(-) diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/MultiResourceItemWriter.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/MultiResourceItemWriter.java index 835abb3527..d07cbda99d 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/MultiResourceItemWriter.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/MultiResourceItemWriter.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2024 the original author or authors. + * Copyright 2006-2025 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. @@ -34,9 +34,6 @@ * {@link #setItemCountLimitPerResource(int)}. Suffix creation can be customized with * {@link #setResourceSuffixCreator(ResourceSuffixCreator)}. *

- * Note that new resources are created only at chunk boundaries i.e. the number of items - * written into one resource is between the limit set by - *

* This writer will create an output file only when there are items to write, which means * there would be no empty file created if no items are passed (for example when all items * are filtered or skipped during the processing phase). @@ -45,6 +42,7 @@ * @param item type * @author Robert Kasanicky * @author Mahmoud Ben Hassine + * @author Henning Pöttker */ public class MultiResourceItemWriter extends AbstractItemStreamItemWriter { @@ -74,22 +72,30 @@ public MultiResourceItemWriter() { @Override public void write(Chunk items) throws Exception { - if (!opened) { - File file = setResourceToDelegate(); - // create only if write is called - file.createNewFile(); - Assert.state(file.canWrite(), "Output resource " + file.getAbsolutePath() + " must be writable"); - delegate.open(new ExecutionContext()); - opened = true; - } - delegate.write(items); - currentResourceItemCount += items.size(); - if (currentResourceItemCount >= itemCountLimitPerResource) { - delegate.close(); - resourceIndex++; - currentResourceItemCount = 0; - setResourceToDelegate(); - opened = false; + int writtenItems = 0; + while (writtenItems < items.size()) { + if (!opened) { + File file = setResourceToDelegate(); + // create only if write is called + file.createNewFile(); + Assert.state(file.canWrite(), "Output resource " + file.getAbsolutePath() + " must be writable"); + delegate.open(new ExecutionContext()); + opened = true; + } + + int itemsToWrite = Math.min(itemCountLimitPerResource - currentResourceItemCount, + items.size() - writtenItems); + delegate.write(new Chunk(items.getItems().subList(writtenItems, writtenItems + itemsToWrite))); + currentResourceItemCount += itemsToWrite; + writtenItems += itemsToWrite; + + if (currentResourceItemCount >= itemCountLimitPerResource) { + delegate.close(); + resourceIndex++; + currentResourceItemCount = 0; + setResourceToDelegate(); + opened = false; + } } } diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/MultiResourceItemWriterFlatFileTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/MultiResourceItemWriterFlatFileTests.java index ffe91317e9..ab23affa63 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/MultiResourceItemWriterFlatFileTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/MultiResourceItemWriterFlatFileTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2023 the original author or authors. + * Copyright 2008-2025 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. @@ -78,22 +78,22 @@ void testBasicMultiResourceWriteScenario() throws Exception { tested.write(Chunk.of("1", "2", "3")); - File part1 = new File(file.getAbsolutePath() + suffixCreator.getSuffix(1)); - assertTrue(part1.exists()); - assertEquals("123", readFile(part1)); + assertFileExistsAndContains(1, "12"); + assertFileExistsAndContains(2, "3"); tested.write(Chunk.of("4")); - File part2 = new File(file.getAbsolutePath() + suffixCreator.getSuffix(2)); - assertTrue(part2.exists()); - assertEquals("4", readFile(part2)); + + assertFileExistsAndContains(2, "34"); tested.write(Chunk.of("5")); - assertEquals("45", readFile(part2)); + + assertFileExistsAndContains(3, "5"); tested.write(Chunk.of("6", "7", "8", "9")); - File part3 = new File(file.getAbsolutePath() + suffixCreator.getSuffix(3)); - assertTrue(part3.exists()); - assertEquals("6789", readFile(part3)); + + assertFileExistsAndContains(3, "56"); + assertFileExistsAndContains(4, "78"); + assertFileExistsAndContains(5, "9"); } @Test @@ -107,7 +107,7 @@ void testUpdateAfterDelegateClose() throws Exception { assertEquals(1, executionContext.getInt(tested.getExecutionContextKey("resource.index"))); tested.write(Chunk.of("1", "2", "3")); tested.update(executionContext); - assertEquals(0, executionContext.getInt(tested.getExecutionContextKey("resource.item.count"))); + assertEquals(1, executionContext.getInt(tested.getExecutionContextKey("resource.item.count"))); assertEquals(2, executionContext.getInt(tested.getExecutionContextKey("resource.index"))); } @@ -121,17 +121,22 @@ void testMultiResourceWriteScenarioWithFooter() throws Exception { tested.write(Chunk.of("1", "2", "3")); - File part1 = new File(file.getAbsolutePath() + suffixCreator.getSuffix(1)); - assertTrue(part1.exists()); + assertFileExistsAndContains(1, "12f"); + assertFileExistsAndContains(2, "3"); tested.write(Chunk.of("4")); - File part2 = new File(file.getAbsolutePath() + suffixCreator.getSuffix(2)); - assertTrue(part2.exists()); + + assertFileExistsAndContains(2, "34f"); + + tested.write(Chunk.of("5")); + + assertFileExistsAndContains(3, "5"); tested.close(); - assertEquals("123f", readFile(part1)); - assertEquals("4f", readFile(part2)); + assertFileExistsAndContains(1, "12f"); + assertFileExistsAndContains(2, "34f"); + assertFileExistsAndContains(3, "5f"); } @@ -144,19 +149,18 @@ void testTransactionalMultiResourceWriteScenarioWithFooter() throws Exception { ResourcelessTransactionManager transactionManager = new ResourcelessTransactionManager(); - new TransactionTemplate(transactionManager).execute(new WriterCallback(Chunk.of("1", "2", "3"))); + new TransactionTemplate(transactionManager).execute(new WriterCallback(Chunk.of("1", "2"))); - File part1 = new File(file.getAbsolutePath() + suffixCreator.getSuffix(1)); - assertTrue(part1.exists()); + assertFileExistsAndContains(1, "12f"); - new TransactionTemplate(transactionManager).execute(new WriterCallback(Chunk.of("4"))); - File part2 = new File(file.getAbsolutePath() + suffixCreator.getSuffix(2)); - assertTrue(part2.exists()); + new TransactionTemplate(transactionManager).execute(new WriterCallback(Chunk.of("3"))); + + assertFileExistsAndContains(2, "3"); tested.close(); - assertEquals("123f", readFile(part1)); - assertEquals("4f", readFile(part2)); + assertFileExistsAndContains(1, "12f"); + assertFileExistsAndContains(2, "3f"); } @@ -168,27 +172,23 @@ void testRestart() throws Exception { tested.write(Chunk.of("1", "2", "3")); - File part1 = new File(file.getAbsolutePath() + suffixCreator.getSuffix(1)); - assertTrue(part1.exists()); - assertEquals("123", readFile(part1)); - - tested.write(Chunk.of("4")); - File part2 = new File(file.getAbsolutePath() + suffixCreator.getSuffix(2)); - assertTrue(part2.exists()); - assertEquals("4", readFile(part2)); + assertFileExistsAndContains(1, "12"); + assertFileExistsAndContains(2, "3"); tested.update(executionContext); tested.close(); tested.open(executionContext); - tested.write(Chunk.of("5")); - assertEquals("45", readFile(part2)); + tested.write(Chunk.of("4")); - tested.write(Chunk.of("6", "7", "8", "9")); - File part3 = new File(file.getAbsolutePath() + suffixCreator.getSuffix(3)); - assertTrue(part3.exists()); - assertEquals("6789", readFile(part3)); + assertFileExistsAndContains(2, "34"); + + tested.write(Chunk.of("5", "6", "7", "8", "9")); + + assertFileExistsAndContains(3, "56"); + assertFileExistsAndContains(4, "78"); + assertFileExistsAndContains(5, "9"); } @Test @@ -201,27 +201,24 @@ void testRestartWithFooter() throws Exception { tested.write(Chunk.of("1", "2", "3")); - File part1 = new File(file.getAbsolutePath() + suffixCreator.getSuffix(1)); - assertTrue(part1.exists()); - assertEquals("123f", readFile(part1)); - - tested.write(Chunk.of("4")); - File part2 = new File(file.getAbsolutePath() + suffixCreator.getSuffix(2)); - assertTrue(part2.exists()); - assertEquals("4", readFile(part2)); + assertFileExistsAndContains(1, "12f"); + assertFileExistsAndContains(2, "3"); tested.update(executionContext); tested.close(); tested.open(executionContext); - tested.write(Chunk.of("5")); - assertEquals("45f", readFile(part2)); + tested.write(Chunk.of("4")); - tested.write(Chunk.of("6", "7", "8", "9")); - File part3 = new File(file.getAbsolutePath() + suffixCreator.getSuffix(3)); - assertTrue(part3.exists()); - assertEquals("6789f", readFile(part3)); + assertFileExistsAndContains(2, "34f"); + + tested.write(Chunk.of("5", "6", "7", "8", "9")); + tested.close(); + + assertFileExistsAndContains(3, "56f"); + assertFileExistsAndContains(4, "78f"); + assertFileExistsAndContains(5, "9f"); } @Test @@ -233,24 +230,28 @@ void testTransactionalRestartWithFooter() throws Exception { ResourcelessTransactionManager transactionManager = new ResourcelessTransactionManager(); - new TransactionTemplate(transactionManager).execute(new WriterCallback(Chunk.of("1", "2", "3"))); + new TransactionTemplate(transactionManager).execute(new WriterCallback(Chunk.of("1", "2"))); - File part1 = new File(file.getAbsolutePath() + suffixCreator.getSuffix(1)); - assertTrue(part1.exists()); - assertEquals("123f", readFile(part1)); + assertFileExistsAndContains(1, "12f"); - new TransactionTemplate(transactionManager).execute(new WriterCallback(Chunk.of("4"))); - File part2 = new File(file.getAbsolutePath() + suffixCreator.getSuffix(2)); - assertTrue(part2.exists()); - assertEquals("4", readFile(part2)); + new TransactionTemplate(transactionManager).execute(new WriterCallback(Chunk.of("3"))); + + assertFileExistsAndContains(2, "3"); tested.update(executionContext); tested.close(); tested.open(executionContext); - new TransactionTemplate(transactionManager).execute(new WriterCallback(Chunk.of("5"))); - assertEquals("45f", readFile(part2)); + new TransactionTemplate(transactionManager).execute(new WriterCallback(Chunk.of("4"))); + + assertFileExistsAndContains(2, "34f"); + } + + private void assertFileExistsAndContains(int index, String expected) throws Exception { + File part = new File(this.file.getAbsolutePath() + this.suffixCreator.getSuffix(index)); + assertTrue(part.exists()); + assertEquals(expected, readFile(part)); } } diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/MultiResourceItemWriterXmlTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/MultiResourceItemWriterXmlTests.java index 38760361c6..f6485adb80 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/MultiResourceItemWriterXmlTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/MultiResourceItemWriterXmlTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2009-2022 the original author or authors. + * Copyright 2009-2025 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. @@ -108,21 +108,26 @@ void multiResourceWritingWithRestart() throws Exception { tested.update(executionContext); tested.close(); - assertEquals(xmlDocStart + "" + xmlDocEnd, readFile(part2)); - assertEquals(xmlDocStart + "" + xmlDocEnd, readFile(part1)); + assertEquals(xmlDocStart + "" + xmlDocEnd, readFile(part2)); + assertEquals(xmlDocStart + "" + xmlDocEnd, readFile(part1)); tested.open(executionContext); tested.write(Chunk.of("5")); - - tested.write(Chunk.of("6", "7", "8", "9")); File part3 = new File(file.getAbsolutePath() + suffixCreator.getSuffix(3)); assertTrue(part3.exists()); + tested.write(Chunk.of("6", "7", "8", "9")); + File part4 = new File(file.getAbsolutePath() + suffixCreator.getSuffix(4)); + assertTrue(part4.exists()); + File part5 = new File(file.getAbsolutePath() + suffixCreator.getSuffix(5)); + assertTrue(part5.exists()); + tested.close(); - assertEquals(xmlDocStart + "" + xmlDocEnd, readFile(part2)); - assertEquals(xmlDocStart + "" + xmlDocEnd, readFile(part3)); + assertEquals(xmlDocStart + "" + xmlDocEnd, readFile(part3)); + assertEquals(xmlDocStart + "" + xmlDocEnd, readFile(part4)); + assertEquals(xmlDocStart + "" + xmlDocEnd, readFile(part5)); } } diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/builder/MultiResourceItemWriterBuilderTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/builder/MultiResourceItemWriterBuilderTests.java index 6eb75b9ed8..ce1ec6b8f4 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/builder/MultiResourceItemWriterBuilderTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/builder/MultiResourceItemWriterBuilderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2023 the original author or authors. + * Copyright 2017-2025 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. @@ -84,22 +84,22 @@ void testBasicMultiResourceWriteScenario() throws Exception { this.writer.write(Chunk.of("1", "2", "3")); - File part1 = new File(this.file.getAbsolutePath() + this.suffixCreator.getSuffix(1)); - assertTrue(part1.exists()); - assertEquals("123", readFile(part1)); + assertFileExistsAndContains(1, "12"); + assertFileExistsAndContains(2, "3"); this.writer.write(Chunk.of("4")); - File part2 = new File(this.file.getAbsolutePath() + this.suffixCreator.getSuffix(2)); - assertTrue(part2.exists()); - assertEquals("4", readFile(part2)); + + assertFileExistsAndContains(2, "34"); this.writer.write(Chunk.of("5")); - assertEquals("45", readFile(part2)); + + assertFileExistsAndContains(3, "5"); this.writer.write(Chunk.of("6", "7", "8", "9")); - File part3 = new File(this.file.getAbsolutePath() + this.suffixCreator.getSuffix(3)); - assertTrue(part3.exists()); - assertEquals("6789", readFile(part3)); + + assertFileExistsAndContains(3, "56"); + assertFileExistsAndContains(4, "78"); + assertFileExistsAndContains(5, "9"); } @Test @@ -117,14 +117,12 @@ void testBasicDefaultSuffixCreator() throws Exception { this.writer.write(Chunk.of("1", "2", "3")); - File part1 = new File(this.file.getAbsolutePath() + simpleResourceSuffixCreator.getSuffix(1)); - assertTrue(part1.exists()); - assertEquals("123", readFile(part1)); + assertFileExistsAndContains(1, "12", simpleResourceSuffixCreator); + assertFileExistsAndContains(2, "3", simpleResourceSuffixCreator); this.writer.write(Chunk.of("4")); - File part2 = new File(this.file.getAbsolutePath() + simpleResourceSuffixCreator.getSuffix(2)); - assertTrue(part2.exists()); - assertEquals("4", readFile(part2)); + + assertFileExistsAndContains(2, "34", simpleResourceSuffixCreator); } @Test @@ -143,7 +141,7 @@ void testUpdateAfterDelegateClose() throws Exception { assertEquals(1, this.executionContext.getInt(this.writer.getExecutionContextKey("resource.index"))); this.writer.write(Chunk.of("1", "2", "3")); this.writer.update(this.executionContext); - assertEquals(0, this.executionContext.getInt(this.writer.getExecutionContextKey("resource.item.count"))); + assertEquals(1, this.executionContext.getInt(this.writer.getExecutionContextKey("resource.item.count"))); assertEquals(2, this.executionContext.getInt(this.writer.getExecutionContextKey("resource.index"))); } @@ -160,26 +158,21 @@ void testRestart() throws Exception { this.writer.write(Chunk.of("1", "2", "3")); - File part1 = new File(this.file.getAbsolutePath() + this.suffixCreator.getSuffix(1)); - assertTrue(part1.exists()); - assertEquals("123", readFile(part1)); - - this.writer.write(Chunk.of("4")); - File part2 = new File(this.file.getAbsolutePath() + this.suffixCreator.getSuffix(2)); - assertTrue(part2.exists()); - assertEquals("4", readFile(part2)); + assertFileExistsAndContains(1, "12"); + assertFileExistsAndContains(2, "3"); this.writer.update(this.executionContext); this.writer.close(); this.writer.open(this.executionContext); - this.writer.write(Chunk.of("5")); - assertEquals("45", readFile(part2)); + this.writer.write(Chunk.of("4")); - this.writer.write(Chunk.of("6", "7", "8", "9")); - File part3 = new File(this.file.getAbsolutePath() + this.suffixCreator.getSuffix(3)); - assertTrue(part3.exists()); - assertEquals("6789", readFile(part3)); + assertFileExistsAndContains(2, "34"); + + this.writer.write(Chunk.of("5", "6", "7", "8")); + + assertFileExistsAndContains(3, "56"); + assertFileExistsAndContains(4, "78"); } @Test @@ -195,26 +188,23 @@ void testRestartNoSaveState() throws Exception { this.writer.write(Chunk.of("1", "2", "3")); - File part1 = new File(this.file.getAbsolutePath() + this.suffixCreator.getSuffix(1)); - assertTrue(part1.exists()); - assertEquals("123", readFile(part1)); - - this.writer.write(Chunk.of("4")); - File part2 = new File(this.file.getAbsolutePath() + this.suffixCreator.getSuffix(2)); - assertTrue(part2.exists()); - assertEquals("4", readFile(part2)); + assertFileExistsAndContains(1, "12"); + assertFileExistsAndContains(2, "3"); this.writer.update(this.executionContext); this.writer.close(); this.writer.open(this.executionContext); - this.writer.write(Chunk.of("5")); - assertEquals("4", readFile(part2)); + this.writer.write(Chunk.of("4")); - this.writer.write(Chunk.of("6", "7", "8", "9")); - File part3 = new File(this.file.getAbsolutePath() + this.suffixCreator.getSuffix(1)); - assertTrue(part3.exists()); - assertEquals("56789", readFile(part3)); + assertFileExistsAndContains(2, "3"); + assertFileExistsAndContains(1, "4"); + + this.writer.write(Chunk.of("5", "6", "7", "8")); + + assertFileExistsAndContains(1, "45"); + assertFileExistsAndContains(2, "67"); + assertFileExistsAndContains(3, "8"); } @Test @@ -265,4 +255,15 @@ private String readFile(File f) throws Exception { return result.toString(); } + private void assertFileExistsAndContains(int index, String expected) throws Exception { + assertFileExistsAndContains(index, expected, this.suffixCreator); + } + + private void assertFileExistsAndContains(int index, String expected, ResourceSuffixCreator suffixCreator) + throws Exception { + File part = new File(this.file.getAbsolutePath() + suffixCreator.getSuffix(index)); + assertTrue(part.exists()); + assertEquals(expected, readFile(part)); + } + } From bd8f9a8d7603fba5fcf5fc03252c13d095cec175 Mon Sep 17 00:00:00 2001 From: Elimelec Burghelea Date: Thu, 23 Jan 2025 01:59:10 +0200 Subject: [PATCH 055/266] Attempt to close all delegate writers even when some fail Issue #4750 Signed-off-by: Elimelec Burghelea --- .../item/support/CompositeItemWriter.java | 20 +++++++++- .../support/CompositeItemWriterTests.java | 39 ++++++++++++++++++- 2 files changed, 56 insertions(+), 3 deletions(-) diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/support/CompositeItemWriter.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/support/CompositeItemWriter.java index d76d5c33e0..8666112769 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/support/CompositeItemWriter.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/support/CompositeItemWriter.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2023 the original author or authors. + * Copyright 2006-2025 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. @@ -25,6 +25,7 @@ import org.springframework.beans.factory.InitializingBean; import org.springframework.util.Assert; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -37,6 +38,7 @@ * @author Robert Kasanicky * @author Dave Syer * @author Mahmoud Ben Hassine + * @author Elimelec Burghelea */ public class CompositeItemWriter implements ItemStreamWriter, InitializingBean { @@ -105,11 +107,25 @@ public void setDelegates(List> delegates) { @Override public void close() throws ItemStreamException { + List exceptions = new ArrayList<>(); + for (ItemWriter writer : delegates) { if (!ignoreItemStream && (writer instanceof ItemStream)) { - ((ItemStream) writer).close(); + try { + ((ItemStream) writer).close(); + } + catch (Exception e) { + exceptions.add(e); + } } } + + if (!exceptions.isEmpty()) { + String message = String.format("Failed to close %d delegate(s) due to exceptions", exceptions.size()); + ItemStreamException holder = new ItemStreamException(message); + exceptions.forEach(holder::addSuppressed); + throw holder; + } } @Override diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/support/CompositeItemWriterTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/support/CompositeItemWriterTests.java index 8d5d3f7b62..89db324007 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/support/CompositeItemWriterTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/support/CompositeItemWriterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2022 the original author or authors. + * Copyright 2008-2025 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. @@ -18,14 +18,18 @@ import java.util.ArrayList; import java.util.List; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.batch.item.Chunk; import org.springframework.batch.item.ExecutionContext; +import org.springframework.batch.item.ItemStreamException; import org.springframework.batch.item.ItemStreamWriter; import org.springframework.batch.item.ItemWriter; +import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; /** * Tests for {@link CompositeItemWriter} @@ -33,6 +37,7 @@ * @author Robert Kasanicky * @author Will Schipp * @author Mahmoud Ben Hassine + * @author Elimelec Burghelea */ class CompositeItemWriterTests { @@ -94,4 +99,36 @@ private void doTestItemStream(boolean expectOpen) throws Exception { itemWriter.write(data); } + @Test + void testCloseWithMultipleDelegate() { + AbstractFileItemWriter delegate1 = mock(); + AbstractFileItemWriter delegate2 = mock(); + CompositeItemWriter itemWriter = new CompositeItemWriter<>(List.of(delegate1, delegate2)); + + itemWriter.close(); + + verify(delegate1).close(); + verify(delegate2).close(); + } + + @Test + void testCloseWithMultipleDelegatesThatThrow() { + AbstractFileItemWriter delegate1 = mock(); + AbstractFileItemWriter delegate2 = mock(); + CompositeItemWriter itemWriter = new CompositeItemWriter<>(List.of(delegate1, delegate2)); + + doThrow(new ItemStreamException("A failure")).when(delegate1).close(); + + try { + itemWriter.close(); + Assertions.fail("Expected an ItemStreamException"); + } + catch (ItemStreamException ignored) { + + } + + verify(delegate1).close(); + verify(delegate2).close(); + } + } From 98a42288446a9fc97071b13218d1655da0b4f996 Mon Sep 17 00:00:00 2001 From: Stefano Cordio Date: Sun, 22 Dec 2024 10:41:49 +0100 Subject: [PATCH 056/266] Allow CI on any branch, skip CD on forks Signed-off-by: Stefano Cordio --- .github/workflows/continuous-integration.yml | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 37dc5a6925..9171cc9021 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -1,9 +1,6 @@ name: CI/CD build -on: - workflow_dispatch: - push: - branches: [ "main" ] +on: [push, pull_request, workflow_dispatch] jobs: build: @@ -20,7 +17,12 @@ jobs: distribution: 'temurin' cache: 'maven' + - name: Build with Maven + if: ${{ github.repository != 'spring-projects/spring-batch' || github.ref_name != 'main' }} + run: mvn -s settings.xml --batch-mode --update-snapshots verify + - name: Build with Maven and deploy to Artifactory + if: ${{ github.repository == 'spring-projects/spring-batch' && github.ref_name == 'main' }} env: ARTIFACTORY_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }} ARTIFACTORY_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }} @@ -37,6 +39,7 @@ jobs: run: echo PROJECT_VERSION=$(mvn help:evaluate -Dexpression=project.version --quiet -DforceStdout) >> $GITHUB_ENV - name: Setup SSH key + if: ${{ github.repository == 'spring-projects/spring-batch' && github.ref_name == 'main' }} env: DOCS_SSH_KEY: ${{ secrets.DOCS_SSH_KEY }} DOCS_SSH_HOST_KEY: ${{ secrets.DOCS_SSH_HOST_KEY }} @@ -47,6 +50,7 @@ jobs: echo "$DOCS_SSH_HOST_KEY" > "$HOME/.ssh/known_hosts" - name: Deploy Java docs + if: ${{ github.repository == 'spring-projects/spring-batch' && github.ref_name == 'main' }} env: DOCS_HOST: ${{ secrets.DOCS_HOST }} DOCS_PATH: ${{ secrets.DOCS_PATH }} From bbd46fb29d69026a60314ad44c8834462f2dad9c Mon Sep 17 00:00:00 2001 From: Henning Poettker Date: Tue, 7 Jan 2025 19:38:57 +0100 Subject: [PATCH 057/266] Polish integration tests In the Mongo integration tests, the application context and in particular the Mongo client are now closed after each test, respectively. Thread-safety of the fault-tolerance integration steps is improved as they are intermittingly stalling. --- .../MongoDBIntegrationTestConfiguration.java | 7 +- .../MongoDBJobExplorerIntegrationTests.java | 4 +- .../MongoDBJobRepositoryIntegrationTests.java | 4 +- ...goExecutionContextDaoIntegrationTests.java | 4 +- ...lerantStepFactoryBeanIntegrationTests.java | 103 ++++------------- ...epFactoryBeanRollbackIntegrationTests.java | 106 ++++-------------- 6 files changed, 55 insertions(+), 173 deletions(-) diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoDBIntegrationTestConfiguration.java b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoDBIntegrationTestConfiguration.java index 015a90e034..31ea7439dd 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoDBIntegrationTestConfiguration.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoDBIntegrationTestConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2024-2025 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. @@ -15,8 +15,6 @@ */ package org.springframework.batch.core.repository.support; -import com.mongodb.client.MongoClient; -import com.mongodb.client.MongoClients; import org.springframework.batch.core.Job; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; import org.springframework.batch.core.explore.JobExplorer; @@ -63,8 +61,7 @@ public JobExplorer jobExplorer(MongoTemplate mongoTemplate, MongoTransactionMana @Bean public MongoDatabaseFactory mongoDatabaseFactory(@Value("${mongo.connectionString}") String connectionString) { - MongoClient mongoClient = MongoClients.create(connectionString); - return new SimpleMongoClientDatabaseFactory(mongoClient, "test"); + return new SimpleMongoClientDatabaseFactory(connectionString + "/test"); } @Bean diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoDBJobExplorerIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoDBJobExplorerIntegrationTests.java index a6ed1c9bb9..f47c731990 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoDBJobExplorerIntegrationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoDBJobExplorerIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2024-2025 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. @@ -30,6 +30,7 @@ import org.springframework.batch.core.launch.JobLauncher; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.DynamicPropertyRegistry; import org.springframework.test.context.DynamicPropertySource; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; @@ -45,6 +46,7 @@ /** * @author Henning Pöttker */ +@DirtiesContext @Testcontainers(disabledWithoutDocker = true) @SpringJUnitConfig(MongoDBIntegrationTestConfiguration.class) public class MongoDBJobExplorerIntegrationTests { diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoDBJobRepositoryIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoDBJobRepositoryIntegrationTests.java index b45aa7bd19..6b70f0b3c7 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoDBJobRepositoryIntegrationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoDBJobRepositoryIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2024-2025 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. @@ -23,6 +23,7 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.DynamicPropertyRegistry; import org.springframework.test.context.DynamicPropertySource; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; @@ -43,6 +44,7 @@ /** * @author Mahmoud Ben Hassine */ +@DirtiesContext @Testcontainers(disabledWithoutDocker = true) @SpringJUnitConfig(MongoDBIntegrationTestConfiguration.class) public class MongoDBJobRepositoryIntegrationTests { diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoExecutionContextDaoIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoExecutionContextDaoIntegrationTests.java index 7b71ca8505..a04795928f 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoExecutionContextDaoIntegrationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoExecutionContextDaoIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2024-2025 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. @@ -36,6 +36,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.data.mongodb.core.MongoOperations; import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.DynamicPropertyRegistry; import org.springframework.test.context.DynamicPropertySource; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; @@ -51,6 +52,7 @@ /** * @author Henning Pöttker */ +@DirtiesContext @Testcontainers(disabledWithoutDocker = true) @SpringJUnitConfig({ MongoDBIntegrationTestConfiguration.class, ExecutionContextDaoConfiguration.class }) public class MongoExecutionContextDaoIntegrationTests { diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/test/step/FaultTolerantStepFactoryBeanIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/test/step/FaultTolerantStepFactoryBeanIntegrationTests.java index 67d24fcefa..2cf59d33c1 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/test/step/FaultTolerantStepFactoryBeanIntegrationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/test/step/FaultTolerantStepFactoryBeanIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2024 the original author or authors. + * Copyright 2010-2025 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. @@ -16,17 +16,14 @@ package org.springframework.batch.core.test.step; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.List; -import javax.sql.DataSource; - import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; import org.springframework.batch.core.BatchStatus; import org.springframework.batch.core.JobExecution; @@ -39,8 +36,8 @@ import org.springframework.batch.item.ItemProcessor; import org.springframework.batch.item.ItemReader; import org.springframework.batch.item.ItemWriter; -import org.springframework.batch.item.ParseException; -import org.springframework.batch.item.UnexpectedInputException; +import org.springframework.batch.item.support.ListItemReader; +import org.springframework.batch.item.support.SynchronizedItemReader; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.lang.Nullable; @@ -48,15 +45,14 @@ import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; import org.springframework.test.jdbc.JdbcTestUtils; import org.springframework.transaction.PlatformTransactionManager; -import org.springframework.util.Assert; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Timeout.ThreadMode.SEPARATE_THREAD; /** * Tests for {@link FaultTolerantStepFactoryBean}. */ @SpringJUnitConfig(locations = "/simple-job-launcher-context.xml") -@Disabled("Randomly failing/hanging") // FIXME This test is randomly failing/hanging class FaultTolerantStepFactoryBeanIntegrationTests { private static final int MAX_COUNT = 1000; @@ -69,12 +65,8 @@ class FaultTolerantStepFactoryBeanIntegrationTests { private SkipWriterStub writer; - private JobExecution jobExecution; - - private StepExecution stepExecution; - @Autowired - private DataSource dataSource; + private JdbcTemplate jdbcTemplate; @Autowired private JobRepository repository; @@ -85,8 +77,8 @@ class FaultTolerantStepFactoryBeanIntegrationTests { @BeforeEach void setUp() { - writer = new SkipWriterStub(dataSource); - processor = new SkipProcessorStub(dataSource); + writer = new SkipWriterStub(jdbcTemplate); + processor = new SkipProcessorStub(jdbcTemplate); factory = new FaultTolerantStepFactoryBean<>(); @@ -101,14 +93,12 @@ void setUp() { taskExecutor.afterPropertiesSet(); factory.setTaskExecutor(taskExecutor); - JdbcTestUtils.deleteFromTables(new JdbcTemplate(dataSource), "ERROR_LOG"); + JdbcTestUtils.deleteFromTables(jdbcTemplate, "ERROR_LOG"); } @Test - void testUpdatesNoRollback() throws Exception { - - JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); + void testUpdatesNoRollback() { writer.write(Chunk.of("foo", "bar")); processor.process("spam"); @@ -121,17 +111,15 @@ void testUpdatesNoRollback() throws Exception { } @Test + @Timeout(value = 30, threadMode = SEPARATE_THREAD) void testMultithreadedSunnyDay() throws Throwable { - jobExecution = repository.createJobExecution("vanillaJob", new JobParameters()); + JobExecution jobExecution = repository.createJobExecution("vanillaJob", new JobParameters()); for (int i = 0; i < MAX_COUNT; i++) { - JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); - - SkipReaderStub reader = new SkipReaderStub(); - reader.clear(); - reader.setItems("1", "2", "3", "4", "5"); + ItemReader reader = new SynchronizedItemReader<>( + new ListItemReader<>(List.of("1", "2", "3", "4", "5"))); factory.setItemReader(reader); writer.clear(); factory.setItemWriter(writer); @@ -144,7 +132,7 @@ void testMultithreadedSunnyDay() throws Throwable { Step step = factory.getObject(); - stepExecution = jobExecution.createStepExecution(factory.getName()); + StepExecution stepExecution = jobExecution.createStepExecution(factory.getName()); repository.add(stepExecution); step.execute(stepExecution); assertEquals(BatchStatus.COMPLETED, stepExecution.getStatus()); @@ -167,48 +155,12 @@ void testMultithreadedSunnyDay() throws Throwable { } - private static class SkipReaderStub implements ItemReader { - - private String[] items; - - private int counter = -1; - - public SkipReaderStub() throws Exception { - super(); - } - - public void setItems(String... items) { - Assert.isTrue(counter < 0, "Items cannot be set once reading has started"); - this.items = items; - } - - public void clear() { - counter = -1; - } - - @Nullable - @Override - public synchronized String read() throws Exception, UnexpectedInputException, ParseException { - counter++; - if (counter >= items.length) { - return null; - } - String item = items[counter]; - return item; - } - - } - private static class SkipWriterStub implements ItemWriter { - private final List written = new ArrayList<>(); - - private final Collection failures = Collections.emptySet(); - private final JdbcTemplate jdbcTemplate; - public SkipWriterStub(DataSource dataSource) { - jdbcTemplate = new JdbcTemplate(dataSource); + public SkipWriterStub(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; } public List getCommitted() { @@ -217,22 +169,13 @@ public List getCommitted() { } public void clear() { - written.clear(); JdbcTestUtils.deleteFromTableWhere(jdbcTemplate, "ERROR_LOG", "STEP_NAME='written'"); } @Override - public void write(Chunk items) throws Exception { + public void write(Chunk items) { for (String item : items) { - written.add(item); jdbcTemplate.update("INSERT INTO ERROR_LOG (MESSAGE, STEP_NAME) VALUES (?, ?)", item, "written"); - checkFailure(item); - } - } - - private void checkFailure(String item) { - if (failures.contains(item)) { - throw new RuntimeException("Planned failure"); } } @@ -242,12 +185,10 @@ private static class SkipProcessorStub implements ItemProcessor private final Log logger = LogFactory.getLog(getClass()); - private final List processed = new ArrayList<>(); - private final JdbcTemplate jdbcTemplate; - public SkipProcessorStub(DataSource dataSource) { - jdbcTemplate = new JdbcTemplate(dataSource); + public SkipProcessorStub(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; } public List getCommitted() { @@ -256,14 +197,12 @@ public List getCommitted() { } public void clear() { - processed.clear(); JdbcTestUtils.deleteFromTableWhere(jdbcTemplate, "ERROR_LOG", "STEP_NAME='processed'"); } @Nullable @Override - public String process(String item) throws Exception { - processed.add(item); + public String process(String item) { logger.debug("Processed item: " + item); jdbcTemplate.update("INSERT INTO ERROR_LOG (MESSAGE, STEP_NAME) VALUES (?, ?)", item, "processed"); return item; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/test/step/FaultTolerantStepFactoryBeanRollbackIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/test/step/FaultTolerantStepFactoryBeanRollbackIntegrationTests.java index 6eb416fa06..eeaeba2365 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/test/step/FaultTolerantStepFactoryBeanRollbackIntegrationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/test/step/FaultTolerantStepFactoryBeanRollbackIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2023 the original author or authors. + * Copyright 2010-2025 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,16 +19,15 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.CopyOnWriteArrayList; -import javax.sql.DataSource; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; import org.springframework.batch.core.BatchStatus; import org.springframework.batch.core.JobExecution; @@ -41,8 +40,8 @@ import org.springframework.batch.item.ItemProcessor; import org.springframework.batch.item.ItemReader; import org.springframework.batch.item.ItemWriter; -import org.springframework.batch.item.ParseException; -import org.springframework.batch.item.UnexpectedInputException; +import org.springframework.batch.item.support.ListItemReader; +import org.springframework.batch.item.support.SynchronizedItemReader; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.lang.Nullable; @@ -50,9 +49,9 @@ import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; import org.springframework.test.jdbc.JdbcTestUtils; import org.springframework.transaction.PlatformTransactionManager; -import org.springframework.util.Assert; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Timeout.ThreadMode.SEPARATE_THREAD; /** * Tests for {@link FaultTolerantStepFactoryBean}. @@ -70,12 +69,8 @@ class FaultTolerantStepFactoryBeanRollbackIntegrationTests { private SkipWriterStub writer; - private JobExecution jobExecution; - - private StepExecution stepExecution; - @Autowired - private DataSource dataSource; + private JdbcTemplate jdbcTemplate; @Autowired private JobRepository repository; @@ -86,8 +81,8 @@ class FaultTolerantStepFactoryBeanRollbackIntegrationTests { @BeforeEach void setUp() { - writer = new SkipWriterStub(dataSource); - processor = new SkipProcessorStub(dataSource); + writer = new SkipWriterStub(jdbcTemplate, "1", "2", "3", "4", "5"); + processor = new SkipProcessorStub(jdbcTemplate); factory = new FaultTolerantStepFactoryBean<>(); @@ -97,14 +92,12 @@ void setUp() { factory.setCommitInterval(3); factory.setSkipLimit(10); - JdbcTestUtils.deleteFromTables(new JdbcTemplate(dataSource), "ERROR_LOG"); + JdbcTestUtils.deleteFromTables(jdbcTemplate, "ERROR_LOG"); } @Test - void testUpdatesNoRollback() throws Exception { - - JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); + void testUpdatesNoRollback() { writer.write(Chunk.of("foo", "bar")); processor.process("spam"); @@ -117,6 +110,7 @@ void testUpdatesNoRollback() throws Exception { } @Test + @Timeout(value = 30, threadMode = SEPARATE_THREAD) void testMultithreadedSkipInWriter() throws Throwable { ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor(); @@ -126,11 +120,9 @@ void testMultithreadedSkipInWriter() throws Throwable { taskExecutor.afterPropertiesSet(); factory.setTaskExecutor(taskExecutor); - @SuppressWarnings("unchecked") - Map, Boolean> skippable = getExceptionMap(Exception.class); - factory.setSkippableExceptionClasses(skippable); + factory.setSkippableExceptionClasses(Map.of(Exception.class, true)); - jobExecution = repository.createJobExecution("skipJob", new JobParameters()); + JobExecution jobExecution = repository.createJobExecution("skipJob", new JobParameters()); for (int i = 0; i < MAX_COUNT; i++) { @@ -138,25 +130,21 @@ void testMultithreadedSkipInWriter() throws Throwable { logger.info("Starting step: " + i); } - JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); assertEquals(0, JdbcTestUtils.countRowsInTable(jdbcTemplate, "ERROR_LOG")); try { - SkipReaderStub reader = new SkipReaderStub(); - reader.clear(); - reader.setItems("1", "2", "3", "4", "5"); + ItemReader reader = new SynchronizedItemReader<>( + new ListItemReader<>(List.of("1", "2", "3", "4", "5"))); factory.setItemReader(reader); writer.clear(); factory.setItemWriter(writer); processor.clear(); factory.setItemProcessor(processor); - writer.setFailures("1", "2", "3", "4", "5"); - Step step = factory.getObject(); - stepExecution = jobExecution.createStepExecution(factory.getName()); + StepExecution stepExecution = jobExecution.createStepExecution(factory.getName()); repository.add(stepExecution); step.execute(stepExecution); assertEquals(BatchStatus.COMPLETED, stepExecution.getStatus()); @@ -178,61 +166,15 @@ void testMultithreadedSkipInWriter() throws Throwable { } - @SuppressWarnings("unchecked") - private Map, Boolean> getExceptionMap(Class... args) { - Map, Boolean> map = new HashMap<>(); - for (Class arg : args) { - map.put(arg, true); - } - return map; - } - - private static class SkipReaderStub implements ItemReader { - - private String[] items; - - private int counter = -1; - - public SkipReaderStub() throws Exception { - super(); - } - - public void setItems(String... items) { - Assert.isTrue(counter < 0, "Items cannot be set once reading has started"); - this.items = items; - } - - public void clear() { - counter = -1; - } - - @Nullable - @Override - public synchronized String read() throws Exception, UnexpectedInputException, ParseException { - counter++; - if (counter >= items.length) { - return null; - } - String item = items[counter]; - return item; - } - - } - private static class SkipWriterStub implements ItemWriter { - private final List written = new CopyOnWriteArrayList<>(); - - private Collection failures = Collections.emptySet(); + private final Collection failures; private final JdbcTemplate jdbcTemplate; - public SkipWriterStub(DataSource dataSource) { - jdbcTemplate = new JdbcTemplate(dataSource); - } - - public void setFailures(String... failures) { + public SkipWriterStub(JdbcTemplate jdbcTemplate, String... failures) { this.failures = Arrays.asList(failures); + this.jdbcTemplate = jdbcTemplate; } public List getCommitted() { @@ -241,14 +183,12 @@ public List getCommitted() { } public void clear() { - written.clear(); JdbcTestUtils.deleteFromTableWhere(jdbcTemplate, "ERROR_LOG", "STEP_NAME='written'"); } @Override - public void write(Chunk items) throws Exception { + public void write(Chunk items) { for (String item : items) { - written.add(item); jdbcTemplate.update("INSERT INTO ERROR_LOG (MESSAGE, STEP_NAME) VALUES (?, ?)", item, "written"); checkFailure(item); } @@ -270,8 +210,8 @@ private static class SkipProcessorStub implements ItemProcessor private final JdbcTemplate jdbcTemplate; - public SkipProcessorStub(DataSource dataSource) { - jdbcTemplate = new JdbcTemplate(dataSource); + public SkipProcessorStub(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; } /** @@ -293,7 +233,7 @@ public void clear() { @Nullable @Override - public String process(String item) throws Exception { + public String process(String item) { processed.add(item); logger.debug("Processed item: " + item); jdbcTemplate.update("INSERT INTO ERROR_LOG (MESSAGE, STEP_NAME) VALUES (?, ?)", item, "processed"); From b4835ef52ea5b628c2c8e83e91e66306ee3abff5 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Mon, 24 Feb 2025 08:39:36 +0100 Subject: [PATCH 058/266] Upgrade the job execution status when appropriate in MongoJobExecutionDao Before this commit, the mongo implementation of job execution DAO did not upgrade the status of the job execution when synchronizing the state with the database. This commit fixes the issue by upgrading the status when appropriate, similar to the jdbc implementation. Resolves #4760 --- .../core/repository/dao/MongoJobExecutionDao.java | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoJobExecutionDao.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoJobExecutionDao.java index 90d3326a9a..da1d81ff78 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoJobExecutionDao.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoJobExecutionDao.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2024-2025 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. @@ -26,7 +26,6 @@ import org.springframework.data.domain.Sort; import org.springframework.data.mongodb.core.MongoOperations; import org.springframework.data.mongodb.core.query.Query; -import org.springframework.data.mongodb.core.query.Update; import org.springframework.jdbc.support.incrementer.DataFieldMaxValueIncrementer; import static org.springframework.data.mongodb.core.query.Criteria.where; @@ -143,14 +142,13 @@ public JobExecution getJobExecution(Long executionId) { @Override public void synchronizeStatus(JobExecution jobExecution) { - Query query = query(where("jobExecutionId").is(jobExecution.getId())); - Update update = Update.update("status", jobExecution.getStatus()); + JobExecution currentJobExecution = getJobExecution(jobExecution.getId()); + if (currentJobExecution != null && currentJobExecution.getStatus().isGreaterThan(jobExecution.getStatus())) { + jobExecution.upgradeStatus(currentJobExecution.getStatus()); + } // TODO the contract mentions to update the version as well. Double check if this // is needed as the version is not used in the tests following the call sites of // synchronizeStatus - this.mongoOperations.updateFirst(query, update, - org.springframework.batch.core.repository.persistence.JobExecution.class, - JOB_EXECUTIONS_COLLECTION_NAME); } } From 982ccc1c1c7e62bdd2c922ebf80f9ce1f3aa4956 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Mon, 24 Feb 2025 09:42:37 +0100 Subject: [PATCH 059/266] Disable randomly failing/hanging test --- .../test/step/FaultTolerantStepFactoryBeanIntegrationTests.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/test/step/FaultTolerantStepFactoryBeanIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/test/step/FaultTolerantStepFactoryBeanIntegrationTests.java index 2cf59d33c1..704c3fc22c 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/test/step/FaultTolerantStepFactoryBeanIntegrationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/test/step/FaultTolerantStepFactoryBeanIntegrationTests.java @@ -22,6 +22,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; @@ -53,6 +54,7 @@ * Tests for {@link FaultTolerantStepFactoryBean}. */ @SpringJUnitConfig(locations = "/simple-job-launcher-context.xml") +@Disabled("Randomly failing/hanging") // FIXME This test is randomly failing/hanging class FaultTolerantStepFactoryBeanIntegrationTests { private static final int MAX_COUNT = 1000; From 1eac9e9feea36320a17140033c7a7c56024a6b11 Mon Sep 17 00:00:00 2001 From: Elimelec Burghelea Date: Thu, 20 Feb 2025 21:00:38 +0200 Subject: [PATCH 060/266] Attempt to close all delegate readers even when some fail Signed-off-by: Elimelec Burghelea --- .../item/support/CompositeItemReader.java | 20 +++++++++++-- .../support/CompositeItemReaderTests.java | 29 ++++++++++++++++++- 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/support/CompositeItemReader.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/support/CompositeItemReader.java index 06148a346c..8da25504fc 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/support/CompositeItemReader.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/support/CompositeItemReader.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2024-2025 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. @@ -15,6 +15,7 @@ */ package org.springframework.batch.item.support; +import java.util.ArrayList; import java.util.Iterator; import java.util.List; @@ -27,6 +28,7 @@ * implementation is not thread-safe. * * @author Mahmoud Ben Hassine + * @author Elimelec Burghelea * @param type of objects to read * @since 5.2 */ @@ -79,8 +81,22 @@ public void update(ExecutionContext executionContext) throws ItemStreamException @Override public void close() throws ItemStreamException { + List exceptions = new ArrayList<>(); + for (ItemStreamReader delegate : delegates) { - delegate.close(); + try { + delegate.close(); + } + catch (Exception e) { + exceptions.add(e); + } + } + + if (!exceptions.isEmpty()) { + String message = String.format("Failed to close %d delegate(s) due to exceptions", exceptions.size()); + ItemStreamException holder = new ItemStreamException(message); + exceptions.forEach(holder::addSuppressed); + throw holder; } } diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/support/CompositeItemReaderTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/support/CompositeItemReaderTests.java index 3775c4299c..70091a0afc 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/support/CompositeItemReaderTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/support/CompositeItemReaderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2024-2025 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. @@ -17,11 +17,14 @@ import java.util.Arrays; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.batch.item.ExecutionContext; +import org.springframework.batch.item.ItemStreamException; import org.springframework.batch.item.ItemStreamReader; +import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -32,6 +35,7 @@ * Test class for {@link CompositeItemReader}. * * @author Mahmoud Ben Hassine + * @author Elimelec Burghelea */ public class CompositeItemReaderTests { @@ -107,4 +111,27 @@ void testCompositeItemReaderClose() { verify(reader2).close(); } + @Test + void testCompositeItemReaderCloseWithDelegateThatThrowsException() { + // given + ItemStreamReader reader1 = mock(); + ItemStreamReader reader2 = mock(); + CompositeItemReader compositeItemReader = new CompositeItemReader<>(Arrays.asList(reader1, reader2)); + + doThrow(new ItemStreamException("A failure")).when(reader1).close(); + + // when + try { + compositeItemReader.close(); + Assertions.fail("Expected an ItemStreamException"); + } + catch (ItemStreamException ignored) { + + } + + // then + verify(reader1).close(); + verify(reader2).close(); + } + } \ No newline at end of file From f40ab202a0326348c98217260106964f25dd1c85 Mon Sep 17 00:00:00 2001 From: Elimelec Burghelea Date: Thu, 20 Feb 2025 21:52:35 +0200 Subject: [PATCH 061/266] Attempt to close all delegate stream readers even when some fail Signed-off-by: Elimelec Burghelea --- .../step/item/TaskletStepExceptionTests.java | 7 +-- .../core/step/tasklet/TaskletStepTests.java | 6 +-- .../item/support/CompositeItemStream.java | 23 +++++++-- .../support/CompositeItemStreamTests.java | 48 +++++++++++++++++-- 4 files changed, 69 insertions(+), 15 deletions(-) diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/TaskletStepExceptionTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/TaskletStepExceptionTests.java index 5bc1fc695f..d4a01e8fc9 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/TaskletStepExceptionTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/TaskletStepExceptionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2022 the original author or authors. + * Copyright 2008-2025 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. @@ -63,6 +63,7 @@ * @author David Turanski * @author Mahmoud Ben Hassine * @author Parikshit Dutta + * @author Elimelec Burghelea */ class TaskletStepExceptionTests { @@ -212,8 +213,8 @@ public void close() throws ItemStreamException { taskletStep.execute(stepExecution); assertEquals(FAILED, stepExecution.getStatus()); - assertTrue(stepExecution.getFailureExceptions().contains(taskletException)); - assertTrue(stepExecution.getFailureExceptions().contains(exception)); + assertEquals(stepExecution.getFailureExceptions().get(0), taskletException); + assertEquals(stepExecution.getFailureExceptions().get(1).getSuppressed()[0], exception); assertEquals(2, jobRepository.getUpdateCount()); } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/tasklet/TaskletStepTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/tasklet/TaskletStepTests.java index ef6e917b70..e429a30e42 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/tasklet/TaskletStepTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/tasklet/TaskletStepTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2023 the original author or authors. + * Copyright 2006-2025 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. @@ -761,7 +761,7 @@ public void close() throws ItemStreamException { Throwable ex = stepExecution.getFailureExceptions().get(0); // The original rollback was caused by this one: - assertEquals("Bar", ex.getMessage()); + assertEquals("Bar", ex.getSuppressed()[0].getMessage()); } @Test @@ -791,7 +791,7 @@ public void close() throws ItemStreamException { assertEquals("", msg); Throwable ex = stepExecution.getFailureExceptions().get(0); // The original rollback was caused by this one: - assertEquals("Bar", ex.getMessage()); + assertEquals("Bar", ex.getSuppressed()[0].getMessage()); } /** diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/support/CompositeItemStream.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/support/CompositeItemStream.java index e773bf8616..8cb2a1c83c 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/support/CompositeItemStream.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/support/CompositeItemStream.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2022 the original author or authors. + * Copyright 2006-2025 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. @@ -28,7 +28,7 @@ * * @author Dave Syer * @author Mahmoud Ben Hassine - * + * @author Elimelec Burghelea */ public class CompositeItemStream implements ItemStream { @@ -102,13 +102,26 @@ public void update(ExecutionContext executionContext) { /** * Broadcast the call to close. * @throws ItemStreamException thrown if one of the {@link ItemStream}s in the list - * fails to close. This is a sequential operation so all itemStreams in the list after - * the one that failed to close will remain open. + * fails to close. */ @Override public void close() throws ItemStreamException { + List exceptions = new ArrayList<>(); + for (ItemStream itemStream : streams) { - itemStream.close(); + try { + itemStream.close(); + } + catch (Exception e) { + exceptions.add(e); + } + } + + if (!exceptions.isEmpty()) { + String message = String.format("Failed to close %d delegate(s) due to exceptions", exceptions.size()); + ItemStreamException holder = new ItemStreamException(message); + exceptions.forEach(holder::addSuppressed); + throw holder; } } diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/support/CompositeItemStreamTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/support/CompositeItemStreamTests.java index 5f1be03821..3861ca0f8d 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/support/CompositeItemStreamTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/support/CompositeItemStreamTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2022 the original author or authors. + * Copyright 2006-2025 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. @@ -15,19 +15,25 @@ */ package org.springframework.batch.item.support; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + import java.util.ArrayList; import java.util.List; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import org.mockito.Mockito; import org.springframework.batch.item.ExecutionContext; import org.springframework.batch.item.ItemStream; +import org.springframework.batch.item.ItemStreamException; import org.springframework.batch.item.ItemStreamSupport; -import static org.junit.jupiter.api.Assertions.assertEquals; - /** * @author Dave Syer - * + * @author Elimelec Burghelea */ class CompositeItemStreamTests { @@ -90,6 +96,40 @@ public void close() { assertEquals(1, list.size()); } + @Test + void testClose2Delegates() { + ItemStream reader1 = Mockito.mock(ItemStream.class); + ItemStream reader2 = Mockito.mock(ItemStream.class); + manager.register(reader1); + manager.register(reader2); + + manager.close(); + + verify(reader1, times(1)).close(); + verify(reader2, times(1)).close(); + } + + @Test + void testClose2DelegatesThatThrowsException() { + ItemStream reader1 = Mockito.mock(ItemStream.class); + ItemStream reader2 = Mockito.mock(ItemStream.class); + manager.register(reader1); + manager.register(reader2); + + doThrow(new ItemStreamException("A failure")).when(reader1).close(); + + try { + manager.close(); + Assertions.fail("Expected an ItemStreamException"); + } + catch (ItemStreamException ignored) { + + } + + verify(reader1, times(1)).close(); + verify(reader2, times(1)).close(); + } + @Test void testCloseDoesNotUnregister() { manager.setStreams(new ItemStream[] { new ItemStreamSupport() { From 75edb29d28b4b852cf8e1edc6b987d4fc68a1cbf Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Mon, 24 Feb 2025 10:49:07 +0100 Subject: [PATCH 062/266] Update Javadocs about exception handling when closing composite streams Related to #4764 and #4750 --- .../batch/item/support/CompositeItemReader.java | 6 ++++++ .../batch/item/support/CompositeItemStream.java | 3 ++- .../batch/item/support/CompositeItemWriter.java | 6 ++++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/support/CompositeItemReader.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/support/CompositeItemReader.java index 8da25504fc..73a92aa57a 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/support/CompositeItemReader.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/support/CompositeItemReader.java @@ -79,6 +79,12 @@ public void update(ExecutionContext executionContext) throws ItemStreamException } } + /** + * Close all delegates. + * @throws ItemStreamException thrown if one of the delegates fails to close. Original + * exceptions thrown by delegates are added as suppressed exceptions into this one, in + * the same order as delegates were registered. + */ @Override public void close() throws ItemStreamException { List exceptions = new ArrayList<>(); diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/support/CompositeItemStream.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/support/CompositeItemStream.java index 8cb2a1c83c..82f55750e8 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/support/CompositeItemStream.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/support/CompositeItemStream.java @@ -102,7 +102,8 @@ public void update(ExecutionContext executionContext) { /** * Broadcast the call to close. * @throws ItemStreamException thrown if one of the {@link ItemStream}s in the list - * fails to close. + * fails to close. Original exceptions thrown by delegates are added as suppressed + * exceptions into this one, in the same order as delegates were registered. */ @Override public void close() throws ItemStreamException { diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/support/CompositeItemWriter.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/support/CompositeItemWriter.java index 8666112769..730213c965 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/support/CompositeItemWriter.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/support/CompositeItemWriter.java @@ -105,6 +105,12 @@ public void setDelegates(List> delegates) { this.delegates = delegates; } + /** + * Close all delegates. + * @throws ItemStreamException thrown if one of the delegates fails to close. Original + * exceptions thrown by delegates are added as suppressed exceptions into this one, in + * the same order as delegates were registered. + */ @Override public void close() throws ItemStreamException { List exceptions = new ArrayList<>(); From 6701606f68997a7f7c18d8c1f31bd1428c37d626 Mon Sep 17 00:00:00 2001 From: charlie881007 <65711157+charlie881007@users.noreply.github.com> Date: Mon, 24 Feb 2025 10:49:19 +0800 Subject: [PATCH 063/266] fix typo Signed-off-by: charlie881007 <65711157+charlie881007@users.noreply.github.com> --- .../step/chunk-oriented-processing/intercepting-execution.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/intercepting-execution.adoc b/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/intercepting-execution.adoc index 023dcaee4f..d07884516a 100644 --- a/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/intercepting-execution.adoc +++ b/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/intercepting-execution.adoc @@ -89,7 +89,7 @@ public interface StepExecutionListener extends StepListener { } ---- -`ExitStatus` has a return type of `afterStep`, to give listeners the chance to +`afterStep` has a return type of `ExitStatus`, to give listeners the chance to modify the exit code that is returned upon completion of a `Step`. The annotations corresponding to this interface are: From e366dcb1978652437ff0480fcb9eeff3eea8b7ce Mon Sep 17 00:00:00 2001 From: Stefano Cordio Date: Fri, 3 Jan 2025 22:33:56 +0100 Subject: [PATCH 064/266] Honor `@NestedTestConfiguration` semantic in `BatchTestContextCustomizerFactory` Signed-off-by: Stefano Cordio --- .../BatchTestContextCustomizerFactory.java | 10 +-- ...atchTestContextCustomizerFactoryTests.java | 23 ++++-- .../SpringBatchTestIntegrationTests.java | 70 +++++++++++++++++++ 3 files changed, 92 insertions(+), 11 deletions(-) create mode 100644 spring-batch-test/src/test/java/org/springframework/batch/test/context/SpringBatchTestIntegrationTests.java diff --git a/spring-batch-test/src/main/java/org/springframework/batch/test/context/BatchTestContextCustomizerFactory.java b/spring-batch-test/src/main/java/org/springframework/batch/test/context/BatchTestContextCustomizerFactory.java index 12625b0dea..3c4888d7a1 100644 --- a/spring-batch-test/src/main/java/org/springframework/batch/test/context/BatchTestContextCustomizerFactory.java +++ b/spring-batch-test/src/main/java/org/springframework/batch/test/context/BatchTestContextCustomizerFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 the original author or authors. + * Copyright 2018-2025 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. @@ -17,23 +17,25 @@ import java.util.List; -import org.springframework.core.annotation.AnnotatedElementUtils; +import org.springframework.lang.Nullable; import org.springframework.test.context.ContextConfigurationAttributes; import org.springframework.test.context.ContextCustomizer; import org.springframework.test.context.ContextCustomizerFactory; +import org.springframework.test.context.TestContextAnnotationUtils; /** * Factory for {@link BatchTestContextCustomizer}. * * @author Mahmoud Ben Hassine + * @author Stefano Cordio * @since 4.1 */ public class BatchTestContextCustomizerFactory implements ContextCustomizerFactory { @Override - public ContextCustomizer createContextCustomizer(Class testClass, + public @Nullable ContextCustomizer createContextCustomizer(Class testClass, List configAttributes) { - if (AnnotatedElementUtils.hasAnnotation(testClass, SpringBatchTest.class)) { + if (TestContextAnnotationUtils.hasAnnotation(testClass, SpringBatchTest.class)) { return new BatchTestContextCustomizer(); } return null; diff --git a/spring-batch-test/src/test/java/org/springframework/batch/test/context/BatchTestContextCustomizerFactoryTests.java b/spring-batch-test/src/test/java/org/springframework/batch/test/context/BatchTestContextCustomizerFactoryTests.java index 4c693d05ec..7d393fde47 100644 --- a/spring-batch-test/src/test/java/org/springframework/batch/test/context/BatchTestContextCustomizerFactoryTests.java +++ b/spring-batch-test/src/test/java/org/springframework/batch/test/context/BatchTestContextCustomizerFactoryTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2018-2022 the original author or authors. + * Copyright 2018-2025 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. @@ -18,38 +18,42 @@ import java.util.Collections; import java.util.List; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import org.springframework.test.context.ContextConfigurationAttributes; import org.springframework.test.context.ContextCustomizer; -import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNull; /** * @author Mahmoud Ben Hassine + * @author Stefano Cordio */ class BatchTestContextCustomizerFactoryTests { private final BatchTestContextCustomizerFactory factory = new BatchTestContextCustomizerFactory(); - @Test - void testCreateContextCustomizer_whenAnnotationIsPresent() { + @ParameterizedTest + @ValueSource(classes = { MyJobTest.class, MyJobTest.MyNestedTest.class }) + void testCreateContextCustomizer_whenAnnotationIsPresent(Class testClass) { // given - Class testClass = MyJobTest.class; List configAttributes = Collections.emptyList(); // when ContextCustomizer contextCustomizer = this.factory.createContextCustomizer(testClass, configAttributes); // then - assertNotNull(contextCustomizer); + assertInstanceOf(BatchTestContextCustomizer.class, contextCustomizer); } @Test void testCreateContextCustomizer_whenAnnotationIsAbsent() { // given - Class testClass = MyOtherJobTest.class; + Class testClass = MyOtherJobTest.class; List configAttributes = Collections.emptyList(); // when @@ -62,6 +66,11 @@ void testCreateContextCustomizer_whenAnnotationIsAbsent() { @SpringBatchTest private static class MyJobTest { + @Nested + class MyNestedTest { + + } + } private static class MyOtherJobTest { diff --git a/spring-batch-test/src/test/java/org/springframework/batch/test/context/SpringBatchTestIntegrationTests.java b/spring-batch-test/src/test/java/org/springframework/batch/test/context/SpringBatchTestIntegrationTests.java new file mode 100644 index 0000000000..b48c39214d --- /dev/null +++ b/spring-batch-test/src/test/java/org/springframework/batch/test/context/SpringBatchTestIntegrationTests.java @@ -0,0 +1,70 @@ +/* + * Copyright 2025 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.test.context; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.batch.test.JobLauncherTestUtils; +import org.springframework.batch.test.JobRepositoryTestUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertSame; + +/** + * @author Stefano Cordio + */ +@SpringJUnitConfig +@SpringBatchTest +class SpringBatchTestIntegrationTests { + + @Autowired + ApplicationContext context; + + @Nested + class InnerWithoutSpringBatchTest { + + @Autowired + ApplicationContext context; + + @Test + void test() { + assertSame(SpringBatchTestIntegrationTests.this.context, context); + assertNotNull(context.getBean(JobLauncherTestUtils.class)); + assertNotNull(context.getBean(JobRepositoryTestUtils.class)); + } + + } + + @Nested + @SpringBatchTest + class InnerWithSpringBatchTest { + + @Autowired + ApplicationContext context; + + @Test + void test() { + assertSame(SpringBatchTestIntegrationTests.this.context, context); + assertNotNull(context.getBean(JobLauncherTestUtils.class)); + assertNotNull(context.getBean(JobRepositoryTestUtils.class)); + } + + } + +} From 9fbdf1ed827c880e046432e4e6bbd555bf1cf175 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Tue, 25 Feb 2025 14:45:37 +0100 Subject: [PATCH 065/266] Revert thread mode in FaultTolerantStepFactoryBeanRollbackIntegrationTests --- .../FaultTolerantStepFactoryBeanRollbackIntegrationTests.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/test/step/FaultTolerantStepFactoryBeanRollbackIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/test/step/FaultTolerantStepFactoryBeanRollbackIntegrationTests.java index eeaeba2365..e17332dac9 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/test/step/FaultTolerantStepFactoryBeanRollbackIntegrationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/test/step/FaultTolerantStepFactoryBeanRollbackIntegrationTests.java @@ -51,7 +51,6 @@ import org.springframework.transaction.PlatformTransactionManager; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Timeout.ThreadMode.SEPARATE_THREAD; /** * Tests for {@link FaultTolerantStepFactoryBean}. @@ -110,7 +109,7 @@ void testUpdatesNoRollback() { } @Test - @Timeout(value = 30, threadMode = SEPARATE_THREAD) + @Timeout(value = 30) void testMultithreadedSkipInWriter() throws Throwable { ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor(); From a79b6f75500c669293e1eb7b8066b5100d3656bb Mon Sep 17 00:00:00 2001 From: yoseplee Date: Wed, 5 Feb 2025 20:09:35 +0900 Subject: [PATCH 066/266] Fix index creation statements in MongoDB DDL script Signed-off-by: yoseplee --- .../batch/core/schema-mongodb.js | 12 +++++------ .../MongoDBJobRepositoryIntegrationTests.java | 21 +++++++++++++++++++ 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-mongodb.js b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-mongodb.js index e3a971ad8a..eb10033e8c 100644 --- a/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-mongodb.js +++ b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-mongodb.js @@ -10,9 +10,9 @@ db.getCollection("BATCH_SEQUENCES").insertOne({_id: "BATCH_JOB_EXECUTION_SEQ", c db.getCollection("BATCH_SEQUENCES").insertOne({_id: "BATCH_STEP_EXECUTION_SEQ", count: Long(0)}); // INDICES -db.getCollection("BATCH_JOB_INSTANCE").createIndex("job_name_idx", {"jobName": 1}, {}); -db.getCollection("BATCH_JOB_INSTANCE").createIndex("job_name_key_idx", {"jobName": 1, "jobKey": 1}, {}); -db.getCollection("BATCH_JOB_INSTANCE").createIndex("job_instance_idx", {"jobInstanceId": -1}, {}); -db.getCollection("BATCH_JOB_EXECUTION").createIndex("job_instance_idx", {"jobInstanceId": 1}, {}); -db.getCollection("BATCH_JOB_EXECUTION").createIndex("job_instance_idx", {"jobInstanceId": 1, "status": 1}, {}); -db.getCollection("BATCH_STEP_EXECUTION").createIndex("step_execution_idx", {"stepExecutionId": 1}, {}); +db.getCollection("BATCH_JOB_INSTANCE").createIndex( {"jobName": 1}, {"name": "job_name_idx"}); +db.getCollection("BATCH_JOB_INSTANCE").createIndex( {"jobName": 1, "jobKey": 1}, {"name": "job_name_key_idx"}); +db.getCollection("BATCH_JOB_INSTANCE").createIndex( {"jobInstanceId": -1}, {"name": "job_instance_idx"}); +db.getCollection("BATCH_JOB_EXECUTION").createIndex( {"jobInstanceId": 1}, {"name": "job_instance_idx"}); +db.getCollection("BATCH_JOB_EXECUTION").createIndex( {"jobInstanceId": 1, "status": 1}, {"name": "job_instance_status_idx"}); +db.getCollection("BATCH_STEP_EXECUTION").createIndex( {"stepExecutionId": 1}, {"name": "step_execution_idx"}); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoDBJobRepositoryIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoDBJobRepositoryIntegrationTests.java index 6b70f0b3c7..b70b80281c 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoDBJobRepositoryIntegrationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoDBJobRepositoryIntegrationTests.java @@ -24,6 +24,8 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.test.annotation.DirtiesContext; +import org.springframework.data.domain.Sort; +import org.springframework.data.mongodb.core.index.Index; import org.springframework.test.context.DynamicPropertyRegistry; import org.springframework.test.context.DynamicPropertySource; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; @@ -64,9 +66,11 @@ static void setMongoDbConnectionString(DynamicPropertyRegistry registry) { @BeforeEach public void setUp() { + // collections mongoTemplate.createCollection("BATCH_JOB_INSTANCE"); mongoTemplate.createCollection("BATCH_JOB_EXECUTION"); mongoTemplate.createCollection("BATCH_STEP_EXECUTION"); + // sequences mongoTemplate.createCollection("BATCH_SEQUENCES"); mongoTemplate.getCollection("BATCH_SEQUENCES") .insertOne(new Document(Map.of("_id", "BATCH_JOB_INSTANCE_SEQ", "count", 0L))); @@ -74,6 +78,23 @@ public void setUp() { .insertOne(new Document(Map.of("_id", "BATCH_JOB_EXECUTION_SEQ", "count", 0L))); mongoTemplate.getCollection("BATCH_SEQUENCES") .insertOne(new Document(Map.of("_id", "BATCH_STEP_EXECUTION_SEQ", "count", 0L))); + // indices + mongoTemplate.indexOps("BATCH_JOB_INSTANCE") + .ensureIndex(new Index().on("jobName", Sort.Direction.ASC).named("job_name_idx")); + mongoTemplate.indexOps("BATCH_JOB_INSTANCE") + .ensureIndex(new Index().on("jobName", Sort.Direction.ASC) + .on("jobKey", Sort.Direction.ASC) + .named("job_name_key_idx")); + mongoTemplate.indexOps("BATCH_JOB_INSTANCE") + .ensureIndex(new Index().on("jobInstanceId", Sort.Direction.DESC).named("job_instance_idx")); + mongoTemplate.indexOps("BATCH_JOB_EXECUTION") + .ensureIndex(new Index().on("jobInstanceId", Sort.Direction.ASC).named("job_instance_idx")); + mongoTemplate.indexOps("BATCH_JOB_EXECUTION") + .ensureIndex(new Index().on("jobInstanceId", Sort.Direction.ASC) + .on("status", Sort.Direction.ASC) + .named("job_instance_status_idx")); + mongoTemplate.indexOps("BATCH_STEP_EXECUTION") + .ensureIndex(new Index().on("stepExecutionId", Sort.Direction.ASC).named("step_execution_idx")); } @Test From 42b1464a0994460112c854c4f93903f59c2a9bc9 Mon Sep 17 00:00:00 2001 From: HeoSeokMun Date: Sun, 27 Oct 2024 15:55:22 +0900 Subject: [PATCH 067/266] Fix dirty flag on ExecutionContext Before this commit, the dirty flag was reset on any put operation, even those that replace existing keys with the same values. This commit fixes the dirty flag to be cleared only by meaningful put operations. Resolves #4685 Resolves #4692 --- .../batch/item/ExecutionContext.java | 13 ++++++++----- .../batch/item/ExecutionContextTests.java | 17 ++++++++++++++--- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/ExecutionContext.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/ExecutionContext.java index 71e56ce4d5..8f000c4656 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/ExecutionContext.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/ExecutionContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2023 the original author or authors. + * Copyright 2006-2025 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. @@ -37,6 +37,7 @@ * @author Lucas Ward * @author Douglas Kaminsky * @author Mahmoud Ben Hassine + * @author Seokmun Heo */ public class ExecutionContext implements Serializable { @@ -124,19 +125,21 @@ public void putDouble(String key, double value) { public void put(String key, @Nullable Object value) { if (value != null) { Object result = this.map.put(key, value); - this.dirty = result == null || !result.equals(value); + this.dirty = this.dirty || result == null || !result.equals(value); } else { Object result = this.map.remove(key); - this.dirty = result != null; + this.dirty = this.dirty || result != null; } } /** * Indicates if context has been changed with a "put" operation since the dirty flag * was last cleared. Note that the last time the flag was cleared might correspond to - * creation of the context. - * @return True if "put" operation has occurred since flag was last cleared + * creation of the context. A context is only dirty if a new value is put or an old + * one is removed. + * @return True if a new value was put or an old one was removed since the last time + * the flag was cleared */ public boolean isDirty() { return this.dirty; diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/ExecutionContextTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/ExecutionContextTests.java index 96e19dfc43..581369b822 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/ExecutionContextTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/ExecutionContextTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2024 the original author or authors. + * Copyright 2006-2025 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. @@ -36,7 +36,7 @@ /** * @author Lucas Ward * @author Mahmoud Ben Hassine - * + * @author Seokmun Heo */ class ExecutionContextTests { @@ -94,11 +94,13 @@ void testNotDirtyWithDuplicate() { } @Test - void testNotDirtyWithRemoveMissing() { + void testDirtyWithRemoveMissing() { context.putString("1", "test"); assertTrue(context.isDirty()); context.putString("1", null); // remove an item that was present assertTrue(context.isDirty()); + + context.clearDirtyFlag(); context.putString("1", null); // remove a non-existent item assertFalse(context.isDirty()); } @@ -167,6 +169,15 @@ void testCopyConstructorNullInput() { assertTrue(context.isEmpty()); } + @Test + void testDirtyWithDuplicate() { + ExecutionContext context = new ExecutionContext(); + context.put("1", "testString1"); + assertTrue(context.isDirty()); + context.put("1", "testString1"); // put the same value + assertTrue(context.isDirty()); + } + /** * Value object for testing serialization */ From e110b359ef01cb297257ba72cf57eaa59efee772 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Tue, 18 Mar 2025 09:15:44 +0100 Subject: [PATCH 068/266] Add AOT runtime hints for infrastructure artifacts This commit adds AOT runtime hints for common classes and interfaces of the infrastructure module. Technology specific APIs (mongodb, kafka, redis, etc) are not included on purpose and are left for users when needed. Resolves #4785 --- .../aot/InfrastructureRuntimeHints.java | 104 ++++++++++++++++++ .../resources/META-INF/spring/aot.factories | 1 + 2 files changed, 105 insertions(+) create mode 100644 spring-batch-infrastructure/src/main/java/org/springframework/batch/infrastructure/aot/InfrastructureRuntimeHints.java create mode 100644 spring-batch-infrastructure/src/main/resources/META-INF/spring/aot.factories diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/infrastructure/aot/InfrastructureRuntimeHints.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/infrastructure/aot/InfrastructureRuntimeHints.java new file mode 100644 index 0000000000..8d67cefb0f --- /dev/null +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/infrastructure/aot/InfrastructureRuntimeHints.java @@ -0,0 +1,104 @@ +/* + * Copyright 2025 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.infrastructure.aot; + +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; +import org.springframework.batch.item.ItemStreamSupport; +import org.springframework.batch.item.amqp.AmqpItemReader; +import org.springframework.batch.item.amqp.AmqpItemWriter; +import org.springframework.batch.item.amqp.builder.AmqpItemReaderBuilder; +import org.springframework.batch.item.amqp.builder.AmqpItemWriterBuilder; +import org.springframework.batch.item.database.JdbcBatchItemWriter; +import org.springframework.batch.item.database.JdbcCursorItemReader; +import org.springframework.batch.item.database.JdbcPagingItemReader; +import org.springframework.batch.item.database.JpaCursorItemReader; +import org.springframework.batch.item.database.JpaItemWriter; +import org.springframework.batch.item.database.JpaPagingItemReader; +import org.springframework.batch.item.database.builder.JdbcBatchItemWriterBuilder; +import org.springframework.batch.item.database.builder.JdbcCursorItemReaderBuilder; +import org.springframework.batch.item.database.builder.JdbcPagingItemReaderBuilder; +import org.springframework.batch.item.database.builder.JpaCursorItemReaderBuilder; +import org.springframework.batch.item.database.builder.JpaItemWriterBuilder; +import org.springframework.batch.item.database.builder.JpaPagingItemReaderBuilder; +import org.springframework.batch.item.file.FlatFileItemReader; +import org.springframework.batch.item.file.FlatFileItemWriter; +import org.springframework.batch.item.file.builder.FlatFileItemReaderBuilder; +import org.springframework.batch.item.file.builder.FlatFileItemWriterBuilder; +import org.springframework.batch.item.jms.JmsItemReader; +import org.springframework.batch.item.jms.JmsItemWriter; +import org.springframework.batch.item.jms.builder.JmsItemReaderBuilder; +import org.springframework.batch.item.jms.builder.JmsItemWriterBuilder; +import org.springframework.batch.item.json.JsonFileItemWriter; +import org.springframework.batch.item.json.JsonItemReader; +import org.springframework.batch.item.json.builder.JsonFileItemWriterBuilder; +import org.springframework.batch.item.json.builder.JsonItemReaderBuilder; +import org.springframework.batch.item.queue.BlockingQueueItemReader; +import org.springframework.batch.item.queue.BlockingQueueItemWriter; +import org.springframework.batch.item.queue.builder.BlockingQueueItemReaderBuilder; +import org.springframework.batch.item.queue.builder.BlockingQueueItemWriterBuilder; +import org.springframework.batch.item.support.AbstractFileItemWriter; +import org.springframework.batch.item.support.AbstractItemCountingItemStreamItemReader; +import org.springframework.batch.item.support.AbstractItemStreamItemReader; +import org.springframework.batch.item.support.AbstractItemStreamItemWriter; +import org.springframework.batch.item.xml.StaxEventItemReader; +import org.springframework.batch.item.xml.StaxEventItemWriter; +import org.springframework.batch.item.xml.builder.StaxEventItemReaderBuilder; +import org.springframework.batch.item.xml.builder.StaxEventItemWriterBuilder; + +import java.util.Set; + +/** + * {@link RuntimeHintsRegistrar} for Spring Batch infrastructure module. + * + * @author Mahmoud Ben Hassine + * @since 5.2.2 + */ +public class InfrastructureRuntimeHints implements RuntimeHintsRegistrar { + + @Override + public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + // reflection hints + Set> classes = Set.of( + // File IO APIs + FlatFileItemReader.class, FlatFileItemReaderBuilder.class, FlatFileItemWriter.class, + FlatFileItemWriterBuilder.class, JsonItemReader.class, JsonItemReaderBuilder.class, + JsonFileItemWriter.class, JsonFileItemWriterBuilder.class, StaxEventItemReader.class, + StaxEventItemReaderBuilder.class, StaxEventItemWriter.class, StaxEventItemWriterBuilder.class, + + // Database IO APIs + JdbcCursorItemReader.class, JdbcCursorItemReaderBuilder.class, JdbcPagingItemReader.class, + JdbcPagingItemReaderBuilder.class, JdbcBatchItemWriter.class, JdbcBatchItemWriterBuilder.class, + JpaCursorItemReader.class, JpaCursorItemReaderBuilder.class, JpaPagingItemReader.class, + JpaPagingItemReaderBuilder.class, JpaItemWriter.class, JpaItemWriterBuilder.class, + + // Queue IO APIs + BlockingQueueItemReader.class, BlockingQueueItemReaderBuilder.class, BlockingQueueItemWriter.class, + BlockingQueueItemWriterBuilder.class, JmsItemReader.class, JmsItemReaderBuilder.class, + JmsItemWriter.class, JmsItemWriterBuilder.class, AmqpItemReader.class, AmqpItemReaderBuilder.class, + AmqpItemWriter.class, AmqpItemWriterBuilder.class, + + // Support classes + AbstractFileItemWriter.class, AbstractItemStreamItemWriter.class, + AbstractItemCountingItemStreamItemReader.class, AbstractItemStreamItemReader.class, + ItemStreamSupport.class); + for (Class type : classes) { + hints.reflection().registerType(type, MemberCategory.values()); + } + } + +} diff --git a/spring-batch-infrastructure/src/main/resources/META-INF/spring/aot.factories b/spring-batch-infrastructure/src/main/resources/META-INF/spring/aot.factories new file mode 100644 index 0000000000..efa2f70c11 --- /dev/null +++ b/spring-batch-infrastructure/src/main/resources/META-INF/spring/aot.factories @@ -0,0 +1 @@ +org.springframework.aot.hint.RuntimeHintsRegistrar=org.springframework.batch.infrastructure.aot.InfrastructureRuntimeHints From 618e03b00bbe74092cd9ee4975d7b666b5435937 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Tue, 18 Mar 2025 09:20:24 +0100 Subject: [PATCH 069/266] Fix incorrect test class name This test class is about the composite item reader and not the composite item writer. --- ...Tests.java => CompositeItemReaderSampleFunctionalTests.java} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename spring-batch-samples/src/test/java/org/springframework/batch/samples/compositereader/{CompositeItemWriterSampleFunctionalTests.java => CompositeItemReaderSampleFunctionalTests.java} (99%) diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/samples/compositereader/CompositeItemWriterSampleFunctionalTests.java b/spring-batch-samples/src/test/java/org/springframework/batch/samples/compositereader/CompositeItemReaderSampleFunctionalTests.java similarity index 99% rename from spring-batch-samples/src/test/java/org/springframework/batch/samples/compositereader/CompositeItemWriterSampleFunctionalTests.java rename to spring-batch-samples/src/test/java/org/springframework/batch/samples/compositereader/CompositeItemReaderSampleFunctionalTests.java index 8c90257b6e..03db277a99 100644 --- a/spring-batch-samples/src/test/java/org/springframework/batch/samples/compositereader/CompositeItemWriterSampleFunctionalTests.java +++ b/spring-batch-samples/src/test/java/org/springframework/batch/samples/compositereader/CompositeItemReaderSampleFunctionalTests.java @@ -50,7 +50,7 @@ import org.springframework.jdbc.support.JdbcTransactionManager; import org.springframework.test.jdbc.JdbcTestUtils; -public class CompositeItemWriterSampleFunctionalTests { +public class CompositeItemReaderSampleFunctionalTests { record Person(int id, String name) { } From 60f83438e95901f7861c9eb139d8e39384ac37f9 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Tue, 18 Mar 2025 09:47:29 +0100 Subject: [PATCH 070/266] Disable test failing on CI but not locally --- .../file/multiresource/MultiResourceFunctionalTests.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/samples/file/multiresource/MultiResourceFunctionalTests.java b/spring-batch-samples/src/test/java/org/springframework/batch/samples/file/multiresource/MultiResourceFunctionalTests.java index 209ac5ce39..b7522968b5 100644 --- a/spring-batch-samples/src/test/java/org/springframework/batch/samples/file/multiresource/MultiResourceFunctionalTests.java +++ b/spring-batch-samples/src/test/java/org/springframework/batch/samples/file/multiresource/MultiResourceFunctionalTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2023 the original author or authors. + * Copyright 2006-2025 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. @@ -16,6 +16,7 @@ package org.springframework.batch.samples.file.multiresource; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.batch.core.BatchStatus; @@ -38,6 +39,7 @@ * @author Mahmoud Ben Hassine * @since 2.0 */ +@Disabled("Failing on the CI platform but not locally") @SpringJUnitConfig(locations = { "/org/springframework/batch/samples/file/multiresource/job/multiResource.xml", "/simple-job-launcher-context.xml" }) class MultiResourceFunctionalTests { From 42056e714d4346bd9a10f2da7f426c7de0d892e4 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Tue, 18 Mar 2025 10:38:05 +0100 Subject: [PATCH 071/266] Update version of IBM DB2 docker image and jdbc driver --- pom.xml | 2 +- .../test/repository/Db2JobRepositoryIntegrationTests.java | 4 ++-- .../support/Db2PagingQueryProviderIntegrationTests.java | 5 +++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index eb01fbdcc5..4aa29bb280 100644 --- a/pom.xml +++ b/pom.xml @@ -123,7 +123,7 @@ 9.1.0 3.5.1 42.7.4 - 11.5.9.0 + 12.1.0.0 19.24.0.0 11.2.3.jre17 1.3.1 diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/test/repository/Db2JobRepositoryIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/test/repository/Db2JobRepositoryIntegrationTests.java index 22b6d109bb..4f7e9041c1 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/test/repository/Db2JobRepositoryIntegrationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/test/repository/Db2JobRepositoryIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2023 the original author or authors. + * Copyright 2020-2025 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. @@ -57,7 +57,7 @@ class Db2JobRepositoryIntegrationTests { // TODO find the best way to externalize and manage image versions - private static final DockerImageName DB2_IMAGE = DockerImageName.parse("ibmcom/db2:11.5.5.1"); + private static final DockerImageName DB2_IMAGE = DockerImageName.parse("icr.io/db2_community/db2:11.5.9.0"); @Container public static Db2Container db2 = new Db2Container(DB2_IMAGE).acceptLicense(); diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/Db2PagingQueryProviderIntegrationTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/Db2PagingQueryProviderIntegrationTests.java index 19d876b9d1..18353345b6 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/Db2PagingQueryProviderIntegrationTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/Db2PagingQueryProviderIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2024-2025 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. @@ -32,6 +32,7 @@ /** * @author Henning Pöttker + * @author Mahmoud Ben Hassine */ @Testcontainers(disabledWithoutDocker = true) @SpringJUnitConfig @@ -39,7 +40,7 @@ class Db2PagingQueryProviderIntegrationTests extends AbstractPagingQueryProviderIntegrationTests { // TODO find the best way to externalize and manage image versions - private static final DockerImageName DB2_IMAGE = DockerImageName.parse("ibmcom/db2:11.5.5.1"); + private static final DockerImageName DB2_IMAGE = DockerImageName.parse("icr.io/db2_community/db2:11.5.9.0"); @Container public static Db2Container db2 = new Db2Container(DB2_IMAGE).acceptLicense(); From 4c9b88eb528eace294ac640cb709154a433c82e3 Mon Sep 17 00:00:00 2001 From: KyeongHoon Lee Date: Thu, 9 Jan 2025 13:00:42 +0900 Subject: [PATCH 072/266] Add FunctionalInterface annotation to ChunkProcessor Signed-off-by: KyeongHoon Lee --- .../springframework/batch/core/step/item/ChunkProcessor.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkProcessor.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkProcessor.java index 3bab818b81..51034c867e 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkProcessor.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2022 the original author or authors. + * Copyright 2006-2025 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. @@ -22,8 +22,10 @@ /** * Interface defined for processing {@link org.springframework.batch.item.Chunk}s. * + * @author Kyeonghoon Lee (Add FunctionalInterface annotation) * @since 2.0 */ +@FunctionalInterface public interface ChunkProcessor { void process(StepContribution contribution, Chunk chunk) throws Exception; From f1ac0c01c51b50f454b5e59011f5569b80599a12 Mon Sep 17 00:00:00 2001 From: Yanming Zhou Date: Wed, 26 Feb 2025 12:54:36 +0800 Subject: [PATCH 073/266] Fix wrong statement in Javadoc of SimplePartitioner Signed-off-by: Yanming Zhou --- .../batch/core/partition/support/SimplePartitioner.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/SimplePartitioner.java b/spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/SimplePartitioner.java index 9e3ebbaa10..de0b44f7b4 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/SimplePartitioner.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/SimplePartitioner.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2013 the original author or authors. + * Copyright 2006-2025 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. @@ -25,7 +25,7 @@ * Simplest possible implementation of {@link Partitioner}. Just creates a set of empty * {@link ExecutionContext} instances, and labels them as * {partition0, partition1, ..., partitionN}, where N is the - * grid size. + * grid size - 1. * * @author Dave Syer * @since 2.0 From a06f39b76d3479a28b8d3cf93904af8a3e37b850 Mon Sep 17 00:00:00 2001 From: Ludovic Bertin Date: Tue, 25 Feb 2025 08:33:57 +0100 Subject: [PATCH 074/266] Add AOT runtime hints for core listeners Signed-off-by: Ludovic Bertin --- .../batch/core/aot/CoreRuntimeHints.java | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/aot/CoreRuntimeHints.java b/spring-batch-core/src/main/java/org/springframework/batch/core/aot/CoreRuntimeHints.java index 84a3c6e885..5c818578c8 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/aot/CoreRuntimeHints.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/aot/CoreRuntimeHints.java @@ -1,5 +1,5 @@ /* - * Copyright 2022-2023 the original author or authors. + * Copyright 2022-2025 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. @@ -98,6 +98,27 @@ public void registerHints(RuntimeHints hints, ClassLoader classLoader) { // proxy hints hints.proxies() + .registerJdkProxy(builder -> builder + .proxiedInterfaces(TypeReference.of("org.springframework.batch.core.StepExecutionListener")) + .proxiedInterfaces(SpringProxy.class, Advised.class, DecoratingProxy.class)) + .registerJdkProxy(builder -> builder + .proxiedInterfaces(TypeReference.of("org.springframework.batch.core.ItemReadListener")) + .proxiedInterfaces(SpringProxy.class, Advised.class, DecoratingProxy.class)) + .registerJdkProxy(builder -> builder + .proxiedInterfaces(TypeReference.of("org.springframework.batch.core.ItemProcessListener")) + .proxiedInterfaces(SpringProxy.class, Advised.class, DecoratingProxy.class)) + .registerJdkProxy(builder -> builder + .proxiedInterfaces(TypeReference.of("org.springframework.batch.core.ItemWriteListener")) + .proxiedInterfaces(SpringProxy.class, Advised.class, DecoratingProxy.class)) + .registerJdkProxy(builder -> builder + .proxiedInterfaces(TypeReference.of("org.springframework.batch.core.ChunkListener")) + .proxiedInterfaces(SpringProxy.class, Advised.class, DecoratingProxy.class)) + .registerJdkProxy(builder -> builder + .proxiedInterfaces(TypeReference.of("org.springframework.batch.core.SkipListener")) + .proxiedInterfaces(SpringProxy.class, Advised.class, DecoratingProxy.class)) + .registerJdkProxy(builder -> builder + .proxiedInterfaces(TypeReference.of("org.springframework.batch.core.JobExecutionListener")) + .proxiedInterfaces(SpringProxy.class, Advised.class, DecoratingProxy.class)) .registerJdkProxy(builder -> builder .proxiedInterfaces(TypeReference.of("org.springframework.batch.core.repository.JobRepository")) .proxiedInterfaces(SpringProxy.class, Advised.class, DecoratingProxy.class)) From 69615714069d13b09976f0a3d001f2d4ba6fd352 Mon Sep 17 00:00:00 2001 From: yeonnex Date: Fri, 11 Oct 2024 20:17:06 +0900 Subject: [PATCH 075/266] Fix variable usage in ScriptItemProcessorTests This commit also adds test dependencies for groovy, javascript, bean shell and jruby script engines. --- pom.xml | 4 ++++ spring-batch-infrastructure/pom.xml | 24 +++++++++++++++++++ .../support/ScriptItemProcessorTests.java | 4 ++-- 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 4aa29bb280..d297febf6e 100644 --- a/pom.xml +++ b/pom.xml @@ -129,6 +129,10 @@ 1.3.1 1.20.4 1.5.3 + 4.0.23 + 15.4 + 2.0b6 + 9.4.8.0 ${spring-amqp.version} diff --git a/spring-batch-infrastructure/pom.xml b/spring-batch-infrastructure/pom.xml index dd208f8379..1f00352b32 100644 --- a/spring-batch-infrastructure/pom.xml +++ b/spring-batch-infrastructure/pom.xml @@ -529,6 +529,30 @@ ${angus-mail.version} test + + org.apache.groovy + groovy-jsr223 + ${groovy-jsr223.version} + test + + + org.openjdk.nashorn + nashorn-core + ${nashorn.version} + test + + + org.apache-extras.beanshell + bsh + ${beanshell.version} + test + + + org.jruby + jruby + ${jruby.version} + test + diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/support/ScriptItemProcessorTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/support/ScriptItemProcessorTests.java index bdbb6205c4..5370d7b74f 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/support/ScriptItemProcessorTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/support/ScriptItemProcessorTests.java @@ -82,7 +82,7 @@ void testJRubyScriptSourceSimple() throws Exception { assumeTrue(languageExists("jruby")); ScriptItemProcessor scriptItemProcessor = new ScriptItemProcessor<>(); - scriptItemProcessor.setScriptSource("$item.upcase", "jruby"); + scriptItemProcessor.setScriptSource("item.upcase", "jruby"); scriptItemProcessor.afterPropertiesSet(); assertEquals("SS", scriptItemProcessor.process("ss"), "Incorrect transformed value"); @@ -93,7 +93,7 @@ void testJRubyScriptSourceMethod() throws Exception { assumeTrue(languageExists("jruby")); ScriptItemProcessor scriptItemProcessor = new ScriptItemProcessor<>(); - scriptItemProcessor.setScriptSource("def process(item) $item.upcase end \n process($item)", "jruby"); + scriptItemProcessor.setScriptSource("def process(item) item.upcase end \n process(item)", "jruby"); scriptItemProcessor.afterPropertiesSet(); assertEquals("SS", scriptItemProcessor.process("ss"), "Incorrect transformed value"); From 4671b62b14d87e33480c02762aa8b6c5964aca09 Mon Sep 17 00:00:00 2001 From: Elimelec Burghelea Date: Thu, 27 Feb 2025 00:38:45 +0200 Subject: [PATCH 076/266] Use Files.delete() for better error reporting Signed-off-by: Elimelec Burghelea --- .../item/support/AbstractFileItemWriter.java | 10 +-- .../batch/item/util/FileUtils.java | 11 ++- .../batch/item/xml/StaxEventItemWriter.java | 10 +-- .../support/AbstractFileItemWriterTest.java | 75 +++++++++++++++++++ .../batch/item/util/FileUtilsTests.java | 41 +++++++++- .../item/xml/StaxEventItemWriterTests.java | 26 ++++++- 6 files changed, 158 insertions(+), 15 deletions(-) create mode 100644 spring-batch-infrastructure/src/test/java/org/springframework/batch/item/support/AbstractFileItemWriterTest.java diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/support/AbstractFileItemWriter.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/support/AbstractFileItemWriter.java index 0396ca8cc7..c3d3a00bbb 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/support/AbstractFileItemWriter.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/support/AbstractFileItemWriter.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2023 the original author or authors. + * Copyright 2006-2025 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. @@ -25,6 +25,7 @@ import java.nio.channels.FileChannel; import java.nio.charset.StandardCharsets; import java.nio.charset.UnsupportedCharsetException; +import java.nio.file.Files; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -61,6 +62,7 @@ * @author Mahmoud Ben Hassine * @author Glenn Renfro * @author Remi Kaeffer + * @author Elimelec Burghelea * @since 4.1 */ public abstract class AbstractFileItemWriter extends AbstractItemStreamItemWriter @@ -268,11 +270,9 @@ public void close() { state.close(); if (state.linesWritten == 0 && shouldDeleteIfEmpty) { try { - if (!resource.getFile().delete()) { - throw new ItemStreamException("Failed to delete empty file on close"); - } + Files.delete(resource.getFile().toPath()); } - catch (IOException e) { + catch (IOException | SecurityException e) { throw new ItemStreamException("Failed to delete empty file on close", e); } } diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/util/FileUtils.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/util/FileUtils.java index 1b82ae1634..c14d9470b3 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/util/FileUtils.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/util/FileUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2024 the original author or authors. + * Copyright 2006-2025 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. @@ -18,6 +18,7 @@ import java.io.File; import java.io.IOException; +import java.nio.file.Files; import org.springframework.batch.item.ItemStreamException; import org.springframework.util.Assert; @@ -28,6 +29,7 @@ * @author Peter Zozom * @author Mahmoud Ben Hassine * @author Taeik Lim + * @author Elimelec Burghelea */ public abstract class FileUtils { @@ -57,8 +59,11 @@ public static void setUpOutputFile(File file, boolean restarted, boolean append, if (!overwriteOutputFile) { throw new ItemStreamException("File already exists: [" + file.getAbsolutePath() + "]"); } - if (!file.delete()) { - throw new IOException("Could not delete file: " + file); + try { + Files.delete(file.toPath()); + } + catch (IOException | SecurityException e) { + throw new IOException("Could not delete file: " + file, e); } } diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/xml/StaxEventItemWriter.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/xml/StaxEventItemWriter.java index fef239f809..2c6e803773 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/xml/StaxEventItemWriter.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/xml/StaxEventItemWriter.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2023 the original author or authors. + * Copyright 2006-2025 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. @@ -24,6 +24,7 @@ import java.io.UnsupportedEncodingException; import java.io.Writer; import java.nio.channels.FileChannel; +import java.nio.file.Files; import java.util.Collections; import java.util.List; import java.util.Map; @@ -75,6 +76,7 @@ * @author Michael Minella * @author Parikshit Dutta * @author Mahmoud Ben Hassine + * @author Elimelec Burghelea */ public class StaxEventItemWriter extends AbstractItemStreamItemWriter implements ResourceAwareItemWriterItemStream, InitializingBean { @@ -726,11 +728,9 @@ public void close() { } if (currentRecordCount == 0 && shouldDeleteIfEmpty) { try { - if (!resource.getFile().delete()) { - throw new ItemStreamException("Failed to delete empty file on close"); - } + Files.delete(resource.getFile().toPath()); } - catch (IOException e) { + catch (IOException | SecurityException e) { throw new ItemStreamException("Failed to delete empty file on close", e); } } diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/support/AbstractFileItemWriterTest.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/support/AbstractFileItemWriterTest.java new file mode 100644 index 0000000000..aacc67e716 --- /dev/null +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/support/AbstractFileItemWriterTest.java @@ -0,0 +1,75 @@ +/* + * Copyright 2025 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.batch.item.support; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.when; + +import java.io.File; + +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.batch.item.Chunk; +import org.springframework.batch.item.ExecutionContext; +import org.springframework.batch.item.ItemStreamException; +import org.springframework.core.io.FileSystemResource; + +/** + * Tests for common methods from {@link AbstractFileItemWriter}. + * + * @author Elimelec Burghelea + */ +class AbstractFileItemWriterTests { + + @Test + void testFailedFileDeletionThrowsException() { + File outputFile = new File("target/data/output.tmp"); + File mocked = Mockito.spy(outputFile); + + TestFileItemWriter writer = new TestFileItemWriter(); + + writer.setResource(new FileSystemResource(mocked)); + writer.setShouldDeleteIfEmpty(true); + writer.setName(writer.getClass().getSimpleName()); + writer.open(new ExecutionContext()); + + when(mocked.delete()).thenReturn(false); + + ItemStreamException exception = assertThrows(ItemStreamException.class, writer::close, + "Expected exception when file deletion fails"); + + assertEquals("Failed to delete empty file on close", exception.getMessage(), "Wrong exception message"); + assertNotNull(exception.getCause(), "Exception should have a cause"); + } + + private static class TestFileItemWriter extends AbstractFileItemWriter { + + @Override + protected String doWrite(Chunk items) { + return String.join("\n", items); + } + + @Override + public void afterPropertiesSet() { + + } + + } + +} \ No newline at end of file diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/util/FileUtilsTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/util/FileUtilsTests.java index 311ef986ba..6faae21e61 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/util/FileUtilsTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/util/FileUtilsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2022 the original author or authors. + * Copyright 2008-2025 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. @@ -28,6 +28,7 @@ import org.springframework.util.Assert; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; @@ -36,6 +37,7 @@ * Tests for {@link FileUtils} * * @author Robert Kasanicky + * @author Elimelec Burghelea */ class FileUtilsTests { @@ -178,6 +180,43 @@ public boolean exists() { } } + @Test + void testCannotDeleteFile() { + + File file = new File("new file") { + + @Override + public boolean createNewFile() { + return true; + } + + @Override + public boolean exists() { + return true; + } + + @Override + public boolean delete() { + return false; + } + + }; + try { + FileUtils.setUpOutputFile(file, false, false, true); + fail("Expected ItemStreamException because file cannot be deleted"); + } + catch (ItemStreamException ex) { + String message = ex.getMessage(); + assertTrue(message.startsWith("Unable to create file"), "Wrong message: " + message); + assertTrue(ex.getCause() instanceof IOException); + assertTrue(ex.getCause().getMessage().startsWith("Could not delete file"), "Wrong message: " + message); + assertNotNull(ex.getCause().getCause(), "Exception should have a cause"); + } + finally { + file.delete(); + } + } + @BeforeEach void setUp() { file.delete(); diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/xml/StaxEventItemWriterTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/xml/StaxEventItemWriterTests.java index f904c59441..08fada774e 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/xml/StaxEventItemWriterTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/xml/StaxEventItemWriterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2023 the original author or authors. + * Copyright 2008-2025 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. @@ -30,6 +30,7 @@ import org.springframework.batch.item.Chunk; import org.springframework.batch.item.ExecutionContext; +import org.springframework.batch.item.ItemStreamException; import org.springframework.batch.item.UnexpectedInputException; import org.springframework.batch.item.WriterNotOpenException; import org.springframework.batch.support.transaction.ResourcelessTransactionManager; @@ -47,9 +48,11 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; /** @@ -57,6 +60,7 @@ * * @author Parikshit Dutta * @author Mahmoud Ben Hassine + * @author Elimelec Burghelea */ class StaxEventItemWriterTests { @@ -831,6 +835,26 @@ void testOpenAndCloseTagsInComplexCallbacksRestart() throws Exception { + "", content, "Wrong content: " + content); } + /** + * Tests that if file.delete() returns false, an appropriate exception is thrown to + * indicate the deletion attempt failed. + */ + @Test + void testFailedFileDeletionThrowsException() throws IOException { + File mockedFile = spy(resource.getFile()); + writer.setResource(new FileSystemResource(mockedFile)); + writer.setShouldDeleteIfEmpty(true); + writer.open(executionContext); + + when(mockedFile.delete()).thenReturn(false); + + ItemStreamException exception = assertThrows(ItemStreamException.class, () -> writer.close(), + "Expected exception when file deletion fails"); + + assertEquals("Failed to delete empty file on close", exception.getMessage(), "Wrong exception message"); + assertNotNull(exception.getCause(), "Exception should have a cause"); + } + private void initWriterForSimpleCallbackTests() throws Exception { writer = createItemWriter(); writer.setHeaderCallback(writer -> { From 30e9dd79f87cfd926ff09e49567c2073b0c2885e Mon Sep 17 00:00:00 2001 From: Yanming Zhou Date: Mon, 17 Mar 2025 15:09:50 +0800 Subject: [PATCH 077/266] Stop using deprecated StepExecutionListenerSupport in document Signed-off-by: Yanming Zhou --- spring-batch-docs/modules/ROOT/pages/common-patterns.adoc | 2 +- spring-batch-docs/modules/ROOT/pages/testing.adoc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/spring-batch-docs/modules/ROOT/pages/common-patterns.adoc b/spring-batch-docs/modules/ROOT/pages/common-patterns.adoc index d5442d1ddb..5e25cddbb7 100644 --- a/spring-batch-docs/modules/ROOT/pages/common-patterns.adoc +++ b/spring-batch-docs/modules/ROOT/pages/common-patterns.adoc @@ -686,7 +686,7 @@ the class definition for `NoWorkFoundStepExecutionListener`: [source, java] ---- -public class NoWorkFoundStepExecutionListener extends StepExecutionListenerSupport { +public class NoWorkFoundStepExecutionListener implements StepExecutionListener { public ExitStatus afterStep(StepExecution stepExecution) { if (stepExecution.getReadCount() == 0) { diff --git a/spring-batch-docs/modules/ROOT/pages/testing.adoc b/spring-batch-docs/modules/ROOT/pages/testing.adoc index f6b3d7e523..3066d034ab 100644 --- a/spring-batch-docs/modules/ROOT/pages/testing.adoc +++ b/spring-batch-docs/modules/ROOT/pages/testing.adoc @@ -303,7 +303,7 @@ the following code snippet shows: [source, java] ---- -public class NoWorkFoundStepExecutionListener extends StepExecutionListenerSupport { +public class NoWorkFoundStepExecutionListener implements StepExecutionListener { public ExitStatus afterStep(StepExecution stepExecution) { if (stepExecution.getReadCount() == 0) { From f9108ee56f6c370f582fccd14e38024abff34eb0 Mon Sep 17 00:00:00 2001 From: Yanming Zhou Date: Fri, 14 Mar 2025 09:04:16 +0800 Subject: [PATCH 078/266] Fix typo Signed-off-by: Yanming Zhou --- .../batch/item/file/builder/FlatFileItemWriterBuilder.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/builder/FlatFileItemWriterBuilder.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/builder/FlatFileItemWriterBuilder.java index 30233fd89c..7de7de5301 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/builder/FlatFileItemWriterBuilder.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/builder/FlatFileItemWriterBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2023 the original author or authors. + * Copyright 2016-2025 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. @@ -97,7 +97,7 @@ public FlatFileItemWriterBuilder saveState(boolean saveState) { * The name used to calculate the key within the * {@link org.springframework.batch.item.ExecutionContext}. Required if * {@link #saveState(boolean)} is set to true. - * @param name name of the reader instance + * @param name name of the writer instance * @return The current instance of the builder. * @see org.springframework.batch.item.ItemStreamSupport#setName(String) */ From 2b1b5d1c9d0dbd682154c40ee2215d604585cc2f Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Wed, 19 Mar 2025 08:20:39 +0100 Subject: [PATCH 079/266] Remove outdated usage of AssertFile in documentation Resolves #4754 --- .../modules/ROOT/pages/testing.adoc | 20 ------------------- 1 file changed, 20 deletions(-) diff --git a/spring-batch-docs/modules/ROOT/pages/testing.adoc b/spring-batch-docs/modules/ROOT/pages/testing.adoc index 3066d034ab..7030be5005 100644 --- a/spring-batch-docs/modules/ROOT/pages/testing.adoc +++ b/spring-batch-docs/modules/ROOT/pages/testing.adoc @@ -274,26 +274,6 @@ int count = StepScopeTestUtils.doInStepScope(stepExecution, }); ---- -[[validatingOutputFiles]] -== Validating Output Files - -When a batch job writes to the database, it is easy to query the database to verify that -the output is as expected. However, if the batch job writes to a file, it is equally -important that the output be verified. Spring Batch provides a class called `AssertFile` -to facilitate the verification of output files. The method called `assertFileEquals` takes -two `File` objects (or two `Resource` objects) and asserts, line by line, that the two -files have the same content. Therefore, it is possible to create a file with the expected -output and to compare it to the actual result, as the following example shows: - -[source, java] ----- -private static final String EXPECTED_FILE = "src/main/resources/data/input.txt"; -private static final String OUTPUT_FILE = "target/test-outputs/output.txt"; - -AssertFile.assertFileEquals(new FileSystemResource(EXPECTED_FILE), - new FileSystemResource(OUTPUT_FILE)); ----- - [[mockingDomainObjects]] == Mocking Domain Objects From e1b0f156e4db9ae2c3b60b83ec372dac8bddad68 Mon Sep 17 00:00:00 2001 From: Yanming Zhou Date: Tue, 4 Mar 2025 14:44:10 +0800 Subject: [PATCH 080/266] Fix incorrect Javadoc Signed-off-by: Yanming Zhou --- .../RemotePartitioningManagerStepBuilderFactory.java | 6 ++---- .../RemotePartitioningWorkerStepBuilderFactory.java | 6 ++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/spring-batch-integration/src/main/java/org/springframework/batch/integration/partition/RemotePartitioningManagerStepBuilderFactory.java b/spring-batch-integration/src/main/java/org/springframework/batch/integration/partition/RemotePartitioningManagerStepBuilderFactory.java index 60a1f8d019..8a3c4995b0 100644 --- a/spring-batch-integration/src/main/java/org/springframework/batch/integration/partition/RemotePartitioningManagerStepBuilderFactory.java +++ b/spring-batch-integration/src/main/java/org/springframework/batch/integration/partition/RemotePartitioningManagerStepBuilderFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2022 the original author or authors. + * Copyright 2019-2025 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. @@ -21,12 +21,10 @@ import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; -import org.springframework.transaction.PlatformTransactionManager; /** * Convenient factory for a {@link RemotePartitioningManagerStepBuilder} which sets the - * {@link JobRepository}, {@link JobExplorer}, {@link BeanFactory} and - * {@link PlatformTransactionManager} automatically. + * {@link JobRepository}, {@link JobExplorer} and {@link BeanFactory} automatically. * * @since 4.2 * @author Mahmoud Ben Hassine diff --git a/spring-batch-integration/src/main/java/org/springframework/batch/integration/partition/RemotePartitioningWorkerStepBuilderFactory.java b/spring-batch-integration/src/main/java/org/springframework/batch/integration/partition/RemotePartitioningWorkerStepBuilderFactory.java index b3c13a1f72..7246b0d259 100644 --- a/spring-batch-integration/src/main/java/org/springframework/batch/integration/partition/RemotePartitioningWorkerStepBuilderFactory.java +++ b/spring-batch-integration/src/main/java/org/springframework/batch/integration/partition/RemotePartitioningWorkerStepBuilderFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2018-2022 the original author or authors. + * Copyright 2018-2025 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. @@ -21,12 +21,10 @@ import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; -import org.springframework.transaction.PlatformTransactionManager; /** * Convenient factory for a {@link RemotePartitioningWorkerStepBuilder} which sets the - * {@link JobRepository}, {@link JobExplorer}, {@link BeanFactory} and - * {@link PlatformTransactionManager} automatically. + * {@link JobRepository}, {@link JobExplorer} and {@link BeanFactory} automatically. * * @since 4.1 * @author Mahmoud Ben Hassine From 2c7178aafd31c94ff6a09bd25ee91b6930ac8506 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Wed, 19 Mar 2025 15:20:19 +0100 Subject: [PATCH 081/266] Prepare release 5.2.2 --- pom.xml | 84 ++++++++++++++++++++++++++++----------------------------- 1 file changed, 42 insertions(+), 42 deletions(-) diff --git a/pom.xml b/pom.xml index d297febf6e..37b279a639 100644 --- a/pom.xml +++ b/pom.xml @@ -61,55 +61,55 @@ 17 - 6.2.1 + 6.2.4 2.0.11 - 6.4.1 - 1.14.2 + 6.4.3 + 1.14.5 - 3.4.1 - 3.4.1 - 3.4.1 - 4.4.1 - 3.3.1 - 3.2.1 - 3.2.9 + 3.4.4 + 3.4.4 + 3.4.4 + 4.4.4 + 3.3.4 + 3.2.4 + 3.2.11 - 2.18.2 + 2.18.3 1.12.0 - 2.11.0 - 6.6.3.Final + 2.12.1 + 6.6.11.Final 3.0.0 2.1.3 3.1.0 - 3.1.0 - 3.1.0 - 4.0.13 - 5.2.1 - 5.11.4 + 3.1.1 + 3.1.0 + 4.0.16 + 5.3.1 + 5.11.4 3.0.2 - 1.4.1 + 1.4.4 - 1.4.20 + 1.4.21 4.13.2 ${junit-jupiter.version} 3.0 - 3.26.3 - 5.14.2 + 3.27.3 + 5.16.1 2.10.0 2.18.0 2.13.0 - 2.0.16 + 2.0.17 2.7.4 2.3.232 - 3.47.1.0 + 3.49.1.0 10.16.1.1 - 2.21.11 - 2.38.0 + 2.24.6 + 2.40.0 4.0.5 2.24.3 8.0.2.Final @@ -119,24 +119,24 @@ 4.0.2 2.0.3 7.1.0 - 1.9.22.1 - 9.1.0 - 3.5.1 - 42.7.4 + 1.9.23 + 9.2.0 + 3.5.2 + 42.7.5 12.1.0.0 - 19.24.0.0 + 19.26.0.0 11.2.3.jre17 1.3.1 - 1.20.4 + 1.20.6 1.5.3 - 4.0.23 - 15.4 + 4.0.26 + 15.6 2.0b6 - 9.4.8.0 + 9.4.12.0 ${spring-amqp.version} - 2.3.2 + 2.5.0 0.16.0 3.0.22 @@ -144,13 +144,13 @@ 0.0.4 - 3.13.0 - 3.5.0 - 3.5.0 - 3.10.0 + 3.14.0 + 3.5.2 + 3.5.2 + 3.11.2 3.3.1 - 1.6.0 - 3.1.3 + 1.7.0 + 3.1.4 3.7.1 3.4.2 0.0.39 From 503adc232445ecce3aefeb488bfed96ef564a741 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Wed, 19 Mar 2025 16:31:09 +0100 Subject: [PATCH 082/266] Release version 5.2.2 --- pom.xml | 2 +- spring-batch-bom/pom.xml | 2 +- spring-batch-core/pom.xml | 2 +- spring-batch-docs/pom.xml | 2 +- spring-batch-infrastructure/pom.xml | 2 +- spring-batch-integration/pom.xml | 2 +- spring-batch-samples/pom.xml | 2 +- spring-batch-test/pom.xml | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 37b279a639..9ad73b8107 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ designed to enable the development of robust batch applications vital for the daily operations of enterprise systems. Spring Batch is part of the Spring Portfolio. - 5.2.2-SNAPSHOT + 5.2.2 pom https://projects.spring.io/spring-batch diff --git a/spring-batch-bom/pom.xml b/spring-batch-bom/pom.xml index a9680681b6..35c27a533f 100644 --- a/spring-batch-bom/pom.xml +++ b/spring-batch-bom/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.2-SNAPSHOT + 5.2.2 spring-batch-bom pom diff --git a/spring-batch-core/pom.xml b/spring-batch-core/pom.xml index 5a4187bb5d..783b995348 100644 --- a/spring-batch-core/pom.xml +++ b/spring-batch-core/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.2-SNAPSHOT + 5.2.2 spring-batch-core jar diff --git a/spring-batch-docs/pom.xml b/spring-batch-docs/pom.xml index d31d9dd8cd..30afe8f50b 100644 --- a/spring-batch-docs/pom.xml +++ b/spring-batch-docs/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.2-SNAPSHOT + 5.2.2 spring-batch-docs Spring Batch Docs diff --git a/spring-batch-infrastructure/pom.xml b/spring-batch-infrastructure/pom.xml index 1f00352b32..3abe16ba46 100644 --- a/spring-batch-infrastructure/pom.xml +++ b/spring-batch-infrastructure/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.2-SNAPSHOT + 5.2.2 spring-batch-infrastructure jar diff --git a/spring-batch-integration/pom.xml b/spring-batch-integration/pom.xml index a57e5f58c5..debc4e8e64 100644 --- a/spring-batch-integration/pom.xml +++ b/spring-batch-integration/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.2-SNAPSHOT + 5.2.2 spring-batch-integration Spring Batch Integration diff --git a/spring-batch-samples/pom.xml b/spring-batch-samples/pom.xml index 9bf671940f..646a150321 100644 --- a/spring-batch-samples/pom.xml +++ b/spring-batch-samples/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.2-SNAPSHOT + 5.2.2 spring-batch-samples jar diff --git a/spring-batch-test/pom.xml b/spring-batch-test/pom.xml index ab9e8b20ba..c9f92b858f 100644 --- a/spring-batch-test/pom.xml +++ b/spring-batch-test/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.2-SNAPSHOT + 5.2.2 spring-batch-test Spring Batch Test From 961734b260ff20c5b936fb17d37338bf90436efb Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Wed, 19 Mar 2025 16:31:34 +0100 Subject: [PATCH 083/266] Next development version --- pom.xml | 2 +- spring-batch-bom/pom.xml | 2 +- spring-batch-core/pom.xml | 2 +- spring-batch-docs/pom.xml | 2 +- spring-batch-infrastructure/pom.xml | 2 +- spring-batch-integration/pom.xml | 2 +- spring-batch-samples/pom.xml | 2 +- spring-batch-test/pom.xml | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 9ad73b8107..659bcfc6b9 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ designed to enable the development of robust batch applications vital for the daily operations of enterprise systems. Spring Batch is part of the Spring Portfolio. - 5.2.2 + 5.2.3-SNAPSHOT pom https://projects.spring.io/spring-batch diff --git a/spring-batch-bom/pom.xml b/spring-batch-bom/pom.xml index 35c27a533f..b84343786e 100644 --- a/spring-batch-bom/pom.xml +++ b/spring-batch-bom/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.2 + 5.2.3-SNAPSHOT spring-batch-bom pom diff --git a/spring-batch-core/pom.xml b/spring-batch-core/pom.xml index 783b995348..2118204831 100644 --- a/spring-batch-core/pom.xml +++ b/spring-batch-core/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.2 + 5.2.3-SNAPSHOT spring-batch-core jar diff --git a/spring-batch-docs/pom.xml b/spring-batch-docs/pom.xml index 30afe8f50b..894f40a577 100644 --- a/spring-batch-docs/pom.xml +++ b/spring-batch-docs/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.2 + 5.2.3-SNAPSHOT spring-batch-docs Spring Batch Docs diff --git a/spring-batch-infrastructure/pom.xml b/spring-batch-infrastructure/pom.xml index 3abe16ba46..d58993fc7f 100644 --- a/spring-batch-infrastructure/pom.xml +++ b/spring-batch-infrastructure/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.2 + 5.2.3-SNAPSHOT spring-batch-infrastructure jar diff --git a/spring-batch-integration/pom.xml b/spring-batch-integration/pom.xml index debc4e8e64..6e7c8786bd 100644 --- a/spring-batch-integration/pom.xml +++ b/spring-batch-integration/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.2 + 5.2.3-SNAPSHOT spring-batch-integration Spring Batch Integration diff --git a/spring-batch-samples/pom.xml b/spring-batch-samples/pom.xml index 646a150321..036ca0d809 100644 --- a/spring-batch-samples/pom.xml +++ b/spring-batch-samples/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.2 + 5.2.3-SNAPSHOT spring-batch-samples jar diff --git a/spring-batch-test/pom.xml b/spring-batch-test/pom.xml index c9f92b858f..3db3eeb42f 100644 --- a/spring-batch-test/pom.xml +++ b/spring-batch-test/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.2 + 5.2.3-SNAPSHOT spring-batch-test Spring Batch Test From e3bcf5c0da092d44dc733a160595cb157ffe38be Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Thu, 20 Mar 2025 11:42:35 +0100 Subject: [PATCH 084/266] Update latest news with v5.2.2 release blog post --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 5cc775db30..81533825dd 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ # Latest news +* March 19, 2025: [Spring Batch 5.2.2 available now](https://spring.io/blog/2025/03/19/spring-batch-5-2-2-available-now) * December 18, 2024: [Spring Batch 5.1.3 and 5.2.1 available now](https://spring.io/blog/2024/12/18/spring-batch-5-1-3-and-5-2-1-available-now) * November 24, 2024: [Bootiful Spring Boot 3.4: Spring Batch](https://spring.io/blog/2024/11/24/bootiful-34-batch) * November 20, 2024: [Spring Batch 5.2.0 goes GA!](https://spring.io/blog/2024/11/20/spring-batch-5-2-0-goes-ga) From d469ae459d930b81ad7596a609445b9650b164b5 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Thu, 20 Mar 2025 11:57:10 +0100 Subject: [PATCH 085/266] Update development version to 6.0.0-SNAPSHOT --- pom.xml | 2 +- spring-batch-bom/pom.xml | 2 +- spring-batch-core/pom.xml | 2 +- spring-batch-docs/pom.xml | 2 +- spring-batch-infrastructure/pom.xml | 2 +- spring-batch-integration/pom.xml | 2 +- spring-batch-samples/pom.xml | 2 +- spring-batch-test/pom.xml | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 659bcfc6b9..b059a18b08 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ designed to enable the development of robust batch applications vital for the daily operations of enterprise systems. Spring Batch is part of the Spring Portfolio. - 5.2.3-SNAPSHOT + 6.0.0-SNAPSHOT pom https://projects.spring.io/spring-batch diff --git a/spring-batch-bom/pom.xml b/spring-batch-bom/pom.xml index b84343786e..a833c69e25 100644 --- a/spring-batch-bom/pom.xml +++ b/spring-batch-bom/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.3-SNAPSHOT + 6.0.0-SNAPSHOT spring-batch-bom pom diff --git a/spring-batch-core/pom.xml b/spring-batch-core/pom.xml index 2118204831..01007a8d98 100644 --- a/spring-batch-core/pom.xml +++ b/spring-batch-core/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.3-SNAPSHOT + 6.0.0-SNAPSHOT spring-batch-core jar diff --git a/spring-batch-docs/pom.xml b/spring-batch-docs/pom.xml index 894f40a577..ca986a25fb 100644 --- a/spring-batch-docs/pom.xml +++ b/spring-batch-docs/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.3-SNAPSHOT + 6.0.0-SNAPSHOT spring-batch-docs Spring Batch Docs diff --git a/spring-batch-infrastructure/pom.xml b/spring-batch-infrastructure/pom.xml index d58993fc7f..71401f0fdd 100644 --- a/spring-batch-infrastructure/pom.xml +++ b/spring-batch-infrastructure/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.3-SNAPSHOT + 6.0.0-SNAPSHOT spring-batch-infrastructure jar diff --git a/spring-batch-integration/pom.xml b/spring-batch-integration/pom.xml index 6e7c8786bd..145357f0cd 100644 --- a/spring-batch-integration/pom.xml +++ b/spring-batch-integration/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.3-SNAPSHOT + 6.0.0-SNAPSHOT spring-batch-integration Spring Batch Integration diff --git a/spring-batch-samples/pom.xml b/spring-batch-samples/pom.xml index 036ca0d809..7196aa21b9 100644 --- a/spring-batch-samples/pom.xml +++ b/spring-batch-samples/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.3-SNAPSHOT + 6.0.0-SNAPSHOT spring-batch-samples jar diff --git a/spring-batch-test/pom.xml b/spring-batch-test/pom.xml index 3db3eeb42f..2320743f8d 100644 --- a/spring-batch-test/pom.xml +++ b/spring-batch-test/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.3-SNAPSHOT + 6.0.0-SNAPSHOT spring-batch-test Spring Batch Test From 2bd5b840643943bd6827b10e6092e93cbb73d1d9 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Fri, 21 Mar 2025 11:18:50 +0100 Subject: [PATCH 086/266] Update dependencies to next major versions snapshots --- pom.xml | 28 +++++++++---------- .../test/resources/META-INF/persistence.xml | 10 ++++--- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/pom.xml b/pom.xml index b059a18b08..e3be833083 100644 --- a/pom.xml +++ b/pom.xml @@ -61,29 +61,29 @@ 17 - 6.2.4 - 2.0.11 - 6.4.3 - 1.14.5 + 7.0.0-SNAPSHOT + 2.0.12-SNAPSHOT + 6.5.0-SNAPSHOT + 1.15.0-SNAPSHOT - 3.4.4 - 3.4.4 - 3.4.4 - 4.4.4 - 3.3.4 - 3.2.4 - 3.2.11 + 4.0.0-SNAPSHOT + 4.0.0-SNAPSHOT + 4.0.0-SNAPSHOT + 5.0.0-SNAPSHOT + 4.0.0-SNAPSHOT + 4.0.0-SNAPSHOT + 3.3.0-SNAPSHOT 2.18.3 1.12.0 2.12.1 - 6.6.11.Final + 7.0.0.Beta4 3.0.0 2.1.3 3.1.0 3.1.1 - 3.1.0 + 3.2.0 4.0.16 5.3.1 5.11.4 @@ -92,7 +92,7 @@ 3.0.2 - 1.4.4 + 1.5.0-SNAPSHOT 1.4.21 4.13.2 diff --git a/spring-batch-infrastructure/src/test/resources/META-INF/persistence.xml b/spring-batch-infrastructure/src/test/resources/META-INF/persistence.xml index bb489174a6..70ac3d9ca0 100644 --- a/spring-batch-infrastructure/src/test/resources/META-INF/persistence.xml +++ b/spring-batch-infrastructure/src/test/resources/META-INF/persistence.xml @@ -1,13 +1,15 @@ - + xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd" + version="2.1"> org.springframework.batch.item.sample.Foo true - + + org/springframework/batch/item/database/Foo.hbm.xml + From 9d38aceade3fe1cb28caadbad233707c5f2b0b5c Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Tue, 29 Apr 2025 08:25:30 +0200 Subject: [PATCH 087/266] Update JUnit Jupiter to version 5.12.2 --- pom.xml | 3 ++- spring-batch-core/pom.xml | 6 ++++++ spring-batch-infrastructure/pom.xml | 6 +++--- spring-batch-integration/pom.xml | 6 ++++++ spring-batch-test/pom.xml | 6 ++++++ 5 files changed, 23 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index e3be833083..b259171a3c 100644 --- a/pom.xml +++ b/pom.xml @@ -86,7 +86,8 @@ 3.2.0 4.0.16 5.3.1 - 5.11.4 + 5.12.2 + 1.12.2 3.0.2 diff --git a/spring-batch-core/pom.xml b/spring-batch-core/pom.xml index 01007a8d98..ae505b3dad 100644 --- a/spring-batch-core/pom.xml +++ b/spring-batch-core/pom.xml @@ -155,6 +155,12 @@ ${testcontainers.version} test + + org.junit.platform + junit-platform-launcher + ${junit-platform-launcher.version} + test + org.hsqldb hsqldb diff --git a/spring-batch-infrastructure/pom.xml b/spring-batch-infrastructure/pom.xml index 71401f0fdd..e305e63536 100644 --- a/spring-batch-infrastructure/pom.xml +++ b/spring-batch-infrastructure/pom.xml @@ -264,9 +264,9 @@ test - org.junit.jupiter - junit-jupiter-api - ${junit-jupiter.version} + org.junit.platform + junit-platform-launcher + ${junit-platform-launcher.version} test diff --git a/spring-batch-integration/pom.xml b/spring-batch-integration/pom.xml index 145357f0cd..fa06f58bce 100644 --- a/spring-batch-integration/pom.xml +++ b/spring-batch-integration/pom.xml @@ -150,6 +150,12 @@ ${junit-jupiter.version} test + + org.junit.platform + junit-platform-launcher + ${junit-platform-launcher.version} + test + org.slf4j slf4j-simple diff --git a/spring-batch-test/pom.xml b/spring-batch-test/pom.xml index 2320743f8d..30ca92fe41 100644 --- a/spring-batch-test/pom.xml +++ b/spring-batch-test/pom.xml @@ -79,6 +79,12 @@ ${junit-vintage-engine.version} test + + org.junit.platform + junit-platform-launcher + ${junit-platform-launcher.version} + test + org.mockito mockito-core From 43ac1f12cfd651abe68a94c3fdde235e3ca5135f Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Tue, 29 Apr 2025 08:57:30 +0200 Subject: [PATCH 088/266] Remove deprecated APIs scheduled for removal in v6 Resolves #4819 --- .../annotation/BatchRegistrar.java | 10 - .../annotation/EnableBatchProcessing.java | 10 +- .../support/DefaultBatchConfiguration.java | 63 +- .../support/JobRegistryBeanPostProcessor.java | 176 --- ...JobRegistrySmartInitializingSingleton.java | 5 +- .../xml/JobRepositoryParser.java | 7 +- .../xml/StepParserStepFactoryBean.java | 20 +- .../support/JobExplorerFactoryBean.java | 18 +- .../batch/core/job/builder/JobBuilder.java | 12 +- .../core/job/builder/JobBuilderHelper.java | 27 +- .../core/listener/ChunkListenerSupport.java | 43 - .../listener/JobExecutionListenerSupport.java | 37 - .../core/listener/SkipListenerSupport.java | 43 - .../StepExecutionListenerSupport.java | 41 - .../dao/JdbcExecutionContextDao.java | 19 +- .../repository/dao/JdbcJobInstanceDao.java | 13 +- .../support/JobRepositoryFactoryBean.java | 27 +- .../builder/AbstractTaskletStepBuilder.java | 29 +- .../batch/core/step/builder/StepBuilder.java | 67 +- .../core/step/builder/StepBuilderHelper.java | 25 +- .../core/step/builder/TaskletStepBuilder.java | 14 +- .../step/factory/SimpleStepFactoryBean.java | 18 +- .../configuration/xml/spring-batch-5.0.xsd | 1368 +++++++++++++++++ .../core/configuration/xml/spring-batch.xsd | 26 +- .../JobRegistryBeanPostProcessorTests.java | 125 -- .../xml/StepParserStepFactoryBeanTests.java | 5 +- .../configuration/xml/StepParserTests.java | 4 +- .../JobRepositoryFactoryBeanTests.java | 46 +- .../core/step/builder/StepBuilderTests.java | 7 +- .../step/item/SimpleStepFactoryBeanTests.java | 3 +- ...syncChunkOrientedStepIntegrationTests.java | 3 +- .../step/tasklet/AsyncTaskletStepTests.java | 3 +- .../JobRegistryIntegrationTests-context.xml | 2 +- .../configuration/support/test-context.xml | 2 +- .../support/trivial-context-autoregister.xml | 2 +- .../xml/JobRegistryJobParserTests-context.xml | 2 +- .../xml/JobRepositoryParserTests-context.xml | 3 +- ...epParserTaskletAttributesTests-context.xml | 2 +- ...tStepAllowStartIfCompleteTests-context.xml | 2 +- .../launch/support/launcher-with-locator.xml | 2 +- ...onment-with-registry-and-auto-register.xml | 2 +- .../resources/simple-job-launcher-context.xml | 2 +- .../modules/ROOT/pages/appendix.adoc | 10 +- .../ROOT/pages/job/advanced-meta-data.adoc | 54 +- .../item-reader-writer-implementations.adoc | 31 +- .../modules/ROOT/pages/scalability.adoc | 23 - .../batch/item/data/MongoItemReader.java | 266 ---- .../batch/item/data/MongoItemWriter.java | 15 +- .../item/data/MongoPagingItemReader.java | 187 ++- .../batch/item/data/Neo4jItemReader.java | 217 --- .../batch/item/data/Neo4jItemWriter.java | 126 -- .../data/builder/MongoItemReaderBuilder.java | 299 ---- .../data/builder/MongoItemWriterBuilder.java | 19 +- .../builder/MongoPagingItemReaderBuilder.java | 34 +- .../data/builder/Neo4jItemReaderBuilder.java | 265 ---- .../data/builder/Neo4jItemWriterBuilder.java | 81 - .../database/support/SqlPagingQueryUtils.java | 60 +- .../SqlWindowingPagingQueryProvider.java | 130 -- .../listener/RepeatListenerSupport.java | 53 - .../support/TaskExecutorRepeatTemplate.java | 21 +- .../support/SystemPropertyInitializer.java | 71 - .../batch/item/data/MongoItemWriterTests.java | 10 +- .../batch/item/data/Neo4jItemReaderTests.java | 171 --- .../batch/item/data/Neo4jItemWriterTests.java | 115 -- .../builder/MongoItemWriterBuilderTests.java | 6 +- .../builder/Neo4jItemReaderBuilderTests.java | 258 ---- .../builder/Neo4jItemWriterBuilderTests.java | 91 -- .../SqlWindowingPagingQueryProviderTests.java | 77 - ...ecutorRepeatTemplateAsynchronousTests.java | 3 +- ...orRepeatTemplateBulkAsynchronousTests.java | 104 +- .../TaskExecutorRepeatTemplateTests.java | 13 +- .../SystemPropertyInitializerTests.java | 62 - .../RemoteChunkingManagerStepBuilder.java | 28 +- .../RemotePartitioningManagerStepBuilder.java | 28 +- .../RemotePartitioningWorkerStepBuilder.java | 49 +- spring-batch-samples/README.md | 2 +- .../main/resources/data-source-context.xml | 1 - .../misc/jmx/adhoc-job-launcher-context.xml | 4 +- .../resources/simple-job-launcher-context.xml | 4 +- 79 files changed, 1632 insertions(+), 3691 deletions(-) delete mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/JobRegistryBeanPostProcessor.java delete mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/listener/ChunkListenerSupport.java delete mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/listener/JobExecutionListenerSupport.java delete mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/listener/SkipListenerSupport.java delete mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/listener/StepExecutionListenerSupport.java create mode 100644 spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch-5.0.xsd delete mode 100644 spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/JobRegistryBeanPostProcessorTests.java delete mode 100644 spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/MongoItemReader.java delete mode 100644 spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/Neo4jItemReader.java delete mode 100644 spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/Neo4jItemWriter.java delete mode 100644 spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/builder/MongoItemReaderBuilder.java delete mode 100644 spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/builder/Neo4jItemReaderBuilder.java delete mode 100644 spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/builder/Neo4jItemWriterBuilder.java delete mode 100644 spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SqlWindowingPagingQueryProvider.java delete mode 100644 spring-batch-infrastructure/src/main/java/org/springframework/batch/repeat/listener/RepeatListenerSupport.java delete mode 100644 spring-batch-infrastructure/src/main/java/org/springframework/batch/support/SystemPropertyInitializer.java delete mode 100644 spring-batch-infrastructure/src/test/java/org/springframework/batch/item/data/Neo4jItemReaderTests.java delete mode 100644 spring-batch-infrastructure/src/test/java/org/springframework/batch/item/data/Neo4jItemWriterTests.java delete mode 100644 spring-batch-infrastructure/src/test/java/org/springframework/batch/item/data/builder/Neo4jItemReaderBuilderTests.java delete mode 100644 spring-batch-infrastructure/src/test/java/org/springframework/batch/item/data/builder/Neo4jItemWriterBuilderTests.java delete mode 100644 spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/SqlWindowingPagingQueryProviderTests.java delete mode 100644 spring-batch-infrastructure/src/test/java/org/springframework/batch/support/SystemPropertyInitializerTests.java diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/BatchRegistrar.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/BatchRegistrar.java index 3d23f6bcf7..073ef836d9 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/BatchRegistrar.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/BatchRegistrar.java @@ -111,11 +111,6 @@ private void registerJobRepository(BeanDefinitionRegistry registry, EnableBatchP beanDefinitionBuilder.addPropertyReference("serializer", executionContextSerializerRef); } - String lobHandlerRef = batchAnnotation.lobHandlerRef(); - if (registry.containsBeanDefinition(lobHandlerRef)) { - beanDefinitionBuilder.addPropertyReference("lobHandler", lobHandlerRef); - } - String conversionServiceRef = batchAnnotation.conversionServiceRef(); if (registry.containsBeanDefinition(conversionServiceRef)) { beanDefinitionBuilder.addPropertyReference("conversionService", conversionServiceRef); @@ -178,11 +173,6 @@ private void registerJobExplorer(BeanDefinitionRegistry registry, EnableBatchPro beanDefinitionBuilder.addPropertyReference("serializer", executionContextSerializerRef); } - String lobHandlerRef = batchAnnotation.lobHandlerRef(); - if (registry.containsBeanDefinition(lobHandlerRef)) { - beanDefinitionBuilder.addPropertyReference("lobHandler", lobHandlerRef); - } - String conversionServiceRef = batchAnnotation.conversionServiceRef(); if (registry.containsBeanDefinition(conversionServiceRef)) { beanDefinitionBuilder.addPropertyReference("conversionService", conversionServiceRef); diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/EnableBatchProcessing.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/EnableBatchProcessing.java index 27239d36c0..6e2740079d 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/EnableBatchProcessing.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/EnableBatchProcessing.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2012-2025 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. @@ -238,14 +238,6 @@ */ String jobKeyGeneratorRef() default "jobKeyGenerator"; - /** - * The large object handler to use in job repository and job explorer. - * @return the bean name of the lob handler to use. Defaults to {@literal lobHandler}. - * @deprecated Since 5.2 with no replacement. Scheduled for removal in v6 - */ - @Deprecated(since = "5.2.0", forRemoval = true) - String lobHandlerRef() default "lobHandler"; - /** * The type of large objects. * @return the type of large objects. diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/DefaultBatchConfiguration.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/DefaultBatchConfiguration.java index 67df9fd41f..484809fb2e 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/DefaultBatchConfiguration.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/DefaultBatchConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2012-2025 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. @@ -67,8 +67,6 @@ import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.support.MetaDataAccessException; import org.springframework.jdbc.support.incrementer.DataFieldMaxValueIncrementer; -import org.springframework.jdbc.support.lob.DefaultLobHandler; -import org.springframework.jdbc.support.lob.LobHandler; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.annotation.Isolation; @@ -85,7 +83,8 @@ *

  • a {@link JobLauncher} named "jobLauncher"
  • *
  • a {@link JobRegistry} named "jobRegistry"
  • *
  • a {@link JobOperator} named "JobOperator"
  • - *
  • a {@link JobRegistryBeanPostProcessor} named "jobRegistryBeanPostProcessor"
  • + *
  • a {@link JobRegistrySmartInitializingSingleton} named + * "jobRegistrySmartInitializingSingleton"
  • *
  • a {@link org.springframework.batch.core.scope.StepScope} named "stepScope"
  • *
  • a {@link org.springframework.batch.core.scope.JobScope} named "jobScope"
  • * @@ -137,7 +136,6 @@ public JobRepository jobRepository() throws BatchConfigurationException { jobRepositoryFactoryBean.setSerializer(getExecutionContextSerializer()); jobRepositoryFactoryBean.setConversionService(getConversionService()); jobRepositoryFactoryBean.setJdbcOperations(getJdbcOperations()); - jobRepositoryFactoryBean.setLobHandler(getLobHandler()); jobRepositoryFactoryBean.setCharset(getCharset()); jobRepositoryFactoryBean.setMaxVarCharLength(getMaxVarCharLength()); jobRepositoryFactoryBean.setIsolationLevelForCreateEnum(getIsolationLevelForCreate()); @@ -150,17 +148,6 @@ public JobRepository jobRepository() throws BatchConfigurationException { } } - /** - * Define a job launcher. - * @return a job launcher - * @throws BatchConfigurationException if unable to configure the default job launcher - * @deprecated Since 5.2. Use {@link #jobLauncher(JobRepository)} instead - */ - @Deprecated(forRemoval = true) - public JobLauncher jobLauncher() throws BatchConfigurationException { - return jobLauncher(jobRepository()); - } - /** * Define a job launcher bean. * @param jobRepository the job repository @@ -191,7 +178,6 @@ public JobExplorer jobExplorer() throws BatchConfigurationException { jobExplorerFactoryBean.setJobKeyGenerator(getJobKeyGenerator()); jobExplorerFactoryBean.setCharset(getCharset()); jobExplorerFactoryBean.setTablePrefix(getTablePrefix()); - jobExplorerFactoryBean.setLobHandler(getLobHandler()); jobExplorerFactoryBean.setConversionService(getConversionService()); jobExplorerFactoryBean.setSerializer(getExecutionContextSerializer()); try { @@ -208,18 +194,6 @@ public JobRegistry jobRegistry() throws BatchConfigurationException { return new MapJobRegistry(); } - /** - * Define a job operator. - * @return a job operator - * @throws BatchConfigurationException if unable to configure the default job operator - * @deprecated Since 5.2. Use - * {@link #jobOperator(JobRepository, JobExplorer, JobRegistry, JobLauncher)} instead - */ - @Deprecated(forRemoval = true) - public JobOperator jobOperator() throws BatchConfigurationException { - return jobOperator(jobRepository(), jobExplorer(), jobRegistry(), jobLauncher()); - } - /** * Define a job operator bean. * @param jobRepository a job repository @@ -249,26 +223,6 @@ public JobOperator jobOperator(JobRepository jobRepository, JobExplorer jobExplo } } - /** - * Defines a {@link JobRegistryBeanPostProcessor}. - * @return a {@link JobRegistryBeanPostProcessor} - * @throws BatchConfigurationException if unable to register the bean - * @since 5.1 - * @deprecated Use {@link #jobRegistrySmartInitializingSingleton(JobRegistry)} instead - */ - @Deprecated(forRemoval = true) - public JobRegistryBeanPostProcessor jobRegistryBeanPostProcessor() throws BatchConfigurationException { - JobRegistryBeanPostProcessor jobRegistryBeanPostProcessor = new JobRegistryBeanPostProcessor(); - jobRegistryBeanPostProcessor.setJobRegistry(jobRegistry()); - try { - jobRegistryBeanPostProcessor.afterPropertiesSet(); - return jobRegistryBeanPostProcessor; - } - catch (Exception e) { - throw new BatchConfigurationException("Unable to configure the default job registry BeanPostProcessor", e); - } - } - /** * Define a {@link JobRegistrySmartInitializingSingleton} bean. * @param jobRegistry the job registry to populate @@ -388,17 +342,6 @@ protected Charset getCharset() { return StandardCharsets.UTF_8; } - /** - * A special handler for large objects. The default is usually fine, except for some - * (usually older) versions of Oracle. - * @return the {@link LobHandler} to use - * @deprecated Since 5.2 with no replacement. Scheduled for removal in v6 - */ - @Deprecated(since = "5.2.0", forRemoval = true) - protected LobHandler getLobHandler() { - return new DefaultLobHandler(); - } - /** * Return the {@link JdbcOperations}. If this property is not overridden, a new * {@link JdbcTemplate} will be created for the configured data source by default. diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/JobRegistryBeanPostProcessor.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/JobRegistryBeanPostProcessor.java deleted file mode 100644 index 1f6ba7acfa..0000000000 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/JobRegistryBeanPostProcessor.java +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Copyright 2006-2024 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.batch.core.configuration.support; - -import java.util.Collection; -import java.util.HashSet; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.configuration.DuplicateJobException; -import org.springframework.batch.core.configuration.JobLocator; -import org.springframework.batch.core.configuration.JobRegistry; -import org.springframework.beans.BeansException; -import org.springframework.beans.FatalBeanException; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.BeanFactoryAware; -import org.springframework.beans.factory.DisposableBean; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.config.BeanPostProcessor; -import org.springframework.beans.factory.support.DefaultListableBeanFactory; -import org.springframework.util.Assert; - -/** - * A {@link BeanPostProcessor} that registers {@link Job} beans with a - * {@link JobRegistry}. Include a bean of this type along with your job configuration and - * use the same {@link JobRegistry} as a {@link JobLocator} when you need to locate a - * {@link Job} to launch. - *

    - * An alternative to this class is {@link JobRegistrySmartInitializingSingleton}, which is - * recommended in cases where this class may cause early bean initializations. You must - * include at most one of either of them as a bean. - * - * @deprecated since 5.2 in favor of {@link JobRegistrySmartInitializingSingleton}. - * @author Dave Syer - * @author Mahmoud Ben Hassine - * - */ -@Deprecated(since = "5.2") -public class JobRegistryBeanPostProcessor - implements BeanPostProcessor, BeanFactoryAware, InitializingBean, DisposableBean { - - private static final Log logger = LogFactory.getLog(JobRegistryBeanPostProcessor.class); - - // It doesn't make sense for this to have a default value... - private JobRegistry jobRegistry = null; - - private final Collection jobNames = new HashSet<>(); - - private String groupName = null; - - private DefaultListableBeanFactory beanFactory; - - /** - * The group name for jobs registered by this component. Optional (defaults to null, - * which means that jobs are registered with their bean names). Useful where there is - * a hierarchy of application contexts all contributing to the same - * {@link JobRegistry}: child contexts can then define an instance with a unique group - * name to avoid clashes between job names. - * @param groupName the groupName to set - */ - public void setGroupName(String groupName) { - this.groupName = groupName; - } - - /** - * Injection setter for {@link JobRegistry}. - * @param jobRegistry the jobConfigurationRegistry to set - */ - public void setJobRegistry(JobRegistry jobRegistry) { - this.jobRegistry = jobRegistry; - } - - @Override - public void setBeanFactory(BeanFactory beanFactory) throws BeansException { - if (beanFactory instanceof DefaultListableBeanFactory) { - this.beanFactory = (DefaultListableBeanFactory) beanFactory; - } - } - - /** - * Make sure the registry is set before use. - * - * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet() - */ - @Override - public void afterPropertiesSet() throws Exception { - Assert.state(jobRegistry != null, "JobRegistry must not be null"); - } - - /** - * Unregister all the {@link Job} instances that were registered by this post - * processor. - * @see org.springframework.beans.factory.DisposableBean#destroy() - */ - @Override - public void destroy() throws Exception { - for (String name : jobNames) { - if (logger.isDebugEnabled()) { - logger.debug("Unregistering job: " + name); - } - jobRegistry.unregister(name); - } - jobNames.clear(); - } - - /** - * If the bean is an instance of {@link Job}, then register it. - * @throws FatalBeanException if there is a {@link DuplicateJobException}. - * - * @see org.springframework.beans.factory.config.BeanPostProcessor#postProcessAfterInitialization(java.lang.Object, - * java.lang.String) - */ - @Override - public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { - if (bean instanceof Job job) { - try { - String groupName = this.groupName; - if (beanFactory != null && beanFactory.containsBean(beanName)) { - groupName = getGroupName(beanFactory.getBeanDefinition(beanName), job); - } - job = groupName == null ? job : new GroupAwareJob(groupName, job); - ReferenceJobFactory jobFactory = new ReferenceJobFactory(job); - String name = jobFactory.getJobName(); - if (logger.isDebugEnabled()) { - logger.debug("Registering job: " + name); - } - jobRegistry.register(jobFactory); - jobNames.add(name); - } - catch (DuplicateJobException e) { - throw new FatalBeanException("Cannot register job configuration", e); - } - return job; - } - return bean; - } - - /** - * Determine a group name for the job to be registered. The default implementation - * returns the {@link #setGroupName(String) groupName} configured. Provides an - * extension point for specialised subclasses. - * @param beanDefinition the bean definition for the job - * @param job the job - * @return a group name for the job (or null if not needed) - */ - protected String getGroupName(BeanDefinition beanDefinition, Job job) { - return groupName; - } - - /** - * Do nothing. - * - * @see org.springframework.beans.factory.config.BeanPostProcessor#postProcessBeforeInitialization(java.lang.Object, - * java.lang.String) - */ - @Override - public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { - return bean; - } - -} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/JobRegistrySmartInitializingSingleton.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/JobRegistrySmartInitializingSingleton.java index ede418cf23..bd6f5b9a44 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/JobRegistrySmartInitializingSingleton.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/JobRegistrySmartInitializingSingleton.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2024-2025 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,9 +42,6 @@ * {@link JobRegistry}. Include a bean of this type along with your job configuration and * use the same {@link JobRegistry} as a {@link JobLocator} when you need to locate a * {@link Job} to launch. - *

    - * This class is an alternative to {@link JobRegistryBeanPostProcessor} and prevents early - * bean initializations. You must include at most one of either of them as a bean. * * @author Henning Pöttker * @since 5.1.1 diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/JobRepositoryParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/JobRepositoryParser.java index be88087562..bbe403a114 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/JobRepositoryParser.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/JobRepositoryParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2022 the original author or authors. + * Copyright 2006-2025 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. @@ -77,8 +77,6 @@ protected void doParse(Element element, ParserContext parserContext, BeanDefinit String maxVarCharLength = element.getAttribute("max-varchar-length"); - String lobHandler = element.getAttribute("lob-handler"); - String serializer = element.getAttribute("serializer"); String conversionService = element.getAttribute("conversion-service"); @@ -97,9 +95,6 @@ protected void doParse(Element element, ParserContext parserContext, BeanDefinit if (StringUtils.hasText(tablePrefix)) { builder.addPropertyValue("tablePrefix", tablePrefix); } - if (StringUtils.hasText(lobHandler)) { - builder.addPropertyReference("lobHandler", lobHandler); - } if (StringUtils.hasText(maxVarCharLength)) { builder.addPropertyValue("maxVarCharLength", maxVarCharLength); } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/StepParserStepFactoryBean.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/StepParserStepFactoryBean.java index 7b18458ee7..187bda9a31 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/StepParserStepFactoryBean.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/StepParserStepFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2023 the original author or authors. + * Copyright 2006-2025 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. @@ -185,8 +185,6 @@ public class StepParserStepFactoryBean implements FactoryBean, BeanN private TaskExecutor taskExecutor; - private Integer throttleLimit; - private ItemReader itemReader; private ItemProcessor itemProcessor; @@ -473,9 +471,6 @@ protected void enhanceTaskletStepBuilder(AbstractTaskletStepBuilder builder) } builder.taskExecutor(taskExecutor); - if (throttleLimit != null) { - builder.throttleLimit(throttleLimit); - } builder.transactionManager(transactionManager); if (transactionTimeout != null || propagation != null || isolation != null || noRollbackExceptionClasses != null) { @@ -992,19 +987,6 @@ public void setTaskExecutor(TaskExecutor taskExecutor) { this.taskExecutor = taskExecutor; } - /** - * Public setter for the throttle limit. This limits the number of tasks queued for - * concurrent processing to prevent thread pools from being overwhelmed. Defaults to - * {@link TaskExecutorRepeatTemplate#DEFAULT_THROTTLE_LIMIT}. - * @param throttleLimit The throttle limit to set. - * @deprecated since 5.0, scheduled for removal in 6.0. This API is not intended for - * end users anyway. It is only used by the XML namespace parser. - */ - @Deprecated(since = "5.0", forRemoval = true) - public void setThrottleLimit(Integer throttleLimit) { - this.throttleLimit = throttleLimit; - } - /** * @param itemReader The {@link ItemReader} to set. */ diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/explore/support/JobExplorerFactoryBean.java b/spring-batch-core/src/main/java/org/springframework/batch/core/explore/support/JobExplorerFactoryBean.java index 9d3e24dae5..49f5661e69 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/explore/support/JobExplorerFactoryBean.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/explore/support/JobExplorerFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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. @@ -51,7 +51,6 @@ import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.support.incrementer.AbstractDataFieldMaxValueIncrementer; import org.springframework.jdbc.support.incrementer.DataFieldMaxValueIncrementer; -import org.springframework.jdbc.support.lob.LobHandler; import org.springframework.lang.NonNull; import org.springframework.util.Assert; @@ -81,8 +80,6 @@ protected long getNextKey() { private JobKeyGenerator jobKeyGenerator; - private LobHandler lobHandler; - private ExecutionContextSerializer serializer; private Charset charset = StandardCharsets.UTF_8; @@ -138,18 +135,6 @@ public void setJobKeyGenerator(JobKeyGenerator jobKeyGenerator) { this.jobKeyGenerator = jobKeyGenerator; } - /** - * The lob handler to use when saving {@link ExecutionContext} instances. Defaults to - * {@code null}, which works for most databases. - * @param lobHandler Large object handler for saving an - * {@link org.springframework.batch.item.ExecutionContext}. - * @deprecated Since 5.2 with no replacement. Scheduled for removal in v6 - */ - @Deprecated(since = "5.2.0", forRemoval = true) - public void setLobHandler(LobHandler lobHandler) { - this.lobHandler = lobHandler; - } - /** * Sets the {@link Charset} to use when deserializing the execution context. Defaults * to "UTF-8". Must not be {@code null}. @@ -210,7 +195,6 @@ public void afterPropertiesSet() throws Exception { protected ExecutionContextDao createExecutionContextDao() throws Exception { JdbcExecutionContextDao dao = new JdbcExecutionContextDao(); dao.setJdbcTemplate(jdbcOperations); - dao.setLobHandler(lobHandler); dao.setTablePrefix(tablePrefix); dao.setSerializer(serializer); dao.setCharset(charset); diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/builder/JobBuilder.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/builder/JobBuilder.java index 6ea4823189..85f44a1a36 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/builder/JobBuilder.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/builder/JobBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2023 the original author or authors. + * Copyright 2006-2025 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. @@ -30,16 +30,6 @@ */ public class JobBuilder extends JobBuilderHelper { - /** - * Create a new builder for a job with the given name. - * @param name the name of the job - * @deprecated use {@link JobBuilder#JobBuilder(String, JobRepository)} - */ - @Deprecated(since = "5.0", forRemoval = true) - public JobBuilder(String name) { - super(name); - } - /** * Create a new builder for a job with the given name. * @param name the name of the job diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/builder/JobBuilderHelper.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/builder/JobBuilderHelper.java index cacf2eab6f..dee585863e 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/builder/JobBuilderHelper.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/builder/JobBuilderHelper.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2023 the original author or authors. + * Copyright 2006-2025 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. @@ -54,17 +54,6 @@ public abstract class JobBuilderHelper> { private final CommonJobProperties properties; - /** - * Create a new {@link JobBuilderHelper}. - * @param name the job name - * @deprecated use {@link JobBuilderHelper#JobBuilderHelper(String, JobRepository)} - */ - @Deprecated(since = "5.1", forRemoval = true) - public JobBuilderHelper(String name) { - this.properties = new CommonJobProperties(); - properties.name = name; - } - /** * Create a new {@link JobBuilderHelper}. * @param name the job name @@ -110,20 +99,6 @@ public B incrementer(JobParametersIncrementer jobParametersIncrementer) { return result; } - /** - * Sets the job repository for the job. - * @param jobRepository the job repository (mandatory) - * @return this to enable fluent chaining - * @deprecated use {@link JobBuilderHelper#JobBuilderHelper(String, JobRepository)} - */ - @Deprecated(since = "5.1", forRemoval = true) - public B repository(JobRepository jobRepository) { - properties.jobRepository = jobRepository; - @SuppressWarnings("unchecked") - B result = (B) this; - return result; - } - /** * Sets the job observation convention. * @param observationConvention the job observation convention (optional) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/ChunkListenerSupport.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/ChunkListenerSupport.java deleted file mode 100644 index 79d742240e..0000000000 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/ChunkListenerSupport.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2006-2023 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.batch.core.listener; - -import org.springframework.batch.core.ChunkListener; -import org.springframework.batch.core.scope.context.ChunkContext; - -/** - * Basic support implementation of {@link ChunkListener} - * - * @author Lucas Ward - * @author Michael Minella - * @deprecated as of 5.0, in favor of the default methods on the {@link ChunkListener} - */ -@Deprecated -public class ChunkListenerSupport implements ChunkListener { - - @Override - public void afterChunk(ChunkContext context) { - } - - @Override - public void beforeChunk(ChunkContext context) { - } - - @Override - public void afterChunkError(ChunkContext context) { - } - -} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/JobExecutionListenerSupport.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/JobExecutionListenerSupport.java deleted file mode 100644 index fe54e5a6b6..0000000000 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/JobExecutionListenerSupport.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2006-2023 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.batch.core.listener; - -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobExecutionListener; - -/** - * @author Dave Syer - * @deprecated as of 5.0, in favor of the default methods on the - * {@link JobExecutionListener} - */ -@Deprecated -public class JobExecutionListenerSupport implements JobExecutionListener { - - @Override - public void afterJob(JobExecution jobExecution) { - } - - @Override - public void beforeJob(JobExecution jobExecution) { - } - -} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/SkipListenerSupport.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/SkipListenerSupport.java deleted file mode 100644 index 00001e2ee3..0000000000 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/SkipListenerSupport.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2006-2023 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.batch.core.listener; - -import org.springframework.batch.core.SkipListener; - -/** - * Basic no-op implementations of all {@link SkipListener} implementations. - * - * @author Dave Syer - * @author Mahmoud Ben Hassine - * @deprecated as of v5.0 in favor of the default methods in {@link SkipListener}. - * - */ -@Deprecated -public class SkipListenerSupport implements SkipListener { - - @Override - public void onSkipInRead(Throwable t) { - } - - @Override - public void onSkipInWrite(S item, Throwable t) { - } - - @Override - public void onSkipInProcess(T item, Throwable t) { - } - -} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/StepExecutionListenerSupport.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/StepExecutionListenerSupport.java deleted file mode 100644 index bd4bedb07f..0000000000 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/StepExecutionListenerSupport.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2006-2023 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.batch.core.listener; - -import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.StepExecutionListener; -import org.springframework.lang.Nullable; - -/** - * @author Dave Syer - * @deprecated as of 5.0, in favor of the default methods on the - * {@link StepExecutionListener} - */ -@Deprecated -public class StepExecutionListenerSupport implements StepExecutionListener { - - @Nullable - @Override - public ExitStatus afterStep(StepExecution stepExecution) { - return null; - } - - @Override - public void beforeStep(StepExecution stepExecution) { - } - -} 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 07915965c0..47b8b40c1c 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 @@ -1,5 +1,5 @@ /* - * Copyright 2006-2024 the original author or authors. + * Copyright 2006-2025 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. @@ -40,8 +40,6 @@ import org.springframework.core.serializer.Serializer; import org.springframework.jdbc.core.BatchPreparedStatementSetter; import org.springframework.jdbc.core.RowMapper; -import org.springframework.jdbc.support.lob.DefaultLobHandler; -import org.springframework.jdbc.support.lob.LobHandler; import org.springframework.lang.NonNull; import org.springframework.util.Assert; @@ -110,8 +108,6 @@ public class JdbcExecutionContextDao extends AbstractJdbcBatchMetadataDao implem private int shortContextLength = DEFAULT_MAX_VARCHAR_LENGTH; - private LobHandler lobHandler = new DefaultLobHandler(); - private ExecutionContextSerializer serializer = new DefaultExecutionContextSerializer(); private final Lock lock = new ReentrantLock(); @@ -268,15 +264,6 @@ public void deleteExecutionContext(StepExecution stepExecution) { getJdbcTemplate().update(getQuery(DELETE_STEP_EXECUTION_CONTEXT), stepExecution.getId()); } - /** - * @deprecated Since 5.2 with no replacement. Scheduled for removal in v6 - * @param lobHandler the lob handler to use - */ - @Deprecated(since = "5.2.0", forRemoval = true) - public void setLobHandler(LobHandler lobHandler) { - this.lobHandler = lobHandler; - } - @Override public void afterPropertiesSet() throws Exception { super.afterPropertiesSet(); @@ -306,7 +293,7 @@ private void persistSerializedContext(final Long executionId, String serializedC getJdbcTemplate().update(getQuery(sql), ps -> { ps.setString(1, shortContext); if (longContext != null) { - lobHandler.getLobCreator().setClobAsString(ps, 2, longContext); + ps.setString(2, longContext); } else { ps.setNull(2, getClobTypeToUse()); @@ -342,7 +329,7 @@ public void setValues(PreparedStatement ps, int i) throws SQLException { } ps.setString(1, shortContext); if (longContext != null) { - lobHandler.getLobCreator().setClobAsString(ps, 2, longContext); + ps.setString(2, longContext); } else { ps.setNull(2, getClobTypeToUse()); 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 27dcc8b7a2..8b503899a9 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 @@ -1,5 +1,5 @@ /* - * Copyright 2006-2023 the original author or authors. + * Copyright 2006-2025 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. @@ -284,17 +284,6 @@ public void deleteJobInstance(JobInstance jobInstance) { getJdbcTemplate().update(getQuery(DELETE_JOB_INSTANCE), jobInstance.getId()); } - /** - * Setter for {@link DataFieldMaxValueIncrementer} to be used when generating primary - * keys for {@link JobInstance} instances. - * @param jobIncrementer the {@link DataFieldMaxValueIncrementer} - * @deprecated as of v5.0 in favor of using the {@link #setJobInstanceIncrementer} - */ - @Deprecated - public void setJobIncrementer(DataFieldMaxValueIncrementer jobIncrementer) { - this.setJobInstanceIncrementer(jobIncrementer); - } - /** * Setter for {@link DataFieldMaxValueIncrementer} to be used when generating primary * keys for {@link JobInstance} instances. diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/JobRepositoryFactoryBean.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/JobRepositoryFactoryBean.java index 70b0b54436..ac2bd74b62 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/JobRepositoryFactoryBean.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/JobRepositoryFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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. @@ -56,8 +56,6 @@ import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.jdbc.core.JdbcOperations; import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.support.lob.DefaultLobHandler; -import org.springframework.jdbc.support.lob.LobHandler; import org.springframework.lang.NonNull; import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -95,8 +93,6 @@ public class JobRepositoryFactoryBean extends AbstractJobRepositoryFactoryBean i private int maxVarCharLengthForShortContext = AbstractJdbcBatchMetadataDao.DEFAULT_SHORT_CONTEXT_LENGTH; - private LobHandler lobHandler; - private ExecutionContextSerializer serializer; private Integer clobType; @@ -124,19 +120,6 @@ public void setSerializer(ExecutionContextSerializer serializer) { this.serializer = serializer; } - /** - * A special handler for large objects. The default is usually fine, except for some - * (usually older) versions of Oracle. The default is determined from the data base - * type. - * @param lobHandler the {@link LobHandler} to set - * @deprecated Since 5.2 with no replacement. Scheduled for removal in v6 - * @see LobHandler - */ - @Deprecated(since = "5.2.0", forRemoval = true) - public void setLobHandler(LobHandler lobHandler) { - this.lobHandler = lobHandler; - } - /** * Public setter for the length of long string columns in database. Do not set this if * you haven't modified the schema. Note this value will be used for the exit message @@ -276,10 +259,6 @@ public void afterPropertiesSet() throws Exception { } } - if (lobHandler == null && databaseType.equalsIgnoreCase(DatabaseType.ORACLE.toString())) { - lobHandler = new DefaultLobHandler(); - } - if (serializer == null) { serializer = new DefaultExecutionContextSerializer(); } @@ -355,10 +334,6 @@ protected ExecutionContextDao createExecutionContextDao() throws Exception { dao.setSerializer(serializer); dao.setCharset(charset); - if (lobHandler != null) { - dao.setLobHandler(lobHandler); - } - dao.afterPropertiesSet(); dao.setShortContextLength(this.maxVarCharLengthForShortContext); return dao; diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/AbstractTaskletStepBuilder.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/AbstractTaskletStepBuilder.java index 55e6a0fdce..3a5eb7ab0f 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/AbstractTaskletStepBuilder.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/AbstractTaskletStepBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2012-2025 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. @@ -67,8 +67,6 @@ public abstract class AbstractTaskletStepBuilder parent) { @@ -88,7 +86,6 @@ public AbstractTaskletStepBuilder(AbstractTaskletStepBuilder parent) { this.transactionAttribute = parent.transactionAttribute; this.streams.addAll(parent.streams); this.exceptionHandler = parent.exceptionHandler; - this.throttleLimit = parent.throttleLimit; this.taskExecutor = parent.taskExecutor; } @@ -125,7 +122,6 @@ public TaskletStep build() { if (taskExecutor != null) { TaskExecutorRepeatTemplate repeatTemplate = new TaskExecutorRepeatTemplate(); repeatTemplate.setTaskExecutor(taskExecutor); - repeatTemplate.setThrottleLimit(throttleLimit); stepOperations = repeatTemplate; } @@ -210,24 +206,6 @@ public B taskExecutor(TaskExecutor taskExecutor) { return self(); } - /** - * In the case of an asynchronous {@link #taskExecutor(TaskExecutor)} the number of - * concurrent tasklet executions can be throttled (beyond any throttling provided by a - * thread pool). The throttle limit should be less than the data source pool size used - * in the job repository for this step. - * @param throttleLimit maximum number of concurrent tasklet executions allowed - * @return this for fluent chaining - * @deprecated with no replacement since 5.0, scheduled for removal in 6.0. Use a - * custom {@link RepeatOperations} implementation (based on a {@link TaskExecutor} - * with a bounded task queue) and set it on the step with - * {@link #stepOperations(RepeatOperations)}. - */ - @Deprecated(since = "5.0", forRemoval = true) - public B throttleLimit(int throttleLimit) { - this.throttleLimit = throttleLimit; - return self(); - } - /** * Sets the exception handler to use in the case of tasklet failures. Default is to * rethrow everything. @@ -302,11 +280,6 @@ protected TaskExecutor getTaskExecutor() { return taskExecutor; } - @Deprecated(since = "5.0", forRemoval = true) - protected int getThrottleLimit() { - return throttleLimit; - } - protected TransactionAttribute getTransactionAttribute() { return transactionAttribute; } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/StepBuilder.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/StepBuilder.java index 8d49029a7a..9ae5289a22 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/StepBuilder.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/StepBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2023 the original author or authors. + * Copyright 2006-2025 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. @@ -34,16 +34,6 @@ */ public class StepBuilder extends StepBuilderHelper { - /** - * Initialize a step builder for a step with the given name. - * @param name the name of the step - * @deprecated use {@link StepBuilder#StepBuilder(String, JobRepository)} - */ - @Deprecated(since = "5.0", forRemoval = true) - public StepBuilder(String name) { - super(name); - } - /** * Initialize a step builder for a step with the given name and job repository. * @param name the name of the step @@ -54,17 +44,6 @@ public StepBuilder(String name, JobRepository jobRepository) { super(name, jobRepository); } - /** - * Build a step with a custom tasklet, not necessarily item processing. - * @param tasklet a tasklet - * @return a {@link TaskletStepBuilder} - * @deprecated use {@link StepBuilder#tasklet(Tasklet, PlatformTransactionManager)} - */ - @Deprecated(since = "5.0", forRemoval = true) - public TaskletStepBuilder tasklet(Tasklet tasklet) { - return new TaskletStepBuilder(this).tasklet(tasklet); - } - /** * Build a step with a custom tasklet, not necessarily item processing. * @param tasklet a tasklet @@ -76,27 +55,6 @@ public TaskletStepBuilder tasklet(Tasklet tasklet, PlatformTransactionManager tr return new TaskletStepBuilder(this).tasklet(tasklet, transactionManager); } - /** - * Build a step that processes items in chunks with the size provided. To extend the - * step to being fault tolerant, call the {@link SimpleStepBuilder#faultTolerant()} - * method on the builder. In most cases you will want to parameterize your call to - * this method, to preserve the type safety of your readers and writers, e.g. - * - *

    -	 * new StepBuilder("step1").<Order, Ledger> chunk(100).reader(new OrderReader()).writer(new LedgerWriter())
    -	 * // ... etc.
    -	 * 
    - * @param chunkSize the chunk size (commit interval) - * @return a {@link SimpleStepBuilder} - * @param the type of item to be processed as input - * @param the type of item to be output - * @deprecated use {@link StepBuilder#chunk(int, PlatformTransactionManager)} - */ - @Deprecated(since = "5.0", forRemoval = true) - public SimpleStepBuilder chunk(int chunkSize) { - return new SimpleStepBuilder(this).chunk(chunkSize); - } - /** * Build a step that processes items in chunks with the size provided. To extend the * step to being fault tolerant, call the {@link SimpleStepBuilder#faultTolerant()} @@ -119,29 +77,6 @@ public SimpleStepBuilder chunk(int chunkSize, PlatformTransactionMa return new SimpleStepBuilder(this).transactionManager(transactionManager).chunk(chunkSize); } - /** - * Build a step that processes items in chunks with the completion policy provided. To - * extend the step to being fault tolerant, call the - * {@link SimpleStepBuilder#faultTolerant()} method on the builder. In most cases you - * will want to parameterize your call to this method, to preserve the type safety of - * your readers and writers, e.g. - * - *
    -	 * new StepBuilder("step1").<Order, Ledger> chunk(100).reader(new OrderReader()).writer(new LedgerWriter())
    -	 * // ... etc.
    -	 * 
    - * @param completionPolicy the completion policy to use to control chunk processing - * @return a {@link SimpleStepBuilder} - * @param the type of item to be processed as input - * @param the type of item to be output - * @deprecated use - * {@link StepBuilder#chunk(CompletionPolicy, PlatformTransactionManager)} - */ - @Deprecated(since = "5.0", forRemoval = true) - public SimpleStepBuilder chunk(CompletionPolicy completionPolicy) { - return new SimpleStepBuilder(this).chunk(completionPolicy); - } - /** * Build a step that processes items in chunks with the completion policy provided. To * extend the step to being fault tolerant, call the diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/StepBuilderHelper.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/StepBuilderHelper.java index 577f383e08..802325a30f 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/StepBuilderHelper.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/StepBuilderHelper.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2023 the original author or authors. + * Copyright 2006-2025 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. @@ -53,17 +53,6 @@ public abstract class StepBuilderHelper> { protected final CommonStepProperties properties; - /** - * Create a new {@link StepBuilderHelper}. - * @param name the step name - * @deprecated use {@link StepBuilderHelper#StepBuilderHelper(String, JobRepository)} - */ - @Deprecated(since = "5.1", forRemoval = true) - public StepBuilderHelper(String name) { - this.properties = new CommonStepProperties(); - properties.name = name; - } - /** * Create a new {@link StepBuilderHelper}. * @param name the step name @@ -85,18 +74,6 @@ protected StepBuilderHelper(StepBuilderHelper parent) { this.properties = new CommonStepProperties(parent.properties); } - /** - * Set the job repository - * @param jobRepository the repository to set - * @return this to enable fluent chaining - * @deprecated use {@link StepBuilderHelper#StepBuilderHelper(String, JobRepository)} - */ - @Deprecated(since = "5.1", forRemoval = true) - public B repository(JobRepository jobRepository) { - properties.jobRepository = jobRepository; - return self(); - } - /** * Sets the step observation convention. * @param observationConvention the step observation convention (optional) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/TaskletStepBuilder.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/TaskletStepBuilder.java index bf4aad229f..896fce2cea 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/TaskletStepBuilder.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/TaskletStepBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2023 the original author or authors. + * Copyright 2006-2025 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. @@ -38,18 +38,6 @@ public TaskletStepBuilder(StepBuilderHelper parent) { super(parent); } - /** - * @param tasklet the tasklet to use - * @return this for fluent chaining - * @deprecated use - * {@link TaskletStepBuilder#tasklet(Tasklet, PlatformTransactionManager)} - */ - @Deprecated(since = "5.0", forRemoval = true) - public TaskletStepBuilder tasklet(Tasklet tasklet) { - this.tasklet = tasklet; - return this; - } - /** * @param tasklet the tasklet to use * @return this for fluent chaining diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/factory/SimpleStepFactoryBean.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/factory/SimpleStepFactoryBean.java index 5acb2a4d93..d2d672676a 100755 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/factory/SimpleStepFactoryBean.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/factory/SimpleStepFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2023 the original author or authors. + * Copyright 2006-2025 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. @@ -441,20 +441,6 @@ protected TaskExecutor getTaskExecutor() { return taskExecutor; } - /** - * Public setter for the throttle limit. This limits the number of tasks queued for - * concurrent processing to prevent thread pools from being overwhelmed. Defaults to - * {@link TaskExecutorRepeatTemplate#DEFAULT_THROTTLE_LIMIT}. - * @param throttleLimit the throttle limit to set. - * @deprecated since 5.0, scheduled for removal in 6.0. Use a pooled - * {@link TaskExecutor} implementation with a limited capacity of its task queue - * instead. - */ - @Deprecated(since = "5.0", forRemoval = true) - public void setThrottleLimit(int throttleLimit) { - this.throttleLimit = throttleLimit; - } - protected void applyConfiguration(SimpleStepBuilder builder) { builder.reader(itemReader); @@ -482,7 +468,6 @@ protected void applyConfiguration(SimpleStepBuilder builder) { } builder.transactionManager(transactionManager); builder.transactionAttribute(getTransactionAttribute()); - builder.repository(jobRepository); builder.observationRegistry(observationRegistry); builder.startLimit(startLimit); builder.allowStartIfComplete(allowStartIfComplete); @@ -491,7 +476,6 @@ protected void applyConfiguration(SimpleStepBuilder builder) { builder.chunkOperations(chunkOperations); builder.stepOperations(stepOperations); builder.taskExecutor(taskExecutor); - builder.throttleLimit(throttleLimit); builder.exceptionHandler(exceptionHandler); if (isReaderTransactionalQueue) { builder.readerIsTransactionalQueue(); diff --git a/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch-5.0.xsd b/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch-5.0.xsd new file mode 100644 index 0000000000..1c5b20f37c --- /dev/null +++ b/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch-5.0.xsd @@ -0,0 +1,1368 @@ + + + + + + + + + + + + + + Defines a job composed of a set of steps and + transitions between steps. The job will be exposed in + the enclosing + bean factory as a component of type Job + that can be launched using a + JobLauncher. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Defines a stage in job processing backed by a + Step. The id attribute must be specified since this + step definition + will be referred to from other elements + to form a Job flow. + + + + + + + + + + + + + + + + + Defines a flow composed of a set of steps and + transitions between steps. + + + + + + + + + + + + + + + + + + A reference to a JobExecutionListener (or a POJO + if using before-job-method / after-job-method or + source level + annotations). + + + + + + + + + + + + + + + A bean definition for a step listener (or POJO if + using *-method attributes or source level + annotations) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Defines a stage in job processing backed by a + Step. The id attribute must be specified. The + step + requires either + a chunk definition, + a tasklet reference, or a reference to a + (possibly abstract) parent step. + + + + + + + + + + + + + + + + Declares job should split here into two or more + subflows. + + + + + + + + A subflow within a job, having the same + format as a job, but without a separate identity. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Declares job should include an externalized flow + here. + + + + + + + + + + + + + + + + + + + + + + Declares job should query a decider to determine + where execution should go next. + + + + + + + + + The decider is a reference to a + JobExecutionDecider that can produce a status to base + the next + transition on. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The tasklet is a reference to another bean + definition that implements + the Tasklet interface. + + + + + + + + + + If the tasklet is specified as a bean definition, then a method can be specified and a POJO + will + be adapted to the Tasklet interface. The method suggested should have the same arguments + as Tasklet.execute (or a subset), and have a compatible return type (boolean, void or RepeatStatus). + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + An exception class name. + + + + + + + + + + + + + + + + + Classify an exception as "included" in the set. Exceptions of this type or a subclass are + included. + + + + + + + + + + + + + + + + Classify an exception as "excluded" from the + set. Exceptions of this type or a subclass are + excluded + + + + + + + + + + + + + + + A reference to a listener, a POJO with a + listener-annotated method, or a POJO with + a method + referenced by a + *-method attribute. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Defines a transition from this step to the + next + one depending on the value of the exit + status. + + + + + + A pattern to match against the exit status + code. Use * and ? as wildcard characters. When a + step finishes + the most + specific match will be chosen to select the next step. + Hint: + always include a default + transition with on="*". + + + + + + + The name of the step to go to next. Must + resolve to one of the other steps in this job. + + + + + + + + + Declares job should be stop at this point and + provides pointer where execution should continue + when + the job is + restarted. + + + + + + A pattern to match against the exit status + code. Use * and ? as wildcard characters. + When a step + finishes + the most specific match will be chosen to + select the next step. + + + + + + The name of the step to start on when the + stopped job is restarted. + Must resolve to one of the + other steps + in this job. + + + + + + The exit code value to end on, defaults to + STOPPED. + + + + + + + + Declares job should end at this point, without + the possibility of restart. + BatchStatus will be + COMPLETED. + ExitStatus is configurable. + + + + + + A pattern to match against the exit status + code. Use * and ? as wildcard characters. + When a step + finishes + the most specific match will be chosen to + select the next step. + + + + + + The exit code value to end on, defaults to + COMPLETED. + + + + + + + + Declares job should fail at this point. + BatchStatus will be FAILED. ExitStatus is configurable. + + + + + + A pattern to match against the exit status + code. Use * and ? as wildcard characters. + When a step + finishes + the most specific match will be chosen to + select the next step. + + + + + + The exit code value to end on, defaults to + FAILED. + + + + + + + + + + + + + + + + + + + + + + + + + The name of the parent bean from which the + configuration should inherit. + + + + + + + + + + + + + Is this bean "abstract", that is, not meant to be + instantiated itself + but rather just serving as + parent for concrete + child bean definitions? + The default is "false". Specify "true" to + tell the bean factory to not + try + to instantiate that particular bean + in any case. + + Note: This attribute will not be inherited by child + bean definitions. + Hence, it needs to be specified per abstract bean + definition. + + + + + + + + + + Should this list be merged with the corresponding + list provided + by the parent? If not, it will + overwrite the parent + list. + + + + + + + + + + This attribute indicates the method from the + class that should + be used to dynamically create a + proxy. + + + + + + + + + + + + + diff --git a/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch.xsd b/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch.xsd index 1c5b20f37c..7656c45854 100644 --- a/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch.xsd +++ b/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch.xsd @@ -6,7 +6,7 @@ xsi:schemaLocation=" http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tool https://www.springframework.org/schema/tool/spring-tool.xsd" - version="5.0"> + version="6.0"> @@ -265,20 +265,6 @@
    - - - - - - - - - - - - - - - diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/JobRegistryBeanPostProcessorTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/JobRegistryBeanPostProcessorTests.java deleted file mode 100644 index a7913ed0b9..0000000000 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/JobRegistryBeanPostProcessorTests.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright 2006-2023 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.batch.core.configuration.support; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.Collection; - -import org.junit.jupiter.api.Test; -import org.springframework.batch.core.configuration.DuplicateJobException; -import org.springframework.batch.core.job.JobSupport; -import org.springframework.beans.FatalBeanException; -import org.springframework.context.support.ClassPathXmlApplicationContext; - -/** - * @author Dave Syer - * @author Mahmoud Ben Hassine - * - */ -class JobRegistryBeanPostProcessorTests { - - private final JobRegistryBeanPostProcessor processor = new JobRegistryBeanPostProcessor(); - - @Test - void testInitializationFails() { - Exception exception = assertThrows(IllegalStateException.class, processor::afterPropertiesSet); - assertTrue(exception.getMessage().contains("JobRegistry")); - } - - @Test - void testBeforeInitialization() { - // should be a no-op - assertEquals("foo", processor.postProcessBeforeInitialization("foo", "bar")); - } - - @Test - void testAfterInitializationWithWrongType() { - // should be a no-op - assertEquals("foo", processor.postProcessAfterInitialization("foo", "bar")); - } - - @Test - void testAfterInitializationWithCorrectType() { - MapJobRegistry registry = new MapJobRegistry(); - processor.setJobRegistry(registry); - JobSupport job = new JobSupport(); - job.setBeanName("foo"); - assertNotNull(processor.postProcessAfterInitialization(job, "bar")); - assertEquals("[foo]", registry.getJobNames().toString()); - } - - @Test - void testAfterInitializationWithGroupName() { - MapJobRegistry registry = new MapJobRegistry(); - processor.setJobRegistry(registry); - processor.setGroupName("jobs"); - JobSupport job = new JobSupport(); - job.setBeanName("foo"); - assertNotNull(processor.postProcessAfterInitialization(job, "bar")); - assertEquals("[jobs.foo]", registry.getJobNames().toString()); - } - - @Test - void testAfterInitializationWithDuplicate() { - MapJobRegistry registry = new MapJobRegistry(); - processor.setJobRegistry(registry); - JobSupport job = new JobSupport(); - job.setBeanName("foo"); - processor.postProcessAfterInitialization(job, "bar"); - Exception exception = assertThrows(FatalBeanException.class, - () -> processor.postProcessAfterInitialization(job, "spam")); - assertTrue(exception.getCause() instanceof DuplicateJobException); - } - - @Test - void testUnregisterOnDestroy() throws Exception { - MapJobRegistry registry = new MapJobRegistry(); - processor.setJobRegistry(registry); - JobSupport job = new JobSupport(); - job.setBeanName("foo"); - assertNotNull(processor.postProcessAfterInitialization(job, "bar")); - processor.destroy(); - assertEquals("[]", registry.getJobNames().toString()); - } - - @Test - void testExecutionWithApplicationContext() throws Exception { - ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("test-context.xml", getClass()); - MapJobRegistry registry = (MapJobRegistry) context.getBean("registry"); - Collection configurations = registry.getJobNames(); - String[] names = context.getBeanNamesForType(JobSupport.class); - int count = names.length; - // Each concrete bean of type JobConfiguration is registered... - assertEquals(count, configurations.size()); - // N.B. there is a failure / wonky mode where a parent bean is given an - // explicit name or beanName (using property setter): in this case then - // child beans will have the same name and will be re-registered (and - // override, if the registry supports that). - assertNotNull(registry.getJob("test-job")); - assertEquals(context.getBean("test-job-with-name"), registry.getJob("foo")); - assertEquals(context.getBean("test-job-with-bean-name"), registry.getJob("bar")); - assertEquals(context.getBean("test-job-with-parent-and-name"), registry.getJob("spam")); - assertEquals(context.getBean("test-job-with-parent-and-bean-name"), registry.getJob("bucket")); - assertEquals(context.getBean("test-job-with-concrete-parent"), registry.getJob("maps")); - assertEquals(context.getBean("test-job-with-concrete-parent-and-name"), registry.getJob("oof")); - assertEquals(context.getBean("test-job-with-concrete-parent-and-bean-name"), registry.getJob("rab")); - } - -} diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StepParserStepFactoryBeanTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StepParserStepFactoryBeanTests.java index 37949ce5fd..91f4963ead 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StepParserStepFactoryBeanTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StepParserStepFactoryBeanTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2023 the original author or authors. + * Copyright 2006-2025 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. @@ -239,7 +239,6 @@ void testFaultTolerantStep() throws Exception { fb.setIsReaderTransactionalQueue(true); fb.setRetryLimit(5); fb.setSkipLimit(100); - fb.setThrottleLimit(10); fb.setRetryListeners(new RetryListener() { }); @SuppressWarnings("unchecked") @@ -251,7 +250,7 @@ void testFaultTolerantStep() throws Exception { assertTrue(step instanceof TaskletStep); Object throttleLimit = ReflectionTestUtils.getField(ReflectionTestUtils.getField(step, "stepOperations"), "throttleLimit"); - assertEquals(10, throttleLimit); + assertEquals(4, throttleLimit); Object tasklet = ReflectionTestUtils.getField(step, "tasklet"); assertTrue(tasklet instanceof ChunkOrientedTasklet); assertFalse((Boolean) ReflectionTestUtils.getField(tasklet, "buffering")); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StepParserTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StepParserTests.java index 541e402264..7d709d6ef8 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StepParserTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StepParserTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2023 the original author or authors. + * Copyright 2006-2025 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. @@ -88,8 +88,6 @@ void testTaskletStepAttributes() throws Exception { StepParserStepFactoryBean factory = beans.get(factoryName); TaskletStep bean = (TaskletStep) factory.getObject(); assertEquals(25, bean.getStartLimit(), "wrong start-limit:"); - Object throttleLimit = ReflectionTestUtils.getField(factory, "throttleLimit"); - assertEquals(10, throttleLimit); } @Test diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/JobRepositoryFactoryBeanTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/JobRepositoryFactoryBeanTests.java index 806a30e5cb..a8669c4c37 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/JobRepositoryFactoryBeanTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/JobRepositoryFactoryBeanTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2023 the original author or authors. + * Copyright 2006-2025 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,8 +42,6 @@ import org.springframework.jdbc.core.JdbcOperations; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.support.incrementer.DataFieldMaxValueIncrementer; -import org.springframework.jdbc.support.lob.DefaultLobHandler; -import org.springframework.jdbc.support.lob.LobHandler; import org.springframework.test.util.ReflectionTestUtils; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.annotation.Isolation; @@ -112,48 +110,6 @@ void testNoDatabaseType() throws Exception { } - @Test - void testOracleLobHandler() throws Exception { - - factory.setDatabaseType("ORACLE"); - - incrementerFactory = mock(); - when(incrementerFactory.isSupportedIncrementerType("ORACLE")).thenReturn(true); - when(incrementerFactory.getIncrementer("ORACLE", tablePrefix + "JOB_SEQ")).thenReturn(new StubIncrementer()); - when(incrementerFactory.getIncrementer("ORACLE", tablePrefix + "JOB_EXECUTION_SEQ")) - .thenReturn(new StubIncrementer()); - when(incrementerFactory.getIncrementer("ORACLE", tablePrefix + "STEP_EXECUTION_SEQ")) - .thenReturn(new StubIncrementer()); - factory.setIncrementerFactory(incrementerFactory); - - factory.afterPropertiesSet(); - LobHandler lobHandler = (LobHandler) ReflectionTestUtils.getField(factory, "lobHandler"); - assertTrue(lobHandler instanceof DefaultLobHandler); - - } - - @Test - void testCustomLobHandler() throws Exception { - - factory.setDatabaseType("ORACLE"); - - incrementerFactory = mock(); - when(incrementerFactory.isSupportedIncrementerType("ORACLE")).thenReturn(true); - when(incrementerFactory.getIncrementer("ORACLE", tablePrefix + "JOB_SEQ")).thenReturn(new StubIncrementer()); - when(incrementerFactory.getIncrementer("ORACLE", tablePrefix + "JOB_EXECUTION_SEQ")) - .thenReturn(new StubIncrementer()); - when(incrementerFactory.getIncrementer("ORACLE", tablePrefix + "STEP_EXECUTION_SEQ")) - .thenReturn(new StubIncrementer()); - factory.setIncrementerFactory(incrementerFactory); - - LobHandler lobHandler = new DefaultLobHandler(); - factory.setLobHandler(lobHandler); - - factory.afterPropertiesSet(); - assertEquals(lobHandler, ReflectionTestUtils.getField(factory, "lobHandler")); - - } - @Test @SuppressWarnings("unchecked") void tesDefaultSerializer() throws Exception { diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/builder/StepBuilderTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/builder/StepBuilderTests.java index b3e1114d6f..61804ee1d4 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/builder/StepBuilderTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/builder/StepBuilderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2025 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. @@ -252,11 +252,6 @@ void testReturnedTypeOfTaskExecutorIsAssignableToSimpleStepBuilder() throws Exce testReturnedTypeOfSetterIsAssignableToSimpleStepBuilder(builder -> builder.taskExecutor(null)); } - @Test - void testReturnedTypeOfThrottleLimitIsAssignableToSimpleStepBuilder() throws Exception { - testReturnedTypeOfSetterIsAssignableToSimpleStepBuilder(builder -> builder.throttleLimit(4)); - } - @Test void testReturnedTypeOfExceptionHandlerIsAssignableToSimpleStepBuilder() throws Exception { testReturnedTypeOfSetterIsAssignableToSimpleStepBuilder( diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/SimpleStepFactoryBeanTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/SimpleStepFactoryBeanTests.java index d4f6137dbb..4455756793 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/SimpleStepFactoryBeanTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/SimpleStepFactoryBeanTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2024 the original author or authors. + * Copyright 2006-2025 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. @@ -151,7 +151,6 @@ void testSimpleConcurrentJob() throws Exception { SimpleStepFactoryBean factory = getStepFactory("foo", "bar"); factory.setTaskExecutor(new SimpleAsyncTaskExecutor()); - factory.setThrottleLimit(1); AbstractStep step = (AbstractStep) factory.getObject(); step.setName("step1"); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/tasklet/AsyncChunkOrientedStepIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/tasklet/AsyncChunkOrientedStepIntegrationTests.java index 5a3efbf659..323c89cd0a 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/tasklet/AsyncChunkOrientedStepIntegrationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/tasklet/AsyncChunkOrientedStepIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2023 the original author or authors. + * Copyright 2006-2025 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. @@ -109,7 +109,6 @@ void init() { job = new JobSupport("FOO"); TaskExecutorRepeatTemplate repeatTemplate = new TaskExecutorRepeatTemplate(); - repeatTemplate.setThrottleLimit(2); repeatTemplate.setTaskExecutor(new SimpleAsyncTaskExecutor()); step.setStepOperations(repeatTemplate); step.setTransactionManager(transactionManager); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/tasklet/AsyncTaskletStepTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/tasklet/AsyncTaskletStepTests.java index 5062972d08..7002c5275e 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/tasklet/AsyncTaskletStepTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/tasklet/AsyncTaskletStepTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2023 the original author or authors. + * Copyright 2006-2025 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,7 +90,6 @@ private void setUp() { step.setJobRepository(jobRepository); TaskExecutorRepeatTemplate template = new TaskExecutorRepeatTemplate(); - template.setThrottleLimit(throttleLimit); SimpleAsyncTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor(); taskExecutor.setConcurrencyLimit(concurrencyLimit); template.setTaskExecutor(taskExecutor); diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/support/JobRegistryIntegrationTests-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/support/JobRegistryIntegrationTests-context.xml index 0537f41739..dccc63e23e 100644 --- a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/support/JobRegistryIntegrationTests-context.xml +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/support/JobRegistryIntegrationTests-context.xml @@ -13,7 +13,7 @@ - + diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/support/test-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/support/test-context.xml index 95b739c01a..1835e82948 100644 --- a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/support/test-context.xml +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/support/test-context.xml @@ -10,7 +10,7 @@ http://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx.xsd"> + class="org.springframework.batch.core.configuration.support.JobRegistrySmartInitializingSingleton"> diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/support/trivial-context-autoregister.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/support/trivial-context-autoregister.xml index cce014e578..e01d220d0a 100644 --- a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/support/trivial-context-autoregister.xml +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/support/trivial-context-autoregister.xml @@ -11,7 +11,7 @@ - + diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/JobRegistryJobParserTests-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/JobRegistryJobParserTests-context.xml index ecbf6bbf36..240e5baa21 100644 --- a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/JobRegistryJobParserTests-context.xml +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/JobRegistryJobParserTests-context.xml @@ -20,7 +20,7 @@ + class="org.springframework.batch.core.configuration.support.JobRegistrySmartInitializingSingleton"> diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/JobRepositoryParserTests-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/JobRepositoryParserTests-context.xml index e7993737bd..ae15fc673c 100644 --- a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/JobRepositoryParserTests-context.xml +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/JobRepositoryParserTests-context.xml @@ -14,9 +14,8 @@ - - + \ No newline at end of file diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StepParserTaskletAttributesTests-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StepParserTaskletAttributesTests-context.xml index 48f6ddedd3..68b7a04fe3 100644 --- a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StepParserTaskletAttributesTests-context.xml +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StepParserTaskletAttributesTests-context.xml @@ -9,7 +9,7 @@ - + diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/TaskletStepAllowStartIfCompleteTests-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/TaskletStepAllowStartIfCompleteTests-context.xml index 8620593d3d..c122971d17 100644 --- a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/TaskletStepAllowStartIfCompleteTests-context.xml +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/TaskletStepAllowStartIfCompleteTests-context.xml @@ -38,7 +38,7 @@ - + diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/launch/support/launcher-with-locator.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/launch/support/launcher-with-locator.xml index 39f42d9ad3..5cb22aea10 100644 --- a/spring-batch-core/src/test/resources/org/springframework/batch/core/launch/support/launcher-with-locator.xml +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/launch/support/launcher-with-locator.xml @@ -15,7 +15,7 @@ - + diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/launch/support/test-environment-with-registry-and-auto-register.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/launch/support/test-environment-with-registry-and-auto-register.xml index eac8d8e9fb..6045e96aed 100644 --- a/spring-batch-core/src/test/resources/org/springframework/batch/core/launch/support/test-environment-with-registry-and-auto-register.xml +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/launch/support/test-environment-with-registry-and-auto-register.xml @@ -9,7 +9,7 @@ - + diff --git a/spring-batch-core/src/test/resources/simple-job-launcher-context.xml b/spring-batch-core/src/test/resources/simple-job-launcher-context.xml index 4e9986d0f6..1dbd4308a4 100644 --- a/spring-batch-core/src/test/resources/simple-job-launcher-context.xml +++ b/spring-batch-core/src/test/resources/simple-job-launcher-context.xml @@ -11,7 +11,7 @@ - + diff --git a/spring-batch-docs/modules/ROOT/pages/appendix.adoc b/spring-batch-docs/modules/ROOT/pages/appendix.adoc index 28d4ca344e..cbbb93e552 100644 --- a/spring-batch-docs/modules/ROOT/pages/appendix.adoc +++ b/spring-batch-docs/modules/ROOT/pages/appendix.adoc @@ -52,11 +52,10 @@ This reader stores message offsets in the execution context to support restart c rows, such that large datasets can be read without running out of memory.|Yes |`ListItemReader`|Provides the items from a list, one at a time.|No -|`MongoItemReader`|Given a `MongoOperations` object and a JSON-based MongoDB +|`MongoPagingItemReader`|Given a `MongoOperations` object and a JSON-based MongoDB query, provides items received from the `MongoOperations#find()` method.|Yes -|`Neo4jItemReader`|Given a `Neo4jOperations` object and the components of a - Cyhper query, items are returned as the result of the Neo4jOperations.query - method.|Yes +|`MongoCursorItemReader`|Given a `MongoOperations` object and a JSON-based MongoDB + query, provides items received from the `MongoOperations#stream()` method.|Yes |`RepositoryItemReader`|Given a Spring Data `PagingAndSortingRepository` object, a `Sort`, and the name of method to execute, returns items provided by the Spring Data repository implementation.|Yes @@ -106,9 +105,6 @@ This reader stores message offsets in the execution context to support restart c |`MongoItemWriter`|Given a `MongoOperations` object, items are written through the `MongoOperations.save(Object)` method. The actual write is delayed until the last possible moment before the transaction commits.|Yes -|`Neo4jItemWriter`|Given a `Neo4jOperations` object, items are persisted through the - `save(Object)` method or deleted through the `delete(Object)`, as dictated by the - `ItemWriter's` configuration|Yes |`PropertyExtractingDelegatingItemWriter`|Extends `AbstractMethodInvokingDelegator` creating arguments on the fly. Arguments are created by retrieving the values from the fields in the item to be processed (through a diff --git a/spring-batch-docs/modules/ROOT/pages/job/advanced-meta-data.adoc b/spring-batch-docs/modules/ROOT/pages/job/advanced-meta-data.adoc index bfa7ff3d1a..4102a8460f 100644 --- a/spring-batch-docs/modules/ROOT/pages/job/advanced-meta-data.adoc +++ b/spring-batch-docs/modules/ROOT/pages/job/advanced-meta-data.adoc @@ -177,58 +177,6 @@ You can populate a `JobRegistry` in one of the following ways: by using a bean post processor, or by using a smart initializing singleton or by using a registrar lifecycle component. The coming sections describe these mechanisms. -[[jobregistrybeanpostprocessor]] -=== JobRegistryBeanPostProcessor - -This is a bean post-processor that can register all jobs as they are created. - -[tabs] -==== -Java:: -+ -The following example shows how to include the `JobRegistryBeanPostProcessor` for a job -defined in Java: -+ -.Java Configuration -[source, java] ----- -@Bean -public JobRegistryBeanPostProcessor jobRegistryBeanPostProcessor(JobRegistry jobRegistry) { - JobRegistryBeanPostProcessor postProcessor = new JobRegistryBeanPostProcessor(); - postProcessor.setJobRegistry(jobRegistry); - return postProcessor; -} ----- - -XML:: -+ -The following example shows how to include the `JobRegistryBeanPostProcessor` for a job -defined in XML: -+ -.XML Configuration -[source, xml] ----- - - - ----- - -==== - - - -Although it is not strictly necessary, the post-processor in the -example has been given an `id` so that it can be included in child -contexts (for example, as a parent bean definition) and cause all jobs created -there to also be registered automatically. - -[WARNING] -.Deprecation -==== -As of version 5.2, the `JobRegistryBeanPostProcessor` class is deprecated in favor of -`JobRegistrySmartInitializingSingleton`, see xref:#jobregistrysmartinitializingsingleton[JobRegistrySmartInitializingSingleton]. -==== - [[jobregistrysmartinitializingsingleton]] === JobRegistrySmartInitializingSingleton @@ -343,7 +291,7 @@ configuration in the child, provided it should be the same as the parent. You can use `AutomaticJobRegistrar` in -conjunction with a `JobRegistryBeanPostProcessor` +conjunction with a `JobRegistrySmartInitializingSingleton` (as long as you also use `DefaultJobLoader`). For instance, this might be desirable if there are jobs defined in the main parent context as well as in the child diff --git a/spring-batch-docs/modules/ROOT/pages/readers-and-writers/item-reader-writer-implementations.adoc b/spring-batch-docs/modules/ROOT/pages/readers-and-writers/item-reader-writer-implementations.adoc index 628b2b85b7..4f3e165925 100644 --- a/spring-batch-docs/modules/ROOT/pages/readers-and-writers/item-reader-writer-implementations.adoc +++ b/spring-batch-docs/modules/ROOT/pages/readers-and-writers/item-reader-writer-implementations.adoc @@ -161,21 +161,21 @@ construct an instance of the `KafkaItemWriter`. == Database Readers Spring Batch offers the following database readers: -* xref:readers-and-writers/item-reader-writer-implementations.adoc#Neo4jItemReader[`Neo4jItemReader`] -* xref:readers-and-writers/item-reader-writer-implementations.adoc#mongoItemReader[`MongoItemReader`] +* xref:readers-and-writers/item-reader-writer-implementations.adoc#mongoPagingItemReader[`MongoPagingItemReader`] +* xref:readers-and-writers/item-reader-writer-implementations.adoc#mongoCursorItemReader[`MongoCursorItemReader`] * xref:readers-and-writers/item-reader-writer-implementations.adoc#repositoryItemReader[`RepositoryItemReader`] -[[Neo4jItemReader]] -=== `Neo4jItemReader` -The `Neo4jItemReader` is an `ItemReader` that reads objects from the graph database Neo4j -by using a paging technique. Spring Batch provides a `Neo4jItemReaderBuilder` to -construct an instance of the `Neo4jItemReader`. +[[mongoPagingItemReader]] +=== `MongoPagingItemReader` +The `MongoPagingItemReader` is an `ItemReader` that reads documents from MongoDB by using a +paging technique. Spring Batch provides a `MongoPagingItemReaderBuilder` to construct an +instance of the `MongoPagingItemReader`. -[[mongoItemReader]] -=== `MongoItemReader` -The `MongoItemReader` is an `ItemReader` that reads documents from MongoDB by using a -paging technique. Spring Batch provides a `MongoItemReaderBuilder` to construct an -instance of the `MongoItemReader`. +[[mongoCursorItemReader]] +=== `MongoCursorItemReader` +The `MongoCursorItemReader` is an `ItemReader` that reads documents from MongoDB by using a +streaming technique. Spring Batch provides a `MongoCursorItemReaderBuilder` to construct an +instance of the `MongoCursorItemReader`. [[repositoryItemReader]] === `RepositoryItemReader` @@ -187,18 +187,11 @@ construct an instance of the `RepositoryItemReader`. == Database Writers Spring Batch offers the following database writers: -* xref:readers-and-writers/item-reader-writer-implementations.adoc#neo4jItemWriter[`Neo4jItemWriter`] * xref:readers-and-writers/item-reader-writer-implementations.adoc#mongoItemWriter[`MongoItemWriter`] * xref:readers-and-writers/item-reader-writer-implementations.adoc#repositoryItemWriter[`RepositoryItemWriter`] * xref:readers-and-writers/item-reader-writer-implementations.adoc#jdbcBatchItemWriter[`JdbcBatchItemWriter`] * xref:readers-and-writers/item-reader-writer-implementations.adoc#jpaItemWriter[`JpaItemWriter`] -[[neo4jItemWriter]] -=== `Neo4jItemWriter` -The `Neo4jItemWriter` is an `ItemWriter` implementation that writes to a Neo4j database. -Spring Batch provides a `Neo4jItemWriterBuilder` to construct an instance of the -`Neo4jItemWriter`. - [[mongoItemWriter]] === `MongoItemWriter` The `MongoItemWriter` is an `ItemWriter` implementation that writes to a MongoDB store diff --git a/spring-batch-docs/modules/ROOT/pages/scalability.adoc b/spring-batch-docs/modules/ROOT/pages/scalability.adoc index b00353c4e6..48f711e6b2 100644 --- a/spring-batch-docs/modules/ROOT/pages/scalability.adoc +++ b/spring-batch-docs/modules/ROOT/pages/scalability.adoc @@ -133,29 +133,6 @@ Note also that there may be limits placed on concurrency by any pooled resources your step, such as a `DataSource`. Be sure to make the pool in those resources at least as large as the desired number of concurrent threads in the step. -[WARNING] -.Throttle limit deprecation -==== -As of v5.0, the throttle limit is deprecated with no replacement. If you want to replace the -current throttling mechanism in the default `TaskExecutorRepeatTemplate`, you need to provide -a custom `RepeatOperations` implementation (based on a `TaskExecutor` with a bounded task queue) -and set it on the step with `StepBuilder#stepOperations`: - -.Java Configuration -[source, java] ----- -@Bean -public Step sampleStep(RepeatOperations customRepeatOperations, JobRepository jobRepository, PlatformTransactionManager transactionManager) { - return new StepBuilder("sampleStep", jobRepository) - .chunk(10, transactionManager) - .reader(itemReader()) - .writer(itemWriter()) - .stepOperations(customRepeatOperations) - .build(); -} ----- -==== - There are some practical limitations of using multi-threaded `Step` implementations for some common batch use cases. Many participants in a `Step` (such as readers and writers) are stateful. If the state is not segregated by thread, those components are not diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/MongoItemReader.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/MongoItemReader.java deleted file mode 100644 index 9c8ce109d6..0000000000 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/MongoItemReader.java +++ /dev/null @@ -1,266 +0,0 @@ -/* - * Copyright 2012-2023 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.batch.item.data; - -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.Map; - -import org.bson.Document; -import org.bson.codecs.DecoderContext; - -import org.springframework.batch.item.ExecutionContext; -import org.springframework.batch.item.ItemReader; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort; -import org.springframework.data.mongodb.core.MongoOperations; -import org.springframework.data.mongodb.core.query.BasicQuery; -import org.springframework.data.mongodb.core.query.Query; -import org.springframework.data.mongodb.util.json.ParameterBindingDocumentCodec; -import org.springframework.data.mongodb.util.json.ParameterBindingJsonReader; -import org.springframework.util.Assert; -import org.springframework.util.ClassUtils; -import org.springframework.util.StringUtils; - -/** - *

    - * Restartable {@link ItemReader} that reads documents from MongoDB via a paging - * technique. - *

    - * - *

    - * If you set JSON String query {@link #setQuery(String)} then it executes the JSON to - * retrieve the requested documents. - *

    - * - *

    - * If you set Query object {@link #setQuery(Query)} then it executes the Query to retrieve - * the requested documents. - *

    - * - *

    - * The query is executed using paged requests specified in the {@link #setPageSize(int)}. - * Additional pages are requested as needed to provide data when the {@link #read()} - * method is called. - *

    - * - *

    - * The JSON String query provided supports parameter substitution via ?<index> - * placeholders where the <index> indicates the index of the parameterValue to - * substitute. - *

    - * - *

    - * The implementation is thread-safe between calls to {@link #open(ExecutionContext)}, but - * remember to use saveState=false if used in a multi-threaded client (no - * restart available). - *

    - * - * @author Michael Minella - * @author Takaaki Iida - * @author Mahmoud Ben Hassine - * @author Parikshit Dutta - * @deprecated Use {@link MongoPagingItemReader} instead. Scheduled for removal in v5.3 or - * later. - */ -@Deprecated(since = "5.1", forRemoval = true) -public class MongoItemReader extends AbstractPaginatedDataItemReader implements InitializingBean { - - protected MongoOperations template; - - protected Query query; - - protected String queryString; - - protected Class type; - - protected Sort sort; - - protected String hint; - - protected String fields; - - protected String collection; - - protected List parameterValues = new ArrayList<>(); - - public MongoItemReader() { - super(); - setName(ClassUtils.getShortName(MongoItemReader.class)); - } - - /** - * A Mongo Query to be used. - * @param query Mongo Query to be used. - */ - public void setQuery(Query query) { - this.query = query; - } - - /** - * Used to perform operations against the MongoDB instance. Also handles the mapping - * of documents to objects. - * @param template the MongoOperations instance to use - * @see MongoOperations - */ - public void setTemplate(MongoOperations template) { - this.template = template; - } - - /** - * A JSON formatted MongoDB query. Parameterization of the provided query is allowed - * via ?<index> placeholders where the <index> indicates the index of the - * parameterValue to substitute. - * @param queryString JSON formatted Mongo query - */ - public void setQuery(String queryString) { - this.queryString = queryString; - } - - /** - * The type of object to be returned for each {@link #read()} call. - * @param type the type of object to return - */ - public void setTargetType(Class type) { - this.type = type; - } - - /** - * {@link List} of values to be substituted in for each of the parameters in the - * query. - * @param parameterValues values - */ - public void setParameterValues(List parameterValues) { - Assert.notNull(parameterValues, "Parameter values must not be null"); - this.parameterValues = parameterValues; - } - - /** - * JSON defining the fields to be returned from the matching documents by MongoDB. - * @param fields JSON string that identifies the fields to sort by. - */ - public void setFields(String fields) { - this.fields = fields; - } - - /** - * {@link Map} of property - * names/{@link org.springframework.data.domain.Sort.Direction} values to sort the - * input by. - * @param sorts map of properties and direction to sort each. - */ - public void setSort(Map sorts) { - Assert.notNull(sorts, "Sorts must not be null"); - this.sort = convertToSort(sorts); - } - - /** - * @param collection Mongo collection to be queried. - */ - public void setCollection(String collection) { - this.collection = collection; - } - - /** - * JSON String telling MongoDB what index to use. - * @param hint string indicating what index to use. - */ - public void setHint(String hint) { - this.hint = hint; - } - - @Override - @SuppressWarnings("unchecked") - protected Iterator doPageRead() { - if (queryString != null) { - Pageable pageRequest = PageRequest.of(page, pageSize, sort); - - String populatedQuery = replacePlaceholders(queryString, parameterValues); - - Query mongoQuery; - - if (StringUtils.hasText(fields)) { - mongoQuery = new BasicQuery(populatedQuery, fields); - } - else { - mongoQuery = new BasicQuery(populatedQuery); - } - - mongoQuery.with(pageRequest); - - if (StringUtils.hasText(hint)) { - mongoQuery.withHint(hint); - } - - if (StringUtils.hasText(collection)) { - return (Iterator) template.find(mongoQuery, type, collection).iterator(); - } - else { - return (Iterator) template.find(mongoQuery, type).iterator(); - } - - } - else { - Pageable pageRequest = PageRequest.of(page, pageSize); - query.with(pageRequest); - - if (StringUtils.hasText(collection)) { - return (Iterator) template.find(query, type, collection).iterator(); - } - else { - return (Iterator) template.find(query, type).iterator(); - } - } - } - - /** - * Checks mandatory properties - * - * @see InitializingBean#afterPropertiesSet() - */ - @Override - public void afterPropertiesSet() throws Exception { - Assert.state(template != null, "An implementation of MongoOperations is required."); - Assert.state(type != null, "A type to convert the input into is required."); - Assert.state(queryString != null || query != null, "A query is required."); - - if (queryString != null) { - Assert.state(sort != null, "A sort is required."); - } - } - - protected String replacePlaceholders(String input, List values) { - ParameterBindingJsonReader reader = new ParameterBindingJsonReader(input, values.toArray()); - DecoderContext decoderContext = DecoderContext.builder().build(); - Document document = new ParameterBindingDocumentCodec().decode(reader, decoderContext); - return document.toJson(); - } - - protected Sort convertToSort(Map sorts) { - List sortValues = new ArrayList<>(sorts.size()); - - for (Map.Entry curSort : sorts.entrySet()) { - sortValues.add(new Sort.Order(curSort.getValue(), curSort.getKey())); - } - - return Sort.by(sortValues); - } - -} diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/MongoItemWriter.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/MongoItemWriter.java index b7aa27f375..6e8219512d 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/MongoItemWriter.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/MongoItemWriter.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2012-2025 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. @@ -97,19 +97,6 @@ public MongoItemWriter() { this.bufferKey = new Object(); } - /** - * Indicates if the items being passed to the writer are to be saved or removed from - * the data store. If set to false (default), the items will be saved or update using - * {@link Mode#UPSERT}. If set to true, then items will be removed. - * @param delete removal indicator - * @deprecated use {@link MongoItemWriter#setMode(Mode)} instead. Scheduled for - * removal in v5.3 or later. - */ - @Deprecated(since = "5.1", forRemoval = true) - public void setDelete(boolean delete) { - this.mode = (delete) ? Mode.REMOVE : Mode.UPSERT; - } - /** * Set the operating {@link Mode} to be applied by this writer. Defaults to * {@link Mode#UPSERT}. diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/MongoPagingItemReader.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/MongoPagingItemReader.java index 5c2278cacc..e9e8ff83d0 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/MongoPagingItemReader.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/MongoPagingItemReader.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2012-2025 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. @@ -15,16 +15,27 @@ */ package org.springframework.batch.item.data; +import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; +import org.bson.Document; +import org.bson.codecs.DecoderContext; import org.springframework.batch.item.ExecutionContext; import org.springframework.batch.item.ItemReader; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.mongodb.core.MongoOperations; +import org.springframework.data.mongodb.core.query.BasicQuery; import org.springframework.data.mongodb.core.query.Query; +import org.springframework.data.mongodb.util.json.ParameterBindingDocumentCodec; +import org.springframework.data.mongodb.util.json.ParameterBindingJsonReader; +import org.springframework.util.Assert; import org.springframework.util.ClassUtils; +import org.springframework.util.StringUtils; /** *

    @@ -67,78 +78,186 @@ * @author Mahmoud Ben Hassine * @author Parikshit Dutta */ -public class MongoPagingItemReader extends MongoItemReader { +public class MongoPagingItemReader extends AbstractPaginatedDataItemReader implements InitializingBean { + + protected MongoOperations template; + + protected Query query; + + protected String queryString; + + protected Class type; + + protected Sort sort; + + protected String hint; + + protected String fields; + + protected String collection; + + protected List parameterValues = new ArrayList<>(); - /** - * Create a new {@link MongoPagingItemReader}. - */ public MongoPagingItemReader() { + super(); setName(ClassUtils.getShortName(MongoPagingItemReader.class)); } - @Override - public void setTemplate(MongoOperations template) { - super.setTemplate(template); + /** + * A Mongo Query to be used. + * @param query Mongo Query to be used. + */ + public void setQuery(Query query) { + this.query = query; } - @Override - public void setQuery(Query query) { - super.setQuery(query); + /** + * Used to perform operations against the MongoDB instance. Also handles the mapping + * of documents to objects. + * @param template the MongoOperations instance to use + * @see MongoOperations + */ + public void setTemplate(MongoOperations template) { + this.template = template; } - @Override + /** + * A JSON formatted MongoDB query. Parameterization of the provided query is allowed + * via ?<index> placeholders where the <index> indicates the index of the + * parameterValue to substitute. + * @param queryString JSON formatted Mongo query + */ public void setQuery(String queryString) { - super.setQuery(queryString); + this.queryString = queryString; } - @Override + /** + * The type of object to be returned for each {@link #read()} call. + * @param type the type of object to return + */ public void setTargetType(Class type) { - super.setTargetType(type); + this.type = type; } - @Override + /** + * {@link List} of values to be substituted in for each of the parameters in the + * query. + * @param parameterValues values + */ public void setParameterValues(List parameterValues) { - super.setParameterValues(parameterValues); + Assert.notNull(parameterValues, "Parameter values must not be null"); + this.parameterValues = parameterValues; } - @Override + /** + * JSON defining the fields to be returned from the matching documents by MongoDB. + * @param fields JSON string that identifies the fields to sort by. + */ public void setFields(String fields) { - super.setFields(fields); + this.fields = fields; } - @Override + /** + * {@link Map} of property + * names/{@link org.springframework.data.domain.Sort.Direction} values to sort the + * input by. + * @param sorts map of properties and direction to sort each. + */ public void setSort(Map sorts) { - super.setSort(sorts); + Assert.notNull(sorts, "Sorts must not be null"); + this.sort = convertToSort(sorts); } - @Override + /** + * @param collection Mongo collection to be queried. + */ public void setCollection(String collection) { - super.setCollection(collection); + this.collection = collection; } - @Override + /** + * JSON String telling MongoDB what index to use. + * @param hint string indicating what index to use. + */ public void setHint(String hint) { - super.setHint(hint); + this.hint = hint; } @Override - public void afterPropertiesSet() throws Exception { - super.afterPropertiesSet(); + @SuppressWarnings("unchecked") + protected Iterator doPageRead() { + if (queryString != null) { + Pageable pageRequest = PageRequest.of(page, pageSize, sort); + + String populatedQuery = replacePlaceholders(queryString, parameterValues); + + Query mongoQuery; + + if (StringUtils.hasText(fields)) { + mongoQuery = new BasicQuery(populatedQuery, fields); + } + else { + mongoQuery = new BasicQuery(populatedQuery); + } + + mongoQuery.with(pageRequest); + + if (StringUtils.hasText(hint)) { + mongoQuery.withHint(hint); + } + + if (StringUtils.hasText(collection)) { + return (Iterator) template.find(mongoQuery, type, collection).iterator(); + } + else { + return (Iterator) template.find(mongoQuery, type).iterator(); + } + + } + else { + Pageable pageRequest = PageRequest.of(page, pageSize); + query.with(pageRequest); + + if (StringUtils.hasText(collection)) { + return (Iterator) template.find(query, type, collection).iterator(); + } + else { + return (Iterator) template.find(query, type).iterator(); + } + } } + /** + * Checks mandatory properties + * + * @see InitializingBean#afterPropertiesSet() + */ @Override - protected Iterator doPageRead() { - return super.doPageRead(); + public void afterPropertiesSet() throws Exception { + Assert.state(template != null, "An implementation of MongoOperations is required."); + Assert.state(type != null, "A type to convert the input into is required."); + Assert.state(queryString != null || query != null, "A query is required."); + + if (queryString != null) { + Assert.state(sort != null, "A sort is required."); + } } - @Override protected String replacePlaceholders(String input, List values) { - return super.replacePlaceholders(input, values); + ParameterBindingJsonReader reader = new ParameterBindingJsonReader(input, values.toArray()); + DecoderContext decoderContext = DecoderContext.builder().build(); + Document document = new ParameterBindingDocumentCodec().decode(reader, decoderContext); + return document.toJson(); } - @Override protected Sort convertToSort(Map sorts) { - return super.convertToSort(sorts); + List sortValues = new ArrayList<>(sorts.size()); + + for (Map.Entry curSort : sorts.entrySet()) { + sortValues.add(new Sort.Order(curSort.getValue(), curSort.getKey())); + } + + return Sort.by(sortValues); } -} +} \ No newline at end of file diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/Neo4jItemReader.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/Neo4jItemReader.java deleted file mode 100644 index 07daeaa9d6..0000000000 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/Neo4jItemReader.java +++ /dev/null @@ -1,217 +0,0 @@ -/* - * Copyright 2012-2023 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.batch.item.data; - -import java.util.ArrayList; -import java.util.Iterator; -import java.util.Map; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.neo4j.ogm.session.Session; -import org.neo4j.ogm.session.SessionFactory; - -import org.springframework.batch.item.ItemReader; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; - -/** - *

    - * Restartable {@link ItemReader} that reads objects from the graph database Neo4j via a - * paging technique. - *

    - * - *

    - * It executes cypher queries built from the statement fragments provided to retrieve the - * requested data. The query is executed using paged requests of a size specified in - * {@link #setPageSize(int)}. Additional pages are requested as needed when the - * {@link #read()} method is called. On restart, the reader will begin again at the same - * number item it left off at. - *

    - * - *

    - * Performance is dependent on your Neo4J configuration (embedded or remote) as well as - * page size. Setting a fairly large page size and using a commit interval that matches - * the page size should provide better performance. - *

    - * - *

    - * This implementation is thread-safe between calls to - * {@link #open(org.springframework.batch.item.ExecutionContext)}, however you should set - * saveState=false if used in a multi-threaded environment (no restart - * available). - *

    - * - * @author Michael Minella - * @author Mahmoud Ben Hassine - * @deprecated since 5.0 in favor of the item reader from ... - */ -@Deprecated -public class Neo4jItemReader extends AbstractPaginatedDataItemReader implements InitializingBean { - - protected Log logger = LogFactory.getLog(getClass()); - - private SessionFactory sessionFactory; - - private String startStatement; - - private String returnStatement; - - private String matchStatement; - - private String whereStatement; - - private String orderByStatement; - - private Class targetType; - - private Map parameterValues; - - /** - * Optional parameters to be used in the cypher query. - * @param parameterValues the parameter values to be used in the cypher query - */ - public void setParameterValues(Map parameterValues) { - this.parameterValues = parameterValues; - } - - protected final Map getParameterValues() { - return this.parameterValues; - } - - /** - * The start segment of the cypher query. START is prepended to the statement provided - * and should not be included. - * @param startStatement the start fragment of the cypher query. - */ - public void setStartStatement(String startStatement) { - this.startStatement = startStatement; - } - - /** - * The return statement of the cypher query. RETURN is prepended to the statement - * provided and should not be included - * @param returnStatement the return fragment of the cypher query. - */ - public void setReturnStatement(String returnStatement) { - this.returnStatement = returnStatement; - } - - /** - * An optional match fragment of the cypher query. MATCH is prepended to the statement - * provided and should not be included. - * @param matchStatement the match fragment of the cypher query - */ - public void setMatchStatement(String matchStatement) { - this.matchStatement = matchStatement; - } - - /** - * An optional where fragment of the cypher query. WHERE is prepended to the statement - * provided and should not be included. - * @param whereStatement where fragment of the cypher query - */ - public void setWhereStatement(String whereStatement) { - this.whereStatement = whereStatement; - } - - /** - * A list of properties to order the results by. This is required so that subsequent - * page requests pull back the segment of results correctly. ORDER BY is prepended to - * the statement provided and should not be included. - * @param orderByStatement order by fragment of the cypher query. - */ - public void setOrderByStatement(String orderByStatement) { - this.orderByStatement = orderByStatement; - } - - protected SessionFactory getSessionFactory() { - return sessionFactory; - } - - /** - * Establish the session factory for the reader. - * @param sessionFactory the factory to use for the reader. - */ - public void setSessionFactory(SessionFactory sessionFactory) { - this.sessionFactory = sessionFactory; - } - - /** - * The object type to be returned from each call to {@link #read()} - * @param targetType the type of object to return. - */ - public void setTargetType(Class targetType) { - this.targetType = targetType; - } - - protected final Class getTargetType() { - return this.targetType; - } - - protected String generateLimitCypherQuery() { - StringBuilder query = new StringBuilder(128); - - query.append("START ").append(startStatement); - query.append(matchStatement != null ? " MATCH " + matchStatement : ""); - query.append(whereStatement != null ? " WHERE " + whereStatement : ""); - query.append(" RETURN ").append(returnStatement); - query.append(" ORDER BY ").append(orderByStatement); - query.append(" SKIP ").append(pageSize * page); - query.append(" LIMIT ").append(pageSize); - - String resultingQuery = query.toString(); - - if (logger.isDebugEnabled()) { - logger.debug(resultingQuery); - } - - return resultingQuery; - } - - /** - * Checks mandatory properties - * - * @see InitializingBean#afterPropertiesSet() - */ - @Override - public void afterPropertiesSet() throws Exception { - Assert.state(sessionFactory != null, "A SessionFactory is required"); - Assert.state(targetType != null, "The type to be returned is required"); - Assert.state(StringUtils.hasText(startStatement), "A START statement is required"); - Assert.state(StringUtils.hasText(returnStatement), "A RETURN statement is required"); - Assert.state(StringUtils.hasText(orderByStatement), "A ORDER BY statement is required"); - } - - @SuppressWarnings("unchecked") - @Override - protected Iterator doPageRead() { - Session session = getSessionFactory().openSession(); - - Iterable queryResults = session.query(getTargetType(), generateLimitCypherQuery(), getParameterValues()); - - if (queryResults != null) { - return queryResults.iterator(); - } - else { - return new ArrayList().iterator(); - } - } - -} diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/Neo4jItemWriter.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/Neo4jItemWriter.java deleted file mode 100644 index c9bcd35bf6..0000000000 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/Neo4jItemWriter.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright 2012-2024 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.batch.item.data; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.neo4j.ogm.session.Session; -import org.neo4j.ogm.session.SessionFactory; - -import org.springframework.batch.item.Chunk; -import org.springframework.batch.item.ItemWriter; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.util.Assert; - -/** - *

    - * A {@link ItemWriter} implementation that writes to a Neo4j database. - *

    - * - *

    - * This writer is thread-safe once all properties are set (normal singleton behavior) so - * it can be used in multiple concurrent transactions. - *

    - * - * @author Michael Minella - * @author Glenn Renfro - * @author Mahmoud Ben Hassine - * @deprecated since 5.0 in favor of the item writer from ... - * - */ -@Deprecated -public class Neo4jItemWriter implements ItemWriter, InitializingBean { - - protected static final Log logger = LogFactory.getLog(Neo4jItemWriter.class); - - private boolean delete = false; - - private SessionFactory sessionFactory; - - /** - * Boolean flag indicating whether the writer should save or delete the item at write - * time. - * @param delete true if write should delete item, false if item should be saved. - * Default is false. - */ - public void setDelete(boolean delete) { - this.delete = delete; - } - - /** - * Establish the session factory that will be used to create {@link Session} instances - * for interacting with Neo4j. - * @param sessionFactory sessionFactory to be used. - */ - public void setSessionFactory(SessionFactory sessionFactory) { - this.sessionFactory = sessionFactory; - } - - /** - * Checks mandatory properties - * - * @see InitializingBean#afterPropertiesSet() - */ - @Override - public void afterPropertiesSet() throws Exception { - Assert.state(this.sessionFactory != null, "A SessionFactory is required"); - } - - /** - * Write all items to the data store. - * - * @see org.springframework.batch.item.ItemWriter#write(Chunk) - */ - @Override - public void write(Chunk chunk) throws Exception { - if (!chunk.isEmpty()) { - doWrite(chunk); - } - } - - /** - * Performs the actual write using the template. This can be overridden by a subclass - * if necessary. - * @param items the list of items to be persisted. - */ - protected void doWrite(Chunk items) { - if (delete) { - delete(items); - } - else { - save(items); - } - } - - private void delete(Chunk items) { - Session session = this.sessionFactory.openSession(); - - for (T item : items) { - session.delete(item); - } - } - - private void save(Chunk items) { - Session session = this.sessionFactory.openSession(); - - for (T item : items) { - session.save(item); - } - } - -} diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/builder/MongoItemReaderBuilder.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/builder/MongoItemReaderBuilder.java deleted file mode 100644 index 17747b8212..0000000000 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/builder/MongoItemReaderBuilder.java +++ /dev/null @@ -1,299 +0,0 @@ -/* - * Copyright 2017-2023 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.batch.item.data.builder; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Map; - -import org.springframework.batch.item.data.MongoItemReader; -import org.springframework.data.domain.Sort; -import org.springframework.data.mongodb.core.MongoOperations; -import org.springframework.data.mongodb.core.query.Query; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; - -/** - * A builder implementation for the {@link MongoItemReader} - * - * @author Glenn Renfro - * @author Mahmoud Ben Hassine - * @author Drummond Dawson - * @author Parikshit Dutta - * @since 4.0 - * @see MongoItemReader - * @deprecated Use {@link MongoPagingItemReaderBuilder} instead. Scheduled for removal in - * v5.3 or later. - */ -@Deprecated(since = "5.1", forRemoval = true) -public class MongoItemReaderBuilder { - - protected MongoOperations template; - - protected String jsonQuery; - - protected Class targetType; - - protected Map sorts; - - protected String hint; - - protected String fields; - - protected String collection; - - protected List parameterValues = new ArrayList<>(); - - protected int pageSize = 10; - - protected boolean saveState = true; - - protected String name; - - protected int maxItemCount = Integer.MAX_VALUE; - - protected int currentItemCount; - - protected Query query; - - /** - * Configure if the state of the - * {@link org.springframework.batch.item.ItemStreamSupport} should be persisted within - * the {@link org.springframework.batch.item.ExecutionContext} for restart purposes. - * @param saveState defaults to true - * @return The current instance of the builder. - */ - public MongoItemReaderBuilder saveState(boolean saveState) { - this.saveState = saveState; - - return this; - } - - /** - * The name used to calculate the key within the - * {@link org.springframework.batch.item.ExecutionContext}. Required if - * {@link #saveState(boolean)} is set to true. - * @param name name of the reader instance - * @return The current instance of the builder. - * @see org.springframework.batch.item.ItemStreamSupport#setName(String) - */ - public MongoItemReaderBuilder name(String name) { - this.name = name; - - return this; - } - - /** - * Configure the max number of items to be read. - * @param maxItemCount the max items to be read - * @return The current instance of the builder. - * @see org.springframework.batch.item.support.AbstractItemCountingItemStreamItemReader#setMaxItemCount(int) - */ - public MongoItemReaderBuilder maxItemCount(int maxItemCount) { - this.maxItemCount = maxItemCount; - - return this; - } - - /** - * Index for the current item. Used on restarts to indicate where to start from. - * @param currentItemCount current index - * @return this instance for method chaining - * @see org.springframework.batch.item.support.AbstractItemCountingItemStreamItemReader#setCurrentItemCount(int) - */ - public MongoItemReaderBuilder currentItemCount(int currentItemCount) { - this.currentItemCount = currentItemCount; - - return this; - } - - /** - * Used to perform operations against the MongoDB instance. Also handles the mapping - * of documents to objects. - * @param template the MongoOperations instance to use - * @see MongoOperations - * @return The current instance of the builder - * @see MongoItemReader#setTemplate(MongoOperations) - */ - public MongoItemReaderBuilder template(MongoOperations template) { - this.template = template; - - return this; - } - - /** - * A JSON formatted MongoDB jsonQuery. Parameterization of the provided jsonQuery is - * allowed via ?<index> placeholders where the <index> indicates the index - * of the parameterValue to substitute. - * @param query JSON formatted Mongo jsonQuery - * @return The current instance of the builder - * @see MongoItemReader#setQuery(String) - */ - public MongoItemReaderBuilder jsonQuery(String query) { - this.jsonQuery = query; - - return this; - } - - /** - * The type of object to be returned for each {@link MongoItemReader#read()} call. - * @param targetType the type of object to return - * @return The current instance of the builder - * @see MongoItemReader#setTargetType(Class) - */ - public MongoItemReaderBuilder targetType(Class targetType) { - this.targetType = targetType; - - return this; - } - - /** - * {@link List} of values to be substituted in for each of the parameters in the - * query. - * @param parameterValues values - * @return The current instance of the builder - * @see MongoItemReader#setParameterValues(List) - */ - public MongoItemReaderBuilder parameterValues(List parameterValues) { - this.parameterValues = parameterValues; - - return this; - } - - /** - * Values to be substituted in for each of the parameters in the query. - * @param parameterValues values - * @return The current instance of the builder - * @see MongoItemReader#setParameterValues(List) - */ - public MongoItemReaderBuilder parameterValues(Object... parameterValues) { - return parameterValues(Arrays.asList(parameterValues)); - } - - /** - * JSON defining the fields to be returned from the matching documents by MongoDB. - * @param fields JSON string that identifies the fields to sort by. - * @return The current instance of the builder - * @see MongoItemReader#setFields(String) - */ - public MongoItemReaderBuilder fields(String fields) { - this.fields = fields; - - return this; - } - - /** - * {@link Map} of property - * names/{@link org.springframework.data.domain.Sort.Direction} values to sort the - * input by. - * @param sorts map of properties and direction to sort each. - * @return The current instance of the builder - * @see MongoItemReader#setSort(Map) - */ - public MongoItemReaderBuilder sorts(Map sorts) { - this.sorts = sorts; - - return this; - } - - /** - * Establish an optional collection that can be queried. - * @param collection Mongo collection to be queried. - * @return The current instance of the builder - * @see MongoItemReader#setCollection(String) - */ - public MongoItemReaderBuilder collection(String collection) { - this.collection = collection; - - return this; - } - - /** - * JSON String telling MongoDB what index to use. - * @param hint string indicating what index to use. - * @return The current instance of the builder - * @see MongoItemReader#setHint(String) - */ - public MongoItemReaderBuilder hint(String hint) { - this.hint = hint; - - return this; - } - - /** - * The number of items to be read with each page. - * @param pageSize the number of items - * @return this instance for method chaining - * @see MongoItemReader#setPageSize(int) - */ - public MongoItemReaderBuilder pageSize(int pageSize) { - this.pageSize = pageSize; - - return this; - } - - /** - * Provide a Spring Data Mongo {@link Query}. This will take precedence over a JSON - * configured query. - * @param query Query to execute - * @return this instance for method chaining - * @see MongoItemReader#setQuery(Query) - */ - public MongoItemReaderBuilder query(Query query) { - this.query = query; - - return this; - } - - /** - * Validates and builds a {@link MongoItemReader}. - * @return a {@link MongoItemReader} - */ - public MongoItemReader build() { - Assert.notNull(this.template, "template is required."); - if (this.saveState) { - Assert.hasText(this.name, "A name is required when saveState is set to true"); - } - Assert.notNull(this.targetType, "targetType is required."); - Assert.state(StringUtils.hasText(this.jsonQuery) || this.query != null, "A query is required"); - - if (StringUtils.hasText(this.jsonQuery) || this.query != null) { - Assert.notNull(this.sorts, "sorts map is required."); - } - - MongoItemReader reader = new MongoItemReader<>(); - reader.setTemplate(this.template); - reader.setTargetType(this.targetType); - reader.setQuery(this.jsonQuery); - reader.setSort(this.sorts); - reader.setHint(this.hint); - reader.setFields(this.fields); - reader.setCollection(this.collection); - reader.setParameterValues(this.parameterValues); - reader.setQuery(this.query); - - reader.setPageSize(this.pageSize); - reader.setName(this.name); - reader.setSaveState(this.saveState); - reader.setCurrentItemCount(this.currentItemCount); - reader.setMaxItemCount(this.maxItemCount); - - return reader; - } - -} diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/builder/MongoItemWriterBuilder.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/builder/MongoItemWriterBuilder.java index 4df60a7d4c..e2cfdcdb48 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/builder/MongoItemWriterBuilder.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/builder/MongoItemWriterBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2023 the original author or authors. + * Copyright 2017-2025 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. @@ -37,23 +37,6 @@ public class MongoItemWriterBuilder { private Mode mode = Mode.UPSERT; - /** - * Indicates if the items being passed to the writer are to be saved or removed from - * the data store. If set to false (default), the items will be saved. If set to true, - * the items will be removed. - * @param delete removal indicator - * @return The current instance of the builder - * @see MongoItemWriter#setDelete(boolean) - * @deprecated Use {@link MongoItemWriterBuilder#mode(Mode)} instead. Scheduled for - * removal in v5.3 or later. - */ - @Deprecated(since = "5.1", forRemoval = true) - public MongoItemWriterBuilder delete(boolean delete) { - this.mode = (delete) ? Mode.REMOVE : Mode.UPSERT; - - return this; - } - /** * Set the operating {@link Mode} to be applied by this writer. Defaults to * {@link Mode#UPSERT}. diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/builder/MongoPagingItemReaderBuilder.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/builder/MongoPagingItemReaderBuilder.java index 286f043f0e..480b3a7c92 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/builder/MongoPagingItemReaderBuilder.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/builder/MongoPagingItemReaderBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2023 the original author or authors. + * Copyright 2017-2025 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. @@ -15,6 +15,7 @@ */ package org.springframework.batch.item.data.builder; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; @@ -36,7 +37,35 @@ * @author Parikshit Dutta * @since 5.1 */ -public class MongoPagingItemReaderBuilder extends MongoItemReaderBuilder { +public class MongoPagingItemReaderBuilder { + + protected MongoOperations template; + + protected String jsonQuery; + + protected Class targetType; + + protected Map sorts; + + protected String hint; + + protected String fields; + + protected String collection; + + protected List parameterValues = new ArrayList<>(); + + protected int pageSize = 10; + + protected boolean saveState = true; + + protected String name; + + protected int maxItemCount = Integer.MAX_VALUE; + + protected int currentItemCount; + + protected Query query; /** * Configure if the state of the @@ -228,7 +257,6 @@ public MongoPagingItemReaderBuilder query(Query query) { return this; } - @Override public MongoPagingItemReader build() { Assert.notNull(this.template, "template is required."); if (this.saveState) { diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/builder/Neo4jItemReaderBuilder.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/builder/Neo4jItemReaderBuilder.java deleted file mode 100644 index 1884d2b181..0000000000 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/builder/Neo4jItemReaderBuilder.java +++ /dev/null @@ -1,265 +0,0 @@ -/* - * Copyright 2017-2023 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.batch.item.data.builder; - -import java.util.Map; - -import org.neo4j.ogm.session.SessionFactory; - -import org.springframework.batch.item.data.Neo4jItemReader; -import org.springframework.util.Assert; - -/** - * A builder for the {@link Neo4jItemReader}. - * - * @author Glenn Renfro - * @author Mahmoud Ben Hassine - * @since 4.0 - * @see Neo4jItemReader - * @deprecated since 5.0 in favor of the item reader builder from ... - */ -@Deprecated -public class Neo4jItemReaderBuilder { - - private SessionFactory sessionFactory; - - private String startStatement; - - private String returnStatement; - - private String matchStatement; - - private String whereStatement; - - private String orderByStatement; - - private Class targetType; - - private Map parameterValues; - - private int pageSize = 10; - - private boolean saveState = true; - - private String name; - - private int maxItemCount = Integer.MAX_VALUE; - - private int currentItemCount; - - /** - * Configure if the state of the - * {@link org.springframework.batch.item.ItemStreamSupport} should be persisted within - * the {@link org.springframework.batch.item.ExecutionContext} for restart purposes. - * @param saveState defaults to true - * @return The current instance of the builder. - */ - public Neo4jItemReaderBuilder saveState(boolean saveState) { - this.saveState = saveState; - - return this; - } - - /** - * The name used to calculate the key within the - * {@link org.springframework.batch.item.ExecutionContext}. Required if - * {@link #saveState(boolean)} is set to true. - * @param name name of the reader instance - * @return The current instance of the builder. - * @see org.springframework.batch.item.ItemStreamSupport#setName(String) - */ - public Neo4jItemReaderBuilder name(String name) { - this.name = name; - - return this; - } - - /** - * Configure the max number of items to be read. - * @param maxItemCount the max items to be read - * @return The current instance of the builder. - * @see org.springframework.batch.item.support.AbstractItemCountingItemStreamItemReader#setMaxItemCount(int) - */ - public Neo4jItemReaderBuilder maxItemCount(int maxItemCount) { - this.maxItemCount = maxItemCount; - - return this; - } - - /** - * Index for the current item. Used on restarts to indicate where to start from. - * @param currentItemCount current index - * @return this instance for method chaining - * @see org.springframework.batch.item.support.AbstractItemCountingItemStreamItemReader#setCurrentItemCount(int) - */ - public Neo4jItemReaderBuilder currentItemCount(int currentItemCount) { - this.currentItemCount = currentItemCount; - - return this; - } - - /** - * Establish the session factory for the reader. - * @param sessionFactory the factory to use for the reader. - * @return this instance for method chaining - * @see Neo4jItemReader#setSessionFactory(SessionFactory) - */ - public Neo4jItemReaderBuilder sessionFactory(SessionFactory sessionFactory) { - this.sessionFactory = sessionFactory; - - return this; - } - - /** - * The number of items to be read with each page. - * @param pageSize the number of items - * @return this instance for method chaining - * @see Neo4jItemReader#setPageSize(int) - */ - public Neo4jItemReaderBuilder pageSize(int pageSize) { - this.pageSize = pageSize; - - return this; - } - - /** - * Optional parameters to be used in the cypher query. - * @param parameterValues the parameter values to be used in the cypher query - * @return this instance for method chaining - * @see Neo4jItemReader#setParameterValues(Map) - */ - public Neo4jItemReaderBuilder parameterValues(Map parameterValues) { - this.parameterValues = parameterValues; - - return this; - } - - /** - * The start segment of the cypher query. START is prepended to the statement provided - * and should not be included. - * @param startStatement the start fragment of the cypher query. - * @return this instance for method chaining - * @see Neo4jItemReader#setStartStatement(String) - */ - public Neo4jItemReaderBuilder startStatement(String startStatement) { - this.startStatement = startStatement; - - return this; - } - - /** - * The return statement of the cypher query. RETURN is prepended to the statement - * provided and should not be included - * @param returnStatement the return fragment of the cypher query. - * @return this instance for method chaining - * @see Neo4jItemReader#setReturnStatement(String) - */ - public Neo4jItemReaderBuilder returnStatement(String returnStatement) { - this.returnStatement = returnStatement; - - return this; - } - - /** - * An optional match fragment of the cypher query. MATCH is prepended to the statement - * provided and should not be included. - * @param matchStatement the match fragment of the cypher query - * @return this instance for method chaining - * @see Neo4jItemReader#setMatchStatement(String) - */ - public Neo4jItemReaderBuilder matchStatement(String matchStatement) { - this.matchStatement = matchStatement; - - return this; - } - - /** - * An optional where fragment of the cypher query. WHERE is prepended to the statement - * provided and should not be included. - * @param whereStatement where fragment of the cypher query - * @return this instance for method chaining - * @see Neo4jItemReader#setWhereStatement(String) - */ - public Neo4jItemReaderBuilder whereStatement(String whereStatement) { - this.whereStatement = whereStatement; - - return this; - } - - /** - * A list of properties to order the results by. This is required so that subsequent - * page requests pull back the segment of results correctly. ORDER BY is prepended to - * the statement provided and should not be included. - * @param orderByStatement order by fragment of the cypher query. - * @return this instance for method chaining - * @see Neo4jItemReader#setOrderByStatement(String) - */ - public Neo4jItemReaderBuilder orderByStatement(String orderByStatement) { - this.orderByStatement = orderByStatement; - - return this; - } - - /** - * The object type to be returned from each call to {@link Neo4jItemReader#read()} - * @param targetType the type of object to return. - * @return this instance for method chaining - * @see Neo4jItemReader#setTargetType(Class) - */ - public Neo4jItemReaderBuilder targetType(Class targetType) { - this.targetType = targetType; - - return this; - } - - /** - * Returns a fully constructed {@link Neo4jItemReader}. - * @return a new {@link Neo4jItemReader} - */ - public Neo4jItemReader build() { - if (this.saveState) { - Assert.hasText(this.name, "A name is required when saveState is set to true"); - } - Assert.notNull(this.sessionFactory, "sessionFactory is required."); - Assert.notNull(this.targetType, "targetType is required."); - Assert.hasText(this.startStatement, "startStatement is required."); - Assert.hasText(this.returnStatement, "returnStatement is required."); - Assert.hasText(this.orderByStatement, "orderByStatement is required."); - Assert.isTrue(this.pageSize > 0, "pageSize must be greater than zero"); - Assert.isTrue(this.maxItemCount > 0, "maxItemCount must be greater than zero"); - Assert.isTrue(this.maxItemCount > this.currentItemCount, "maxItemCount must be greater than currentItemCount"); - - Neo4jItemReader reader = new Neo4jItemReader<>(); - reader.setMatchStatement(this.matchStatement); - reader.setOrderByStatement(this.orderByStatement); - reader.setPageSize(this.pageSize); - reader.setParameterValues(this.parameterValues); - reader.setSessionFactory(this.sessionFactory); - reader.setTargetType(this.targetType); - reader.setStartStatement(this.startStatement); - reader.setReturnStatement(this.returnStatement); - reader.setWhereStatement(this.whereStatement); - reader.setName(this.name); - reader.setSaveState(this.saveState); - reader.setCurrentItemCount(this.currentItemCount); - reader.setMaxItemCount(this.maxItemCount); - - return reader; - } - -} diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/builder/Neo4jItemWriterBuilder.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/builder/Neo4jItemWriterBuilder.java deleted file mode 100644 index 1e4e334799..0000000000 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/builder/Neo4jItemWriterBuilder.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright 2017-2023 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.batch.item.data.builder; - -import org.neo4j.ogm.session.Session; -import org.neo4j.ogm.session.SessionFactory; - -import org.springframework.batch.item.data.Neo4jItemWriter; -import org.springframework.util.Assert; - -/** - * A builder implementation for the {@link Neo4jItemWriter} - * - * @author Glenn Renfro - * @author Mahmoud Ben Hassine - * @since 4.0 - * @see Neo4jItemWriter - * @deprecated since 5.0 in favor of the item writer builder from ... - */ -@Deprecated -public class Neo4jItemWriterBuilder { - - private boolean delete = false; - - private SessionFactory sessionFactory; - - /** - * Boolean flag indicating whether the writer should save or delete the item at write - * time. - * @param delete true if write should delete item, false if item should be saved. - * Default is false. - * @return The current instance of the builder - * @see Neo4jItemWriter#setDelete(boolean) - */ - public Neo4jItemWriterBuilder delete(boolean delete) { - this.delete = delete; - - return this; - } - - /** - * Establish the session factory that will be used to create {@link Session} instances - * for interacting with Neo4j. - * @param sessionFactory sessionFactory to be used. - * @return The current instance of the builder - * @see Neo4jItemWriter#setSessionFactory(SessionFactory) - */ - public Neo4jItemWriterBuilder sessionFactory(SessionFactory sessionFactory) { - this.sessionFactory = sessionFactory; - - return this; - } - - /** - * Validates and builds a {@link org.springframework.batch.item.data.Neo4jItemWriter}. - * @return a {@link Neo4jItemWriter} - */ - public Neo4jItemWriter build() { - Assert.notNull(sessionFactory, "sessionFactory is required."); - Neo4jItemWriter writer = new Neo4jItemWriter<>(); - writer.setDelete(this.delete); - writer.setSessionFactory(this.sessionFactory); - return writer; - } - -} diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SqlPagingQueryUtils.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SqlPagingQueryUtils.java index 2ae66e4388..265c6275c3 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SqlPagingQueryUtils.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SqlPagingQueryUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2024 the original author or authors. + * Copyright 2006-2025 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. @@ -61,36 +61,6 @@ public static String generateLimitSqlQuery(AbstractSqlPagingQueryProvider provid return sql.toString(); } - /** - * Generate SQL query string using a LIMIT clause - * @param provider {@link AbstractSqlPagingQueryProvider} providing the implementation - * specifics - * @param remainingPageQuery is this query for the remaining pages (true) as opposed - * to the first page (false) - * @param limitClause the implementation specific limit clause to be used - * @return the generated query - * @deprecated as of v5.0 in favor of - * {@link #generateLimitGroupedSqlQuery(AbstractSqlPagingQueryProvider, java.lang.String)} - */ - @Deprecated - public static String generateLimitGroupedSqlQuery(AbstractSqlPagingQueryProvider provider, - boolean remainingPageQuery, String limitClause) { - StringBuilder sql = new StringBuilder(); - sql.append("SELECT * "); - sql.append(" FROM ("); - sql.append("SELECT ").append(provider.getSelectClause()); - sql.append(" FROM ").append(provider.getFromClause()); - sql.append(provider.getWhereClause() == null ? "" : " WHERE " + provider.getWhereClause()); - buildGroupByClause(provider, sql); - sql.append(") AS MAIN_QRY "); - sql.append("WHERE "); - buildSortConditions(provider, sql); - sql.append(" ORDER BY ").append(buildSortClause(provider)); - sql.append(" ").append(limitClause); - - return sql.toString(); - } - /** * Generate SQL query string using a LIMIT clause * @param provider {@link AbstractSqlPagingQueryProvider} providing the implementation @@ -136,34 +106,6 @@ public static String generateTopSqlQuery(AbstractSqlPagingQueryProvider provider return sql.toString(); } - /** - * Generate SQL query string using a TOP clause - * @param provider {@link AbstractSqlPagingQueryProvider} providing the implementation - * specifics - * @param remainingPageQuery is this query for the remaining pages (true) as opposed - * to the first page (false) - * @param topClause the implementation specific top clause to be used - * @return the generated query - * @deprecated since v5.2 in favor of - * {@link #generateGroupedTopSqlQuery(AbstractSqlPagingQueryProvider, String)} - */ - @Deprecated - public static String generateGroupedTopSqlQuery(AbstractSqlPagingQueryProvider provider, boolean remainingPageQuery, - String topClause) { - StringBuilder sql = new StringBuilder(); - sql.append("SELECT ").append(topClause).append(" * FROM ("); - sql.append("SELECT ").append(provider.getSelectClause()); - sql.append(" FROM ").append(provider.getFromClause()); - sql.append(provider.getWhereClause() == null ? "" : " WHERE " + provider.getWhereClause()); - buildGroupByClause(provider, sql); - sql.append(") AS MAIN_QRY "); - sql.append("WHERE "); - buildSortConditions(provider, sql); - sql.append(" ORDER BY ").append(buildSortClause(provider)); - - return sql.toString(); - } - /** * Generate SQL query string using a TOP clause * @param provider {@link AbstractSqlPagingQueryProvider} providing the implementation diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SqlWindowingPagingQueryProvider.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SqlWindowingPagingQueryProvider.java deleted file mode 100644 index 00e0d04711..0000000000 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SqlWindowingPagingQueryProvider.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright 2006-2024 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.batch.item.database.support; - -import org.springframework.util.StringUtils; - -/** - * Generic Paging Query Provider using standard SQL:2003 windowing functions. These - * features are supported by DB2, Oracle, SQL Server 2005, Sybase and Apache Derby version - * 10.4.1.3 - * - * @author Thomas Risberg - * @author Michael Minella - * @since 2.0 - * @deprecated since 5.2.1 with no replacement. Scheduled for removal in 6.0. - */ -@Deprecated(forRemoval = true) -public class SqlWindowingPagingQueryProvider extends AbstractSqlPagingQueryProvider { - - @Override - public String generateFirstPageQuery(int pageSize) { - StringBuilder sql = new StringBuilder(); - sql.append("SELECT * FROM ( "); - sql.append("SELECT ") - .append(StringUtils.hasText(getOrderedQueryAlias()) ? getOrderedQueryAlias() + ".*, " : "*, "); - sql.append("ROW_NUMBER() OVER (").append(getOverClause()); - sql.append(") AS ROW_NUMBER"); - sql.append(getOverSubstituteClauseStart()); - sql.append(" FROM ") - .append(getFromClause()) - .append(getWhereClause() == null ? "" : " WHERE " + getWhereClause()); - sql.append(getGroupClause() == null ? "" : " GROUP BY " + getGroupClause()); - sql.append(getOverSubstituteClauseEnd()); - sql.append(") ") - .append(getSubQueryAlias()) - .append("WHERE ") - .append(extractTableAlias()) - .append("ROW_NUMBER <= ") - .append(pageSize); - sql.append(" ORDER BY ").append(SqlPagingQueryUtils.buildSortClause(this)); - - return sql.toString(); - } - - protected String getOrderedQueryAlias() { - return ""; - } - - protected Object getSubQueryAlias() { - return "AS TMP_SUB "; - } - - protected Object extractTableAlias() { - String alias = String.valueOf(getSubQueryAlias()); - if (StringUtils.hasText(alias) && alias.toUpperCase().startsWith("AS")) { - alias = alias.substring(3).trim() + "."; - } - return alias; - } - - @Override - public String generateRemainingPagesQuery(int pageSize) { - StringBuilder sql = new StringBuilder(); - sql.append("SELECT * FROM ( "); - sql.append("SELECT ") - .append(StringUtils.hasText(getOrderedQueryAlias()) ? getOrderedQueryAlias() + ".*, " : "*, "); - sql.append("ROW_NUMBER() OVER (").append(getOverClause()); - sql.append(") AS ROW_NUMBER"); - sql.append(getOverSubstituteClauseStart()); - sql.append(" FROM ").append(getFromClause()); - if (getWhereClause() != null) { - sql.append(" WHERE "); - sql.append(getWhereClause()); - } - - sql.append(getGroupClause() == null ? "" : " GROUP BY " + getGroupClause()); - sql.append(getOverSubstituteClauseEnd()); - sql.append(") ") - .append(getSubQueryAlias()) - .append("WHERE ") - .append(extractTableAlias()) - .append("ROW_NUMBER <= ") - .append(pageSize); - sql.append(" AND "); - SqlPagingQueryUtils.buildSortConditions(this, sql); - sql.append(" ORDER BY ").append(SqlPagingQueryUtils.buildSortClause(this)); - - return sql.toString(); - } - - protected String getOverClause() { - StringBuilder sql = new StringBuilder(); - - sql.append(" ORDER BY ").append(buildSortClause(this)); - - return sql.toString(); - } - - protected String getOverSubstituteClauseStart() { - return ""; - } - - protected String getOverSubstituteClauseEnd() { - return ""; - } - - /** - * Generates ORDER BY attributes based on the sort keys. - * @param provider the paging query provider - * @return a String that can be appended to an ORDER BY clause. - */ - private String buildSortClause(AbstractSqlPagingQueryProvider provider) { - return SqlPagingQueryUtils.buildSortClause(provider.getSortKeysWithoutAliases()); - } - -} diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/repeat/listener/RepeatListenerSupport.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/repeat/listener/RepeatListenerSupport.java deleted file mode 100644 index cea6e890da..0000000000 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/repeat/listener/RepeatListenerSupport.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2006-2021 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.batch.repeat.listener; - -import org.springframework.batch.repeat.RepeatStatus; -import org.springframework.batch.repeat.RepeatContext; -import org.springframework.batch.repeat.RepeatListener; - -/** - * Empty method implementation of {@link RepeatListener}. - * - * @author Dave Syer - * @author Mahmoud Ben Hassine - * @deprecated as of v5.0 in favor of the default methods in {@link RepeatListener}. - */ -@Deprecated -public class RepeatListenerSupport implements RepeatListener { - - @Override - public void before(RepeatContext context) { - } - - @Override - public void after(RepeatContext context, RepeatStatus result) { - } - - @Override - public void close(RepeatContext context) { - } - - @Override - public void onError(RepeatContext context, Throwable e) { - } - - @Override - public void open(RepeatContext context) { - } - -} diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/repeat/support/TaskExecutorRepeatTemplate.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/repeat/support/TaskExecutorRepeatTemplate.java index 44dba5b449..005fefe4f7 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/repeat/support/TaskExecutorRepeatTemplate.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/repeat/support/TaskExecutorRepeatTemplate.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2023 the original author or authors. + * Copyright 2006-2025 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. @@ -59,25 +59,6 @@ public class TaskExecutorRepeatTemplate extends RepeatTemplate { private TaskExecutor taskExecutor = new SyncTaskExecutor(); - /** - * Public setter for the throttle limit. The throttle limit is the largest number of - * concurrent tasks that can be executing at one time - if a new task arrives and the - * throttle limit is breached we wait for one of the executing tasks to finish before - * submitting the new one to the {@link TaskExecutor}. Default value is - * {@link #DEFAULT_THROTTLE_LIMIT}. N.B. when used with a thread pooled - * {@link TaskExecutor} the thread pool might prevent the throttle limit actually - * being reached (so make the core pool size larger than the throttle limit if - * possible). - * @param throttleLimit the throttleLimit to set. - * @deprecated since 5.0, scheduled for removal in 6.0. Use a pooled - * {@link TaskExecutor} implemenation with a limited capacity of its task queue - * instead. - */ - @Deprecated(since = "5.0", forRemoval = true) - public void setThrottleLimit(int throttleLimit) { - this.throttleLimit = throttleLimit; - } - /** * Setter for task executor to be used to run the individual item callbacks. * @param taskExecutor a TaskExecutor diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/SystemPropertyInitializer.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/SystemPropertyInitializer.java deleted file mode 100644 index ea2ff9bacf..0000000000 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/SystemPropertyInitializer.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2006-2024 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.batch.support; - -import org.springframework.beans.factory.InitializingBean; -import org.springframework.util.Assert; - -/** - * Helper class that sets up a System property with a default value. A System property is - * created with the specified key name, and default value (i.e. if the property already - * exists it is not changed). - * - * @author Dave Syer - * @deprecated since 5.2 with no replacement. - */ -@Deprecated(since = "5.2.0", forRemoval = true) -public class SystemPropertyInitializer implements InitializingBean { - - /** - * Name of system property used by default. - */ - public static final String ENVIRONMENT = "org.springframework.batch.support.SystemPropertyInitializer.ENVIRONMENT"; - - private String keyName = ENVIRONMENT; - - private String defaultValue; - - /** - * Set the key name for the System property that is created. Defaults to - * {@link #ENVIRONMENT}. - * @param keyName the key name to set - */ - public void setKeyName(String keyName) { - this.keyName = keyName; - } - - /** - * Mandatory property specifying the default value of the System property. - * @param defaultValue the default value to set - */ - public void setDefaultValue(String defaultValue) { - this.defaultValue = defaultValue; - } - - /** - * Sets the System property with the provided name and default value. - * - * @see InitializingBean#afterPropertiesSet() - */ - @Override - public void afterPropertiesSet() throws Exception { - Assert.state(defaultValue != null || System.getProperty(keyName) != null, - "Either a default value must be specified or the value should already be set for System property: " - + keyName); - System.setProperty(keyName, System.getProperty(keyName, defaultValue)); - } - -} diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/data/MongoItemWriterTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/data/MongoItemWriterTests.java index 55919f242d..1ed55109af 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/data/MongoItemWriterTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/data/MongoItemWriterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2022 the original author or authors. + * Copyright 2013-2025 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. @@ -199,7 +199,7 @@ void testWriteTransactionReadOnly() { @Test void testRemoveNoObjectIdNoCollection() throws Exception { - writer.setDelete(true); + writer.setMode(Mode.REMOVE); Chunk items = Chunk.of(new Item("Foo"), new Item("Bar")); writer.write(items); @@ -210,7 +210,7 @@ void testRemoveNoObjectIdNoCollection() throws Exception { @Test void testRemoveNoObjectIdWithCollection() throws Exception { - writer.setDelete(true); + writer.setMode(Mode.REMOVE); Chunk items = Chunk.of(new Item("Foo"), new Item("Bar")); writer.setCollection("collection"); @@ -222,7 +222,7 @@ void testRemoveNoObjectIdWithCollection() throws Exception { @Test void testRemoveNoTransactionNoCollection() throws Exception { - writer.setDelete(true); + writer.setMode(Mode.REMOVE); Chunk items = Chunk.of(new Item(1), new Item(2)); writer.write(items); @@ -233,7 +233,7 @@ void testRemoveNoTransactionNoCollection() throws Exception { @Test void testRemoveNoTransactionWithCollection() throws Exception { - writer.setDelete(true); + writer.setMode(Mode.REMOVE); Chunk items = Chunk.of(new Item(1), new Item(2)); writer.setCollection("collection"); diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/data/Neo4jItemReaderTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/data/Neo4jItemReaderTests.java deleted file mode 100644 index 34d2791810..0000000000 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/data/Neo4jItemReaderTests.java +++ /dev/null @@ -1,171 +0,0 @@ -/* - * Copyright 2013-2022 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.batch.item.data; - -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.neo4j.ogm.session.Session; -import org.neo4j.ogm.session.SessionFactory; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.isNull; -import static org.mockito.Mockito.when; - -@SuppressWarnings("deprecation") -@ExtendWith(MockitoExtension.class) -class Neo4jItemReaderTests { - - @Mock - private Iterable result; - - @Mock - private SessionFactory sessionFactory; - - @Mock - private Session session; - - private Neo4jItemReader buildSessionBasedReader() throws Exception { - Neo4jItemReader reader = new Neo4jItemReader<>(); - - reader.setSessionFactory(this.sessionFactory); - reader.setTargetType(String.class); - reader.setStartStatement("n=node(*)"); - reader.setReturnStatement("*"); - reader.setOrderByStatement("n.age"); - reader.setPageSize(50); - reader.afterPropertiesSet(); - - return reader; - } - - @Test - void testAfterPropertiesSet() throws Exception { - - Neo4jItemReader reader = new Neo4jItemReader<>(); - - Exception exception = assertThrows(IllegalStateException.class, reader::afterPropertiesSet); - assertEquals("A SessionFactory is required", exception.getMessage()); - - reader.setSessionFactory(this.sessionFactory); - - exception = assertThrows(IllegalStateException.class, reader::afterPropertiesSet); - assertEquals("The type to be returned is required", exception.getMessage()); - - reader.setTargetType(String.class); - - exception = assertThrows(IllegalStateException.class, reader::afterPropertiesSet); - assertEquals("A START statement is required", exception.getMessage()); - - reader.setStartStatement("n=node(*)"); - - exception = assertThrows(IllegalStateException.class, reader::afterPropertiesSet); - assertEquals("A RETURN statement is required", exception.getMessage()); - - reader.setReturnStatement("n.name, n.phone"); - - exception = assertThrows(IllegalStateException.class, reader::afterPropertiesSet); - assertEquals("A ORDER BY statement is required", exception.getMessage()); - - reader.setOrderByStatement("n.age"); - reader.afterPropertiesSet(); - - reader = new Neo4jItemReader<>(); - reader.setSessionFactory(this.sessionFactory); - reader.setTargetType(String.class); - reader.setStartStatement("n=node(*)"); - reader.setReturnStatement("n.name, n.phone"); - reader.setOrderByStatement("n.age"); - - reader.afterPropertiesSet(); - } - - @Test - void testNullResultsWithSession() throws Exception { - - Neo4jItemReader itemReader = buildSessionBasedReader(); - - ArgumentCaptor query = ArgumentCaptor.forClass(String.class); - - when(this.sessionFactory.openSession()).thenReturn(this.session); - when(this.session.query(eq(String.class), query.capture(), isNull())).thenReturn(null); - - assertFalse(itemReader.doPageRead().hasNext()); - assertEquals("START n=node(*) RETURN * ORDER BY n.age SKIP 0 LIMIT 50", query.getValue()); - } - - @Test - void testNoResultsWithSession() throws Exception { - Neo4jItemReader itemReader = buildSessionBasedReader(); - ArgumentCaptor query = ArgumentCaptor.forClass(String.class); - - when(this.sessionFactory.openSession()).thenReturn(this.session); - when(this.session.query(eq(String.class), query.capture(), isNull())).thenReturn(result); - when(result.iterator()).thenReturn(Collections.emptyIterator()); - - assertFalse(itemReader.doPageRead().hasNext()); - assertEquals("START n=node(*) RETURN * ORDER BY n.age SKIP 0 LIMIT 50", query.getValue()); - } - - @Test - void testResultsWithMatchAndWhereWithSession() throws Exception { - Neo4jItemReader itemReader = buildSessionBasedReader(); - itemReader.setMatchStatement("n -- m"); - itemReader.setWhereStatement("has(n.name)"); - itemReader.setReturnStatement("m"); - itemReader.afterPropertiesSet(); - - when(this.sessionFactory.openSession()).thenReturn(this.session); - when(this.session.query(String.class, - "START n=node(*) MATCH n -- m WHERE has(n.name) RETURN m ORDER BY n.age SKIP 0 LIMIT 50", null)) - .thenReturn(result); - when(result.iterator()).thenReturn(Arrays.asList("foo", "bar", "baz").iterator()); - - assertTrue(itemReader.doPageRead().hasNext()); - } - - @Test - void testResultsWithMatchAndWhereWithParametersWithSession() throws Exception { - Neo4jItemReader itemReader = buildSessionBasedReader(); - Map params = new HashMap<>(); - params.put("foo", "bar"); - itemReader.setParameterValues(params); - itemReader.setMatchStatement("n -- m"); - itemReader.setWhereStatement("has(n.name)"); - itemReader.setReturnStatement("m"); - itemReader.afterPropertiesSet(); - - when(this.sessionFactory.openSession()).thenReturn(this.session); - when(this.session.query(String.class, - "START n=node(*) MATCH n -- m WHERE has(n.name) RETURN m ORDER BY n.age SKIP 0 LIMIT 50", params)) - .thenReturn(result); - when(result.iterator()).thenReturn(Arrays.asList("foo", "bar", "baz").iterator()); - - assertTrue(itemReader.doPageRead().hasNext()); - } - -} diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/data/Neo4jItemWriterTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/data/Neo4jItemWriterTests.java deleted file mode 100644 index 2a9650f601..0000000000 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/data/Neo4jItemWriterTests.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright 2013-2022 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.batch.item.data; - -import org.junit.jupiter.api.Test; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoSettings; -import org.mockito.quality.Strictness; -import org.neo4j.ogm.session.Session; -import org.neo4j.ogm.session.SessionFactory; - -import org.springframework.batch.item.Chunk; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; -import static org.mockito.Mockito.when; - -@SuppressWarnings("deprecation") -@MockitoSettings(strictness = Strictness.LENIENT) -class Neo4jItemWriterTests { - - private Neo4jItemWriter writer; - - @Mock - private SessionFactory sessionFactory; - - @Mock - private Session session; - - @Test - void testAfterPropertiesSet() throws Exception { - - writer = new Neo4jItemWriter<>(); - - Exception exception = assertThrows(IllegalStateException.class, writer::afterPropertiesSet); - assertEquals("A SessionFactory is required", exception.getMessage()); - - writer.setSessionFactory(this.sessionFactory); - - writer.afterPropertiesSet(); - - writer = new Neo4jItemWriter<>(); - - writer.setSessionFactory(this.sessionFactory); - - writer.afterPropertiesSet(); - } - - @Test - void testWriteNoItemsWithSession() throws Exception { - writer = new Neo4jItemWriter<>(); - - writer.setSessionFactory(this.sessionFactory); - writer.afterPropertiesSet(); - - when(this.sessionFactory.openSession()).thenReturn(this.session); - writer.write(new Chunk<>()); - - verifyNoInteractions(this.session); - } - - @Test - void testWriteItemsWithSession() throws Exception { - writer = new Neo4jItemWriter<>(); - - writer.setSessionFactory(this.sessionFactory); - writer.afterPropertiesSet(); - - Chunk items = new Chunk<>(); - items.add("foo"); - items.add("bar"); - - when(this.sessionFactory.openSession()).thenReturn(this.session); - writer.write(items); - - verify(this.session).save("foo"); - verify(this.session).save("bar"); - } - - @Test - void testDeleteItemsWithSession() throws Exception { - writer = new Neo4jItemWriter<>(); - - writer.setSessionFactory(this.sessionFactory); - writer.afterPropertiesSet(); - - Chunk items = new Chunk<>(); - items.add("foo"); - items.add("bar"); - - writer.setDelete(true); - - when(this.sessionFactory.openSession()).thenReturn(this.session); - writer.write(items); - - verify(this.session).delete("foo"); - verify(this.session).delete("bar"); - } - -} diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/data/builder/MongoItemWriterBuilderTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/data/builder/MongoItemWriterBuilderTests.java index 699dd40f57..13b4f58bf0 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/data/builder/MongoItemWriterBuilderTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/data/builder/MongoItemWriterBuilderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2022 the original author or authors. + * Copyright 2017-2025 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. @@ -112,7 +112,9 @@ void testWriteToCollection() throws Exception { @Test void testDelete() throws Exception { - MongoItemWriter writer = new MongoItemWriterBuilder().template(this.template).delete(true).build(); + MongoItemWriter writer = new MongoItemWriterBuilder().template(this.template) + .mode(MongoItemWriter.Mode.REMOVE) + .build(); writer.write(this.removeItems); diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/data/builder/Neo4jItemReaderBuilderTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/data/builder/Neo4jItemReaderBuilderTests.java deleted file mode 100644 index 08d7dacc57..0000000000 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/data/builder/Neo4jItemReaderBuilderTests.java +++ /dev/null @@ -1,258 +0,0 @@ -/* - * Copyright 2017-2022 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.batch.item.data.builder; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.neo4j.ogm.session.Session; -import org.neo4j.ogm.session.SessionFactory; - -import org.springframework.batch.item.data.Neo4jItemReader; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.Mockito.when; - -/** - * @author Glenn Renfro - */ -@SuppressWarnings("deprecation") -@ExtendWith(MockitoExtension.class) -class Neo4jItemReaderBuilderTests { - - @Mock - private Iterable result; - - @Mock - private SessionFactory sessionFactory; - - @Mock - private Session session; - - @Test - void testFullyQualifiedItemReader() throws Exception { - Neo4jItemReader itemReader = new Neo4jItemReaderBuilder().sessionFactory(this.sessionFactory) - .targetType(String.class) - .startStatement("n=node(*)") - .orderByStatement("n.age") - .pageSize(50) - .name("bar") - .matchStatement("n -- m") - .whereStatement("has(n.name)") - .returnStatement("m") - .build(); - - when(this.sessionFactory.openSession()).thenReturn(this.session); - when(this.session.query(String.class, - "START n=node(*) MATCH n -- m WHERE has(n.name) RETURN m ORDER BY n.age SKIP 0 LIMIT 50", null)) - .thenReturn(result); - when(result.iterator()).thenReturn(Arrays.asList("foo", "bar", "baz").iterator()); - - assertEquals("foo", itemReader.read(), "The expected value was not returned by reader."); - assertEquals("bar", itemReader.read(), "The expected value was not returned by reader."); - assertEquals("baz", itemReader.read(), "The expected value was not returned by reader."); - } - - @Test - void testCurrentSize() throws Exception { - Neo4jItemReader itemReader = new Neo4jItemReaderBuilder().sessionFactory(this.sessionFactory) - .targetType(String.class) - .startStatement("n=node(*)") - .orderByStatement("n.age") - .pageSize(50) - .name("bar") - .returnStatement("m") - .currentItemCount(0) - .maxItemCount(1) - .build(); - - when(this.sessionFactory.openSession()).thenReturn(this.session); - when(this.session.query(String.class, "START n=node(*) RETURN m ORDER BY n.age SKIP 0 LIMIT 50", null)) - .thenReturn(result); - when(result.iterator()).thenReturn(Arrays.asList("foo", "bar", "baz").iterator()); - - assertEquals("foo", itemReader.read(), "The expected value was not returned by reader."); - assertNull(itemReader.read(), "The expected value was not should be null."); - } - - @Test - void testResultsWithMatchAndWhereWithParametersWithSession() throws Exception { - Map params = new HashMap<>(); - params.put("foo", "bar"); - Neo4jItemReader itemReader = new Neo4jItemReaderBuilder().sessionFactory(this.sessionFactory) - .targetType(String.class) - .startStatement("n=node(*)") - .returnStatement("*") - .orderByStatement("n.age") - .pageSize(50) - .name("foo") - .parameterValues(params) - .matchStatement("n -- m") - .whereStatement("has(n.name)") - .returnStatement("m") - .build(); - - when(this.sessionFactory.openSession()).thenReturn(this.session); - when(this.session.query(String.class, - "START n=node(*) MATCH n -- m WHERE has(n.name) RETURN m ORDER BY n.age SKIP 0 LIMIT 50", params)) - .thenReturn(result); - when(result.iterator()).thenReturn(Arrays.asList("foo", "bar", "baz").iterator()); - - assertEquals("foo", itemReader.read(), "The expected value was not returned by reader."); - } - - @Test - void testNoSessionFactory() { - var builder = new Neo4jItemReaderBuilder().targetType(String.class) - .startStatement("n=node(*)") - .returnStatement("*") - .orderByStatement("n.age") - .pageSize(50) - .name("bar"); - Exception exception = assertThrows(IllegalArgumentException.class, builder::build); - assertEquals("sessionFactory is required.", exception.getMessage()); - } - - @Test - void testZeroPageSize() { - validateExceptionMessage(new Neo4jItemReaderBuilder().sessionFactory(this.sessionFactory) - .targetType(String.class) - .startStatement("n=node(*)") - .returnStatement("*") - .orderByStatement("n.age") - .pageSize(0) - .name("foo") - .matchStatement("n -- m") - .whereStatement("has(n.name)") - .returnStatement("m"), "pageSize must be greater than zero"); - } - - @Test - void testZeroMaxItemCount() { - validateExceptionMessage(new Neo4jItemReaderBuilder().sessionFactory(this.sessionFactory) - .targetType(String.class) - .startStatement("n=node(*)") - .returnStatement("*") - .orderByStatement("n.age") - .pageSize(5) - .maxItemCount(0) - .name("foo") - .matchStatement("n -- m") - .whereStatement("has(n.name)") - .returnStatement("m"), "maxItemCount must be greater than zero"); - } - - @Test - void testCurrentItemCountGreaterThanMaxItemCount() { - validateExceptionMessage(new Neo4jItemReaderBuilder().sessionFactory(this.sessionFactory) - .targetType(String.class) - .startStatement("n=node(*)") - .returnStatement("*") - .orderByStatement("n.age") - .pageSize(5) - .maxItemCount(5) - .currentItemCount(6) - .name("foo") - .matchStatement("n -- m") - .whereStatement("has(n.name)") - .returnStatement("m"), "maxItemCount must be greater than currentItemCount"); - } - - @Test - void testNullName() { - validateExceptionMessage(new Neo4jItemReaderBuilder().sessionFactory(this.sessionFactory) - .targetType(String.class) - .startStatement("n=node(*)") - .returnStatement("*") - .orderByStatement("n.age") - .pageSize(50), "A name is required when saveState is set to true"); - - // tests that name is not required if saveState is set to false. - new Neo4jItemReaderBuilder().sessionFactory(this.sessionFactory) - .targetType(String.class) - .startStatement("n=node(*)") - .returnStatement("*") - .orderByStatement("n.age") - .saveState(false) - .pageSize(50) - .build(); - } - - @Test - void testNullTargetType() { - validateExceptionMessage(new Neo4jItemReaderBuilder().sessionFactory(this.sessionFactory) - .startStatement("n=node(*)") - .returnStatement("*") - .orderByStatement("n.age") - .pageSize(50) - .name("bar") - .matchStatement("n -- m") - .whereStatement("has(n.name)") - .returnStatement("m"), "targetType is required."); - } - - @Test - void testNullStartStatement() { - validateExceptionMessage(new Neo4jItemReaderBuilder().sessionFactory(this.sessionFactory) - .targetType(String.class) - .returnStatement("*") - .orderByStatement("n.age") - .pageSize(50) - .name("bar") - .matchStatement("n -- m") - .whereStatement("has(n.name)") - .returnStatement("m"), "startStatement is required."); - } - - @Test - void testNullReturnStatement() { - validateExceptionMessage(new Neo4jItemReaderBuilder().sessionFactory(this.sessionFactory) - .targetType(String.class) - .startStatement("n=node(*)") - .orderByStatement("n.age") - .pageSize(50) - .name("bar") - .matchStatement("n -- m") - .whereStatement("has(n.name)"), "returnStatement is required."); - } - - @Test - void testNullOrderByStatement() { - validateExceptionMessage(new Neo4jItemReaderBuilder().sessionFactory(this.sessionFactory) - .targetType(String.class) - .startStatement("n=node(*)") - .returnStatement("*") - .pageSize(50) - .name("bar") - .matchStatement("n -- m") - .whereStatement("has(n.name)") - .returnStatement("m"), "orderByStatement is required."); - } - - private void validateExceptionMessage(Neo4jItemReaderBuilder builder, String message) { - Exception exception = assertThrows(IllegalArgumentException.class, builder::build); - assertEquals(message, exception.getMessage()); - } - -} diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/data/builder/Neo4jItemWriterBuilderTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/data/builder/Neo4jItemWriterBuilderTests.java deleted file mode 100644 index 3f6629483c..0000000000 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/data/builder/Neo4jItemWriterBuilderTests.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright 2017-2022 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.batch.item.data.builder; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.neo4j.ogm.session.Session; -import org.neo4j.ogm.session.SessionFactory; - -import org.springframework.batch.item.Chunk; -import org.springframework.batch.item.data.Neo4jItemWriter; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -/** - * @author Glenn Renfro - * @author Mahmoud Ben Hassine - */ -@SuppressWarnings("deprecation") -@ExtendWith(MockitoExtension.class) -class Neo4jItemWriterBuilderTests { - - @Mock - private SessionFactory sessionFactory; - - @Mock - private Session session; - - @Test - void testBasicWriter() throws Exception { - Neo4jItemWriter writer = new Neo4jItemWriterBuilder().sessionFactory(this.sessionFactory) - .build(); - Chunk items = new Chunk<>(); - items.add("foo"); - items.add("bar"); - - when(this.sessionFactory.openSession()).thenReturn(this.session); - writer.write(items); - - verify(this.session).save("foo"); - verify(this.session).save("bar"); - verify(this.session, never()).delete("foo"); - verify(this.session, never()).delete("bar"); - } - - @Test - void testBasicDelete() throws Exception { - Neo4jItemWriter writer = new Neo4jItemWriterBuilder().delete(true) - .sessionFactory(this.sessionFactory) - .build(); - Chunk items = new Chunk<>(); - items.add("foo"); - items.add("bar"); - - when(this.sessionFactory.openSession()).thenReturn(this.session); - writer.write(items); - - verify(this.session).delete("foo"); - verify(this.session).delete("bar"); - verify(this.session, never()).save("foo"); - verify(this.session, never()).save("bar"); - } - - @Test - void testNoSessionFactory() { - Exception exception = assertThrows(IllegalArgumentException.class, - () -> new Neo4jItemWriterBuilder().build()); - assertEquals("sessionFactory is required.", exception.getMessage()); - } - -} diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/SqlWindowingPagingQueryProviderTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/SqlWindowingPagingQueryProviderTests.java deleted file mode 100644 index cd58aecfb9..0000000000 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/SqlWindowingPagingQueryProviderTests.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2006-2024 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.batch.item.database.support; - -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -/** - * @author Thomas Risberg - * @author Michael Minella - */ -class SqlWindowingPagingQueryProviderTests extends AbstractSqlPagingQueryProviderTests { - - @SuppressWarnings("removal") - SqlWindowingPagingQueryProviderTests() { - pagingQueryProvider = new SqlWindowingPagingQueryProvider(); - } - - @Test - @Override - void testGenerateFirstPageQuery() { - String sql = "SELECT * FROM ( SELECT *, ROW_NUMBER() OVER ( ORDER BY id ASC) AS ROW_NUMBER FROM foo WHERE bar = 1) AS TMP_SUB WHERE TMP_SUB.ROW_NUMBER <= 100 ORDER BY id ASC"; - String s = pagingQueryProvider.generateFirstPageQuery(pageSize); - assertEquals(sql, s); - } - - @Test - @Override - void testGenerateRemainingPagesQuery() { - String sql = "SELECT * FROM ( SELECT *, ROW_NUMBER() OVER ( ORDER BY id ASC) AS ROW_NUMBER FROM foo WHERE bar = 1) AS TMP_SUB WHERE TMP_SUB.ROW_NUMBER <= 100 AND ((id > ?)) ORDER BY id ASC"; - String s = pagingQueryProvider.generateRemainingPagesQuery(pageSize); - assertEquals(sql, s); - } - - @Test - @Override - void testGenerateFirstPageQueryWithGroupBy() { - pagingQueryProvider.setGroupClause("dep"); - String sql = "SELECT * FROM ( SELECT *, ROW_NUMBER() OVER ( ORDER BY id ASC) AS ROW_NUMBER FROM foo WHERE bar = 1 GROUP BY dep) AS TMP_SUB WHERE TMP_SUB.ROW_NUMBER <= 100 ORDER BY id ASC"; - String s = pagingQueryProvider.generateFirstPageQuery(pageSize); - assertEquals(sql, s); - } - - @Test - @Override - void testGenerateRemainingPagesQueryWithGroupBy() { - pagingQueryProvider.setGroupClause("dep"); - String sql = "SELECT * FROM ( SELECT *, ROW_NUMBER() OVER ( ORDER BY id ASC) AS ROW_NUMBER FROM foo WHERE bar = 1 GROUP BY dep) AS TMP_SUB WHERE TMP_SUB.ROW_NUMBER <= 100 AND ((id > ?)) ORDER BY id ASC"; - String s = pagingQueryProvider.generateRemainingPagesQuery(pageSize); - assertEquals(sql, s); - } - - @Override - String getFirstPageSqlWithMultipleSortKeys() { - return "SELECT * FROM ( SELECT *, ROW_NUMBER() OVER ( ORDER BY name ASC, id DESC) AS ROW_NUMBER FROM foo WHERE bar = 1) AS TMP_SUB WHERE TMP_SUB.ROW_NUMBER <= 100 ORDER BY name ASC, id DESC"; - } - - @Override - String getRemainingSqlWithMultipleSortKeys() { - return "SELECT * FROM ( SELECT *, ROW_NUMBER() OVER ( ORDER BY name ASC, id DESC) AS ROW_NUMBER FROM foo WHERE bar = 1) AS TMP_SUB WHERE TMP_SUB.ROW_NUMBER <= 100 AND ((name > ?) OR (name = ? AND id < ?)) ORDER BY name ASC, id DESC"; - } - -} diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/repeat/support/TaskExecutorRepeatTemplateAsynchronousTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/repeat/support/TaskExecutorRepeatTemplateAsynchronousTests.java index 5d6dd52924..89a393ae49 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/repeat/support/TaskExecutorRepeatTemplateAsynchronousTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/repeat/support/TaskExecutorRepeatTemplateAsynchronousTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2023 the original author or authors. + * Copyright 2006-2025 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. @@ -159,7 +159,6 @@ void testThrottleLimit() { SimpleAsyncTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor(); taskExecutor.setConcurrencyLimit(300); template.setTaskExecutor(taskExecutor); - template.setThrottleLimit(throttleLimit); String threadName = Thread.currentThread().getName(); Set threadNames = ConcurrentHashMap.newKeySet(); diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/repeat/support/TaskExecutorRepeatTemplateBulkAsynchronousTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/repeat/support/TaskExecutorRepeatTemplateBulkAsynchronousTests.java index cb39231a65..47dd4e892f 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/repeat/support/TaskExecutorRepeatTemplateBulkAsynchronousTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/repeat/support/TaskExecutorRepeatTemplateBulkAsynchronousTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2023 the original author or authors. + * Copyright 2006-2025 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. @@ -18,12 +18,10 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.atomic.AtomicInteger; import org.apache.commons.logging.Log; @@ -35,7 +33,6 @@ import org.springframework.batch.repeat.RepeatContext; import org.springframework.batch.repeat.RepeatStatus; import org.springframework.batch.repeat.policy.SimpleCompletionPolicy; -import org.springframework.core.task.SimpleAsyncTaskExecutor; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; /** @@ -54,8 +51,6 @@ class TaskExecutorRepeatTemplateBulkAsynchronousTests { private int total = 1000; - private int throttleLimit = 30; - private volatile int early = Integer.MAX_VALUE; private volatile int error = Integer.MAX_VALUE; @@ -77,7 +72,6 @@ void setUp() { threadPool.setQueueCapacity(0); threadPool.afterPropertiesSet(); template.setTaskExecutor(threadPool); - template.setThrottleLimit(throttleLimit); items = Collections.synchronizedList(new ArrayList<>()); @@ -117,102 +111,6 @@ void tearDown() { threadPool.destroy(); } - @Test - void testThrottleLimit() { - - template.iterate(callback); - int frequency = Collections.frequency(items, null); - assertEquals(total, items.size() - frequency); - assertTrue(frequency > 1); - assertTrue(frequency <= throttleLimit + 1); - - } - - @Test - void testThrottleLimitEarlyFinish() { - - early = 2; - - template.iterate(callback); - int frequency = Collections.frequency(items, null); - assertEquals(total, items.size() - frequency); - assertTrue(frequency > 1); - assertTrue(frequency <= throttleLimit + 1); - - } - - @Test - void testThrottleLimitEarlyFinishThreadStarvation() { - - early = 2; - ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor(); - // Set the concurrency limit below the throttle limit for possible - // starvation condition - taskExecutor.setMaxPoolSize(20); - taskExecutor.setCorePoolSize(10); - taskExecutor.setQueueCapacity(0); - // This is the most sensible setting, otherwise the bookkeeping in - // ResultHolderResultQueue gets out of whack when tasks are aborted. - taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); - taskExecutor.afterPropertiesSet(); - template.setTaskExecutor(taskExecutor); - - template.iterate(callback); - int frequency = Collections.frequency(items, null); - // Extra tasks will be submitted before the termination is detected - assertEquals(total, items.size() - frequency); - assertTrue(frequency <= throttleLimit + 1); - - taskExecutor.destroy(); - - } - - @Test - void testThrottleLimitEarlyFinishOneThread() { - - early = 4; - SimpleAsyncTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor(); - taskExecutor.setConcurrencyLimit(1); - - // This is kind of slow with only one thread, so reduce size: - throttleLimit = 10; - total = 20; - - template.setThrottleLimit(throttleLimit); - template.setTaskExecutor(taskExecutor); - - template.iterate(callback); - int frequency = Collections.frequency(items, null); - assertEquals(total, items.size() - frequency); - assertTrue(frequency <= throttleLimit + 1); - - } - - @Test - void testThrottleLimitWithEarlyCompletion() { - - early = 2; - template.setCompletionPolicy(new SimpleCompletionPolicy(10)); - - template.iterate(callback); - int frequency = Collections.frequency(items, null); - assertEquals(10, items.size() - frequency); - assertEquals(0, frequency); - - } - - @Test - void testThrottleLimitWithError() { - - error = 50; - - Exception exception = assertThrows(Exception.class, () -> template.iterate(callback)); - assertEquals("Planned", exception.getMessage()); - int frequency = Collections.frequency(items, null); - assertEquals(0, frequency); - - } - @Test void testErrorThrownByCallback() { diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/repeat/support/TaskExecutorRepeatTemplateTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/repeat/support/TaskExecutorRepeatTemplateTests.java index c9245d7174..32926ce554 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/repeat/support/TaskExecutorRepeatTemplateTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/repeat/support/TaskExecutorRepeatTemplateTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2022 the original author or authors. + * Copyright 2006-2025 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. @@ -16,12 +16,9 @@ package org.springframework.batch.repeat.support; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; - -import org.junit.jupiter.api.Test; - /** * @author Dave Syer + * @author Mahmoud Ben Hassine */ public class TaskExecutorRepeatTemplateTests extends SimpleRepeatTemplateTests { @@ -30,10 +27,4 @@ public RepeatTemplate getRepeatTemplate() { return new TaskExecutorRepeatTemplate(); } - @Test - void testSetThrottleLimit() { - // no check for illegal values - assertDoesNotThrow(() -> new TaskExecutorRepeatTemplate().setThrottleLimit(-1)); - } - } diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/support/SystemPropertyInitializerTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/support/SystemPropertyInitializerTests.java deleted file mode 100644 index f35d707858..0000000000 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/support/SystemPropertyInitializerTests.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2006-2022 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.batch.support; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -/** - * @author Dave Syer - * - */ -class SystemPropertyInitializerTests { - - private static final String SIMPLE_NAME = SystemPropertyInitializerTests.class.getSimpleName(); - - private final SystemPropertyInitializer initializer = new SystemPropertyInitializer(); - - @BeforeEach - @AfterEach - void initializeProperty() { - System.clearProperty(SystemPropertyInitializer.ENVIRONMENT); - System.clearProperty(SIMPLE_NAME); - } - - @Test - void testSetKeyName() throws Exception { - initializer.setKeyName(SIMPLE_NAME); - System.setProperty(SIMPLE_NAME, "foo"); - initializer.afterPropertiesSet(); - assertEquals("foo", System.getProperty(SIMPLE_NAME)); - } - - @Test - void testSetDefaultValue() throws Exception { - initializer.setDefaultValue("foo"); - initializer.afterPropertiesSet(); - assertEquals("foo", System.getProperty(SystemPropertyInitializer.ENVIRONMENT)); - } - - @Test - void testNoDefaultValue() { - assertThrows(IllegalStateException.class, initializer::afterPropertiesSet); - } - -} diff --git a/spring-batch-integration/src/main/java/org/springframework/batch/integration/chunk/RemoteChunkingManagerStepBuilder.java b/spring-batch-integration/src/main/java/org/springframework/batch/integration/chunk/RemoteChunkingManagerStepBuilder.java index 4d033b61cf..dce57ad57c 100644 --- a/spring-batch-integration/src/main/java/org/springframework/batch/integration/chunk/RemoteChunkingManagerStepBuilder.java +++ b/spring-batch-integration/src/main/java/org/springframework/batch/integration/chunk/RemoteChunkingManagerStepBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2023 the original author or authors. + * Copyright 2019-2025 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. @@ -82,17 +82,6 @@ public class RemoteChunkingManagerStepBuilder extends FaultTolerantStepBui private long throttleLimit = DEFAULT_THROTTLE_LIMIT; - /** - * Create a new {@link RemoteChunkingManagerStepBuilder}. - * @param stepName name of the manager step - * @deprecated use - * {@link RemoteChunkingManagerStepBuilder#RemoteChunkingManagerStepBuilder(String, JobRepository)} - */ - @Deprecated(since = "5.0", forRemoval = true) - public RemoteChunkingManagerStepBuilder(String stepName) { - super(new StepBuilder(stepName)); - } - /** * Create a new {@link RemoteChunkingManagerStepBuilder}. * @param stepName name of the manager step @@ -228,21 +217,6 @@ public RemoteChunkingManagerStepBuilder reader(ItemReader rea return this; } - /** - * Set the job repository - * @param jobRepository the repository to set - * @return this to enable fluent chaining - * @deprecated use - * {@link RemoteChunkingManagerStepBuilder#RemoteChunkingManagerStepBuilder(String, JobRepository)} - */ - @Override - @SuppressWarnings("removal") - @Deprecated(since = "5.1", forRemoval = true) - public RemoteChunkingManagerStepBuilder repository(JobRepository jobRepository) { - super.repository(jobRepository); - return this; - } - @Override public RemoteChunkingManagerStepBuilder transactionManager(PlatformTransactionManager transactionManager) { super.transactionManager(transactionManager); diff --git a/spring-batch-integration/src/main/java/org/springframework/batch/integration/partition/RemotePartitioningManagerStepBuilder.java b/spring-batch-integration/src/main/java/org/springframework/batch/integration/partition/RemotePartitioningManagerStepBuilder.java index 9759b4bc13..fd9a24f66c 100644 --- a/spring-batch-integration/src/main/java/org/springframework/batch/integration/partition/RemotePartitioningManagerStepBuilder.java +++ b/spring-batch-integration/src/main/java/org/springframework/batch/integration/partition/RemotePartitioningManagerStepBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2023 the original author or authors. + * Copyright 2019-2025 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. @@ -78,17 +78,6 @@ public class RemotePartitioningManagerStepBuilder extends PartitionStepBuilder { private long timeout = DEFAULT_TIMEOUT; - /** - * Create a new {@link RemotePartitioningManagerStepBuilder}. - * @param stepName name of the manager step - * @deprecated use - * {@link RemotePartitioningManagerStepBuilder#RemotePartitioningManagerStepBuilder(String, JobRepository)} - */ - @Deprecated(since = "5.0", forRemoval = true) - public RemotePartitioningManagerStepBuilder(String stepName) { - super(new StepBuilder(stepName)); - } - /** * Create a new {@link RemotePartitioningManagerStepBuilder}. * @param stepName name of the manager step @@ -243,21 +232,6 @@ private boolean isPolling() { return this.inputChannel == null; } - /** - * Set the job repository - * @param jobRepository the repository to set - * @return this to enable fluent chaining - * @deprecated use - * {@link RemotePartitioningManagerStepBuilder#RemotePartitioningManagerStepBuilder(String, JobRepository)} - */ - @Override - @SuppressWarnings("removal") - @Deprecated(since = "5.1", forRemoval = true) - public RemotePartitioningManagerStepBuilder repository(JobRepository jobRepository) { - super.repository(jobRepository); - return this; - } - @Override public RemotePartitioningManagerStepBuilder partitioner(String workerStepName, Partitioner partitioner) { super.partitioner(workerStepName, partitioner); diff --git a/spring-batch-integration/src/main/java/org/springframework/batch/integration/partition/RemotePartitioningWorkerStepBuilder.java b/spring-batch-integration/src/main/java/org/springframework/batch/integration/partition/RemotePartitioningWorkerStepBuilder.java index 16fda82326..a0fa3b9f11 100644 --- a/spring-batch-integration/src/main/java/org/springframework/batch/integration/partition/RemotePartitioningWorkerStepBuilder.java +++ b/spring-batch-integration/src/main/java/org/springframework/batch/integration/partition/RemotePartitioningWorkerStepBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2018-2023 the original author or authors. + * Copyright 2018-2025 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. @@ -80,17 +80,6 @@ public class RemotePartitioningWorkerStepBuilder extends StepBuilder { private BeanFactory beanFactory; - /** - * Initialize a step builder for a step with the given name. - * @param name the name of the step - * @deprecated use - * {@link RemotePartitioningWorkerStepBuilder#RemotePartitioningWorkerStepBuilder(String, JobRepository)} - */ - @Deprecated(since = "5.0", forRemoval = true) - public RemotePartitioningWorkerStepBuilder(String name) { - super(name); - } - /** * Initialize a step builder for a step with the given name. * @param name the name of the step @@ -157,21 +146,6 @@ public RemotePartitioningWorkerStepBuilder beanFactory(BeanFactory beanFactory) return this; } - /** - * Set the job repository - * @param jobRepository the repository to set - * @return this to enable fluent chaining - * @deprecated use - * {@link RemotePartitioningWorkerStepBuilder#RemotePartitioningWorkerStepBuilder(String, JobRepository)} - */ - @Override - @SuppressWarnings("removal") - @Deprecated(since = "5.1", forRemoval = true) - public RemotePartitioningWorkerStepBuilder repository(JobRepository jobRepository) { - super.repository(jobRepository); - return this; - } - @Override public RemotePartitioningWorkerStepBuilder startLimit(int startLimit) { super.startLimit(startLimit); @@ -196,39 +170,18 @@ public RemotePartitioningWorkerStepBuilder allowStartIfComplete(boolean allowSta return this; } - @Deprecated(since = "5.0", forRemoval = true) - @Override - public TaskletStepBuilder tasklet(Tasklet tasklet) { - configureWorkerIntegrationFlow(); - return super.tasklet(tasklet); - } - @Override public TaskletStepBuilder tasklet(Tasklet tasklet, PlatformTransactionManager transactionManager) { configureWorkerIntegrationFlow(); return super.tasklet(tasklet, transactionManager); } - @Deprecated(since = "5.0", forRemoval = true) - @Override - public SimpleStepBuilder chunk(int chunkSize) { - configureWorkerIntegrationFlow(); - return super.chunk(chunkSize); - } - @Override public SimpleStepBuilder chunk(int chunkSize, PlatformTransactionManager transactionManager) { configureWorkerIntegrationFlow(); return super.chunk(chunkSize, transactionManager); } - @Deprecated(since = "5.0", forRemoval = true) - @Override - public SimpleStepBuilder chunk(CompletionPolicy completionPolicy) { - configureWorkerIntegrationFlow(); - return super.chunk(completionPolicy); - } - @Override public SimpleStepBuilder chunk(CompletionPolicy completionPolicy, PlatformTransactionManager transactionManager) { diff --git a/spring-batch-samples/README.md b/spring-batch-samples/README.md index 770c7fd938..4b334cf8ee 100644 --- a/spring-batch-samples/README.md +++ b/spring-batch-samples/README.md @@ -603,7 +603,7 @@ class without any argument to start the sample. ### MongoDB sample This sample is a showcase of MongoDB support in Spring Batch. It copies data from -an input collection to an output collection using `MongoItemReader` and `MongoItemWriter`. +an input collection to an output collection using `MongoPagingItemReader` and `MongoItemWriter`. To run the sample, you need to have a MongoDB server up and running on `localhost:27017` (you can change these defaults in `mongodb-sample.properties`). If you use docker, diff --git a/spring-batch-samples/src/main/resources/data-source-context.xml b/spring-batch-samples/src/main/resources/data-source-context.xml index 39c1506bda..8f17365d1c 100644 --- a/spring-batch-samples/src/main/resources/data-source-context.xml +++ b/spring-batch-samples/src/main/resources/data-source-context.xml @@ -25,5 +25,4 @@ - diff --git a/spring-batch-samples/src/main/resources/org/springframework/batch/samples/misc/jmx/adhoc-job-launcher-context.xml b/spring-batch-samples/src/main/resources/org/springframework/batch/samples/misc/jmx/adhoc-job-launcher-context.xml index efbc83b8d5..a94273a433 100644 --- a/spring-batch-samples/src/main/resources/org/springframework/batch/samples/misc/jmx/adhoc-job-launcher-context.xml +++ b/spring-batch-samples/src/main/resources/org/springframework/batch/samples/misc/jmx/adhoc-job-launcher-context.xml @@ -11,13 +11,13 @@ - + + p:dataSource-ref="dataSource" p:transactionManager-ref="transactionManager"/> - + + p:dataSource-ref="dataSource" p:transactionManager-ref="transactionManager"/> Date: Tue, 29 Apr 2025 12:00:58 +0200 Subject: [PATCH 089/266] Rearrange methods in JobExplorer/JobRepository APIs --- .../batch/core/explore/JobExplorer.java | 122 +++++++++++------- .../batch/core/repository/JobRepository.java | 98 ++++++++------ 2 files changed, 128 insertions(+), 92 deletions(-) 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 85c69655f9..f10a5fa30e 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-2023 the original author or authors. + * Copyright 2006-2025 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. @@ -40,6 +40,25 @@ */ public interface JobExplorer { + /* + * =================================================================================== + * Job operations + * =================================================================================== + */ + + /** + * Query the repository for all unique {@link JobInstance} names (sorted + * alphabetically). + * @return the list of job names that have been executed. + */ + List getJobNames(); + + /* + * =================================================================================== + * Job instance operations + * =================================================================================== + */ + /** * Fetch {@link JobInstance} values in descending order of creation (and, therefore, * usually, of first execution). @@ -50,6 +69,16 @@ public interface JobExplorer { */ List getJobInstances(String jobName, int start, int count); + /** + * Fetch {@link JobInstance} values in descending order of creation (and, therefore, + * usually of first execution) with a 'like' or wildcard criteria. + * @param jobName The name of the job for which to query. + * @param start The start index of the instances to return. + * @param count The maximum number of instances to return. + * @return a list of {@link JobInstance} for the requested job name. + */ + List findJobInstancesByJobName(String jobName, int start, int count); + /** * Find the last job instance, by ID, for the given job. * @param jobName The name of the job. @@ -62,31 +91,6 @@ 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 the parent - * {@link JobInstance} and associated {@link ExecutionContext} and - * {@link StepExecution} instances (also including their execution contexts). - * @param executionId The job execution ID. - * @return the {@link JobExecution} that has this ID or {@code null} if not found. - */ - @Nullable - JobExecution getJobExecution(@Nullable Long executionId); - - /** - * Retrieve a {@link StepExecution} by its ID and parent {@link JobExecution} ID. The - * execution context for the step should be available in the result, and the parent - * job execution should have its primitive properties, but it may not contain the job - * instance information. - * @param jobExecutionId The parent job execution ID. - * @param stepExecutionId The step execution ID. - * @return the {@link StepExecution} that has this ID or {@code null} if not found. - * - * @see #getJobExecution(Long) - */ - @Nullable - StepExecution getStepExecution(@Nullable Long jobExecutionId, @Nullable Long stepExecutionId); - /** * @param instanceId {@link Long} The ID for the {@link JobInstance} to obtain. * @return the {@code JobInstance} that has this ID, or {@code null} if not found. @@ -107,6 +111,34 @@ default JobInstance getJobInstance(String jobName, JobParameters jobParameters) throw new UnsupportedOperationException(); } + /** + * Query the repository for the number of unique {@link JobInstance} objects + * associated with the supplied job name. + * @param jobName The name of the job for which to query. + * @return the number of {@link JobInstance}s that exist within the associated job + * repository. + * @throws NoSuchJobException thrown when there is no {@link JobInstance} for the + * jobName specified. + */ + long getJobInstanceCount(@Nullable String jobName) throws NoSuchJobException; + + /* + * =================================================================================== + * Job execution operations + * =================================================================================== + */ + + /** + * Retrieve a {@link JobExecution} by its ID. The complete object graph for this + * execution should be returned (unless otherwise indicated), including the parent + * {@link JobInstance} and associated {@link ExecutionContext} and + * {@link StepExecution} instances (also including their execution contexts). + * @param executionId The job execution ID. + * @return the {@link JobExecution} that has this ID or {@code null} if not found. + */ + @Nullable + JobExecution getJobExecution(@Nullable Long executionId); + /** * Retrieve job executions by their job instance. The corresponding step executions * may not be fully hydrated (for example, their execution context may be missing), @@ -142,32 +174,24 @@ default JobExecution getLastJobExecution(JobInstance jobInstance) { */ Set findRunningJobExecutions(@Nullable String jobName); - /** - * Query the repository for all unique {@link JobInstance} names (sorted - * alphabetically). - * @return the list of job names that have been executed. - */ - List getJobNames(); - - /** - * Fetch {@link JobInstance} values in descending order of creation (and, therefore, - * usually of first execution) with a 'like' or wildcard criteria. - * @param jobName The name of the job for which to query. - * @param start The start index of the instances to return. - * @param count The maximum number of instances to return. - * @return a list of {@link JobInstance} for the requested job name. + /* + * =================================================================================== + * Step execution operations + * =================================================================================== */ - List findJobInstancesByJobName(String jobName, int start, int count); /** - * Query the repository for the number of unique {@link JobInstance} objects - * associated with the supplied job name. - * @param jobName The name of the job for which to query. - * @return the number of {@link JobInstance}s that exist within the associated job - * repository. - * @throws NoSuchJobException thrown when there is no {@link JobInstance} for the - * jobName specified. + * Retrieve a {@link StepExecution} by its ID and parent {@link JobExecution} ID. The + * execution context for the step should be available in the result, and the parent + * job execution should have its primitive properties, but it may not contain the job + * instance information. + * @param jobExecutionId The parent job execution ID. + * @param stepExecutionId The step execution ID. + * @return the {@link StepExecution} that has this ID or {@code null} if not found. + * + * @see #getJobExecution(Long) */ - long getJobInstanceCount(@Nullable String jobName) throws NoSuchJobException; + @Nullable + StepExecution getStepExecution(@Nullable Long jobExecutionId, @Nullable Long stepExecutionId); } 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 b8db9253b2..b4c7d3da88 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 @@ -1,5 +1,5 @@ /* - * Copyright 2006-2023 the original author or authors. + * Copyright 2006-2025 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. @@ -50,6 +50,12 @@ */ public interface JobRepository { + /* + * =================================================================================== + * Read only operations + * =================================================================================== + */ + /** * Retrieve the names of all job instances sorted alphabetically - i.e. jobs that have * ever been executed. @@ -74,6 +80,28 @@ default List findJobInstancesByName(String jobName, int start, int return Collections.emptyList(); } + /** + * 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 + */ + boolean isJobInstanceExists(String jobName, JobParameters jobParameters); + + /** + * @param jobName {@link String} name of the job. + * @param jobParameters {@link JobParameters} parameters for the job instance. + * @return the {@link JobInstance} with the given name and parameters, or + * {@code null}. + * + * @since 5.0 + */ + @Nullable + default JobInstance getJobInstance(String jobName, JobParameters jobParameters) { + throw new UnsupportedOperationException(); + } + /** * Return all {@link JobExecution}s for given {@link JobInstance}, sorted backwards by * creation order (so the first element is the most recent). @@ -86,13 +114,33 @@ default List findJobExecutions(JobInstance jobInstance) { } /** - * 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 + * @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 + */ + @Nullable + JobExecution getLastJobExecution(String jobName, JobParameters jobParameters); + + /** + * @param jobInstance {@link JobInstance} instance containing the step executions. + * @param stepName the name of the step execution that might have run. + * @return the last execution of step for the given job instance. + */ + @Nullable + StepExecution getLastStepExecution(JobInstance jobInstance, String stepName); + + /** + * @param jobInstance {@link JobInstance} instance containing the step executions. + * @param stepName the name of the step execution that might have run. + * @return the execution count of the step within the given job instance. + */ + long getStepExecutionCount(JobInstance jobInstance, String stepName); + + /* + * =================================================================================== + * Write/Update operations + * =================================================================================== */ - boolean isJobInstanceExists(String jobName, JobParameters jobParameters); /** * Create a new {@link JobInstance} with the name and job parameters provided. @@ -187,42 +235,6 @@ JobExecution createJobExecution(String jobName, JobParameters jobParameters) */ void updateExecutionContext(JobExecution jobExecution); - /** - * @param jobName {@link String} name of the job. - * @param jobParameters {@link JobParameters} parameters for the job instance. - * @return the {@link JobInstance} with the given name and parameters, or - * {@code null}. - * - * @since 5.0 - */ - @Nullable - default JobInstance getJobInstance(String jobName, JobParameters jobParameters) { - throw new UnsupportedOperationException(); - } - - /** - * @param jobInstance {@link JobInstance} instance containing the step executions. - * @param stepName the name of the step execution that might have run. - * @return the last execution of step for the given job instance. - */ - @Nullable - StepExecution getLastStepExecution(JobInstance jobInstance, String stepName); - - /** - * @param jobInstance {@link JobInstance} instance containing the step executions. - * @param stepName the name of the step execution that might have run. - * @return the execution count of the step within the given job instance. - */ - long getStepExecutionCount(JobInstance jobInstance, String stepName); - - /** - * @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 - */ - @Nullable - JobExecution getLastJobExecution(String jobName, JobParameters jobParameters); - /** * Delete the step execution along with its execution context. * @param stepExecution the step execution to delete From bf53794d6a1f1ab08d3fbc18cc40e1048e376e9c Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Tue, 29 Apr 2025 12:18:27 +0200 Subject: [PATCH 090/266] Deprecate redundant methods in JobExplorer/JobInstanceDao APIs Resolves #4821 --- .../batch/core/explore/JobExplorer.java | 3 ++ .../explore/support/SimpleJobExplorer.java | 7 ++++- .../repository/dao/JdbcJobInstanceDao.java | 31 ++++--------------- .../core/repository/dao/JobInstanceDao.java | 5 ++- .../repository/dao/MongoJobInstanceDao.java | 20 +++++------- 5 files changed, 26 insertions(+), 40 deletions(-) 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 f10a5fa30e..0b839c7d80 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 @@ -76,7 +76,10 @@ public interface JobExplorer { * @param start The start index of the instances to return. * @param count The maximum number of instances to return. * @return a list of {@link JobInstance} for the requested job name. + * @deprecated Since v6.0 and scheduled for removal in v6.2. Use + * {@link #getJobInstances(String, int, int)} */ + @Deprecated(forRemoval = true) List findJobInstancesByJobName(String jobName, int start, int count); /** 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 236be9902d..58b86d037a 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 @@ -230,9 +230,14 @@ private void getStepExecutionDependencies(StepExecution stepExecution) { } } + /** + * @deprecated since v6.0 and scheduled for removal in v6.2. Use + * {@link #getJobInstances(String, int, int)} instead. + */ + @Deprecated(forRemoval = true) @Override public List findJobInstancesByJobName(String jobName, int start, int count) { - return jobInstanceDao.findJobInstancesByName(jobName, start, count); + return getJobInstances(jobName, start, count); } } 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 8b503899a9..b7e94badbd 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 @@ -332,33 +332,14 @@ public JobInstance mapRow(ResultSet rs, int rowNum) throws SQLException { } + /** + * @deprecated since v6.0 and scheduled for removal in v6.2. Use + * {@link #getJobInstances(String, int, int)} instead. + */ + @Deprecated(forRemoval = true) @Override - @SuppressWarnings({ "rawtypes", "unchecked" }) public List findJobInstancesByName(String jobName, final int start, final int count) { - ResultSetExtractor extractor = new ResultSetExtractor() { - private final List list = new ArrayList<>(); - - @Override - public Object extractData(ResultSet rs) throws SQLException, DataAccessException { - int rowNum = 0; - while (rowNum < start && rs.next()) { - rowNum++; - } - while (rowNum < start + count && rs.next()) { - RowMapper rowMapper = new JobInstanceRowMapper(); - list.add(rowMapper.mapRow(rs, rowNum)); - rowNum++; - } - return list; - } - }; - - if (jobName.contains(STAR_WILDCARD)) { - jobName = jobName.replaceAll("\\" + STAR_WILDCARD, SQL_WILDCARD); - } - - return (List) getJdbcTemplate().query(getQuery(FIND_LAST_JOBS_LIKE_NAME), extractor, jobName); - + return getJobInstances(jobName, start, count); } } 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 4c2ac43be4..c430043ed1 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-2023 the original author or authors. + * Copyright 2006-2025 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. @@ -117,7 +117,10 @@ default JobInstance getLastJobInstance(String jobName) { * should begin. * @param count int containing the number of job instances to return. * @return a list of {@link JobInstance} for the job name requested. + * @deprecated Since v6.0 and scheduled for removal in v6.2. Use + * {@link #getJobInstances(String, int, int)} */ + @Deprecated(forRemoval = true) List findJobInstancesByName(String jobName, int start, int count); /** diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoJobInstanceDao.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoJobInstanceDao.java index b967e35f77..a325b34bde 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoJobInstanceDao.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoJobInstanceDao.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2024-2025 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. @@ -143,20 +143,14 @@ public List getJobNames() { .toList(); } + /** + * @deprecated since v6.0 and scheduled for removal in v6.2. Use + * {@link #getJobInstances(String, int, int)} instead. + */ + @Deprecated(forRemoval = true) @Override public List findJobInstancesByName(String jobName, int start, int count) { - Query query = query(where("jobName").alike(Example.of(jobName))); - Sort.Order sortOrder = Sort.Order.desc("jobInstanceId"); - List jobInstances = this.mongoOperations - .find(query.with(Sort.by(sortOrder)), - org.springframework.batch.core.repository.persistence.JobInstance.class, COLLECTION_NAME) - .stream() - .toList(); - return jobInstances.subList(start, jobInstances.size()) - .stream() - .map(this.jobInstanceConverter::toJobInstance) - .limit(count) - .toList(); + return getJobInstances(jobName, start, count); } @Override From b8c93d677ed86130262042fb8565ce30816c2270 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Mon, 5 May 2025 15:26:43 +0200 Subject: [PATCH 091/266] Make JobRepository extend JobExplorer Resolves #4824 --- .../batch/core/explore/JobExplorer.java | 126 +++++++++-- .../explore/support/SimpleJobExplorer.java | 209 ++++++++++++------ .../batch/core/repository/JobRepository.java | 98 +------- .../support/SimpleJobRepository.java | 98 +------- 4 files changed, 264 insertions(+), 267 deletions(-) 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 0b839c7d80..6ee09295e9 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 @@ -15,6 +15,7 @@ */ package org.springframework.batch.core.explore; +import java.util.Collections; import java.util.List; import java.util.Set; @@ -51,7 +52,9 @@ public interface JobExplorer { * alphabetically). * @return the list of job names that have been executed. */ - List getJobNames(); + default List getJobNames() { + return Collections.emptyList(); + } /* * =================================================================================== @@ -67,7 +70,9 @@ public interface JobExplorer { * @param count The maximum number of instances to return. * @return the {@link JobInstance} values up to a maximum of count values. */ - List getJobInstances(String jobName, int start, int count); + default List getJobInstances(String jobName, int start, int count) { + return Collections.emptyList(); + } /** * Fetch {@link JobInstance} values in descending order of creation (and, therefore, @@ -79,8 +84,51 @@ public interface JobExplorer { * @deprecated Since v6.0 and scheduled for removal in v6.2. Use * {@link #getJobInstances(String, int, int)} */ - @Deprecated(forRemoval = true) - List findJobInstancesByJobName(String jobName, int start, int count); + @Deprecated(since = "6.0", forRemoval = true) + default List findJobInstancesByJobName(String jobName, int start, int count) { + return Collections.emptyList(); + } + + /** + * Fetch the last job instances with the provided name, sorted backwards by primary + * key, using a 'like' criteria + * @param jobName {@link String} containing the name of the job. + * @param start int containing the offset of where list of job instances results + * should begin. + * @param count int containing the number of job instances to return. + * @return a list of {@link JobInstance} for the job name requested. + * @since 5.0 + * @deprecated since v6.0 and scheduled for removal in v6.2. Use + * {@link #getJobInstances(String, int, int)} + */ + @Deprecated(since = "6.0", forRemoval = true) + default List findJobInstancesByName(String jobName, int start, int count) { + return Collections.emptyList(); + } + + /** + * 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 + * @deprecated Since v6.0 and scheduled for removal in v6.2. Use + * {@link #getJobInstance(String, JobParameters)} and check for {@code null} result + * instead. + */ + @Deprecated(since = "6.0", forRemoval = true) + default boolean isJobInstanceExists(String jobName, JobParameters jobParameters) { + return getJobInstance(jobName, jobParameters) != null; + } + + /** + * @param instanceId {@link Long} The ID for the {@link JobInstance} to obtain. + * @return the {@code JobInstance} that has this ID, or {@code null} if not found. + */ + @Nullable + default JobInstance getJobInstance(@Nullable Long instanceId) { + throw new UnsupportedOperationException(); + } /** * Find the last job instance, by ID, for the given job. @@ -94,13 +142,6 @@ default JobInstance getLastJobInstance(String jobName) { throw new UnsupportedOperationException(); } - /** - * @param instanceId {@link Long} The ID for the {@link JobInstance} to obtain. - * @return the {@code JobInstance} that has this ID, or {@code null} if not found. - */ - @Nullable - JobInstance getJobInstance(@Nullable Long instanceId); - /** * @param jobName {@link String} name of the job. * @param jobParameters {@link JobParameters} parameters for the job instance. @@ -123,7 +164,9 @@ default JobInstance getJobInstance(String jobName, JobParameters jobParameters) * @throws NoSuchJobException thrown when there is no {@link JobInstance} for the * jobName specified. */ - long getJobInstanceCount(@Nullable String jobName) throws NoSuchJobException; + default long getJobInstanceCount(@Nullable String jobName) throws NoSuchJobException { + throw new UnsupportedOperationException(); + } /* * =================================================================================== @@ -140,7 +183,9 @@ default JobInstance getJobInstance(String jobName, JobParameters jobParameters) * @return the {@link JobExecution} that has this ID or {@code null} if not found. */ @Nullable - JobExecution getJobExecution(@Nullable Long executionId); + default JobExecution getJobExecution(@Nullable Long executionId) { + throw new UnsupportedOperationException(); + } /** * Retrieve job executions by their job instance. The corresponding step executions @@ -150,7 +195,23 @@ default JobInstance getJobInstance(String jobName, JobParameters jobParameters) * @param jobInstance The {@link JobInstance} to query. * @return the list of all executions for the specified {@link JobInstance}. */ - List getJobExecutions(JobInstance jobInstance); + default List getJobExecutions(JobInstance jobInstance) { + return Collections.emptyList(); + } + + /** + * Return all {@link JobExecution}s for given {@link JobInstance}, sorted backwards by + * creation order (so the first element is the most recent). + * @param jobInstance parent {@link JobInstance} of the {@link JobExecution}s to find. + * @return {@link List} containing JobExecutions for the jobInstance. + * @since 5.0 + * @deprecated since v6.0 and scheduled for removal in v6.2. Use + * {@link #getJobExecutions(JobInstance)} + */ + @Deprecated(since = "6.0", forRemoval = true) + default List findJobExecutions(JobInstance jobInstance) { + return Collections.emptyList(); + } /** * Find the last {@link JobExecution} that has been created for a given @@ -167,6 +228,16 @@ default JobExecution getLastJobExecution(JobInstance jobInstance) { throw new UnsupportedOperationException(); } + /** + * @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 + */ + @Nullable + default JobExecution getLastJobExecution(String jobName, JobParameters jobParameters) { + throw new UnsupportedOperationException(); + } + /** * Retrieve running job executions. The corresponding step executions may not be fully * hydrated (for example, their execution context may be missing), depending on the @@ -175,7 +246,9 @@ default JobExecution getLastJobExecution(JobInstance jobInstance) { * @param jobName The name of the job. * @return the set of running executions for jobs with the specified name. */ - Set findRunningJobExecutions(@Nullable String jobName); + default Set findRunningJobExecutions(@Nullable String jobName) { + return Collections.emptySet(); + } /* * =================================================================================== @@ -195,6 +268,27 @@ default JobExecution getLastJobExecution(JobInstance jobInstance) { * @see #getJobExecution(Long) */ @Nullable - StepExecution getStepExecution(@Nullable Long jobExecutionId, @Nullable Long stepExecutionId); + default StepExecution getStepExecution(@Nullable Long jobExecutionId, @Nullable Long stepExecutionId) { + throw new UnsupportedOperationException(); + } + + /** + * @param jobInstance {@link JobInstance} instance containing the step executions. + * @param stepName the name of the step execution that might have run. + * @return the last execution of step for the given job instance. + */ + @Nullable + default StepExecution getLastStepExecution(JobInstance jobInstance, String stepName) { + throw new UnsupportedOperationException(); + } + + /** + * @param jobInstance {@link JobInstance} instance containing the step executions. + * @param stepName the name of the step execution that might have run. + * @return the execution count of the step within the given job instance. + */ + default long getStepExecutionCount(JobInstance jobInstance, String stepName) { + throw new UnsupportedOperationException(); + } } 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 58b86d037a..bf8857b16a 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-2023 the original author or authors. + * Copyright 2006-2025 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. @@ -26,6 +26,7 @@ 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.lang.Nullable; import java.util.List; @@ -49,20 +50,13 @@ */ public class SimpleJobExplorer implements JobExplorer { - private JobInstanceDao jobInstanceDao; + protected JobInstanceDao jobInstanceDao; - private JobExecutionDao jobExecutionDao; + protected JobExecutionDao jobExecutionDao; - private StepExecutionDao stepExecutionDao; + protected StepExecutionDao stepExecutionDao; - private ExecutionContextDao ecDao; - - /** - * Provides a default constructor with low visibility in case you want to use - * aop:proxy-target-class="true" for the AOP interceptor. - */ - SimpleJobExplorer() { - } + protected ExecutionContextDao ecDao; /** * Constructor to initialize the job {@link SimpleJobExplorer}. @@ -80,6 +74,79 @@ public SimpleJobExplorer(JobInstanceDao jobInstanceDao, JobExecutionDao jobExecu this.ecDao = ecDao; } + /* + * =================================================================================== + * Job operations + * =================================================================================== + */ + + @Override + public List getJobNames() { + return jobInstanceDao.getJobNames(); + } + + /* + * =================================================================================== + * Job instance operations + * =================================================================================== + */ + + @Override + @Deprecated(since = "6.0", forRemoval = true) + public boolean isJobInstanceExists(String jobName, JobParameters jobParameters) { + return jobInstanceDao.getJobInstance(jobName, jobParameters) != null; + } + + /** + * @deprecated since v6.0 and scheduled for removal in v6.2. Use + * {@link #getJobInstances(String, int, int)} instead. + */ + @Deprecated(since = "6.0", forRemoval = true) + @Override + public List findJobInstancesByJobName(String jobName, int start, int count) { + return getJobInstances(jobName, start, count); + } + + @Override + @Deprecated(since = "6.0", forRemoval = true) + public List findJobInstancesByName(String jobName, int start, int count) { + return this.jobInstanceDao.findJobInstancesByName(jobName, start, count); + } + + @Nullable + @Override + public JobInstance getJobInstance(@Nullable Long instanceId) { + return jobInstanceDao.getJobInstance(instanceId); + } + + @Nullable + @Override + public JobInstance getJobInstance(String jobName, JobParameters jobParameters) { + return jobInstanceDao.getJobInstance(jobName, jobParameters); + } + + @Nullable + @Override + public JobInstance getLastJobInstance(String jobName) { + return jobInstanceDao.getLastJobInstance(jobName); + } + + @Override + public List getJobInstances(String jobName, int start, int count) { + return jobInstanceDao.getJobInstances(jobName, start, count); + } + + @Override + public long getJobInstanceCount(@Nullable String jobName) throws NoSuchJobException { + return jobInstanceDao.getJobInstanceCount(jobName); + } + + /* + * =================================================================================== + * Job execution operations + * =================================================================================== + */ + @Override public List getJobExecutions(JobInstance jobInstance) { List executions = jobExecutionDao.findJobExecutions(jobInstance); @@ -105,6 +172,32 @@ public JobExecution getLastJobExecution(JobInstance jobInstance) { return lastJobExecution; } + @Deprecated(since = "6.0", forRemoval = true) + @Override + public List findJobExecutions(JobInstance jobInstance) { + List jobExecutions = this.jobExecutionDao.findJobExecutions(jobInstance); + for (JobExecution jobExecution : jobExecutions) { + this.stepExecutionDao.addStepExecutions(jobExecution); + } + return jobExecutions; + } + + @Override + @Nullable + public JobExecution getLastJobExecution(String jobName, JobParameters jobParameters) { + JobInstance jobInstance = jobInstanceDao.getJobInstance(jobName, jobParameters); + if (jobInstance == null) { + return null; + } + JobExecution jobExecution = jobExecutionDao.getLastJobExecution(jobInstance); + + if (jobExecution != null) { + jobExecution.setExecutionContext(ecDao.getExecutionContext(jobExecution)); + stepExecutionDao.addStepExecutions(jobExecution); + } + return jobExecution; + } + @Override public Set findRunningJobExecutions(@Nullable String jobName) { Set executions = jobExecutionDao.findRunningJobExecutions(jobName); @@ -134,6 +227,24 @@ public JobExecution getJobExecution(@Nullable Long executionId) { return jobExecution; } + /* + * Find all dependencies for a JobExecution, including JobInstance (which requires + * JobParameters) plus StepExecutions + */ + private void getJobExecutionDependencies(JobExecution jobExecution) { + JobInstance jobInstance = jobInstanceDao.getJobInstance(jobExecution); + stepExecutionDao.addStepExecutions(jobExecution); + jobExecution.setJobInstance(jobInstance); + jobExecution.setExecutionContext(ecDao.getExecutionContext(jobExecution)); + + } + + /* + * =================================================================================== + * Step execution operations + * =================================================================================== + */ + @Nullable @Override public StepExecution getStepExecution(@Nullable Long jobExecutionId, @Nullable Long executionId) { @@ -147,39 +258,41 @@ public StepExecution getStepExecution(@Nullable Long jobExecutionId, @Nullable L return stepExecution; } - @Nullable @Override - public JobInstance getJobInstance(@Nullable Long instanceId) { - return jobInstanceDao.getJobInstance(instanceId); - } - @Nullable - @Override - public JobInstance getJobInstance(String jobName, JobParameters jobParameters) { - return jobInstanceDao.getJobInstance(jobName, jobParameters); - } + public StepExecution getLastStepExecution(JobInstance jobInstance, String stepName) { + StepExecution latest = stepExecutionDao.getLastStepExecution(jobInstance, stepName); - @Nullable - @Override - public JobInstance getLastJobInstance(String jobName) { - return jobInstanceDao.getLastJobInstance(jobName); - } + if (latest != null) { + ExecutionContext stepExecutionContext = ecDao.getExecutionContext(latest); + latest.setExecutionContext(stepExecutionContext); + ExecutionContext jobExecutionContext = ecDao.getExecutionContext(latest.getJobExecution()); + latest.getJobExecution().setExecutionContext(jobExecutionContext); + } - @Override - public List getJobInstances(String jobName, int start, int count) { - return jobInstanceDao.getJobInstances(jobName, start, count); + return latest; } + /** + * @return number of executions of the step within given job instance + */ @Override - public List getJobNames() { - return jobInstanceDao.getJobNames(); + public long getStepExecutionCount(JobInstance jobInstance, String stepName) { + return stepExecutionDao.countStepExecutions(jobInstance, stepName); } - @Override - public long getJobInstanceCount(@Nullable String jobName) throws NoSuchJobException { - return jobInstanceDao.getJobInstanceCount(jobName); + private void getStepExecutionDependencies(StepExecution stepExecution) { + if (stepExecution != null) { + stepExecution.setExecutionContext(ecDao.getExecutionContext(stepExecution)); + } } + /* + * =================================================================================== + * protected methods + * =================================================================================== + */ + /** * @return instance of {@link JobInstanceDao}. * @since 5.1 @@ -212,32 +325,4 @@ protected ExecutionContextDao getEcDao() { return ecDao; } - /* - * Find all dependencies for a JobExecution, including JobInstance (which requires - * JobParameters) plus StepExecutions - */ - private void getJobExecutionDependencies(JobExecution jobExecution) { - JobInstance jobInstance = jobInstanceDao.getJobInstance(jobExecution); - stepExecutionDao.addStepExecutions(jobExecution); - jobExecution.setJobInstance(jobInstance); - jobExecution.setExecutionContext(ecDao.getExecutionContext(jobExecution)); - - } - - private void getStepExecutionDependencies(StepExecution stepExecution) { - if (stepExecution != null) { - stepExecution.setExecutionContext(ecDao.getExecutionContext(stepExecution)); - } - } - - /** - * @deprecated since v6.0 and scheduled for removal in v6.2. Use - * {@link #getJobInstances(String, int, int)} instead. - */ - @Deprecated(forRemoval = true) - @Override - public List findJobInstancesByJobName(String jobName, int start, int count) { - return getJobInstances(jobName, start, count); - } - } 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 b4c7d3da88..5fdc6be7bd 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,15 +22,13 @@ import org.springframework.batch.core.JobParameters; import org.springframework.batch.core.Step; import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.explore.JobExplorer; 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.lang.Nullable; import org.springframework.transaction.annotation.Isolation; import java.util.Collection; -import java.util.Collections; -import java.util.List; /** *

    @@ -48,99 +46,7 @@ * @author Mahmoud Ben Hassine * @author Parikshit Dutta */ -public interface JobRepository { - - /* - * =================================================================================== - * Read only operations - * =================================================================================== - */ - - /** - * Retrieve the names of all job instances sorted alphabetically - i.e. jobs that have - * ever been executed. - * @return the names of all job instances - * @since 5.0 - */ - default List getJobNames() { - return Collections.emptyList(); - } - - /** - * Fetch the last job instances with the provided name, sorted backwards by primary - * key, using a 'like' criteria - * @param jobName {@link String} containing the name of the job. - * @param start int containing the offset of where list of job instances results - * should begin. - * @param count int containing the number of job instances to return. - * @return a list of {@link JobInstance} for the job name requested. - * @since 5.0 - */ - default List findJobInstancesByName(String jobName, int start, int count) { - return Collections.emptyList(); - } - - /** - * 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 - */ - boolean isJobInstanceExists(String jobName, JobParameters jobParameters); - - /** - * @param jobName {@link String} name of the job. - * @param jobParameters {@link JobParameters} parameters for the job instance. - * @return the {@link JobInstance} with the given name and parameters, or - * {@code null}. - * - * @since 5.0 - */ - @Nullable - default JobInstance getJobInstance(String jobName, JobParameters jobParameters) { - throw new UnsupportedOperationException(); - } - - /** - * Return all {@link JobExecution}s for given {@link JobInstance}, sorted backwards by - * creation order (so the first element is the most recent). - * @param jobInstance parent {@link JobInstance} of the {@link JobExecution}s to find. - * @return {@link List} containing JobExecutions for the jobInstance. - * @since 5.0 - */ - default List findJobExecutions(JobInstance jobInstance) { - return Collections.emptyList(); - } - - /** - * @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 - */ - @Nullable - JobExecution getLastJobExecution(String jobName, JobParameters jobParameters); - - /** - * @param jobInstance {@link JobInstance} instance containing the step executions. - * @param stepName the name of the step execution that might have run. - * @return the last execution of step for the given job instance. - */ - @Nullable - StepExecution getLastStepExecution(JobInstance jobInstance, String stepName); - - /** - * @param jobInstance {@link JobInstance} instance containing the step executions. - * @param stepName the name of the step execution that might have run. - * @return the execution count of the step within the given job instance. - */ - long getStepExecutionCount(JobInstance jobInstance, String stepName); - - /* - * =================================================================================== - * Write/Update operations - * =================================================================================== - */ +public interface JobRepository extends JobExplorer { /** * Create a new {@link JobInstance} with the name and job parameters provided. 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 e98752c987..60c906b093 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 @@ -1,5 +1,5 @@ /* - * Copyright 2006-2024 the original author or authors. + * Copyright 2006-2025 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. @@ -23,6 +23,7 @@ import org.springframework.batch.core.JobInstance; import org.springframework.batch.core.JobParameters; import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.explore.support.SimpleJobExplorer; import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException; import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException; import org.springframework.batch.core.repository.JobRepository; @@ -32,7 +33,6 @@ 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.lang.Nullable; import org.springframework.util.Assert; import java.time.LocalDateTime; @@ -60,56 +60,13 @@ * @see StepExecutionDao * */ -public class SimpleJobRepository implements JobRepository { +public class SimpleJobRepository extends SimpleJobExplorer implements JobRepository { private static final Log logger = LogFactory.getLog(SimpleJobRepository.class); - private JobInstanceDao jobInstanceDao; - - private JobExecutionDao jobExecutionDao; - - private StepExecutionDao stepExecutionDao; - - private ExecutionContextDao ecDao; - - /** - * Provide default constructor with low visibility in case user wants to use - * aop:proxy-target-class="true" for AOP interceptor. - */ - SimpleJobRepository() { - } - public SimpleJobRepository(JobInstanceDao jobInstanceDao, JobExecutionDao jobExecutionDao, StepExecutionDao stepExecutionDao, ExecutionContextDao ecDao) { - super(); - this.jobInstanceDao = jobInstanceDao; - this.jobExecutionDao = jobExecutionDao; - this.stepExecutionDao = stepExecutionDao; - this.ecDao = ecDao; - } - - @Override - public List getJobNames() { - return this.jobInstanceDao.getJobNames(); - } - - @Override - public List findJobInstancesByName(String jobName, int start, int count) { - return this.jobInstanceDao.findJobInstancesByName(jobName, start, count); - } - - @Override - public List findJobExecutions(JobInstance jobInstance) { - List jobExecutions = this.jobExecutionDao.findJobExecutions(jobInstance); - for (JobExecution jobExecution : jobExecutions) { - this.stepExecutionDao.addStepExecutions(jobExecution); - } - return jobExecutions; - } - - @Override - public boolean isJobInstanceExists(String jobName, JobParameters jobParameters) { - return jobInstanceDao.getJobInstance(jobName, jobParameters) != null; + super(jobInstanceDao, jobExecutionDao, stepExecutionDao, ecDao); } @Override @@ -249,34 +206,6 @@ public void updateExecutionContext(JobExecution jobExecution) { ecDao.updateExecutionContext(jobExecution); } - @Override - public JobInstance getJobInstance(String jobName, JobParameters jobParameters) { - return jobInstanceDao.getJobInstance(jobName, jobParameters); - } - - @Override - @Nullable - public StepExecution getLastStepExecution(JobInstance jobInstance, String stepName) { - StepExecution latest = stepExecutionDao.getLastStepExecution(jobInstance, stepName); - - if (latest != null) { - ExecutionContext stepExecutionContext = ecDao.getExecutionContext(latest); - latest.setExecutionContext(stepExecutionContext); - ExecutionContext jobExecutionContext = ecDao.getExecutionContext(latest.getJobExecution()); - latest.getJobExecution().setExecutionContext(jobExecutionContext); - } - - return latest; - } - - /** - * @return number of executions of the step within given job instance - */ - @Override - public long getStepExecutionCount(JobInstance jobInstance, String stepName) { - return stepExecutionDao.countStepExecutions(jobInstance, stepName); - } - /** * Check to determine whether or not the JobExecution that is the parent of the * provided StepExecution has been interrupted. If, after synchronizing the status @@ -293,23 +222,6 @@ private void checkForInterruption(StepExecution stepExecution) { } } - @Override - @Nullable - public JobExecution getLastJobExecution(String jobName, JobParameters jobParameters) { - JobInstance jobInstance = jobInstanceDao.getJobInstance(jobName, jobParameters); - if (jobInstance == null) { - return null; - } - JobExecution jobExecution = jobExecutionDao.getLastJobExecution(jobInstance); - - if (jobExecution != null) { - jobExecution.setExecutionContext(ecDao.getExecutionContext(jobExecution)); - stepExecutionDao.addStepExecutions(jobExecution); - } - return jobExecution; - - } - @Override public void deleteStepExecution(StepExecution stepExecution) { this.ecDao.deleteExecutionContext(stepExecution); @@ -328,7 +240,7 @@ public void deleteJobExecution(JobExecution jobExecution) { @Override public void deleteJobInstance(JobInstance jobInstance) { - List jobExecutions = findJobExecutions(jobInstance); + List jobExecutions = getJobExecutions(jobInstance); for (JobExecution jobExecution : jobExecutions) { deleteJobExecution(jobExecution); } From 6992b79b8dc6f6e87f1dd75548328f9011ec699e Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Mon, 5 May 2025 17:14:12 +0200 Subject: [PATCH 092/266] Remove dependency to JobExplorer in SimpleJobOperator Resolves #4817 --- .../annotation/BatchRegistrar.java | 1 - .../support/DefaultBatchConfiguration.java | 6 +- .../support/JobOperatorFactoryBean.java | 12 ---- .../launch/support/SimpleJobOperator.java | 25 ++----- .../support/JobOperatorFactoryBeanTests.java | 2 - .../support/SimpleJobOperatorTests.java | 66 +++++++++---------- .../OptimisticLockingFailureTests-context.xml | 1 - .../resources/simple-job-launcher-context.xml | 3 +- .../misc/jmx/adhoc-job-launcher-context.xml | 5 -- .../job/skipSample-job-launcher-context.xml | 2 +- .../resources/simple-job-launcher-context.xml | 3 +- 11 files changed, 42 insertions(+), 84 deletions(-) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/BatchRegistrar.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/BatchRegistrar.java index 073ef836d9..631f12b262 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/BatchRegistrar.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/BatchRegistrar.java @@ -254,7 +254,6 @@ private void registerJobOperator(BeanDefinitionRegistry registry, EnableBatchPro beanDefinitionBuilder.addPropertyReference(JOB_REPOSITORY, JOB_REPOSITORY); beanDefinitionBuilder.addPropertyReference(JOB_LAUNCHER, JOB_LAUNCHER); - beanDefinitionBuilder.addPropertyReference(JOB_EXPLORER, JOB_EXPLORER); beanDefinitionBuilder.addPropertyReference(JOB_REGISTRY, JOB_REGISTRY); // set optional properties diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/DefaultBatchConfiguration.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/DefaultBatchConfiguration.java index 484809fb2e..1a628b3d93 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/DefaultBatchConfiguration.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/DefaultBatchConfiguration.java @@ -197,7 +197,6 @@ public JobRegistry jobRegistry() throws BatchConfigurationException { /** * Define a job operator bean. * @param jobRepository a job repository - * @param jobExplorer a job explorer * @param jobRegistry a job registry * @param jobLauncher a job launcher * @return a job operator @@ -205,12 +204,11 @@ public JobRegistry jobRegistry() throws BatchConfigurationException { * @since 5.2 */ @Bean - public JobOperator jobOperator(JobRepository jobRepository, JobExplorer jobExplorer, JobRegistry jobRegistry, - JobLauncher jobLauncher) throws BatchConfigurationException { + public JobOperator jobOperator(JobRepository jobRepository, JobRegistry jobRegistry, JobLauncher jobLauncher) + throws BatchConfigurationException { JobOperatorFactoryBean jobOperatorFactoryBean = new JobOperatorFactoryBean(); jobOperatorFactoryBean.setTransactionManager(getTransactionManager()); jobOperatorFactoryBean.setJobRepository(jobRepository); - jobOperatorFactoryBean.setJobExplorer(jobExplorer); jobOperatorFactoryBean.setJobRegistry(jobRegistry); jobOperatorFactoryBean.setJobLauncher(jobLauncher); jobOperatorFactoryBean.setJobParametersConverter(getJobParametersConverter()); diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/JobOperatorFactoryBean.java b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/JobOperatorFactoryBean.java index a3c07375ec..fdeea5a3a0 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/JobOperatorFactoryBean.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/JobOperatorFactoryBean.java @@ -61,8 +61,6 @@ public class JobOperatorFactoryBean implements FactoryBean, Initial private JobRepository jobRepository; - private JobExplorer jobExplorer; - private JobParametersConverter jobParametersConverter = new DefaultJobParametersConverter(); private final ProxyFactory proxyFactory = new ProxyFactory(); @@ -72,7 +70,6 @@ public void afterPropertiesSet() throws Exception { Assert.notNull(this.transactionManager, "TransactionManager must not be null"); Assert.notNull(this.jobLauncher, "JobLauncher must not be null"); Assert.notNull(this.jobRegistry, "JobRegistry must not be null"); - Assert.notNull(this.jobExplorer, "JobExplorer must not be null"); Assert.notNull(this.jobRepository, "JobRepository must not be null"); if (this.transactionAttributeSource == null) { Properties transactionAttributes = new Properties(); @@ -108,14 +105,6 @@ public void setJobRepository(JobRepository jobRepository) { this.jobRepository = jobRepository; } - /** - * Setter for the job explorer. - * @param jobExplorer the job explorer to set - */ - public void setJobExplorer(JobExplorer jobExplorer) { - this.jobExplorer = jobExplorer; - } - /** * Setter for the job parameters converter. * @param jobParametersConverter the job parameters converter to set @@ -166,7 +155,6 @@ public JobOperator getObject() throws Exception { private SimpleJobOperator getTarget() throws Exception { SimpleJobOperator simpleJobOperator = new SimpleJobOperator(); simpleJobOperator.setJobRegistry(this.jobRegistry); - simpleJobOperator.setJobExplorer(this.jobExplorer); simpleJobOperator.setJobRepository(this.jobRepository); simpleJobOperator.setJobLauncher(this.jobLauncher); simpleJobOperator.setJobParametersConverter(this.jobParametersConverter); diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/SimpleJobOperator.java b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/SimpleJobOperator.java index 059e769960..5c28562b78 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/SimpleJobOperator.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/SimpleJobOperator.java @@ -93,8 +93,6 @@ public class SimpleJobOperator implements JobOperator, InitializingBean { private ListableJobLocator jobRegistry; - private JobExplorer jobExplorer; - private JobLauncher jobLauncher; private JobRepository jobRepository; @@ -112,7 +110,6 @@ public class SimpleJobOperator implements JobOperator, InitializingBean { public void afterPropertiesSet() throws Exception { Assert.state(jobLauncher != null, "JobLauncher must be provided"); Assert.state(jobRegistry != null, "JobLocator must be provided"); - Assert.state(jobExplorer != null, "JobExplorer must be provided"); Assert.state(jobRepository != null, "JobRepository must be provided"); } @@ -132,14 +129,6 @@ public void setJobRegistry(ListableJobLocator jobRegistry) { this.jobRegistry = jobRegistry; } - /** - * Public setter for the {@link JobExplorer}. - * @param jobExplorer the {@link JobExplorer} to set - */ - public void setJobExplorer(JobExplorer jobExplorer) { - this.jobExplorer = jobExplorer; - } - public void setJobRepository(JobRepository jobRepository) { this.jobRepository = jobRepository; } @@ -154,12 +143,12 @@ public void setJobLauncher(JobLauncher jobLauncher) { @Override public List getExecutions(long instanceId) throws NoSuchJobInstanceException { - JobInstance jobInstance = jobExplorer.getJobInstance(instanceId); + JobInstance jobInstance = jobRepository.getJobInstance(instanceId); if (jobInstance == null) { throw new NoSuchJobInstanceException(String.format("No job instance with id=%d", instanceId)); } List list = new ArrayList<>(); - for (JobExecution jobExecution : jobExplorer.getJobExecutions(jobInstance)) { + for (JobExecution jobExecution : jobRepository.getJobExecutions(jobInstance)) { list.add(jobExecution.getId()); } return list; @@ -173,7 +162,7 @@ public Set getJobNames() { @Override public List getJobInstances(String jobName, int start, int count) throws NoSuchJobException { List list = new ArrayList<>(); - List jobInstances = jobExplorer.getJobInstances(jobName, start, count); + List jobInstances = jobRepository.getJobInstances(jobName, start, count); for (JobInstance jobInstance : jobInstances) { list.add(jobInstance.getId()); } @@ -186,7 +175,7 @@ public List getJobInstances(String jobName, int start, int count) throws N @Override @Nullable public JobInstance getJobInstance(String jobName, JobParameters jobParameters) { - return this.jobExplorer.getJobInstance(jobName, jobParameters); + return this.jobRepository.getJobInstance(jobName, jobParameters); } @Override @@ -201,7 +190,7 @@ public String getParameters(long executionId) throws NoSuchJobExecutionException @Override public Set getRunningExecutions(String jobName) throws NoSuchJobException { Set set = new LinkedHashSet<>(); - for (JobExecution jobExecution : jobExplorer.findRunningJobExecutions(jobName)) { + for (JobExecution jobExecution : jobRepository.findRunningJobExecutions(jobName)) { set.add(jobExecution.getId()); } if (set.isEmpty() && !jobRegistry.getJobNames().contains(jobName)) { @@ -299,7 +288,7 @@ public Long startNextInstance(String jobName) } Job job = jobRegistry.getJob(jobName); - JobParameters parameters = new JobParametersBuilder(jobExplorer).getNextJobParameters(job).toJobParameters(); + JobParameters parameters = new JobParametersBuilder(jobRepository).getNextJobParameters(job).toJobParameters(); if (logger.isInfoEnabled()) { logger.info(String.format("Attempting to launch job with name=%s and parameters=%s", jobName, parameters)); } @@ -389,7 +378,7 @@ public JobExecution abandon(long jobExecutionId) } private JobExecution findExecutionById(long executionId) throws NoSuchJobExecutionException { - JobExecution jobExecution = jobExplorer.getJobExecution(executionId); + JobExecution jobExecution = jobRepository.getJobExecution(executionId); if (jobExecution == null) { throw new NoSuchJobExecutionException("No JobExecution found for id: [" + executionId + "]"); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/JobOperatorFactoryBeanTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/JobOperatorFactoryBeanTests.java index c09cbe5925..e8c8778a90 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/JobOperatorFactoryBeanTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/JobOperatorFactoryBeanTests.java @@ -57,7 +57,6 @@ public void testJobOperatorCreation() throws Exception { JobOperatorFactoryBean jobOperatorFactoryBean = new JobOperatorFactoryBean(); jobOperatorFactoryBean.setTransactionManager(this.transactionManager); jobOperatorFactoryBean.setJobLauncher(this.jobLauncher); - jobOperatorFactoryBean.setJobExplorer(this.jobExplorer); jobOperatorFactoryBean.setJobRegistry(this.jobRegistry); jobOperatorFactoryBean.setJobRepository(this.jobRepository); jobOperatorFactoryBean.setJobParametersConverter(this.jobParametersConverter); @@ -80,7 +79,6 @@ public void testCustomTransactionAttributesSource() throws Exception { JobOperatorFactoryBean jobOperatorFactoryBean = new JobOperatorFactoryBean(); jobOperatorFactoryBean.setTransactionManager(this.transactionManager); jobOperatorFactoryBean.setJobLauncher(this.jobLauncher); - jobOperatorFactoryBean.setJobExplorer(this.jobExplorer); jobOperatorFactoryBean.setJobRegistry(this.jobRegistry); jobOperatorFactoryBean.setJobRepository(this.jobRepository); jobOperatorFactoryBean.setJobParametersConverter(this.jobParametersConverter); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/SimpleJobOperatorTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/SimpleJobOperatorTests.java index d5d3951c2c..412e7d9787 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/SimpleJobOperatorTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/SimpleJobOperatorTests.java @@ -79,8 +79,6 @@ class SimpleJobOperatorTests { protected Job job; - private JobExplorer jobExplorer; - private JobRepository jobRepository; private JobParameters jobParameters; @@ -120,10 +118,6 @@ public Set getJobNames() { jobOperator.setJobLauncher( (job, jobParameters) -> new JobExecution(new JobInstance(123L, job.getName()), 999L, jobParameters)); - jobExplorer = mock(); - - jobOperator.setJobExplorer(jobExplorer); - jobRepository = mock(); jobOperator.setJobRepository(jobRepository); @@ -159,8 +153,8 @@ void testMandatoryProperties() { void testStartNextInstanceSunnyDay() throws Exception { jobParameters = new JobParameters(); JobInstance jobInstance = new JobInstance(321L, "foo"); - when(jobExplorer.getJobInstances("foo", 0, 1)).thenReturn(Collections.singletonList(jobInstance)); - when(jobExplorer.getJobExecutions(jobInstance)) + when(jobRepository.getJobInstances("foo", 0, 1)).thenReturn(Collections.singletonList(jobInstance)); + when(jobRepository.getJobExecutions(jobInstance)) .thenReturn(Collections.singletonList(new JobExecution(jobInstance, new JobParameters()))); Long value = jobOperator.startNextInstance("foo"); assertEquals(999, value.longValue()); @@ -190,9 +184,9 @@ void testStartNewInstanceAlreadyExists() { @Test void testResumeSunnyDay() throws Exception { jobParameters = new JobParameters(); - when(jobExplorer.getJobExecution(111L)) + when(jobRepository.getJobExecution(111L)) .thenReturn(new JobExecution(new JobInstance(123L, job.getName()), 111L, jobParameters)); - jobExplorer.getJobExecution(111L); + jobRepository.getJobExecution(111L); Long value = jobOperator.restart(111L); assertEquals(999, value.longValue()); } @@ -201,8 +195,8 @@ void testResumeSunnyDay() throws Exception { void testGetSummarySunnyDay() throws Exception { jobParameters = new JobParameters(); JobExecution jobExecution = new JobExecution(new JobInstance(123L, job.getName()), 111L, jobParameters); - when(jobExplorer.getJobExecution(111L)).thenReturn(jobExecution); - jobExplorer.getJobExecution(111L); + when(jobRepository.getJobExecution(111L)).thenReturn(jobExecution); + jobRepository.getJobExecution(111L); String value = jobOperator.getSummary(111L); assertEquals(jobExecution.toString(), value); } @@ -210,7 +204,7 @@ void testGetSummarySunnyDay() throws Exception { @Test void testGetSummaryNoSuchExecution() { jobParameters = new JobParameters(); - jobExplorer.getJobExecution(111L); + jobRepository.getJobExecution(111L); assertThrows(NoSuchJobExecutionException.class, () -> jobOperator.getSummary(111L)); } @@ -222,7 +216,7 @@ void testGetStepExecutionSummariesSunnyDay() throws Exception { jobExecution.createStepExecution("step1"); jobExecution.createStepExecution("step2"); jobExecution.getStepExecutions().iterator().next().setId(21L); - when(jobExplorer.getJobExecution(111L)).thenReturn(jobExecution); + when(jobRepository.getJobExecution(111L)).thenReturn(jobExecution); Map value = jobOperator.getStepExecutionSummaries(111L); assertEquals(2, value.size()); } @@ -230,7 +224,7 @@ void testGetStepExecutionSummariesSunnyDay() throws Exception { @Test void testGetStepExecutionSummariesNoSuchExecution() { jobParameters = new JobParameters(); - jobExplorer.getJobExecution(111L); + jobRepository.getJobExecution(111L); assertThrows(NoSuchJobExecutionException.class, () -> jobOperator.getStepExecutionSummaries(111L)); } @@ -238,7 +232,7 @@ void testGetStepExecutionSummariesNoSuchExecution() { void testFindRunningExecutionsSunnyDay() throws Exception { jobParameters = new JobParameters(); JobExecution jobExecution = new JobExecution(new JobInstance(123L, job.getName()), 111L, jobParameters); - when(jobExplorer.findRunningJobExecutions("foo")).thenReturn(Collections.singleton(jobExecution)); + when(jobRepository.findRunningJobExecutions("foo")).thenReturn(Collections.singleton(jobExecution)); Set value = jobOperator.getRunningExecutions("foo"); assertEquals(111L, value.iterator().next().longValue()); } @@ -247,14 +241,14 @@ void testFindRunningExecutionsSunnyDay() throws Exception { @SuppressWarnings("unchecked") void testFindRunningExecutionsNoSuchJob() { jobParameters = new JobParameters(); - when(jobExplorer.findRunningJobExecutions("no-such-job")).thenReturn(Collections.EMPTY_SET); + when(jobRepository.findRunningJobExecutions("no-such-job")).thenReturn(Collections.EMPTY_SET); assertThrows(NoSuchJobException.class, () -> jobOperator.getRunningExecutions("no-such-job")); } @Test void testGetJobParametersSunnyDay() throws Exception { final JobParameters jobParameters = new JobParameters(); - when(jobExplorer.getJobExecution(111L)) + when(jobRepository.getJobExecution(111L)) .thenReturn(new JobExecution(new JobInstance(123L, job.getName()), 111L, jobParameters)); String value = jobOperator.getParameters(111L); assertEquals("a=b", value); @@ -262,7 +256,7 @@ void testGetJobParametersSunnyDay() throws Exception { @Test void testGetJobParametersNoSuchExecution() { - jobExplorer.getJobExecution(111L); + jobRepository.getJobExecution(111L); assertThrows(NoSuchJobExecutionException.class, () -> jobOperator.getParameters(111L)); } @@ -270,8 +264,8 @@ void testGetJobParametersNoSuchExecution() { void testGetLastInstancesSunnyDay() throws Exception { jobParameters = new JobParameters(); JobInstance jobInstance = new JobInstance(123L, job.getName()); - when(jobExplorer.getJobInstances("foo", 0, 2)).thenReturn(Collections.singletonList(jobInstance)); - jobExplorer.getJobInstances("foo", 0, 2); + when(jobRepository.getJobInstances("foo", 0, 2)).thenReturn(Collections.singletonList(jobInstance)); + jobRepository.getJobInstances("foo", 0, 2); List value = jobOperator.getJobInstances("foo", 0, 2); assertEquals(123L, value.get(0).longValue()); } @@ -279,7 +273,7 @@ void testGetLastInstancesSunnyDay() throws Exception { @Test void testGetLastInstancesNoSuchJob() { jobParameters = new JobParameters(); - jobExplorer.getJobInstances("no-such-job", 0, 2); + jobRepository.getJobInstances("no-such-job", 0, 2); assertThrows(NoSuchJobException.class, () -> jobOperator.getJobInstances("no-such-job", 0, 2)); } @@ -291,11 +285,11 @@ public void testGetJobInstanceWithNameAndParameters() { JobInstance jobInstance = mock(); // when - when(this.jobExplorer.getJobInstance(jobName, jobParameters)).thenReturn(jobInstance); + when(this.jobRepository.getJobInstance(jobName, jobParameters)).thenReturn(jobInstance); JobInstance actualJobInstance = this.jobOperator.getJobInstance(jobName, jobParameters); // then - verify(this.jobExplorer).getJobInstance(jobName, jobParameters); + verify(this.jobRepository).getJobInstance(jobName, jobParameters); assertEquals(jobInstance, actualJobInstance); } @@ -309,17 +303,17 @@ void testGetJobNames() { @Test void testGetExecutionsSunnyDay() throws Exception { JobInstance jobInstance = new JobInstance(123L, job.getName()); - when(jobExplorer.getJobInstance(123L)).thenReturn(jobInstance); + when(jobRepository.getJobInstance(123L)).thenReturn(jobInstance); JobExecution jobExecution = new JobExecution(jobInstance, 111L, jobParameters); - when(jobExplorer.getJobExecutions(jobInstance)).thenReturn(Collections.singletonList(jobExecution)); + when(jobRepository.getJobExecutions(jobInstance)).thenReturn(Collections.singletonList(jobExecution)); List value = jobOperator.getExecutions(123L); assertEquals(111L, value.iterator().next().longValue()); } @Test void testGetExecutionsNoSuchInstance() { - jobExplorer.getJobInstance(123L); + jobRepository.getJobInstance(123L); assertThrows(NoSuchJobInstanceException.class, () -> jobOperator.getExecutions(123L)); } @@ -327,8 +321,8 @@ void testGetExecutionsNoSuchInstance() { void testStop() throws Exception { JobInstance jobInstance = new JobInstance(123L, job.getName()); JobExecution jobExecution = new JobExecution(jobInstance, 111L, jobParameters); - when(jobExplorer.getJobExecution(111L)).thenReturn(jobExecution); - jobExplorer.getJobExecution(111L); + when(jobRepository.getJobExecution(111L)).thenReturn(jobExecution); + jobRepository.getJobExecution(111L); jobRepository.update(jobExecution); jobOperator.stop(111L); assertEquals(BatchStatus.STOPPING, jobExecution.getStatus()); @@ -350,10 +344,10 @@ void testStopTasklet() throws Exception { when(step.getTasklet()).thenReturn(tasklet); when(step.getName()).thenReturn("test_job.step1"); when(jobRegistry.getJob(any(String.class))).thenReturn(job); - when(jobExplorer.getJobExecution(111L)).thenReturn(jobExecution); + when(jobRepository.getJobExecution(111L)).thenReturn(jobExecution); jobOperator.setJobRegistry(jobRegistry); - jobExplorer.getJobExecution(111L); + jobRepository.getJobExecution(111L); jobRepository.update(jobExecution); jobOperator.stop(111L); assertEquals(BatchStatus.STOPPING, jobExecution.getStatus()); @@ -369,7 +363,7 @@ void testStopTaskletWhenJobNotRegistered() throws Exception { when(step.getTasklet()).thenReturn(tasklet); when(jobRegistry.getJob(job.getName())).thenThrow(new NoSuchJobException("Unable to find job")); - when(jobExplorer.getJobExecution(111L)).thenReturn(jobExecution); + when(jobRepository.getJobExecution(111L)).thenReturn(jobExecution); jobOperator.setJobRegistry(jobRegistry); jobOperator.stop(111L); @@ -405,10 +399,10 @@ public void stop() { when(step.getTasklet()).thenReturn(tasklet); when(step.getName()).thenReturn("test_job.step1"); when(jobRegistry.getJob(any(String.class))).thenReturn(job); - when(jobExplorer.getJobExecution(111L)).thenReturn(jobExecution); + when(jobRepository.getJobExecution(111L)).thenReturn(jobExecution); jobOperator.setJobRegistry(jobRegistry); - jobExplorer.getJobExecution(111L); + jobRepository.getJobExecution(111L); jobRepository.update(jobExecution); jobOperator.stop(111L); assertEquals(BatchStatus.STOPPING, jobExecution.getStatus()); @@ -419,7 +413,7 @@ void testAbort() throws Exception { JobInstance jobInstance = new JobInstance(123L, job.getName()); JobExecution jobExecution = new JobExecution(jobInstance, 111L, jobParameters); jobExecution.setStatus(BatchStatus.STOPPING); - when(jobExplorer.getJobExecution(123L)).thenReturn(jobExecution); + when(jobRepository.getJobExecution(123L)).thenReturn(jobExecution); jobRepository.update(jobExecution); jobOperator.abandon(123L); assertEquals(BatchStatus.ABANDONED, jobExecution.getStatus()); @@ -431,7 +425,7 @@ void testAbortNonStopping() { JobInstance jobInstance = new JobInstance(123L, job.getName()); JobExecution jobExecution = new JobExecution(jobInstance, 111L, jobParameters); jobExecution.setStatus(BatchStatus.STARTED); - when(jobExplorer.getJobExecution(123L)).thenReturn(jobExecution); + when(jobRepository.getJobExecution(123L)).thenReturn(jobExecution); jobRepository.update(jobExecution); assertThrows(JobExecutionAlreadyRunningException.class, () -> jobOperator.abandon(123L)); } diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/repository/dao/OptimisticLockingFailureTests-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/repository/dao/OptimisticLockingFailureTests-context.xml index e6573aa452..d3958ef861 100644 --- a/spring-batch-core/src/test/resources/org/springframework/batch/core/repository/dao/OptimisticLockingFailureTests-context.xml +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/repository/dao/OptimisticLockingFailureTests-context.xml @@ -58,7 +58,6 @@ - diff --git a/spring-batch-core/src/test/resources/simple-job-launcher-context.xml b/spring-batch-core/src/test/resources/simple-job-launcher-context.xml index 1dbd4308a4..8bedd42400 100644 --- a/spring-batch-core/src/test/resources/simple-job-launcher-context.xml +++ b/spring-batch-core/src/test/resources/simple-job-launcher-context.xml @@ -21,8 +21,7 @@ + p:jobLauncher-ref="jobLauncher" p:jobRepository-ref="jobRepository" p:jobRegistry-ref="jobRegistry" /> - - @@ -53,7 +49,6 @@ - diff --git a/spring-batch-samples/src/main/resources/org/springframework/batch/samples/skip/job/skipSample-job-launcher-context.xml b/spring-batch-samples/src/main/resources/org/springframework/batch/samples/skip/job/skipSample-job-launcher-context.xml index 4b78507734..bc9d4bc447 100644 --- a/spring-batch-samples/src/main/resources/org/springframework/batch/samples/skip/job/skipSample-job-launcher-context.xml +++ b/spring-batch-samples/src/main/resources/org/springframework/batch/samples/skip/job/skipSample-job-launcher-context.xml @@ -14,7 +14,7 @@ p:dataSource-ref="dataSource" p:transactionManager-ref="transactionManager" /> + p:jobRepository-ref="jobRepository" p:jobRegistry-ref="jobRegistry" /> diff --git a/spring-batch-samples/src/main/resources/simple-job-launcher-context.xml b/spring-batch-samples/src/main/resources/simple-job-launcher-context.xml index ee3c50c529..61db4fb2b7 100644 --- a/spring-batch-samples/src/main/resources/simple-job-launcher-context.xml +++ b/spring-batch-samples/src/main/resources/simple-job-launcher-context.xml @@ -21,8 +21,7 @@ + p:jobLauncher-ref="jobLauncher" p:jobRepository-ref="jobRepository" p:jobRegistry-ref="jobRegistry" /> Date: Mon, 5 May 2025 18:36:07 +0200 Subject: [PATCH 093/266] Remove JobExplorer bean registration from the default batch configuration Resolves #4825 --- .../annotation/BatchRegistrar.java | 47 ------------------- .../annotation/EnableBatchProcessing.java | 3 -- .../support/DefaultBatchConfiguration.java | 21 --------- .../annotation/BatchRegistrarTests.java | 9 ---- .../DefaultBatchConfigurationTests.java | 3 -- 5 files changed, 83 deletions(-) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/BatchRegistrar.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/BatchRegistrar.java index 631f12b262..d8e58f138f 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/BatchRegistrar.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/BatchRegistrar.java @@ -53,8 +53,6 @@ class BatchRegistrar implements ImportBeanDefinitionRegistrar { private static final String JOB_REPOSITORY = "jobRepository"; - private static final String JOB_EXPLORER = "jobExplorer"; - private static final String JOB_LAUNCHER = "jobLauncher"; private static final String JOB_REGISTRY = "jobRegistry"; @@ -70,7 +68,6 @@ public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, B .get(EnableBatchProcessing.class) .synthesize(); registerJobRepository(registry, batchAnnotation); - registerJobExplorer(registry, batchAnnotation); registerJobLauncher(registry, batchAnnotation); registerJobRegistry(registry); registerJobRegistrySmartInitializingSingleton(registry); @@ -151,50 +148,6 @@ private void registerJobRepository(BeanDefinitionRegistry registry, EnableBatchP registry.registerBeanDefinition(JOB_REPOSITORY, beanDefinitionBuilder.getBeanDefinition()); } - private void registerJobExplorer(BeanDefinitionRegistry registry, EnableBatchProcessing batchAnnotation) { - if (registry.containsBeanDefinition(JOB_EXPLORER)) { - LOGGER.info("Bean jobExplorer already defined in the application context, skipping" - + " the registration of a jobExplorer"); - return; - } - BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder - .genericBeanDefinition(JobExplorerFactoryBean.class); - - // set mandatory properties - String dataSourceRef = batchAnnotation.dataSourceRef(); - beanDefinitionBuilder.addPropertyReference("dataSource", dataSourceRef); - - String transactionManagerRef = batchAnnotation.transactionManagerRef(); - beanDefinitionBuilder.addPropertyReference("transactionManager", transactionManagerRef); - - // set optional properties - String executionContextSerializerRef = batchAnnotation.executionContextSerializerRef(); - if (registry.containsBeanDefinition(executionContextSerializerRef)) { - beanDefinitionBuilder.addPropertyReference("serializer", executionContextSerializerRef); - } - - String conversionServiceRef = batchAnnotation.conversionServiceRef(); - if (registry.containsBeanDefinition(conversionServiceRef)) { - beanDefinitionBuilder.addPropertyReference("conversionService", conversionServiceRef); - } - - String jobKeyGeneratorRef = batchAnnotation.jobKeyGeneratorRef(); - if (registry.containsBeanDefinition(jobKeyGeneratorRef)) { - beanDefinitionBuilder.addPropertyReference("jobKeyGenerator", jobKeyGeneratorRef); - } - - String charset = batchAnnotation.charset(); - if (charset != null) { - beanDefinitionBuilder.addPropertyValue("charset", Charset.forName(charset)); - } - - String tablePrefix = batchAnnotation.tablePrefix(); - if (tablePrefix != null) { - beanDefinitionBuilder.addPropertyValue("tablePrefix", tablePrefix); - } - registry.registerBeanDefinition(JOB_EXPLORER, beanDefinitionBuilder.getBeanDefinition()); - } - private void registerJobLauncher(BeanDefinitionRegistry registry, EnableBatchProcessing batchAnnotation) { if (registry.containsBeanDefinition(JOB_LAUNCHER)) { LOGGER.info("Bean jobLauncher already defined in the application context, skipping" diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/EnableBatchProcessing.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/EnableBatchProcessing.java index 6e2740079d..999706a53a 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/EnableBatchProcessing.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/EnableBatchProcessing.java @@ -87,9 +87,6 @@ * {@link TaskExecutorJobLauncher}) *

  • a {@link JobRegistry} (bean name "jobRegistry" of type * {@link org.springframework.batch.core.configuration.support.MapJobRegistry})
  • - *
  • a {@link org.springframework.batch.core.explore.JobExplorer} (bean name - * "jobExplorer" of type - * {@link org.springframework.batch.core.explore.support.SimpleJobExplorer})
  • *
  • a {@link org.springframework.batch.core.launch.JobOperator} (bean name * "jobOperator" of type * {@link org.springframework.batch.core.launch.support.SimpleJobOperator})
  • diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/DefaultBatchConfiguration.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/DefaultBatchConfiguration.java index 1a628b3d93..dea1f33319 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/DefaultBatchConfiguration.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/DefaultBatchConfiguration.java @@ -79,7 +79,6 @@ * *