diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 6ee3f86c63..30f58a8110 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,10 +1,10 @@ Thank you for taking time to contribute this pull request! You might have already read the [contributor guide][1], but as a reminder, please make sure to: -* Sign the [contributor license agreement](https://cla.pivotal.io/sign/spring) * Rebase your changes on the latest `main` branch and squash your commits * Add/Update unit tests as needed * Run a build and make sure all tests pass prior to submission +* Sign-off commits according to the [Developer Certificate of Origin](https://spring.io/blog/2025/01/06/hello-dco-goodbye-cla-simplifying-contributions-to-spring) For more details, please check the [contributor guide][1]. Thank you upfront! 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 diff --git a/.github/release-files-spec.json b/.github/release-files-spec.json new file mode 100644 index 0000000000..1d071702fc --- /dev/null +++ b/.github/release-files-spec.json @@ -0,0 +1,18 @@ +{ + "files": [ + { + "aql": { + "items.find": { + "$and": [ + { + "@build.name": "${buildname}", + "@build.number": "${buildnumber}", + "path": { "$match": "org/springframework/batch/spring-batch-*" } + } + ] + } + }, + "target": "nexus/" + } + ] +} diff --git a/.github/workflows/artifactory-milestone-release.yml b/.github/workflows/artifactory-milestone-release.yml deleted file mode 100644 index 4c368a39a4..0000000000 --- a/.github/workflows/artifactory-milestone-release.yml +++ /dev/null @@ -1,38 +0,0 @@ -name: Artifactory Milestone Release - -on: - workflow_dispatch: - inputs: - releaseVersion: - description: "Milestone release version" - required: true - -jobs: - build: - name: Release milestone to Artifactory - runs-on: ubuntu-latest - steps: - - name: Checkout source code - uses: actions/checkout@v3 - - - name: Set up JDK 17 - uses: actions/setup-java@v3 - with: - java-version: '17' - distribution: 'temurin' - cache: 'maven' - - - name: Capture release version - run: echo RELEASE_VERSION=${{ github.event.inputs.releaseVersion }} >> $GITHUB_ENV - - - name: Update release version - run: mvn versions:set -DgenerateBackupPoms=false -DnewVersion=$RELEASE_VERSION - - - name: Enforce release rules - run: mvn org.apache.maven.plugins:maven-enforcer-plugin:enforce -Drules=requireReleaseDeps - - - name: Build with Maven and deploy to Artifactory's milestone repository - env: - ARTIFACTORY_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }} - ARTIFACTORY_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }} - run: mvn -P artifactory-milestone -s settings.xml --batch-mode -Dmaven.test.skip=true deploy diff --git a/.github/workflows/artifactory-staging.yml b/.github/workflows/artifactory-staging.yml index ae8a04859c..b62b2e6848 100644 --- a/.github/workflows/artifactory-staging.yml +++ b/.github/workflows/artifactory-staging.yml @@ -13,26 +13,32 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout source code - uses: actions/checkout@v3 + uses: actions/checkout@v4.2.2 - name: Set up JDK 17 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4.7.1 with: java-version: '17' distribution: 'temurin' cache: 'maven' - - name: Capture release version - run: echo RELEASE_VERSION=${{ github.event.inputs.releaseVersion }} >> $GITHUB_ENV - - name: Update release version - run: mvn versions:set -DgenerateBackupPoms=false -DnewVersion=$RELEASE_VERSION + run: mvn versions:set -DgenerateBackupPoms=false -DnewVersion=${{ github.event.inputs.releaseVersion }} - name: Enforce release rules run: mvn org.apache.maven.plugins:maven-enforcer-plugin:enforce -Drules=requireReleaseDeps - - name: Build with Maven and deploy to Artifactory staging repository - env: - ARTIFACTORY_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }} - ARTIFACTORY_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }} - run: mvn -P artifactory-staging -s settings.xml --batch-mode -Dmaven.test.skip=true deploy + - name: Build with Maven + run: mvn -DaltDeploymentRepository=local::file:deployment-repository --no-transfer-progress --batch-mode -Dmaven.test.skip=true deploy + + - name: Deploy to Artifactory + uses: spring-io/artifactory-deploy-action@v0.0.2 + with: + uri: 'https://repo.spring.io' + username: ${{ secrets.ARTIFACTORY_USERNAME }} + password: ${{ secrets.ARTIFACTORY_PASSWORD }} + build-name: 'spring-batch-${{ github.event.inputs.releaseVersion }}' + repository: 'libs-staging-local' + folder: 'deployment-repository' + signing-key: ${{ secrets.GPG_PRIVATE_KEY }} + signing-passphrase: ${{ secrets.GPG_PASSPHRASE }} diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 37dc5a6925..ab5e0aeed2 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -1,30 +1,37 @@ name: CI/CD build -on: - workflow_dispatch: - push: - branches: [ "main" ] +on: [push, pull_request, workflow_dispatch] jobs: build: - name: Build branch + name: Build main branch runs-on: ubuntu-latest steps: - name: Checkout source code - uses: actions/checkout@v3 + uses: actions/checkout@v4.2.2 - name: Set up JDK 17 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4.7.1 with: java-version: '17' distribution: 'temurin' cache: 'maven' - - name: Build with Maven and deploy to Artifactory - env: - ARTIFACTORY_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }} - ARTIFACTORY_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }} - run: mvn -s settings.xml --batch-mode --update-snapshots deploy + - name: Build with Maven + run: mvn -DaltDeploymentRepository=local::file:deployment-repository --no-transfer-progress --batch-mode --update-snapshots deploy + + - name: Deploy to Artifactory + if: ${{ github.repository == 'spring-projects/spring-batch' && github.ref_name == 'main' }} + uses: spring-io/artifactory-deploy-action@v0.0.2 + with: + uri: 'https://repo.spring.io' + username: ${{ secrets.ARTIFACTORY_USERNAME }} + password: ${{ secrets.ARTIFACTORY_PASSWORD }} + build-name: 'spring-batch-main' + repository: 'libs-snapshot-local' + folder: 'deployment-repository' + signing-key: ${{ secrets.GPG_PRIVATE_KEY }} + signing-passphrase: ${{ secrets.GPG_PASSPHRASE }} - name: Generate Java docs run: mvn javadoc:aggregate @@ -37,6 +44,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 +55,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 }} diff --git a/.github/workflows/maven-central-release.yml b/.github/workflows/maven-central-release.yml index bce60ac9ea..45608509c9 100644 --- a/.github/workflows/maven-central-release.yml +++ b/.github/workflows/maven-central-release.yml @@ -3,81 +3,30 @@ name: Maven Central Release on: workflow_dispatch: inputs: - releaseVersion: - description: "Release version" + buildName: + description: "Artifactory build name" + required: true + buildNumber: + description: "Artifactory build number" required: true jobs: - build: + + release: runs-on: ubuntu-latest steps: - - - name: Capture release version - run: echo RELEASE_VERSION=${{ github.event.inputs.releaseVersion }} >> $GITHUB_ENV - - - name: Prepare directory structure - run: | - mkdir -p nexus/org/springframework/batch/spring-batch-bom/$RELEASE_VERSION - mkdir -p nexus/org/springframework/batch/spring-batch-infrastructure/$RELEASE_VERSION - mkdir -p nexus/org/springframework/batch/spring-batch-core/$RELEASE_VERSION - mkdir -p nexus/org/springframework/batch/spring-batch-test/$RELEASE_VERSION - mkdir -p nexus/org/springframework/batch/spring-batch-integration/$RELEASE_VERSION - - - name: Download release files from Artifactory + - name: Checkout source code + uses: actions/checkout@v4.2.2 + - name: Set Up JFrog CLI + uses: jfrog/setup-jfrog-cli@9fe0f98bd45b19e6e931d457f4e98f8f84461fb5 # v4.4.1 env: - ARTIFACTORY_URL: "https://repo.spring.io/libs-staging-local/org/springframework/batch" - ARTIFACTORY_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }} - ARTIFACTORY_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }} - run: | - echo "Downloading BOM artifacts" - cd nexus/org/springframework/batch/spring-batch-bom/$RELEASE_VERSION - wget --user="$ARTIFACTORY_USERNAME" --password="$ARTIFACTORY_PASSWORD" $ARTIFACTORY_URL/spring-batch-bom/$RELEASE_VERSION/spring-batch-bom-$RELEASE_VERSION.pom - - echo "Downloading infrastructure artifacts" - cd ../../../../../.. - cd nexus/org/springframework/batch/spring-batch-infrastructure/$RELEASE_VERSION - wget --user="$ARTIFACTORY_USERNAME" --password="$ARTIFACTORY_PASSWORD" $ARTIFACTORY_URL/spring-batch-infrastructure/$RELEASE_VERSION/spring-batch-infrastructure-$RELEASE_VERSION.pom - wget --user="$ARTIFACTORY_USERNAME" --password="$ARTIFACTORY_PASSWORD" $ARTIFACTORY_URL/spring-batch-infrastructure/$RELEASE_VERSION/spring-batch-infrastructure-$RELEASE_VERSION.jar - wget --user="$ARTIFACTORY_USERNAME" --password="$ARTIFACTORY_PASSWORD" $ARTIFACTORY_URL/spring-batch-infrastructure/$RELEASE_VERSION/spring-batch-infrastructure-$RELEASE_VERSION-javadoc.jar - wget --user="$ARTIFACTORY_USERNAME" --password="$ARTIFACTORY_PASSWORD" $ARTIFACTORY_URL/spring-batch-infrastructure/$RELEASE_VERSION/spring-batch-infrastructure-$RELEASE_VERSION-sources.jar - - echo "Downloading core artifacts" - cd ../../../../../.. - cd nexus/org/springframework/batch/spring-batch-core/$RELEASE_VERSION - wget --user="$ARTIFACTORY_USERNAME" --password="$ARTIFACTORY_PASSWORD" $ARTIFACTORY_URL/spring-batch-core/$RELEASE_VERSION/spring-batch-core-$RELEASE_VERSION.pom - wget --user="$ARTIFACTORY_USERNAME" --password="$ARTIFACTORY_PASSWORD" $ARTIFACTORY_URL/spring-batch-core/$RELEASE_VERSION/spring-batch-core-$RELEASE_VERSION.jar - wget --user="$ARTIFACTORY_USERNAME" --password="$ARTIFACTORY_PASSWORD" $ARTIFACTORY_URL/spring-batch-core/$RELEASE_VERSION/spring-batch-core-$RELEASE_VERSION-javadoc.jar - wget --user="$ARTIFACTORY_USERNAME" --password="$ARTIFACTORY_PASSWORD" $ARTIFACTORY_URL/spring-batch-core/$RELEASE_VERSION/spring-batch-core-$RELEASE_VERSION-sources.jar - - echo "Downloading test artifacts" - cd ../../../../../.. - cd nexus/org/springframework/batch/spring-batch-test/$RELEASE_VERSION - wget --user="$ARTIFACTORY_USERNAME" --password="$ARTIFACTORY_PASSWORD" $ARTIFACTORY_URL/spring-batch-test/$RELEASE_VERSION/spring-batch-test-$RELEASE_VERSION.pom - wget --user="$ARTIFACTORY_USERNAME" --password="$ARTIFACTORY_PASSWORD" $ARTIFACTORY_URL/spring-batch-test/$RELEASE_VERSION/spring-batch-test-$RELEASE_VERSION.jar - wget --user="$ARTIFACTORY_USERNAME" --password="$ARTIFACTORY_PASSWORD" $ARTIFACTORY_URL/spring-batch-test/$RELEASE_VERSION/spring-batch-test-$RELEASE_VERSION-javadoc.jar - wget --user="$ARTIFACTORY_USERNAME" --password="$ARTIFACTORY_PASSWORD" $ARTIFACTORY_URL/spring-batch-test/$RELEASE_VERSION/spring-batch-test-$RELEASE_VERSION-sources.jar - - echo "Downloading integration artifacts" - cd ../../../../../.. - cd nexus/org/springframework/batch/spring-batch-integration/$RELEASE_VERSION - wget --user="$ARTIFACTORY_USERNAME" --password="$ARTIFACTORY_PASSWORD" $ARTIFACTORY_URL/spring-batch-integration/$RELEASE_VERSION/spring-batch-integration-$RELEASE_VERSION.pom - wget --user="$ARTIFACTORY_USERNAME" --password="$ARTIFACTORY_PASSWORD" $ARTIFACTORY_URL/spring-batch-integration/$RELEASE_VERSION/spring-batch-integration-$RELEASE_VERSION.jar - wget --user="$ARTIFACTORY_USERNAME" --password="$ARTIFACTORY_PASSWORD" $ARTIFACTORY_URL/spring-batch-integration/$RELEASE_VERSION/spring-batch-integration-$RELEASE_VERSION-javadoc.jar - 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 - id: nexus + JF_ENV_SPRING: ${{ secrets.JF_ARTIFACTORY_SPRING }} + - name: Download Release Artifacts + shell: bash + run: jf rt download --spec .github/release-files-spec.json --spec-vars 'buildname=${{ github.event.inputs.buildName }};buildnumber=${{ github.event.inputs.buildNumber }}' + - name: Sync to Maven Central + uses: spring-io/central-publish-action@0cdd90d12e6876341e82860d951e1bcddc1e51b6 # v0.2.0 with: - url: ${{ secrets.OSSRH_URL }} - username: ${{ secrets.OSSRH_S01_TOKEN_USERNAME }} - password: ${{ secrets.OSSRH_S01_TOKEN_PASSWORD }} - staging-profile-name: ${{ secrets.OSSRH_STAGING_PROFILE_NAME }} - create: true - upload: true - close: true - release: true - generate-checksums: true - pgp-sign: true - pgp-sign-passphrase: ${{ secrets.GPG_PASSPHRASE }} - pgp-sign-private-key: ${{ secrets.GPG_PRIVATE_KEY }} + token-name: ${{ secrets.CENTRAL_TOKEN_USERNAME }} + token: ${{ secrets.CENTRAL_TOKEN_PASSWORD }} + timeout: 60m diff --git a/.gitignore b/.gitignore index e5ff39fa6a..4563de84f8 100644 --- a/.gitignore +++ b/.gitignore @@ -12,7 +12,8 @@ derbydb derby.log com.springsource.sts.config.flow.prefs s3.properties -.idea +.idea/* +!/.idea/icon.svg *.iml *.ipr *.iws diff --git a/.idea/icon.svg b/.idea/icon.svg new file mode 100644 index 0000000000..3ad7681541 --- /dev/null +++ b/.idea/icon.svg @@ -0,0 +1 @@ +Codestin Search App \ No newline at end of file diff --git a/.mvn/jvm.config b/.mvn/jvm.config new file mode 100644 index 0000000000..32599cefea --- /dev/null +++ b/.mvn/jvm.config @@ -0,0 +1,10 @@ +--add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED +--add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED +--add-exports jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED +--add-exports jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED +--add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED +--add-exports jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED +--add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED +--add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED +--add-opens jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED +--add-opens jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED 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 diff --git a/README.md b/README.md index 3dd6c56f63..0025c1b98a 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,15 @@ +# Latest news + +* September 17, 2025: [Spring Batch 6.0.0 M3 and 5.2.3 available now](https://spring.io/blog/2025/09/17/spring-batch-6-0-0-m3-5-2-3-released) +* August 20, 2025: [Spring Batch 6.0.0 M2 available now](https://spring.io/blog/2025/08/20/spring-batch-6) +* July 23, 2025: [Spring Batch 6.0.0 M1 is out!](https://spring.io/blog/2025/07/23/spring-batch-6) + # 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) Spring Batch is a lightweight, comprehensive batch framework designed to enable the development of robust batch applications vital for the daily operations of enterprise systems. Spring Batch builds upon the productivity, POJO-based development approach, and general ease of use capabilities people have come to know from the [Spring Framework](https://github.com/spring-projects/spring-framework), while making it easy for developers to access and leverage more advanced enterprise services when necessary. -If you are looking for a runtime orchestration tool for your Batch applications, or need a management console to view current and historic executions, take a look at [Spring Cloud Data Flow](https://cloud.spring.io/spring-cloud-dataflow/). It is an orchestration tool for deploying and executing data integration based microservices including Spring Batch applications. - # Getting Started ## Two minutes tutorial @@ -214,12 +218,10 @@ We welcome contributions in any kind! Here are some ways for you to contribute t * Github is for social coding: if you want to write code, we encourage contributions through pull requests. If you want to contribute code this way, please familiarize yourself with the process outlined here: [Contributor Guidelines](https://github.com/spring-projects/spring-batch/blob/main/CONTRIBUTING.md). * Watch for Spring Batch related articles on [spring.io](https://spring.io). -Before we accept pull requests, we will need you to sign the [contributor's agreement](https://support.springsource.com/spring_committer_signup). Signing the contributor's agreement does not grant anyone commit rights to the main repository, but it does mean that we can accept your contributions, and you will get an author credit if we do. Active contributors might be asked to join the core team, and given the ability to merge pull requests. - # Code of Conduct Please see our [code of conduct](https://github.com/spring-projects/.github/blob/main/CODE_OF_CONDUCT.md). # 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). diff --git a/pom.xml b/pom.xml index eb854c35f1..3f72767482 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 + 6.0.0-SNAPSHOT pom https://projects.spring.io/spring-batch @@ -39,15 +39,6 @@ Github Actions https://github.com/spring-projects/spring-batch/actions - - - spring-snapshots - https://repo.spring.io/libs-snapshot-local - - false - - - Apache 2.0 @@ -61,95 +52,103 @@ 17 - 6.2.0-RC1 - 2.0.9 - 6.4.0-M3 - 1.14.0-M3 + 7.0.0-SNAPSHOT + 2.0.13-SNAPSHOT + 7.0.0-SNAPSHOT + 1.16.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 + 4.0.0-SNAPSHOT + 4.0.0-SNAPSHOT + 4.0.0-SNAPSHOT + 5.0.0-SNAPSHOT + 4.0.0-SNAPSHOT + 4.0.0-SNAPSHOT + 4.0.0-SNAPSHOT - 2.18.0 + 2.19.2 1.12.0 - 2.11.0 - 6.6.0.Final + 2.13.1 + 7.1.0.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 + 3.1.1 + 3.2.0 + 5.5.1 + 6.0.0-RC3 + 6.0.0-RC3 3.0.2 - 1.4.0-M3 + 1.6.0-SNAPSHOT - 1.4.20 + 1.4.21 4.13.2 ${junit-jupiter.version} 3.0 - 3.26.3 - 5.14.1 - 2.10.0 - 2.17.0 - 2.12.0 - 2.0.16 - 2.7.3 + 3.27.4 + 5.19.0 + 2.10.3 + 2.20.0 + 2.13.0 + 2.0.17 + 2.7.4 2.3.232 - 3.46.1.3 + 3.50.3.0 10.16.1.1 - 2.21.11 - 2.37.0 + 2.25.12 + 2.42.0 4.0.5 - 2.23.1 - 8.0.1.Final + 2.25.1 + 9.0.1.Final 6.0.1 4.0.2 2.0.1 4.0.2 - 2.0.3 - 7.0.0 - 1.9.22.1 - 9.0.0 - 3.4.1 - 42.7.4 - 11.5.9.0 - 19.24.0.0 + 2.0.4 + 7.1.1 + 1.9.24 + 9.4.0 + 3.5.5 + 42.7.7 + 12.1.2.0 + 19.28.0.0 11.2.3.jre17 1.3.1 - 1.20.1 + 1.21.3 + 2.2.4 1.5.3 + 4.0.28 + 15.6 + 2.0b6 + 9.4.13.0 + 6.8.0.RELEASE + 6.1.0 ${spring-amqp.version} - 2.3.2 - 0.16.0 - 3.0.22 + 2.5.0 + 1.4.1 + 3.0.22 0.0.4 - 3.13.0 - 3.5.0 - 3.5.0 - 3.10.0 + 3.14.0 + 3.5.3 + 3.5.3 + 3.11.3 3.3.1 - 1.6.0 - 3.1.3 + 1.7.2 + 3.1.4 3.7.1 3.4.2 - 0.0.39 + 0.0.47 + 2.41.0 @@ -176,7 +175,20 @@ ${java.version} -parameters + + -XDcompilePolicy=simple + --should-stop=ifError=FLOW + + -Xplugin:ErrorProne + + + + com.google.errorprone + error_prone_core + ${error-prone.version} + + @@ -187,6 +199,7 @@ ${surefireArgLine} **/*IntegrationTests.java + **/*FunctionalTests.java @@ -197,6 +210,7 @@ **/*IntegrationTests.java + **/*FunctionalTests.java @@ -303,33 +317,6 @@ - - - artifactory-staging - - - spring-staging - https://repo.spring.io/libs-staging-local - - false - - - - - - artifactory-milestone - - - spring-milestones - https://repo.spring.io/libs-milestone-local - - false - - - - - - maven-central @@ -352,14 +339,6 @@ false - - spring-milestones - Spring Milestones - https://repo.spring.io/milestone - - false - - diff --git a/settings.xml b/settings.xml deleted file mode 100644 index 890e930709..0000000000 --- a/settings.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - spring-snapshots - ${env.ARTIFACTORY_USERNAME} - ${env.ARTIFACTORY_PASSWORD} - - - spring-staging - ${env.ARTIFACTORY_USERNAME} - ${env.ARTIFACTORY_PASSWORD} - - - spring-milestones - ${env.ARTIFACTORY_USERNAME} - ${env.ARTIFACTORY_PASSWORD} - - - - \ No newline at end of file diff --git a/spring-batch-bom/pom.xml b/spring-batch-bom/pom.xml index ea12c3c9aa..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.0-M2 + 6.0.0-SNAPSHOT spring-batch-bom pom diff --git a/spring-batch-core/pom.xml b/spring-batch-core/pom.xml index bf804530b2..048a1e24f1 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 + 6.0.0-SNAPSHOT spring-batch-core jar @@ -128,6 +128,7 @@ org.springframework.data spring-data-commons ${spring-data-commons.version} + true org.mongodb @@ -155,6 +156,12 @@ ${testcontainers.version} test + + org.junit.platform + junit-platform-launcher + ${junit-platform-launcher.version} + test + org.hsqldb hsqldb diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/Entity.java b/spring-batch-core/src/main/java/org/springframework/batch/core/Entity.java index 6bcc5818ab..78e3e2bcb3 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/Entity.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/Entity.java @@ -146,7 +146,7 @@ public boolean equals(Object other) { @Override public int hashCode() { if (id == null) { - return super.hashCode(); + return System.identityHashCode(this); } return 39 + 87 * id.hashCode(); } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/ExitStatus.java b/spring-batch-core/src/main/java/org/springframework/batch/core/ExitStatus.java index edaa4986eb..e03c084b95 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/ExitStatus.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/ExitStatus.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. @@ -15,6 +15,7 @@ */ package org.springframework.batch.core; +import org.jspecify.annotations.Nullable; import org.springframework.util.StringUtils; import java.io.PrintWriter; @@ -28,6 +29,7 @@ * * @author Dave Syer * @author Mahmoud Ben Hassine + * @author JiWon Seo * */ public class ExitStatus implements Serializable, Comparable { @@ -198,7 +200,7 @@ public String toString() { * @see java.lang.Object#equals(java.lang.Object) */ @Override - public boolean equals(Object obj) { + public boolean equals(@Nullable Object obj) { if (obj == null) { return false; } @@ -230,7 +232,7 @@ public ExitStatus replaceExitCode(String code) { * @return {@code true} if the exit code is {@code EXECUTING} or {@code UNKNOWN}. */ public boolean isRunning() { - return "EXECUTING".equals(this.exitCode) || "UNKNOWN".equals(this.exitCode); + return EXECUTING.exitCode.equals(this.exitCode) || UNKNOWN.exitCode.equals(this.exitCode); } /** diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/AfterChunk.java b/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/AfterChunk.java index f3015aa683..7b3d897baa 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/AfterChunk.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/AfterChunk.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 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. @@ -15,8 +15,8 @@ */ package org.springframework.batch.core.annotation; -import org.springframework.batch.core.ChunkListener; -import org.springframework.batch.core.scope.context.ChunkContext; +import org.springframework.batch.core.listener.ChunkListener; +import org.springframework.batch.item.Chunk; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -24,13 +24,13 @@ import java.lang.annotation.Target; /** - * Marks a method to be called after a chunk is executed.
- *
- * Expected signature: void afterChunk(ChunkContext context) + * Marks a method to be called after a chunk is processed.
+ * Expected signature: void afterChunk(Chunk) * * @author Lucas Ward + * @author Mahmoud Ben Hassine * @since 2.0 - * @see ChunkListener#afterChunk(ChunkContext context) + * @see ChunkListener#afterChunk(Chunk) */ @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.METHOD }) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/AfterChunkError.java b/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/AfterChunkError.java index b80866e76e..0e0b51b203 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/AfterChunkError.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/AfterChunkError.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 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,7 +15,7 @@ */ package org.springframework.batch.core.annotation; -import org.springframework.batch.core.ChunkListener; +import org.springframework.batch.core.listener.ChunkListener; import org.springframework.batch.core.scope.context.ChunkContext; import java.lang.annotation.ElementType; diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/AfterJob.java b/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/AfterJob.java index 3759583c97..a3c343e3c9 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/AfterJob.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/AfterJob.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. @@ -21,9 +21,9 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobExecutionListener; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.listener.JobExecutionListener; /** * Marks a method to be called after a {@link Job} has completed. Annotated methods are diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/AfterProcess.java b/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/AfterProcess.java index a20ded0b52..63ae581b43 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/AfterProcess.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/AfterProcess.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. @@ -21,7 +21,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.springframework.batch.core.ItemProcessListener; +import org.springframework.batch.core.listener.ItemProcessListener; import org.springframework.batch.item.ItemProcessor; /** diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/AfterRead.java b/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/AfterRead.java index 5837e77cce..9fa9970110 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/AfterRead.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/AfterRead.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 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. @@ -20,7 +20,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.springframework.batch.core.ItemReadListener; +import org.springframework.batch.core.listener.ItemReadListener; import org.springframework.batch.item.ItemReader; /** diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/AfterStep.java b/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/AfterStep.java index aa77dda9e4..c9d25ee211 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/AfterStep.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/AfterStep.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,9 +22,9 @@ import java.lang.annotation.Target; import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.StepExecutionListener; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.step.StepExecution; +import org.springframework.batch.core.listener.StepExecutionListener; /** * Marks a method to be called after a {@link Step} has completed. Annotated methods are diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/AfterWrite.java b/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/AfterWrite.java index 720d01e0f9..6f1361866f 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/AfterWrite.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/AfterWrite.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. @@ -21,7 +21,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.springframework.batch.core.ItemWriteListener; +import org.springframework.batch.core.listener.ItemWriteListener; import org.springframework.batch.item.ItemWriter; /** diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/BeforeChunk.java b/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/BeforeChunk.java index 3140f6ae79..d65910710f 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/BeforeChunk.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/BeforeChunk.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 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. @@ -20,17 +20,18 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.springframework.batch.core.ChunkListener; -import org.springframework.batch.core.scope.context.ChunkContext; +import org.springframework.batch.core.listener.ChunkListener; +import org.springframework.batch.item.Chunk; /** * Marks a method to be called before a chunk is executed.
*
- * Expected signature: void beforeChunk(ChunkContext context) + * Expected signature: void beforeChunk(Chunk) * * @author Lucas Ward + * @author Mahmoud Ben Hassine * @since 2.0 - * @see ChunkListener#beforeChunk(ChunkContext context) + * @see ChunkListener#beforeChunk(Chunk) */ @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.METHOD }) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/BeforeJob.java b/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/BeforeJob.java index 25aba39758..f47ba4bbb5 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/BeforeJob.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/BeforeJob.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. @@ -21,10 +21,10 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobExecutionListener; -import org.springframework.batch.core.Step; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.listener.JobExecutionListener; +import org.springframework.batch.core.step.Step; import org.springframework.beans.factory.annotation.Qualifier; /** diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/BeforeProcess.java b/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/BeforeProcess.java index c986390397..79b8ba90eb 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/BeforeProcess.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/BeforeProcess.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 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. @@ -20,7 +20,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.springframework.batch.core.ItemProcessListener; +import org.springframework.batch.core.listener.ItemProcessListener; import org.springframework.batch.item.ItemProcessor; /** diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/BeforeRead.java b/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/BeforeRead.java index 382bc2215c..7e4532d36c 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/BeforeRead.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/BeforeRead.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 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. @@ -20,7 +20,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.springframework.batch.core.ItemReadListener; +import org.springframework.batch.core.listener.ItemReadListener; import org.springframework.batch.item.ItemReader; /** diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/BeforeStep.java b/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/BeforeStep.java index d34eb8023d..5db8edbe2a 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/BeforeStep.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/BeforeStep.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. @@ -21,9 +21,9 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.StepExecutionListener; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.step.StepExecution; +import org.springframework.batch.core.listener.StepExecutionListener; /** * Marks a method to be called before a {@link Step} is executed, which comes after a diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/BeforeWrite.java b/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/BeforeWrite.java index f9e5d581c3..320a6e31b3 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/BeforeWrite.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/BeforeWrite.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. @@ -21,7 +21,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.springframework.batch.core.ItemWriteListener; +import org.springframework.batch.core.listener.ItemWriteListener; import org.springframework.batch.item.ItemWriter; /** diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/OnChunkError.java b/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/OnChunkError.java new file mode 100644 index 0000000000..e83d9cbd60 --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/OnChunkError.java @@ -0,0 +1,38 @@ +/* + * Copyright 2025-present 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.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.batch.core.listener.ChunkListener; +import org.springframework.batch.item.Chunk; + +/** + * Marks a method to be called after a chunk has failed.
+ * Expected signature: void onChunkError(Exception, Chunk) + * + * @author Mahmoud Ben Hassine + * @since 6.0 + * @see ChunkListener#onChunkError(Exception, Chunk) + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.METHOD }) +public @interface OnChunkError { + +} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/OnProcessError.java b/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/OnProcessError.java index 8ea21c2cad..9e766e63db 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/OnProcessError.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/OnProcessError.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. @@ -21,7 +21,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.springframework.batch.core.ItemProcessListener; +import org.springframework.batch.core.listener.ItemProcessListener; import org.springframework.batch.item.ItemProcessor; /** diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/OnReadError.java b/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/OnReadError.java index a81c6a7f9d..0f19f7e401 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/OnReadError.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/OnReadError.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 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. @@ -20,7 +20,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.springframework.batch.core.ItemReadListener; +import org.springframework.batch.core.listener.ItemReadListener; import org.springframework.batch.item.ItemReader; /** diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/OnSkipInProcess.java b/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/OnSkipInProcess.java index 08c46fc347..2570e78823 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/OnSkipInProcess.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/OnSkipInProcess.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. @@ -21,7 +21,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.springframework.batch.core.SkipListener; +import org.springframework.batch.core.listener.SkipListener; import org.springframework.batch.item.ItemProcessor; /** diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/OnSkipInRead.java b/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/OnSkipInRead.java index 89535bbbcd..382d783b50 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/OnSkipInRead.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/OnSkipInRead.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. @@ -21,7 +21,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.springframework.batch.core.SkipListener; +import org.springframework.batch.core.listener.SkipListener; import org.springframework.batch.item.ItemReader; /** diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/OnSkipInWrite.java b/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/OnSkipInWrite.java index 02c39dc798..aa86c22e37 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/OnSkipInWrite.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/OnSkipInWrite.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2007 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. @@ -21,7 +21,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.springframework.batch.core.SkipListener; +import org.springframework.batch.core.listener.SkipListener; import org.springframework.batch.item.ItemWriter; /** diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/OnWriteError.java b/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/OnWriteError.java index 564219619b..6d38fa3ae5 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/OnWriteError.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/OnWriteError.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. @@ -21,7 +21,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.springframework.batch.core.ItemWriteListener; +import org.springframework.batch.core.listener.ItemWriteListener; import org.springframework.batch.item.ItemWriter; /** 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..f34f3d8bec 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. @@ -51,12 +51,22 @@ import org.springframework.aot.hint.TypeReference; import org.springframework.batch.core.Entity; import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobInstance; -import org.springframework.batch.core.JobParameter; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.StepContribution; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobInstance; +import org.springframework.batch.core.job.parameters.JobParameter; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.launch.JobOperator; +import org.springframework.batch.core.listener.ChunkListener; +import org.springframework.batch.core.listener.ItemProcessListener; +import org.springframework.batch.core.listener.ItemReadListener; +import org.springframework.batch.core.listener.ItemWriteListener; +import org.springframework.batch.core.listener.JobExecutionListener; +import org.springframework.batch.core.listener.SkipListener; +import org.springframework.batch.core.listener.StepExecutionListener; +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.core.repository.explore.JobExplorer; +import org.springframework.batch.core.step.StepContribution; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.scope.context.JobContext; import org.springframework.batch.core.scope.context.StepContext; import org.springframework.batch.item.Chunk; @@ -69,6 +79,8 @@ * @author Glenn Renfro * @author Mahmoud Ben Hassine * @author Alexander Arshavskiy + * @author Andrey Litvitski + * @author François Martin * @since 5.0 */ public class CoreRuntimeHints implements RuntimeHintsRegistrar { @@ -98,30 +110,41 @@ public void registerHints(RuntimeHints hints, ClassLoader classLoader) { // proxy hints hints.proxies() - .registerJdkProxy(builder -> builder - .proxiedInterfaces(TypeReference.of("org.springframework.batch.core.repository.JobRepository")) + .registerJdkProxy(builder -> builder.proxiedInterfaces(TypeReference.of(StepExecutionListener.class)) .proxiedInterfaces(SpringProxy.class, Advised.class, DecoratingProxy.class)) - .registerJdkProxy(builder -> builder - .proxiedInterfaces(TypeReference.of("org.springframework.batch.core.explore.JobExplorer")) + .registerJdkProxy(builder -> builder.proxiedInterfaces(TypeReference.of(ItemReadListener.class)) .proxiedInterfaces(SpringProxy.class, Advised.class, DecoratingProxy.class)) - .registerJdkProxy(builder -> builder - .proxiedInterfaces(TypeReference.of("org.springframework.batch.core.launch.JobOperator")) + .registerJdkProxy(builder -> builder.proxiedInterfaces(TypeReference.of(ItemProcessListener.class)) + .proxiedInterfaces(SpringProxy.class, Advised.class, DecoratingProxy.class)) + .registerJdkProxy(builder -> builder.proxiedInterfaces(TypeReference.of(ItemWriteListener.class)) + .proxiedInterfaces(SpringProxy.class, Advised.class, DecoratingProxy.class)) + .registerJdkProxy(builder -> builder.proxiedInterfaces(TypeReference.of(ChunkListener.class)) + .proxiedInterfaces(SpringProxy.class, Advised.class, DecoratingProxy.class)) + .registerJdkProxy(builder -> builder.proxiedInterfaces(TypeReference.of(SkipListener.class)) + .proxiedInterfaces(SpringProxy.class, Advised.class, DecoratingProxy.class)) + .registerJdkProxy(builder -> builder.proxiedInterfaces(TypeReference.of(JobExecutionListener.class)) + .proxiedInterfaces(SpringProxy.class, Advised.class, DecoratingProxy.class)) + .registerJdkProxy(builder -> builder.proxiedInterfaces(TypeReference.of(JobRepository.class)) + .proxiedInterfaces(SpringProxy.class, Advised.class, DecoratingProxy.class)) + .registerJdkProxy(builder -> builder.proxiedInterfaces(TypeReference.of(JobExplorer.class)) + .proxiedInterfaces(SpringProxy.class, Advised.class, DecoratingProxy.class)) + .registerJdkProxy(builder -> builder.proxiedInterfaces(TypeReference.of(JobOperator.class)) .proxiedInterfaces(SpringProxy.class, Advised.class, DecoratingProxy.class)); // reflection hints - hints.reflection().registerType(Types.class, MemberCategory.DECLARED_FIELDS); - hints.reflection().registerType(JobContext.class, MemberCategory.INVOKE_PUBLIC_METHODS); - hints.reflection().registerType(StepContext.class, MemberCategory.INVOKE_PUBLIC_METHODS); - hints.reflection().registerType(JobParameter.class, MemberCategory.values()); - hints.reflection().registerType(JobParameters.class, MemberCategory.values()); - hints.reflection().registerType(ExitStatus.class, MemberCategory.values()); - hints.reflection().registerType(JobInstance.class, MemberCategory.values()); - hints.reflection().registerType(JobExecution.class, MemberCategory.values()); - hints.reflection().registerType(StepExecution.class, MemberCategory.values()); - hints.reflection().registerType(StepContribution.class, MemberCategory.values()); - hints.reflection().registerType(Entity.class, MemberCategory.values()); - hints.reflection().registerType(ExecutionContext.class, MemberCategory.values()); - hints.reflection().registerType(Chunk.class, MemberCategory.values()); + hints.reflection().registerType(Types.class); + hints.reflection().registerType(JobContext.class); + hints.reflection().registerType(StepContext.class); + hints.reflection().registerType(JobParameter.class); + hints.reflection().registerType(JobParameters.class); + hints.reflection().registerType(ExitStatus.class); + hints.reflection().registerType(JobInstance.class); + hints.reflection().registerType(JobExecution.class); + hints.reflection().registerType(StepExecution.class); + hints.reflection().registerType(StepContribution.class); + hints.reflection().registerType(Entity.class); + hints.reflection().registerType(ExecutionContext.class); + hints.reflection().registerType(Chunk.class); jdkTypes.stream() .map(TypeReference::of) .forEach(type -> hints.reflection().registerType(type, MemberCategory.values())); diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/DuplicateJobException.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/DuplicateJobException.java index 72890dcaf5..a8fd88a27a 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/DuplicateJobException.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/DuplicateJobException.java @@ -15,8 +15,8 @@ */ package org.springframework.batch.core.configuration; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobExecutionException; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecutionException; /** * Checked exception that indicates a name clash when registering {@link Job} instances. diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/JobFactory.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/JobFactory.java index dafe2035f7..5a7ccf83eb 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/JobFactory.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/JobFactory.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,14 +15,15 @@ */ package org.springframework.batch.core.configuration; -import org.springframework.batch.core.Job; +import org.springframework.batch.core.job.Job; /** * Strategy for creating a single job. * * @author Dave Syer - * + * @author Mahmoud Ben Hassine */ +@Deprecated(since = "6.0", forRemoval = true) public interface JobFactory { /** diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/JobLocator.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/JobLocator.java index 9e195e10bc..202026cb92 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/JobLocator.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/JobLocator.java @@ -15,7 +15,7 @@ */ package org.springframework.batch.core.configuration; -import org.springframework.batch.core.Job; +import org.springframework.batch.core.job.Job; import org.springframework.batch.core.launch.NoSuchJobException; import org.springframework.lang.Nullable; @@ -25,8 +25,10 @@ * * @author Dave Syer * @author Mahmoud Ben Hassine - * + * @deprecated since 6.0 in favor of {@link JobRegistry}. Scheduled for removal in 6.2 or + * later. */ +@Deprecated(since = "6.0", forRemoval = true) public interface JobLocator { /** diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/JobRegistry.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/JobRegistry.java index 86c4539fb0..48569a95be 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/JobRegistry.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/JobRegistry.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,24 +15,43 @@ */ package org.springframework.batch.core.configuration; -import org.springframework.batch.core.Job; +import java.util.Collection; + +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.launch.NoSuchJobException; /** * A runtime service registry interface for registering job configurations by * name. * * @author Dave Syer + * @author Mahmoud Ben Hassine * */ -public interface JobRegistry extends ListableJobLocator { +public interface JobRegistry { + + /** + * Returns a {@link Job} by name. + * @param name the name of the {@link Job} which should be unique + * @return a {@link Job} identified by the given name + * @throws NoSuchJobException if the required configuration can not be found. + */ + Job getJob(String name) throws NoSuchJobException; + + /** + * Provides the currently registered job names. The return value is unmodifiable and + * disconnected from the underlying registry storage. + * @return a collection of String. Empty if none are registered. + */ + Collection getJobNames(); /** * Registers a {@link Job} at runtime. - * @param jobFactory the {@link Job} to be registered - * @throws DuplicateJobException if a factory with the same job name has already been + * @param job the {@link Job} to be registered + * @throws DuplicateJobException if a job with the same name has already been * registered. */ - void register(JobFactory jobFactory) throws DuplicateJobException; + void register(Job job) throws DuplicateJobException; /** * Unregisters a previously registered {@link Job}. If it was not previously diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/ListableJobLocator.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/ListableJobLocator.java index 0fe16eb219..74978678e4 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/ListableJobLocator.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/ListableJobLocator.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2007 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. @@ -21,8 +21,11 @@ * A listable extension of {@link JobLocator}. * * @author Dave Syer - * + * @author Mahmoud Ben Hassine + * @deprecated since 6.0, scheduled for removal in 6.2 or later. Use {@link JobRegistry} + * instead. */ +@Deprecated(since = "6.0", forRemoval = true) public interface ListableJobLocator extends JobLocator { /** diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/StepRegistry.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/StepRegistry.java index c1a710d63a..4330a74db2 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/StepRegistry.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/StepRegistry.java @@ -15,15 +15,15 @@ */ package org.springframework.batch.core.configuration; -import org.springframework.batch.core.Step; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.job.Job; import org.springframework.batch.core.launch.NoSuchJobException; import org.springframework.batch.core.step.NoSuchStepException; import java.util.Collection; /** - * Registry keeping track of all the {@link Step} instances defined in a - * {@link org.springframework.batch.core.Job}. + * Registry keeping track of all the {@link Step} instances defined in a {@link Job}. * * @author Sebastien Gerard * @author Stephane Nicoll diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/AutomaticJobRegistrarBeanPostProcessor.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/AutomaticJobRegistrarBeanPostProcessor.java index c8db9b7311..36d054ffce 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/AutomaticJobRegistrarBeanPostProcessor.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/AutomaticJobRegistrarBeanPostProcessor.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. @@ -30,7 +30,9 @@ * * @author Mahmoud Ben Hassine * @since 5.0 + * @deprecated since 6.0 with no replacement. Scheduled for removal in 6.2 or later. */ +@Deprecated(since = "6.0", forRemoval = true) class AutomaticJobRegistrarBeanPostProcessor implements BeanFactoryPostProcessor, BeanPostProcessor { private ConfigurableListableBeanFactory beanFactory; diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/BatchObservabilityBeanPostProcessor.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/BatchObservabilityBeanPostProcessor.java index bc3d35159e..a8abdcfd6d 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/BatchObservabilityBeanPostProcessor.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/BatchObservabilityBeanPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2022 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. @@ -20,6 +20,7 @@ import org.apache.commons.logging.LogFactory; import org.springframework.batch.core.job.AbstractJob; +import org.springframework.batch.core.launch.support.TaskExecutorJobOperator; import org.springframework.batch.core.step.AbstractStep; import org.springframework.beans.BeansException; import org.springframework.beans.factory.NoSuchBeanDefinitionException; @@ -28,8 +29,8 @@ import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; /** - * Bean post processor that configures observable batch artifacts (jobs and steps) with - * Micrometer's observation registry. + * Bean post processor that configures observable batch artifacts (typically jobs and + * steps) with a Micrometer's observation registry. * * @author Mahmoud Ben Hassine * @since 5.0 @@ -48,13 +49,17 @@ public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { try { - if (bean instanceof AbstractJob || bean instanceof AbstractStep) { + if (bean instanceof AbstractJob || bean instanceof AbstractStep + || bean instanceof TaskExecutorJobOperator) { ObservationRegistry observationRegistry = this.beanFactory.getBean(ObservationRegistry.class); - if (bean instanceof AbstractJob) { - ((AbstractJob) bean).setObservationRegistry(observationRegistry); + if (bean instanceof AbstractJob job) { + job.setObservationRegistry(observationRegistry); } - if (bean instanceof AbstractStep) { - ((AbstractStep) bean).setObservationRegistry(observationRegistry); + if (bean instanceof AbstractStep step) { + step.setObservationRegistry(observationRegistry); + } + if (bean instanceof TaskExecutorJobOperator operator) { + operator.setObservationRegistry(observationRegistry); } } } 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..79a3659163 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. @@ -22,18 +22,18 @@ import org.springframework.batch.core.configuration.support.AutomaticJobRegistrar; import org.springframework.batch.core.configuration.support.DefaultJobLoader; -import org.springframework.batch.core.configuration.support.JobRegistrySmartInitializingSingleton; import org.springframework.batch.core.configuration.support.MapJobRegistry; -import org.springframework.batch.core.explore.support.JobExplorerFactoryBean; import org.springframework.batch.core.launch.support.JobOperatorFactoryBean; -import org.springframework.batch.core.launch.support.TaskExecutorJobLauncher; -import org.springframework.batch.core.repository.support.JobRepositoryFactoryBean; +import org.springframework.batch.core.repository.support.JdbcJobRepositoryFactoryBean; +import org.springframework.batch.core.repository.support.MongoJobRepositoryFactoryBean; +import org.springframework.batch.core.repository.support.ResourcelessJobRepository; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; import org.springframework.core.log.LogMessage; import org.springframework.core.type.AnnotationMetadata; +import org.springframework.transaction.annotation.Isolation; import org.springframework.util.StopWatch; import org.springframework.util.StringUtils; @@ -49,7 +49,9 @@ class BatchRegistrar implements ImportBeanDefinitionRegistrar { private static final Log LOGGER = LogFactory.getLog(BatchRegistrar.class); - 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_OPERATOR = "jobOperator"; @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { @@ -59,11 +61,7 @@ public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, B EnableBatchProcessing batchAnnotation = importingClassMetadata.getAnnotations() .get(EnableBatchProcessing.class) .synthesize(); - registerJobRepository(registry, batchAnnotation); - registerJobExplorer(registry, batchAnnotation); - registerJobLauncher(registry, batchAnnotation); - registerJobRegistry(registry); - registerJobRegistrySmartInitializingSingleton(registry); + registerJobRepository(registry, importingClassMetadata); registerJobOperator(registry, batchAnnotation); registerAutomaticJobRegistrar(registry, batchAnnotation); watch.stop(); @@ -74,174 +72,137 @@ public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, B private void validateState(AnnotationMetadata importingClassMetadata) { if (!importingClassMetadata.isAnnotated(EnableBatchProcessing.class.getName())) { String className = importingClassMetadata.getClassName(); - String errorMessage = String.format(MISSING_ANNOTATION_ERROR_MESSAGE, className); + String errorMessage = "EnableBatchProcessing is not present on importing class '%s' as expected" + .formatted(className); throw new IllegalStateException(errorMessage); } } - private void registerJobRepository(BeanDefinitionRegistry registry, EnableBatchProcessing batchAnnotation) { - if (registry.containsBeanDefinition("jobRepository")) { + private void registerJobRepository(BeanDefinitionRegistry registry, AnnotationMetadata importingClassMetadata) { + if (registry.containsBeanDefinition(JOB_REPOSITORY)) { LOGGER.info("Bean jobRepository already defined in the application context, skipping" + " the registration of a jobRepository"); return; } + if (importingClassMetadata.hasAnnotation(EnableJdbcJobRepository.class.getName())) { + registerJdbcJobRepository(registry, importingClassMetadata); + } + else { + if (importingClassMetadata.hasAnnotation(EnableMongoJobRepository.class.getName())) { + registerMongoJobRepository(registry, importingClassMetadata); + } + else { + registerDefaultJobRepository(registry); + } + } + } + + private void registerJdbcJobRepository(BeanDefinitionRegistry registry, AnnotationMetadata importingClassMetadata) { + EnableJdbcJobRepository jdbcJobRepositoryAnnotation = importingClassMetadata.getAnnotations() + .get(EnableJdbcJobRepository.class) + .synthesize(); BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder - .genericBeanDefinition(JobRepositoryFactoryBean.class); + .genericBeanDefinition(JdbcJobRepositoryFactoryBean.class); // set mandatory properties - String dataSourceRef = batchAnnotation.dataSourceRef(); + String dataSourceRef = jdbcJobRepositoryAnnotation.dataSourceRef(); beanDefinitionBuilder.addPropertyReference("dataSource", dataSourceRef); - String transactionManagerRef = batchAnnotation.transactionManagerRef(); + String transactionManagerRef = jdbcJobRepositoryAnnotation.transactionManagerRef(); beanDefinitionBuilder.addPropertyReference("transactionManager", transactionManagerRef); // set optional properties - String executionContextSerializerRef = batchAnnotation.executionContextSerializerRef(); + String executionContextSerializerRef = jdbcJobRepositoryAnnotation.executionContextSerializerRef(); if (registry.containsBeanDefinition(executionContextSerializerRef)) { beanDefinitionBuilder.addPropertyReference("serializer", executionContextSerializerRef); } - String lobHandlerRef = batchAnnotation.lobHandlerRef(); - if (registry.containsBeanDefinition(lobHandlerRef)) { - beanDefinitionBuilder.addPropertyReference("lobHandler", lobHandlerRef); - } - - String conversionServiceRef = batchAnnotation.conversionServiceRef(); + String conversionServiceRef = jdbcJobRepositoryAnnotation.conversionServiceRef(); if (registry.containsBeanDefinition(conversionServiceRef)) { beanDefinitionBuilder.addPropertyReference("conversionService", conversionServiceRef); } - String incrementerFactoryRef = batchAnnotation.incrementerFactoryRef(); + String incrementerFactoryRef = jdbcJobRepositoryAnnotation.incrementerFactoryRef(); if (registry.containsBeanDefinition(incrementerFactoryRef)) { beanDefinitionBuilder.addPropertyReference("incrementerFactory", incrementerFactoryRef); } - String jobKeyGeneratorRef = batchAnnotation.jobKeyGeneratorRef(); - if (registry.containsBeanDefinition(jobKeyGeneratorRef)) { - beanDefinitionBuilder.addPropertyReference("jobKeyGenerator", jobKeyGeneratorRef); - } - - String charset = batchAnnotation.charset(); + String charset = jdbcJobRepositoryAnnotation.charset(); if (charset != null) { beanDefinitionBuilder.addPropertyValue("charset", Charset.forName(charset)); } - String tablePrefix = batchAnnotation.tablePrefix(); + String tablePrefix = jdbcJobRepositoryAnnotation.tablePrefix(); if (tablePrefix != null) { beanDefinitionBuilder.addPropertyValue("tablePrefix", tablePrefix); } - String isolationLevelForCreate = batchAnnotation.isolationLevelForCreate(); - if (isolationLevelForCreate != null) { - beanDefinitionBuilder.addPropertyValue("isolationLevelForCreate", isolationLevelForCreate); - } - - String databaseType = batchAnnotation.databaseType(); + String databaseType = jdbcJobRepositoryAnnotation.databaseType(); if (StringUtils.hasText(databaseType)) { beanDefinitionBuilder.addPropertyValue("databaseType", databaseType); } - beanDefinitionBuilder.addPropertyValue("maxVarCharLength", batchAnnotation.maxVarCharLength()); - beanDefinitionBuilder.addPropertyValue("clobType", batchAnnotation.clobType()); - registry.registerBeanDefinition("jobRepository", beanDefinitionBuilder.getBeanDefinition()); - } - - private void registerJobExplorer(BeanDefinitionRegistry registry, EnableBatchProcessing batchAnnotation) { - if (registry.containsBeanDefinition("jobExplorer")) { - 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 jdbcOperationsRef = jdbcJobRepositoryAnnotation.jdbcOperationsRef(); + if (registry.containsBeanDefinition(jdbcOperationsRef)) { + beanDefinitionBuilder.addPropertyReference("jdbcOperations", jdbcOperationsRef); } - String lobHandlerRef = batchAnnotation.lobHandlerRef(); - if (registry.containsBeanDefinition(lobHandlerRef)) { - beanDefinitionBuilder.addPropertyReference("lobHandler", lobHandlerRef); - } + beanDefinitionBuilder.addPropertyValue("maxVarCharLength", jdbcJobRepositoryAnnotation.maxVarCharLength()); + beanDefinitionBuilder.addPropertyValue("clobType", jdbcJobRepositoryAnnotation.clobType()); + beanDefinitionBuilder.addPropertyValue("validateTransactionState", + jdbcJobRepositoryAnnotation.validateTransactionState()); - String conversionServiceRef = batchAnnotation.conversionServiceRef(); - if (registry.containsBeanDefinition(conversionServiceRef)) { - beanDefinitionBuilder.addPropertyReference("conversionService", conversionServiceRef); + Isolation isolationLevelForCreate = jdbcJobRepositoryAnnotation.isolationLevelForCreate(); + if (isolationLevelForCreate != null) { + beanDefinitionBuilder.addPropertyValue("isolationLevelForCreateEnum", isolationLevelForCreate); } - String jobKeyGeneratorRef = batchAnnotation.jobKeyGeneratorRef(); + String jobKeyGeneratorRef = jdbcJobRepositoryAnnotation.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("jobExplorer", beanDefinitionBuilder.getBeanDefinition()); + registry.registerBeanDefinition(JOB_REPOSITORY, beanDefinitionBuilder.getBeanDefinition()); } - private void registerJobLauncher(BeanDefinitionRegistry registry, EnableBatchProcessing batchAnnotation) { - if (registry.containsBeanDefinition("jobLauncher")) { - LOGGER.info("Bean jobLauncher already defined in the application context, skipping" - + " the registration of a jobLauncher"); - return; - } + private void registerMongoJobRepository(BeanDefinitionRegistry registry, + AnnotationMetadata importingClassMetadata) { BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder - .genericBeanDefinition(TaskExecutorJobLauncher.class); - // set mandatory properties - beanDefinitionBuilder.addPropertyReference("jobRepository", "jobRepository"); - - // set optional properties - String taskExecutorRef = batchAnnotation.taskExecutorRef(); - if (registry.containsBeanDefinition(taskExecutorRef)) { - beanDefinitionBuilder.addPropertyReference("taskExecutor", taskExecutorRef); + .genericBeanDefinition(MongoJobRepositoryFactoryBean.class); + EnableMongoJobRepository mongoJobRepositoryAnnotation = importingClassMetadata.getAnnotations() + .get(EnableMongoJobRepository.class) + .synthesize(); + String mongoOperationsRef = mongoJobRepositoryAnnotation.mongoOperationsRef(); + if (registry.containsBeanDefinition(mongoOperationsRef)) { + beanDefinitionBuilder.addPropertyReference("mongoOperations", mongoOperationsRef); + } + String transactionManagerRef = mongoJobRepositoryAnnotation.transactionManagerRef(); + if (registry.containsBeanDefinition(transactionManagerRef)) { + beanDefinitionBuilder.addPropertyReference("transactionManager", transactionManagerRef); + } + Isolation isolationLevelForCreate = mongoJobRepositoryAnnotation.isolationLevelForCreate(); + if (isolationLevelForCreate != null) { + beanDefinitionBuilder.addPropertyValue("isolationLevelForCreate", isolationLevelForCreate); } - registry.registerBeanDefinition("jobLauncher", beanDefinitionBuilder.getBeanDefinition()); - } - private void registerJobRegistry(BeanDefinitionRegistry registry) { - if (registry.containsBeanDefinition("jobRegistry")) { - LOGGER.info("Bean jobRegistry already defined in the application context, skipping" - + " the registration of a jobRegistry"); - return; + String jobKeyGeneratorRef = mongoJobRepositoryAnnotation.jobKeyGeneratorRef(); + if (registry.containsBeanDefinition(jobKeyGeneratorRef)) { + beanDefinitionBuilder.addPropertyReference("jobKeyGenerator", jobKeyGeneratorRef); } - BeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(MapJobRegistry.class) - .getBeanDefinition(); - registry.registerBeanDefinition("jobRegistry", beanDefinition); + beanDefinitionBuilder.addPropertyValue("validateTransactionState", + mongoJobRepositoryAnnotation.validateTransactionState()); + + registry.registerBeanDefinition(JOB_REPOSITORY, beanDefinitionBuilder.getBeanDefinition()); } - private void registerJobRegistrySmartInitializingSingleton(BeanDefinitionRegistry registry) { - if (registry.containsBeanDefinition("jobRegistrySmartInitializingSingleton")) { - LOGGER - .info("Bean jobRegistrySmartInitializingSingleton already defined in the application context, skipping" - + " the registration of a jobRegistrySmartInitializingSingleton"); - return; - } + private void registerDefaultJobRepository(BeanDefinitionRegistry registry) { BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder - .genericBeanDefinition(JobRegistrySmartInitializingSingleton.class); - beanDefinitionBuilder.addPropertyReference("jobRegistry", "jobRegistry"); - - registry.registerBeanDefinition("jobRegistrySmartInitializingSingleton", - beanDefinitionBuilder.getBeanDefinition()); + .genericBeanDefinition(ResourcelessJobRepository.class); + registry.registerBeanDefinition(JOB_REPOSITORY, beanDefinitionBuilder.getBeanDefinition()); } private void registerJobOperator(BeanDefinitionRegistry registry, EnableBatchProcessing batchAnnotation) { - if (registry.containsBeanDefinition("jobOperator")) { + if (registry.containsBeanDefinition(JOB_OPERATOR)) { LOGGER.info("Bean jobOperator already defined in the application context, skipping" + " the registration of a jobOperator"); return; @@ -249,21 +210,35 @@ private void registerJobOperator(BeanDefinitionRegistry registry, EnableBatchPro BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder .genericBeanDefinition(JobOperatorFactoryBean.class); // set mandatory properties - 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); // set optional properties + String jobRegistryRef = batchAnnotation.jobRegistryRef(); + if (registry.containsBeanDefinition(jobRegistryRef)) { + beanDefinitionBuilder.addPropertyReference("jobRegistry", jobRegistryRef); + } + + String observationRegistryRef = batchAnnotation.observationRegistryRef(); + if (registry.containsBeanDefinition(observationRegistryRef)) { + beanDefinitionBuilder.addPropertyReference("observationRegistry", observationRegistryRef); + } + + String transactionManagerRef = batchAnnotation.transactionManagerRef(); + if (registry.containsBeanDefinition(transactionManagerRef)) { + beanDefinitionBuilder.addPropertyReference("transactionManager", transactionManagerRef); + } + + String taskExecutorRef = batchAnnotation.taskExecutorRef(); + if (registry.containsBeanDefinition(taskExecutorRef)) { + beanDefinitionBuilder.addPropertyReference("taskExecutor", taskExecutorRef); + } + @SuppressWarnings("removal") String jobParametersConverterRef = batchAnnotation.jobParametersConverterRef(); if (registry.containsBeanDefinition(jobParametersConverterRef)) { beanDefinitionBuilder.addPropertyReference("jobParametersConverter", jobParametersConverterRef); } - registry.registerBeanDefinition("jobOperator", beanDefinitionBuilder.getBeanDefinition()); + registry.registerBeanDefinition(JOB_OPERATOR, beanDefinitionBuilder.getBeanDefinition()); } private void registerAutomaticJobRegistrar(BeanDefinitionRegistry registry, EnableBatchProcessing batchAnnotation) { @@ -276,7 +251,7 @@ private void registerAutomaticJobRegistrar(BeanDefinitionRegistry registry, Enab return; } BeanDefinition jobLoaderBeanDefinition = BeanDefinitionBuilder.genericBeanDefinition(DefaultJobLoader.class) - .addPropertyReference("jobRegistry", "jobRegistry") + .addPropertyValue("jobRegistry", new MapJobRegistry()) .getBeanDefinition(); registry.registerBeanDefinition("jobLoader", jobLoaderBeanDefinition); BeanDefinition jobRegistrarBeanDefinition = BeanDefinitionBuilder 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..810a531d5a 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. @@ -15,27 +15,19 @@ */ package org.springframework.batch.core.configuration.annotation; -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import java.sql.Types; - -import javax.sql.DataSource; - -import org.springframework.batch.core.configuration.JobRegistry; import org.springframework.batch.core.configuration.support.ApplicationContextFactory; import org.springframework.batch.core.configuration.support.AutomaticJobRegistrar; +import org.springframework.batch.core.configuration.support.GroupAwareJob; import org.springframework.batch.core.configuration.support.ScopeConfiguration; import org.springframework.batch.core.converter.JobParametersConverter; -import org.springframework.batch.core.launch.JobLauncher; -import org.springframework.batch.core.launch.support.TaskExecutorJobLauncher; import org.springframework.batch.core.repository.JobRepository; -import org.springframework.batch.core.repository.dao.AbstractJdbcBatchMetadataDao; -import org.springframework.batch.support.DatabaseType; import org.springframework.context.annotation.Import; -import org.springframework.transaction.PlatformTransactionManager; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; /** *

@@ -47,7 +39,6 @@ *

  * @Configuration
  * @EnableBatchProcessing
- * @Import(DataSourceConfiguration.class)
  * public class AppConfig {
  *
  *     @Bean
@@ -67,9 +58,10 @@
  * }
  * 
* - * This annotation configures JDBC-based Batch infrastructure beans, so you must provide a - * {@link DataSource} and a {@link PlatformTransactionManager} as beans in the application - * context. + * By default,this annotation configures a resouceless batch infrastructure (ie based on a + * {@link org.springframework.batch.core.repository.support.ResourcelessJobRepository} and + * a + * {@link org.springframework.batch.support.transaction.ResourcelessTransactionManager}). * * Note that only one of your configuration classes needs to have the * @EnableBatchProcessing annotation. Once you have an @@ -82,23 +74,16 @@ * *
    *
  • a {@link JobRepository} (bean name "jobRepository" of type - * {@link org.springframework.batch.core.repository.support.SimpleJobRepository})
  • - *
  • a {@link JobLauncher} (bean name "jobLauncher" of type - * {@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})
  • + * {@link org.springframework.batch.core.repository.support.ResourcelessJobRepository}) *
  • a {@link org.springframework.batch.core.launch.JobOperator} (bean name * "jobOperator" of type - * {@link org.springframework.batch.core.launch.support.SimpleJobOperator})
  • - *
  • a - * {@link org.springframework.batch.core.configuration.support.JobRegistrySmartInitializingSingleton} - * (bean name "jobRegistrySmartInitializingSingleton" of type - * {@link org.springframework.batch.core.configuration.support.JobRegistrySmartInitializingSingleton})
  • + * {@link org.springframework.batch.core.launch.support.TaskExecutorJobOperator}) *
* + * Other configuration types like JDBC-based or MongoDB-based batch infrastructures can be + * defined using store specific annotations like {@link EnableJdbcJobRepository} or + * {@link EnableMongoJobRepository}. + * * If the configuration is specified as modular=true, the context also * contains an {@link AutomaticJobRegistrar}. The job registrar is useful for modularizing * your configuration if there are multiple jobs. It works by creating separate child @@ -145,8 +130,8 @@ * * * - * + * * * * @@ -156,7 +141,8 @@ * @author Dave Syer * @author Mahmoud Ben Hassine * @author Taeik Lim - * + * @see EnableJdbcJobRepository + * @see EnableMongoJobRepository */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @@ -172,114 +158,47 @@ * {@link ApplicationContextFactory}. * @return boolean indicating whether the configuration is going to be modularized * into multiple application contexts. Defaults to {@code false}. + * @deprecated since 6.0 in favor of Spring's context hierarchies and + * {@link GroupAwareJob}s. Scheduled for removal in 6.2 or later. */ + @Deprecated(since = "6.0", forRemoval = true) boolean modular() default false; /** - * Set the data source to use in the job repository and job explorer. - * @return the bean name of the data source to use. Default to {@literal dataSource}. - */ - String dataSourceRef() default "dataSource"; - - /** - * Set the type of the data source to use in the job repository. The default type will - * be introspected from the datasource's metadata. - * @since 5.1 - * @see DatabaseType - * @return the type of data source. - */ - String databaseType() default ""; - - /** - * Set the transaction manager to use in the job repository. - * @return the bean name of the transaction manager to use. Defaults to - * {@literal transactionManager} - */ - String transactionManagerRef() default "transactionManager"; - - /** - * Set the execution context serializer to use in the job repository and job explorer. - * @return the bean name of the execution context serializer to use. Default to - * {@literal executionContextSerializer}. - */ - String executionContextSerializerRef() default "executionContextSerializer"; - - /** - * The charset to use in the job repository and job explorer - * @return the charset to use. Defaults to {@literal UTF-8}. - */ - String charset() default "UTF-8"; - - /** - * The Batch tables prefix. Defaults to {@literal "BATCH_"}. - * @return the Batch table prefix - */ - String tablePrefix() default AbstractJdbcBatchMetadataDao.DEFAULT_TABLE_PREFIX; - - /** - * The maximum length of exit messages in the database. - * @return the maximum length of exit messages in the database - */ - int maxVarCharLength() default AbstractJdbcBatchMetadataDao.DEFAULT_EXIT_MESSAGE_LENGTH; - - /** - * The incrementer factory to use in various DAOs. - * @return the bean name of the incrementer factory to use. Defaults to - * {@literal incrementerFactory}. - */ - String incrementerFactoryRef() default "incrementerFactory"; - - /** - * The generator that determines a unique key for identifying job instance objects - * @return the bean name of the job key generator to use. Defaults to - * {@literal jobKeyGenerator}. - * - * @since 5.1 - */ - 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. + * Set the task executor to use in the job operator. + * @return the bean name of the task executor to use. Defaults to + * {@literal taskExecutor} */ - int clobType() default Types.CLOB; + String taskExecutorRef() default "taskExecutor"; /** - * Set the isolation level for create parameter value. Defaults to - * {@literal ISOLATION_SERIALIZABLE}. - * @return the value of the isolation level for create parameter + * Set the job registry to use in the job operator. + * @return the bean name of the job registry to use. Defaults to + * {@literal jobRegistry} */ - String isolationLevelForCreate() default "ISOLATION_SERIALIZABLE"; + String jobRegistryRef() default "jobRegistry"; /** - * Set the task executor to use in the job launcher. - * @return the bean name of the task executor to use. Defaults to - * {@literal taskExecutor} + * Set the observation registry to use in batch artifacts. + * @return the bean name of the observation registry to use. Defaults to + * {@literal observationRegistry} */ - String taskExecutorRef() default "taskExecutor"; + String observationRegistryRef() default "observationRegistry"; /** - * Set the conversion service to use in the job repository and job explorer. This - * service is used to convert job parameters from String literal to typed values and - * vice versa. - * @return the bean name of the conversion service to use. Defaults to - * {@literal conversionService} + * Set the transaction manager to use in the job operator. + * @return the bean name of the transaction manager to use. Defaults to + * {@literal transactionManager} */ - String conversionServiceRef() default "conversionService"; + String transactionManagerRef() default "transactionManager"; /** * Set the {@link JobParametersConverter} to use in the job operator. * @return the bean name of the job parameters converter to use. Defaults to * {@literal jobParametersConverter} + * @deprecated since 6.0 with no replacement. Scheduled for removal in 6.2 or later */ + @Deprecated(since = "6.0", forRemoval = true) String jobParametersConverterRef() default "jobParametersConverter"; } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/EnableJdbcJobRepository.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/EnableJdbcJobRepository.java new file mode 100644 index 0000000000..012e317e1b --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/EnableJdbcJobRepository.java @@ -0,0 +1,154 @@ +/* + * 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. + * 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.annotation; + +import org.springframework.batch.core.repository.dao.AbstractJdbcBatchMetadataDao; +import org.springframework.batch.support.DatabaseType; +import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.core.JdbcOperations; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.annotation.Isolation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.sql.Types; + +import javax.sql.DataSource; + +/** + * Annotation to enable a JDBC-based infrastructure in a Spring Batch application. + *

+ * This annotation should be used on a {@link Configuration @Configuration} class + * annotated with {@link EnableBatchProcessing }. It will automatically configure the + * necessary beans for a JDBC-based infrastructure, including a job repository. + *

+ * The default configuration assumes that a {@link DataSource} bean named "dataSource" and + * a {@link PlatformTransactionManager} bean named "transactionManager" are available in + * the application context. + * + * @author Mahmoud Ben Hassine + * @since 6.0 + * @see EnableBatchProcessing + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface EnableJdbcJobRepository { + + /** + * Set the type of the data source to use in the job repository. The default type will + * be introspected from the datasource's metadata. + * @since 5.1 + * @see DatabaseType + * @return the type of data source. + */ + String databaseType() default ""; + + /** + * Set the value of the {@code validateTransactionState} parameter. Defaults to + * {@code true}. + * @return true if the transaction state should be validated, false otherwise + */ + boolean validateTransactionState() default true; + + /** + * Set the isolation level for create parameter value. Defaults to + * {@link Isolation#SERIALIZABLE}. + * @return the value of the isolation level for create parameter + */ + Isolation isolationLevelForCreate() default Isolation.SERIALIZABLE; + + /** + * The charset to use in the job repository + * @return the charset to use. Defaults to {@literal UTF-8}. + */ + String charset() default "UTF-8"; + + /** + * The Batch tables prefix. Defaults to + * {@link AbstractJdbcBatchMetadataDao#DEFAULT_TABLE_PREFIX}. + * @return the Batch table prefix + */ + String tablePrefix() default AbstractJdbcBatchMetadataDao.DEFAULT_TABLE_PREFIX; + + /** + * The maximum length of exit messages in the database. Defaults to + * {@link AbstractJdbcBatchMetadataDao#DEFAULT_EXIT_MESSAGE_LENGTH} + * @return the maximum length of exit messages in the database + */ + int maxVarCharLength() default AbstractJdbcBatchMetadataDao.DEFAULT_EXIT_MESSAGE_LENGTH; + + /** + * The type of large objects. + * @return the type of large objects. + */ + int clobType() default Types.CLOB; + + /** + * Set the data source to use in the job repository. + * @return the bean name of the data source to use. Default to {@literal dataSource}. + */ + String dataSourceRef() default "dataSource"; + + /** + * Set the {@link PlatformTransactionManager} to use in the job repository. + * @return the bean name of the transaction manager to use. Defaults to + * {@literal transactionManager} + */ + String transactionManagerRef() default "transactionManager"; + + /** + * Set the {@link JdbcOperations} to use in the job repository. + * @return the bean name of the {@link JdbcOperations} to use. Defaults to + * {@literal jdbcTemplate}. + */ + String jdbcOperationsRef() default "jdbcTemplate"; + + /** + * The generator that determines a unique key for identifying job instance objects + * @return the bean name of the job key generator to use. Defaults to + * {@literal jobKeyGenerator}. + * + * @since 5.1 + */ + String jobKeyGeneratorRef() default "jobKeyGenerator"; + + /** + * Set the execution context serializer to use in the job repository. + * @return the bean name of the execution context serializer to use. Default to + * {@literal executionContextSerializer}. + */ + String executionContextSerializerRef() default "executionContextSerializer"; + + /** + * The incrementer factory to use in various DAOs. + * @return the bean name of the incrementer factory to use. Defaults to + * {@literal incrementerFactory}. + */ + String incrementerFactoryRef() default "incrementerFactory"; + + /** + * Set the conversion service to use in the job repository. This service is used to + * convert job parameters from String literal to typed values and vice versa. + * @return the bean name of the conversion service to use. Defaults to + * {@literal conversionService} + */ + String conversionServiceRef() default "conversionService"; + +} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/EnableMongoJobRepository.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/EnableMongoJobRepository.java new file mode 100644 index 0000000000..f4233eb1aa --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/EnableMongoJobRepository.java @@ -0,0 +1,80 @@ +/* + * 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. + * 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.annotation; + +import org.springframework.context.annotation.Configuration; +import org.springframework.data.mongodb.MongoTransactionManager; +import org.springframework.data.mongodb.core.MongoOperations; +import org.springframework.transaction.annotation.Isolation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * * Annotation to enable a MongoDB-based job repository in a Spring Batch application. + *

+ * This annotation should be used on a {@link Configuration @Configuration} class + * annotated with {@link EnableBatchProcessing}. It will automatically configure the + * necessary beans for a MongoDB-based infrastructure, including a job repository. + *

+ * The default configuration assumes that a {@link MongoOperations} bean named + * "mongoTemplate" and a {@link MongoTransactionManager} bean named "transactionManager" + * are available in the application context. + * + * @author Mahmoud Ben Hassine + * @since 6.0 + * @see EnableBatchProcessing + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface EnableMongoJobRepository { + + String mongoOperationsRef() default "mongoTemplate"; + + /** + * Set the {@link MongoTransactionManager} to use in the job repository. + * @return the bean name of the transaction manager to use. Defaults to + * {@literal transactionManager} + */ + String transactionManagerRef() default "transactionManager"; + + /** + * Set the isolation level for create parameter value. Defaults to + * {@link Isolation#SERIALIZABLE}. + * @return the value of the isolation level for create parameter + */ + Isolation isolationLevelForCreate() default Isolation.SERIALIZABLE; + + /** + * Set the value of the {@code validateTransactionState} parameter. Defaults to + * {@code true}. + * @return true if the transaction state should be validated, false otherwise + */ + boolean validateTransactionState() default true; + + /** + * The generator that determines a unique key for identifying job instance objects + * @return the bean name of the job key generator to use. Defaults to + * {@literal jobKeyGenerator}. + * + */ + String jobKeyGeneratorRef() default "jobKeyGenerator"; + +} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/AbstractApplicationContextFactory.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/AbstractApplicationContextFactory.java index 535886f96c..3466e93cd5 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/AbstractApplicationContextFactory.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/AbstractApplicationContextFactory.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. @@ -47,7 +47,10 @@ * every time it is requested. It is lazily initialized and cached. Clients should ensure * that it is closed when it is no longer needed. If a path is not set, the parent is * always returned. + * + * @deprecated since 6.0 with no replacement. Scheduled for removal in 6.2 or later. */ +@Deprecated(since = "6.0", forRemoval = true) public abstract class AbstractApplicationContextFactory implements ApplicationContextFactory, ApplicationContextAware { private static final Log logger = LogFactory.getLog(AbstractApplicationContextFactory.class); @@ -196,13 +199,11 @@ protected void prepareContext(ConfigurableApplicationContext parent, Configurabl protected void prepareBeanFactory(ConfigurableListableBeanFactory parent, ConfigurableListableBeanFactory beanFactory) { if (copyConfiguration && parent != null) { - List parentPostProcessors = new ArrayList<>(); - List childPostProcessors = new ArrayList<>(); - - childPostProcessors.addAll(beanFactory instanceof AbstractBeanFactory - ? ((AbstractBeanFactory) beanFactory).getBeanPostProcessors() : new ArrayList<>()); - parentPostProcessors.addAll(parent instanceof AbstractBeanFactory - ? ((AbstractBeanFactory) parent).getBeanPostProcessors() : new ArrayList<>()); + List childPostProcessors = new ArrayList<>( + beanFactory instanceof AbstractBeanFactory factory ? factory.getBeanPostProcessors() + : new ArrayList<>()); + List parentPostProcessors = new ArrayList<>(parent instanceof AbstractBeanFactory factory + ? factory.getBeanPostProcessors() : new ArrayList<>()); try { Class applicationContextAwareProcessorClass = ClassUtils.forName( @@ -237,8 +238,8 @@ protected void prepareBeanFactory(ConfigurableListableBeanFactory parent, beanFactory.copyConfigurationFrom(parent); - List beanPostProcessors = beanFactory instanceof AbstractBeanFactory - ? ((AbstractBeanFactory) beanFactory).getBeanPostProcessors() : new ArrayList<>(); + List beanPostProcessors = beanFactory instanceof AbstractBeanFactory abstractBeanFactory + ? abstractBeanFactory.getBeanPostProcessors() : new ArrayList<>(); beanPostProcessors.clear(); beanPostProcessors.addAll(aggregatedPostProcessors); diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ApplicationContextFactory.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ApplicationContextFactory.java index 7647661970..2ad87be583 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ApplicationContextFactory.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ApplicationContextFactory.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,7 +16,7 @@ package org.springframework.batch.core.configuration.support; -import org.springframework.batch.core.Job; +import org.springframework.batch.core.job.Job; import org.springframework.context.ApplicationContext; import org.springframework.context.ConfigurableApplicationContext; @@ -25,7 +25,10 @@ * primarily useful when creating a new {@link ApplicationContext} for a {@link Job}. * * @author Lucas Ward + * @author Mahmoud Ben Hassine + * @deprecated since 6.0 with no replacement. Scheduled for removal in 6.2 or later. */ +@Deprecated(since = "6.0", forRemoval = true) public interface ApplicationContextFactory { ConfigurableApplicationContext createApplicationContext(); diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ApplicationContextJobFactory.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ApplicationContextJobFactory.java index a60c6b9615..8167a837dd 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ApplicationContextJobFactory.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ApplicationContextJobFactory.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. @@ -15,7 +15,7 @@ */ package org.springframework.batch.core.configuration.support; -import org.springframework.batch.core.Job; +import org.springframework.batch.core.job.Job; import org.springframework.batch.core.configuration.JobFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.ConfigurableApplicationContext; @@ -26,8 +26,10 @@ * * @author Dave Syer * @author Mahmoud Ben Hassine + * @deprecated since 6.0 with no replacement. Scheduled for removal in 6.2 or later. * */ +@Deprecated(since = "6.0", forRemoval = true) public class ApplicationContextJobFactory implements JobFactory { private final Job job; diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/AutomaticJobRegistrar.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/AutomaticJobRegistrar.java index e8496b83d6..76d2345bae 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/AutomaticJobRegistrar.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/AutomaticJobRegistrar.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. @@ -20,7 +20,7 @@ import java.util.Arrays; import java.util.Collection; -import org.springframework.batch.core.Job; +import org.springframework.batch.core.job.Job; import org.springframework.batch.core.configuration.DuplicateJobException; import org.springframework.batch.core.configuration.JobRegistry; import org.springframework.beans.factory.InitializingBean; @@ -42,7 +42,9 @@ * @author Dave Syer * @author Mahmoud Ben Hassine * @since 2.1 + * @deprecated since 6.0 with no replacement. Scheduled for removal in 6.2 or later. */ +@Deprecated(since = "6.0", forRemoval = true) public class AutomaticJobRegistrar implements Ordered, SmartLifecycle, ApplicationContextAware, InitializingBean { private final Collection applicationContextFactories = new ArrayList<>(); @@ -79,8 +81,8 @@ public void setApplicationContext(ApplicationContext applicationContext) { * use */ public void addApplicationContextFactory(ApplicationContextFactory applicationContextFactory) { - if (applicationContextFactory instanceof ApplicationContextAware) { - ((ApplicationContextAware) applicationContextFactory).setApplicationContext(applicationContext); + if (applicationContextFactory instanceof ApplicationContextAware applicationContextAware) { + applicationContextAware.setApplicationContext(applicationContext); } this.applicationContextFactories.add(applicationContextFactory); } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ClasspathXmlApplicationContextsFactoryBean.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ClasspathXmlApplicationContextsFactoryBean.java index 316c364527..58bac350c0 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ClasspathXmlApplicationContextsFactoryBean.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ClasspathXmlApplicationContextsFactoryBean.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. @@ -35,8 +35,10 @@ * * @author Dave Syer * @author Mahmoud Ben Hassine - * + * @deprecated since 6.0 with no replacement. Scheduled for removal in 6.2 or later. */ +@SuppressWarnings("removal") +@Deprecated(since = "6.0", forRemoval = true) public class ClasspathXmlApplicationContextsFactoryBean implements FactoryBean, ApplicationContextAware { 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..f47ed31559 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. @@ -15,77 +15,41 @@ */ package org.springframework.batch.core.configuration.support; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.sql.Types; +import io.micrometer.observation.ObservationRegistry; -import javax.sql.DataSource; - -import org.springframework.batch.core.DefaultJobKeyGenerator; -import org.springframework.batch.core.JobInstance; -import org.springframework.batch.core.JobKeyGenerator; +import org.springframework.batch.core.configuration.DuplicateJobException; +import org.springframework.batch.core.configuration.annotation.BatchObservabilityBeanPostProcessor; +import org.springframework.batch.core.job.Job; import org.springframework.batch.core.configuration.BatchConfigurationException; import org.springframework.batch.core.configuration.JobRegistry; -import org.springframework.batch.core.converter.DateToStringConverter; import org.springframework.batch.core.converter.DefaultJobParametersConverter; import org.springframework.batch.core.converter.JobParametersConverter; -import org.springframework.batch.core.converter.LocalDateTimeToStringConverter; -import org.springframework.batch.core.converter.LocalDateToStringConverter; -import org.springframework.batch.core.converter.LocalTimeToStringConverter; -import org.springframework.batch.core.converter.StringToDateConverter; -import org.springframework.batch.core.converter.StringToLocalDateConverter; -import org.springframework.batch.core.converter.StringToLocalDateTimeConverter; -import org.springframework.batch.core.converter.StringToLocalTimeConverter; -import org.springframework.batch.core.explore.JobExplorer; -import org.springframework.batch.core.explore.support.JobExplorerFactoryBean; -import org.springframework.batch.core.launch.JobLauncher; import org.springframework.batch.core.launch.JobOperator; import org.springframework.batch.core.launch.support.JobOperatorFactoryBean; -import org.springframework.batch.core.launch.support.TaskExecutorJobLauncher; -import org.springframework.batch.core.repository.ExecutionContextSerializer; +import org.springframework.batch.core.launch.support.TaskExecutorJobOperator; import org.springframework.batch.core.repository.JobRepository; -import org.springframework.batch.core.repository.dao.AbstractJdbcBatchMetadataDao; -import org.springframework.batch.core.repository.dao.DefaultExecutionContextSerializer; -import org.springframework.batch.core.repository.dao.JdbcExecutionContextDao; -import org.springframework.batch.core.repository.dao.JdbcJobExecutionDao; -import org.springframework.batch.core.repository.dao.JdbcStepExecutionDao; -import org.springframework.batch.core.repository.support.JobRepositoryFactoryBean; -import org.springframework.batch.item.database.support.DataFieldMaxValueIncrementerFactory; -import org.springframework.batch.item.database.support.DefaultDataFieldMaxValueIncrementerFactory; -import org.springframework.batch.support.DatabaseType; +import org.springframework.batch.core.repository.support.ResourcelessJobRepository; +import org.springframework.batch.support.transaction.ResourcelessTransactionManager; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; -import org.springframework.core.convert.support.ConfigurableConversionService; -import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.core.task.SyncTaskExecutor; import org.springframework.core.task.TaskExecutor; -import org.springframework.jdbc.core.JdbcOperations; -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; /** - * Base {@link Configuration} class that provides common JDBC-based infrastructure beans - * for enabling and using Spring Batch. + * Base {@link Configuration} class that provides common infrastructure beans for enabling + * and using Spring Batch. *

* This configuration class configures and registers the following beans in the * application context: * *

    - *
  • a {@link JobRepository} named "jobRepository"
  • - *
  • a {@link JobExplorer} named "jobExplorer"
  • - *
  • a {@link JobLauncher} named "jobLauncher"
  • - *
  • a {@link JobRegistry} named "jobRegistry"
  • - *
  • a {@link JobOperator} named "JobOperator"
  • - *
  • a {@link JobRegistryBeanPostProcessor} named "jobRegistryBeanPostProcessor"
  • + *
  • a {@link ResourcelessJobRepository} named "jobRepository"
  • + *
  • a {@link TaskExecutorJobOperator} named "jobOperator"
  • *
  • a {@link org.springframework.batch.core.scope.StepScope} named "stepScope"
  • *
  • a {@link org.springframework.batch.core.scope.JobScope} named "jobScope"
  • *
@@ -113,7 +77,7 @@ * @since 5.0 */ @Configuration(proxyBeanMethods = false) -@Import(ScopeConfiguration.class) +@Import({ ScopeConfiguration.class, BatchObservabilityBeanPostProcessor.class }) public class DefaultBatchConfiguration implements ApplicationContextAware { protected ApplicationContext applicationContext; @@ -124,122 +88,19 @@ public void setApplicationContext(ApplicationContext applicationContext) throws } @Bean - public JobRepository jobRepository() throws BatchConfigurationException { - JobRepositoryFactoryBean jobRepositoryFactoryBean = new JobRepositoryFactoryBean(); - try { - jobRepositoryFactoryBean.setDataSource(getDataSource()); - jobRepositoryFactoryBean.setTransactionManager(getTransactionManager()); - jobRepositoryFactoryBean.setDatabaseType(getDatabaseType()); - jobRepositoryFactoryBean.setIncrementerFactory(getIncrementerFactory()); - jobRepositoryFactoryBean.setJobKeyGenerator(getJobKeyGenerator()); - jobRepositoryFactoryBean.setClobType(getClobType()); - jobRepositoryFactoryBean.setTablePrefix(getTablePrefix()); - jobRepositoryFactoryBean.setSerializer(getExecutionContextSerializer()); - jobRepositoryFactoryBean.setConversionService(getConversionService()); - jobRepositoryFactoryBean.setJdbcOperations(getJdbcOperations()); - jobRepositoryFactoryBean.setLobHandler(getLobHandler()); - jobRepositoryFactoryBean.setCharset(getCharset()); - jobRepositoryFactoryBean.setMaxVarCharLength(getMaxVarCharLength()); - jobRepositoryFactoryBean.setIsolationLevelForCreateEnum(getIsolationLevelForCreate()); - jobRepositoryFactoryBean.setValidateTransactionState(getValidateTransactionState()); - jobRepositoryFactoryBean.afterPropertiesSet(); - return jobRepositoryFactoryBean.getObject(); - } - catch (Exception e) { - throw new BatchConfigurationException("Unable to configure the default job repository", e); - } - } - - /** - * 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 - * @return a job launcher - * @throws BatchConfigurationException if unable to configure the default job launcher - * @since 5.2 - */ - @Bean - public JobLauncher jobLauncher(JobRepository jobRepository) throws BatchConfigurationException { - TaskExecutorJobLauncher taskExecutorJobLauncher = new TaskExecutorJobLauncher(); - taskExecutorJobLauncher.setJobRepository(jobRepository); - taskExecutorJobLauncher.setTaskExecutor(getTaskExecutor()); - try { - taskExecutorJobLauncher.afterPropertiesSet(); - return taskExecutorJobLauncher; - } - catch (Exception e) { - throw new BatchConfigurationException("Unable to configure the default job launcher", e); - } - } - - @Bean - public JobExplorer jobExplorer() throws BatchConfigurationException { - JobExplorerFactoryBean jobExplorerFactoryBean = new JobExplorerFactoryBean(); - jobExplorerFactoryBean.setDataSource(getDataSource()); - jobExplorerFactoryBean.setTransactionManager(getTransactionManager()); - jobExplorerFactoryBean.setJdbcOperations(getJdbcOperations()); - jobExplorerFactoryBean.setJobKeyGenerator(getJobKeyGenerator()); - jobExplorerFactoryBean.setCharset(getCharset()); - jobExplorerFactoryBean.setTablePrefix(getTablePrefix()); - jobExplorerFactoryBean.setLobHandler(getLobHandler()); - jobExplorerFactoryBean.setConversionService(getConversionService()); - jobExplorerFactoryBean.setSerializer(getExecutionContextSerializer()); - try { - jobExplorerFactoryBean.afterPropertiesSet(); - return jobExplorerFactoryBean.getObject(); - } - catch (Exception e) { - throw new BatchConfigurationException("Unable to configure the default job explorer", e); - } - } - - @Bean - public JobRegistry jobRegistry() throws BatchConfigurationException { - return new MapJobRegistry(); + public JobRepository jobRepository() { + return new ResourcelessJobRepository(); } - /** - * 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 - * @param jobExplorer a job explorer - * @param jobRegistry a job registry - * @param jobLauncher a job launcher - * @return a job operator - * @throws BatchConfigurationException if unable to configure the default job operator - * @since 5.2 - */ @Bean - public JobOperator jobOperator(JobRepository jobRepository, JobExplorer jobExplorer, JobRegistry jobRegistry, - JobLauncher jobLauncher) throws BatchConfigurationException { + public JobOperator jobOperator(JobRepository jobRepository) throws BatchConfigurationException { JobOperatorFactoryBean jobOperatorFactoryBean = new JobOperatorFactoryBean(); - jobOperatorFactoryBean.setTransactionManager(getTransactionManager()); jobOperatorFactoryBean.setJobRepository(jobRepository); - jobOperatorFactoryBean.setJobExplorer(jobExplorer); - jobOperatorFactoryBean.setJobRegistry(jobRegistry); - jobOperatorFactoryBean.setJobLauncher(jobLauncher); + jobOperatorFactoryBean.setJobRegistry(getJobRegistry()); + jobOperatorFactoryBean.setTransactionManager(getTransactionManager()); + jobOperatorFactoryBean.setObservationRegistry(getObservationRegistry()); jobOperatorFactoryBean.setJobParametersConverter(getJobParametersConverter()); + jobOperatorFactoryBean.setTaskExecutor(getTaskExecutor()); try { jobOperatorFactoryBean.afterPropertiesSet(); return jobOperatorFactoryBean.getObject(); @@ -249,221 +110,42 @@ 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 - * @throws BatchConfigurationException if unable to register the bean - * @return a bean of type {@link JobRegistrySmartInitializingSingleton} - * @since 5.2 - */ - @Bean - public JobRegistrySmartInitializingSingleton jobRegistrySmartInitializingSingleton(JobRegistry jobRegistry) - throws BatchConfigurationException { - JobRegistrySmartInitializingSingleton jobRegistrySmartInitializingSingleton = new JobRegistrySmartInitializingSingleton(); - jobRegistrySmartInitializingSingleton.setJobRegistry(jobRegistry); - try { - jobRegistrySmartInitializingSingleton.afterPropertiesSet(); - return jobRegistrySmartInitializingSingleton; - } - catch (Exception e) { - throw new BatchConfigurationException( - "Unable to configure the default job registry SmartInitializingSingleton", e); - } - } - - /* - * Getters to customize the configuration of infrastructure beans - */ - - /** - * Return the data source to use for Batch meta-data. Defaults to the bean of type - * {@link DataSource} and named "dataSource" in the application context. - * @return The data source to use for Batch meta-data - */ - protected DataSource getDataSource() { - String errorMessage = " To use the default configuration, a data source bean named 'dataSource'" - + " should be defined in the application context but none was found. Override getDataSource()" - + " to provide the data source to use for Batch meta-data."; - if (this.applicationContext.getBeansOfType(DataSource.class).isEmpty()) { - throw new BatchConfigurationException( - "Unable to find a DataSource bean in the application context." + errorMessage); - } - else { - if (!this.applicationContext.containsBean("dataSource")) { - throw new BatchConfigurationException(errorMessage); + protected JobRegistry getJobRegistry() { + MapJobRegistry jobRegistry = new MapJobRegistry(); + this.applicationContext.getBeansOfType(Job.class).values().forEach(job -> { + try { + jobRegistry.register(job); } - } - return this.applicationContext.getBean("dataSource", DataSource.class); - } - - /** - * Return the transaction manager to use for the job repository. Defaults to the bean - * of type {@link PlatformTransactionManager} and named "transactionManager" in the - * application context. - * @return The transaction manager to use for the job repository - */ - protected PlatformTransactionManager getTransactionManager() { - String errorMessage = " To use the default configuration, a transaction manager bean named 'transactionManager'" - + " should be defined in the application context but none was found. Override getTransactionManager()" - + " to provide the transaction manager to use for the job repository."; - if (this.applicationContext.getBeansOfType(PlatformTransactionManager.class).isEmpty()) { - throw new BatchConfigurationException( - "Unable to find a PlatformTransactionManager bean in the application context." + errorMessage); - } - else { - if (!this.applicationContext.containsBean("transactionManager")) { - throw new BatchConfigurationException(errorMessage); + catch (DuplicateJobException e) { + throw new BatchConfigurationException(e); } - } - return this.applicationContext.getBean("transactionManager", PlatformTransactionManager.class); - } - - /** - * Return the value of the {@code validateTransactionState} parameter. Defaults to - * {@code true}. - * @return true if the transaction state should be validated, false otherwise - */ - protected boolean getValidateTransactionState() { - return true; + }); + return jobRegistry; } /** - * Return the transaction isolation level when creating job executions. Defaults to - * {@link Isolation#SERIALIZABLE}. - * @return the transaction isolation level when creating job executions + * Return the {@link ObservationRegistry} to use for the job operator. Defaults to + * {@link ObservationRegistry#NOOP}. + * @return The ObservationRegistry to use for the job operator + * @since 6.0 */ - protected Isolation getIsolationLevelForCreate() { - return Isolation.SERIALIZABLE; + protected ObservationRegistry getObservationRegistry() { + return ObservationRegistry.NOOP; } /** - * Return the length of long string columns in database. Do not override this if you - * haven't modified the schema. Note this value will be used for the exit message in - * both {@link JdbcJobExecutionDao} and {@link JdbcStepExecutionDao} and also the - * short version of the execution context in {@link JdbcExecutionContextDao} . For - * databases with multi-byte character sets this number can be smaller (by up to a - * factor of 2 for 2-byte characters) than the declaration of the column length in the - * DDL for the tables. Defaults to - * {@link AbstractJdbcBatchMetadataDao#DEFAULT_EXIT_MESSAGE_LENGTH} + * Return the transaction manager to use for the job operator. Defaults to + * {@link ResourcelessTransactionManager}. + * @return The transaction manager to use for the job operator */ - protected int getMaxVarCharLength() { - return AbstractJdbcBatchMetadataDao.DEFAULT_EXIT_MESSAGE_LENGTH; - } - - /** - * Return the prefix of Batch meta-data tables. Defaults to - * {@link AbstractJdbcBatchMetadataDao#DEFAULT_TABLE_PREFIX}. - * @return the prefix of meta-data tables - */ - protected String getTablePrefix() { - return AbstractJdbcBatchMetadataDao.DEFAULT_TABLE_PREFIX; - } - - /** - * Return the {@link Charset} to use when serializing/deserializing the execution - * context. Defaults to "UTF-8". - * @return the charset to use when serializing/deserializing the execution context - */ - 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. - * @return the {@link JdbcOperations} to use - */ - protected JdbcOperations getJdbcOperations() { - return new JdbcTemplate(getDataSource()); - } - - /** - * A custom implementation of the {@link ExecutionContextSerializer}. The default, if - * not injected, is the {@link DefaultExecutionContextSerializer}. - * @return the serializer to use to serialize/deserialize the execution context - */ - protected ExecutionContextSerializer getExecutionContextSerializer() { - return new DefaultExecutionContextSerializer(); - } - - /** - * Return the value from {@link java.sql.Types} class to indicate the type to use for - * a CLOB - * @return the value from {@link java.sql.Types} class to indicate the type to use for - * a CLOB - */ - protected int getClobType() { - return Types.CLOB; - } - - /** - * Return the factory for creating {@link DataFieldMaxValueIncrementer} - * implementations used to increment entity IDs in meta-data tables. - * @return the factory for creating {@link DataFieldMaxValueIncrementer} - * implementations. - */ - protected DataFieldMaxValueIncrementerFactory getIncrementerFactory() { - return new DefaultDataFieldMaxValueIncrementerFactory(getDataSource()); - } - - /** - * A custom implementation of the {@link JobKeyGenerator}. The default, if not - * injected, is the {@link DefaultJobKeyGenerator}. - * @return the generator that creates the key used in identifying {@link JobInstance} - * objects - * @since 5.1 - */ - protected JobKeyGenerator getJobKeyGenerator() { - return new DefaultJobKeyGenerator(); - } - - /** - * Return the database type. The default will be introspected from the JDBC meta-data - * of the data source. - * @return the database type - * @throws MetaDataAccessException if an error occurs when trying to get the database - * type of JDBC meta-data - * - */ - protected String getDatabaseType() throws MetaDataAccessException { - return DatabaseType.fromMetaData(getDataSource()).name(); + protected PlatformTransactionManager getTransactionManager() { + return new ResourcelessTransactionManager(); } /** - * Return the {@link TaskExecutor} to use in the the job launcher. Defaults to + * Return the {@link TaskExecutor} to use in the job operator. Defaults to * {@link SyncTaskExecutor}. - * @return the {@link TaskExecutor} to use in the the job launcher. + * @return the {@link TaskExecutor} to use in the job operator. */ protected TaskExecutor getTaskExecutor() { return new SyncTaskExecutor(); @@ -473,28 +155,12 @@ protected TaskExecutor getTaskExecutor() { * Return the {@link JobParametersConverter} to use in the job operator. Defaults to * {@link DefaultJobParametersConverter} * @return the {@link JobParametersConverter} to use in the job operator. + * @deprecated since 6.0 with no replacement and scheduled for removal in 6.2 or + * later. */ + @Deprecated(since = "6.0", forRemoval = true) protected JobParametersConverter getJobParametersConverter() { return new DefaultJobParametersConverter(); } - /** - * Return the conversion service to use in the job repository and job explorer. This - * service is used to convert job parameters from String literal to typed values and - * vice versa. - * @return the {@link ConfigurableConversionService} to use. - */ - protected ConfigurableConversionService getConversionService() { - DefaultConversionService conversionService = new DefaultConversionService(); - conversionService.addConverter(new DateToStringConverter()); - conversionService.addConverter(new StringToDateConverter()); - conversionService.addConverter(new LocalDateToStringConverter()); - conversionService.addConverter(new StringToLocalDateConverter()); - conversionService.addConverter(new LocalTimeToStringConverter()); - conversionService.addConverter(new StringToLocalTimeConverter()); - conversionService.addConverter(new LocalDateTimeToStringConverter()); - conversionService.addConverter(new StringToLocalDateTimeConverter()); - return conversionService; - } - } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/DefaultJobLoader.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/DefaultJobLoader.java index 4dde8ea152..aa14354826 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/DefaultJobLoader.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/DefaultJobLoader.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,10 +24,9 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.Step; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.step.Step; import org.springframework.batch.core.configuration.DuplicateJobException; -import org.springframework.batch.core.configuration.JobFactory; import org.springframework.batch.core.configuration.JobRegistry; import org.springframework.batch.core.configuration.StepRegistry; import org.springframework.batch.core.launch.NoSuchJobException; @@ -47,7 +46,9 @@ * @author Dave Syer * @author Stephane Nicoll * @author Mahmoud Ben Hassine + * @deprecated since 6.0 with no replacement. Scheduled for removal in 6.2 or later. */ +@Deprecated(since = "6.0", forRemoval = true) public class DefaultJobLoader implements JobLoader, InitializingBean { private static final Log logger = LogFactory.getLog(DefaultJobLoader.class); @@ -173,7 +174,7 @@ private Collection doLoad(ApplicationContextFactory factory, boolean unregi if (!autoRegistrationDetected) { - Job job = (Job) context.getBean(name); + Job job = context.getBean(name, Job.class); String jobName = job.getName(); // On reload try to unregister first @@ -251,15 +252,14 @@ private Collection getSteps(final StepLocator stepLocator, final Applicati * @throws DuplicateJobException if that job is already registered */ private void doRegister(ConfigurableApplicationContext context, Job job) throws DuplicateJobException { - final JobFactory jobFactory = new ReferenceJobFactory(job); - jobRegistry.register(jobFactory); + jobRegistry.register(job); if (stepRegistry != null) { - if (!(job instanceof StepLocator)) { + if (!(job instanceof StepLocator stepLocator)) { throw new UnsupportedOperationException("Cannot locate steps from a Job that is not a StepLocator: job=" + job.getName() + " does not implement StepLocator"); } - stepRegistry.register(job.getName(), getSteps((StepLocator) job, context)); + stepRegistry.register(job.getName(), getSteps(stepLocator, context)); } } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/GenericApplicationContextFactory.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/GenericApplicationContextFactory.java index a9074f6671..0e69248c72 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/GenericApplicationContextFactory.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/GenericApplicationContextFactory.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. @@ -39,7 +39,10 @@ * the child {@link ApplicationContext} is returned. The child context is not re-created * every time it is requested. It is lazily initialized and cached. Clients should ensure * that it is closed when it is no longer needed. + * + * @deprecated since 6.0 with no replacement. Scheduled for removal in 6.2 or later. */ +@Deprecated(since = "6.0", forRemoval = true) public class GenericApplicationContextFactory extends AbstractApplicationContextFactory { /** @@ -126,7 +129,7 @@ protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) { GenericApplicationContextFactory.this.prepareBeanFactory(parentBeanFactory, beanFactory); for (Class cls : getBeanFactoryPostProcessorClasses()) { for (String name : parent.getBeanNamesForType(cls)) { - beanFactory.registerSingleton(name, (parent.getBean(name))); + beanFactory.registerSingleton(name, parent.getBean(name)); } } } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/GroupAwareJob.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/GroupAwareJob.java index 2ea527202c..b0cbce6657 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/GroupAwareJob.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/GroupAwareJob.java @@ -15,10 +15,10 @@ */ package org.springframework.batch.core.configuration.support; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobParametersIncrementer; -import org.springframework.batch.core.JobParametersValidator; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParametersIncrementer; +import org.springframework.batch.core.job.parameters.JobParametersValidator; import org.springframework.lang.Nullable; import org.springframework.util.ClassUtils; @@ -74,7 +74,7 @@ public void execute(JobExecution execution) { /** * Concatenates the group name and the delegate job name (joining with a "."). * - * @see org.springframework.batch.core.Job#getName() + * @see Job#getName() */ @Override public String getName() { @@ -99,8 +99,8 @@ public JobParametersValidator getJobParametersValidator() { @Override public boolean equals(Object obj) { - if (obj instanceof GroupAwareJob) { - return ((GroupAwareJob) obj).delegate.equals(delegate); + if (obj instanceof GroupAwareJob groupAwareJob) { + return groupAwareJob.delegate.equals(delegate); } return false; } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/JdbcDefaultBatchConfiguration.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/JdbcDefaultBatchConfiguration.java new file mode 100644 index 0000000000..2b3cc40e15 --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/JdbcDefaultBatchConfiguration.java @@ -0,0 +1,291 @@ +/* + * 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. + * 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 org.springframework.batch.core.configuration.BatchConfigurationException; +import org.springframework.batch.core.converter.DateToStringConverter; +import org.springframework.batch.core.converter.LocalDateTimeToStringConverter; +import org.springframework.batch.core.converter.LocalDateToStringConverter; +import org.springframework.batch.core.converter.LocalTimeToStringConverter; +import org.springframework.batch.core.converter.StringToDateConverter; +import org.springframework.batch.core.converter.StringToLocalDateConverter; +import org.springframework.batch.core.converter.StringToLocalDateTimeConverter; +import org.springframework.batch.core.converter.StringToLocalTimeConverter; +import org.springframework.batch.core.job.DefaultJobKeyGenerator; +import org.springframework.batch.core.job.JobInstance; +import org.springframework.batch.core.job.JobKeyGenerator; +import org.springframework.batch.core.launch.JobOperator; +import org.springframework.batch.core.repository.ExecutionContextSerializer; +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.core.repository.dao.AbstractJdbcBatchMetadataDao; +import org.springframework.batch.core.repository.dao.DefaultExecutionContextSerializer; +import org.springframework.batch.core.repository.dao.jdbc.JdbcExecutionContextDao; +import org.springframework.batch.core.repository.dao.jdbc.JdbcJobExecutionDao; +import org.springframework.batch.core.repository.dao.jdbc.JdbcStepExecutionDao; +import org.springframework.batch.core.repository.support.JdbcJobRepositoryFactoryBean; +import org.springframework.batch.item.database.support.DataFieldMaxValueIncrementerFactory; +import org.springframework.batch.item.database.support.DefaultDataFieldMaxValueIncrementerFactory; +import org.springframework.batch.support.DatabaseType; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.convert.support.ConfigurableConversionService; +import org.springframework.core.convert.support.DefaultConversionService; +import org.springframework.jdbc.core.JdbcOperations; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.support.MetaDataAccessException; +import org.springframework.jdbc.support.incrementer.DataFieldMaxValueIncrementer; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.annotation.Isolation; + +import javax.sql.DataSource; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.sql.Types; + +/** + * Base {@link Configuration} class that provides common JDBC-based infrastructure beans + * for enabling and using Spring Batch. + *

+ * This configuration class configures and registers the following beans in the + * application context: + * + *

    + *
  • a {@link JobRepository} named "jobRepository"
  • + *
  • a {@link JobOperator} named "jobOperator"
  • + *
  • a {@link org.springframework.batch.core.scope.StepScope} named "stepScope"
  • + *
  • a {@link org.springframework.batch.core.scope.JobScope} named "jobScope"
  • + *
+ * + * Customization is possible by extending the class and overriding getters. + *

+ * A typical usage of this class is as follows:

+ * @Configuration
+ * public class MyJobConfiguration extends JdbcDefaultBatchConfiguration {
+ *
+ *     @Bean
+ *     public Job job(JobRepository jobRepository) {
+ *         return new JobBuilder("myJob", jobRepository)
+ *                 // define job flow as needed
+ *                 .build();
+ *     }
+ *
+ * }
+ * 
+ * + * @author Mahmoud Ben Hassine + * @since 6.0 + */ +@Configuration(proxyBeanMethods = false) +public class JdbcDefaultBatchConfiguration extends DefaultBatchConfiguration { + + @Bean + @Override + public JobRepository jobRepository() throws BatchConfigurationException { + JdbcJobRepositoryFactoryBean jobRepositoryFactoryBean = new JdbcJobRepositoryFactoryBean(); + try { + jobRepositoryFactoryBean.setDataSource(getDataSource()); + jobRepositoryFactoryBean.setTransactionManager(getTransactionManager()); + jobRepositoryFactoryBean.setDatabaseType(getDatabaseType()); + jobRepositoryFactoryBean.setIncrementerFactory(getIncrementerFactory()); + jobRepositoryFactoryBean.setJobKeyGenerator(getJobKeyGenerator()); + jobRepositoryFactoryBean.setClobType(getClobType()); + jobRepositoryFactoryBean.setTablePrefix(getTablePrefix()); + jobRepositoryFactoryBean.setSerializer(getExecutionContextSerializer()); + jobRepositoryFactoryBean.setConversionService(getConversionService()); + jobRepositoryFactoryBean.setJdbcOperations(getJdbcOperations()); + jobRepositoryFactoryBean.setCharset(getCharset()); + jobRepositoryFactoryBean.setMaxVarCharLength(getMaxVarCharLength()); + jobRepositoryFactoryBean.setIsolationLevelForCreateEnum(getIsolationLevelForCreate()); + jobRepositoryFactoryBean.setValidateTransactionState(getValidateTransactionState()); + jobRepositoryFactoryBean.afterPropertiesSet(); + return jobRepositoryFactoryBean.getObject(); + } + catch (Exception e) { + throw new BatchConfigurationException("Unable to configure the default job repository", e); + } + } + + /* + * Getters to customize the configuration of infrastructure beans + */ + + /** + * Return the data source to use for Batch meta-data. Defaults to the bean of type + * {@link DataSource} and named "dataSource" in the application context. + * @return The data source to use for Batch meta-data + */ + protected DataSource getDataSource() { + String errorMessage = " To use the default configuration, a data source bean named 'dataSource'" + + " should be defined in the application context but none was found. Override getDataSource()" + + " to provide the data source to use for Batch meta-data."; + if (this.applicationContext.getBeansOfType(DataSource.class).isEmpty()) { + throw new BatchConfigurationException( + "Unable to find a DataSource bean in the application context." + errorMessage); + } + else { + if (!this.applicationContext.containsBean("dataSource")) { + throw new BatchConfigurationException(errorMessage); + } + } + return this.applicationContext.getBean("dataSource", DataSource.class); + } + + @Override + protected PlatformTransactionManager getTransactionManager() { + String errorMessage = " To use the default configuration, a PlatformTransactionManager bean named 'transactionManager'" + + " should be defined in the application context but none was found. Override getTransactionManager()" + + " to provide the transaction manager to use for the job repository."; + if (this.applicationContext.getBeansOfType(PlatformTransactionManager.class).isEmpty()) { + throw new BatchConfigurationException( + "Unable to find a PlatformTransactionManager bean in the application context." + errorMessage); + } + else { + if (!this.applicationContext.containsBean("transactionManager")) { + throw new BatchConfigurationException(errorMessage); + } + } + return this.applicationContext.getBean("transactionManager", PlatformTransactionManager.class); + } + + /** + * Return the length of long string columns in database. Do not override this if you + * haven't modified the schema. Note this value will be used for the exit message in + * both {@link JdbcJobExecutionDao} and {@link JdbcStepExecutionDao} and also the + * short version of the execution context in {@link JdbcExecutionContextDao} . For + * databases with multi-byte character sets this number can be smaller (by up to a + * factor of 2 for 2-byte characters) than the declaration of the column length in the + * DDL for the tables. Defaults to + * {@link AbstractJdbcBatchMetadataDao#DEFAULT_EXIT_MESSAGE_LENGTH} + */ + protected int getMaxVarCharLength() { + return AbstractJdbcBatchMetadataDao.DEFAULT_EXIT_MESSAGE_LENGTH; + } + + /** + * Return the prefix of Batch meta-data tables. Defaults to + * {@link AbstractJdbcBatchMetadataDao#DEFAULT_TABLE_PREFIX}. + * @return the prefix of meta-data tables + */ + protected String getTablePrefix() { + return AbstractJdbcBatchMetadataDao.DEFAULT_TABLE_PREFIX; + } + + /** + * Return the {@link Charset} to use when serializing/deserializing the execution + * context. Defaults to "UTF-8". + * @return the charset to use when serializing/deserializing the execution context + */ + protected Charset getCharset() { + return StandardCharsets.UTF_8; + } + + /** + * Return the {@link JdbcOperations}. If this property is not overridden, a new + * {@link JdbcTemplate} will be created for the configured data source by default. + * @return the {@link JdbcOperations} to use + */ + protected JdbcOperations getJdbcOperations() { + return new JdbcTemplate(getDataSource()); + } + + /** + * A custom implementation of the {@link ExecutionContextSerializer}. The default, if + * not injected, is the {@link DefaultExecutionContextSerializer}. + * @return the serializer to use to serialize/deserialize the execution context + */ + protected ExecutionContextSerializer getExecutionContextSerializer() { + return new DefaultExecutionContextSerializer(); + } + + /** + * Return the value from {@link Types} class to indicate the type to use for a CLOB + * @return the value from {@link Types} class to indicate the type to use for a CLOB + */ + protected int getClobType() { + return Types.CLOB; + } + + /** + * Return the factory for creating {@link DataFieldMaxValueIncrementer} + * implementations used to increment entity IDs in meta-data tables. + * @return the factory for creating {@link DataFieldMaxValueIncrementer} + * implementations. + */ + protected DataFieldMaxValueIncrementerFactory getIncrementerFactory() { + return new DefaultDataFieldMaxValueIncrementerFactory(getDataSource()); + } + + /** + * Return the database type. The default will be introspected from the JDBC meta-data + * of the data source. + * @return the database type + * @throws MetaDataAccessException if an error occurs when trying to get the database + * type of JDBC meta-data + * + */ + protected String getDatabaseType() throws MetaDataAccessException { + return DatabaseType.fromMetaData(getDataSource()).name(); + } + + /** + * Return the conversion service to use in the job repository and job explorer. This + * service is used to convert job parameters from String literal to typed values and + * vice versa. + * @return the {@link ConfigurableConversionService} to use. + */ + protected ConfigurableConversionService getConversionService() { + DefaultConversionService conversionService = new DefaultConversionService(); + conversionService.addConverter(new DateToStringConverter()); + conversionService.addConverter(new StringToDateConverter()); + conversionService.addConverter(new LocalDateToStringConverter()); + conversionService.addConverter(new StringToLocalDateConverter()); + conversionService.addConverter(new LocalTimeToStringConverter()); + conversionService.addConverter(new StringToLocalTimeConverter()); + conversionService.addConverter(new LocalDateTimeToStringConverter()); + conversionService.addConverter(new StringToLocalDateTimeConverter()); + return conversionService; + } + + /** + * Return the value of the {@code validateTransactionState} parameter. Defaults to + * {@code true}. + * @return true if the transaction state should be validated, false otherwise + */ + protected boolean getValidateTransactionState() { + return true; + } + + /** + * Return the transaction isolation level when creating job executions. Defaults to + * {@link Isolation#SERIALIZABLE}. + * @return the transaction isolation level when creating job executions + */ + protected Isolation getIsolationLevelForCreate() { + return Isolation.SERIALIZABLE; + } + + /** + * A custom implementation of the {@link JobKeyGenerator}. The default, if not + * injected, is the {@link DefaultJobKeyGenerator}. + * @return the generator that creates the key used in identifying {@link JobInstance} + * objects + * @since 5.1 + */ + protected JobKeyGenerator getJobKeyGenerator() { + return new DefaultJobKeyGenerator(); + } + +} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/JobFactoryRegistrationListener.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/JobFactoryRegistrationListener.java index 3ed14c2974..b55ce50e71 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/JobFactoryRegistrationListener.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/JobFactoryRegistrationListener.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. @@ -27,8 +27,10 @@ * Generic service that can bind and unbind a {@link JobFactory} in a {@link JobRegistry}. * * @author Dave Syer - * + * @author Mahmoud Ben Hassine + * @deprecated since 6.0 with no replacement. Scheduled for removal in 6.2 or later. */ +@Deprecated(since = "6.0", forRemoval = true) public class JobFactoryRegistrationListener { private final Log logger = LogFactory.getLog(getClass()); @@ -53,7 +55,7 @@ public void bind(JobFactory jobFactory, Map params) throws Exception if (logger.isInfoEnabled()) { logger.info("Binding JobFactory: " + jobFactory.getJobName()); } - jobRegistry.register(jobFactory); + jobRegistry.register(jobFactory.createJob()); } /** diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/JobLoader.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/JobLoader.java index e4821843fe..1b4288c785 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/JobLoader.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/JobLoader.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. @@ -17,13 +17,16 @@ import java.util.Collection; -import org.springframework.batch.core.Job; +import org.springframework.batch.core.job.Job; import org.springframework.batch.core.configuration.DuplicateJobException; /** * @author Dave Syer + * @author Mahmoud Ben Hassine * @since 2.1 + * @deprecated since 6.0 with no replacement. Scheduled for removal in 6.2 or later. */ +@Deprecated(since = "6.0", forRemoval = true) public interface JobLoader { /** 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..aafa8b4a49 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. @@ -21,7 +21,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.batch.core.Job; +import org.springframework.batch.core.job.Job; import org.springframework.batch.core.configuration.DuplicateJobException; import org.springframework.batch.core.configuration.JobLocator; import org.springframework.batch.core.configuration.JobRegistry; @@ -42,13 +42,13 @@ * {@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 + * @deprecated since 6.0 with no replacement. Register a {@link MapJobRegistry} as a bean, + * and it will automatically register all {@link Job} beans in the application context. */ +@Deprecated(since = "6.0", forRemoval = true) public class JobRegistrySmartInitializingSingleton implements SmartInitializingSingleton, BeanFactoryAware, InitializingBean, DisposableBean { @@ -146,12 +146,11 @@ private void postProcessAfterInitialization(Job job, String beanName) { groupName = getGroupName(defaultListableBeanFactory.getBeanDefinition(beanName), job); } job = groupName == null ? job : new GroupAwareJob(groupName, job); - ReferenceJobFactory jobFactory = new ReferenceJobFactory(job); - String name = jobFactory.getJobName(); + String name = job.getName(); if (logger.isDebugEnabled()) { logger.debug("Registering job: " + name); } - jobRegistry.register(jobFactory); + jobRegistry.register(job); jobNames.add(name); } catch (DuplicateJobException e) { diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/MapJobRegistry.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/MapJobRegistry.java index 3e55bedc0c..9058740855 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/MapJobRegistry.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/MapJobRegistry.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2019 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,68 +16,89 @@ package org.springframework.batch.core.configuration.support; import java.util.Collections; +import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import org.springframework.batch.core.Job; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.batch.core.job.Job; import org.springframework.batch.core.configuration.DuplicateJobException; -import org.springframework.batch.core.configuration.JobFactory; import org.springframework.batch.core.configuration.JobRegistry; import org.springframework.batch.core.launch.NoSuchJobException; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.SmartInitializingSingleton; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** - * Simple, thread-safe, map-based implementation of {@link JobRegistry}. + * Simple, thread-safe, map-based implementation of {@link JobRegistry}. This registry is + * a {@link SmartInitializingSingleton} that is automatically populated with all + * {@link Job} beans in the {@link ApplicationContext}. * * @author Dave Syer * @author Robert Fischer * @author Mahmoud Ben Hassine */ -public class MapJobRegistry implements JobRegistry { +public class MapJobRegistry implements JobRegistry, SmartInitializingSingleton, ApplicationContextAware { + + protected final Log logger = LogFactory.getLog(getClass()); /** - * The map holding the registered job factories. + * The map holding the registered jobs. */ - // The "final" ensures that it is visible and initialized when the constructor - // resolves. - private final ConcurrentMap map = new ConcurrentHashMap<>(); + private final ConcurrentMap map = new ConcurrentHashMap<>(); + + private ApplicationContext applicationContext; + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.applicationContext = applicationContext; + } + + @Override + public void afterSingletonsInstantiated() { + Map jobBeans = this.applicationContext.getBeansOfType(Job.class); + this.map.putAll(jobBeans); + } @Override - public void register(JobFactory jobFactory) throws DuplicateJobException { - Assert.notNull(jobFactory, "jobFactory is null"); - String name = jobFactory.getJobName(); - Assert.notNull(name, "Job configuration must have a name."); - JobFactory previousValue = map.putIfAbsent(name, jobFactory); + public void register(Job job) throws DuplicateJobException { + Assert.notNull(job, "job must not be null"); + String jobName = job.getName(); + Assert.notNull(jobName, "Job name must not be null"); + Job previousValue = this.map.putIfAbsent(jobName, job); if (previousValue != null) { - throw new DuplicateJobException("A job configuration with this name [" + name + "] was already registered"); + throw new DuplicateJobException("A job with this name [" + jobName + "] was already registered"); } } @Override public void unregister(String name) { - Assert.notNull(name, "Job configuration must have a name."); - map.remove(name); + Assert.notNull(name, "Job name must not be null"); + this.map.remove(name); } @Override public Job getJob(@Nullable String name) throws NoSuchJobException { - JobFactory factory = map.get(name); - if (factory == null) { - throw new NoSuchJobException("No job configuration with the name [" + name + "] was registered"); + Job job = this.map.get(name); + if (job == null) { + throw new NoSuchJobException("No job with the name [" + name + "] was registered"); } else { - return factory.createJob(); + return job; } } /** - * Provides an unmodifiable view of the job names. + * Provides an unmodifiable view of job names. */ @Override public Set getJobNames() { - return Collections.unmodifiableSet(map.keySet()); + return Collections.unmodifiableSet(this.map.keySet()); } } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/MapStepRegistry.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/MapStepRegistry.java index 0d3aa396b9..051a44edd0 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/MapStepRegistry.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/MapStepRegistry.java @@ -21,7 +21,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import org.springframework.batch.core.Step; +import org.springframework.batch.core.step.Step; import org.springframework.batch.core.configuration.DuplicateJobException; import org.springframework.batch.core.configuration.StepRegistry; import org.springframework.batch.core.launch.NoSuchJobException; diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/MongoDefaultBatchConfiguration.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/MongoDefaultBatchConfiguration.java new file mode 100644 index 0000000000..fdb18c63d3 --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/MongoDefaultBatchConfiguration.java @@ -0,0 +1,151 @@ +/* + * 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. + * 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 org.springframework.batch.core.configuration.BatchConfigurationException; +import org.springframework.batch.core.job.DefaultJobKeyGenerator; +import org.springframework.batch.core.job.JobInstance; +import org.springframework.batch.core.job.JobKeyGenerator; +import org.springframework.batch.core.launch.JobOperator; +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.core.repository.support.MongoJobRepositoryFactoryBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.mongodb.MongoTransactionManager; +import org.springframework.data.mongodb.core.MongoOperations; +import org.springframework.transaction.annotation.Isolation; + +/** + * Base {@link Configuration} class that provides common MongoDB-based infrastructure + * beans for enabling and using Spring Batch. + *

+ * This configuration class configures and registers the following beans in the + * application context: + * + *

    + *
  • a {@link JobRepository} named "jobRepository"
  • + *
  • a {@link JobOperator} named "jobOperator"
  • + *
  • a {@link org.springframework.batch.core.scope.StepScope} named "stepScope"
  • + *
  • a {@link org.springframework.batch.core.scope.JobScope} named "jobScope"
  • + *
+ * + * Customization is possible by extending the class and overriding getters. + *

+ * A typical usage of this class is as follows:

+ * @Configuration
+ * public class MyJobConfiguration extends MongoDefaultBatchConfiguration {
+ *
+ *     @Bean
+ *     public Job job(JobRepository jobRepository) {
+ *         return new JobBuilder("myJob", jobRepository)
+ *                 // define job flow as needed
+ *                 .build();
+ *     }
+ *
+ * }
+ * 
+ * + * @author Mahmoud Ben Hassine + * @since 6.0 + */ +@Configuration(proxyBeanMethods = false) +public class MongoDefaultBatchConfiguration extends DefaultBatchConfiguration { + + @Bean + @Override + public JobRepository jobRepository() throws BatchConfigurationException { + MongoJobRepositoryFactoryBean jobRepositoryFactoryBean = new MongoJobRepositoryFactoryBean(); + try { + jobRepositoryFactoryBean.setMongoOperations(getMongoOperations()); + jobRepositoryFactoryBean.setTransactionManager(getTransactionManager()); + jobRepositoryFactoryBean.setIsolationLevelForCreateEnum(getIsolationLevelForCreate()); + jobRepositoryFactoryBean.setValidateTransactionState(getValidateTransactionState()); + jobRepositoryFactoryBean.setJobKeyGenerator(getJobKeyGenerator()); + jobRepositoryFactoryBean.afterPropertiesSet(); + return jobRepositoryFactoryBean.getObject(); + } + catch (Exception e) { + throw new BatchConfigurationException("Unable to configure the default job repository", e); + } + } + + /* + * Getters to customize the configuration of infrastructure beans + */ + + protected MongoOperations getMongoOperations() { + String errorMessage = " To use the default configuration, a MongoOperations bean named 'mongoTemplate'" + + " should be defined in the application context but none was found. Override getMongoOperations()" + + " to provide the MongoOperations for Batch meta-data."; + if (this.applicationContext.getBeansOfType(MongoOperations.class).isEmpty()) { + throw new BatchConfigurationException( + "Unable to find a MongoOperations bean in the application context." + errorMessage); + } + else { + if (!this.applicationContext.containsBean("mongoTemplate")) { + throw new BatchConfigurationException(errorMessage); + } + } + return this.applicationContext.getBean("mongoTemplate", MongoOperations.class); + } + + @Override + protected MongoTransactionManager getTransactionManager() { + String errorMessage = " To use the default configuration, a MongoTransactionManager bean named 'transactionManager'" + + " should be defined in the application context but none was found. Override getTransactionManager()" + + " to provide the transaction manager to use for the job repository."; + if (this.applicationContext.getBeansOfType(MongoTransactionManager.class).isEmpty()) { + throw new BatchConfigurationException( + "Unable to find a MongoTransactionManager bean in the application context." + errorMessage); + } + else { + if (!this.applicationContext.containsBean("transactionManager")) { + throw new BatchConfigurationException(errorMessage); + } + } + return this.applicationContext.getBean("transactionManager", MongoTransactionManager.class); + } + + /** + * Return the value of the {@code validateTransactionState} parameter. Defaults to + * {@code true}. + * @return true if the transaction state should be validated, false otherwise + */ + protected boolean getValidateTransactionState() { + return true; + } + + /** + * Return the transaction isolation level when creating job executions. Defaults to + * {@link Isolation#SERIALIZABLE}. + * @return the transaction isolation level when creating job executions + */ + protected Isolation getIsolationLevelForCreate() { + return Isolation.SERIALIZABLE; + } + + /** + * A custom implementation of the {@link JobKeyGenerator}. The default, if not + * injected, is the {@link DefaultJobKeyGenerator}. + * @return the generator that creates the key used in identifying {@link JobInstance} + * objects + * @since 5.1 + */ + protected JobKeyGenerator getJobKeyGenerator() { + return new DefaultJobKeyGenerator(); + } + +} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ReferenceJobFactory.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ReferenceJobFactory.java index 4664448c0c..aed7d306c6 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ReferenceJobFactory.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ReferenceJobFactory.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. @@ -15,7 +15,7 @@ */ package org.springframework.batch.core.configuration.support; -import org.springframework.batch.core.Job; +import org.springframework.batch.core.job.Job; import org.springframework.batch.core.configuration.JobFactory; /** @@ -23,8 +23,10 @@ * {@link Job}. * * @author Dave Syer - * + * @author Mahmoud Ben Hassine + * @deprecated since 6.0 with no replacement. Scheduled for removal in 6.2 or later. */ +@Deprecated(since = "6.0", forRemoval = true) public class ReferenceJobFactory implements JobFactory { private final Job job; 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..c378258a91 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. @@ -404,13 +404,12 @@ protected static Collection createTransition(FlowExecutionStatus endBuilder.addConstructorArgValue(exitCodeExists ? exitCode : status.getName()); String endName = (status == FlowExecutionStatus.STOPPED ? STOP_ELE - : status == FlowExecutionStatus.FAILED ? FAIL_ELE : END_ELE) + (endCounter++); + : status == FlowExecutionStatus.FAILED ? FAIL_ELE : END_ELE) + endCounter++; endBuilder.addConstructorArgValue(endName); 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/main/java/org/springframework/batch/core/configuration/xml/AbstractStepParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/AbstractStepParser.java index c12eaf8633..73ef8f82cc 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/AbstractStepParser.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/AbstractStepParser.java @@ -20,6 +20,7 @@ import org.w3c.dom.NodeList; import org.springframework.batch.core.listener.StepListenerMetaData; +import org.springframework.batch.core.step.Step; import org.springframework.beans.MutablePropertyValues; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinitionHolder; @@ -35,9 +36,9 @@ /** * Internal parser for the <step/> elements inside a job. A step element references - * a bean definition for a {@link org.springframework.batch.core.Step} and goes on to - * (optionally) list a set of transitions from that step to others with <next - * on="pattern" to="stepName"/>. Used by the {@link JobParser}. + * a bean definition for a {@link Step} and goes on to (optionally) list a set of + * transitions from that step to others with <next on="pattern" to="stepName"/>. + * Used by the {@link JobParser}. * * @author Dave Syer * @author Thomas Risberg @@ -118,16 +119,13 @@ protected AbstractBeanDefinition parseStep(Element stepElement, ParserContext pa new TaskletParser().parseTasklet(stepElement, nestedElement, bd, parserContext, stepUnderspecified); } else if (FLOW_ELE.equals(name)) { - boolean stepUnderspecified = CoreNamespaceUtils.isUnderspecified(stepElement); - parseFlow(stepElement, nestedElement, bd, parserContext, stepUnderspecified); + parseFlow(stepElement, nestedElement, bd); } else if (PARTITION_ELE.equals(name)) { - boolean stepUnderspecified = CoreNamespaceUtils.isUnderspecified(stepElement); - parsePartition(stepElement, nestedElement, bd, parserContext, stepUnderspecified, jobFactoryRef); + parsePartition(stepElement, nestedElement, bd, parserContext, jobFactoryRef); } else if (JOB_ELE.equals(name)) { - boolean stepUnderspecified = CoreNamespaceUtils.isUnderspecified(stepElement); - parseJob(stepElement, nestedElement, bd, parserContext, stepUnderspecified); + parseJob(nestedElement, bd, parserContext); } else if ("description".equals(name)) { bd.setDescription(nestedElement.getTextContent()); @@ -199,7 +197,7 @@ else if (ns.equals("http://www.springframework.org/schema/batch")) { } private void parsePartition(Element stepElement, Element partitionElement, AbstractBeanDefinition bd, - ParserContext parserContext, boolean stepUnderspecified, String jobFactoryRef) { + ParserContext parserContext, String jobFactoryRef) { bd.setBeanClass(StepParserStepFactoryBean.class); bd.setAttribute("isNamespaceStep", true); @@ -258,8 +256,7 @@ else if (inlineStepElement != null) { } - private void parseJob(Element stepElement, Element jobElement, AbstractBeanDefinition bd, - ParserContext parserContext, boolean stepUnderspecified) { + private void parseJob(Element jobElement, AbstractBeanDefinition bd, ParserContext parserContext) { bd.setBeanClass(StepParserStepFactoryBean.class); bd.setAttribute("isNamespaceStep", true); @@ -285,8 +282,7 @@ private void parseJob(Element stepElement, Element jobElement, AbstractBeanDefin } - private void parseFlow(Element stepElement, Element flowElement, AbstractBeanDefinition bd, - ParserContext parserContext, boolean stepUnderspecified) { + private void parseFlow(Element stepElement, Element flowElement, AbstractBeanDefinition bd) { bd.setBeanClass(StepParserStepFactoryBean.class); bd.setAttribute("isNamespaceStep", true); diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/CoreNamespacePostProcessor.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/CoreNamespacePostProcessor.java index 7f250d389c..cbaed045bb 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/CoreNamespacePostProcessor.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/CoreNamespacePostProcessor.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. @@ -115,23 +115,21 @@ public Object postProcessBeforeInitialization(Object bean, String beanName) thro * @return the bean with default collaborators injected into it */ private Object injectDefaults(Object bean) { - if (bean instanceof JobParserJobFactoryBean) { - JobParserJobFactoryBean fb = (JobParserJobFactoryBean) bean; + if (bean instanceof JobParserJobFactoryBean fb) { JobRepository jobRepository = fb.getJobRepository(); if (jobRepository == null) { - fb.setJobRepository((JobRepository) applicationContext.getBean(DEFAULT_JOB_REPOSITORY_NAME)); + fb.setJobRepository(applicationContext.getBean(DEFAULT_JOB_REPOSITORY_NAME, JobRepository.class)); } } - else if (bean instanceof StepParserStepFactoryBean) { - StepParserStepFactoryBean fb = (StepParserStepFactoryBean) bean; + else if (bean instanceof StepParserStepFactoryBean fb) { JobRepository jobRepository = fb.getJobRepository(); if (jobRepository == null) { - fb.setJobRepository((JobRepository) applicationContext.getBean(DEFAULT_JOB_REPOSITORY_NAME)); + fb.setJobRepository(applicationContext.getBean(DEFAULT_JOB_REPOSITORY_NAME, JobRepository.class)); } PlatformTransactionManager transactionManager = fb.getTransactionManager(); if (transactionManager == null && fb.requiresTransactionManager()) { fb.setTransactionManager( - (PlatformTransactionManager) applicationContext.getBean(DEFAULT_TRANSACTION_MANAGER_NAME)); + applicationContext.getBean(DEFAULT_TRANSACTION_MANAGER_NAME, PlatformTransactionManager.class)); } } return bean; diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/ExceptionElementParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/ExceptionElementParser.java index 382a7b6d97..de8aff9119 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/ExceptionElementParser.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/ExceptionElementParser.java @@ -32,8 +32,8 @@ public ManagedMap parse(Element element, ParserContex if (children.size() == 1) { ManagedMap map = new ManagedMap<>(); Element exceptionClassesElement = children.get(0); - addExceptionClasses("include", true, exceptionClassesElement, map, parserContext); - addExceptionClasses("exclude", false, exceptionClassesElement, map, parserContext); + addExceptionClasses("include", true, exceptionClassesElement, map); + addExceptionClasses("exclude", false, exceptionClassesElement, map); map.put(new TypedStringValue(ForceRollbackForWriteSkipException.class.getName(), Class.class), true); return map; } @@ -46,7 +46,7 @@ else if (children.size() > 1) { } private void addExceptionClasses(String elementName, boolean include, Element exceptionClassesElement, - ManagedMap map, ParserContext parserContext) { + ManagedMap map) { for (Element child : DomUtils.getChildElementsByTagName(exceptionClassesElement, elementName)) { String className = child.getAttribute("class"); map.put(new TypedStringValue(className, Class.class), include); diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/InlineStepParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/InlineStepParser.java index 22fa9bb18c..4e9123a0bc 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/InlineStepParser.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/InlineStepParser.java @@ -18,6 +18,7 @@ import java.util.Collection; import org.springframework.batch.core.job.flow.support.state.StepState; +import org.springframework.batch.core.step.Step; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.parsing.BeanComponentDefinition; import org.springframework.beans.factory.support.AbstractBeanDefinition; @@ -27,9 +28,9 @@ /** * Internal parser for the <step/> elements inside a job. A step element references - * a bean definition for a {@link org.springframework.batch.core.Step} and goes on to - * (optionally) list a set of transitions from that step to others with <next - * on="pattern" to="stepName"/>. Used by the {@link JobParser}. + * a bean definition for a {@link Step} and goes on to (optionally) list a set of + * transitions from that step to others with <next on="pattern" to="stepName"/>. + * Used by the {@link JobParser}. * * @see JobParser * @author Dave Syer diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/JobParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/JobParser.java index 8254cd66d8..9931c03172 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/JobParser.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/JobParser.java @@ -18,6 +18,7 @@ import java.util.Arrays; import java.util.List; +import org.springframework.batch.core.job.Job; import org.springframework.beans.BeanMetadataElement; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinitionHolder; @@ -33,7 +34,7 @@ /** * Parser for the <job/> element in the Batch namespace. Sets up and returns a bean - * definition for a {@link org.springframework.batch.core.Job}. + * definition for a {@link Job}. * * @author Dave Syer * @author Mahmoud Ben Hassine @@ -103,7 +104,7 @@ protected void doParse(Element element, ParserContext parserContext, BeanDefinit builder.addPropertyValue("restartable", restartableAttribute); } - String incrementer = (element.getAttribute("incrementer")); + String incrementer = element.getAttribute("incrementer"); if (StringUtils.hasText(incrementer)) { builder.addPropertyReference("jobParametersIncrementer", incrementer); } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/JobParserJobFactoryBean.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/JobParserJobFactoryBean.java index b46ff39874..cd299c48ee 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/JobParserJobFactoryBean.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/JobParserJobFactoryBean.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. @@ -15,9 +15,9 @@ */ package org.springframework.batch.core.configuration.xml; -import org.springframework.batch.core.JobExecutionListener; -import org.springframework.batch.core.JobParametersIncrementer; -import org.springframework.batch.core.JobParametersValidator; +import org.springframework.batch.core.listener.JobExecutionListener; +import org.springframework.batch.core.job.parameters.JobParametersIncrementer; +import org.springframework.batch.core.job.parameters.JobParametersValidator; import org.springframework.batch.core.job.flow.Flow; import org.springframework.batch.core.job.flow.FlowJob; import org.springframework.batch.core.repository.JobRepository; 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..730296ea77 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. @@ -15,6 +15,7 @@ */ package org.springframework.batch.core.configuration.xml; +import org.springframework.batch.core.repository.support.JdbcJobRepositoryFactoryBean; import org.springframework.beans.factory.BeanDefinitionStoreException; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.RuntimeBeanReference; @@ -28,7 +29,7 @@ /** * Parser for the <job-repository/> element in the Batch namespace. Sets up and - * returns a JobRepositoryFactoryBean. + * returns a {@link JdbcJobRepositoryFactoryBean}. * * @author Thomas Risberg * @author Mahmoud Ben Hassine @@ -39,7 +40,7 @@ public class JobRepositoryParser extends AbstractSingleBeanDefinitionParser { @Override protected String getBeanClassName(Element element) { - return "org.springframework.batch.core.repository.support.JobRepositoryFactoryBean"; + return "org.springframework.batch.core.repository.support.JdbcJobRepositoryFactoryBean"; } @Override @@ -57,7 +58,7 @@ protected String resolveId(Element element, AbstractBeanDefinition definition, P /** * Parse and create a bean definition for a - * {@link org.springframework.batch.core.repository.support.JobRepositoryFactoryBean} + * {@link org.springframework.batch.core.repository.support.JdbcJobRepositoryFactoryBean} * . */ @Override @@ -77,8 +78,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 +96,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/SimpleFlowFactoryBean.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/SimpleFlowFactoryBean.java index 216935b2ef..85fc80fe1d 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/SimpleFlowFactoryBean.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/SimpleFlowFactoryBean.java @@ -205,7 +205,7 @@ public FlowExecutionStatus handle(FlowExecutor executor) throws Exception { @Override public Collection getFlows() { - return (state instanceof FlowHolder) ? ((FlowHolder) state).getFlows() : Collections.emptyList(); + return (state instanceof FlowHolder flowHolder) ? flowHolder.getFlows() : Collections.emptyList(); } } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/StandaloneStepParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/StandaloneStepParser.java index 2b07ad9677..ad39ddcffe 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/StandaloneStepParser.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/StandaloneStepParser.java @@ -15,13 +15,14 @@ */ package org.springframework.batch.core.configuration.xml; +import org.springframework.batch.core.step.Step; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.xml.ParserContext; import org.w3c.dom.Element; /** * Internal parser for the <step/> elements for a job. A step element references a - * bean definition for a {@link org.springframework.batch.core.Step}. + * bean definition for a {@link Step}. * * @author Dave Syer * @author Thomas Risberg 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..67e745d58d 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. @@ -23,20 +23,20 @@ import java.util.Map; import java.util.Set; -import org.springframework.batch.core.ChunkListener; -import org.springframework.batch.core.ItemProcessListener; -import org.springframework.batch.core.ItemReadListener; -import org.springframework.batch.core.ItemWriteListener; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.SkipListener; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepExecutionListener; -import org.springframework.batch.core.StepListener; +import org.springframework.batch.core.launch.JobOperator; +import org.springframework.batch.core.listener.ChunkListener; +import org.springframework.batch.core.listener.ItemProcessListener; +import org.springframework.batch.core.listener.ItemReadListener; +import org.springframework.batch.core.listener.ItemWriteListener; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.listener.SkipListener; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.listener.StepExecutionListener; +import org.springframework.batch.core.listener.StepListener; import org.springframework.batch.core.job.flow.Flow; -import org.springframework.batch.core.launch.JobLauncher; import org.springframework.batch.core.partition.PartitionHandler; -import org.springframework.batch.core.partition.support.Partitioner; -import org.springframework.batch.core.partition.support.StepExecutionAggregator; +import org.springframework.batch.core.partition.Partitioner; +import org.springframework.batch.core.partition.StepExecutionAggregator; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.step.AbstractStep; import org.springframework.batch.core.step.builder.AbstractTaskletStepBuilder; @@ -61,7 +61,6 @@ import org.springframework.batch.item.ItemWriter; import org.springframework.batch.repeat.CompletionPolicy; import org.springframework.batch.repeat.policy.SimpleCompletionPolicy; -import org.springframework.batch.repeat.support.TaskExecutorRepeatTemplate; import org.springframework.beans.factory.BeanNameAware; import org.springframework.beans.factory.FactoryBean; import org.springframework.classify.BinaryExceptionClassifier; @@ -126,7 +125,7 @@ public class StepParserStepFactoryBean implements FactoryBean, BeanN // private Job job; - private JobLauncher jobLauncher; + private JobOperator jobOperator; private JobParametersExtractor jobParametersExtractor; @@ -185,8 +184,6 @@ public class StepParserStepFactoryBean implements FactoryBean, BeanN private TaskExecutor taskExecutor; - private Integer throttleLimit; - private ItemReader itemReader; private ItemProcessor itemProcessor; @@ -275,8 +272,8 @@ protected void enhanceCommonStep(StepBuilderHelper builder) { builder.startLimit(startLimit); } for (Object listener : stepExecutionListeners) { - if (listener instanceof StepExecutionListener) { - builder.listener((StepExecutionListener) listener); + if (listener instanceof StepExecutionListener stepExecutionListener) { + builder.listener(stepExecutionListener); } } } @@ -473,9 +470,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) { @@ -522,7 +516,7 @@ private Step createJobStep() throws Exception { JobStepBuilder builder = new StepBuilder(name, jobRepository).job(job); enhanceCommonStep(builder); builder.parametersExtractor(jobParametersExtractor); - builder.launcher(jobLauncher); + builder.operator(jobOperator); return builder.build(); } @@ -571,14 +565,14 @@ private void validateDependency(String dependentName, Object dependentValue, Str * @return {@code true} if the object has a value */ private boolean isPresent(Object o) { - if (o instanceof Integer) { - return isPositive((Integer) o); + if (o instanceof Integer i) { + return isPositive(i); } - if (o instanceof Collection) { - return !((Collection) o).isEmpty(); + if (o instanceof Collection collection) { + return !collection.isEmpty(); } - if (o instanceof Map) { - return !((Map) o).isEmpty(); + if (o instanceof Map map) { + return !map.isEmpty(); } return o != null; } @@ -662,8 +656,8 @@ public void setJobParametersExtractor(JobParametersExtractor jobParametersExtrac this.jobParametersExtractor = jobParametersExtractor; } - public void setJobLauncher(JobLauncher jobLauncher) { - this.jobLauncher = jobLauncher; + public void setJobOperator(JobOperator jobOperator) { + this.jobOperator = jobOperator; } // ========================================================= @@ -893,7 +887,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.
*
@@ -992,19 +986,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/configuration/xml/TaskletParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/TaskletParser.java index a1a6668579..0f316b81ed 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/TaskletParser.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/TaskletParser.java @@ -213,7 +213,7 @@ private void handleExceptionElement(Element element, ParserContext parserContext ManagedList list = new ManagedList<>(); list.setMergeEnabled(exceptionClassesElement.hasAttribute(MERGE_ATTR) && Boolean.parseBoolean(exceptionClassesElement.getAttribute(MERGE_ATTR))); - addExceptionClasses("include", exceptionClassesElement, list, parserContext); + addExceptionClasses("include", exceptionClassesElement, list); propertyValues.addPropertyValue(propertyName, list); } else if (children.size() > 1) { @@ -224,7 +224,7 @@ else if (children.size() > 1) { } private void addExceptionClasses(String elementName, Element exceptionClassesElement, - ManagedList list, ParserContext parserContext) { + ManagedList list) { for (Element child : DomUtils.getChildElementsByTagName(exceptionClassesElement, elementName)) { String className = child.getAttribute("class"); list.add(new TypedStringValue(className, Class.class)); diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/TopLevelStepParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/TopLevelStepParser.java index 19f5fffb80..297c6ef6bb 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/TopLevelStepParser.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/TopLevelStepParser.java @@ -15,6 +15,7 @@ */ package org.springframework.batch.core.configuration.xml; +import org.springframework.batch.core.step.Step; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser; import org.springframework.beans.factory.xml.ParserContext; @@ -22,7 +23,7 @@ /** * Parser for the <step/> top level element in the Batch namespace. Sets up and - * returns a bean definition for a {@link org.springframework.batch.core.Step}. + * returns a bean definition for a {@link Step}. * * @author Thomas Risberg * diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/converter/DefaultJobParametersConverter.java b/spring-batch-core/src/main/java/org/springframework/batch/core/converter/DefaultJobParametersConverter.java index a9f671ca56..454c691872 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/converter/DefaultJobParametersConverter.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/converter/DefaultJobParametersConverter.java @@ -19,9 +19,9 @@ import java.util.Map.Entry; import java.util.Properties; -import org.springframework.batch.core.JobParameter; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.JobParametersBuilder; +import org.springframework.batch.core.job.parameters.JobParameter; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; import org.springframework.core.convert.support.ConfigurableConversionService; import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.lang.NonNull; @@ -108,7 +108,7 @@ public JobParameters getJobParameters(@Nullable Properties properties) { } /** - * @see org.springframework.batch.core.converter.JobParametersConverter#getProperties(org.springframework.batch.core.JobParameters) + * @see org.springframework.batch.core.converter.JobParametersConverter#getProperties(JobParameters) */ @Override public Properties getProperties(@Nullable JobParameters jobParameters) { diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/converter/JobParametersConverter.java b/spring-batch-core/src/main/java/org/springframework/batch/core/converter/JobParametersConverter.java index 60d9f58ab5..128938f48f 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/converter/JobParametersConverter.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/converter/JobParametersConverter.java @@ -18,8 +18,8 @@ import java.util.Properties; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.JobParametersBuilder; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; import org.springframework.lang.Nullable; /** diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/converter/JsonJobParametersConverter.java b/spring-batch-core/src/main/java/org/springframework/batch/core/converter/JsonJobParametersConverter.java index c7a0c784f7..a38b071c0f 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/converter/JsonJobParametersConverter.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/converter/JsonJobParametersConverter.java @@ -18,8 +18,8 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import org.springframework.batch.core.JobParameter; -import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.job.parameters.JobParameter; +import org.springframework.batch.core.job.parameters.JobParameters; /** * Converter for {@link JobParameters} instances that uses a JSON naming convention for diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/AbstractJob.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/AbstractJob.java index 34d6d19f58..783a239cb0 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/AbstractJob.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/AbstractJob.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. @@ -21,10 +21,6 @@ import java.util.List; import java.util.stream.Collectors; -import io.micrometer.core.instrument.LongTaskTimer; -import io.micrometer.core.instrument.MeterRegistry; -import io.micrometer.core.instrument.Metrics; -import io.micrometer.core.instrument.Tag; import io.micrometer.observation.Observation; import io.micrometer.observation.ObservationRegistry; import org.apache.commons.logging.Log; @@ -32,25 +28,19 @@ import org.springframework.batch.core.BatchStatus; import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobExecutionException; -import org.springframework.batch.core.JobExecutionListener; -import org.springframework.batch.core.JobInterruptedException; -import org.springframework.batch.core.JobParametersIncrementer; -import org.springframework.batch.core.JobParametersValidator; +import org.springframework.batch.core.job.parameters.DefaultJobParametersValidator; +import org.springframework.batch.core.job.parameters.JobParametersIncrementer; +import org.springframework.batch.core.job.parameters.JobParametersValidator; +import org.springframework.batch.core.listener.JobExecutionListener; import org.springframework.batch.core.SpringBatchVersion; -import org.springframework.batch.core.StartLimitExceededException; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.observability.BatchMetrics; +import org.springframework.batch.core.observability.jfr.events.job.JobExecutionEvent; +import org.springframework.batch.core.observability.micrometer.MicrometerMetrics; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.launch.NoSuchJobException; import org.springframework.batch.core.launch.support.ExitCodeMapper; import org.springframework.batch.core.listener.CompositeJobExecutionListener; -import org.springframework.batch.core.observability.BatchJobContext; -import org.springframework.batch.core.observability.BatchJobObservation; -import org.springframework.batch.core.observability.BatchJobObservationConvention; -import org.springframework.batch.core.observability.BatchMetrics; -import org.springframework.batch.core.observability.DefaultBatchJobObservationConvention; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.repository.JobRestartException; import org.springframework.batch.core.scope.context.JobSynchronizationManager; @@ -90,11 +80,7 @@ public abstract class AbstractJob implements Job, StepLocator, BeanNameAware, In private StepHandler stepHandler; - private ObservationRegistry observationRegistry = ObservationRegistry.NOOP; - - private MeterRegistry meterRegistry = Metrics.globalRegistry; - - private BatchJobObservationConvention observationConvention = new DefaultBatchJobObservationConvention(); + private ObservationRegistry observationRegistry; /** * Default constructor. @@ -129,6 +115,10 @@ public void setJobParametersValidator(JobParametersValidator jobParametersValida @Override public void afterPropertiesSet() throws Exception { Assert.state(jobRepository != null, "JobRepository must be set"); + if (this.observationRegistry == null) { + logger.info("No ObservationRegistry has been set, defaulting to ObservationRegistry NOOP"); + this.observationRegistry = ObservationRegistry.NOOP; + } } /** @@ -281,16 +271,15 @@ public final void execute(JobExecution execution) { } JobSynchronizationManager.register(execution); - String activeJobMeterName = "job.active"; - LongTaskTimer longTaskTimer = BatchMetrics.createLongTaskTimer(this.meterRegistry, activeJobMeterName, - "Active jobs", Tag.of(BatchMetrics.METRICS_PREFIX + activeJobMeterName + ".name", - execution.getJobInstance().getJobName())); - LongTaskTimer.Sample longTaskTimerSample = longTaskTimer.start(); - Observation observation = BatchMetrics - .createObservation(BatchJobObservation.BATCH_JOB_OBSERVATION.getName(), new BatchJobContext(execution), - this.observationRegistry) - .contextualName(execution.getJobInstance().getJobName()) - .observationConvention(this.observationConvention) + JobExecutionEvent jobExecutionEvent = new JobExecutionEvent(execution.getJobInstance().getJobName(), + execution.getJobInstance().getId(), execution.getId()); + jobExecutionEvent.begin(); + Observation observation = MicrometerMetrics + .createObservation(BatchMetrics.METRICS_PREFIX + "job", this.observationRegistry) + .highCardinalityKeyValue(BatchMetrics.METRICS_PREFIX + "job.instanceId", + execution.getJobInstance().getId().toString()) + .highCardinalityKeyValue(BatchMetrics.METRICS_PREFIX + "job.executionId", execution.getId().toString()) + .lowCardinalityKeyValue(BatchMetrics.METRICS_PREFIX + "job.name", execution.getJobInstance().getJobName()) .start(); try (Observation.Scope scope = observation.openScope()) { @@ -353,7 +342,8 @@ public final void execute(JobExecution execution) { execution.setExitStatus(exitStatus.and(newExitStatus)); } stopObservation(execution, observation); - longTaskTimerSample.stop(); + jobExecutionEvent.exitStatus = execution.getExitStatus().getExitCode(); + jobExecutionEvent.commit(); execution.setEndTime(LocalDateTime.now()); try { @@ -378,6 +368,8 @@ private void stopObservation(JobExecution execution, Observation observation) { if (!throwables.isEmpty()) { observation.error(mergedThrowables(throwables)); } + observation.lowCardinalityKeyValue(BatchMetrics.METRICS_PREFIX + "job.status", + execution.getExitStatus().getExitCode()); observation.stop(); } @@ -435,18 +427,10 @@ private void updateStatus(JobExecution jobExecution, BatchStatus status) { jobRepository.update(jobExecution); } - public void setObservationConvention(BatchJobObservationConvention observationConvention) { - this.observationConvention = observationConvention; - } - public void setObservationRegistry(ObservationRegistry observationRegistry) { this.observationRegistry = observationRegistry; } - public void setMeterRegistry(MeterRegistry meterRegistry) { - this.meterRegistry = meterRegistry; - } - @Override public String toString() { return ClassUtils.getShortName(getClass()) + ": [name=" + name + "]"; diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/DefaultJobKeyGenerator.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/DefaultJobKeyGenerator.java similarity index 86% rename from spring-batch-core/src/main/java/org/springframework/batch/core/DefaultJobKeyGenerator.java rename to spring-batch-core/src/main/java/org/springframework/batch/core/job/DefaultJobKeyGenerator.java index 9944fdfadd..5da1bcde58 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/DefaultJobKeyGenerator.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/DefaultJobKeyGenerator.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. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.batch.core; +package org.springframework.batch.core.job; import java.nio.charset.StandardCharsets; import java.util.ArrayList; @@ -21,6 +21,8 @@ import java.util.List; import java.util.Map; +import org.springframework.batch.core.job.parameters.JobParameter; +import org.springframework.batch.core.job.parameters.JobParameters; import org.springframework.util.Assert; import org.springframework.util.DigestUtils; @@ -34,7 +36,7 @@ * @author Mahmoud Ben Hassine * @since 2.2 */ -public class DefaultJobKeyGenerator implements JobKeyGenerator { +public class DefaultJobKeyGenerator implements JobKeyGenerator { /** * Generates the job key to be used based on the {@link JobParameters} instance diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/Job.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/Job.java similarity index 77% rename from spring-batch-core/src/main/java/org/springframework/batch/core/Job.java rename to spring-batch-core/src/main/java/org/springframework/batch/core/job/Job.java index fe0d0fbf15..22c91a5651 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/Job.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/Job.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. @@ -13,9 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.batch.core; +package org.springframework.batch.core.job; -import org.springframework.batch.core.job.DefaultJobParametersValidator; +import org.springframework.batch.core.job.parameters.DefaultJobParametersValidator; +import org.springframework.batch.core.job.parameters.JobParametersIncrementer; +import org.springframework.batch.core.job.parameters.JobParametersValidator; import org.springframework.lang.Nullable; /** @@ -26,9 +28,18 @@ * @author Dave Syer * @author Mahmoud Ben Hassine */ +@FunctionalInterface public interface Job { - String getName(); + /** + * The name of the job. This is used to distinguish between different jobs and must be + * unique within the job repository. If not explicitly set, the name will default to + * the fully qualified class name. + * @return the name of the job (never {@code null}) + */ + default String getName() { + return this.getClass().getName(); + } /** * Flag to indicate if this job can be restarted, at least in principle. diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/JobExecution.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/JobExecution.java similarity index 96% rename from spring-batch-core/src/main/java/org/springframework/batch/core/JobExecution.java rename to spring-batch-core/src/main/java/org/springframework/batch/core/job/JobExecution.java index c595f43e40..bfccba5e4b 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/JobExecution.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/JobExecution.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.batch.core; +package org.springframework.batch.core.job; import java.io.IOException; import java.io.ObjectInputStream; @@ -28,6 +28,9 @@ import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; +import org.springframework.batch.core.*; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.item.ExecutionContext; import org.springframework.lang.Nullable; @@ -203,7 +206,7 @@ public void upgradeStatus(BatchStatus status) { /** * Convenience getter for the {@code id} of the enclosing job. Useful for DAO * implementations. - * @return the @{code id} of the enclosing job. + * @return the {@code id} of the enclosing job. */ public Long getJobId() { if (jobInstance != null) { @@ -302,11 +305,10 @@ public void setCreateTime(LocalDateTime createTime) { } /** - * Package-private method for re-constituting the step executions from existing - * instances. + * Add a step execution from an existing instance. * @param stepExecution The {@code stepExecution} execution to be added. */ - void addStepExecution(StepExecution stepExecution) { + public void addStepExecution(StepExecution stepExecution) { stepExecutions.add(stepExecution); } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/JobExecutionException.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/JobExecutionException.java similarity index 97% rename from spring-batch-core/src/main/java/org/springframework/batch/core/JobExecutionException.java rename to spring-batch-core/src/main/java/org/springframework/batch/core/job/JobExecutionException.java index f64cd022ef..c808e40845 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/JobExecutionException.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/JobExecutionException.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.batch.core; +package org.springframework.batch.core.job; /** * Root of exception hierarchy for checked exceptions in job and step execution. Clients diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/JobInstance.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/JobInstance.java similarity index 93% rename from spring-batch-core/src/main/java/org/springframework/batch/core/JobInstance.java rename to spring-batch-core/src/main/java/org/springframework/batch/core/job/JobInstance.java index cecdc5481e..0feb0dfa7a 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/JobInstance.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/JobInstance.java @@ -14,8 +14,10 @@ * limitations under the License. */ -package org.springframework.batch.core; +package org.springframework.batch.core.job; +import org.springframework.batch.core.Entity; +import org.springframework.batch.core.job.parameters.JobParameters; import org.springframework.util.Assert; /** diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/JobInterruptedException.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/JobInterruptedException.java similarity index 95% rename from spring-batch-core/src/main/java/org/springframework/batch/core/JobInterruptedException.java rename to spring-batch-core/src/main/java/org/springframework/batch/core/job/JobInterruptedException.java index 8ba4c75832..7282e81894 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/JobInterruptedException.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/JobInterruptedException.java @@ -14,7 +14,9 @@ * limitations under the License. */ -package org.springframework.batch.core; +package org.springframework.batch.core.job; + +import org.springframework.batch.core.BatchStatus; /** * Exception to indicate the job has been interrupted. The exception state indicated is 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/job/JobKeyGenerator.java similarity index 78% rename from spring-batch-core/src/main/java/org/springframework/batch/core/JobKeyGenerator.java rename to spring-batch-core/src/main/java/org/springframework/batch/core/job/JobKeyGenerator.java index 589434b97f..36371d5ebd 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/job/JobKeyGenerator.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. @@ -13,7 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.batch.core; +package org.springframework.batch.core.job; + +import org.springframework.batch.core.job.parameters.JobParameters; /** * Strategy interface for the generation of the key used in identifying unique @@ -21,10 +23,11 @@ * * @author Michael Minella * @author Mahmoud Ben Hassine - * @param The type of the source data used to calculate the key. + * @author Taeik Lim * @since 2.2 */ -public interface JobKeyGenerator { +@FunctionalInterface +public interface JobKeyGenerator { /** * Method to generate the unique key used to identify a job instance. @@ -32,6 +35,6 @@ public interface JobKeyGenerator { * {@code null}). * @return a unique string identifying the job based on the information supplied. */ - String generateKey(T source); + String generateKey(JobParameters source); } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/SimpleJob.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/SimpleJob.java index b22317ef28..d2d2db1825 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/SimpleJob.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/SimpleJob.java @@ -21,12 +21,8 @@ import java.util.List; import org.springframework.batch.core.BatchStatus; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobInterruptedException; -import org.springframework.batch.core.StartLimitExceededException; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.repository.JobRestartException; import org.springframework.batch.core.step.StepLocator; @@ -79,8 +75,8 @@ public Collection getStepNames() { for (Step step : steps) { names.add(step.getName()); - if (step instanceof StepLocator) { - names.addAll(((StepLocator) step).getStepNames()); + if (step instanceof StepLocator stepLocator) { + names.addAll(stepLocator.getStepNames()); } } return names; @@ -100,8 +96,8 @@ public Step getStep(String stepName) { if (step.getName().equals(stepName)) { return step; } - else if (step instanceof StepLocator) { - Step result = ((StepLocator) step).getStep(stepName); + else if (step instanceof StepLocator stepLocator) { + Step result = stepLocator.getStep(stepName); if (result != null) { return result; } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/SimpleStepHandler.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/SimpleStepHandler.java index 930ab7f0cb..3693272865 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/SimpleStepHandler.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/SimpleStepHandler.java @@ -19,12 +19,8 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.batch.core.BatchStatus; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobInstance; -import org.springframework.batch.core.JobInterruptedException; -import org.springframework.batch.core.StartLimitExceededException; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.repository.JobRestartException; import org.springframework.batch.item.ExecutionContext; @@ -179,8 +175,7 @@ public StepExecution handleStep(Step step, JobExecution execution) * Detect whether a step execution belongs to this job execution. * @param jobExecution the current job execution * @param stepExecution an existing step execution - * @return true if the {@link org.springframework.batch.core.StepExecution} is part of - * the {@link org.springframework.batch.core.JobExecution} + * @return true if the {@link StepExecution} is part of the {@link JobExecution} */ private boolean stepExecutionPartOfExistingJobExecution(JobExecution jobExecution, StepExecution stepExecution) { return stepExecution != null && stepExecution.getJobExecutionId() != null diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/StartLimitExceededException.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/StartLimitExceededException.java similarity index 95% rename from spring-batch-core/src/main/java/org/springframework/batch/core/StartLimitExceededException.java rename to spring-batch-core/src/main/java/org/springframework/batch/core/job/StartLimitExceededException.java index 46e6582585..90eb31eb3d 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/StartLimitExceededException.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/StartLimitExceededException.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.batch.core; +package org.springframework.batch.core.job; /** * Indicates the step's start limit has been exceeded. diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/StepHandler.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/StepHandler.java index ebe18808e3..59052f6512 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/StepHandler.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/StepHandler.java @@ -16,12 +16,8 @@ package org.springframework.batch.core.job; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobInterruptedException; -import org.springframework.batch.core.StartLimitExceededException; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.repository.JobRestartException; /** diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/UnexpectedJobExecutionException.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/UnexpectedJobExecutionException.java similarity index 96% rename from spring-batch-core/src/main/java/org/springframework/batch/core/UnexpectedJobExecutionException.java rename to spring-batch-core/src/main/java/org/springframework/batch/core/job/UnexpectedJobExecutionException.java index eda11002f3..82cecb6aeb 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/UnexpectedJobExecutionException.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/UnexpectedJobExecutionException.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.batch.core; +package org.springframework.batch.core.job; /** * Indicates to the framework that a critical error has occurred and processing should diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/builder/FlowBuilder.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/builder/FlowBuilder.java index 17d3559471..963e7bb92f 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/builder/FlowBuilder.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/builder/FlowBuilder.java @@ -25,7 +25,7 @@ import java.util.Set; import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.Step; +import org.springframework.batch.core.step.Step; import org.springframework.batch.core.job.flow.Flow; import org.springframework.batch.core.job.flow.FlowExecutionStatus; import org.springframework.batch.core.job.flow.JobExecutionDecider; @@ -238,8 +238,8 @@ protected Flow flow() { } flow = new SimpleFlow(name); // optimization for flows that only have one state that itself is a flow: - if (currentState instanceof FlowState && states.size() == 1) { - return ((FlowState) currentState).getFlows().iterator().next(); + if (currentState instanceof FlowState flowState && states.size() == 1) { + return flowState.getFlows().iterator().next(); } addDanglingEndStates(); flow.setStateTransitions(transitions); @@ -282,23 +282,21 @@ private void doFrom(Object input) { private State createState(Object input) { State result; - if (input instanceof Step) { + if (input instanceof Step step) { if (!states.containsKey(input)) { - Step step = (Step) input; - states.put(input, new StepState(prefix + "step" + (stepCounter++), step)); + states.put(input, new StepState(prefix + "step" + stepCounter++, step)); } result = states.get(input); } - else if (input instanceof JobExecutionDecider) { + else if (input instanceof JobExecutionDecider jobExecutionDecider) { if (!states.containsKey(input)) { - states.put(input, - new DecisionState((JobExecutionDecider) input, prefix + "decision" + (decisionCounter++))); + states.put(input, new DecisionState(jobExecutionDecider, prefix + "decision" + decisionCounter++)); } result = states.get(input); } - else if (input instanceof Flow) { + else if (input instanceof Flow f) { if (!states.containsKey(input)) { - states.put(input, new FlowState((Flow) input, prefix + "flow" + (flowCounter++))); + states.put(input, new FlowState(f, prefix + "flow" + flowCounter++)); } result = states.get(input); } @@ -311,7 +309,7 @@ else if (input instanceof Flow) { private SplitState createState(Collection flows, TaskExecutor executor, SplitState parentSplit) { if (!states.containsKey(flows)) { - states.put(flows, new SplitState(flows, prefix + "split" + (splitCounter++), parentSplit)); + states.put(flows, new SplitState(flows, prefix + "split" + splitCounter++, parentSplit)); } SplitState result = (SplitState) states.get(flows); if (executor != null) { @@ -392,7 +390,7 @@ protected void stop(String pattern) { } protected void stop(String pattern, State restart) { - EndState next = new EndState(FlowExecutionStatus.STOPPED, "STOPPED", prefix + "stop" + (endCounter++), true); + EndState next = new EndState(FlowExecutionStatus.STOPPED, "STOPPED", prefix + "stop" + endCounter++, true); addTransition(pattern, next); currentState = next; addTransition("*", restart); @@ -403,7 +401,7 @@ private void end(String pattern) { } private void end(String pattern, String code) { - addTransition(pattern, new EndState(FlowExecutionStatus.COMPLETED, code, prefix + "end" + (endCounter++))); + addTransition(pattern, new EndState(FlowExecutionStatus.COMPLETED, code, prefix + "end" + endCounter++)); } private void fail(String pattern) { @@ -634,11 +632,11 @@ public SplitBuilder(FlowBuilder parent, TaskExecutor executor) { */ public FlowBuilder add(Flow... flows) { Collection list = new ArrayList<>(Arrays.asList(flows)); - String name = "split" + (parent.splitCounter++); + String name = "split" + parent.splitCounter++; State one = parent.currentState; - if (one instanceof SplitState) { - parent.currentState = parent.createState(list, executor, (SplitState) one); + if (one instanceof SplitState splitState) { + parent.currentState = parent.createState(list, executor, splitState); return parent; } @@ -647,8 +645,8 @@ public FlowBuilder add(Flow... flows) { stateBuilder.currentState = one; list.add(stateBuilder.build()); } - else if (one instanceof FlowState && parent.states.size() == 1) { - list.add(((FlowState) one).getFlows().iterator().next()); + else if (one instanceof FlowState flowState && parent.states.size() == 1) { + list.add(flowState.getFlows().iterator().next()); } parent.currentState = parent.createState(list, executor, null); diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/builder/FlowJobBuilder.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/builder/FlowJobBuilder.java index ccb718d39f..0e75832001 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/builder/FlowJobBuilder.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/builder/FlowJobBuilder.java @@ -15,8 +15,8 @@ */ package org.springframework.batch.core.job.builder; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.Step; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.step.Step; import org.springframework.batch.core.job.flow.Flow; import org.springframework.batch.core.job.flow.FlowJob; import org.springframework.batch.core.job.flow.JobExecutionDecider; 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..c42eb8e6d7 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. @@ -15,7 +15,7 @@ */ package org.springframework.batch.core.job.builder; -import org.springframework.batch.core.Step; +import org.springframework.batch.core.step.Step; import org.springframework.batch.core.job.flow.Flow; import org.springframework.batch.core.job.flow.JobExecutionDecider; import org.springframework.batch.core.repository.JobRepository; @@ -31,17 +31,17 @@ 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)} + * Create a new builder for a job with the given job repository. The name of the job + * will be set to the bean name by default. + * @param jobRepository the job repository to which the job should report to. + * @since 6.0 */ - @Deprecated(since = "5.0", forRemoval = true) - public JobBuilder(String name) { - super(name); + public JobBuilder(JobRepository jobRepository) { + super(jobRepository); } /** - * Create a new builder for a job with the given name. + * Create a new builder for a job with the given name and job repository. * @param name the name of the job * @param jobRepository the job repository to which the job should report to * @since 5.0 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..48028c94a1 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. @@ -22,20 +22,17 @@ import java.util.List; import java.util.Set; -import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.observation.ObservationRegistry; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.batch.core.JobExecutionListener; -import org.springframework.batch.core.JobParametersIncrementer; -import org.springframework.batch.core.JobParametersValidator; +import org.springframework.batch.core.listener.JobExecutionListener; +import org.springframework.batch.core.job.parameters.JobParametersIncrementer; +import org.springframework.batch.core.job.parameters.JobParametersValidator; import org.springframework.batch.core.annotation.AfterJob; import org.springframework.batch.core.annotation.BeforeJob; import org.springframework.batch.core.job.AbstractJob; import org.springframework.batch.core.listener.JobListenerFactoryBean; -import org.springframework.batch.core.observability.BatchJobObservationConvention; -import org.springframework.batch.core.observability.DefaultBatchJobObservationConvention; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.support.ReflectionUtils; @@ -56,13 +53,12 @@ public abstract class JobBuilderHelper> { /** * Create a new {@link JobBuilderHelper}. - * @param name the job name - * @deprecated use {@link JobBuilderHelper#JobBuilderHelper(String, JobRepository)} + * @param jobRepository the job repository + * @since 6.0 */ - @Deprecated(since = "5.1", forRemoval = true) - public JobBuilderHelper(String name) { + public JobBuilderHelper(JobRepository jobRepository) { this.properties = new CommonJobProperties(); - properties.name = name; + properties.jobRepository = jobRepository; } /** @@ -110,33 +106,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) - * @return this to enable fluent chaining - * @since 5.1 - */ - public B observationConvention(BatchJobObservationConvention observationConvention) { - properties.observationConvention = observationConvention; - @SuppressWarnings("unchecked") - B result = (B) this; - return result; - } - /** * Sets the observation registry for the job. * @param observationRegistry the observation registry (optional) @@ -149,18 +118,6 @@ public B observationRegistry(ObservationRegistry observationRegistry) { return result; } - /** - * Sets the meter registry for the job. - * @param meterRegistry the meter registry (optional) - * @return this to enable fluent chaining - */ - public B meterRegistry(MeterRegistry meterRegistry) { - properties.meterRegistry = meterRegistry; - @SuppressWarnings("unchecked") - B result = (B) this; - return result; - } - /** * Registers objects using the annotation based listener configuration. * @param listener the object that has a method configured with listener annotation @@ -228,18 +185,10 @@ protected void enhance(AbstractJob job) { if (jobParametersValidator != null) { job.setJobParametersValidator(jobParametersValidator); } - BatchJobObservationConvention observationConvention = properties.getObservationConvention(); - if (observationConvention != null) { - job.setObservationConvention(observationConvention); - } ObservationRegistry observationRegistry = properties.getObservationRegistry(); if (observationRegistry != null) { job.setObservationRegistry(observationRegistry); } - MeterRegistry meterRegistry = properties.getMeterRegistry(); - if (meterRegistry != null) { - job.setMeterRegistry(meterRegistry); - } Boolean restartable = properties.getRestartable(); if (restartable != null) { @@ -254,18 +203,16 @@ protected void enhance(AbstractJob job) { public static class CommonJobProperties { + private String name; + private Set jobExecutionListeners = new LinkedHashSet<>(); private boolean restartable = true; private JobRepository jobRepository; - private BatchJobObservationConvention observationConvention = new DefaultBatchJobObservationConvention(); - private ObservationRegistry observationRegistry; - private MeterRegistry meterRegistry; - private JobParametersIncrementer jobParametersIncrementer; private JobParametersValidator jobParametersValidator; @@ -277,9 +224,7 @@ public CommonJobProperties(CommonJobProperties properties) { this.name = properties.name; this.restartable = properties.restartable; this.jobRepository = properties.jobRepository; - this.observationConvention = properties.observationConvention; this.observationRegistry = properties.observationRegistry; - this.meterRegistry = properties.meterRegistry; this.jobExecutionListeners = new LinkedHashSet<>(properties.jobExecutionListeners); this.jobParametersIncrementer = properties.jobParametersIncrementer; this.jobParametersValidator = properties.jobParametersValidator; @@ -309,14 +254,6 @@ public void setJobRepository(JobRepository jobRepository) { this.jobRepository = jobRepository; } - public BatchJobObservationConvention getObservationConvention() { - return observationConvention; - } - - public void setObservationConvention(BatchJobObservationConvention observationConvention) { - this.observationConvention = observationConvention; - } - public ObservationRegistry getObservationRegistry() { return observationRegistry; } @@ -325,14 +262,6 @@ public void setObservationRegistry(ObservationRegistry observationRegistry) { this.observationRegistry = observationRegistry; } - public MeterRegistry getMeterRegistry() { - return meterRegistry; - } - - public void setMeterRegistry(MeterRegistry meterRegistry) { - this.meterRegistry = meterRegistry; - } - public String getName() { return name; } @@ -361,8 +290,6 @@ public void setRestartable(boolean restartable) { this.restartable = restartable; } - private String name; - } } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/builder/JobFlowBuilder.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/builder/JobFlowBuilder.java index db456d4863..0ae824d3dc 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/builder/JobFlowBuilder.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/builder/JobFlowBuilder.java @@ -15,7 +15,7 @@ */ package org.springframework.batch.core.job.builder; -import org.springframework.batch.core.Step; +import org.springframework.batch.core.step.Step; import org.springframework.batch.core.job.flow.Flow; import org.springframework.batch.core.job.flow.JobExecutionDecider; import org.springframework.beans.factory.InitializingBean; @@ -63,9 +63,9 @@ public JobFlowBuilder(FlowJobBuilder parent, Flow flow) { public FlowJobBuilder build() { Flow flow = flow(); - if (flow instanceof InitializingBean) { + if (flow instanceof InitializingBean initializingBean) { try { - ((InitializingBean) flow).afterPropertiesSet(); + initializingBean.afterPropertiesSet(); } catch (Exception e) { throw new FlowBuilderException(e); diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/builder/SimpleJobBuilder.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/builder/SimpleJobBuilder.java index b714d484ea..5668353f4c 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/builder/SimpleJobBuilder.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/builder/SimpleJobBuilder.java @@ -18,8 +18,8 @@ import java.util.ArrayList; import java.util.List; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.Step; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.step.Step; import org.springframework.batch.core.job.SimpleJob; import org.springframework.batch.core.job.flow.JobExecutionDecider; import org.springframework.core.task.TaskExecutor; @@ -156,7 +156,7 @@ public SimpleJobBuilder next(Step step) { * @param executor instance of {@link TaskExecutor} to be used. * @return builder for fluent chaining */ - public JobFlowBuilder.SplitBuilder split(TaskExecutor executor) { + public FlowBuilder.SplitBuilder split(TaskExecutor executor) { for (Step step : steps) { if (builder == null) { builder = new JobFlowBuilder(new FlowJobBuilder(this), step); diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/FlowExecutor.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/FlowExecutor.java index 4f24417f36..9b916d749a 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/FlowExecutor.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/FlowExecutor.java @@ -15,11 +15,11 @@ */ package org.springframework.batch.core.job.flow; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobInterruptedException; -import org.springframework.batch.core.StartLimitExceededException; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobInterruptedException; +import org.springframework.batch.core.job.StartLimitExceededException; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.repository.JobRestartException; import org.springframework.lang.Nullable; diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/FlowJob.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/FlowJob.java index 33e2f491fe..65e3604e85 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/FlowJob.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/FlowJob.java @@ -19,10 +19,10 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobExecutionException; -import org.springframework.batch.core.Step; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobExecutionException; +import org.springframework.batch.core.step.Step; import org.springframework.batch.core.job.AbstractJob; import org.springframework.batch.core.job.SimpleStepHandler; import org.springframework.batch.core.step.StepHolder; @@ -96,13 +96,13 @@ private void findSteps(Flow flow, Map map) { map.put(name, locator.getStep(name)); } } - else if (state instanceof StepHolder) { - Step step = ((StepHolder) state).getStep(); + else if (state instanceof StepHolder stepHolder) { + Step step = stepHolder.getStep(); String name = step.getName(); stepMap.put(name, step); } - else if (state instanceof FlowHolder) { - for (Flow subflow : ((FlowHolder) state).getFlows()) { + else if (state instanceof FlowHolder flowHolder) { + for (Flow subflow : flowHolder.getFlows()) { findSteps(subflow, map); } } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/FlowStep.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/FlowStep.java index de1ca1b5c0..4dea1a8b49 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/FlowStep.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/FlowStep.java @@ -1,5 +1,5 @@ /* - * Copyright 2009-2012 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. @@ -15,9 +15,9 @@ */ package org.springframework.batch.core.job.flow; -import org.springframework.batch.core.JobExecutionException; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobExecutionException; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.job.SimpleStepHandler; import org.springframework.batch.core.job.StepHandler; import org.springframework.batch.core.repository.JobRepository; @@ -32,6 +32,7 @@ * parent and one each for the flow steps). * * @author Dave Syer + * @author Mahmoud Ben Hassine * */ public class FlowStep extends AbstractStep { @@ -39,10 +40,12 @@ public class FlowStep extends AbstractStep { private Flow flow; /** - * Default constructor convenient for configuration purposes. + * Create a new instance of a {@link FlowStep} with the given job repository. + * @param jobRepository the job repository to use. Must not be null. + * @since 6.0 */ - public FlowStep() { - super(null); + public FlowStep(JobRepository jobRepository) { + super(jobRepository); } /** diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/JobExecutionDecider.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/JobExecutionDecider.java index 9ccad19835..34db827b1d 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/JobExecutionDecider.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/JobExecutionDecider.java @@ -15,8 +15,8 @@ */ package org.springframework.batch.core.job.flow; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.step.StepExecution; import org.springframework.lang.Nullable; /** diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/JobFlowExecutor.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/JobFlowExecutor.java index c1583e25f1..e72278f638 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/JobFlowExecutor.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/JobFlowExecutor.java @@ -18,11 +18,11 @@ import org.springframework.batch.core.BatchStatus; import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobInterruptedException; -import org.springframework.batch.core.StartLimitExceededException; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobInterruptedException; +import org.springframework.batch.core.job.StartLimitExceededException; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.job.StepHandler; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.repository.JobRestartException; @@ -40,7 +40,7 @@ */ public class JobFlowExecutor implements FlowExecutor { - private final ThreadLocal stepExecutionHolder = new ThreadLocal<>(); + private static final ThreadLocal stepExecutionHolder = new ThreadLocal<>(); private final JobExecution execution; diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/support/SimpleFlow.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/support/SimpleFlow.java index 1818d017fd..e90f7db82a 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/support/SimpleFlow.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/support/SimpleFlow.java @@ -29,8 +29,8 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.job.flow.Flow; import org.springframework.batch.core.job.flow.FlowExecution; import org.springframework.batch.core.job.flow.FlowExecutionException; @@ -243,9 +243,8 @@ protected State nextState(String stateName, FlowExecutionStatus status, StepExec } protected boolean isFlowContinued(State state, FlowExecutionStatus status, StepExecution stepExecution) { - boolean continued = true; - continued = state != null && status != FlowExecutionStatus.STOPPED; + boolean continued = state != null && status != FlowExecutionStatus.STOPPED; if (stepExecution != null) { Boolean reRun = (Boolean) stepExecution.getExecutionContext().get("batch.restart"); diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/support/state/EndState.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/support/state/EndState.java index c6528b03d0..f628c11878 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/support/state/EndState.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/support/state/EndState.java @@ -17,7 +17,7 @@ package org.springframework.batch.core.job.flow.support.state; import org.springframework.batch.core.BatchStatus; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.job.flow.FlowExecutionStatus; import org.springframework.batch.core.job.flow.FlowExecutor; import org.springframework.batch.core.job.flow.State; 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..8bedef1114 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 { @@ -128,15 +129,19 @@ public FlowExecutionStatus handle(final FlowExecutor executor) throws Exception catch (ExecutionException e) { // Unwrap the expected exceptions Throwable cause = e.getCause(); - if (cause instanceof Exception) { - throw (Exception) cause; + if (cause instanceof Exception exception) { + exceptions.add(exception); } 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/main/java/org/springframework/batch/core/job/flow/support/state/StepState.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/support/state/StepState.java index 73d20b4193..ec38ae382a 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/support/state/StepState.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/support/state/StepState.java @@ -20,7 +20,7 @@ import java.util.Collection; import java.util.List; -import org.springframework.batch.core.Step; +import org.springframework.batch.core.step.Step; import org.springframework.batch.core.job.flow.FlowExecutionStatus; import org.springframework.batch.core.job.flow.FlowExecutor; import org.springframework.batch.core.job.flow.State; @@ -84,8 +84,8 @@ public Collection getStepNames() { names.add(step.getName()); - if (step instanceof StepLocator) { - names.addAll(((StepLocator) step).getStepNames()); + if (step instanceof StepLocator stepLocator) { + names.addAll(stepLocator.getStepNames()); } return names; @@ -98,8 +98,8 @@ public Step getStep(String stepName) throws NoSuchStepException { if (step.getName().equals(stepName)) { result = step; } - else if (step instanceof StepLocator) { - result = ((StepLocator) step).getStep(stepName); + else if (step instanceof StepLocator stepLocator) { + result = stepLocator.getStep(stepName); } return result; diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/CompositeJobParametersValidator.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/parameters/CompositeJobParametersValidator.java similarity index 90% rename from spring-batch-core/src/main/java/org/springframework/batch/core/job/CompositeJobParametersValidator.java rename to spring-batch-core/src/main/java/org/springframework/batch/core/job/parameters/CompositeJobParametersValidator.java index 8ed88989d9..743afa4d85 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/CompositeJobParametersValidator.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/parameters/CompositeJobParametersValidator.java @@ -13,13 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.batch.core.job; +package org.springframework.batch.core.job.parameters; import java.util.List; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.JobParametersInvalidException; -import org.springframework.batch.core.JobParametersValidator; import org.springframework.beans.factory.InitializingBean; import org.springframework.lang.Nullable; import org.springframework.util.Assert; diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/DefaultJobParametersValidator.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/parameters/DefaultJobParametersValidator.java similarity index 95% rename from spring-batch-core/src/main/java/org/springframework/batch/core/job/DefaultJobParametersValidator.java rename to spring-batch-core/src/main/java/org/springframework/batch/core/job/parameters/DefaultJobParametersValidator.java index 114670c294..836cc74803 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/DefaultJobParametersValidator.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/parameters/DefaultJobParametersValidator.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.batch.core.job; +package org.springframework.batch.core.job.parameters; import java.util.Arrays; import java.util.Collection; @@ -23,9 +23,6 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.JobParametersInvalidException; -import org.springframework.batch.core.JobParametersValidator; import org.springframework.beans.factory.InitializingBean; import org.springframework.lang.Nullable; import org.springframework.util.Assert; diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/JobParameter.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/parameters/JobParameter.java similarity index 98% rename from spring-batch-core/src/main/java/org/springframework/batch/core/JobParameter.java rename to spring-batch-core/src/main/java/org/springframework/batch/core/job/parameters/JobParameter.java index cd9853a5aa..7c02f48b5c 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/JobParameter.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/parameters/JobParameter.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.batch.core; +package org.springframework.batch.core.job.parameters; import java.io.Serializable; diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/JobParameters.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/parameters/JobParameters.java similarity index 98% rename from spring-batch-core/src/main/java/org/springframework/batch/core/JobParameters.java rename to spring-batch-core/src/main/java/org/springframework/batch/core/job/parameters/JobParameters.java index a5e54b0c65..b4de56936f 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/JobParameters.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/parameters/JobParameters.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.batch.core; +package org.springframework.batch.core.job.parameters; import java.io.Serializable; import java.time.LocalDate; @@ -373,7 +373,7 @@ public String toString() { for (Map.Entry> entry : this.parameters.entrySet()) { parameters.add(String.format("'%s':'%s'", entry.getKey(), entry.getValue())); } - return new StringBuilder("{").append(String.join(",", parameters)).append("}").toString(); + return "{" + String.join(",", parameters) + "}"; } } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/JobParametersBuilder.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/parameters/JobParametersBuilder.java similarity index 78% rename from spring-batch-core/src/main/java/org/springframework/batch/core/JobParametersBuilder.java rename to spring-batch-core/src/main/java/org/springframework/batch/core/job/parameters/JobParametersBuilder.java index a12ad7bc67..7bebeedcd9 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/JobParametersBuilder.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/parameters/JobParametersBuilder.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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.batch.core; +package org.springframework.batch.core.job.parameters; import java.time.LocalDate; import java.time.LocalDateTime; @@ -23,7 +23,7 @@ import java.util.HashMap; import java.util.Map; -import org.springframework.batch.core.explore.JobExplorer; +import org.springframework.batch.core.job.JobInstance; import org.springframework.lang.NonNull; import org.springframework.util.Assert; @@ -49,8 +49,6 @@ public class JobParametersBuilder { private Map> parameterMap; - private JobExplorer jobExplorer; - /** * Default constructor. Initializes the builder with empty parameters. */ @@ -58,31 +56,11 @@ public JobParametersBuilder() { this.parameterMap = new HashMap<>(); } - /** - * @param jobExplorer {@link JobExplorer} used for looking up previous job parameter - * information. - */ - public JobParametersBuilder(JobExplorer jobExplorer) { - this.jobExplorer = jobExplorer; - this.parameterMap = new HashMap<>(); - } - /** * Copy constructor. Initializes the builder with the supplied parameters. * @param jobParameters {@link JobParameters} instance used to initialize the builder. */ public JobParametersBuilder(JobParameters jobParameters) { - this(jobParameters, null); - } - - /** - * Copy constructor. Initializes the builder with the supplied parameters. - * @param jobParameters {@link JobParameters} instance used to initialize the builder. - * @param jobExplorer {@link JobExplorer} used for looking up previous job parameter - * information. - */ - public JobParametersBuilder(JobParameters jobParameters, JobExplorer jobExplorer) { - this.jobExplorer = jobExplorer; this.parameterMap = new HashMap<>(jobParameters.getParameters()); } @@ -316,51 +294,4 @@ public JobParametersBuilder addJobParameters(JobParameters jobParameters) { return this; } - /** - * Initializes the {@link JobParameters} based on the state of the {@link Job}. This - * should be called after all parameters have been entered into the builder. All - * parameters already set on this builder instance are appended to those retrieved - * from the job incrementer, overriding any with the same key (this is the same - * behavior as - * {@link org.springframework.batch.core.launch.support.CommandLineJobRunner} with the - * {@code -next} option and - * {@link org.springframework.batch.core.launch.JobOperator#startNextInstance(String)}). - * @param job The job for which the {@link JobParameters} are being constructed. - * @return a reference to this object. - * - * @since 4.0 - */ - public JobParametersBuilder getNextJobParameters(Job job) { - Assert.state(this.jobExplorer != null, "A JobExplorer is required to get next job parameters"); - Assert.notNull(job, "Job must not be null"); - Assert.notNull(job.getJobParametersIncrementer(), - "No job parameters incrementer found for job=" + job.getName()); - - String name = job.getName(); - JobParameters nextParameters; - JobInstance lastInstance = this.jobExplorer.getLastJobInstance(name); - JobParametersIncrementer incrementer = job.getJobParametersIncrementer(); - if (lastInstance == null) { - // Start from a completely clean sheet - nextParameters = incrementer.getNext(new JobParameters()); - } - else { - JobExecution previousExecution = this.jobExplorer.getLastJobExecution(lastInstance); - if (previousExecution == null) { - // Normally this will not happen - an instance exists with no executions - nextParameters = incrementer.getNext(new JobParameters()); - } - else { - nextParameters = incrementer.getNext(previousExecution.getJobParameters()); - } - } - - // start with parameters from the incrementer - Map> nextParametersMap = new HashMap<>(nextParameters.getParameters()); - // append new parameters (overriding those with the same key) - nextParametersMap.putAll(this.parameterMap); - this.parameterMap = nextParametersMap; - return this; - } - } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/JobParametersIncrementer.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/parameters/JobParametersIncrementer.java similarity index 95% rename from spring-batch-core/src/main/java/org/springframework/batch/core/JobParametersIncrementer.java rename to spring-batch-core/src/main/java/org/springframework/batch/core/job/parameters/JobParametersIncrementer.java index 86d94dc52a..61caebe6a2 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/JobParametersIncrementer.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/parameters/JobParametersIncrementer.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.batch.core; +package org.springframework.batch.core.job.parameters; import org.springframework.lang.Nullable; diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/JobParametersInvalidException.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/parameters/JobParametersInvalidException.java similarity index 86% rename from spring-batch-core/src/main/java/org/springframework/batch/core/JobParametersInvalidException.java rename to spring-batch-core/src/main/java/org/springframework/batch/core/job/parameters/JobParametersInvalidException.java index c769bda7c6..2e9b2a139e 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/JobParametersInvalidException.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/parameters/JobParametersInvalidException.java @@ -13,7 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.batch.core; +package org.springframework.batch.core.job.parameters; + +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecutionException; /** * Exception for {@link Job} to signal that some {@link JobParameters} are invalid. diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/JobParametersValidator.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/parameters/JobParametersValidator.java similarity index 92% rename from spring-batch-core/src/main/java/org/springframework/batch/core/JobParametersValidator.java rename to spring-batch-core/src/main/java/org/springframework/batch/core/job/parameters/JobParametersValidator.java index 15e691bc34..c794e2b385 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/JobParametersValidator.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/parameters/JobParametersValidator.java @@ -13,8 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.batch.core; +package org.springframework.batch.core.job.parameters; +import org.springframework.batch.core.job.Job; import org.springframework.lang.Nullable; /** diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/JobExecutionNotFailedException.java b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/JobExecutionNotFailedException.java index b76206d945..688dfc7eb5 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/JobExecutionNotFailedException.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/JobExecutionNotFailedException.java @@ -15,7 +15,7 @@ */ package org.springframework.batch.core.launch; -import org.springframework.batch.core.JobExecutionException; +import org.springframework.batch.core.job.JobExecutionException; /** * Checked exception to indicate that user asked for a job execution to be resumed when diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/JobExecutionNotRunningException.java b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/JobExecutionNotRunningException.java index d376735ee9..ac588eca01 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/JobExecutionNotRunningException.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/JobExecutionNotRunningException.java @@ -15,7 +15,7 @@ */ package org.springframework.batch.core.launch; -import org.springframework.batch.core.JobExecutionException; +import org.springframework.batch.core.job.JobExecutionException; /** * Checked exception indicating that a JobExecution that is not currently running has been diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/JobExecutionNotStoppedException.java b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/JobExecutionNotStoppedException.java index 11567df815..9fa0ab46f8 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/JobExecutionNotStoppedException.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/JobExecutionNotStoppedException.java @@ -15,7 +15,7 @@ */ package org.springframework.batch.core.launch; -import org.springframework.batch.core.JobExecutionException; +import org.springframework.batch.core.job.JobExecutionException; /** * Checked exception to indicate that user asked for a job execution to be aborted when diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/JobInstanceAlreadyExistsException.java b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/JobInstanceAlreadyExistsException.java index f99bc19725..748f94af2c 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/JobInstanceAlreadyExistsException.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/JobInstanceAlreadyExistsException.java @@ -15,8 +15,8 @@ */ package org.springframework.batch.core.launch; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobExecutionException; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecutionException; /** * Checked exception to indicate that a required {@link Job} is not available. diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/JobLauncher.java b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/JobLauncher.java index 79afede5ce..20f3eaf4d8 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/JobLauncher.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/JobLauncher.java @@ -15,10 +15,10 @@ */ package org.springframework.batch.core.launch; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.JobParametersInvalidException; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersInvalidException; import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException; import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException; import org.springframework.batch.core.repository.JobRestartException; @@ -34,8 +34,11 @@ * @author Dave Syer * @author Taeik Lim * @author Mahmoud Ben Hassine + * @deprecated since 6.0 in favor of {@link JobOperator}. Scheduled for removal in 6.2 or + * later. */ @FunctionalInterface +@Deprecated(since = "6.0", forRemoval = true) public interface JobLauncher { /** diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/JobOperator.java b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/JobOperator.java index a56947412b..a50d0dda95 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/JobOperator.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/JobOperator.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. @@ -20,86 +20,40 @@ import java.util.Properties; import java.util.Set; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobInstance; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.JobParametersIncrementer; -import org.springframework.batch.core.JobParametersInvalidException; -import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.UnexpectedJobExecutionException; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobInstance; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersIncrementer; +import org.springframework.batch.core.job.parameters.JobParametersInvalidException; +import org.springframework.batch.core.step.StepExecution; +import org.springframework.batch.core.job.UnexpectedJobExecutionException; +import org.springframework.batch.core.configuration.JobRegistry; import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException; import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException; import org.springframework.batch.core.repository.JobRestartException; import org.springframework.lang.Nullable; /** - * Low level interface for inspecting and controlling jobs with access only to primitive - * and collection types. Suitable for a command-line client (e.g. that launches a new - * process for each operation), or a remote launcher like a JMX console. + * High level interface for operating batch jobs. * * @author Dave Syer * @author Mahmoud Ben Hassine + * @author Yejeong Ham * @since 2.0 */ -public interface JobOperator { +@SuppressWarnings("removal") +public interface JobOperator extends JobLauncher { /** - * List the {@link JobExecution JobExecutions} associated with a particular - * {@link JobInstance}, in reverse order of creation (and therefore usually of - * execution). - * @param instanceId the id of a {@link JobInstance} - * @return the id values of all the {@link JobExecution JobExecutions} associated with - * this instance - * @throws NoSuchJobInstanceException if the {@link JobInstance} associated with the - * {@code instanceId} cannot be found. - */ - List getExecutions(long instanceId) throws NoSuchJobInstanceException; - - /** - * List the {@link JobInstance JobInstances} for a given job name, in reverse order of - * creation (and therefore usually of first execution). - * @param jobName the job name that all the instances have - * @param start the start index of the instances - * @param count the maximum number of values to return - * @return the id values of the {@link JobInstance JobInstances} - * @throws NoSuchJobException is thrown if no {@link JobInstance}s for the jobName - * exist. - */ - List getJobInstances(String jobName, int start, int count) throws NoSuchJobException; - - /** - * @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(); - } - - /** - * Get the id values of all the running {@link JobExecution JobExecutions} with the - * given job name. - * @param jobName the name of the job to search under - * @return the id values of the running {@link JobExecution} instances - * @throws NoSuchJobException if there are no {@link JobExecution JobExecutions} with - * that job name - */ - Set getRunningExecutions(String jobName) throws NoSuchJobException; - - /** - * Get the {@link JobParameters} as a human readable String (new line separated - * key=value pairs). - * @param executionId the id of an existing {@link JobExecution} - * @return the job parameters that were used to launch the associated instance - * @throws NoSuchJobExecutionException if the id was not associated with any - * {@link JobExecution} + * List the available job names that can be launched with + * {@link #start(String, Properties)}. + * @return a set of job names + * @deprecated since 6.0 in favor of {@link JobRegistry#getJobNames()}. Scheduled for + * removal in 6.2 or later. */ - String getParameters(long executionId) throws NoSuchJobExecutionException; + @Deprecated(since = "6.0", forRemoval = true) + Set getJobNames(); /** * Start a new instance of a job with the parameters specified. @@ -111,12 +65,38 @@ default JobInstance getJobInstance(String jobName, JobParameters jobParameters) * parameters already exists * @throws JobParametersInvalidException thrown if any of the job parameters are * invalid. + * @deprecated since 6.0 in favor of {@link #start(Job, JobParameters)}. Scheduled for + * removal in 6.2 or later. */ + @Deprecated(since = "6.0", forRemoval = true) default Long start(String jobName, Properties parameters) throws NoSuchJobException, JobInstanceAlreadyExistsException, JobParametersInvalidException { throw new UnsupportedOperationException(); } + /** + * Start a new instance of a job with the specified parameters. If the job defines a + * {@link JobParametersIncrementer}, then the incrementer will be used to calculate + * the next parameters in the sequence and the provided parameters will be ignored. + * @param job the {@link Job} to start + * @param jobParameters the {@link JobParameters} to start the job with + * @return the {@link JobExecution} that was started + * @throws NoSuchJobException if the given {@link Job} is not registered + * @throws JobParametersInvalidException thrown if any of the job parameters are + * @throws JobExecutionAlreadyRunningException if the JobInstance identified by the + * properties already has an execution running. invalid. + * @throws JobRestartException if the execution would be a re-start, but a re-start is + * either not allowed or not needed. + * @throws JobInstanceAlreadyCompleteException if the job has been run before with the + * same parameters and completed successfully + * @throws IllegalArgumentException if the job or job parameters are null. + */ + default JobExecution start(Job job, JobParameters jobParameters) + throws NoSuchJobException, JobInstanceAlreadyCompleteException, JobExecutionAlreadyRunningException, + JobRestartException, JobParametersInvalidException { + throw new UnsupportedOperationException(); + } + /** * Restart a failed or stopped {@link JobExecution}. Fails with an exception if the id * provided does not exist or corresponds to a {@link JobInstance} that in normal @@ -132,10 +112,32 @@ default Long start(String jobName, Properties parameters) * @throws JobRestartException if there is a non-specific error with the restart (e.g. * corrupt or inconsistent restart data) * @throws JobParametersInvalidException if the parameters are not valid for this job + * @deprecated since 6.0 in favor of {@link #restart(JobExecution)}. Scheduled for + * removal in 6.2 or later. */ + @Deprecated(since = "6.0", forRemoval = true) Long restart(long executionId) throws JobInstanceAlreadyCompleteException, NoSuchJobExecutionException, NoSuchJobException, JobRestartException, JobParametersInvalidException; + /** + * Restart a failed or stopped {@link JobExecution}. Fails with an exception if the + * execution provided does not exist or corresponds to a {@link JobInstance} that in + * normal circumstances already completed successfully. + * @param jobExecution the failed or stopped {@link JobExecution} to restart + * @return the {@link JobExecution} that was started + * @throws JobInstanceAlreadyCompleteException if the job was already successfully + * completed + * @throws NoSuchJobExecutionException if the id was not associated with any + * {@link JobExecution} + * @throws NoSuchJobException if the {@link JobExecution} was found, but its + * corresponding {@link Job} is no longer available for launching + * @throws JobRestartException if there is a non-specific error with the restart (e.g. + * corrupt or inconsistent restart data) + * @throws JobParametersInvalidException if the parameters are not valid for this job + */ + JobExecution restart(JobExecution jobExecution) throws JobInstanceAlreadyCompleteException, + NoSuchJobExecutionException, NoSuchJobException, JobRestartException, JobParametersInvalidException; + /** * Launch the next in a sequence of {@link JobInstance} determined by the * {@link JobParametersIncrementer} attached to the specified job. If the previous @@ -160,11 +162,37 @@ Long restart(long executionId) throws JobInstanceAlreadyCompleteException, NoSuc * that is already executing. * @throws JobInstanceAlreadyCompleteException thrown if attempting to restart a * completed job. + * @deprecated since 6.0 in favor of {@link #startNextInstance(Job)}. Scheduled for + * removal in 6.2 or later. */ + @Deprecated(since = "6.0", forRemoval = true) Long startNextInstance(String jobName) throws NoSuchJobException, JobParametersNotFoundException, JobRestartException, JobExecutionAlreadyRunningException, JobInstanceAlreadyCompleteException, UnexpectedJobExecutionException, JobParametersInvalidException; + /** + * Launch the next in a sequence of {@link JobInstance} determined by the + * {@link JobParametersIncrementer} attached to the specified job. If the previous + * instance is still in a failed state, this method should still create a new instance + * and run it with different parameters (as long as the + * {@link JobParametersIncrementer} is working).
+ *
+ * + * The last three exception described below should be extremely unlikely, but cannot + * be ruled out entirely. It points to some other thread or process trying to use this + * method (or a similar one) at the same time. + * @param job the job to launch + * @return the {@link JobExecution} created when the job is launched + * @throws UnexpectedJobExecutionException if an unexpected condition arises + * @throws JobRestartException thrown if a job is restarted illegally. + * @throws JobExecutionAlreadyRunningException thrown if attempting to restart a job + * that is already executing. + * @throws JobInstanceAlreadyCompleteException thrown if attempting to restart a + * completed job. + */ + JobExecution startNextInstance(Job job) throws JobRestartException, JobExecutionAlreadyRunningException, + JobInstanceAlreadyCompleteException, UnexpectedJobExecutionException; + /** * Send a stop signal to the {@link JobExecution} with the supplied id. The signal is * successfully sent if this method returns true, but that doesn't mean that the job @@ -176,9 +204,137 @@ Long startNextInstance(String jobName) throws NoSuchJobException, JobParametersN * supplied * @throws JobExecutionNotRunningException if the {@link JobExecution} is not running * (so cannot be stopped) + * @deprecated since 6.0 in favor of {@link #stop(JobExecution)}. Scheduled for + * removal in 6.2 or later. */ + @Deprecated(since = "6.0", forRemoval = true) boolean stop(long executionId) throws NoSuchJobExecutionException, JobExecutionNotRunningException; + /** + * Send a stop signal to the supplied {@link JobExecution}. The signal is successfully + * sent if this method returns true, but that doesn't mean that the job has stopped. + * The only way to be sure of that is to poll the job execution status. + * @param jobExecution the running {@link JobExecution} + * @return true if the message was successfully sent (does not guarantee that the job + * has stopped) + * @throws JobExecutionNotRunningException if the supplied {@link JobExecution} is not + * running (so cannot be stopped) + */ + boolean stop(JobExecution jobExecution) throws JobExecutionNotRunningException; + + /** + * Mark the {@link JobExecution} as ABANDONED. If a stop signal is ignored because the + * process died this is the best way to mark a job as finished with (as opposed to + * STOPPED). An abandoned job execution cannot be restarted by the framework. + * @param jobExecutionId the job execution id to abort + * @return the {@link JobExecution} that was aborted + * @throws NoSuchJobExecutionException thrown if there is no job execution for the + * jobExecutionId. + * @throws JobExecutionAlreadyRunningException if the job is running (it should be + * stopped first) + * @deprecated since 6.0 in favor of {@link #abandon(JobExecution)}. Scheduled for + * removal in 6.2 or later. + */ + @Deprecated(since = "6.0", forRemoval = true) + JobExecution abandon(long jobExecutionId) throws NoSuchJobExecutionException, JobExecutionAlreadyRunningException; + + /** + * Mark the {@link JobExecution} as ABANDONED. If a stop signal is ignored because the + * process died this is the best way to mark a job as finished with (as opposed to + * STOPPED). An abandoned job execution cannot be restarted by the framework. + * @param jobExecution the job execution to abort + * @return the {@link JobExecution} that was aborted + * @throws JobExecutionAlreadyRunningException if the job execution is running (it + * should be stopped first) + */ + JobExecution abandon(JobExecution jobExecution) throws JobExecutionAlreadyRunningException; + + /** + * Marks the given {@link JobExecution} as {@code FAILED} when it is stuck in a + * {@code STARTED} state due to an abrupt shutdown or failure, in order to make it + * restartable. This operation makes a previously non-restartable execution eligible + * for restart by updating its execution context with the flag {@code recovered=true}. + * @param jobExecution the {@link JobExecution} to recover + * @return the {@link JobExecution} after it has been marked as recovered + * @since 6.0 + */ + JobExecution recover(JobExecution jobExecution); + + /** + * List the {@link JobExecution JobExecutions} associated with a particular + * {@link JobInstance}, in reverse order of creation (and therefore usually of + * execution). + * @param instanceId the id of a {@link JobInstance} + * @return the id values of all the {@link JobExecution JobExecutions} associated with + * this instance + * @throws NoSuchJobInstanceException if the {@link JobInstance} associated with the + * {@code instanceId} cannot be found. + * @deprecated Since 6.0 in favor of + * {@link org.springframework.batch.core.repository.JobRepository#getJobExecutions(JobInstance)}. + * Scheduled for removal in 6.2 or later. + */ + @Deprecated(since = "6.0", forRemoval = true) + List getExecutions(long instanceId) throws NoSuchJobInstanceException; + + /** + * List the {@link JobInstance JobInstances} for a given job name, in reverse order of + * creation (and therefore usually of first execution). + * @param jobName the job name that all the instances have + * @param start the start index of the instances + * @param count the maximum number of values to return + * @return the id values of the {@link JobInstance JobInstances} + * @throws NoSuchJobException is thrown if no {@link JobInstance}s for the jobName + * exist. + * @deprecated Since 6.0 in favor of + * {@link org.springframework.batch.core.repository.JobRepository#getJobInstances(String, int, int)}. + * Scheduled for removal in 6.2 or later. + */ + @Deprecated(since = "6.0", forRemoval = true) + List getJobInstances(String jobName, int start, int count) throws NoSuchJobException; + + /** + * @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}. + * @deprecated Since 6.0 in favor of + * {@link org.springframework.batch.core.repository.JobRepository#getJobInstance(String, JobParameters)}. + * Scheduled for removal in 6.2 or later. + */ + @Deprecated(since = "6.0", forRemoval = true) + @Nullable + default JobInstance getJobInstance(String jobName, JobParameters jobParameters) { + throw new UnsupportedOperationException(); + } + + /** + * Get the id values of all the running {@link JobExecution JobExecutions} with the + * given job name. + * @param jobName the name of the job to search under + * @return the id values of the running {@link JobExecution} instances + * @throws NoSuchJobException if there are no {@link JobExecution JobExecutions} with + * that job name + * @deprecated Since 6.0 in favor of + * {@link org.springframework.batch.core.repository.JobRepository#findRunningJobExecutions(String)}. + * Scheduled for removal in 6.2 or later. + */ + @Deprecated(since = "6.0", forRemoval = true) + Set getRunningExecutions(String jobName) throws NoSuchJobException; + + /** + * Get the {@link JobParameters} as a human readable String (new line separated + * key=value pairs). + * @param executionId the id of an existing {@link JobExecution} + * @return the job parameters that were used to launch the associated instance + * @throws NoSuchJobExecutionException if the id was not associated with any + * {@link JobExecution} + * @deprecated Since 6.0 in favor of the getJobParameters() method of + * {@link org.springframework.batch.core.repository.JobRepository#getJobExecution(Long)}. + * Scheduled for removal in 6.2 or later. + */ + @Deprecated(since = "6.0", forRemoval = true) + String getParameters(long executionId) throws NoSuchJobExecutionException; + /** * Summarise the {@link JobExecution} with the supplied id, giving details of status, * start and end times etc. @@ -186,7 +342,11 @@ Long startNextInstance(String jobName) throws NoSuchJobException, JobParametersN * @return a String summarising the state of the job execution * @throws NoSuchJobExecutionException if there is no {@link JobExecution} with the * supplied id + * @deprecated Since 6.0 in favor of the toString() method of + * {@link org.springframework.batch.core.repository.JobRepository#getJobExecution(Long)}. + * Scheduled for removal in 6.2 or later. */ + @Deprecated(since = "6.0", forRemoval = true) String getSummary(long executionId) throws NoSuchJobExecutionException; /** @@ -196,27 +356,11 @@ Long startNextInstance(String jobName) throws NoSuchJobException, JobParametersN * @return a map of step execution id to String summarising the state of the execution * @throws NoSuchJobExecutionException if there is no {@link JobExecution} with the * supplied id + * @deprecated Since 6.0 in favor of the getStepExecutions() method of + * {@link org.springframework.batch.core.repository.JobRepository#getJobExecution(Long)}. + * Scheduled for removal in 6.2 or later. */ + @Deprecated(since = "6.0", forRemoval = true) Map getStepExecutionSummaries(long executionId) throws NoSuchJobExecutionException; - /** - * List the available job names that can be launched with - * {@link #start(String, Properties)}. - * @return a set of job names - */ - Set getJobNames(); - - /** - * Mark the {@link JobExecution} as ABANDONED. If a stop signal is ignored because the - * process died this is the best way to mark a job as finished with (as opposed to - * STOPPED). An abandoned job execution cannot be restarted by the framework. - * @param jobExecutionId the job execution id to abort - * @return the {@link JobExecution} that was aborted - * @throws NoSuchJobExecutionException thrown if there is no job execution for the - * jobExecutionId. - * @throws JobExecutionAlreadyRunningException if the job is running (it should be - * stopped first) - */ - JobExecution abandon(long jobExecutionId) throws NoSuchJobExecutionException, JobExecutionAlreadyRunningException; - } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/JobParametersNotFoundException.java b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/JobParametersNotFoundException.java index 8ff1f36633..cc2db0986c 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/JobParametersNotFoundException.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/JobParametersNotFoundException.java @@ -15,8 +15,8 @@ */ package org.springframework.batch.core.launch; -import org.springframework.batch.core.JobExecutionException; -import org.springframework.batch.core.JobParametersIncrementer; +import org.springframework.batch.core.job.JobExecutionException; +import org.springframework.batch.core.job.parameters.JobParametersIncrementer; /** * Checked exception to indicate that a required {@link JobParametersIncrementer} is not diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/NoSuchJobException.java b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/NoSuchJobException.java index 8131ecfa6a..d7d053a660 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/NoSuchJobException.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/NoSuchJobException.java @@ -15,8 +15,8 @@ */ package org.springframework.batch.core.launch; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobExecutionException; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecutionException; /** * Checked exception to indicate that a required {@link Job} is not available. diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/NoSuchJobExecutionException.java b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/NoSuchJobExecutionException.java index 7135d5cfe9..13f17c7bbf 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/NoSuchJobExecutionException.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/NoSuchJobExecutionException.java @@ -15,8 +15,8 @@ */ package org.springframework.batch.core.launch; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobExecutionException; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobExecutionException; /** * Checked exception to indicate that a required {@link JobExecution} is not available. diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/NoSuchJobInstanceException.java b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/NoSuchJobInstanceException.java index 1f6a48c9fc..29d4a4ba07 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/NoSuchJobInstanceException.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/NoSuchJobInstanceException.java @@ -15,8 +15,8 @@ */ package org.springframework.batch.core.launch; -import org.springframework.batch.core.JobExecutionException; -import org.springframework.batch.core.JobInstance; +import org.springframework.batch.core.job.JobExecutionException; +import org.springframework.batch.core.job.JobInstance; /** * Exception that signals that the user requested an operation on a non-existent diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/CommandLineJobOperator.java b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/CommandLineJobOperator.java new file mode 100644 index 0000000000..0ef3b38733 --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/CommandLineJobOperator.java @@ -0,0 +1,354 @@ +/* + * 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.core.launch.support; + +import java.util.Arrays; +import java.util.List; +import java.util.Properties; + +import org.springframework.batch.core.configuration.JobRegistry; +import org.springframework.batch.core.converter.DefaultJobParametersConverter; +import org.springframework.batch.core.converter.JobParametersConverter; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.launch.JobOperator; +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.beans.BeansException; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.core.log.LogAccessor; + +import static org.springframework.batch.core.launch.support.ExitCodeMapper.JVM_EXITCODE_COMPLETED; +import static org.springframework.batch.core.launch.support.ExitCodeMapper.JVM_EXITCODE_GENERIC_ERROR; + +/** + * A command-line utility to operate Spring Batch jobs using the {@link JobOperator}. It + * allows starting, stopping, restarting, abandoning and recovering jobs from the command + * line. + *

+ * This utility requires a Spring application context to be set up with the necessary + * batch infrastructure, including a {@link JobOperator}, a {@link JobRepository}, and a + * {@link JobRegistry} populated with the jobs to operate. It can also be configured with + * a custom {@link ExitCodeMapper} and a {@link JobParametersConverter}. + * + *

+ * This class is designed to be run from the command line, and the Javadoc of the + * {@link #main(String[])} method explains the various operations and exit codes. + * + * @author Mahmoud Ben Hassine + * @author Yejeong Ham + * @since 6.0 + */ +public class CommandLineJobOperator { + + private static final LogAccessor logger = new LogAccessor(CommandLineJobOperator.class); + + private final JobOperator jobOperator; + + private final JobRepository jobRepository; + + private final JobRegistry jobRegistry; + + private ExitCodeMapper exitCodeMapper = new SimpleJvmExitCodeMapper(); + + private JobParametersConverter jobParametersConverter = new DefaultJobParametersConverter(); + + /** + * Create a new {@link CommandLineJobOperator} instance. + * @param jobOperator the {@link JobOperator} to use for job operations + * @param jobRepository the {@link JobRepository} to use for job meta-data management + * @param jobRegistry the {@link JobRegistry} to use for job lookup by name + */ + public CommandLineJobOperator(JobOperator jobOperator, JobRepository jobRepository, JobRegistry jobRegistry) { + this.jobOperator = jobOperator; + this.jobRepository = jobRepository; + this.jobRegistry = jobRegistry; + } + + /** + * Set the {@link JobParametersConverter} to use for converting command line + * parameters to {@link JobParameters}. Defaults to a + * {@link DefaultJobParametersConverter}. + * @param jobParametersConverter the job parameters converter to set + */ + public void setJobParametersConverter(JobParametersConverter jobParametersConverter) { + this.jobParametersConverter = jobParametersConverter; + } + + /** + * Set the {@link ExitCodeMapper} to use for converting job exit codes to JVM exit + * codes. Defaults to a {@link SimpleJvmExitCodeMapper}. + * @param exitCodeMapper the exit code mapper to set + */ + public void setExitCodeMapper(ExitCodeMapper exitCodeMapper) { + this.exitCodeMapper = exitCodeMapper; + } + + /** + * Start a job with the given name and parameters. + * @param jobName the name of the job to start + * @param parameters the parameters for the job + * @return the exit code of the job execution, or JVM_EXITCODE_GENERIC_ERROR if an + * error occurs + */ + public int start(String jobName, Properties parameters) { + logger.info(() -> "Starting job with name '" + jobName + "' and parameters: " + parameters); + try { + Job job = this.jobRegistry.getJob(jobName); + JobParameters jobParameters = this.jobParametersConverter.getJobParameters(parameters); + JobExecution jobExecution = this.jobOperator.start(job, jobParameters); + return this.exitCodeMapper.intValue(jobExecution.getExitStatus().getExitCode()); + } + catch (Exception e) { + return JVM_EXITCODE_GENERIC_ERROR; + } + } + + /** + * Start the next instance of the job with the given name. + * @param jobName the name of the job to start + * @return the exit code of the job execution, or JVM_EXITCODE_GENERIC_ERROR if an + * error occurs + */ + public int startNextInstance(String jobName) { + logger.info(() -> "Starting next instance of job '" + jobName + "'"); + try { + Job job = this.jobRegistry.getJob(jobName); + JobExecution jobExecution = this.jobOperator.startNextInstance(job); + return this.exitCodeMapper.intValue(jobExecution.getExitStatus().getExitCode()); + } + catch (Exception e) { + return JVM_EXITCODE_GENERIC_ERROR; + } + } + + /** + * Send a stop signal to the job execution with given ID. The signal is successfully + * sent if this method returns JVM_EXITCODE_COMPLETED, but that doesn't mean that the + * job has stopped. The only way to be sure of that is to poll the job execution + * status. + * @param jobExecutionId the ID of the job execution to stop + * @return JVM_EXITCODE_COMPLETED if the stop signal was successfully sent to the job + * execution, JVM_EXITCODE_GENERIC_ERROR otherwise + * @see JobOperator#stop(JobExecution) + */ + public int stop(long jobExecutionId) { + logger.info(() -> "Stopping job execution with ID: " + jobExecutionId); + try { + JobExecution jobExecution = this.jobRepository.getJobExecution(jobExecutionId); + if (jobExecution == null) { + logger.error(() -> "No job execution found with ID: " + jobExecutionId); + return JVM_EXITCODE_GENERIC_ERROR; + } + boolean stopSignalSent = this.jobOperator.stop(jobExecution); + return stopSignalSent ? JVM_EXITCODE_COMPLETED : JVM_EXITCODE_GENERIC_ERROR; + } + catch (Exception e) { + return JVM_EXITCODE_GENERIC_ERROR; + } + } + + /** + * Restart the job execution with the given ID. + * @param jobExecutionId the ID of the job execution to restart + * @return the exit code of the restarted job execution, or JVM_EXITCODE_GENERIC_ERROR + * if an error occurs + */ + public int restart(long jobExecutionId) { + logger.info(() -> "Restarting job execution with ID: " + jobExecutionId); + try { + JobExecution jobExecution = this.jobRepository.getJobExecution(jobExecutionId); + if (jobExecution == null) { + logger.error(() -> "No job execution found with ID: " + jobExecutionId); + return JVM_EXITCODE_GENERIC_ERROR; + } + JobExecution restartedExecution = this.jobOperator.restart(jobExecution); + return this.exitCodeMapper.intValue(restartedExecution.getExitStatus().getExitCode()); + } + catch (Exception e) { + return JVM_EXITCODE_GENERIC_ERROR; + } + } + + /** + * Abandon the job execution with the given ID. + * @param jobExecutionId the ID of the job execution to abandon + * @return the exit code of the abandoned job execution, or JVM_EXITCODE_GENERIC_ERROR + * if an error occurs + */ + public int abandon(long jobExecutionId) { + logger.info(() -> "Abandoning job execution with ID: " + jobExecutionId); + try { + JobExecution jobExecution = this.jobRepository.getJobExecution(jobExecutionId); + if (jobExecution == null) { + logger.error(() -> "No job execution found with ID: " + jobExecutionId); + return JVM_EXITCODE_GENERIC_ERROR; + } + JobExecution abandonedExecution = this.jobOperator.abandon(jobExecution); + return this.exitCodeMapper.intValue(abandonedExecution.getExitStatus().getExitCode()); + } + catch (Exception e) { + return JVM_EXITCODE_GENERIC_ERROR; + } + } + + /** + * Recover the job execution with the given ID that is stuck in a {@code STARTED} + * state due to an abrupt shutdown or failure, making it eligible for restart. + * @param jobExecutionId the ID of the job execution to recover + * @return the exit code of the recovered job execution, or JVM_EXITCODE_GENERIC_ERROR + * if an error occurs + */ + public int recover(long jobExecutionId) { + logger.info(() -> "Recovering job execution with ID: " + jobExecutionId); + try { + JobExecution jobExecution = this.jobRepository.getJobExecution(jobExecutionId); + if (jobExecution == null) { + logger.error(() -> "No job execution found with ID: " + jobExecutionId); + return JVM_EXITCODE_GENERIC_ERROR; + } + JobExecution recoveredExecution = this.jobOperator.recover(jobExecution); + return this.exitCodeMapper.intValue(recoveredExecution.getExitStatus().getExitCode()); + } + catch (Exception e) { + return JVM_EXITCODE_GENERIC_ERROR; + } + } + + // @formatter:off + /** + * Main method to operate jobs from the command line. + *

+ * Usage: + * + * java org.springframework.batch.core.launch.support.CommandLineJobOperator \ + * fully.qualified.name.of.JobConfigurationClass \ + * operation \ + * parameters + * + *

+ * where operation is one of the following: + *

    + *
  • start jobName [jobParameters]
  • + *
  • startNextInstance jobName
  • + *
  • restart jobExecutionId
  • + *
  • stop jobExecutionId
  • + *
  • abandon jobExecutionId
  • + *
  • recover jobExecutionId
  • + *
+ *

+ * and jobParameters are key-value pairs in the form name=value,type,identifying. + *

+ * Exit status: + *

    + *
  • 0: Job completed successfully
  • + *
  • 1: Job failed to (re)start or an error occurred
  • + *
  • 2: Job configuration class not found
  • + *
+ */ + // @formatter:on + public static void main(String[] args) { + if (args.length < 3) { + String usage = """ + Usage: java %s + where operation is one of the following: + - start jobName [jobParameters] + - startNextInstance jobName + - restart jobExecutionId + - stop jobExecutionId + - abandon jobExecutionId + - recover jobExecutionId + and jobParameters are key-value pairs in the form name=value,type,identifying. + """; + System.err.printf(String.format(usage, CommandLineJobOperator.class.getName())); + System.exit(1); + } + + String jobConfigurationClassName = args[0]; + String operation = args[1]; + + ConfigurableApplicationContext context = null; + try { + Class jobConfigurationClass = Class.forName(jobConfigurationClassName); + context = new AnnotationConfigApplicationContext(jobConfigurationClass); + } + catch (ClassNotFoundException classNotFoundException) { + System.err.println("Job configuration class not found: " + jobConfigurationClassName); + System.exit(2); + } + + JobOperator jobOperator = null; + JobRepository jobRepository = null; + JobRegistry jobRegistry = null; + try { + jobOperator = context.getBean(JobOperator.class); + jobRepository = context.getBean(JobRepository.class); + jobRegistry = context.getBean(JobRegistry.class); + } + catch (BeansException e) { + System.err.println("A required bean was not found in the application context: " + e.getMessage()); + System.exit(1); + } + CommandLineJobOperator operator = new CommandLineJobOperator(jobOperator, jobRepository, jobRegistry); + + int exitCode; + String jobName; + long jobExecutionId; + switch (operation) { + case "start": + jobName = args[2]; + List jobParameters = Arrays.asList(args).subList(3, args.length); + exitCode = operator.start(jobName, parse(jobParameters)); + break; + case "startNextInstance": + jobName = args[2]; + exitCode = operator.startNextInstance(jobName); + break; + case "stop": + jobExecutionId = Long.parseLong(args[2]); + exitCode = operator.stop(jobExecutionId); + break; + case "restart": + jobExecutionId = Long.parseLong(args[2]); + exitCode = operator.restart(jobExecutionId); + break; + case "abandon": + jobExecutionId = Long.parseLong(args[2]); + exitCode = operator.abandon(jobExecutionId); + break; + case "recover": + jobExecutionId = Long.parseLong(args[2]); + exitCode = operator.recover(jobExecutionId); + break; + default: + System.err.println("Unknown operation: " + operation); + exitCode = JVM_EXITCODE_GENERIC_ERROR; + } + + System.exit(exitCode); + } + + private static Properties parse(List jobParameters) { + Properties properties = new Properties(); + for (String jobParameter : jobParameters) { + String[] tokens = jobParameter.split("="); + properties.put(tokens[0], tokens[1]); + } + return properties; + } + +} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/CommandLineJobRunner.java b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/CommandLineJobRunner.java index 34bdf928b0..d4071ba3cb 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/CommandLineJobRunner.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/CommandLineJobRunner.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. @@ -31,21 +31,17 @@ import org.springframework.batch.core.BatchStatus; import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobInstance; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.JobParametersBuilder; -import org.springframework.batch.core.JobParametersIncrementer; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobInstance; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersIncrementer; import org.springframework.batch.core.configuration.JobLocator; +import org.springframework.batch.core.configuration.JobRegistry; import org.springframework.batch.core.converter.DefaultJobParametersConverter; import org.springframework.batch.core.converter.JobParametersConverter; -import org.springframework.batch.core.explore.JobExplorer; -import org.springframework.batch.core.launch.JobExecutionNotFailedException; -import org.springframework.batch.core.launch.JobExecutionNotRunningException; -import org.springframework.batch.core.launch.JobExecutionNotStoppedException; -import org.springframework.batch.core.launch.JobLauncher; -import org.springframework.batch.core.launch.NoSuchJobException; +import org.springframework.batch.core.launch.*; +import org.springframework.batch.core.repository.explore.JobExplorer; import org.springframework.batch.core.repository.JobRepository; import org.springframework.beans.factory.BeanDefinitionStoreException; import org.springframework.beans.factory.config.AutowireCapableBeanFactory; @@ -74,7 +70,7 @@ * can be used to load the job and its context from a single location. All dependencies of * the launcher will then be satisfied by autowiring by type from the combined application * context. Default values are provided for all fields except the {@link JobLauncher} and - * {@link JobLocator} . Therefore, if autowiring fails to set it (it should be noted that + * {@link JobRegistry} . Therefore, if autowiring fails to set it (it should be noted that * dependency checking is disabled because most of the fields have default values and thus * don't require dependencies to be fulfilled via autowiring) then an exception will be * thrown. It should also be noted that even if an exception is thrown by this class, it @@ -163,8 +159,8 @@ * {@link BeanDefinitionStoreException} will be thrown. The same exception will also be * thrown if there is more than one present. Assuming the JobLauncher has been set * correctly, the jobIdentifier argument will be used to obtain an actual {@link Job}. If - * a {@link JobLocator} has been set, then it will be used, if not the beanFactory will be - * asked, using the jobIdentifier as the bean id. + * a {@link JobRegistry} has been set, then it will be used, if not the beanFactory will + * be asked, using the jobIdentifier as the bean id. *

* * @author Dave Syer @@ -172,7 +168,10 @@ * @author Mahmoud Ben Hassine * @author Minsoo Kim * @since 1.0 + * @deprecated since 6.0 in favor of {@link CommandLineJobOperator}. Scheduled for removal + * in 6.2 or later. */ +@Deprecated(since = "6.0", forRemoval = true) public class CommandLineJobRunner { protected static final Log logger = LogFactory.getLog(CommandLineJobRunner.class); @@ -183,6 +182,8 @@ public class CommandLineJobRunner { private JobLocator jobLocator; + private JobRegistry jobRegistry; + private static SystemExiter systemExiter = new JvmSystemExiter(); private static String message = ""; @@ -274,11 +275,22 @@ public void exit(int status) { /** * {@link JobLocator} to find a job to run. * @param jobLocator a {@link JobLocator} + * @deprecated since 6.0 in favor of {{@link #setJobRegistry(JobRegistry)}}. Scheduled + * for removal in 6.2 or later. */ + @Deprecated(since = "6.0", forRemoval = true) public void setJobLocator(JobLocator jobLocator) { this.jobLocator = jobLocator; } + /** + * Set the {@link JobRegistry}. + * @param jobRegistry a {@link JobRegistry} + */ + public void setJobRegistry(JobRegistry jobRegistry) { + this.jobRegistry = jobRegistry; + } + /* * Start a job by obtaining a combined classpath using the job launcher and job paths. * If a JobLocator has been set, then use it to obtain an actual job, if not ask the @@ -348,20 +360,35 @@ int start(String jobPath, String jobIdentifier, String[] parameters, Set } Job job = null; - if (jobLocator != null) { + if (jobRegistry != null) { try { - job = jobLocator.getJob(jobName); + job = jobRegistry.getJob(jobName); } - catch (NoSuchJobException e) { + catch (NoSuchJobException ignored) { } } if (job == null) { - job = (Job) context.getBean(jobName); + job = context.getBean(jobName, Job.class); } if (opts.contains("-next")) { - jobParameters = new JobParametersBuilder(jobParameters, jobExplorer).getNextJobParameters(job) - .toJobParameters(); + JobInstance lastInstance = jobRepository.getLastJobInstance(jobName); + JobParametersIncrementer incrementer = job.getJobParametersIncrementer(); + if (lastInstance == null) { + // Start from a completely clean sheet + jobParameters = incrementer.getNext(new JobParameters()); + } + else { + JobExecution previousExecution = jobRepository.getLastJobExecution(lastInstance); + if (previousExecution == null) { + // Normally this will not happen - an instance exists with no + // executions + jobParameters = incrementer.getNext(new JobParameters()); + } + else { + jobParameters = incrementer.getNext(previousExecution.getJobParameters()); + } + } } JobExecution jobExecution = launcher.run(job, jobParameters); diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/DataFieldMaxValueJobParametersIncrementer.java b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/DataFieldMaxValueJobParametersIncrementer.java index 5cce9c53f9..759aa4400e 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/DataFieldMaxValueJobParametersIncrementer.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/DataFieldMaxValueJobParametersIncrementer.java @@ -15,9 +15,9 @@ */ package org.springframework.batch.core.launch.support; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.JobParametersBuilder; -import org.springframework.batch.core.JobParametersIncrementer; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; +import org.springframework.batch.core.job.parameters.JobParametersIncrementer; import org.springframework.jdbc.support.incrementer.DataFieldMaxValueIncrementer; import org.springframework.lang.Nullable; import org.springframework.util.Assert; 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..c30a35eaca 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 @@ -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. @@ -15,23 +15,34 @@ */ package org.springframework.batch.core.launch.support; -import java.util.Properties; +import java.lang.reflect.Method; +import io.micrometer.observation.ObservationRegistry; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.aop.framework.ProxyFactory; +import org.springframework.batch.core.configuration.BatchConfigurationException; +import org.springframework.batch.core.configuration.DuplicateJobException; import org.springframework.batch.core.configuration.JobRegistry; +import org.springframework.batch.core.configuration.support.MapJobRegistry; import org.springframework.batch.core.converter.DefaultJobParametersConverter; import org.springframework.batch.core.converter.JobParametersConverter; -import org.springframework.batch.core.explore.JobExplorer; -import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; import org.springframework.batch.core.launch.JobOperator; import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.support.transaction.ResourcelessTransactionManager; +import org.springframework.beans.BeansException; import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.InitializingBean; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.core.task.SyncTaskExecutor; +import org.springframework.core.task.TaskExecutor; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionManager; -import org.springframework.transaction.annotation.Isolation; -import org.springframework.transaction.annotation.Propagation; -import org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource; +import org.springframework.transaction.interceptor.DefaultTransactionAttribute; +import org.springframework.transaction.interceptor.MethodMapTransactionAttributeSource; import org.springframework.transaction.interceptor.TransactionAttributeSource; import org.springframework.transaction.interceptor.TransactionInterceptor; import org.springframework.util.Assert; @@ -41,15 +52,15 @@ * {@link JobOperator}. * * @see JobOperator - * @see SimpleJobOperator + * @see TaskExecutorJobOperator * @author Mahmoud Ben Hassine * @since 5.0 */ -public class JobOperatorFactoryBean implements FactoryBean, InitializingBean { +public class JobOperatorFactoryBean implements FactoryBean, ApplicationContextAware, InitializingBean { - private static final String TRANSACTION_ISOLATION_LEVEL_PREFIX = "ISOLATION_"; + protected static final Log logger = LogFactory.getLog(JobOperatorFactoryBean.class); - private static final String TRANSACTION_PROPAGATION_PREFIX = "PROPAGATION_"; + private ApplicationContext applicationContext; private PlatformTransactionManager transactionManager; @@ -57,33 +68,54 @@ public class JobOperatorFactoryBean implements FactoryBean, Initial private JobRegistry jobRegistry; - private JobLauncher jobLauncher; - private JobRepository jobRepository; - private JobExplorer jobExplorer; - private JobParametersConverter jobParametersConverter = new DefaultJobParametersConverter(); + private TaskExecutor taskExecutor; + + private ObservationRegistry observationRegistry; + private final ProxyFactory proxyFactory = new ProxyFactory(); + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.applicationContext = applicationContext; + } + @Override 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.jobRegistry == null) { + this.jobRegistry = new MapJobRegistry(); + populateJobRegistry(); + logger.info( + "No JobRegistry has been set, defaulting to a MapJobRegistry populated with jobs defined in the application context."); + } + if (this.transactionManager == null) { + this.transactionManager = new ResourcelessTransactionManager(); + logger.info("No transaction manager has been set, defaulting to ResourcelessTransactionManager."); + } + if (this.taskExecutor == null) { + logger.info("No TaskExecutor has been set, defaulting to synchronous executor."); + this.taskExecutor = new SyncTaskExecutor(); + } if (this.transactionAttributeSource == null) { - Properties transactionAttributes = new Properties(); - String transactionProperties = String.join(",", TRANSACTION_PROPAGATION_PREFIX + Propagation.REQUIRED, - TRANSACTION_ISOLATION_LEVEL_PREFIX + Isolation.DEFAULT); - transactionAttributes.setProperty("stop*", transactionProperties); - this.transactionAttributeSource = new NameMatchTransactionAttributeSource(); - ((NameMatchTransactionAttributeSource) transactionAttributeSource).setProperties(transactionAttributes); + this.transactionAttributeSource = new DefaultJobOperatorTransactionAttributeSource(); } } + private void populateJobRegistry() { + this.applicationContext.getBeansOfType(Job.class).values().forEach(job -> { + try { + jobRegistry.register(job); + } + catch (DuplicateJobException e) { + throw new BatchConfigurationException(e); + } + }); + } + /** * Setter for the job registry. * @param jobRegistry the job registry to set @@ -92,14 +124,6 @@ public void setJobRegistry(JobRegistry jobRegistry) { this.jobRegistry = jobRegistry; } - /** - * Setter for the job launcher. - * @param jobLauncher the job launcher to set - */ - public void setJobLauncher(JobLauncher jobLauncher) { - this.jobLauncher = jobLauncher; - } - /** * Setter for the job repository. * @param jobRepository the job repository to set @@ -108,22 +132,35 @@ 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 + * @deprecated since 6.0 with nor replacement. Scheduled for removal in 6.2 or later. */ + @Deprecated(since = "6.0", forRemoval = true) public void setJobParametersConverter(JobParametersConverter jobParametersConverter) { this.jobParametersConverter = jobParametersConverter; } + /** + * Set the TaskExecutor. (Optional) + * @param taskExecutor instance of {@link TaskExecutor}. + * @since 6.0 + */ + public void setTaskExecutor(TaskExecutor taskExecutor) { + this.taskExecutor = taskExecutor; + } + + /** + * Set the observation registry to use for metrics. Defaults to + * {@link ObservationRegistry#NOOP}. + * @param observationRegistry the observation registry to use + * @since 6.0 + */ + public void setObservationRegistry(ObservationRegistry observationRegistry) { + this.observationRegistry = observationRegistry; + } + /** * Setter for the transaction manager. * @param transactionManager the transaction manager to set @@ -163,15 +200,38 @@ public JobOperator getObject() throws Exception { return (JobOperator) this.proxyFactory.getProxy(getClass().getClassLoader()); } - 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); - simpleJobOperator.afterPropertiesSet(); - return simpleJobOperator; + @SuppressWarnings("removal") + private TaskExecutorJobOperator getTarget() throws Exception { + TaskExecutorJobOperator taskExecutorJobOperator = new TaskExecutorJobOperator(); + taskExecutorJobOperator.setJobRegistry(this.jobRegistry); + taskExecutorJobOperator.setJobRepository(this.jobRepository); + taskExecutorJobOperator.setTaskExecutor(this.taskExecutor); + if (this.observationRegistry != null) { + taskExecutorJobOperator.setObservationRegistry(this.observationRegistry); + } + taskExecutorJobOperator.setJobParametersConverter(this.jobParametersConverter); + taskExecutorJobOperator.afterPropertiesSet(); + return taskExecutorJobOperator; + } + + private static class DefaultJobOperatorTransactionAttributeSource extends MethodMapTransactionAttributeSource { + + public DefaultJobOperatorTransactionAttributeSource() { + DefaultTransactionAttribute transactionAttribute = new DefaultTransactionAttribute(); + try { + Method stopMethod = TaskExecutorJobOperator.class.getMethod("stop", JobExecution.class); + Method abandonMethod = TaskExecutorJobOperator.class.getMethod("abandon", JobExecution.class); + Method recoverMethod = TaskExecutorJobOperator.class.getMethod("recover", JobExecution.class); + addTransactionalMethod(stopMethod, transactionAttribute); + addTransactionalMethod(abandonMethod, transactionAttribute); + addTransactionalMethod(recoverMethod, transactionAttribute); + } + catch (NoSuchMethodException e) { + throw new IllegalStateException("Failed to initialize default transaction attributes for JobOperator", + e); + } + } + } } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/JvmSystemExiter.java b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/JvmSystemExiter.java index b0d9e855f2..7834bfab69 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/JvmSystemExiter.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/JvmSystemExiter.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. @@ -23,8 +23,9 @@ * * @author Lucas Ward * @author Dave Syer - * + * @deprecated since 6.0 with no replacement. Scheduled for removal in 6.2 or later. */ +@Deprecated(since = "6.0", forRemoval = true) public class JvmSystemExiter implements SystemExiter { /** diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/RunIdIncrementer.java b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/RunIdIncrementer.java index 824aa10363..ee2fac0417 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/RunIdIncrementer.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/RunIdIncrementer.java @@ -15,10 +15,10 @@ */ package org.springframework.batch.core.launch.support; -import org.springframework.batch.core.JobParameter; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.JobParametersBuilder; -import org.springframework.batch.core.JobParametersIncrementer; +import org.springframework.batch.core.job.parameters.JobParameter; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; +import org.springframework.batch.core.job.parameters.JobParametersIncrementer; import org.springframework.lang.Nullable; /** diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/RuntimeExceptionTranslator.java b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/RuntimeExceptionTranslator.java index cc0e3536d4..4a1bf31bf7 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/RuntimeExceptionTranslator.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/RuntimeExceptionTranslator.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. @@ -20,8 +20,10 @@ /** * @author Dave Syer - * + * @author Mahmoud Ben Hassine + * @deprecated since 6.0 with no replacement, for removal in 6.2 or later. */ +@Deprecated(since = "6.0", forRemoval = true) public class RuntimeExceptionTranslator implements MethodInterceptor { @Override 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..db0c1a351a 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 @@ -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. @@ -29,23 +29,21 @@ import org.apache.commons.logging.LogFactory; import org.springframework.batch.core.BatchStatus; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobInstance; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.JobParametersBuilder; -import org.springframework.batch.core.JobParametersInvalidException; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.UnexpectedJobExecutionException; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobInstance; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersIncrementer; +import org.springframework.batch.core.job.parameters.JobParametersInvalidException; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.step.StoppableStep; +import org.springframework.batch.core.step.StepExecution; +import org.springframework.batch.core.job.UnexpectedJobExecutionException; import org.springframework.batch.core.configuration.JobRegistry; -import org.springframework.batch.core.configuration.ListableJobLocator; import org.springframework.batch.core.converter.DefaultJobParametersConverter; import org.springframework.batch.core.converter.JobParametersConverter; -import org.springframework.batch.core.explore.JobExplorer; import org.springframework.batch.core.launch.JobExecutionNotRunningException; import org.springframework.batch.core.launch.JobInstanceAlreadyExistsException; -import org.springframework.batch.core.launch.JobLauncher; import org.springframework.batch.core.launch.JobOperator; import org.springframework.batch.core.launch.NoSuchJobException; import org.springframework.batch.core.launch.NoSuchJobExecutionException; @@ -66,12 +64,10 @@ import org.springframework.util.Assert; /** - * Simple implementation of the JobOperator interface. Due to the amount of functionality - * the implementation is combining, the following dependencies are required: + * Simple implementation of the {@link JobOperator} interface. the following dependencies + * are required: * *
    - *
  • {@link JobLauncher} - *
  • {@link JobExplorer} *
  • {@link JobRepository} *
  • {@link JobRegistry} *
@@ -84,22 +80,23 @@ * @author Lucas Ward * @author Will Schipp * @author Mahmoud Ben Hassine + * @author Andrey Litvitski + * @author Yejeong Ham + * @author Hyunsang Han * @since 2.0 + * @deprecated since 6.0 in favor of {@link TaskExecutorJobOperator}. Scheduled for + * removal in 6.2 or later. */ -public class SimpleJobOperator implements JobOperator, InitializingBean { +@SuppressWarnings("removal") +@Deprecated(since = "6.0", forRemoval = true) +public class SimpleJobOperator extends TaskExecutorJobLauncher implements JobOperator, InitializingBean { private static final String ILLEGAL_STATE_MSG = "Illegal state (only happens on a race condition): " + "%s with name=%s and parameters=%s"; - private ListableJobLocator jobRegistry; + protected JobRegistry jobRegistry; - private JobExplorer jobExplorer; - - private JobLauncher jobLauncher; - - private JobRepository jobRepository; - - private JobParametersConverter jobParametersConverter = new DefaultJobParametersConverter(); + protected JobParametersConverter jobParametersConverter = new DefaultJobParametersConverter(); private final Log logger = LogFactory.getLog(getClass()); @@ -110,124 +107,102 @@ public class SimpleJobOperator implements JobOperator, InitializingBean { */ @Override public void afterPropertiesSet() throws Exception { - Assert.state(jobLauncher != null, "JobLauncher must be provided"); + super.afterPropertiesSet(); Assert.state(jobRegistry != null, "JobLocator must be provided"); - Assert.state(jobExplorer != null, "JobExplorer must be provided"); - Assert.state(jobRepository != null, "JobRepository must be provided"); } /** * Public setter for the {@link JobParametersConverter}. * @param jobParametersConverter the {@link JobParametersConverter} to set + * @deprecated since 6.0 with no replacement. Scheduled for removal in 6.2 or later. */ + @Deprecated(since = "6.0", forRemoval = true) public void setJobParametersConverter(JobParametersConverter jobParametersConverter) { this.jobParametersConverter = jobParametersConverter; } /** - * Public setter for the {@link ListableJobLocator}. - * @param jobRegistry the {@link ListableJobLocator} to set + * Public setter for the {@link JobRegistry}. + * @param jobRegistry the {@link JobRegistry} to set */ - public void setJobRegistry(ListableJobLocator jobRegistry) { + public void setJobRegistry(JobRegistry 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; - } - - /** - * Public setter for the {@link JobLauncher}. - * @param jobLauncher the {@link JobLauncher} to set - */ - public void setJobLauncher(JobLauncher jobLauncher) { - this.jobLauncher = jobLauncher; - } - @Override - public List getExecutions(long instanceId) throws NoSuchJobInstanceException { - JobInstance jobInstance = jobExplorer.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)) { - list.add(jobExecution.getId()); + @Deprecated(since = "6.0", forRemoval = true) + public Long start(String jobName, Properties parameters) + throws NoSuchJobException, JobInstanceAlreadyExistsException, JobParametersInvalidException { + if (logger.isInfoEnabled()) { + logger.info("Checking status of job with name=" + jobName); } - return list; - } - @Override - public Set getJobNames() { - return new TreeSet<>(jobRegistry.getJobNames()); - } + JobParameters jobParameters = jobParametersConverter.getJobParameters(parameters); - @Override - public List getJobInstances(String jobName, int start, int count) throws NoSuchJobException { - List list = new ArrayList<>(); - List jobInstances = jobExplorer.getJobInstances(jobName, start, count); - for (JobInstance jobInstance : jobInstances) { - list.add(jobInstance.getId()); - } - if (list.isEmpty() && !jobRegistry.getJobNames().contains(jobName)) { - throw new NoSuchJobException("No such job (either in registry or in historical data): " + jobName); + if (jobRepository.getJobInstance(jobName, jobParameters) != null) { + throw new JobInstanceAlreadyExistsException( + String.format("Cannot start a job instance that already exists with name=%s and parameters={%s}", + jobName, parameters)); } - return list; - } - @Override - @Nullable - public JobInstance getJobInstance(String jobName, JobParameters jobParameters) { - return this.jobExplorer.getJobInstance(jobName, jobParameters); - } - - @Override - public String getParameters(long executionId) throws NoSuchJobExecutionException { - JobExecution jobExecution = findExecutionById(executionId); - - Properties properties = this.jobParametersConverter.getProperties(jobExecution.getJobParameters()); - - return PropertiesConverter.propertiesToString(properties); - } - - @Override - public Set getRunningExecutions(String jobName) throws NoSuchJobException { - Set set = new LinkedHashSet<>(); - for (JobExecution jobExecution : jobExplorer.findRunningJobExecutions(jobName)) { - set.add(jobExecution.getId()); + Job job = jobRegistry.getJob(jobName); + if (logger.isInfoEnabled()) { + logger + .info(String.format("Attempting to launch job with name=%s and parameters={%s}", jobName, parameters)); } - if (set.isEmpty() && !jobRegistry.getJobNames().contains(jobName)) { - throw new NoSuchJobException("No such job (either in registry or in historical data): " + jobName); + try { + return run(job, jobParameters).getId(); } - return set; - } - - @Override - public Map getStepExecutionSummaries(long executionId) throws NoSuchJobExecutionException { - JobExecution jobExecution = findExecutionById(executionId); - - Map map = new LinkedHashMap<>(); - for (StepExecution stepExecution : jobExecution.getStepExecutions()) { - map.put(stepExecution.getId(), stepExecution.toString()); + catch (JobExecutionAlreadyRunningException e) { + throw new UnexpectedJobExecutionException( + String.format(ILLEGAL_STATE_MSG, "job execution already running", jobName, parameters), e); } - return map; + catch (JobRestartException e) { + throw new UnexpectedJobExecutionException( + String.format(ILLEGAL_STATE_MSG, "job not restartable", jobName, parameters), e); + } + catch (JobInstanceAlreadyCompleteException e) { + throw new UnexpectedJobExecutionException( + String.format(ILLEGAL_STATE_MSG, "job already complete", jobName, parameters), e); + } + } - @Override - public String getSummary(long executionId) throws NoSuchJobExecutionException { - JobExecution jobExecution = findExecutionById(executionId); - return jobExecution.toString(); + /** + * Start a new instance of a job with the specified parameters. If the job defines a + * {@link JobParametersIncrementer}, then the incrementer will be used to calculate + * the next parameters in the sequence and the provided parameters will be ignored. + * @param job the {@link Job} to start + * @param jobParameters the {@link JobParameters} to start the job with + * @return the {@link JobExecution} that was started + * @throws NoSuchJobException if the given {@link Job} is not registered + * @throws JobParametersInvalidException thrown if any of the job parameters are + * @throws JobExecutionAlreadyRunningException if the JobInstance identified by the + * properties already has an execution running. invalid. + * @throws JobRestartException if the execution would be a re-start, but a re-start is + * either not allowed or not needed. + * @throws JobInstanceAlreadyCompleteException if the job has been run before with the + * same parameters and completed successfully + * @throws IllegalArgumentException if the job or job parameters are null. + */ + public JobExecution start(Job job, JobParameters jobParameters) + throws NoSuchJobException, JobInstanceAlreadyCompleteException, JobExecutionAlreadyRunningException, + JobRestartException, JobParametersInvalidException { + Assert.notNull(job, "The Job must not be null."); + Assert.notNull(jobParameters, "The JobParameters must not be null."); + if (job.getJobParametersIncrementer() != null) { + if (!jobParameters.isEmpty() && logger.isWarnEnabled()) { + logger.warn(String.format( + "Attempting to launch job: [%s] which defines an incrementer with additional parameters: [%s]. Additional parameters will be ignored.", + job.getName(), jobParameters)); + } + return startNextInstance(job); + } + return run(job, jobParameters); } @Override + @Deprecated(since = "6.0", forRemoval = true) public Long restart(long executionId) throws JobInstanceAlreadyCompleteException, NoSuchJobExecutionException, NoSuchJobException, JobRestartException, JobParametersInvalidException { @@ -244,7 +219,7 @@ public Long restart(long executionId) throws JobInstanceAlreadyCompleteException logger.info(String.format("Attempting to resume job with name=%s and parameters=%s", jobName, parameters)); } try { - return jobLauncher.run(job, parameters).getId(); + return run(job, parameters).getId(); } catch (JobExecutionAlreadyRunningException e) { throw new UnexpectedJobExecutionException( @@ -254,44 +229,28 @@ public Long restart(long executionId) throws JobInstanceAlreadyCompleteException } @Override - public Long start(String jobName, Properties parameters) - throws NoSuchJobException, JobInstanceAlreadyExistsException, JobParametersInvalidException { - if (logger.isInfoEnabled()) { - logger.info("Checking status of job with name=" + jobName); - } - - JobParameters jobParameters = jobParametersConverter.getJobParameters(parameters); - - if (jobRepository.isJobInstanceExists(jobName, jobParameters)) { - throw new JobInstanceAlreadyExistsException( - String.format("Cannot start a job instance that already exists with name=%s and parameters={%s}", - jobName, parameters)); - } + public JobExecution restart(JobExecution jobExecution) throws JobInstanceAlreadyCompleteException, + NoSuchJobExecutionException, NoSuchJobException, JobRestartException, JobParametersInvalidException { + String jobName = jobExecution.getJobInstance().getJobName(); Job job = jobRegistry.getJob(jobName); + JobParameters parameters = jobExecution.getJobParameters(); + if (logger.isInfoEnabled()) { - logger - .info(String.format("Attempting to launch job with name=%s and parameters={%s}", jobName, parameters)); + logger.info("Resuming job execution: " + jobExecution); } try { - return jobLauncher.run(job, jobParameters).getId(); + return run(job, parameters); } catch (JobExecutionAlreadyRunningException e) { throw new UnexpectedJobExecutionException( String.format(ILLEGAL_STATE_MSG, "job execution already running", jobName, parameters), e); } - catch (JobRestartException e) { - throw new UnexpectedJobExecutionException( - String.format(ILLEGAL_STATE_MSG, "job not restartable", jobName, parameters), e); - } - catch (JobInstanceAlreadyCompleteException e) { - throw new UnexpectedJobExecutionException( - String.format(ILLEGAL_STATE_MSG, "job already complete", jobName, parameters), e); - } } @Override + @Deprecated(since = "6.0", forRemoval = true) public Long startNextInstance(String jobName) throws NoSuchJobException, UnexpectedJobExecutionException, JobParametersInvalidException { if (logger.isInfoEnabled()) { @@ -299,32 +258,69 @@ public Long startNextInstance(String jobName) } Job job = jobRegistry.getJob(jobName); - JobParameters parameters = new JobParametersBuilder(jobExplorer).getNextJobParameters(job).toJobParameters(); + return startNextInstance(job).getId(); + } + + @Override + public JobExecution startNextInstance(Job job) throws UnexpectedJobExecutionException { + Assert.notNull(job, "Job must not be null"); + Assert.notNull(job.getJobParametersIncrementer(), + "No job parameters incrementer found for job=" + job.getName()); + String name = job.getName(); + JobParameters nextParameters; + JobInstance lastInstance = jobRepository.getLastJobInstance(name); + JobParametersIncrementer incrementer = job.getJobParametersIncrementer(); + if (lastInstance == null) { + // Start from a completely clean sheet + nextParameters = incrementer.getNext(new JobParameters()); + } + else { + JobExecution previousExecution = jobRepository.getLastJobExecution(lastInstance); + if (previousExecution == null) { + // Normally this will not happen - an instance exists with no executions + nextParameters = incrementer.getNext(new JobParameters()); + } + else { + nextParameters = incrementer.getNext(previousExecution.getJobParameters()); + } + } if (logger.isInfoEnabled()) { - logger.info(String.format("Attempting to launch job with name=%s and parameters=%s", jobName, parameters)); + logger.info("Launching next instance of job: [" + job.getName() + "] with parameters: [" + nextParameters + + "]"); } try { - return jobLauncher.run(job, parameters).getId(); + return run(job, nextParameters); } catch (JobExecutionAlreadyRunningException e) { throw new UnexpectedJobExecutionException( - String.format(ILLEGAL_STATE_MSG, "job already running", jobName, parameters), e); + String.format(ILLEGAL_STATE_MSG, "job already running", job.getName(), nextParameters), e); } catch (JobRestartException e) { throw new UnexpectedJobExecutionException( - String.format(ILLEGAL_STATE_MSG, "job not restartable", jobName, parameters), e); + String.format(ILLEGAL_STATE_MSG, "job not restartable", job.getName(), nextParameters), e); } catch (JobInstanceAlreadyCompleteException e) { throw new UnexpectedJobExecutionException( - String.format(ILLEGAL_STATE_MSG, "job instance already complete", jobName, parameters), e); + String.format(ILLEGAL_STATE_MSG, "job instance already complete", job.getName(), nextParameters), + e); + } + catch (JobParametersInvalidException e) { + throw new UnexpectedJobExecutionException("Invalid job parameters " + nextParameters, e); } } @Override + @Deprecated(since = "6.0", forRemoval = true) public boolean stop(long executionId) throws NoSuchJobExecutionException, JobExecutionNotRunningException { JobExecution jobExecution = findExecutionById(executionId); + return stop(jobExecution); + } + + @Override + public boolean stop(JobExecution jobExecution) throws JobExecutionNotRunningException { + Assert.notNull(jobExecution, "JobExecution must not be null"); // Indicate the execution should be stopped by setting it's status to // 'STOPPING'. It is assumed that // the step implementation will check this status at chunk boundaries. @@ -333,27 +329,35 @@ public boolean stop(long executionId) throws NoSuchJobExecutionException, JobExe throw new JobExecutionNotRunningException( "JobExecution must be running so that it can be stopped: " + jobExecution); } + if (logger.isInfoEnabled()) { + logger.info("Stopping job execution: " + jobExecution); + } jobExecution.setStatus(BatchStatus.STOPPING); jobRepository.update(jobExecution); try { Job job = jobRegistry.getJob(jobExecution.getJobInstance().getJobName()); - if (job instanceof StepLocator) {// can only process as StepLocator is the - // only way to get the step object + if (job instanceof StepLocator stepLocator) { + // can only process as StepLocator is the only way to get the step object // get the current stepExecution for (StepExecution stepExecution : jobExecution.getStepExecutions()) { if (stepExecution.getStatus().isRunning()) { try { // have the step execution that's running -> need to 'stop' it - Step step = ((StepLocator) job).getStep(stepExecution.getStepName()); - if (step instanceof TaskletStep) { - Tasklet tasklet = ((TaskletStep) step).getTasklet(); - if (tasklet instanceof StoppableTasklet) { + Step step = stepLocator.getStep(stepExecution.getStepName()); + if (step instanceof TaskletStep taskletStep) { + Tasklet tasklet = taskletStep.getTasklet(); + if (tasklet instanceof StoppableTasklet stoppableTasklet) { StepSynchronizationManager.register(stepExecution); - ((StoppableTasklet) tasklet).stop(); + stoppableTasklet.stop(stepExecution); StepSynchronizationManager.release(); } } + if (step instanceof StoppableStep stoppableStep) { + StepSynchronizationManager.register(stepExecution); + stoppableStep.stop(stepExecution); + StepSynchronizationManager.release(); + } } catch (NoSuchStepException e) { logger.warn("Step not found", e); @@ -363,17 +367,26 @@ public boolean stop(long executionId) throws NoSuchJobExecutionException, JobExe } } catch (NoSuchJobException e) { - logger.warn("Cannot find Job object in the job registry. StoppableTasklet#stop() will not be called", e); + logger.warn( + "Cannot find Job object in the job registry. StoppableTasklet#stop(StepExecution stepExecution) will not be called", + e); } return true; } @Override + @Deprecated(since = "6.0", forRemoval = true) public JobExecution abandon(long jobExecutionId) throws NoSuchJobExecutionException, JobExecutionAlreadyRunningException { JobExecution jobExecution = findExecutionById(jobExecutionId); + return abandon(jobExecution); + } + + @Override + public JobExecution abandon(JobExecution jobExecution) throws JobExecutionAlreadyRunningException { + Assert.notNull(jobExecution, "JobExecution must not be null"); if (jobExecution.getStatus().isLessThan(BatchStatus.STOPPING)) { throw new JobExecutionAlreadyRunningException( "JobExecution is running or complete and therefore cannot be aborted"); @@ -388,8 +401,134 @@ public JobExecution abandon(long jobExecutionId) return jobExecution; } + @Override + public JobExecution recover(JobExecution jobExecution) { + Assert.notNull(jobExecution, "JobExecution must not be null"); + if (jobExecution.getExecutionContext().containsKey("recovered")) { + if (logger.isWarnEnabled()) { + logger.warn("Job execution already recovered: " + jobExecution); + } + return jobExecution; + } + + BatchStatus jobStatus = jobExecution.getStatus(); + if (jobStatus == BatchStatus.COMPLETED || jobStatus == BatchStatus.ABANDONED + || jobStatus == BatchStatus.UNKNOWN) { + if (logger.isWarnEnabled()) { + logger.warn( + "JobExecution is already complete or abandoned or in an unknown state, and therefore cannot be recovered: " + + jobExecution); + } + return jobExecution; + } + + if (logger.isInfoEnabled()) { + logger.info("Recovering job execution: " + jobExecution); + } + + for (StepExecution stepExecution : jobExecution.getStepExecutions()) { + BatchStatus stepStatus = stepExecution.getStatus(); + if (stepStatus.isRunning()) { + stepExecution.setStatus(BatchStatus.FAILED); + stepExecution.setEndTime(LocalDateTime.now()); + stepExecution.getExecutionContext().put("recovered", true); + jobRepository.update(stepExecution); + } + } + + jobExecution.setStatus(BatchStatus.FAILED); + jobExecution.setEndTime(LocalDateTime.now()); + jobExecution.getExecutionContext().put("recovered", true); + jobRepository.update(jobExecution); + + return jobExecution; + } + + @Override + @Deprecated(since = "6.0", forRemoval = true) + public Set getJobNames() { + return new TreeSet<>(jobRegistry.getJobNames()); + } + + @Override + @Deprecated(since = "6.0", forRemoval = true) + public List getExecutions(long instanceId) throws NoSuchJobInstanceException { + 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 : jobRepository.getJobExecutions(jobInstance)) { + list.add(jobExecution.getId()); + } + return list; + } + + @Override + @Deprecated(since = "6.0", forRemoval = true) + public List getJobInstances(String jobName, int start, int count) throws NoSuchJobException { + List list = new ArrayList<>(); + List jobInstances = jobRepository.getJobInstances(jobName, start, count); + for (JobInstance jobInstance : jobInstances) { + list.add(jobInstance.getId()); + } + if (list.isEmpty() && !jobRegistry.getJobNames().contains(jobName)) { + throw new NoSuchJobException("No such job (either in registry or in historical data): " + jobName); + } + return list; + } + + @Override + @Nullable + @Deprecated(since = "6.0", forRemoval = true) + public JobInstance getJobInstance(String jobName, JobParameters jobParameters) { + return this.jobRepository.getJobInstance(jobName, jobParameters); + } + + @Override + @Deprecated(since = "6.0", forRemoval = true) + public String getParameters(long executionId) throws NoSuchJobExecutionException { + JobExecution jobExecution = findExecutionById(executionId); + + Properties properties = this.jobParametersConverter.getProperties(jobExecution.getJobParameters()); + + return PropertiesConverter.propertiesToString(properties); + } + + @Override + @Deprecated(since = "6.0", forRemoval = true) + public Set getRunningExecutions(String jobName) throws NoSuchJobException { + Set set = new LinkedHashSet<>(); + for (JobExecution jobExecution : jobRepository.findRunningJobExecutions(jobName)) { + set.add(jobExecution.getId()); + } + if (set.isEmpty() && !jobRegistry.getJobNames().contains(jobName)) { + throw new NoSuchJobException("No such job (either in registry or in historical data): " + jobName); + } + return set; + } + + @Override + @Deprecated(since = "6.0", forRemoval = true) + public Map getStepExecutionSummaries(long executionId) throws NoSuchJobExecutionException { + JobExecution jobExecution = findExecutionById(executionId); + + Map map = new LinkedHashMap<>(); + for (StepExecution stepExecution : jobExecution.getStepExecutions()) { + map.put(stepExecution.getId(), stepExecution.toString()); + } + return map; + } + + @Override + @Deprecated(since = "6.0", forRemoval = true) + public String getSummary(long executionId) throws NoSuchJobExecutionException { + JobExecution jobExecution = findExecutionById(executionId); + return jobExecution.toString(); + } + 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/main/java/org/springframework/batch/core/launch/support/SystemExiter.java b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/SystemExiter.java index a94c8b116f..d3b980fb9b 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/SystemExiter.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/SystemExiter.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. @@ -21,8 +21,9 @@ * unit test would cause the entire jvm to finish. * * @author Lucas Ward - * + * @deprecated since 6.0 with no replacement. Scheduled for removal in 6.2 or later. */ +@Deprecated(since = "6.0", forRemoval = true) public interface SystemExiter { /** diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/TaskExecutorJobLauncher.java b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/TaskExecutorJobLauncher.java index aab3443cd5..afede31346 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/TaskExecutorJobLauncher.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/TaskExecutorJobLauncher.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. @@ -17,20 +17,18 @@ import java.time.Duration; -import io.micrometer.core.instrument.Counter; -import io.micrometer.core.instrument.MeterRegistry; -import io.micrometer.core.instrument.Metrics; +import io.micrometer.observation.ObservationRegistry; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.batch.core.BatchStatus; import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobInstance; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.JobParametersInvalidException; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobInstance; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersInvalidException; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.launch.JobLauncher; import org.springframework.batch.core.observability.BatchMetrics; import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException; @@ -65,18 +63,20 @@ * @since 1.0 * @see JobRepository * @see TaskExecutor + * @deprecated since 6.0 in favor of {@link TaskExecutorJobOperator}. Scheduled for + * removal in 6.2 or later. */ +@SuppressWarnings("removal") +@Deprecated(since = "6.0", forRemoval = true) public class TaskExecutorJobLauncher implements JobLauncher, InitializingBean { protected static final Log logger = LogFactory.getLog(TaskExecutorJobLauncher.class); - private JobRepository jobRepository; + protected JobRepository jobRepository; - private TaskExecutor taskExecutor; + protected TaskExecutor taskExecutor; - private MeterRegistry meterRegistry = Metrics.globalRegistry; - - private Counter jobLaunchCount; // NoopCounter is still incubating + protected ObservationRegistry observationRegistry; /** * Run the provided job with the given {@link JobParameters}. The @@ -101,9 +101,6 @@ public JobExecution run(final Job job, final JobParameters jobParameters) Assert.notNull(job, "The Job must not be null."); Assert.notNull(jobParameters, "The JobParameters must not be null."); - if (this.jobLaunchCount != null) { - this.jobLaunchCount.increment(); - } final JobExecution jobExecution; JobExecution lastExecution = jobRepository.getLastJobExecution(job.getName(), jobParameters); @@ -173,11 +170,11 @@ public void run() { } private void rethrow(Throwable t) { - if (t instanceof RuntimeException) { - throw (RuntimeException) t; + if (t instanceof RuntimeException runtimeException) { + throw runtimeException; } - else if (t instanceof Error) { - throw (Error) t; + else if (t instanceof Error error) { + throw error; } throw new IllegalStateException(t); } @@ -210,16 +207,6 @@ public void setTaskExecutor(TaskExecutor taskExecutor) { this.taskExecutor = taskExecutor; } - /** - * Set the meter registry to use for metrics. Defaults to - * {@link Metrics#globalRegistry}. - * @param meterRegistry the meter registry - * @since 5.0 - */ - public void setMeterRegistry(MeterRegistry meterRegistry) { - this.meterRegistry = meterRegistry; - } - /** * Ensure the required dependencies of a {@link JobRepository} have been set. */ @@ -230,7 +217,6 @@ public void afterPropertiesSet() throws Exception { logger.info("No TaskExecutor has been set, defaulting to synchronous executor."); taskExecutor = new SyncTaskExecutor(); } - this.jobLaunchCount = BatchMetrics.createCounter(this.meterRegistry, "job.launch.count", "Job launch count"); } } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/TaskExecutorJobOperator.java b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/TaskExecutorJobOperator.java new file mode 100644 index 0000000000..68ef26c9df --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/TaskExecutorJobOperator.java @@ -0,0 +1,158 @@ +/* + * 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. + * 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.launch.support; + +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationRegistry; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.batch.core.configuration.JobRegistry; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.UnexpectedJobExecutionException; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersInvalidException; +import org.springframework.batch.core.launch.JobExecutionNotRunningException; +import org.springframework.batch.core.launch.JobOperator; +import org.springframework.batch.core.launch.NoSuchJobException; +import org.springframework.batch.core.launch.NoSuchJobExecutionException; +import org.springframework.batch.core.observability.jfr.events.job.JobLaunchEvent; +import org.springframework.batch.core.observability.micrometer.MicrometerMetrics; +import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException; +import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException; +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.core.repository.JobRestartException; +import org.springframework.core.task.TaskExecutor; +import org.springframework.util.Assert; + +import static org.springframework.batch.core.observability.BatchMetrics.METRICS_PREFIX; + +/** + * A {@link org.springframework.core.task.TaskExecutor}-based implementation of the + * {@link JobOperator} interface. The following dependencies are required: + * + *
    + *
  • {@link JobRepository} + *
  • {@link JobRegistry} + *
+ * + * This class can be instantiated with a {@link JobOperatorFactoryBean} to create a + * transactional proxy around the job operator. + * + * @see JobOperatorFactoryBean + * @author Dave Syer + * @author Lucas Ward + * @author Will Schipp + * @author Mahmoud Ben Hassine + * @author Yejeong Ham + * @since 6.0 + */ +@SuppressWarnings("removal") +public class TaskExecutorJobOperator extends SimpleJobOperator { + + private static final Log logger = LogFactory.getLog(TaskExecutorJobOperator.class.getName()); + + protected ObservationRegistry observationRegistry; + + @Override + public void afterPropertiesSet() throws Exception { + super.afterPropertiesSet(); + if (this.observationRegistry == null) { + logger.info("No ObservationRegistry has been set, defaulting to ObservationRegistry NOOP"); + this.observationRegistry = ObservationRegistry.NOOP; + } + } + + @Override + public void setJobRegistry(JobRegistry jobRegistry) { + Assert.notNull(jobRegistry, "JobRegistry must not be null"); + this.jobRegistry = jobRegistry; + } + + @Override + public void setJobRepository(JobRepository jobRepository) { + Assert.notNull(jobRepository, "JobRepository must not be null"); + this.jobRepository = jobRepository; + } + + @Override + public void setTaskExecutor(TaskExecutor taskExecutor) { + Assert.notNull(taskExecutor, "TaskExecutor must not be null"); + this.taskExecutor = taskExecutor; + } + + /** + * Set the observation registry to use for observations. Defaults to + * {@link ObservationRegistry#NOOP}. + * @param observationRegistry the observation registry + * @since 6.0 + */ + public void setObservationRegistry(ObservationRegistry observationRegistry) { + Assert.notNull(observationRegistry, "ObservationRegistry must not be null"); + this.observationRegistry = observationRegistry; + } + + @Override + public JobExecution start(Job job, JobParameters jobParameters) + throws NoSuchJobException, JobInstanceAlreadyCompleteException, JobExecutionAlreadyRunningException, + JobRestartException, JobParametersInvalidException { + Assert.notNull(job, "Job must not be null"); + Assert.notNull(jobParameters, "JobParameters must not be null"); + new JobLaunchEvent(job.getName(), jobParameters.toString()).commit(); + Observation observation = MicrometerMetrics + .createObservation(METRICS_PREFIX + "job.launch.count", this.observationRegistry) + .start(); + try (var scope = observation.openScope()) { + return super.start(job, jobParameters); + } + finally { + observation.stop(); + } + } + + @Override + public JobExecution restart(JobExecution jobExecution) throws JobInstanceAlreadyCompleteException, + NoSuchJobExecutionException, NoSuchJobException, JobRestartException, JobParametersInvalidException { + Assert.notNull(jobExecution, "JobExecution must not be null"); + return super.restart(jobExecution); + } + + @Override + public JobExecution startNextInstance(Job job) throws UnexpectedJobExecutionException { + Assert.notNull(job, "Job must not be null"); + return super.startNextInstance(job); + } + + @Override + public boolean stop(JobExecution jobExecution) throws JobExecutionNotRunningException { + Assert.notNull(jobExecution, "JobExecution must not be null"); + return super.stop(jobExecution); + } + + @Override + public JobExecution abandon(JobExecution jobExecution) throws JobExecutionAlreadyRunningException { + Assert.notNull(jobExecution, "JobExecution must not be null"); + return super.abandon(jobExecution); + } + + @Override + public JobExecution recover(JobExecution jobExecution) { + Assert.notNull(jobExecution, "JobExecution must not be null"); + return super.recover(jobExecution); + } + +} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/AbstractListenerFactoryBean.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/AbstractListenerFactoryBean.java index 18c9e4cf2e..00ca69e6fd 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/AbstractListenerFactoryBean.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/AbstractListenerFactoryBean.java @@ -150,8 +150,8 @@ public Object getObject() { // create a proxy listener for only the interfaces that have methods to // be called ProxyFactory proxyFactory = new ProxyFactory(); - if (delegate instanceof Advised) { - proxyFactory.setTargetSource(((Advised) delegate).getTargetSource()); + if (delegate instanceof Advised advised) { + proxyFactory.setTargetSource(advised.getTargetSource()); } else { proxyFactory.setTarget(delegate); @@ -214,15 +214,13 @@ public static boolean isListener(Object target, Class listenerType, ListenerM if (listenerType.isInstance(target)) { return true; } - if (target instanceof Advised) { - TargetSource targetSource = ((Advised) target).getTargetSource(); - if (targetSource != null && targetSource.getTargetClass() != null - && listenerType.isAssignableFrom(targetSource.getTargetClass())) { + if (target instanceof Advised advised) { + TargetSource targetSource = advised.getTargetSource(); + if (targetSource.getTargetClass() != null && listenerType.isAssignableFrom(targetSource.getTargetClass())) { return true; } - if (targetSource != null && targetSource.getTargetClass() != null - && targetSource.getTargetClass().isInterface()) { + if (targetSource.getTargetClass() != null && targetSource.getTargetClass().isInterface()) { logger.warn(String.format( "%s is an interface. The implementing class will not be queried for annotation based listener configurations. If using @StepScope on a @Bean method, be sure to return the implementing class so listener annotations can be used.", targetSource.getTargetClass().getName())); diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/ChunkListener.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/ChunkListener.java similarity index 55% rename from spring-batch-core/src/main/java/org/springframework/batch/core/ChunkListener.java rename to spring-batch-core/src/main/java/org/springframework/batch/core/listener/ChunkListener.java index 951410235b..45b91756db 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/ChunkListener.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/ChunkListener.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. @@ -13,9 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.batch.core; +package org.springframework.batch.core.listener; import org.springframework.batch.core.scope.context.ChunkContext; +import org.springframework.batch.item.Chunk; /** * Listener interface for the lifecycle of a chunk. A chunk can be thought of as a @@ -23,6 +24,9 @@ *

* {@link ChunkListener} shouldn't throw exceptions and expect continued processing, they * must be handled in the implementation or the step will terminate. + *

+ * Note: This listener is not called in concurrent steps. + *

* * @author Lucas Ward * @author Michael Minella @@ -30,24 +34,32 @@ * @author Parikshit Dutta * @author Injae Kim */ -public interface ChunkListener extends StepListener { +public interface ChunkListener extends StepListener { /** * The key for retrieving the rollback exception. + * @deprecated since 6.0 with no replacement. Scheduled for removal in 6.2 or later. */ + @Deprecated(since = "6.0", forRemoval = true) String ROLLBACK_EXCEPTION_KEY = "sb_rollback_exception"; /** * Callback before the chunk is executed, but inside the transaction. * @param context The current {@link ChunkContext} + * @deprecated since 6.0, use {@link #beforeChunk(Chunk)} instead. Scheduled for + * removal in 6.2 or later. */ + @Deprecated(since = "6.0", forRemoval = true) default void beforeChunk(ChunkContext context) { } /** * Callback after the chunk is executed, outside the transaction. * @param context The current {@link ChunkContext} + * @deprecated since 6.0, use {@link #afterChunk(Chunk)} instead. Scheduled for + * removal in 6.2 or later. */ + @Deprecated(since = "6.0", forRemoval = true) default void afterChunk(ChunkContext context) { } @@ -61,8 +73,39 @@ default void afterChunk(ChunkContext context) { * from here. * @param context the chunk context containing the exception that caused the * underlying rollback. + * @deprecated since 6.0, use {@link #onChunkError(Exception,Chunk)} instead. + * Scheduled for removal in 6.2 or later. */ + @Deprecated(since = "6.0", forRemoval = true) default void afterChunkError(ChunkContext context) { } + /** + * Callback before the chunk is processed, inside the transaction. This method is not + * called in concurrent steps. + * @since 6.0 + */ + default void beforeChunk(Chunk chunk) { + } + + /** + * Callback after the chunk is written, inside the transaction. This method is not + * called in concurrent steps. + * @since 6.0 + */ + default void afterChunk(Chunk chunk) { + } + + /** + * Callback if an exception occurs while processing or writing a chunk, inside the + * transaction, which is about to be rolled back. As a result, you should use + * {@code PROPAGATION_REQUIRES_NEW} for any transactional operation that is called + * here. This method is not called in concurrent steps. + * @param exception the exception that caused the underlying rollback. + * @param chunk the processed chunk + * @since 6.0 + */ + default void onChunkError(Exception exception, Chunk chunk) { + } + } 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/CompositeChunkListener.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/CompositeChunkListener.java index 1d7b747012..eb5baf7134 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/CompositeChunkListener.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/CompositeChunkListener.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. @@ -19,8 +19,8 @@ import java.util.Iterator; import java.util.List; -import org.springframework.batch.core.ChunkListener; import org.springframework.batch.core.scope.context.ChunkContext; +import org.springframework.batch.item.Chunk; import org.springframework.core.Ordered; /** @@ -28,7 +28,7 @@ * @author Mahmoud Ben Hassine * */ -public class CompositeChunkListener implements ChunkListener { +public class CompositeChunkListener implements ChunkListener { private final OrderedComposite listeners = new OrderedComposite<>(); @@ -74,8 +74,11 @@ public void register(ChunkListener chunkListener) { /** * Call the registered listeners in reverse order. * - * @see org.springframework.batch.core.ChunkListener#afterChunk(ChunkContext context) + * @see ChunkListener#afterChunk(ChunkContext context) + * @deprecated since 6.0, use {@link #afterChunk(Chunk)} instead. Scheduled for + * removal in 6.2 or later. */ + @Deprecated(since = "6.0", forRemoval = true) @Override public void afterChunk(ChunkContext context) { for (Iterator iterator = listeners.reverse(); iterator.hasNext();) { @@ -84,12 +87,28 @@ public void afterChunk(ChunkContext context) { } } + /** + * Call the registered listeners in reverse order. + * + * @see ChunkListener#afterChunk(Chunk) + */ + @Override + public void afterChunk(Chunk chunk) { + for (Iterator iterator = listeners.reverse(); iterator.hasNext();) { + ChunkListener listener = iterator.next(); + listener.afterChunk(chunk); + } + } + /** * Call the registered listeners in order, respecting and prioritizing those that * implement {@link Ordered}. * - * @see org.springframework.batch.core.ChunkListener#beforeChunk(ChunkContext context) + * @see ChunkListener#beforeChunk(ChunkContext context) + * @deprecated since 6.0, use {@link #beforeChunk(Chunk)} instead. Scheduled for + * removal in 6.2 or later. */ + @Deprecated(since = "6.0", forRemoval = true) @Override public void beforeChunk(ChunkContext context) { for (Iterator iterator = listeners.iterator(); iterator.hasNext();) { @@ -98,12 +117,28 @@ public void beforeChunk(ChunkContext context) { } } + /** + * Call the registered listeners in order, respecting and prioritizing those that + * implement {@link Ordered}. + * + * @see ChunkListener#beforeChunk(Chunk chunk) + */ + @Override + public void beforeChunk(Chunk chunk) { + for (Iterator iterator = listeners.iterator(); iterator.hasNext();) { + ChunkListener listener = iterator.next(); + listener.beforeChunk(chunk); + } + } + /** * Call the registered listeners in reverse order. * - * @see org.springframework.batch.core.ChunkListener#afterChunkError(ChunkContext - * context) + * @see ChunkListener#afterChunkError(ChunkContext context) + * @deprecated since 6.0, use {@link #onChunkError(Exception,Chunk)} instead. + * Scheduled for removal in 6.2 or later. */ + @Deprecated(since = "6.0", forRemoval = true) @Override public void afterChunkError(ChunkContext context) { for (Iterator iterator = listeners.reverse(); iterator.hasNext();) { @@ -112,4 +147,17 @@ public void afterChunkError(ChunkContext context) { } } + /** + * Call the registered listeners in reverse order. + * + * @see ChunkListener#onChunkError(Exception, Chunk) + */ + @Override + public void onChunkError(Exception exception, Chunk chunk) { + for (Iterator iterator = listeners.reverse(); iterator.hasNext();) { + ChunkListener listener = iterator.next(); + listener.onChunkError(exception, chunk); + } + } + } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/CompositeItemProcessListener.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/CompositeItemProcessListener.java index 882770dbd4..19c966a503 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/CompositeItemProcessListener.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/CompositeItemProcessListener.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,7 +18,6 @@ import java.util.Iterator; import java.util.List; -import org.springframework.batch.core.ItemProcessListener; import org.springframework.core.Ordered; import org.springframework.lang.Nullable; @@ -52,8 +51,7 @@ public void register(ItemProcessListener itemProcessorList /** * Call the registered listeners in reverse order, respecting and prioritising those * that implement {@link Ordered}. - * @see org.springframework.batch.core.ItemProcessListener#afterProcess(java.lang.Object, - * java.lang.Object) + * @see ItemProcessListener#afterProcess(java.lang.Object, java.lang.Object) */ @Override public void afterProcess(T item, @Nullable S result) { @@ -66,7 +64,7 @@ public void afterProcess(T item, @Nullable S result) { /** * Call the registered listeners in order, respecting and prioritising those that * implement {@link Ordered}. - * @see org.springframework.batch.core.ItemProcessListener#beforeProcess(java.lang.Object) + * @see ItemProcessListener#beforeProcess(java.lang.Object) */ @Override public void beforeProcess(T item) { @@ -79,8 +77,7 @@ public void beforeProcess(T item) { /** * Call the registered listeners in reverse order, respecting and prioritising those * that implement {@link Ordered}. - * @see org.springframework.batch.core.ItemProcessListener#onProcessError(java.lang.Object, - * java.lang.Exception) + * @see ItemProcessListener#onProcessError(java.lang.Object, java.lang.Exception) */ @Override public void onProcessError(T item, Exception e) { diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/CompositeItemReadListener.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/CompositeItemReadListener.java index 18fa7599a7..f006af5458 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/CompositeItemReadListener.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/CompositeItemReadListener.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,7 +18,6 @@ import java.util.Iterator; import java.util.List; -import org.springframework.batch.core.ItemReadListener; import org.springframework.core.Ordered; /** @@ -51,7 +50,7 @@ public void register(ItemReadListener itemReaderListener) { /** * Call the registered listeners in reverse order, respecting and prioritising those * that implement {@link Ordered}. - * @see org.springframework.batch.core.ItemReadListener#afterRead(java.lang.Object) + * @see ItemReadListener#afterRead(java.lang.Object) */ @Override public void afterRead(T item) { @@ -64,7 +63,7 @@ public void afterRead(T item) { /** * Call the registered listeners in order, respecting and prioritising those that * implement {@link Ordered}. - * @see org.springframework.batch.core.ItemReadListener#beforeRead() + * @see ItemReadListener#beforeRead() */ @Override public void beforeRead() { @@ -77,7 +76,7 @@ public void beforeRead() { /** * Call the registered listeners in reverse order, respecting and prioritising those * that implement {@link Ordered}. - * @see org.springframework.batch.core.ItemReadListener#onReadError(java.lang.Exception) + * @see ItemReadListener#onReadError(java.lang.Exception) */ @Override public void onReadError(Exception ex) { diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/CompositeItemWriteListener.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/CompositeItemWriteListener.java index 300bc30a9e..cf7b1916ac 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/CompositeItemWriteListener.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/CompositeItemWriteListener.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,7 +18,6 @@ import java.util.Iterator; import java.util.List; -import org.springframework.batch.core.ItemWriteListener; import org.springframework.batch.item.Chunk; import org.springframework.core.Ordered; diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/CompositeJobExecutionListener.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/CompositeJobExecutionListener.java index 304b1b2a92..c769d1ceaa 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/CompositeJobExecutionListener.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/CompositeJobExecutionListener.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,8 +18,7 @@ import java.util.Iterator; import java.util.List; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobExecutionListener; +import org.springframework.batch.core.job.JobExecution; import org.springframework.core.Ordered; /** @@ -51,7 +50,7 @@ public void register(JobExecutionListener jobExecutionListener) { /** * Call the registered listeners in reverse order, respecting and prioritising those * that implement {@link Ordered}. - * @see org.springframework.batch.core.JobExecutionListener#afterJob(org.springframework.batch.core.JobExecution) + * @see JobExecutionListener#afterJob(JobExecution) */ @Override public void afterJob(JobExecution jobExecution) { @@ -64,7 +63,7 @@ public void afterJob(JobExecution jobExecution) { /** * Call the registered listeners in order, respecting and prioritising those that * implement {@link Ordered}. - * @see org.springframework.batch.core.JobExecutionListener#beforeJob(org.springframework.batch.core.JobExecution) + * @see JobExecutionListener#beforeJob(JobExecution) */ @Override public void beforeJob(JobExecution jobExecution) { diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/CompositeSkipListener.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/CompositeSkipListener.java index 13a355b8c0..d2f969527f 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/CompositeSkipListener.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/CompositeSkipListener.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,7 +18,6 @@ import java.util.Iterator; import java.util.List; -import org.springframework.batch.core.SkipListener; import org.springframework.core.Ordered; /** @@ -49,7 +48,7 @@ public void register(SkipListener listener) { /** * Call the registered listeners in order, respecting and prioritising those that * implement {@link Ordered}. - * @see org.springframework.batch.core.SkipListener#onSkipInRead(java.lang.Throwable) + * @see SkipListener#onSkipInRead(java.lang.Throwable) */ @Override public void onSkipInRead(Throwable t) { @@ -62,8 +61,7 @@ public void onSkipInRead(Throwable t) { /** * Call the registered listeners in order, respecting and prioritising those that * implement {@link Ordered}. - * @see org.springframework.batch.core.SkipListener#onSkipInWrite(java.lang.Object, - * java.lang.Throwable) + * @see SkipListener#onSkipInWrite(java.lang.Object, java.lang.Throwable) */ @Override public void onSkipInWrite(S item, Throwable t) { @@ -76,8 +74,7 @@ public void onSkipInWrite(S item, Throwable t) { /** * Call the registered listeners in order, respecting and prioritising those that * implement {@link Ordered}. - * @see org.springframework.batch.core.SkipListener#onSkipInWrite(java.lang.Object, - * java.lang.Throwable) + * @see SkipListener#onSkipInWrite(java.lang.Object, java.lang.Throwable) */ @Override public void onSkipInProcess(T item, Throwable t) { diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/CompositeStepExecutionListener.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/CompositeStepExecutionListener.java index bfaa770926..b3421897ae 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/CompositeStepExecutionListener.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/CompositeStepExecutionListener.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. @@ -19,8 +19,7 @@ import java.util.Iterator; import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.StepExecutionListener; +import org.springframework.batch.core.step.StepExecution; import org.springframework.core.Ordered; import org.springframework.lang.Nullable; @@ -55,7 +54,7 @@ public void register(StepExecutionListener stepExecutionListener) { /** * Call the registered listeners in reverse order, respecting and prioritizing those * that implement {@link Ordered}. - * @see org.springframework.batch.core.StepExecutionListener#afterStep(StepExecution) + * @see StepExecutionListener#afterStep(StepExecution) */ @Nullable @Override @@ -71,7 +70,7 @@ public ExitStatus afterStep(StepExecution stepExecution) { /** * Call the registered listeners in order, respecting and prioritizing those that * implement {@link Ordered}. - * @see org.springframework.batch.core.StepExecutionListener#beforeStep(StepExecution) + * @see StepExecutionListener#beforeStep(StepExecution) */ @Override public void beforeStep(StepExecution stepExecution) { diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/ExecutionContextPromotionListener.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/ExecutionContextPromotionListener.java index 525dc86b92..262cda639a 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/ExecutionContextPromotionListener.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/ExecutionContextPromotionListener.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,10 +16,9 @@ package org.springframework.batch.core.listener; import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.StepExecutionListener; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.item.ExecutionContext; import org.springframework.batch.support.PatternMatcher; import org.springframework.beans.factory.InitializingBean; diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/ItemListenerSupport.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/ItemListenerSupport.java index f2023a9294..e283904216 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/ItemListenerSupport.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/ItemListenerSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2021 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,10 +15,6 @@ */ package org.springframework.batch.core.listener; -import org.springframework.batch.core.ItemProcessListener; -import org.springframework.batch.core.ItemReadListener; -import org.springframework.batch.core.ItemWriteListener; - /** * Basic no-op implementation of the {@link ItemReadListener}, * {@link ItemProcessListener}, and {@link ItemWriteListener} interfaces. All are diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/ItemProcessListener.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/ItemProcessListener.java similarity index 94% rename from spring-batch-core/src/main/java/org/springframework/batch/core/ItemProcessListener.java rename to spring-batch-core/src/main/java/org/springframework/batch/core/listener/ItemProcessListener.java index 23f6cf4bd4..fb3d394fcc 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/ItemProcessListener.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/ItemProcessListener.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. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.batch.core; +package org.springframework.batch.core.listener; import org.springframework.batch.item.ItemProcessor; import org.springframework.lang.Nullable; diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/ItemReadListener.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/ItemReadListener.java similarity index 92% rename from spring-batch-core/src/main/java/org/springframework/batch/core/ItemReadListener.java rename to spring-batch-core/src/main/java/org/springframework/batch/core/listener/ItemReadListener.java index d12e80e629..7a6dc6f710 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/ItemReadListener.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/ItemReadListener.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. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.batch.core; +package org.springframework.batch.core.listener; import org.springframework.batch.item.ItemReader; diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/ItemWriteListener.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/ItemWriteListener.java similarity index 95% rename from spring-batch-core/src/main/java/org/springframework/batch/core/ItemWriteListener.java rename to spring-batch-core/src/main/java/org/springframework/batch/core/listener/ItemWriteListener.java index 46c55786c0..9e05c5458b 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/ItemWriteListener.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/ItemWriteListener.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. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.batch.core; +package org.springframework.batch.core.listener; import org.springframework.batch.core.scope.context.ChunkContext; import org.springframework.batch.item.Chunk; diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/JobExecutionListener.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/JobExecutionListener.java similarity index 87% rename from spring-batch-core/src/main/java/org/springframework/batch/core/JobExecutionListener.java rename to spring-batch-core/src/main/java/org/springframework/batch/core/listener/JobExecutionListener.java index bd0c7b6a92..814fd8846a 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/JobExecutionListener.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/JobExecutionListener.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. @@ -13,7 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.batch.core; +package org.springframework.batch.core.listener; + +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; /** * Provide callbacks at specific points in the lifecycle of a {@link Job}. Implementations diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/JobListenerFactoryBean.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/JobListenerFactoryBean.java index 76ae37e5f0..87428c364c 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/JobListenerFactoryBean.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/JobListenerFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 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. @@ -15,8 +15,6 @@ */ package org.springframework.batch.core.listener; -import org.springframework.batch.core.JobExecutionListener; - /** * This {@link AbstractListenerFactoryBean} implementation is used to create a * {@link JobExecutionListener}. diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/JobListenerMetaData.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/JobListenerMetaData.java index 3f5b515502..a268ebcc3d 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/JobListenerMetaData.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/JobListenerMetaData.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 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. @@ -19,8 +19,7 @@ import java.util.HashMap; import java.util.Map; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobExecutionListener; +import org.springframework.batch.core.job.JobExecution; import org.springframework.batch.core.annotation.AfterJob; import org.springframework.batch.core.annotation.BeforeJob; import org.springframework.lang.Nullable; diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/JobParameterExecutionContextCopyListener.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/JobParameterExecutionContextCopyListener.java index c26d473ad0..277a712389 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/JobParameterExecutionContextCopyListener.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/JobParameterExecutionContextCopyListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2021 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,10 +18,9 @@ import java.util.Arrays; import java.util.Collection; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.StepExecutionListener; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.item.ExecutionContext; /** diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/MethodInvokerMethodInterceptor.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/MethodInvokerMethodInterceptor.java index b6cd083e9a..78a5d81701 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/MethodInvokerMethodInterceptor.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/MethodInvokerMethodInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 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. @@ -21,7 +21,7 @@ import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.StepExecutionListener; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.support.MethodInvoker; /** @@ -29,8 +29,7 @@ * will execute all methods tied to a particular method name, with the provided arguments. * The only possible return value that is handled is of type ExitStatus, since the only * StepListener implementation that isn't void is - * {@link StepExecutionListener#afterStep(org.springframework.batch.core.StepExecution)} , - * which returns ExitStatus. + * {@link StepExecutionListener#afterStep(StepExecution)} , which returns ExitStatus. * * @author Lucas Ward * @author Mahmoud Ben Hassine @@ -68,12 +67,12 @@ public Object invoke(MethodInvocation invocation) throws Throwable { ExitStatus status = null; for (MethodInvoker invoker : invokers) { Object retVal = invoker.invokeMethod(invocation.getArguments()); - if (retVal instanceof ExitStatus) { + if (retVal instanceof ExitStatus exitStatus) { if (status != null) { - status = status.and((ExitStatus) retVal); + status = status.and(exitStatus); } else { - status = (ExitStatus) retVal; + status = exitStatus; } } } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/MulticasterBatchListener.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/MulticasterBatchListener.java index 81db370944..02ef2821fa 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/MulticasterBatchListener.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/MulticasterBatchListener.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,15 +18,8 @@ import java.lang.reflect.InvocationTargetException; import java.util.List; -import org.springframework.batch.core.ChunkListener; import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.ItemProcessListener; -import org.springframework.batch.core.ItemReadListener; -import org.springframework.batch.core.ItemWriteListener; -import org.springframework.batch.core.SkipListener; -import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.StepExecutionListener; -import org.springframework.batch.core.StepListener; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.scope.context.ChunkContext; import org.springframework.batch.item.Chunk; import org.springframework.batch.item.ItemStream; @@ -78,11 +71,11 @@ public void setListeners(List listeners) { * @param listener the {@link StepListener} instance to be registered. */ public void register(StepListener listener) { - if (listener instanceof StepExecutionListener) { - this.stepListener.register((StepExecutionListener) listener); + if (listener instanceof StepExecutionListener stepExecutionListener) { + this.stepListener.register(stepExecutionListener); } - if (listener instanceof ChunkListener) { - this.chunkListener.register((ChunkListener) listener); + if (listener instanceof ChunkListener cl) { + this.chunkListener.register(cl); } if (listener instanceof ItemReadListener) { @SuppressWarnings("unchecked") @@ -162,7 +155,7 @@ public ExitStatus afterStep(StepExecution stepExecution) { } /** - * @see org.springframework.batch.core.listener.CompositeStepExecutionListener#beforeStep(org.springframework.batch.core.StepExecution) + * @see org.springframework.batch.core.listener.CompositeStepExecutionListener#beforeStep(StepExecution) */ @Override public void beforeStep(StepExecution stepExecution) { @@ -323,8 +316,8 @@ public void afterChunkError(ChunkContext context) { */ private Throwable getTargetException(RuntimeException e) { Throwable cause = e.getCause(); - if (cause != null && cause instanceof InvocationTargetException) { - return ((InvocationTargetException) cause).getTargetException(); + if (cause instanceof InvocationTargetException invocationTargetException) { + return invocationTargetException.getTargetException(); } return e; } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/SkipListener.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/SkipListener.java similarity index 92% rename from spring-batch-core/src/main/java/org/springframework/batch/core/SkipListener.java rename to spring-batch-core/src/main/java/org/springframework/batch/core/listener/SkipListener.java index 57c79e56dc..64c08b0a03 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/SkipListener.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/SkipListener.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. @@ -13,7 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.batch.core; +package org.springframework.batch.core.listener; + +import org.springframework.batch.core.step.Step; /** * Interface for listener to skipped items. Callbacks are called by {@link Step} 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/StepExecutionListener.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/StepExecutionListener.java similarity index 86% rename from spring-batch-core/src/main/java/org/springframework/batch/core/StepExecutionListener.java rename to spring-batch-core/src/main/java/org/springframework/batch/core/listener/StepExecutionListener.java index f1e9a26baf..9c451b417c 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/StepExecutionListener.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/StepExecutionListener.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. @@ -13,8 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.batch.core; +package org.springframework.batch.core.listener; +import org.springframework.batch.core.ExitStatus; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.step.StepExecution; import org.springframework.lang.Nullable; /** 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/StepListener.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/StepListener.java similarity index 89% rename from spring-batch-core/src/main/java/org/springframework/batch/core/StepListener.java rename to spring-batch-core/src/main/java/org/springframework/batch/core/listener/StepListener.java index 7e12fa48f5..e3282e4901 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/StepListener.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/StepListener.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. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.batch.core; +package org.springframework.batch.core.listener; /** * Marker interface that acts as a parent to all step domain listeners, such as: diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/StepListenerFactoryBean.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/StepListenerFactoryBean.java index ace030474c..196f4ca16a 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/StepListenerFactoryBean.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/StepListenerFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 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. @@ -15,8 +15,6 @@ */ package org.springframework.batch.core.listener; -import org.springframework.batch.core.StepListener; - /** * This {@link AbstractListenerFactoryBean} implementation is used to create a * {@link StepListener}. diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/StepListenerMetaData.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/StepListenerMetaData.java index 943497828f..4fb5aad1b1 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/StepListenerMetaData.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/StepListenerMetaData.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 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. @@ -19,14 +19,7 @@ import java.util.HashMap; import java.util.Map; -import org.springframework.batch.core.ChunkListener; -import org.springframework.batch.core.ItemProcessListener; -import org.springframework.batch.core.ItemReadListener; -import org.springframework.batch.core.ItemWriteListener; -import org.springframework.batch.core.SkipListener; -import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.StepExecutionListener; -import org.springframework.batch.core.StepListener; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.annotation.AfterChunk; import org.springframework.batch.core.annotation.AfterChunkError; import org.springframework.batch.core.annotation.AfterProcess; @@ -53,6 +46,7 @@ * methods, their interfaces, annotation, and expected arguments. * * @author Lucas Ward + * @author Hyunsang Han * @since 2.0 * @see StepListenerFactoryBean */ @@ -60,8 +54,8 @@ public enum StepListenerMetaData implements ListenerMetaData { BEFORE_STEP("beforeStep", "before-step-method", BeforeStep.class, StepExecutionListener.class, StepExecution.class), AFTER_STEP("afterStep", "after-step-method", AfterStep.class, StepExecutionListener.class, StepExecution.class), - BEFORE_CHUNK("beforeChunk", "before-chunk-method", BeforeChunk.class, ChunkListener.class, ChunkContext.class), - AFTER_CHUNK("afterChunk", "after-chunk-method", AfterChunk.class, ChunkListener.class, ChunkContext.class), + BEFORE_CHUNK("beforeChunk", "before-chunk-method", BeforeChunk.class, ChunkListener.class, Chunk.class), + AFTER_CHUNK("afterChunk", "after-chunk-method", AfterChunk.class, ChunkListener.class, Chunk.class), AFTER_CHUNK_ERROR("afterChunkError", "after-chunk-error-method", AfterChunkError.class, ChunkListener.class, ChunkContext.class), BEFORE_READ("beforeRead", "before-read-method", BeforeRead.class, ItemReadListener.class), diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/StepListenerSupport.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/StepListenerSupport.java index bc10b1d2bd..ca707f9874 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/StepListenerSupport.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/StepListenerSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2021 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,11 +15,6 @@ */ package org.springframework.batch.core.listener; -import org.springframework.batch.core.ChunkListener; -import org.springframework.batch.core.SkipListener; -import org.springframework.batch.core.StepExecutionListener; -import org.springframework.batch.core.StepListener; - /** * Basic no-op implementations of all {@link StepListener} interfaces. * diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/observability/BatchJobContext.java b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/BatchJobContext.java deleted file mode 100644 index 4c593fd3f0..0000000000 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/observability/BatchJobContext.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 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.core.observability; - -import io.micrometer.observation.Observation; - -import org.springframework.batch.core.JobExecution; - -import java.util.function.Supplier; - -/** - * Observation context for batch jobs. - * - * @author Marcin Grzejszczak - * @since 5.0 - */ -public class BatchJobContext extends Observation.Context implements Supplier { - - private final JobExecution jobExecution; - - public BatchJobContext(JobExecution jobExecution) { - this.jobExecution = jobExecution; - } - - public JobExecution getJobExecution() { - return jobExecution; - } - - @Override - public BatchJobContext get() { - return this; - } - -} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/observability/BatchJobObservation.java b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/BatchJobObservation.java deleted file mode 100644 index 75132a7458..0000000000 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/observability/BatchJobObservation.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright 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.core.observability; - -import io.micrometer.common.docs.KeyName; -import io.micrometer.observation.docs.ObservationDocumentation; - -/** - * Observation created around a Job execution. - * - * @author Marcin Grzejszczak - * @author Mahmoud Ben Hassine - * @since 5.0 - */ -public enum BatchJobObservation implements ObservationDocumentation { - - BATCH_JOB_OBSERVATION { - @Override - public String getName() { - return "spring.batch.job"; - } - - @Override - public String getContextualName() { - return "%s"; - } - - @Override - public KeyName[] getLowCardinalityKeyNames() { - return JobLowCardinalityTags.values(); - } - - @Override - public KeyName[] getHighCardinalityKeyNames() { - return JobHighCardinalityTags.values(); - } - - @Override - public String getPrefix() { - return "spring.batch"; - } - }; - - enum JobLowCardinalityTags implements KeyName { - - /** - * Name of the Spring Batch job. - */ - JOB_NAME { - @Override - public String asString() { - return "spring.batch.job.name"; - } - }, - - /** - * Job status. - */ - JOB_STATUS { - @Override - public String asString() { - return "spring.batch.job.status"; - } - } - - } - - enum JobHighCardinalityTags implements KeyName { - - /** - * ID of the Spring Batch job instance. - */ - JOB_INSTANCE_ID { - @Override - public String asString() { - return "spring.batch.job.instanceId"; - } - }, - - /** - * ID of the Spring Batch job execution. - */ - JOB_EXECUTION_ID { - @Override - public String asString() { - return "spring.batch.job.executionId"; - } - } - - } - -} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/observability/BatchJobObservationConvention.java b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/BatchJobObservationConvention.java deleted file mode 100644 index 52521f43eb..0000000000 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/observability/BatchJobObservationConvention.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 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.core.observability; - -import io.micrometer.observation.Observation; -import io.micrometer.observation.ObservationConvention; - -/** - * {@link ObservationConvention} for {@link BatchJobContext}. - * - * @author Marcin Grzejszczak - * @author Mahmoud Ben Hassine - * @since 5.0 - */ -public interface BatchJobObservationConvention extends ObservationConvention { - - @Override - default boolean supportsContext(Observation.Context context) { - return context instanceof BatchJobContext; - } - -} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/observability/BatchMetrics.java b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/BatchMetrics.java index a0d6196b1a..0d6d8b8f1d 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/observability/BatchMetrics.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/BatchMetrics.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. @@ -17,31 +17,16 @@ import java.time.Duration; import java.time.LocalDateTime; -import java.util.Arrays; import java.util.concurrent.TimeUnit; -import io.micrometer.core.instrument.Counter; -import io.micrometer.core.instrument.LongTaskTimer; -import io.micrometer.core.instrument.MeterRegistry; -import io.micrometer.core.instrument.Tag; -import io.micrometer.core.instrument.Timer; -import io.micrometer.core.instrument.observation.DefaultMeterObservationHandler; -import io.micrometer.observation.Observation; -import io.micrometer.observation.ObservationRegistry; - import org.springframework.lang.Nullable; /** - * Central class for batch metrics. It provides: - * - *
    - *
  • the main entry point to interact with Micrometer's API to create common metrics - * such as {@link Timer} and {@link LongTaskTimer}.
  • - *
  • Some utility methods like calculating durations and formatting them in a human - * readable format.
  • - *
- * - * Only intended for internal use. + * Central class for batch metrics. It provides some utility methods like calculating + * durations and formatting them in a human-readable format. + *

+ * Only intended for internal use. + *

* * @author Mahmoud Ben Hassine * @author Glenn Renfro @@ -55,99 +40,11 @@ public final class BatchMetrics { public static final String STATUS_FAILURE = "FAILURE"; - private BatchMetrics() { - } - - /** - * Create a {@link Timer}. - * @param meterRegistry the meter registry to use - * @param name of the timer. Will be prefixed with - * {@link BatchMetrics#METRICS_PREFIX}. - * @param description of the timer - * @param tags of the timer - * @return a new timer instance - */ - public static Timer createTimer(MeterRegistry meterRegistry, String name, String description, Tag... tags) { - return Timer.builder(METRICS_PREFIX + name) - .description(description) - .tags(Arrays.asList(tags)) - .register(meterRegistry); - } + public static final String STATUS_COMMITTED = "COMMITTED"; - /** - * Create a {@link Counter}. - * @param meterRegistry the meter registry to use - * @param name of the counter. Will be prefixed with - * {@link BatchMetrics#METRICS_PREFIX}. - * @param description of the counter - * @param tags of the counter - * @return a new timer instance - */ - public static Counter createCounter(MeterRegistry meterRegistry, String name, String description, Tag... tags) { - return Counter.builder(METRICS_PREFIX + name) - .description(description) - .tags(Arrays.asList(tags)) - .register(meterRegistry); - } + public static final String STATUS_ROLLED_BACK = "ROLLED_BACK"; - /** - * Create a new {@link Observation}. It's not started, you must explicitly call - * {@link Observation#start()} to start it. - *

- * Remember to register the {@link DefaultMeterObservationHandler} via the - * {@code Metrics.globalRegistry.withTimerObservationHandler()} in the user code. - * Otherwise you won't observe any metrics. - * @param name of the observation - * @param context of the batch job observation - * @return a new observation instance - * @since 5.0 - */ - public static Observation createObservation(String name, BatchJobContext context, - ObservationRegistry observationRegistry) { - return Observation.createNotStarted(name, context, observationRegistry); - } - - /** - * Create a new {@link Observation}. It's not started, you must explicitly call - * {@link Observation#start()} to start it. - *

- * Remember to register the {@link DefaultMeterObservationHandler} via the - * {@code Metrics.globalRegistry.withTimerObservationHandler()} in the user code. - * Otherwise you won't observe any metrics. - * @param name of the observation - * @param context of the observation step context - * @return a new observation instance - * @since 5.0 - */ - public static Observation createObservation(String name, BatchStepContext context, - ObservationRegistry observationRegistry) { - return Observation.createNotStarted(name, context, observationRegistry); - } - - /** - * Create a new {@link Timer.Sample}. - * @param meterRegistry the meter registry to use - * @return a new timer sample instance - */ - public static Timer.Sample createTimerSample(MeterRegistry meterRegistry) { - return Timer.start(meterRegistry); - } - - /** - * Create a new {@link LongTaskTimer}. - * @param meterRegistry the meter registry to use - * @param name of the long task timer. Will be prefixed with - * {@link BatchMetrics#METRICS_PREFIX}. - * @param description of the long task timer. - * @param tags of the timer - * @return a new long task timer instance - */ - public static LongTaskTimer createLongTaskTimer(MeterRegistry meterRegistry, String name, String description, - Tag... tags) { - return LongTaskTimer.builder(METRICS_PREFIX + name) - .description(description) - .tags(Arrays.asList(tags)) - .register(meterRegistry); + private BatchMetrics() { } /** @@ -176,7 +73,7 @@ public static String formatDuration(@Nullable Duration duration) { StringBuilder formattedDuration = new StringBuilder(); long hours = duration.toHours(); long minutes = duration.toMinutes(); - long seconds = duration.getSeconds(); + long seconds = duration.toSeconds(); long millis = duration.toMillis(); if (hours != 0) { formattedDuration.append(hours).append("h"); diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/observability/BatchStepContext.java b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/BatchStepContext.java deleted file mode 100644 index 7b1a3a0bdc..0000000000 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/observability/BatchStepContext.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 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.core.observability; - -import io.micrometer.observation.Observation; - -import org.springframework.batch.core.StepExecution; - -import java.util.function.Supplier; - -/** - * Observation context for batch steps. - * - * @author Marcin Grzejszczak - * @since 5.0 - */ -public class BatchStepContext extends Observation.Context implements Supplier { - - private final StepExecution stepExecution; - - public BatchStepContext(StepExecution stepExecution) { - this.stepExecution = stepExecution; - } - - public StepExecution getStepExecution() { - return stepExecution; - } - - @Override - public BatchStepContext get() { - return this; - } - -} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/observability/BatchStepObservation.java b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/BatchStepObservation.java deleted file mode 100644 index 7244fe02b8..0000000000 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/observability/BatchStepObservation.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright 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.core.observability; - -import io.micrometer.common.docs.KeyName; -import io.micrometer.observation.docs.ObservationDocumentation; - -/** - * Observation created around a step execution. - * - * @author Marcin Grzejszczak - * @author Mahmoud Ben Hassine - * @since 5.0 - */ -public enum BatchStepObservation implements ObservationDocumentation { - - BATCH_STEP_OBSERVATION { - @Override - public String getName() { - return "spring.batch.step"; - } - - @Override - public String getContextualName() { - return "%s"; - } - - @Override - public KeyName[] getLowCardinalityKeyNames() { - return StepLowCardinalityTags.values(); - } - - @Override - public KeyName[] getHighCardinalityKeyNames() { - return StepHighCardinalityTags.values(); - } - - @Override - public String getPrefix() { - return "spring.batch"; - } - }; - - enum StepLowCardinalityTags implements KeyName { - - /** - * Name of the Spring Batch step. - */ - STEP_NAME { - @Override - public String asString() { - return "spring.batch.step.name"; - } - }, - - /** - * Type of the Spring Batch step. - */ - STEP_TYPE { - @Override - public String asString() { - return "spring.batch.step.type"; - } - }, - - /** - * Name of the Spring Batch job enclosing the step. - */ - JOB_NAME { - @Override - public String asString() { - return "spring.batch.step.job.name"; - } - }, - - /** - * Step status. - */ - STEP_STATUS { - @Override - public String asString() { - return "spring.batch.step.status"; - } - } - - } - - enum StepHighCardinalityTags implements KeyName { - - /** - * ID of the Spring Batch step execution. - */ - STEP_EXECUTION_ID { - @Override - public String asString() { - return "spring.batch.step.executionId"; - } - } - - } - -} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/observability/DefaultBatchJobObservationConvention.java b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/DefaultBatchJobObservationConvention.java deleted file mode 100644 index 84712acf62..0000000000 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/observability/DefaultBatchJobObservationConvention.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 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.core.observability; - -import io.micrometer.common.KeyValues; - -import org.springframework.batch.core.JobExecution; - -/** - * Default {@link BatchJobObservationConvention} implementation. - * - * @author Marcin Grzejszczak - * @author Mahmoud Ben Hassine - * @since 5.0 - */ -public class DefaultBatchJobObservationConvention implements BatchJobObservationConvention { - - @Override - public KeyValues getLowCardinalityKeyValues(BatchJobContext context) { - JobExecution execution = context.getJobExecution(); - return KeyValues.of( - BatchJobObservation.JobLowCardinalityTags.JOB_NAME.withValue(execution.getJobInstance().getJobName()), - BatchJobObservation.JobLowCardinalityTags.JOB_STATUS - .withValue(execution.getExitStatus().getExitCode())); - } - - @Override - public KeyValues getHighCardinalityKeyValues(BatchJobContext context) { - JobExecution execution = context.getJobExecution(); - return KeyValues.of( - BatchJobObservation.JobHighCardinalityTags.JOB_INSTANCE_ID - .withValue(String.valueOf(execution.getJobInstance().getInstanceId())), - BatchJobObservation.JobHighCardinalityTags.JOB_EXECUTION_ID - .withValue(String.valueOf(execution.getId()))); - } - -} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/observability/DefaultBatchStepObservationConvention.java b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/DefaultBatchStepObservationConvention.java deleted file mode 100644 index 6fcf6b0508..0000000000 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/observability/DefaultBatchStepObservationConvention.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 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.core.observability; - -import io.micrometer.common.KeyValues; - -import org.springframework.batch.core.StepExecution; - -/** - * Default {@link BatchStepObservationConvention} implementation. - * - * @author Marcin Grzejszczak - * @author Mahmoud Ben Hassine - * @since 5.0 - */ -public class DefaultBatchStepObservationConvention implements BatchStepObservationConvention { - - @Override - public KeyValues getLowCardinalityKeyValues(BatchStepContext context) { - StepExecution execution = context.getStepExecution(); - return KeyValues.of(BatchStepObservation.StepLowCardinalityTags.STEP_NAME.withValue(execution.getStepName()), - BatchStepObservation.StepLowCardinalityTags.JOB_NAME - .withValue(execution.getJobExecution().getJobInstance().getJobName()), - BatchStepObservation.StepLowCardinalityTags.STEP_STATUS - .withValue(execution.getExitStatus().getExitCode())); - } - - @Override - public KeyValues getHighCardinalityKeyValues(BatchStepContext context) { - StepExecution execution = context.getStepExecution(); - return KeyValues.of(BatchStepObservation.StepHighCardinalityTags.STEP_EXECUTION_ID - .withValue(String.valueOf(execution.getId()))); - } - -} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/job/JobExecutionEvent.java b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/job/JobExecutionEvent.java new file mode 100644 index 0000000000..416c524444 --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/job/JobExecutionEvent.java @@ -0,0 +1,46 @@ +/* + * Copyright 2025-present 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.observability.jfr.events.job; + +import jdk.jfr.Category; +import jdk.jfr.Description; +import jdk.jfr.Event; +import jdk.jfr.Label; + +@Label("Job Execution") +@Description("Job Execution Event") +@Category({ "Spring Batch", "Job" }) +public class JobExecutionEvent extends Event { + + @Label("Job Name") + public String jobName; + + @Label("Job Instance Id") + public long jobInstanceId; + + @Label("Job Execution Id") + public long jobExecutionId; + + @Label("Job Exit Status") + public String exitStatus; + + public JobExecutionEvent(String jobName, long jobInstanceId, long jobExecutionId) { + this.jobName = jobName; + this.jobInstanceId = jobInstanceId; + this.jobExecutionId = jobExecutionId; + } + +} \ No newline at end of file diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/job/JobLaunchEvent.java b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/job/JobLaunchEvent.java new file mode 100644 index 0000000000..269d099196 --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/job/JobLaunchEvent.java @@ -0,0 +1,39 @@ +/* + * Copyright 2025-present 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.observability.jfr.events.job; + +import jdk.jfr.Category; +import jdk.jfr.Description; +import jdk.jfr.Event; +import jdk.jfr.Label; + +@Label("Job Launch Request") +@Description("Job Launch Request Event") +@Category({ "Spring Batch", "Job" }) +public class JobLaunchEvent extends Event { + + @Label("Job Name") + public String jobName; + + @Label("Job Parameters") + public String jobParameters; + + public JobLaunchEvent(String jobName, String jobParameters) { + this.jobParameters = jobParameters; + this.jobName = jobName; + } + +} \ No newline at end of file diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/StepExecutionEvent.java b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/StepExecutionEvent.java new file mode 100644 index 0000000000..6e7784fc79 --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/StepExecutionEvent.java @@ -0,0 +1,50 @@ +/* + * Copyright 2025-present 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.observability.jfr.events.step; + +import jdk.jfr.Category; +import jdk.jfr.Description; +import jdk.jfr.Event; +import jdk.jfr.Label; + +@Label("Step Execution") +@Description("Step Execution Event") +@Category({ "Spring Batch", "Step" }) +public class StepExecutionEvent extends Event { + + @Label("Step Name") + public String stepName; + + @Label("Job Name") + public String jobName; + + @Label("Step Execution Id") + public long stepExecutionId; + + @Label("Job Execution Id") + public long jobExecutionId; + + @Label("Step Exit Status") + public String exitStatus; + + public StepExecutionEvent(String stepName, String jobName, long stepExecutionId, long jobExecutionId) { + this.stepName = stepName; + this.jobName = jobName; + this.stepExecutionId = stepExecutionId; + this.jobExecutionId = jobExecutionId; + } + +} \ No newline at end of file diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/chunk/ChunkScanEvent.java b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/chunk/ChunkScanEvent.java new file mode 100644 index 0000000000..d5d11e7d1b --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/chunk/ChunkScanEvent.java @@ -0,0 +1,42 @@ +/* + * Copyright 2025-present 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.observability.jfr.events.step.chunk; + +import jdk.jfr.Category; +import jdk.jfr.Description; +import jdk.jfr.Event; +import jdk.jfr.Label; + +@Label("Chunk Scan") +@Description("Chunk Scan Event") +@Category({ "Spring Batch", "Step", "Chunk" }) +public class ChunkScanEvent extends Event { + + @Label("Step Name") + public String stepName; + + @Label("Step Execution Id") + public long stepExecutionId; + + @Label("Skip Count") + public long skipCount; + + public ChunkScanEvent(String stepName, long stepExecutionId) { + this.stepName = stepName; + this.stepExecutionId = stepExecutionId; + } + +} \ No newline at end of file diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/chunk/ChunkTransactionEvent.java b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/chunk/ChunkTransactionEvent.java new file mode 100644 index 0000000000..695f3afcfa --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/chunk/ChunkTransactionEvent.java @@ -0,0 +1,42 @@ +/* + * Copyright 2025-present 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.observability.jfr.events.step.chunk; + +import jdk.jfr.Category; +import jdk.jfr.Description; +import jdk.jfr.Event; +import jdk.jfr.Label; + +@Label("Chunk Transaction") +@Description("Chunk Transaction Event") +@Category({ "Spring Batch", "Step", "Chunk" }) +public class ChunkTransactionEvent extends Event { + + @Label("Step Name") + public String stepName; + + @Label("Step Execution Id") + public long stepExecutionId; + + @Label("Transaction Status") + public String transactionStatus; + + public ChunkTransactionEvent(String stepName, long stepExecutionId) { + this.stepName = stepName; + this.stepExecutionId = stepExecutionId; + } + +} \ No newline at end of file diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/chunk/ChunkWriteEvent.java b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/chunk/ChunkWriteEvent.java new file mode 100644 index 0000000000..6139abb60b --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/chunk/ChunkWriteEvent.java @@ -0,0 +1,46 @@ +/* + * Copyright 2025-present 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.observability.jfr.events.step.chunk; + +import jdk.jfr.Category; +import jdk.jfr.Description; +import jdk.jfr.Event; +import jdk.jfr.Label; + +@Label("Chunk Write") +@Description("Chunk Write Event") +@Category({ "Spring Batch", "Step", "Chunk" }) +public class ChunkWriteEvent extends Event { + + @Label("Step Name") + public String stepName; + + @Label("Step Execution Id") + public long stepExecutionId; + + @Label("Chunk Write Status") + public String chunkWriteStatus; + + @Label("Item Count") + public long itemCount; + + public ChunkWriteEvent(String stepName, long stepExecutionId, long itemCount) { + this.itemCount = itemCount; + this.stepName = stepName; + this.stepExecutionId = stepExecutionId; + } + +} \ No newline at end of file diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/chunk/ItemProcessEvent.java b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/chunk/ItemProcessEvent.java new file mode 100644 index 0000000000..358794dcff --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/chunk/ItemProcessEvent.java @@ -0,0 +1,42 @@ +/* + * Copyright 2025-present 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.observability.jfr.events.step.chunk; + +import jdk.jfr.Category; +import jdk.jfr.Description; +import jdk.jfr.Event; +import jdk.jfr.Label; + +@Label("Item Process") +@Description("Item Process Event") +@Category({ "Spring Batch", "Step", "Chunk" }) +public class ItemProcessEvent extends Event { + + @Label("Step Name") + public String stepName; + + @Label("Step Execution Id") + public long stepExecutionId; + + @Label("Item Process Status") + public String itemProcessStatus; + + public ItemProcessEvent(String stepName, long stepExecutionId) { + this.stepName = stepName; + this.stepExecutionId = stepExecutionId; + } + +} \ No newline at end of file diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/chunk/ItemReadEvent.java b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/chunk/ItemReadEvent.java new file mode 100644 index 0000000000..5e55c0de3d --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/chunk/ItemReadEvent.java @@ -0,0 +1,42 @@ +/* + * Copyright 2025-present 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.observability.jfr.events.step.chunk; + +import jdk.jfr.Category; +import jdk.jfr.Description; +import jdk.jfr.Event; +import jdk.jfr.Label; + +@Label("Item Read") +@Description("Item Read Event") +@Category({ "Spring Batch", "Step", "Chunk" }) +public class ItemReadEvent extends Event { + + @Label("Step Name") + public String stepName; + + @Label("Step Execution Id") + public long stepExecutionId; + + @Label("Item Read Status") + public String itemReadStatus; + + public ItemReadEvent(String stepName, long stepExecutionId) { + this.stepName = stepName; + this.stepExecutionId = stepExecutionId; + } + +} \ No newline at end of file diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/partition/PartitionAggregateEvent.java b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/partition/PartitionAggregateEvent.java new file mode 100644 index 0000000000..f516b08de1 --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/partition/PartitionAggregateEvent.java @@ -0,0 +1,39 @@ +/* + * Copyright 2025-present 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.observability.jfr.events.step.partition; + +import jdk.jfr.Category; +import jdk.jfr.Description; +import jdk.jfr.Event; +import jdk.jfr.Label; + +@Label("Partition Aggregate") +@Description("Partition Aggregate Event") +@Category({ "Spring Batch", "Step", "Partition" }) +public class PartitionAggregateEvent extends Event { + + @Label("Step Name") + public String stepName; + + @Label("Step Execution Id") + public long stepExecutionId; + + public PartitionAggregateEvent(String stepName, long stepExecutionId) { + this.stepName = stepName; + this.stepExecutionId = stepExecutionId; + } + +} \ No newline at end of file diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/partition/PartitionSplitEvent.java b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/partition/PartitionSplitEvent.java new file mode 100644 index 0000000000..26504edc2f --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/partition/PartitionSplitEvent.java @@ -0,0 +1,42 @@ +/* + * Copyright 2025-present 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.observability.jfr.events.step.partition; + +import jdk.jfr.Category; +import jdk.jfr.Description; +import jdk.jfr.Event; +import jdk.jfr.Label; + +@Label("Partition Split") +@Description("Partition Split Event") +@Category({ "Spring Batch", "Step", "Partition" }) +public class PartitionSplitEvent extends Event { + + @Label("Step Name") + public String stepName; + + @Label("Step Execution Id") + public long stepExecutionId; + + @Label("Partition count") + public long partitionCount; + + public PartitionSplitEvent(String stepName, long stepExecutionId) { + this.stepName = stepName; + this.stepExecutionId = stepExecutionId; + } + +} \ No newline at end of file diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/tasklet/TaskletExecutionEvent.java b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/tasklet/TaskletExecutionEvent.java new file mode 100644 index 0000000000..97fbb7b6d7 --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/tasklet/TaskletExecutionEvent.java @@ -0,0 +1,46 @@ +/* + * Copyright 2025-present 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.observability.jfr.events.step.tasklet; + +import jdk.jfr.Category; +import jdk.jfr.Description; +import jdk.jfr.Event; +import jdk.jfr.Label; + +@Label("Tasklet Execution") +@Description("Tasklet Execution Event") +@Category({ "Spring Batch", "Step", "Tasklet" }) +public class TaskletExecutionEvent extends Event { + + @Label("Step Name") + public String stepName; + + @Label("Step Execution Id") + public long stepExecutionId; + + @Label("Tasklet Type") + public String taskletType; + + @Label("Tasklet Status") + public String taskletStatus; + + public TaskletExecutionEvent(String stepName, long stepExecutionId, String taskletType) { + this.taskletType = taskletType; + this.stepName = stepName; + this.stepExecutionId = stepExecutionId; + } + +} \ No newline at end of file diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/observability/micrometer/MicrometerMetrics.java b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/micrometer/MicrometerMetrics.java new file mode 100644 index 0000000000..cd0d7e9ff9 --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/micrometer/MicrometerMetrics.java @@ -0,0 +1,111 @@ +/* + * Copyright 2025-present 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.observability.micrometer; + +import java.util.Arrays; + +import io.micrometer.core.instrument.Counter; +import io.micrometer.core.instrument.LongTaskTimer; +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.Tag; +import io.micrometer.core.instrument.Timer; +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationRegistry; + +import org.springframework.batch.core.observability.BatchMetrics; + +/** + * Central class for Micrometer metrics. Only intended for internal use. + * + * @author Mahmoud Ben Hassine + * @since 6.0 + */ +public final class MicrometerMetrics { + + private MicrometerMetrics() { + } + + /** + * Create a new {@link Observation}. It's not started, you must explicitly call + * {@link Observation#start()} to start it. + * @param name of the observation + * @param observationRegistry the observation registry to use + * @return a new observation instance + * @since 6.0 + */ + public static Observation createObservation(String name, ObservationRegistry observationRegistry) { + return Observation.createNotStarted(name, observationRegistry); + } + + /** + * Create a {@link Timer}. + * @param meterRegistry the meter registry to use + * @param name of the timer. Will be prefixed with + * {@link BatchMetrics#METRICS_PREFIX}. + * @param description of the timer + * @param tags of the timer + * @return a new timer instance + */ + public static Timer createTimer(MeterRegistry meterRegistry, String name, String description, Tag... tags) { + return Timer.builder(BatchMetrics.METRICS_PREFIX + name) + .description(description) + .tags(Arrays.asList(tags)) + .register(meterRegistry); + } + + /** + * Create a {@link Counter}. + * @param meterRegistry the meter registry to use + * @param name of the counter. Will be prefixed with + * {@link BatchMetrics#METRICS_PREFIX}. + * @param description of the counter + * @param tags of the counter + * @return a new timer instance + */ + public static Counter createCounter(MeterRegistry meterRegistry, String name, String description, Tag... tags) { + return Counter.builder(BatchMetrics.METRICS_PREFIX + name) + .description(description) + .tags(Arrays.asList(tags)) + .register(meterRegistry); + } + + /** + * Create a new {@link Timer.Sample}. + * @param meterRegistry the meter registry to use + * @return a new timer sample instance + */ + public static Timer.Sample createTimerSample(MeterRegistry meterRegistry) { + return Timer.start(meterRegistry); + } + + /** + * Create a new {@link LongTaskTimer}. + * @param meterRegistry the meter registry to use + * @param name of the long task timer. Will be prefixed with + * {@link BatchMetrics#METRICS_PREFIX}. + * @param description of the long task timer. + * @param tags of the timer + * @return a new long task timer instance + */ + public static LongTaskTimer createLongTaskTimer(MeterRegistry meterRegistry, String name, String description, + Tag... tags) { + return LongTaskTimer.builder(BatchMetrics.METRICS_PREFIX + name) + .description(description) + .tags(Arrays.asList(tags)) + .register(meterRegistry); + } + +} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/partition/PartitionHandler.java b/spring-batch-core/src/main/java/org/springframework/batch/core/partition/PartitionHandler.java index 81373f9cae..648bc46473 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/partition/PartitionHandler.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/partition/PartitionHandler.java @@ -18,7 +18,7 @@ import java.util.Collection; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.item.ExecutionContext; /** diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/PartitionNameProvider.java b/spring-batch-core/src/main/java/org/springframework/batch/core/partition/PartitionNameProvider.java similarity index 87% rename from spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/PartitionNameProvider.java rename to spring-batch-core/src/main/java/org/springframework/batch/core/partition/PartitionNameProvider.java index 6198b3a1d9..745111eb57 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/PartitionNameProvider.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/partition/PartitionNameProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2009 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. @@ -14,7 +14,9 @@ * limitations under the License. */ -package org.springframework.batch.core.partition.support; +package org.springframework.batch.core.partition; + +import org.springframework.batch.core.partition.support.SimplePartitioner; import java.util.Collection; @@ -33,6 +35,7 @@ *

* * @author Dave Syer + * @author Mahmoud Ben Hassine * @since 2.1.3 * */ diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/PartitionStep.java b/spring-batch-core/src/main/java/org/springframework/batch/core/partition/PartitionStep.java similarity index 73% rename from spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/PartitionStep.java rename to spring-batch-core/src/main/java/org/springframework/batch/core/partition/PartitionStep.java index 2b84033c87..4cf74fb1f0 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/PartitionStep.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/partition/PartitionStep.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. @@ -14,14 +14,16 @@ * limitations under the License. */ -package org.springframework.batch.core.partition.support; +package org.springframework.batch.core.partition; import org.springframework.batch.core.BatchStatus; -import org.springframework.batch.core.JobExecutionException; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.partition.PartitionHandler; -import org.springframework.batch.core.partition.StepExecutionSplitter; +import org.springframework.batch.core.job.JobExecutionException; +import org.springframework.batch.core.observability.jfr.events.step.partition.PartitionAggregateEvent; +import org.springframework.batch.core.observability.jfr.events.step.partition.PartitionSplitEvent; +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.step.StepExecution; +import org.springframework.batch.core.partition.support.DefaultStepExecutionAggregator; import org.springframework.batch.core.step.AbstractStep; import org.springframework.batch.item.ExecutionContext; import org.springframework.util.Assert; @@ -44,6 +46,15 @@ public class PartitionStep extends AbstractStep { private StepExecutionAggregator stepExecutionAggregator = new DefaultStepExecutionAggregator(); + /** + * Create a new instance of a {@link PartitionStep} with the given job repository. + * @param jobRepository the job repository to use. Must not be null. + * @since 6.0 + */ + public PartitionStep(JobRepository jobRepository) { + super(jobRepository); + } + /** * A {@link PartitionHandler} which can send out step executions for remote processing * and bring back the results. @@ -98,10 +109,21 @@ public void afterPropertiesSet() throws Exception { protected void doExecute(StepExecution stepExecution) throws Exception { stepExecution.getExecutionContext().put(STEP_TYPE_KEY, this.getClass().getName()); - // Wait for task completion and then aggregate the results + // Split execution into partitions and wait for task completion + PartitionSplitEvent partitionSplitEvent = new PartitionSplitEvent(stepExecution.getStepName(), + stepExecution.getId()); + partitionSplitEvent.begin(); Collection executions = partitionHandler.handle(stepExecutionSplitter, stepExecution); + partitionSplitEvent.partitionCount = executions.size(); stepExecution.upgradeStatus(BatchStatus.COMPLETED); + partitionSplitEvent.commit(); + + // aggregate the results of the executions + PartitionAggregateEvent partitionAggregateEvent = new PartitionAggregateEvent(stepExecution.getStepName(), + stepExecution.getId()); + partitionAggregateEvent.begin(); stepExecutionAggregator.aggregate(stepExecution, executions); + partitionAggregateEvent.commit(); // If anything failed or had a problem we need to crap out if (stepExecution.getStatus().isUnsuccessful()) { diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/Partitioner.java b/spring-batch-core/src/main/java/org/springframework/batch/core/partition/Partitioner.java similarity index 91% rename from spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/Partitioner.java rename to spring-batch-core/src/main/java/org/springframework/batch/core/partition/Partitioner.java index 2df66d1adb..5943450deb 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/Partitioner.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/partition/Partitioner.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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.batch.core.partition.support; +package org.springframework.batch.core.partition; import java.util.Map; @@ -28,6 +28,7 @@ * * @author Dave Syer * @author Taeik Lim + * @author Mahmoud Ben Hassine * @since 2.0 */ @FunctionalInterface diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/StepExecutionAggregator.java b/spring-batch-core/src/main/java/org/springframework/batch/core/partition/StepExecutionAggregator.java similarity index 78% rename from spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/StepExecutionAggregator.java rename to spring-batch-core/src/main/java/org/springframework/batch/core/partition/StepExecutionAggregator.java index bffa64ade0..892d8df05e 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/StepExecutionAggregator.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/partition/StepExecutionAggregator.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. @@ -13,18 +13,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.batch.core.partition.support; +package org.springframework.batch.core.partition; import java.util.Collection; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.step.StepExecution; /** - * Strategy for a aggregating step executions, usually when they are the result of + * Strategy for aggregating step executions, usually when they are the result of * partitioned or remote execution. * * @author Dave Syer * @author Taeik Lim + * @author Mahmoud Ben Hassine * @since 2.1 * */ diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/partition/StepExecutionSplitter.java b/spring-batch-core/src/main/java/org/springframework/batch/core/partition/StepExecutionSplitter.java index 0b5e83f952..394effa2af 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/partition/StepExecutionSplitter.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/partition/StepExecutionSplitter.java @@ -16,9 +16,9 @@ package org.springframework.batch.core.partition; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobExecutionException; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobExecutionException; +import org.springframework.batch.core.step.StepExecution; import java.util.Set; diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/AbstractPartitionHandler.java b/spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/AbstractPartitionHandler.java index 0d4538511e..0d7692eba1 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/AbstractPartitionHandler.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/AbstractPartitionHandler.java @@ -18,16 +18,15 @@ import java.util.Collection; import java.util.Set; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.partition.PartitionHandler; import org.springframework.batch.core.partition.StepExecutionSplitter; /** * Base {@link PartitionHandler} implementation providing common base features. Subclasses - * are expected to implement only the - * {@link #doHandle(org.springframework.batch.core.StepExecution, java.util.Set)} method - * which returns with the result of the execution(s) or an exception if the step failed to - * process. + * are expected to implement only the {@link #doHandle(StepExecution, java.util.Set)} + * method which returns with the result of the execution(s) or an exception if the step + * failed to process. * * @author Sebastien Gerard * @author Dave Syer diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/DefaultStepExecutionAggregator.java b/spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/DefaultStepExecutionAggregator.java index 27ba91b018..1f0f4fe75b 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/DefaultStepExecutionAggregator.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/DefaultStepExecutionAggregator.java @@ -18,7 +18,8 @@ import org.springframework.batch.core.BatchStatus; import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.step.StepExecution; +import org.springframework.batch.core.partition.StepExecutionAggregator; import org.springframework.util.Assert; import java.util.Collection; diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/MultiResourcePartitioner.java b/spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/MultiResourcePartitioner.java index 32cfe6f066..6ed5de1c3a 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/MultiResourcePartitioner.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/MultiResourcePartitioner.java @@ -20,6 +20,7 @@ import java.util.HashMap; import java.util.Map; +import org.springframework.batch.core.partition.Partitioner; import org.springframework.batch.item.ExecutionContext; import org.springframework.core.io.Resource; import org.springframework.util.Assert; diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/RemoteStepExecutionAggregator.java b/spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/RemoteStepExecutionAggregator.java index 8f3b5f4f59..b603017e38 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/RemoteStepExecutionAggregator.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/RemoteStepExecutionAggregator.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. @@ -21,9 +21,10 @@ import java.util.Set; import java.util.stream.Collectors; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.explore.JobExplorer; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.step.StepExecution; +import org.springframework.batch.core.partition.StepExecutionAggregator; +import org.springframework.batch.core.repository.JobRepository; import org.springframework.beans.factory.InitializingBean; import org.springframework.util.Assert; @@ -38,7 +39,7 @@ public class RemoteStepExecutionAggregator implements StepExecutionAggregator, I private StepExecutionAggregator delegate = new DefaultStepExecutionAggregator(); - private JobExplorer jobExplorer; + private JobRepository jobRepository; /** * Create a new instance (useful for configuration purposes). @@ -47,20 +48,20 @@ public RemoteStepExecutionAggregator() { } /** - * Create a new instance with a job explorer that can be used to refresh the data when - * aggregating. - * @param jobExplorer the {@link JobExplorer} to use + * Create a new instance with a job repository that can be used to refresh the data + * when aggregating. + * @param jobRepository the {@link JobRepository} to use */ - public RemoteStepExecutionAggregator(JobExplorer jobExplorer) { + public RemoteStepExecutionAggregator(JobRepository jobRepository) { super(); - this.jobExplorer = jobExplorer; + this.jobRepository = jobRepository; } /** - * @param jobExplorer the jobExplorer to set + * @param jobRepository the jobRepository to set */ - public void setJobExplorer(JobExplorer jobExplorer) { - this.jobExplorer = jobExplorer; + public void setJobRepository(JobRepository jobRepository) { + this.jobRepository = jobRepository; } /** @@ -75,13 +76,13 @@ public void setDelegate(StepExecutionAggregator delegate) { */ @Override public void afterPropertiesSet() throws Exception { - Assert.state(jobExplorer != null, "A JobExplorer must be provided"); + Assert.state(jobRepository != null, "A JobRepository must be provided"); } /** * Aggregates the input executions into the result {@link StepExecution} delegating to * the delegate aggregator once the input has been refreshed from the - * {@link JobExplorer}. + * {@link JobRepository}. * * @see StepExecutionAggregator #aggregate(StepExecution, Collection) */ @@ -96,7 +97,7 @@ public void aggregate(StepExecution result, Collection executions Assert.state(id != null, "StepExecution has null id. It must be saved first: " + stepExecution); return id; }).collect(Collectors.toSet()); - JobExecution jobExecution = jobExplorer.getJobExecution(result.getJobExecutionId()); + JobExecution jobExecution = jobRepository.getJobExecution(result.getJobExecutionId()); Assert.state(jobExecution != null, "Could not load JobExecution from JobRepository for id " + result.getJobExecutionId()); List updates = jobExecution.getStepExecutions() 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..6f7230225f 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. @@ -19,13 +19,14 @@ import java.util.HashMap; import java.util.Map; +import org.springframework.batch.core.partition.Partitioner; import org.springframework.batch.item.ExecutionContext; /** * 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 diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/SimpleStepExecutionSplitter.java b/spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/SimpleStepExecutionSplitter.java index 699e95fc12..c31f85b62f 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/SimpleStepExecutionSplitter.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/SimpleStepExecutionSplitter.java @@ -24,11 +24,13 @@ import java.util.Set; import org.springframework.batch.core.BatchStatus; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobExecutionException; -import org.springframework.batch.core.JobInstance; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobExecutionException; +import org.springframework.batch.core.job.JobInstance; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.step.StepExecution; +import org.springframework.batch.core.partition.PartitionNameProvider; +import org.springframework.batch.core.partition.Partitioner; import org.springframework.batch.core.partition.StepExecutionSplitter; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.item.ExecutionContext; @@ -191,9 +193,9 @@ private Map getContexts(StepExecution stepExecution, i result = partitioner.partition(splitSize); } else { - if (partitioner instanceof PartitionNameProvider) { + if (partitioner instanceof PartitionNameProvider partitionNameProvider) { result = new HashMap<>(); - Collection names = ((PartitionNameProvider) partitioner).getPartitionNames(splitSize); + Collection names = partitionNameProvider.getPartitionNames(splitSize); for (String name : names) { /* * We need to return the same keys as the original (failed) execution, diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/TaskExecutorPartitionHandler.java b/spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/TaskExecutorPartitionHandler.java index 25f55aa78c..bc0ff6d1a7 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/TaskExecutorPartitionHandler.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/TaskExecutorPartitionHandler.java @@ -23,8 +23,8 @@ import org.springframework.batch.core.BatchStatus; import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.partition.PartitionHandler; import org.springframework.batch.core.step.StepHolder; import org.springframework.beans.factory.InitializingBean; diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/JobExecutionAlreadyRunningException.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/JobExecutionAlreadyRunningException.java index a2f682896e..43384a9902 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/JobExecutionAlreadyRunningException.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/JobExecutionAlreadyRunningException.java @@ -15,7 +15,7 @@ */ package org.springframework.batch.core.repository; -import org.springframework.batch.core.JobExecutionException; +import org.springframework.batch.core.job.JobExecutionException; /** * @author Dave Syer diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/JobInstanceAlreadyCompleteException.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/JobInstanceAlreadyCompleteException.java index 577ae8ad13..59dd702f70 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/JobInstanceAlreadyCompleteException.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/JobInstanceAlreadyCompleteException.java @@ -15,7 +15,7 @@ */ package org.springframework.batch.core.repository; -import org.springframework.batch.core.JobExecutionException; +import org.springframework.batch.core.job.JobExecutionException; /** * An exception indicating an illegal attempt to restart a job that was already completed 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..0fbf671699 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. @@ -16,12 +16,14 @@ package org.springframework.batch.core.repository; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobInstance; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobInstance; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.launch.NoSuchJobException; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.step.StepExecution; +import org.springframework.batch.core.repository.explore.JobExplorer; import org.springframework.batch.core.repository.dao.JobExecutionDao; import org.springframework.batch.core.repository.dao.JobInstanceDao; import org.springframework.batch.item.ExecutionContext; @@ -31,6 +33,7 @@ import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Set; /** *

@@ -48,51 +51,209 @@ * @author Mahmoud Ben Hassine * @author Parikshit Dutta */ -public interface JobRepository { +@SuppressWarnings("removal") +public interface JobRepository extends JobExplorer { + + /* + * =================================================================================== + * Read operations + * =================================================================================== + */ + + /* + * =================================================================================== + * Job 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 + * Query the repository for all unique {@link JobInstance} names (sorted + * alphabetically). + * @return the list of job names that have been executed. */ default List getJobNames() { return Collections.emptyList(); } + /* + * =================================================================================== + * Job instance operations + * =================================================================================== + */ + /** - * 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 + * Fetch {@link JobInstance} values in descending order of creation (and, therefore, + * usually, of first execution). + * @param jobName The name of the job to query. + * @param start The start index of the instances to return. + * @param count The maximum number of instances to return. + * @return the {@link JobInstance} values up to a maximum of count values. */ - default List findJobInstancesByName(String jobName, int start, int count) { + default List getJobInstances(String jobName, int start, int count) { 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. + * @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. + * @param jobName The name of the job. + * @return the last job instance by Id if any or {@code null} otherwise. + * + * @since 4.2 + */ + @Nullable + default JobInstance getLastJobInstance(String jobName) { + throw new UnsupportedOperationException(); + } + + /** + * @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 */ - default List findJobExecutions(JobInstance jobInstance) { + @Nullable + 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. + */ + default long getJobInstanceCount(@Nullable String jobName) throws NoSuchJobException { + throw new UnsupportedOperationException(); + } + + /* + * =================================================================================== + * 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 + default JobExecution getJobExecution(@Nullable Long executionId) { + throw new UnsupportedOperationException(); + } + + /** + * Retrieve job executions by their job instance. The corresponding step executions + * may not be fully hydrated (for example, their execution context may be missing), + * depending on the implementation. In that case, use + * {@link #getStepExecution(Long, Long)} to hydrate them. + * @param jobInstance The {@link JobInstance} to query. + * @return the list of all executions for the specified {@link JobInstance}. + */ + default List getJobExecutions(JobInstance jobInstance) { 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 + * Find the last {@link JobExecution} that has been created for a given + * {@link JobInstance}. + * @param jobInstance The {@code JobInstance} for which to find the last + * {@code JobExecution}. + * @return the last {@code JobExecution} that has been created for this instance or + * {@code null} if no job execution is found for the given job instance. + * + * @since 4.2 + */ + @Nullable + default JobExecution getLastJobExecution(JobInstance jobInstance) { + throw new UnsupportedOperationException(); + } + + /** + * @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 + * implementation. In that case, use {@link #getStepExecution(Long, Long)} to hydrate + * them. + * @param jobName The name of the job. + * @return the set of running executions for jobs with the specified name. + */ + default Set findRunningJobExecutions(@Nullable String jobName) { + return Collections.emptySet(); + } + + /* + * =================================================================================== + * Step execution operations + * =================================================================================== + */ + + /** + * 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 + 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(); + } + + /* + * =================================================================================== + * Write operations + * =================================================================================== */ - boolean isJobInstanceExists(String jobName, JobParameters jobParameters); /** * Create a new {@link JobInstance} with the name and job parameters provided. @@ -187,42 +348,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 diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/JobRestartException.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/JobRestartException.java index 4a33ee182e..21ec468b3e 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/JobRestartException.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/JobRestartException.java @@ -15,7 +15,7 @@ */ package org.springframework.batch.core.repository; -import org.springframework.batch.core.JobExecutionException; +import org.springframework.batch.core.job.JobExecutionException; /** * An exception indicating an illegal attempt to restart a job. diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/ExecutionContextDao.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/ExecutionContextDao.java index c27b7b264f..53921956e4 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/ExecutionContextDao.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/ExecutionContextDao.java @@ -18,8 +18,8 @@ import java.util.Collection; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.item.ExecutionContext; /** diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/Jackson2ExecutionContextStringSerializer.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/Jackson2ExecutionContextStringSerializer.java index 9890c09853..813a7756d5 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/Jackson2ExecutionContextStringSerializer.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/Jackson2ExecutionContextStringSerializer.java @@ -55,8 +55,8 @@ import com.fasterxml.jackson.databind.ser.std.StdSerializer; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; -import org.springframework.batch.core.JobParameter; -import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.job.parameters.JobParameter; +import org.springframework.batch.core.job.parameters.JobParameters; import org.springframework.batch.core.repository.ExecutionContextSerializer; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.util.Assert; @@ -173,7 +173,8 @@ private JobParametersModule() { addSerializer(JobParameter.class, new JobParameterSerializer(JobParameter.class)); } - private abstract class JobParametersMixIn { + @SuppressWarnings("unused") + private abstract static class JobParametersMixIn { @JsonIgnore abstract boolean isEmpty(); @@ -183,7 +184,7 @@ private abstract class JobParametersMixIn { } - private class JobParameterSerializer extends StdSerializer { + private static class JobParameterSerializer extends StdSerializer { protected JobParameterSerializer(Class type) { super(type); @@ -303,8 +304,9 @@ static class TrustedTypeIdResolver implements TypeIdResolver { "java.lang.Byte", "java.lang.Short", "java.lang.Integer", "java.lang.Long", "java.lang.Double", "java.lang.Float", "java.math.BigDecimal", "java.math.BigInteger", "java.lang.String", "java.lang.Character", "java.lang.CharSequence", "java.util.Properties", "[Ljava.util.Properties;", - "org.springframework.batch.core.JobParameter", "org.springframework.batch.core.JobParameters", - "java.util.concurrent.ConcurrentHashMap", "java.sql.Date"); + "org.springframework.batch.core.job.parameters.JobParameter", + "org.springframework.batch.core.job.parameters.JobParameters", "java.util.concurrent.ConcurrentHashMap", + "java.sql.Date"); private final Set trustedClassNames = new LinkedHashSet<>(TRUSTED_CLASS_NAMES); diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JobExecutionDao.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JobExecutionDao.java index ec8da0c7a6..4bdb677018 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JobExecutionDao.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JobExecutionDao.java @@ -19,8 +19,8 @@ import java.util.List; import java.util.Set; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobInstance; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobInstance; import org.springframework.lang.Nullable; /** 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..581e02c00d 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. @@ -18,9 +18,9 @@ import java.util.List; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobInstance; -import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobInstance; +import org.springframework.batch.core.job.parameters.JobParameters; import org.springframework.batch.core.launch.NoSuchJobException; import org.springframework.lang.Nullable; @@ -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/StepExecutionDao.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/StepExecutionDao.java index 58e43bd8ef..5bdc678471 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/StepExecutionDao.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/StepExecutionDao.java @@ -18,9 +18,9 @@ import java.util.Collection; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobInstance; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobInstance; +import org.springframework.batch.core.step.StepExecution; import org.springframework.lang.Nullable; public interface StepExecutionDao { 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/jdbc/JdbcExecutionContextDao.java similarity index 90% rename from spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JdbcExecutionContextDao.java rename to spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/jdbc/JdbcExecutionContextDao.java index 07915965c0..e585661a80 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/jdbc/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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.batch.core.repository.dao; +package org.springframework.batch.core.repository.dao.jdbc; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -27,21 +27,22 @@ import java.util.Collection; import java.util.HashMap; import java.util.Iterator; -import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; +import java.util.stream.Stream; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.repository.ExecutionContextSerializer; +import org.springframework.batch.core.repository.dao.AbstractJdbcBatchMetadataDao; +import org.springframework.batch.core.repository.dao.DefaultExecutionContextSerializer; +import org.springframework.batch.core.repository.dao.ExecutionContextDao; import org.springframework.batch.item.ExecutionContext; 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; @@ -57,6 +58,7 @@ * @author Michael Minella * @author David Turanski * @author Mahmoud Ben Hassine + * @author Yanming Zhou */ public class JdbcExecutionContextDao extends AbstractJdbcBatchMetadataDao implements ExecutionContextDao { @@ -110,8 +112,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(); @@ -154,13 +154,9 @@ public ExecutionContext getExecutionContext(JobExecution jobExecution) { Long executionId = jobExecution.getId(); Assert.notNull(executionId, "ExecutionId must not be null."); - List results = getJdbcTemplate().query(getQuery(FIND_JOB_EXECUTION_CONTEXT), - new ExecutionContextRowMapper(), executionId); - if (!results.isEmpty()) { - return results.get(0); - } - else { - return new ExecutionContext(); + try (Stream stream = getJdbcTemplate().queryForStream(getQuery(FIND_JOB_EXECUTION_CONTEXT), + new ExecutionContextRowMapper(), executionId)) { + return stream.findFirst().orElseGet(ExecutionContext::new); } } @@ -169,13 +165,9 @@ public ExecutionContext getExecutionContext(StepExecution stepExecution) { Long executionId = stepExecution.getId(); Assert.notNull(executionId, "ExecutionId must not be null."); - List results = getJdbcTemplate().query(getQuery(FIND_STEP_EXECUTION_CONTEXT), - new ExecutionContextRowMapper(), executionId); - if (results.size() > 0) { - return results.get(0); - } - else { - return new ExecutionContext(); + try (Stream stream = getJdbcTemplate().queryForStream(getQuery(FIND_STEP_EXECUTION_CONTEXT), + new ExecutionContextRowMapper(), executionId)) { + return stream.findFirst().orElseGet(ExecutionContext::new); } } @@ -268,15 +260,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 +289,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 +325,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/JdbcJobExecutionDao.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/jdbc/JdbcJobExecutionDao.java similarity index 90% rename from spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JdbcJobExecutionDao.java rename to spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/jdbc/JdbcJobExecutionDao.java index 9ec0a9e2d8..012f42982f 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JdbcJobExecutionDao.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/jdbc/JdbcJobExecutionDao.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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.batch.core.repository.dao; +package org.springframework.batch.core.repository.dao.jdbc; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -28,16 +28,17 @@ import java.util.Set; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; +import java.util.stream.Stream; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.batch.core.BatchStatus; import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobInstance; -import org.springframework.batch.core.JobParameter; -import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobInstance; +import org.springframework.batch.core.job.parameters.JobParameter; +import org.springframework.batch.core.job.parameters.JobParameters; import org.springframework.batch.core.converter.DateToStringConverter; import org.springframework.batch.core.converter.LocalDateTimeToStringConverter; import org.springframework.batch.core.converter.LocalDateToStringConverter; @@ -46,6 +47,9 @@ import org.springframework.batch.core.converter.StringToLocalDateConverter; import org.springframework.batch.core.converter.StringToLocalDateTimeConverter; import org.springframework.batch.core.converter.StringToLocalTimeConverter; +import org.springframework.batch.core.repository.dao.AbstractJdbcBatchMetadataDao; +import org.springframework.batch.core.repository.dao.JobExecutionDao; +import org.springframework.batch.core.repository.dao.NoSuchObjectException; import org.springframework.beans.factory.InitializingBean; import org.springframework.core.convert.support.ConfigurableConversionService; import org.springframework.core.convert.support.DefaultConversionService; @@ -74,6 +78,7 @@ * @author Dimitrios Liapis * @author Philippe Marschall * @author Jinwoo Bae + * @author Yanming Zhou */ public class JdbcJobExecutionDao extends AbstractJdbcBatchMetadataDao implements JobExecutionDao, InitializingBean { @@ -98,28 +103,22 @@ SELECT COUNT(*) private static final String UPDATE_JOB_EXECUTION = """ UPDATE %PREFIX%JOB_EXECUTION - SET START_TIME = ?, END_TIME = ?, STATUS = ?, EXIT_CODE = ?, EXIT_MESSAGE = ?, VERSION = ?, CREATE_TIME = ?, LAST_UPDATED = ? + SET START_TIME = ?, END_TIME = ?, STATUS = ?, EXIT_CODE = ?, EXIT_MESSAGE = ?, VERSION = VERSION + 1, CREATE_TIME = ?, LAST_UPDATED = ? WHERE JOB_EXECUTION_ID = ? AND VERSION = ? """; - private static final String FIND_JOB_EXECUTIONS = """ + private static final String GET_JOB_EXECUTIONS = """ SELECT JOB_EXECUTION_ID, START_TIME, END_TIME, STATUS, EXIT_CODE, EXIT_MESSAGE, CREATE_TIME, LAST_UPDATED, VERSION FROM %PREFIX%JOB_EXECUTION - WHERE JOB_INSTANCE_ID = ? - ORDER BY JOB_EXECUTION_ID DESC """; - private static final String GET_LAST_EXECUTION = """ - SELECT JOB_EXECUTION_ID, START_TIME, END_TIME, STATUS, EXIT_CODE, EXIT_MESSAGE, CREATE_TIME, LAST_UPDATED, VERSION - FROM %PREFIX%JOB_EXECUTION E - WHERE JOB_INSTANCE_ID = ? AND JOB_EXECUTION_ID IN (SELECT MAX(JOB_EXECUTION_ID) FROM %PREFIX%JOB_EXECUTION E2 WHERE E2.JOB_INSTANCE_ID = ?) - """; + private static final String FIND_JOB_EXECUTIONS = GET_JOB_EXECUTIONS + + " WHERE JOB_INSTANCE_ID = ? ORDER BY JOB_EXECUTION_ID DESC"; - private static final String GET_EXECUTION_BY_ID = """ - SELECT JOB_EXECUTION_ID, START_TIME, END_TIME, STATUS, EXIT_CODE, EXIT_MESSAGE, CREATE_TIME, LAST_UPDATED, VERSION - FROM %PREFIX%JOB_EXECUTION - WHERE JOB_EXECUTION_ID = ? - """; + private static final String GET_LAST_EXECUTION = GET_JOB_EXECUTIONS + + " WHERE JOB_INSTANCE_ID = ? AND JOB_EXECUTION_ID IN (SELECT MAX(JOB_EXECUTION_ID) FROM %PREFIX%JOB_EXECUTION E2 WHERE E2.JOB_INSTANCE_ID = ?)"; + + private static final String GET_EXECUTION_BY_ID = GET_JOB_EXECUTIONS + " WHERE JOB_EXECUTION_ID = ?"; private static final String GET_RUNNING_EXECUTIONS = """ SELECT E.JOB_EXECUTION_ID, E.START_TIME, E.END_TIME, E.STATUS, E.EXIT_CODE, E.EXIT_MESSAGE, E.CREATE_TIME, E.LAST_UPDATED, E.VERSION, E.JOB_INSTANCE_ID @@ -146,7 +145,7 @@ SELECT COUNT(*) private static final String DELETE_JOB_EXECUTION = """ DELETE FROM %PREFIX%JOB_EXECUTION - WHERE JOB_EXECUTION_ID = ? + WHERE JOB_EXECUTION_ID = ? AND VERSION = ? """; private static final String DELETE_JOB_EXECUTION_PARAMETERS = """ @@ -284,7 +283,6 @@ public void updateJobExecution(JobExecution jobExecution) { this.lock.lock(); try { - Integer version = jobExecution.getVersion() + 1; String exitDescription = jobExecution.getExitStatus().getExitDescription(); if (exitDescription != null && exitDescription.length() > exitMessageLength) { @@ -301,7 +299,7 @@ public void updateJobExecution(JobExecution jobExecution) { Timestamp lastUpdated = jobExecution.getLastUpdated() == null ? null : Timestamp.valueOf(jobExecution.getLastUpdated()); Object[] parameters = new Object[] { startTime, endTime, jobExecution.getStatus().toString(), - jobExecution.getExitStatus().getExitCode(), exitDescription, version, createTime, lastUpdated, + jobExecution.getExitStatus().getExitCode(), exitDescription, createTime, lastUpdated, jobExecution.getId(), jobExecution.getVersion() }; // Check if given JobExecution's Id already exists, if none is found @@ -315,7 +313,7 @@ public void updateJobExecution(JobExecution jobExecution) { int count = getJdbcTemplate().update(getQuery(UPDATE_JOB_EXECUTION), parameters, new int[] { Types.TIMESTAMP, Types.TIMESTAMP, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, - Types.INTEGER, Types.TIMESTAMP, Types.TIMESTAMP, Types.BIGINT, Types.INTEGER }); + Types.TIMESTAMP, Types.TIMESTAMP, Types.BIGINT, Types.INTEGER }); // Avoid concurrent modifications... if (count == 0) { @@ -339,16 +337,9 @@ public JobExecution getLastJobExecution(JobInstance jobInstance) { Long id = jobInstance.getId(); - List executions = getJdbcTemplate().query(getQuery(GET_LAST_EXECUTION), - new JobExecutionRowMapper(jobInstance), id, id); - - Assert.state(executions.size() <= 1, "There must be at most one latest job execution"); - - if (executions.isEmpty()) { - return null; - } - else { - return executions.get(0); + try (Stream stream = getJdbcTemplate().queryForStream(getQuery(GET_LAST_EXECUTION), + new JobExecutionRowMapper(jobInstance), id, id)) { + return stream.findFirst().orElse(null); } } @@ -395,7 +386,13 @@ public void synchronizeStatus(JobExecution jobExecution) { */ @Override public void deleteJobExecution(JobExecution jobExecution) { - getJdbcTemplate().update(getQuery(DELETE_JOB_EXECUTION), jobExecution.getId()); + int count = getJdbcTemplate().update(getQuery(DELETE_JOB_EXECUTION), jobExecution.getId(), + jobExecution.getVersion()); + + if (count == 0) { + throw new OptimisticLockingFailureException("Attempt to delete job execution id=" + jobExecution.getId() + + " with wrong version (" + jobExecution.getVersion() + ")"); + } } /** 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/jdbc/JdbcJobInstanceDao.java similarity index 79% rename from spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JdbcJobInstanceDao.java rename to spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/jdbc/JdbcJobInstanceDao.java index 27dcc8b7a2..6e39d21584 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/jdbc/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. @@ -14,23 +14,27 @@ * limitations under the License. */ -package org.springframework.batch.core.repository.dao; +package org.springframework.batch.core.repository.dao.jdbc; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Types; import java.util.ArrayList; import java.util.List; +import java.util.stream.Stream; -import org.springframework.batch.core.DefaultJobKeyGenerator; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobInstance; -import org.springframework.batch.core.JobKeyGenerator; -import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.job.DefaultJobKeyGenerator; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobInstance; +import org.springframework.batch.core.job.JobKeyGenerator; +import org.springframework.batch.core.job.parameters.JobParameters; import org.springframework.batch.core.launch.NoSuchJobException; +import org.springframework.batch.core.repository.dao.AbstractJdbcBatchMetadataDao; +import org.springframework.batch.core.repository.dao.JobInstanceDao; import org.springframework.beans.factory.InitializingBean; import org.springframework.dao.DataAccessException; import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.dao.OptimisticLockingFailureException; import org.springframework.jdbc.core.ResultSetExtractor; import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.support.incrementer.DataFieldMaxValueIncrementer; @@ -53,11 +57,14 @@ * @author Will Schipp * @author Mahmoud Ben Hassine * @author Parikshit Dutta + * @author Yanming Zhou */ public class JdbcJobInstanceDao extends AbstractJdbcBatchMetadataDao implements JobInstanceDao, InitializingBean { + @SuppressWarnings("unused") private static final String STAR_WILDCARD = "*"; + @SuppressWarnings("unused") private static final String SQL_WILDCARD = "%"; private static final String CREATE_JOB_INSTANCE = """ @@ -71,7 +78,7 @@ public class JdbcJobInstanceDao extends AbstractJdbcBatchMetadataDao implements WHERE JOB_NAME = ? """; - private static final String FIND_JOBS_WITH_KEY = FIND_JOBS_WITH_NAME + " and JOB_KEY = ?"; + private static final String FIND_JOBS_WITH_KEY = FIND_JOBS_WITH_NAME + " AND JOB_KEY = ?"; private static final String COUNT_JOBS_WITH_NAME = """ SELECT COUNT(*) @@ -79,11 +86,8 @@ SELECT COUNT(*) WHERE JOB_NAME = ? """; - private static final String FIND_JOBS_WITH_EMPTY_KEY = """ - SELECT JOB_INSTANCE_ID, JOB_NAME - FROM %PREFIX%JOB_INSTANCE - WHERE JOB_NAME = ? AND (JOB_KEY = ? OR JOB_KEY IS NULL) - """; + private static final String FIND_JOBS_WITH_EMPTY_KEY = FIND_JOBS_WITH_NAME + + " AND (JOB_KEY = ? OR JOB_KEY IS NULL)"; private static final String GET_JOB_FROM_ID = """ SELECT JOB_INSTANCE_ID, JOB_NAME, JOB_KEY, VERSION @@ -106,7 +110,7 @@ SELECT COUNT(*) private static final String FIND_LAST_JOBS_BY_NAME = """ SELECT JOB_INSTANCE_ID, JOB_NAME FROM %PREFIX%JOB_INSTANCE - WHERE JOB_NAME = ? + WHERE JOB_NAME LIKE ? ORDER BY JOB_INSTANCE_ID DESC """; @@ -116,20 +120,14 @@ SELECT COUNT(*) WHERE I1.JOB_NAME = ? AND I1.JOB_INSTANCE_ID = (SELECT MAX(I2.JOB_INSTANCE_ID) FROM %PREFIX%JOB_INSTANCE I2 WHERE I2.JOB_NAME = ?) """; - private static final String FIND_LAST_JOBS_LIKE_NAME = """ - SELECT JOB_INSTANCE_ID, JOB_NAME - FROM %PREFIX%JOB_INSTANCE - WHERE JOB_NAME LIKE ? ORDER BY JOB_INSTANCE_ID DESC - """; - private static final String DELETE_JOB_INSTANCE = """ DELETE FROM %PREFIX%JOB_INSTANCE - WHERE JOB_INSTANCE_ID = ? + WHERE JOB_INSTANCE_ID = ? AND VERSION = ? """; private DataFieldMaxValueIncrementer jobInstanceIncrementer; - private JobKeyGenerator jobKeyGenerator = new DefaultJobKeyGenerator(); + private JobKeyGenerator jobKeyGenerator = new DefaultJobKeyGenerator(); /** * In this JDBC implementation a job instance id is obtained by asking the @@ -178,21 +176,12 @@ public JobInstance getJobInstance(final String jobName, final JobParameters jobP RowMapper rowMapper = new JobInstanceRowMapper(); - List instances; - if (StringUtils.hasLength(jobKey)) { - instances = getJdbcTemplate().query(getQuery(FIND_JOBS_WITH_KEY), rowMapper, jobName, jobKey); - } - else { - instances = getJdbcTemplate().query(getQuery(FIND_JOBS_WITH_EMPTY_KEY), rowMapper, jobName, jobKey); + try (Stream stream = getJdbcTemplate().queryForStream( + getQuery(StringUtils.hasLength(jobKey) ? FIND_JOBS_WITH_KEY : FIND_JOBS_WITH_EMPTY_KEY), rowMapper, + jobName, jobKey)) { + return stream.findFirst().orElse(null); } - if (instances.isEmpty()) { - return null; - } - else { - Assert.state(instances.size() == 1, "instance count must be 1 but was " + instances.size()); - return instances.get(0); - } } @Override @@ -236,6 +225,10 @@ public List extractData(ResultSet rs) throws SQLException, DataAcce }; + if (jobName.contains(STAR_WILDCARD)) { + jobName = jobName.replaceAll("\\" + STAR_WILDCARD, SQL_WILDCARD); + } + return getJdbcTemplate().query(getQuery(FIND_LAST_JOBS_BY_NAME), extractor, jobName); } @@ -281,18 +274,13 @@ public long getJobInstanceCount(@Nullable String jobName) throws NoSuchJobExcept */ @Override public void deleteJobInstance(JobInstance jobInstance) { - getJdbcTemplate().update(getQuery(DELETE_JOB_INSTANCE), jobInstance.getId()); - } + int count = getJdbcTemplate().update(getQuery(DELETE_JOB_INSTANCE), jobInstance.getId(), + jobInstance.getVersion()); - /** - * 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); + if (count == 0) { + throw new OptimisticLockingFailureException("Attempt to delete job instance id=" + jobInstance.getId() + + " with wrong version (" + jobInstance.getVersion() + ")"); + } } /** @@ -343,33 +331,15 @@ 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. + */ + @SuppressWarnings("removal") + @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/JdbcStepExecutionDao.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/jdbc/JdbcStepExecutionDao.java similarity index 89% rename from spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JdbcStepExecutionDao.java rename to spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/jdbc/JdbcStepExecutionDao.java index 594e2e7eef..a9c910eb09 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/jdbc/JdbcStepExecutionDao.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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.batch.core.repository.dao; +package org.springframework.batch.core.repository.dao.jdbc; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -29,15 +29,18 @@ import java.util.List; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; +import java.util.stream.Stream; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.batch.core.BatchStatus; import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobInstance; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobInstance; +import org.springframework.batch.core.step.StepExecution; +import org.springframework.batch.core.repository.dao.AbstractJdbcBatchMetadataDao; +import org.springframework.batch.core.repository.dao.StepExecutionDao; import org.springframework.beans.factory.InitializingBean; import org.springframework.dao.OptimisticLockingFailureException; import org.springframework.jdbc.core.BatchPreparedStatementSetter; @@ -66,6 +69,7 @@ * @author Mahmoud Ben Hassine * @author Baris Cubukcuoglu * @author Minsoo Kim + * @author Yanming Zhou * @see StepExecutionDao */ public class JdbcStepExecutionDao extends AbstractJdbcBatchMetadataDao implements StepExecutionDao, InitializingBean { @@ -79,19 +83,19 @@ public class JdbcStepExecutionDao extends AbstractJdbcBatchMetadataDao implement private static final String UPDATE_STEP_EXECUTION = """ UPDATE %PREFIX%STEP_EXECUTION - SET START_TIME = ?, END_TIME = ?, STATUS = ?, COMMIT_COUNT = ?, READ_COUNT = ?, FILTER_COUNT = ?, WRITE_COUNT = ?, EXIT_CODE = ?, EXIT_MESSAGE = ?, VERSION = ?, READ_SKIP_COUNT = ?, PROCESS_SKIP_COUNT = ?, WRITE_SKIP_COUNT = ?, ROLLBACK_COUNT = ?, LAST_UPDATED = ? + SET START_TIME = ?, END_TIME = ?, STATUS = ?, COMMIT_COUNT = ?, READ_COUNT = ?, FILTER_COUNT = ?, WRITE_COUNT = ?, EXIT_CODE = ?, EXIT_MESSAGE = ?, VERSION = VERSION + 1, READ_SKIP_COUNT = ?, PROCESS_SKIP_COUNT = ?, WRITE_SKIP_COUNT = ?, ROLLBACK_COUNT = ?, LAST_UPDATED = ? WHERE STEP_EXECUTION_ID = ? AND VERSION = ? """; private static final String GET_RAW_STEP_EXECUTIONS = """ SELECT STEP_EXECUTION_ID, STEP_NAME, START_TIME, END_TIME, STATUS, COMMIT_COUNT, READ_COUNT, FILTER_COUNT, WRITE_COUNT, EXIT_CODE, EXIT_MESSAGE, READ_SKIP_COUNT, WRITE_SKIP_COUNT, PROCESS_SKIP_COUNT, ROLLBACK_COUNT, LAST_UPDATED, VERSION, CREATE_TIME FROM %PREFIX%STEP_EXECUTION - WHERE JOB_EXECUTION_ID = ? """; - private static final String GET_STEP_EXECUTIONS = GET_RAW_STEP_EXECUTIONS + " ORDER BY STEP_EXECUTION_ID"; + private static final String GET_STEP_EXECUTIONS = GET_RAW_STEP_EXECUTIONS + + " WHERE JOB_EXECUTION_ID = ? ORDER BY STEP_EXECUTION_ID"; - private static final String GET_STEP_EXECUTION = GET_RAW_STEP_EXECUTIONS + " AND STEP_EXECUTION_ID = ?"; + private static final String GET_STEP_EXECUTION = GET_RAW_STEP_EXECUTIONS + " WHERE STEP_EXECUTION_ID = ?"; private static final String GET_LAST_STEP_EXECUTION = """ SELECT SE.STEP_EXECUTION_ID, SE.STEP_NAME, SE.START_TIME, SE.END_TIME, SE.STATUS, SE.COMMIT_COUNT, SE.READ_COUNT, SE.FILTER_COUNT, SE.WRITE_COUNT, SE.EXIT_CODE, SE.EXIT_MESSAGE, SE.READ_SKIP_COUNT, SE.WRITE_SKIP_COUNT, SE.PROCESS_SKIP_COUNT, SE.ROLLBACK_COUNT, SE.LAST_UPDATED, SE.VERSION, SE.CREATE_TIME, JE.JOB_EXECUTION_ID, JE.START_TIME, JE.END_TIME, JE.STATUS, JE.EXIT_CODE, JE.EXIT_MESSAGE, JE.CREATE_TIME, JE.LAST_UPDATED, JE.VERSION @@ -114,7 +118,7 @@ SELECT COUNT(*) private static final String DELETE_STEP_EXECUTION = """ DELETE FROM %PREFIX%STEP_EXECUTION - WHERE STEP_EXECUTION_ID = ? + WHERE STEP_EXECUTION_ID = ? and VERSION = ? """; private static final Comparator BY_CREATE_TIME_DESC_ID_DESC = Comparator @@ -267,7 +271,6 @@ public void updateStepExecution(StepExecution stepExecution) { this.lock.lock(); try { - Integer version = stepExecution.getVersion() + 1; Timestamp startTime = stepExecution.getStartTime() == null ? null : Timestamp.valueOf(stepExecution.getStartTime()); Timestamp endTime = stepExecution.getEndTime() == null ? null @@ -277,14 +280,13 @@ public void updateStepExecution(StepExecution stepExecution) { Object[] parameters = new Object[] { startTime, endTime, stepExecution.getStatus().toString(), stepExecution.getCommitCount(), stepExecution.getReadCount(), stepExecution.getFilterCount(), stepExecution.getWriteCount(), stepExecution.getExitStatus().getExitCode(), exitDescription, - version, stepExecution.getReadSkipCount(), stepExecution.getProcessSkipCount(), + stepExecution.getReadSkipCount(), stepExecution.getProcessSkipCount(), 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, - Types.INTEGER }); + new int[] { Types.TIMESTAMP, Types.TIMESTAMP, Types.VARCHAR, Types.BIGINT, Types.BIGINT, + Types.BIGINT, Types.BIGINT, Types.VARCHAR, Types.VARCHAR, Types.BIGINT, Types.BIGINT, + Types.BIGINT, Types.BIGINT, Types.TIMESTAMP, Types.BIGINT, Types.INTEGER }); // Avoid concurrent modifications... if (count == 0) { @@ -325,16 +327,9 @@ private String truncateExitDescription(String description) { @Override @Nullable public StepExecution getStepExecution(JobExecution jobExecution, Long stepExecutionId) { - List executions = getJdbcTemplate().query(getQuery(GET_STEP_EXECUTION), - new StepExecutionRowMapper(jobExecution), jobExecution.getId(), stepExecutionId); - - Assert.state(executions.size() <= 1, - "There can be at most one step execution with given name for single job execution"); - if (executions.isEmpty()) { - return null; - } - else { - return executions.get(0); + try (Stream stream = getJdbcTemplate().queryForStream(getQuery(GET_STEP_EXECUTION), + new StepExecutionRowMapper(jobExecution), stepExecutionId)) { + return stream.findFirst().orElse(null); } } @@ -379,7 +374,13 @@ public long countStepExecutions(JobInstance jobInstance, String stepName) { */ @Override public void deleteStepExecution(StepExecution stepExecution) { - getJdbcTemplate().update(getQuery(DELETE_STEP_EXECUTION), stepExecution.getId()); + int count = getJdbcTemplate().update(getQuery(DELETE_STEP_EXECUTION), stepExecution.getId(), + stepExecution.getVersion()); + + if (count == 0) { + throw new OptimisticLockingFailureException("Attempt to delete step execution id=" + stepExecution.getId() + + " with wrong version (" + stepExecution.getVersion() + ")"); + } } private static class StepExecutionRowMapper implements RowMapper { @@ -396,15 +397,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()); 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/mongodb/MongoExecutionContextDao.java similarity index 80% rename from spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoExecutionContextDao.java rename to spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/mongodb/MongoExecutionContextDao.java index 485882a163..ce61e8b8d2 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/mongodb/MongoExecutionContextDao.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. @@ -13,13 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.batch.core.repository.dao; +package org.springframework.batch.core.repository.dao.mongodb; import java.util.Collection; -import java.util.Map; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.step.StepExecution; +import org.springframework.batch.core.repository.dao.ExecutionContextDao; import org.springframework.batch.item.ExecutionContext; import org.springframework.data.mongodb.core.MongoOperations; import org.springframework.data.mongodb.core.query.Query; @@ -46,8 +46,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 +58,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 +71,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 +84,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/mongodb/MongoJobExecutionDao.java similarity index 84% rename from spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoJobExecutionDao.java rename to spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/mongodb/MongoJobExecutionDao.java index c4525970d7..d95c8d9105 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/mongodb/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. @@ -13,20 +13,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.batch.core.repository.dao; +package org.springframework.batch.core.repository.dao.mongodb; import java.util.HashSet; import java.util.List; import java.util.Set; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobInstance; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobInstance; +import org.springframework.batch.core.repository.dao.JobExecutionDao; import org.springframework.batch.core.repository.persistence.converter.JobExecutionConverter; import org.springframework.batch.core.repository.persistence.converter.JobInstanceConverter; 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; @@ -126,29 +126,30 @@ 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)); } @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); } } 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/mongodb/MongoJobInstanceDao.java similarity index 84% rename from spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoJobInstanceDao.java rename to spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/mongodb/MongoJobInstanceDao.java index b967e35f77..2d742aa9e6 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/mongodb/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. @@ -13,18 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.batch.core.repository.dao; +package org.springframework.batch.core.repository.dao.mongodb; import java.util.List; -import org.springframework.batch.core.DefaultJobKeyGenerator; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobInstance; -import org.springframework.batch.core.JobKeyGenerator; -import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.job.DefaultJobKeyGenerator; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobInstance; +import org.springframework.batch.core.job.JobKeyGenerator; +import org.springframework.batch.core.job.parameters.JobParameters; import org.springframework.batch.core.launch.NoSuchJobException; +import org.springframework.batch.core.repository.dao.JobInstanceDao; import org.springframework.batch.core.repository.persistence.converter.JobInstanceConverter; -import org.springframework.data.domain.Example; import org.springframework.data.domain.Sort; import org.springframework.data.mongodb.core.MongoOperations; import org.springframework.data.mongodb.core.query.Query; @@ -48,7 +48,7 @@ public class MongoJobInstanceDao implements JobInstanceDao { private DataFieldMaxValueIncrementer jobInstanceIncrementer; - private JobKeyGenerator jobKeyGenerator = new DefaultJobKeyGenerator(); + private JobKeyGenerator jobKeyGenerator = new DefaultJobKeyGenerator(); private final JobInstanceConverter jobInstanceConverter = new JobInstanceConverter(); @@ -58,7 +58,7 @@ public MongoJobInstanceDao(MongoOperations mongoOperations) { this.jobInstanceIncrementer = new MongoSequenceIncrementer(mongoOperations, SEQUENCE_NAME); } - public void setJobKeyGenerator(JobKeyGenerator jobKeyGenerator) { + public void setJobKeyGenerator(JobKeyGenerator jobKeyGenerator) { this.jobKeyGenerator = jobKeyGenerator; } @@ -143,20 +143,15 @@ public List getJobNames() { .toList(); } + /** + * @deprecated since v6.0 and scheduled for removal in v6.2. Use + * {@link #getJobInstances(String, int, int)} instead. + */ + @SuppressWarnings("removal") + @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 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/mongodb/MongoSequenceIncrementer.java similarity index 57% rename from spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoSequenceIncrementer.java rename to spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/mongodb/MongoSequenceIncrementer.java index 683d2ad69e..9722db637f 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/mongodb/MongoSequenceIncrementer.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. @@ -13,22 +13,22 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.batch.core.repository.dao; +package org.springframework.batch.core.repository.dao.mongodb; + +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/java/org/springframework/batch/core/repository/dao/MongoStepExecutionDao.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/mongodb/MongoStepExecutionDao.java similarity index 92% rename from spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoStepExecutionDao.java rename to spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/mongodb/MongoStepExecutionDao.java index 44215babd7..a7bac8ce26 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/mongodb/MongoStepExecutionDao.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. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.batch.core.repository.dao; +package org.springframework.batch.core.repository.dao.mongodb; import java.util.ArrayList; import java.util.Collection; @@ -21,9 +21,10 @@ import java.util.List; import java.util.Optional; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobInstance; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobInstance; +import org.springframework.batch.core.step.StepExecution; +import org.springframework.batch.core.repository.dao.StepExecutionDao; import org.springframework.batch.core.repository.persistence.converter.JobExecutionConverter; import org.springframework.batch.core.repository.persistence.converter.StepExecutionConverter; import org.springframework.data.mongodb.core.MongoOperations; @@ -89,8 +90,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; } @@ -111,6 +113,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)); 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/repository/explore/JobExplorer.java similarity index 50% rename from spring-batch-core/src/main/java/org/springframework/batch/core/explore/JobExplorer.java rename to spring-batch-core/src/main/java/org/springframework/batch/core/repository/explore/JobExplorer.java index 85c69655f9..aae7366d76 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/repository/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. @@ -13,16 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.batch.core.explore; +package org.springframework.batch.core.repository.explore; +import java.util.Collections; import java.util.List; import java.util.Set; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobInstance; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobInstance; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.launch.NoSuchJobException; +import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.item.ExecutionContext; import org.springframework.lang.Nullable; @@ -37,9 +39,33 @@ * @author Mahmoud Ben Hassine * @author Parikshit Dutta * @since 2.0 + * @deprecated since 6.0 in favor of {@link JobRepository}. Scheduled for removal in 6.2 + * or later. */ +@Deprecated(since = "6.0", forRemoval = true) 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. + */ + default List getJobNames() { + return Collections.emptyList(); + } + + /* + * =================================================================================== + * Job instance operations + * =================================================================================== + */ + /** * Fetch {@link JobInstance} values in descending order of creation (and, therefore, * usually, of first execution). @@ -48,51 +74,77 @@ 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(); + } /** - * Find the last job instance, by ID, for the given job. - * @param jobName The name of the job. - * @return the last job instance by Id if any or {@code null} otherwise. - * - * @since 4.2 + * 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. + * @deprecated Since v6.0 and scheduled for removal in v6.2. Use + * {@link #getJobInstances(String, int, int)} */ - @Nullable - default JobInstance getLastJobInstance(String jobName) { - throw new UnsupportedOperationException(); + @Deprecated(since = "6.0", forRemoval = true) + default List findJobInstancesByJobName(String jobName, int start, int count) { + return Collections.emptyList(); } /** - * 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. + * 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)} */ - @Nullable - JobExecution getJobExecution(@Nullable Long executionId); + @Deprecated(since = "6.0", forRemoval = true) + default List findJobInstancesByName(String jobName, int start, int count) { + return Collections.emptyList(); + } /** - * 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) + * 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. */ - @Nullable - StepExecution getStepExecution(@Nullable Long jobExecutionId, @Nullable Long stepExecutionId); + @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 - JobInstance getJobInstance(@Nullable Long instanceId); + default JobInstance getJobInstance(@Nullable Long instanceId) { + throw new UnsupportedOperationException(); + } + + /** + * Find the last job instance, by ID, for the given job. + * @param jobName The name of the job. + * @return the last job instance by Id if any or {@code null} otherwise. + * + * @since 4.2 + */ + @Nullable + default JobInstance getLastJobInstance(String jobName) { + throw new UnsupportedOperationException(); + } /** * @param jobName {@link String} name of the job. @@ -107,6 +159,38 @@ 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. + */ + default long getJobInstanceCount(@Nullable String jobName) throws NoSuchJobException { + throw new UnsupportedOperationException(); + } + + /* + * =================================================================================== + * 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 + default JobExecution getJobExecution(@Nullable Long executionId) { + throw new UnsupportedOperationException(); + } + /** * Retrieve job executions by their job instance. The corresponding step executions * may not be fully hydrated (for example, their execution context may be missing), @@ -115,7 +199,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 @@ -132,6 +232,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 @@ -140,34 +250,49 @@ 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(); + } + + /* + * =================================================================================== + * Step execution operations + * =================================================================================== + */ /** - * Query the repository for all unique {@link JobInstance} names (sorted - * alphabetically). - * @return the list of job names that have been executed. + * 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) */ - List getJobNames(); + @Nullable + default StepExecution getStepExecution(@Nullable Long jobExecutionId, @Nullable Long stepExecutionId) { + throw new UnsupportedOperationException(); + } /** - * 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. + * @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. */ - List findJobInstancesByJobName(String jobName, int start, int count); + @Nullable + default StepExecution getLastStepExecution(JobInstance jobInstance, String stepName) { + 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. + * @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 getJobInstanceCount(@Nullable String jobName) throws NoSuchJobException; + default long getStepExecutionCount(JobInstance jobInstance, String stepName) { + throw new UnsupportedOperationException(); + } } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/explore/package-info.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/explore/package-info.java similarity index 76% rename from spring-batch-core/src/main/java/org/springframework/batch/core/explore/package-info.java rename to spring-batch-core/src/main/java/org/springframework/batch/core/repository/explore/package-info.java index b5671f50be..c759d4d869 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/explore/package-info.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/explore/package-info.java @@ -5,6 +5,6 @@ * @author Mahmoud Ben Hassine */ @NonNullApi -package org.springframework.batch.core.explore; +package org.springframework.batch.core.repository.explore; import org.springframework.lang.NonNullApi; diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/explore/support/AbstractJobExplorerFactoryBean.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/explore/support/AbstractJobExplorerFactoryBean.java similarity index 95% rename from spring-batch-core/src/main/java/org/springframework/batch/core/explore/support/AbstractJobExplorerFactoryBean.java rename to spring-batch-core/src/main/java/org/springframework/batch/core/repository/explore/support/AbstractJobExplorerFactoryBean.java index 8f6ae2052c..1b8627688b 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/explore/support/AbstractJobExplorerFactoryBean.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/explore/support/AbstractJobExplorerFactoryBean.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. @@ -14,12 +14,12 @@ * limitations under the License. */ -package org.springframework.batch.core.explore.support; +package org.springframework.batch.core.repository.explore.support; import java.util.Properties; import org.springframework.aop.framework.ProxyFactory; -import org.springframework.batch.core.explore.JobExplorer; +import org.springframework.batch.core.repository.explore.JobExplorer; import org.springframework.batch.core.repository.dao.ExecutionContextDao; import org.springframework.batch.core.repository.dao.JobExecutionDao; import org.springframework.batch.core.repository.dao.JobInstanceDao; @@ -43,7 +43,9 @@ * @author Dave Syer * @author Mahmoud Ben Hassine * @since 2.0 + * @deprecated since 6.0 with no replacement. Scheduled for removal in 6.2 or later. */ +@Deprecated(since = "6.0", forRemoval = true) public abstract class AbstractJobExplorerFactoryBean implements FactoryBean, InitializingBean { private static final String TRANSACTION_ISOLATION_LEVEL_PREFIX = "ISOLATION_"; diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/explore/support/JdbcJobExplorerFactoryBean.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/explore/support/JdbcJobExplorerFactoryBean.java new file mode 100644 index 0000000000..495fff19e3 --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/explore/support/JdbcJobExplorerFactoryBean.java @@ -0,0 +1,36 @@ +/* + * 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. + * 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.explore.support; + +import org.springframework.batch.core.repository.support.JdbcJobRepositoryFactoryBean; +import org.springframework.beans.factory.FactoryBean; + +/** + * A {@link FactoryBean} that automates the creation of a {@link SimpleJobExplorer} by + * using JDBC DAO implementations. Requires the user to describe what kind of database + * they use. + * + * @author Dave Syer + * @author Mahmoud Ben Hassine + * @deprecated since 6.0 in favor of {@link JdbcJobRepositoryFactoryBean}. Scheduled for + * removal in 6.2 or later. + */ +@SuppressWarnings("removal") +@Deprecated(since = "6.0", forRemoval = true) +public class JdbcJobExplorerFactoryBean extends JobExplorerFactoryBean { + +} 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/repository/explore/support/JobExplorerFactoryBean.java similarity index 82% rename from spring-batch-core/src/main/java/org/springframework/batch/core/explore/support/JobExplorerFactoryBean.java rename to spring-batch-core/src/main/java/org/springframework/batch/core/repository/explore/support/JobExplorerFactoryBean.java index 9d3e24dae5..98da269d85 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/repository/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. @@ -14,15 +14,15 @@ * limitations under the License. */ -package org.springframework.batch.core.explore.support; +package org.springframework.batch.core.repository.explore.support; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import javax.sql.DataSource; -import org.springframework.batch.core.DefaultJobKeyGenerator; -import org.springframework.batch.core.JobKeyGenerator; +import org.springframework.batch.core.job.DefaultJobKeyGenerator; +import org.springframework.batch.core.job.JobKeyGenerator; import org.springframework.batch.core.converter.DateToStringConverter; import org.springframework.batch.core.converter.LocalDateTimeToStringConverter; import org.springframework.batch.core.converter.LocalDateToStringConverter; @@ -35,14 +35,14 @@ import org.springframework.batch.core.repository.dao.AbstractJdbcBatchMetadataDao; import org.springframework.batch.core.repository.dao.DefaultExecutionContextSerializer; import org.springframework.batch.core.repository.dao.ExecutionContextDao; -import org.springframework.batch.core.repository.dao.JdbcExecutionContextDao; -import org.springframework.batch.core.repository.dao.JdbcJobExecutionDao; -import org.springframework.batch.core.repository.dao.JdbcJobInstanceDao; -import org.springframework.batch.core.repository.dao.JdbcStepExecutionDao; +import org.springframework.batch.core.repository.dao.jdbc.JdbcExecutionContextDao; +import org.springframework.batch.core.repository.dao.jdbc.JdbcJobExecutionDao; +import org.springframework.batch.core.repository.dao.jdbc.JdbcJobInstanceDao; +import org.springframework.batch.core.repository.dao.jdbc.JdbcStepExecutionDao; 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.batch.core.repository.support.JdbcJobRepositoryFactoryBean; import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.InitializingBean; import org.springframework.core.convert.support.ConfigurableConversionService; @@ -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; @@ -63,31 +62,32 @@ * @author Dave Syer * @author Mahmoud Ben Hassine * @since 2.0 + * @deprecated since 6.0 in favor of {@link JdbcJobRepositoryFactoryBean}. Scheduled for + * removal in 6.2 or later. */ +@Deprecated(since = "6.0", forRemoval = true) public class JobExplorerFactoryBean extends AbstractJobExplorerFactoryBean implements InitializingBean { - private DataSource dataSource; + protected DataSource dataSource; - private JdbcOperations jdbcOperations; + protected JdbcOperations jdbcOperations; - private String tablePrefix = AbstractJdbcBatchMetadataDao.DEFAULT_TABLE_PREFIX; + protected String tablePrefix = AbstractJdbcBatchMetadataDao.DEFAULT_TABLE_PREFIX; - private final DataFieldMaxValueIncrementer incrementer = new AbstractDataFieldMaxValueIncrementer() { + protected final DataFieldMaxValueIncrementer incrementer = new AbstractDataFieldMaxValueIncrementer() { @Override protected long getNextKey() { throw new IllegalStateException("JobExplorer is read only."); } }; - private JobKeyGenerator jobKeyGenerator; + protected JobKeyGenerator jobKeyGenerator; - private LobHandler lobHandler; + protected ExecutionContextSerializer serializer; - private ExecutionContextSerializer serializer; + protected Charset charset = StandardCharsets.UTF_8; - private Charset charset = StandardCharsets.UTF_8; - - private ConfigurableConversionService conversionService; + protected ConfigurableConversionService conversionService; /** * A custom implementation of {@link ExecutionContextSerializer}. The default, if not @@ -138,18 +138,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 +198,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/explore/support/MongoJobExplorerFactoryBean.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/explore/support/MongoJobExplorerFactoryBean.java similarity index 61% rename from spring-batch-core/src/main/java/org/springframework/batch/core/explore/support/MongoJobExplorerFactoryBean.java rename to spring-batch-core/src/main/java/org/springframework/batch/core/repository/explore/support/MongoJobExplorerFactoryBean.java index 8b24b7febb..13997a0fd7 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/repository/explore/support/MongoJobExplorerFactoryBean.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. @@ -13,24 +13,36 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.batch.core.explore.support; +package org.springframework.batch.core.repository.explore.support; import org.springframework.batch.core.repository.dao.ExecutionContextDao; 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.core.repository.dao.MongoExecutionContextDao; -import org.springframework.batch.core.repository.dao.MongoJobExecutionDao; -import org.springframework.batch.core.repository.dao.MongoJobInstanceDao; -import org.springframework.batch.core.repository.dao.MongoStepExecutionDao; +import org.springframework.batch.core.repository.dao.mongodb.MongoExecutionContextDao; +import org.springframework.batch.core.repository.dao.mongodb.MongoJobExecutionDao; +import org.springframework.batch.core.repository.dao.mongodb.MongoJobInstanceDao; +import org.springframework.batch.core.repository.dao.mongodb.MongoStepExecutionDao; +import org.springframework.batch.core.repository.support.MongoJobRepositoryFactoryBean; 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 + * @deprecated since 6.0 in favor of {@link MongoJobRepositoryFactoryBean}. Scheduled for + * removal in 6.2 or later. */ +@Deprecated(since = "6.0", forRemoval = true) public class MongoJobExplorerFactoryBean extends AbstractJobExplorerFactoryBean implements InitializingBean { private MongoOperations mongoOperations; 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/repository/explore/support/SimpleJobExplorer.java similarity index 60% rename from spring-batch-core/src/main/java/org/springframework/batch/core/explore/support/SimpleJobExplorer.java rename to spring-batch-core/src/main/java/org/springframework/batch/core/repository/explore/support/SimpleJobExplorer.java index 236be9902d..5e846060a3 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/repository/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. @@ -14,18 +14,20 @@ * limitations under the License. */ -package org.springframework.batch.core.explore.support; +package org.springframework.batch.core.repository.explore.support; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobInstance; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.explore.JobExplorer; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobInstance; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.step.StepExecution; +import org.springframework.batch.core.repository.explore.JobExplorer; import org.springframework.batch.core.launch.NoSuchJobException; import org.springframework.batch.core.repository.dao.ExecutionContextDao; 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.core.repository.support.SimpleJobRepository; +import org.springframework.batch.item.ExecutionContext; import org.springframework.lang.Nullable; import java.util.List; @@ -46,23 +48,20 @@ * @see JobExecutionDao * @see StepExecutionDao * @since 2.0 + * @deprecated since 6.0 in favor of {@link SimpleJobRepository}. Scheduled for removal in + * 6.2 or later. */ +@SuppressWarnings("removal") +@Deprecated(since = "6.0", forRemoval = true) 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 +79,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 getJobInstances(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 +177,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 +232,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 +263,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,27 +330,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)); - } - } - - @Override - public List findJobInstancesByJobName(String jobName, int start, int count) { - return jobInstanceDao.findJobInstancesByName(jobName, start, count); - } - } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/explore/support/package-info.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/explore/support/package-info.java similarity index 72% rename from spring-batch-core/src/main/java/org/springframework/batch/core/explore/support/package-info.java rename to spring-batch-core/src/main/java/org/springframework/batch/core/repository/explore/support/package-info.java index 6150d736cb..44b0a8f465 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/explore/support/package-info.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/explore/support/package-info.java @@ -5,6 +5,6 @@ * @author Mahmoud Ben Hassine */ @NonNullApi -package org.springframework.batch.core.explore.support; +package org.springframework.batch.core.repository.explore.support; import org.springframework.lang.NonNullApi; diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/persistence/converter/JobExecutionConverter.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/persistence/converter/JobExecutionConverter.java index 686c48464c..3239b485b9 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/persistence/converter/JobExecutionConverter.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/persistence/converter/JobExecutionConverter.java @@ -18,8 +18,8 @@ import java.util.HashMap; import java.util.Map; -import org.springframework.batch.core.JobInstance; -import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.job.JobInstance; +import org.springframework.batch.core.job.parameters.JobParameters; import org.springframework.batch.core.repository.persistence.ExecutionContext; import org.springframework.batch.core.repository.persistence.ExitStatus; import org.springframework.batch.core.repository.persistence.JobExecution; @@ -35,11 +35,12 @@ public class JobExecutionConverter { private final StepExecutionConverter stepExecutionConverter = new StepExecutionConverter(); - public org.springframework.batch.core.JobExecution toJobExecution(JobExecution source, JobInstance jobInstance) { - Map> parameterMap = new HashMap<>(); + public org.springframework.batch.core.job.JobExecution toJobExecution(JobExecution source, + JobInstance jobInstance) { + Map> parameterMap = new HashMap<>(); source.getJobParameters() .forEach((key, value) -> parameterMap.put(key, this.jobParameterConverter.toJobParameter(value))); - org.springframework.batch.core.JobExecution jobExecution = new org.springframework.batch.core.JobExecution( + org.springframework.batch.core.job.JobExecution jobExecution = new org.springframework.batch.core.job.JobExecution( jobInstance, source.getJobExecutionId(), new JobParameters(parameterMap)); jobExecution.addStepExecutions(source.getStepExecutions() .stream() @@ -57,7 +58,7 @@ public org.springframework.batch.core.JobExecution toJobExecution(JobExecution s return jobExecution; } - public JobExecution fromJobExecution(org.springframework.batch.core.JobExecution source) { + public JobExecution fromJobExecution(org.springframework.batch.core.job.JobExecution source) { JobExecution jobExecution = new JobExecution(); jobExecution.setJobExecutionId(source.getId()); jobExecution.setJobInstanceId(source.getJobInstance().getInstanceId()); diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/persistence/converter/JobInstanceConverter.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/persistence/converter/JobInstanceConverter.java index 82b3a277de..a52f23ee75 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/persistence/converter/JobInstanceConverter.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/persistence/converter/JobInstanceConverter.java @@ -23,11 +23,11 @@ */ public class JobInstanceConverter { - public org.springframework.batch.core.JobInstance toJobInstance(JobInstance source) { - return new org.springframework.batch.core.JobInstance(source.getJobInstanceId(), source.getJobName()); + public org.springframework.batch.core.job.JobInstance toJobInstance(JobInstance source) { + return new org.springframework.batch.core.job.JobInstance(source.getJobInstanceId(), source.getJobName()); } - public JobInstance fromJobInstance(org.springframework.batch.core.JobInstance source) { + public JobInstance fromJobInstance(org.springframework.batch.core.job.JobInstance source) { JobInstance jobInstance = new JobInstance(); jobInstance.setJobName(source.getJobName()); jobInstance.setJobInstanceId(source.getInstanceId()); diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/persistence/converter/JobParameterConverter.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/persistence/converter/JobParameterConverter.java index 361c98c36b..dfa6a89b82 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/persistence/converter/JobParameterConverter.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/persistence/converter/JobParameterConverter.java @@ -23,9 +23,9 @@ */ public class JobParameterConverter { - public org.springframework.batch.core.JobParameter toJobParameter(JobParameter source) { + public org.springframework.batch.core.job.parameters.JobParameter toJobParameter(JobParameter source) { try { - return new org.springframework.batch.core.JobParameter<>(source.value(), + return new org.springframework.batch.core.job.parameters.JobParameter<>(source.value(), (Class) Class.forName(source.type()), source.identifying()); } catch (ClassNotFoundException e) { @@ -33,7 +33,7 @@ public org.springframework.batch.core.JobParameter toJobParameter(JobPara } } - public JobParameter fromJobParameter(org.springframework.batch.core.JobParameter source) { + public JobParameter fromJobParameter(org.springframework.batch.core.job.parameters.JobParameter source) { return new JobParameter<>(source.getValue(), source.getType().getName(), source.isIdentifying()); } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/persistence/converter/StepExecutionConverter.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/persistence/converter/StepExecutionConverter.java index 221e9c50cf..785cd2456f 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/persistence/converter/StepExecutionConverter.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/persistence/converter/StepExecutionConverter.java @@ -15,7 +15,7 @@ */ package org.springframework.batch.core.repository.persistence.converter; -import org.springframework.batch.core.JobExecution; +import org.springframework.batch.core.job.JobExecution; import org.springframework.batch.core.repository.persistence.ExecutionContext; import org.springframework.batch.core.repository.persistence.ExitStatus; import org.springframework.batch.core.repository.persistence.StepExecution; @@ -26,9 +26,9 @@ */ public class StepExecutionConverter { - public org.springframework.batch.core.StepExecution toStepExecution(StepExecution source, + public org.springframework.batch.core.step.StepExecution toStepExecution(StepExecution source, JobExecution jobExecution) { - org.springframework.batch.core.StepExecution stepExecution = new org.springframework.batch.core.StepExecution( + org.springframework.batch.core.step.StepExecution stepExecution = new org.springframework.batch.core.step.StepExecution( source.getName(), jobExecution, source.getStepExecutionId()); stepExecution.setStatus(source.getStatus()); stepExecution.setReadCount(source.getReadCount()); @@ -53,7 +53,7 @@ public org.springframework.batch.core.StepExecution toStepExecution(StepExecutio return stepExecution; } - public StepExecution fromStepExecution(org.springframework.batch.core.StepExecution source) { + public StepExecution fromStepExecution(org.springframework.batch.core.step.StepExecution source) { StepExecution stepExecution = new StepExecution(); stepExecution.setStepExecutionId(source.getId()); stepExecution.setJobExecutionId(source.getJobExecutionId()); diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/AbstractJobRepositoryFactoryBean.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/AbstractJobRepositoryFactoryBean.java index 639a034ebd..50a5ef4186 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/AbstractJobRepositoryFactoryBean.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/AbstractJobRepositoryFactoryBean.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. @@ -22,6 +22,9 @@ import org.springframework.aop.framework.ProxyFactory; import org.springframework.aop.support.DefaultPointcutAdvisor; import org.springframework.aop.support.NameMatchMethodPointcut; +import org.springframework.batch.core.job.DefaultJobKeyGenerator; +import org.springframework.batch.core.job.JobKeyGenerator; +import org.springframework.batch.core.job.parameters.JobParameters; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.repository.dao.ExecutionContextDao; import org.springframework.batch.core.repository.dao.JobExecutionDao; @@ -43,7 +46,8 @@ * A {@link FactoryBean} that automates the creation of a {@link SimpleJobRepository}. * Declares abstract methods for providing DAO object implementations. * - * @see JobRepositoryFactoryBean + * @see JdbcJobRepositoryFactoryBean + * @see MongoJobRepositoryFactoryBean * @author Ben Hale * @author Lucas Ward * @author Robert Kasanicky @@ -70,6 +74,8 @@ public abstract class AbstractJobRepositoryFactoryBean implements FactoryBeanThe 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 */ @@ -41,7 +49,9 @@ public void setMongoOperations(MongoOperations mongoOperations) { @Override protected JobInstanceDao createJobInstanceDao() { - return new MongoJobInstanceDao(this.mongoOperations); + MongoJobInstanceDao mongoJobInstanceDao = new MongoJobInstanceDao(this.mongoOperations); + mongoJobInstanceDao.setJobKeyGenerator(this.jobKeyGenerator); + return mongoJobInstanceDao; } @Override diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/ResourcelessJobRepository.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/ResourcelessJobRepository.java index 9fb6b33dd8..694cd471a7 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/ResourcelessJobRepository.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/ResourcelessJobRepository.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. @@ -16,16 +16,17 @@ package org.springframework.batch.core.repository.support; import java.time.LocalDateTime; -import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.List; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobInstance; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobInstance; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.support.transaction.ResourcelessTransactionManager; +import org.springframework.lang.Nullable; /** * A {@link JobRepository} implementation that does not use or store batch meta-data. It @@ -49,53 +50,117 @@ public class ResourcelessJobRepository implements JobRepository { private JobExecution jobExecution; + private long stepExecutionIdIncrementer = 0L; + + /* + * =================================================================================== + * Job operations + * =================================================================================== + */ + + @Override + public List getJobNames() { + if (this.jobInstance == null) { + return Collections.emptyList(); + } + return Collections.singletonList(this.jobInstance.getJobName()); + } + + /* + * =================================================================================== + * Job instance operations + * =================================================================================== + */ + + @Override + public List getJobInstances(String jobName, int start, int count) { + if (this.jobInstance == null) { + return Collections.emptyList(); + } + return Collections.singletonList(this.jobInstance); + } + + @Override + @Nullable + public JobInstance getJobInstance(@Nullable Long instanceId) { + return this.jobInstance; + } + + @Override + @Nullable + public JobInstance getLastJobInstance(String jobName) { + return this.jobInstance; + } + + @Override + @Nullable + public JobInstance getJobInstance(String jobName, JobParameters jobParameters) { + return this.jobInstance; + } + + @SuppressWarnings("removal") @Override + @Deprecated(since = "6.0", forRemoval = true) public boolean isJobInstanceExists(String jobName, JobParameters jobParameters) { return false; } + @Override + public long getJobInstanceCount(String jobName) { + return 1; + } + @Override public JobInstance createJobInstance(String jobName, JobParameters jobParameters) { this.jobInstance = new JobInstance(1L, jobName); return this.jobInstance; } + /* + * =================================================================================== + * Job execution operations + * =================================================================================== + */ + @Override - public JobExecution createJobExecution(String jobName, JobParameters jobParameters) { - if (this.jobInstance == null) { - createJobInstance(jobName, jobParameters); - } - this.jobExecution = new JobExecution(this.jobInstance, 1L, jobParameters); + @Nullable + public JobExecution getJobExecution(Long executionId) { return this.jobExecution; } @Override - public void update(JobExecution jobExecution) { - jobExecution.setLastUpdated(LocalDateTime.now()); - this.jobExecution = jobExecution; + @Nullable + public JobExecution getLastJobExecution(String jobName, JobParameters jobParameters) { + return this.jobExecution; } @Override - public void add(StepExecution stepExecution) { - this.addAll(Collections.singletonList(stepExecution)); + @Nullable + public JobExecution getLastJobExecution(JobInstance jobInstance) { + return this.jobExecution; } @Override - public void addAll(Collection stepExecutions) { - this.jobExecution.addStepExecutions(new ArrayList<>(stepExecutions)); + public List getJobExecutions(JobInstance jobInstance) { + if (this.jobExecution == null) { + return Collections.emptyList(); + } + return Collections.singletonList(this.jobExecution); } @Override - public void update(StepExecution stepExecution) { - stepExecution.setLastUpdated(LocalDateTime.now()); - if (this.jobExecution.isStopping()) { - stepExecution.setTerminateOnly(); + public JobExecution createJobExecution(String jobName, JobParameters jobParameters) { + if (this.jobInstance == null) { + createJobInstance(jobName, jobParameters); } + this.jobExecution = new JobExecution(this.jobInstance, 1L, jobParameters); + return this.jobExecution; } @Override - public void updateExecutionContext(StepExecution stepExecution) { - stepExecution.setLastUpdated(LocalDateTime.now()); + public void update(JobExecution jobExecution) { + jobExecution.setLastUpdated(LocalDateTime.now()); + this.jobExecution = jobExecution; } @Override @@ -103,8 +168,31 @@ public void updateExecutionContext(JobExecution jobExecution) { jobExecution.setLastUpdated(LocalDateTime.now()); } + /* + * =================================================================================== + * Step execution operations + * =================================================================================== + */ + + @Override + @Nullable + public StepExecution getStepExecution(Long jobExecutionId, Long stepExecutionId) { + if (this.jobExecution == null || !this.jobExecution.getId().equals(jobExecutionId)) { + return null; + } + return this.jobExecution.getStepExecutions() + .stream() + .filter(stepExecution -> stepExecution.getId().equals(stepExecutionId)) + .findFirst() + .orElse(null); + } + @Override + @Nullable public StepExecution getLastStepExecution(JobInstance jobInstance, String stepName) { + if (this.jobExecution == null || !this.jobExecution.getJobInstance().getId().equals(jobInstance.getId())) { + return null; + } return this.jobExecution.getStepExecutions() .stream() .filter(stepExecution -> stepExecution.getStepName().equals(stepName)) @@ -121,8 +209,28 @@ public long getStepExecutionCount(JobInstance jobInstance, String stepName) { } @Override - public JobExecution getLastJobExecution(String jobName, JobParameters jobParameters) { - return this.jobExecution; + public void add(StepExecution stepExecution) { + stepExecution.setId(this.stepExecutionIdIncrementer++); + } + + @Override + public void addAll(Collection stepExecutions) { + for (StepExecution stepExecution : stepExecutions) { + this.add(stepExecution); + } + } + + @Override + public void update(StepExecution stepExecution) { + stepExecution.setLastUpdated(LocalDateTime.now()); + if (this.jobExecution.isStopping()) { + stepExecution.setTerminateOnly(); + } + } + + @Override + public void updateExecutionContext(StepExecution stepExecution) { + stepExecution.setLastUpdated(LocalDateTime.now()); } } \ No newline at end of file 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..202e024d23 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. @@ -19,10 +19,11 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.batch.core.BatchStatus; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobInstance; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobInstance; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.step.StepExecution; +import org.springframework.batch.core.repository.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,14 @@ * @see StepExecutionDao * */ -public class SimpleJobRepository implements JobRepository { +@SuppressWarnings("removal") +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 +207,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 +223,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 +241,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); } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/resource/StepExecutionSimpleCompletionPolicy.java b/spring-batch-core/src/main/java/org/springframework/batch/core/resource/StepExecutionSimpleCompletionPolicy.java index 3bc7bc0aeb..64d8f45b6e 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/resource/StepExecutionSimpleCompletionPolicy.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/resource/StepExecutionSimpleCompletionPolicy.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2021 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,9 +16,9 @@ package org.springframework.batch.core.resource; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.StepExecutionListener; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.step.StepExecution; +import org.springframework.batch.core.listener.StepExecutionListener; import org.springframework.batch.repeat.CompletionPolicy; import org.springframework.batch.repeat.RepeatContext; import org.springframework.batch.repeat.RepeatStatus; @@ -44,7 +44,9 @@ * @author Dave Syer * @author Mahmoud Ben Hassine * @see CompletionPolicy + * @deprecated since 6.0 with no replacement. Scheduled for removal in 6.2 or later. */ +@Deprecated(since = "6.0", forRemoval = true) public class StepExecutionSimpleCompletionPolicy implements StepExecutionListener, CompletionPolicy { private CompletionPolicy delegate; @@ -65,7 +67,7 @@ public void setKeyName(String keyName) { * {@link JobParameters}. If there is a Long parameter with the given key name, the * intValue of this parameter is used. If not an exception will be thrown. * - * @see org.springframework.batch.core.StepExecutionListener#beforeStep(org.springframework.batch.core.StepExecution) + * @see StepExecutionListener#beforeStep(StepExecution) */ @Override public void beforeStep(StepExecution stepExecution) { diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/scope/BatchScopeSupport.java b/spring-batch-core/src/main/java/org/springframework/batch/core/scope/BatchScopeSupport.java index 7cf74c855c..1b8da10bf5 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/scope/BatchScopeSupport.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/scope/BatchScopeSupport.java @@ -185,8 +185,8 @@ protected Object resolveValue(Object value) { BeanDefinition definition = null; String beanName = null; - if (value instanceof BeanDefinition) { - definition = (BeanDefinition) value; + if (value instanceof BeanDefinition beanDefinition) { + definition = beanDefinition; beanName = BeanDefinitionReaderUtils.generateBeanName(definition, registry); } else if (value instanceof BeanDefinitionHolder holder) { diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/scope/context/JobContext.java b/spring-batch-core/src/main/java/org/springframework/batch/core/scope/context/JobContext.java index 25e51964c7..bd92302649 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/scope/context/JobContext.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/scope/context/JobContext.java @@ -25,11 +25,11 @@ import java.util.Properties; import java.util.Set; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobInstance; -import org.springframework.batch.core.JobParameter; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.UnexpectedJobExecutionException; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobInstance; +import org.springframework.batch.core.job.parameters.JobParameter; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.UnexpectedJobExecutionException; import org.springframework.batch.core.scope.StepScope; import org.springframework.batch.item.ExecutionContext; import org.springframework.batch.repeat.context.SynchronizedAttributeAccessor; @@ -161,8 +161,8 @@ public void close() { } Exception error = errors.get(0); - if (error instanceof RuntimeException) { - throw (RuntimeException) error; + if (error instanceof RuntimeException runtimeException) { + throw runtimeException; } else { throw new UnexpectedJobExecutionException( diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/scope/context/JobScopeManager.java b/spring-batch-core/src/main/java/org/springframework/batch/core/scope/context/JobScopeManager.java index 668f53c8fb..c7b5162529 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/scope/context/JobScopeManager.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/scope/context/JobScopeManager.java @@ -18,8 +18,8 @@ import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobExecution; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; /** * Convenient aspect to wrap a single threaded job execution, where the implementation of @@ -32,7 +32,7 @@ @Aspect public class JobScopeManager { - @Around("execution(void org.springframework.batch.core.Job+.execute(*)) && target(job) && args(jobExecution)") + @Around("execution(void org.springframework.batch.core.job.Job+.execute(*)) && target(job) && args(jobExecution)") public void execute(Job job, JobExecution jobExecution) { JobSynchronizationManager.register(jobExecution); try { diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/scope/context/JobSynchronizationManager.java b/spring-batch-core/src/main/java/org/springframework/batch/core/scope/context/JobSynchronizationManager.java index 0471cb4143..e3fa5d2ee3 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/scope/context/JobSynchronizationManager.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/scope/context/JobSynchronizationManager.java @@ -15,8 +15,8 @@ */ package org.springframework.batch.core.scope.context; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobExecution; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; import org.springframework.lang.Nullable; /** @@ -60,12 +60,12 @@ public static JobContext getContext() { * Register a context with the current thread - always put a matching {@link #close()} * call in a finally block to ensure that the correct context is available in the * enclosing block. - * @param JobExecution the step context to register + * @param jobExecution the step context to register * @return a new {@link JobContext} or the current one if it has the same * {@link JobExecution} */ - public static JobContext register(JobExecution JobExecution) { - return manager.register(JobExecution); + public static JobContext register(JobExecution jobExecution) { + return manager.register(jobExecution); } /** diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/scope/context/StepContext.java b/spring-batch-core/src/main/java/org/springframework/batch/core/scope/context/StepContext.java index a076b5bee9..579aab3879 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/scope/context/StepContext.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/scope/context/StepContext.java @@ -25,11 +25,11 @@ import java.util.Properties; import java.util.Set; -import org.springframework.batch.core.JobInstance; -import org.springframework.batch.core.JobParameter; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.UnexpectedJobExecutionException; +import org.springframework.batch.core.job.JobInstance; +import org.springframework.batch.core.job.parameters.JobParameter; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.step.StepExecution; +import org.springframework.batch.core.job.UnexpectedJobExecutionException; import org.springframework.batch.core.scope.StepScope; import org.springframework.batch.item.ExecutionContext; import org.springframework.batch.repeat.context.SynchronizedAttributeAccessor; @@ -197,8 +197,8 @@ public void close() { } Exception error = errors.get(0); - if (error instanceof RuntimeException) { - throw (RuntimeException) error; + if (error instanceof RuntimeException runtimeException) { + throw runtimeException; } else { throw new UnexpectedJobExecutionException( diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/scope/context/StepContextRepeatCallback.java b/spring-batch-core/src/main/java/org/springframework/batch/core/scope/context/StepContextRepeatCallback.java index a30466c378..7ad0ca1dd4 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/scope/context/StepContextRepeatCallback.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/scope/context/StepContextRepeatCallback.java @@ -20,8 +20,8 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.repeat.RepeatCallback; import org.springframework.batch.repeat.RepeatContext; import org.springframework.batch.repeat.RepeatStatus; diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/scope/context/StepScopeManager.java b/spring-batch-core/src/main/java/org/springframework/batch/core/scope/context/StepScopeManager.java index 7780dc950c..7ee8cff9c0 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/scope/context/StepScopeManager.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/scope/context/StepScopeManager.java @@ -18,9 +18,9 @@ import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; -import org.springframework.batch.core.JobInterruptedException; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobInterruptedException; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.step.StepExecution; /** * Convenient aspect to wrap a single threaded step execution, where the implementation of @@ -32,7 +32,7 @@ @Aspect public class StepScopeManager { - @Around("execution(void org.springframework.batch.core.Step+.execute(*)) && target(step) && args(stepExecution)") + @Around("execution(void org.springframework.batch.core.step.Step+.execute(*)) && target(step) && args(stepExecution)") public void execute(Step step, StepExecution stepExecution) throws JobInterruptedException { StepSynchronizationManager.register(stepExecution); try { diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/scope/context/StepSynchronizationManager.java b/spring-batch-core/src/main/java/org/springframework/batch/core/scope/context/StepSynchronizationManager.java index 9fbf4ef853..34c24d0dc0 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/scope/context/StepSynchronizationManager.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/scope/context/StepSynchronizationManager.java @@ -15,8 +15,8 @@ */ package org.springframework.batch.core.scope.context; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.step.StepExecution; import org.springframework.lang.Nullable; /** diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/scope/context/SynchronizationManagerSupport.java b/spring-batch-core/src/main/java/org/springframework/batch/core/scope/context/SynchronizationManagerSupport.java index 1891f55883..f76a48b55a 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/scope/context/SynchronizationManagerSupport.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/scope/context/SynchronizationManagerSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2021 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. @@ -28,6 +28,7 @@ * @author Dave Syer * @author Jimmy Praet * @author Mahmoud Ben Hassine + * @author Yanming Zhou * @since 3.0 */ public abstract class SynchronizationManagerSupport { @@ -87,11 +88,7 @@ public C register(@Nullable E execution) { getCurrent().push(execution); C context; synchronized (contexts) { - context = contexts.get(execution); - if (context == null) { - context = createNewContext(execution); - contexts.put(execution, context); - } + context = contexts.computeIfAbsent(execution, this::createNewContext); } increment(); return context; @@ -131,11 +128,7 @@ public void increment() { if (current != null) { AtomicInteger count; synchronized (counts) { - count = counts.get(current); - if (count == null) { - count = new AtomicInteger(); - counts.put(current, count); - } + count = counts.computeIfAbsent(current, k -> new AtomicInteger()); } count.incrementAndGet(); } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/AbstractStep.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/AbstractStep.java index c2339b95df..4cac2f98b9 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/AbstractStep.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/AbstractStep.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. @@ -20,8 +20,6 @@ import java.util.List; import java.util.stream.Collectors; -import io.micrometer.core.instrument.MeterRegistry; -import io.micrometer.core.instrument.Metrics; import io.micrometer.observation.Observation; import io.micrometer.observation.ObservationRegistry; import org.apache.commons.logging.Log; @@ -29,21 +27,17 @@ import org.springframework.batch.core.BatchStatus; import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.JobInterruptedException; +import org.springframework.batch.core.job.JobInterruptedException; import org.springframework.batch.core.SpringBatchVersion; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.StepExecutionListener; -import org.springframework.batch.core.UnexpectedJobExecutionException; +import org.springframework.batch.core.listener.StepExecutionListener; +import org.springframework.batch.core.job.UnexpectedJobExecutionException; import org.springframework.batch.core.configuration.annotation.StepScope; import org.springframework.batch.core.launch.NoSuchJobException; import org.springframework.batch.core.launch.support.ExitCodeMapper; import org.springframework.batch.core.listener.CompositeStepExecutionListener; import org.springframework.batch.core.observability.BatchMetrics; -import org.springframework.batch.core.observability.BatchStepContext; -import org.springframework.batch.core.observability.BatchStepObservation; -import org.springframework.batch.core.observability.BatchStepObservationConvention; -import org.springframework.batch.core.observability.DefaultBatchStepObservationConvention; +import org.springframework.batch.core.observability.jfr.events.step.StepExecutionEvent; +import org.springframework.batch.core.observability.micrometer.MicrometerMetrics; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.scope.context.StepSynchronizationManager; import org.springframework.batch.item.ExecutionContext; @@ -65,7 +59,7 @@ * @author Mahmoud Ben Hassine * @author Jinwoo Bae */ -public abstract class AbstractStep implements Step, InitializingBean, BeanNameAware { +public abstract class AbstractStep implements StoppableStep, InitializingBean, BeanNameAware { private static final Log logger = LogFactory.getLog(AbstractStep.class); @@ -79,23 +73,45 @@ public abstract class AbstractStep implements Step, InitializingBean, BeanNameAw private JobRepository jobRepository; - private ObservationRegistry observationRegistry = ObservationRegistry.NOOP; + protected ObservationRegistry observationRegistry; - private MeterRegistry meterRegistry = Metrics.globalRegistry; + /** + * Create a new {@link AbstractStep}. + * @deprecated since 6.0 for removal in 7.0. Use {@link #AbstractStep(JobRepository)} + * instead. + */ + @Deprecated(since = "6.0", forRemoval = true) + public AbstractStep() { + } - private BatchStepObservationConvention observationConvention = new DefaultBatchStepObservationConvention(); + /** + * Create a new {@link AbstractStep}. + * @deprecated since 6.0 for removal in 7.0. Use {@link #AbstractStep(JobRepository)} + * instead. + */ + @Deprecated(since = "6.0", forRemoval = true) + public AbstractStep(String name) { + Assert.notNull(name, "Step name must not be null"); + this.name = name; + } /** - * Default constructor. + * Create a new {@link AbstractStep} with the given job repository. + * @param jobRepository the job repository. Must not be null. + * @since 6.0 */ - public AbstractStep() { - super(); + public AbstractStep(JobRepository jobRepository) { + Assert.notNull(jobRepository, "JobRepository must not be null"); + this.jobRepository = jobRepository; } @Override public void afterPropertiesSet() throws Exception { - Assert.state(name != null, "A Step must have a name"); Assert.state(jobRepository != null, "JobRepository is mandatory"); + if (this.observationRegistry == null) { + logger.info("No ObservationRegistry has been set, defaulting to ObservationRegistry NOOP"); + this.observationRegistry = ObservationRegistry.NOOP; + } } @Override @@ -156,14 +172,6 @@ public void setAllowStartIfComplete(boolean allowStartIfComplete) { this.allowStartIfComplete = allowStartIfComplete; } - /** - * Convenient constructor for setting only the name property. - * @param name Name of the step - */ - public AbstractStep(String name) { - this.name = name; - } - /** * Extension point for subclasses to execute business logic. Subclasses should set the * {@link ExitStatus} on the {@link StepExecution} before returning. @@ -202,18 +210,26 @@ public final void execute(StepExecution stepExecution) throws JobInterruptedException, UnexpectedJobExecutionException { Assert.notNull(stepExecution, "stepExecution must not be null"); + Assert.state(stepExecution.getId() != null, + "StepExecution has no id. It must be saved before it can be executed."); stepExecution.getExecutionContext().put(SpringBatchVersion.BATCH_VERSION_KEY, SpringBatchVersion.getVersion()); if (logger.isDebugEnabled()) { logger.debug("Executing: id=" + stepExecution.getId()); } + StepExecutionEvent stepExecutionEvent = new StepExecutionEvent(stepExecution.getStepName(), + stepExecution.getJobExecution().getJobInstance().getJobName(), stepExecution.getId(), + stepExecution.getJobExecutionId()); + stepExecutionEvent.begin(); stepExecution.setStartTime(LocalDateTime.now()); stepExecution.setStatus(BatchStatus.STARTED); - Observation observation = BatchMetrics - .createObservation(BatchStepObservation.BATCH_STEP_OBSERVATION.getName(), - new BatchStepContext(stepExecution), this.observationRegistry) - .contextualName(stepExecution.getStepName()) - .observationConvention(this.observationConvention) + Observation observation = MicrometerMetrics + .createObservation(BatchMetrics.METRICS_PREFIX + "step", this.observationRegistry) + .highCardinalityKeyValue(BatchMetrics.METRICS_PREFIX + "step.executionId", stepExecution.getId().toString()) + .lowCardinalityKeyValue(BatchMetrics.METRICS_PREFIX + "step.name", stepExecution.getStepName()) + .lowCardinalityKeyValue(BatchMetrics.METRICS_PREFIX + "step.type", getClass().getName()) + .lowCardinalityKeyValue(BatchMetrics.METRICS_PREFIX + "step.job.name", + stepExecution.getJobExecution().getJobInstance().getJobName()) .start(); getJobRepository().update(stepExecution); @@ -293,6 +309,8 @@ public final void execute(StepExecution stepExecution) + "This job is now in an unknown state and should not be restarted.", name, stepExecution.getJobExecution().getJobInstance().getJobName()), e); } + stepExecutionEvent.exitStatus = stepExecution.getExitStatus().getExitCode(); + stepExecutionEvent.commit(); stopObservation(stepExecution, observation); stepExecution.setExitStatus(exitStatus); @@ -331,6 +349,8 @@ private void stopObservation(StepExecution stepExecution, Observation observatio if (!throwables.isEmpty()) { observation.error(mergedThrowables(throwables)); } + observation.lowCardinalityKeyValue(BatchMetrics.METRICS_PREFIX + "step.status", + stepExecution.getExitStatus().getExitCode()); observation.stop(); } @@ -430,16 +450,8 @@ else if (ex instanceof NoSuchJobException || ex.getCause() instanceof NoSuchJobE return exitStatus; } - public void setObservationConvention(BatchStepObservationConvention observationConvention) { - this.observationConvention = observationConvention; - } - public void setObservationRegistry(ObservationRegistry observationRegistry) { this.observationRegistry = observationRegistry; } - public void setMeterRegistry(MeterRegistry meterRegistry) { - this.meterRegistry = meterRegistry; - } - } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/FatalStepExecutionException.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/FatalStepExecutionException.java index 2b40a9bde3..5be3202068 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/FatalStepExecutionException.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/FatalStepExecutionException.java @@ -15,7 +15,7 @@ */ package org.springframework.batch.core.step; -import org.springframework.batch.core.UnexpectedJobExecutionException; +import org.springframework.batch.core.job.UnexpectedJobExecutionException; /** * @author Dave Syer diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/NoWorkFoundStepExecutionListener.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/NoWorkFoundStepExecutionListener.java index e30e9bd426..940230080a 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/NoWorkFoundStepExecutionListener.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/NoWorkFoundStepExecutionListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 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. @@ -17,8 +17,7 @@ package org.springframework.batch.core.step; import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.StepExecutionListener; +import org.springframework.batch.core.listener.StepExecutionListener; import org.springframework.lang.Nullable; /** diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/Step.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/Step.java similarity index 78% rename from spring-batch-core/src/main/java/org/springframework/batch/core/Step.java rename to spring-batch-core/src/main/java/org/springframework/batch/core/step/Step.java index 834cfac6ce..03447ddb0e 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/Step.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/Step.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. @@ -13,7 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.batch.core; +package org.springframework.batch.core.step; + +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobInterruptedException; /** * Batch domain interface representing the configuration of a step. As with a {@link Job}, @@ -24,6 +27,7 @@ * @author Mahmoud Ben Hassine * */ +@FunctionalInterface public interface Step { /** @@ -32,9 +36,14 @@ public interface Step { String STEP_TYPE_KEY = "batch.stepType"; /** - * @return the name of this step. + * The name of the step. This is used to distinguish between different steps and must + * be unique within a job. If not explicitly set, the name will default to the fully + * qualified class name. + * @return the name of the step (never {@code null}) */ - String getName(); + default String getName() { + return this.getClass().getName(); + } /** * @return {@code true} if a step that is already marked as complete can be started diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/StepContribution.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/StepContribution.java similarity index 89% rename from spring-batch-core/src/main/java/org/springframework/batch/core/StepContribution.java rename to spring-batch-core/src/main/java/org/springframework/batch/core/step/StepContribution.java index fcbeaa9284..ae703f9277 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/StepContribution.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/StepContribution.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. @@ -13,10 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.batch.core; +package org.springframework.batch.core.step; import java.io.Serializable; +import org.springframework.batch.core.ExitStatus; + /** * Represents a contribution to a {@link StepExecution}, buffering changes until they can * be applied at a chunk boundary. @@ -71,7 +73,15 @@ public ExitStatus getExitStatus() { } /** - * Increment the counter for the number of items processed. + * Increment the counter for the number of filtered items. + * @since 6.0.0 + */ + public void incrementFilterCount() { + this.incrementFilterCount(1); + } + + /** + * Increment the counter for the number of filtered items. * @param count The {@code long} amount to increment by. */ public void incrementFilterCount(long count) { @@ -155,6 +165,15 @@ public void incrementWriteSkipCount() { writeSkipCount++; } + /** + * Increment the write skip count for this contribution. + * @param count The {@code long} amount to increment by. + * @since 6.0.0 + */ + public void incrementWriteSkipCount(long count) { + writeSkipCount += count; + } + /** * */ diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/StepExecution.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/StepExecution.java similarity index 97% rename from spring-batch-core/src/main/java/org/springframework/batch/core/StepExecution.java rename to spring-batch-core/src/main/java/org/springframework/batch/core/step/StepExecution.java index 1bf5164778..939102aaa5 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/StepExecution.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/StepExecution.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.batch.core; +package org.springframework.batch.core.step; import java.io.IOException; import java.io.ObjectInputStream; @@ -23,6 +23,11 @@ import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; +import org.springframework.batch.core.BatchStatus; +import org.springframework.batch.core.Entity; +import org.springframework.batch.core.ExitStatus; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; import org.springframework.batch.item.ExecutionContext; import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -492,7 +497,7 @@ public boolean equals(Object obj) { return super.equals(obj); } - return stepName.equals(other.getStepName()) && (jobExecutionId.equals(other.getJobExecutionId())) + return stepName.equals(other.getStepName()) && jobExecutionId.equals(other.getJobExecutionId()) && getId().equals(other.getId()); } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/StepHolder.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/StepHolder.java index 1f4a51c91d..33ad81ef12 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/StepHolder.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/StepHolder.java @@ -15,8 +15,6 @@ */ package org.springframework.batch.core.step; -import org.springframework.batch.core.Step; - /** * Interface for holders of a {@link Step} as a convenience for callers who need access to * the underlying instance. diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/StepInterruptionPolicy.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/StepInterruptionPolicy.java index 20a90fa6d6..1c2f74c75c 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/StepInterruptionPolicy.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/StepInterruptionPolicy.java @@ -16,9 +16,7 @@ package org.springframework.batch.core.step; -import org.springframework.batch.core.JobInterruptedException; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobInterruptedException; /** * Strategy interface for an interruption policy. This policy allows {@link Step} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/StepLocator.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/StepLocator.java index c275d20623..bc32a2b984 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/StepLocator.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/StepLocator.java @@ -17,8 +17,6 @@ import java.util.Collection; -import org.springframework.batch.core.Step; - /** * Interface for locating a {@link Step} instance by name. * diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/StepLocatorStepFactoryBean.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/StepLocatorStepFactoryBean.java index 98faf4b483..734d519ff4 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/StepLocatorStepFactoryBean.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/StepLocatorStepFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2013 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,8 +15,7 @@ */ package org.springframework.batch.core.step; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.Step; +import org.springframework.batch.core.job.Job; import org.springframework.beans.factory.FactoryBean; /** @@ -25,8 +24,9 @@ * point. * * @author Dave Syer - * + * @deprecated since 6.0 with no replacement. Scheduled for removal in 7.0. */ +@Deprecated(since = "6.0", forRemoval = true) public class StepLocatorStepFactoryBean implements FactoryBean { public StepLocator stepLocator; diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/observability/BatchStepObservationConvention.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/StoppableStep.java similarity index 50% rename from spring-batch-core/src/main/java/org/springframework/batch/core/observability/BatchStepObservationConvention.java rename to spring-batch-core/src/main/java/org/springframework/batch/core/step/StoppableStep.java index 00ac6ad04a..e455861f96 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/observability/BatchStepObservationConvention.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/StoppableStep.java @@ -1,5 +1,5 @@ /* - * Copyright 2022 the original author or authors. + * Copyright 2025-present 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. @@ -13,24 +13,25 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -package org.springframework.batch.core.observability; - -import io.micrometer.observation.Observation; -import io.micrometer.observation.ObservationConvention; +package org.springframework.batch.core.step; /** - * {@link ObservationConvention} for {@link BatchStepContext}. + * Extension of the {@link Step} interface to be implemented by steps that support being + * stopped. * - * @author Marcin Grzejszczak * @author Mahmoud Ben Hassine - * @since 5.0 + * @since 6.0 */ -public interface BatchStepObservationConvention extends ObservationConvention { +public interface StoppableStep extends Step { - @Override - default boolean supportsContext(Observation.Context context) { - return context instanceof BatchStepContext; + /** + * Callback to signal the step to stop. The default implementation sets the + * {@link StepExecution} to terminate only. Concrete implementations can override this + * method to add custom stop logic. + * @param stepExecution the current step execution + */ + default void stop(StepExecution stepExecution) { + stepExecution.setTerminateOnly(); } } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/ThreadStepInterruptionPolicy.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/ThreadStepInterruptionPolicy.java index f1ee332fb1..6f815c966b 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/ThreadStepInterruptionPolicy.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/ThreadStepInterruptionPolicy.java @@ -18,8 +18,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.batch.core.JobInterruptedException; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobInterruptedException; /** * Policy that checks the current thread to see if it has been interrupted. 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..fa454da245 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. @@ -20,8 +20,8 @@ import java.util.LinkedHashSet; import java.util.Set; -import org.springframework.batch.core.ChunkListener; -import org.springframework.batch.core.StepExecutionListener; +import org.springframework.batch.core.listener.ChunkListener; +import org.springframework.batch.core.listener.StepExecutionListener; import org.springframework.batch.core.annotation.AfterChunk; import org.springframework.batch.core.annotation.AfterChunkError; import org.springframework.batch.core.annotation.BeforeChunk; @@ -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; } @@ -150,8 +146,8 @@ public TaskletStep build() { protected void registerStepListenerAsChunkListener() { for (StepExecutionListener stepExecutionListener : properties.getStepExecutionListeners()) { - if (stepExecutionListener instanceof ChunkListener) { - listener((ChunkListener) stepExecutionListener); + if (stepExecutionListener instanceof ChunkListener chunkListener) { + listener(chunkListener); } } } @@ -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/ChunkOrientedStepBuilder.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/ChunkOrientedStepBuilder.java new file mode 100644 index 0000000000..376ec86dcb --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/ChunkOrientedStepBuilder.java @@ -0,0 +1,443 @@ +/* + * Copyright 2025-present 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.step.builder; + +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.Set; + +import io.micrometer.observation.ObservationRegistry; + +import org.springframework.batch.core.annotation.AfterChunk; +import org.springframework.batch.core.annotation.AfterProcess; +import org.springframework.batch.core.annotation.AfterRead; +import org.springframework.batch.core.annotation.AfterWrite; +import org.springframework.batch.core.annotation.BeforeChunk; +import org.springframework.batch.core.annotation.BeforeProcess; +import org.springframework.batch.core.annotation.BeforeRead; +import org.springframework.batch.core.annotation.BeforeWrite; +import org.springframework.batch.core.annotation.OnChunkError; +import org.springframework.batch.core.annotation.OnProcessError; +import org.springframework.batch.core.annotation.OnReadError; +import org.springframework.batch.core.annotation.OnWriteError; +import org.springframework.batch.core.listener.ChunkListener; +import org.springframework.batch.core.listener.ItemProcessListener; +import org.springframework.batch.core.listener.ItemReadListener; +import org.springframework.batch.core.listener.ItemWriteListener; +import org.springframework.batch.core.listener.SkipListener; +import org.springframework.batch.core.listener.StepListener; +import org.springframework.batch.core.listener.StepListenerFactoryBean; +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.core.step.StepInterruptionPolicy; +import org.springframework.batch.core.step.ThreadStepInterruptionPolicy; +import org.springframework.batch.core.step.item.ChunkOrientedStep; +import org.springframework.batch.core.step.skip.AlwaysSkipItemSkipPolicy; +import org.springframework.batch.core.step.skip.LimitCheckingExceptionHierarchySkipPolicy; +import org.springframework.batch.core.step.skip.SkipPolicy; +import org.springframework.batch.item.ItemProcessor; +import org.springframework.batch.item.ItemReader; +import org.springframework.batch.item.ItemStream; +import org.springframework.batch.item.ItemWriter; +import org.springframework.batch.support.ReflectionUtils; +import org.springframework.batch.support.transaction.ResourcelessTransactionManager; +import org.springframework.core.retry.RetryListener; +import org.springframework.core.retry.RetryPolicy; +import org.springframework.core.task.AsyncTaskExecutor; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.interceptor.DefaultTransactionAttribute; +import org.springframework.transaction.interceptor.TransactionAttribute; +import org.springframework.util.Assert; + +/** + * A builder for {@link ChunkOrientedStep}. This class extends {@link StepBuilderHelper} + * to provide common properties and methods for building chunk-oriented steps. + * + * @author Mahmoud Ben Hassine + * @since 6.0 + */ +public class ChunkOrientedStepBuilder extends StepBuilderHelper> { + + private final int chunkSize; + + private ItemReader reader; + + private ItemProcessor processor; + + private ItemWriter writer; + + private PlatformTransactionManager transactionManager = new ResourcelessTransactionManager(); + + private TransactionAttribute transactionAttribute = new DefaultTransactionAttribute(); + + private final Set streams = new LinkedHashSet<>(); + + private final Set stepListeners = new LinkedHashSet<>(); + + private StepInterruptionPolicy interruptionPolicy = new ThreadStepInterruptionPolicy(); + + private boolean faultTolerant; + + private RetryPolicy retryPolicy; + + private final Set retryListeners = new LinkedHashSet<>(); + + private final Set> retryableExceptions = new HashSet<>(); + + private long retryLimit = -1; + + private SkipPolicy skipPolicy; + + private final Set> skipListeners = new LinkedHashSet<>(); + + private final Set> skippableExceptions = new HashSet<>(); + + private long skipLimit = -1; + + private AsyncTaskExecutor asyncTaskExecutor; + + private ObservationRegistry observationRegistry; + + ChunkOrientedStepBuilder(StepBuilderHelper parent, int chunkSize) { + super(parent); + this.chunkSize = chunkSize; + } + + /** + * Create a new {@link ChunkOrientedStepBuilder} with the given job repository and + * transaction manager. The step name will be assigned to the bean name. + * @param jobRepository the job repository + * @param chunkSize the size of the chunk to be processed + */ + public ChunkOrientedStepBuilder(JobRepository jobRepository, int chunkSize) { + super(jobRepository); + this.chunkSize = chunkSize; + } + + /** + * Create a new {@link ChunkOrientedStepBuilder} with the given step name, job + * repository and transaction manager. + * @param name the step name + * @param jobRepository the job repository + * @param chunkSize the size of the chunk to be processed + */ + public ChunkOrientedStepBuilder(String name, JobRepository jobRepository, int chunkSize) { + super(name, jobRepository); + this.chunkSize = chunkSize; + } + + @Override + protected ChunkOrientedStepBuilder self() { + return this; + } + + /** + * An item reader that provides a stream of items. Will be automatically registered as + * a {@link #stream(ItemStream)} or listener if it implements the corresponding + * interface. + * @param reader an item reader + * @return this for fluent chaining + */ + public ChunkOrientedStepBuilder reader(ItemReader reader) { + this.reader = reader; + return self(); + } + + /** + * An item processor that processes or transforms a stream of items. Will be + * automatically registered as a {@link #stream(ItemStream)} or listener if it + * implements the corresponding interface. + * @param processor an item processor + * @return this for fluent chaining + */ + public ChunkOrientedStepBuilder processor(ItemProcessor processor) { + this.processor = processor; + return self(); + } + + /** + * An item writer that writes a chunk of items. Will be automatically registered as a + * {@link #stream(ItemStream)} or listener if it implements the corresponding + * interface. + * @param writer an item writer + * @return this for fluent chaining + */ + public ChunkOrientedStepBuilder writer(ItemWriter writer) { + this.writer = writer; + return self(); + } + + /** + * Sets the transaction manager to use for the chunk-oriented tasklet. Defaults to a + * {@link ResourcelessTransactionManager} if none is provided. + * @param transactionManager a transaction manager set + * @return this for fluent chaining + */ + public ChunkOrientedStepBuilder transactionManager(PlatformTransactionManager transactionManager) { + this.transactionManager = transactionManager; + return self(); + } + + /** + * Sets the transaction attributes for the tasklet execution. Defaults to the default + * values for the transaction manager, but can be manipulated to provide longer + * timeouts for instance. + * @param transactionAttribute a transaction attribute set + * @return this for fluent chaining + */ + public ChunkOrientedStepBuilder transactionAttribute(TransactionAttribute transactionAttribute) { + this.transactionAttribute = transactionAttribute; + return self(); + } + + /** + * Register a stream for callbacks that manage restart data. + * @param stream the stream to register + * @return this for fluent chaining + */ + public ChunkOrientedStepBuilder stream(ItemStream stream) { + streams.add(stream); + return self(); + } + + /** + * Register an item reader listener. + * @param listener the listener to register + * @return this for fluent chaining + */ + public ChunkOrientedStepBuilder listener(StepListener listener) { + this.stepListeners.add(listener); + return self(); + } + + /** + * Registers objects using the annotation-based listener configuration. + * @param listener the object that has a method configured with listener annotation(s) + * @return this for fluent chaining + */ + public ChunkOrientedStepBuilder listener(Object listener) { + Set listenerMethods = new HashSet<>(); + listenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), BeforeChunk.class)); + listenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), AfterChunk.class)); + listenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), OnChunkError.class)); + listenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), BeforeRead.class)); + listenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), AfterRead.class)); + listenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), OnReadError.class)); + listenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), BeforeProcess.class)); + listenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), AfterProcess.class)); + listenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), OnProcessError.class)); + listenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), BeforeWrite.class)); + listenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), AfterWrite.class)); + listenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), OnWriteError.class)); + + if (!listenerMethods.isEmpty()) { + StepListenerFactoryBean factory = new StepListenerFactoryBean(); + factory.setDelegate(listener); + this.stepListeners.add((StepListener) factory.getObject()); + } + + return self(); + } + + /** + * Set the interruption policy for the step. This policy determines how the step + * handles interruptions, such as when a job is stopped or restarted. The policy is + * checked at chunk boundaries to decide whether to continue processing or stop. + * Defaults to {@link ThreadStepInterruptionPolicy}. + * @return this for fluent chaining + */ + public ChunkOrientedStepBuilder interruptionPolicy(StepInterruptionPolicy interruptionPolicy) { + this.interruptionPolicy = interruptionPolicy; + return self(); + } + + /** + * Set whether the step is fault-tolerant or not. A fault-tolerant step can handle + * failures and continue processing without failing the entire step. This is useful + * for scenarios where individual items may fail and be skipped, but the overall step + * should still complete successfully. Defaults to false. + * @return this for fluent chaining + */ + public ChunkOrientedStepBuilder faultTolerant() { + this.faultTolerant = true; + return self(); + } + + /** + * Set the retry policy for the step. This policy determines how the step handles + * retries in case of failures. It can be used to define the number of retry attempts + * and the conditions under which retries should occur. Defaults to no retry policy. + * @param retryPolicy the retry policy to use + * @return this for fluent chaining + */ + public ChunkOrientedStepBuilder retryPolicy(RetryPolicy retryPolicy) { + Assert.notNull(retryPolicy, "retryPolicy must not be null"); + this.retryPolicy = retryPolicy; + return self(); + } + + /** + * Add a retry listener to the step. Retry listeners are notified of retry events and + * can be used to implement custom retry logic or logging. + * @param retryListener the retry listener to add + * @return this for fluent chaining + */ + public ChunkOrientedStepBuilder retryListener(RetryListener retryListener) { + this.retryListeners.add(retryListener); + return self(); + } + + @SafeVarargs + public final ChunkOrientedStepBuilder retry(Class... retryableExceptions) { + this.retryableExceptions.addAll(Arrays.stream(retryableExceptions).toList()); + return self(); + } + + public ChunkOrientedStepBuilder retryLimit(long retryLimit) { + Assert.isTrue(retryLimit > 0, "retryLimit must be positive"); + this.retryLimit = retryLimit; + return self(); + } + + /** + * Set the skip policy for the step. This policy determines how the step handles + * skipping items in case of failures. It can be used to define the conditions under + * which items should be skipped and how many times an item can be skipped before the + * step fails. Defaults to {@link AlwaysSkipItemSkipPolicy}. + * @param skipPolicy the skip policy to use + * @return this for fluent chaining + */ + public ChunkOrientedStepBuilder skipPolicy(SkipPolicy skipPolicy) { + Assert.notNull(skipPolicy, "skipPolicy must not be null"); + this.skipPolicy = skipPolicy; + return self(); + } + + /** + * Add a skip listener to the step. Skip listeners are notified when an item is + * skipped due to a failure or an error. They can be used to implement custom skip + * logic or logging. + * @param skipListener the skip listener to add + * @return this for fluent chaining + */ + public ChunkOrientedStepBuilder skipListener(SkipListener skipListener) { + this.skipListeners.add(skipListener); + return self(); + } + + @SafeVarargs + public final ChunkOrientedStepBuilder skip(Class... skippableExceptions) { + this.skippableExceptions.addAll(Arrays.stream(skippableExceptions).toList()); + return self(); + } + + public ChunkOrientedStepBuilder skipLimit(long skipLimit) { + Assert.isTrue(skipLimit > 0, "skipLimit must be positive"); + this.skipLimit = skipLimit; + return self(); + } + + /** + * Set the asynchronous task executor to be used for processing items concurrently. + * This allows for concurrent processing of items, improving performance and + * throughput. If not set, the step will process items sequentially. + * @param asyncTaskExecutor the asynchronous task executor to use + * @return this for fluent chaining + */ + public ChunkOrientedStepBuilder taskExecutor(AsyncTaskExecutor asyncTaskExecutor) { + this.asyncTaskExecutor = asyncTaskExecutor; + return self(); + } + + /** + * Set the observation registry to be used for collecting metrics during step + * execution. This allows for monitoring and analyzing the performance of the step. If + * not set, it will default to {@link ObservationRegistry#NOOP}. + * @param observationRegistry the observation registry to use + * @return this for fluent chaining + */ + public ChunkOrientedStepBuilder observationRegistry(ObservationRegistry observationRegistry) { + this.observationRegistry = observationRegistry; + return self(); + } + + @SuppressWarnings("unchecked") + public ChunkOrientedStep build() { + ChunkOrientedStep chunkOrientedStep = new ChunkOrientedStep<>(this.getName(), this.chunkSize, this.reader, + this.writer, this.getJobRepository()); + if (this.processor != null) { + chunkOrientedStep.setItemProcessor(this.processor); + } + chunkOrientedStep.setTransactionManager(this.transactionManager); + chunkOrientedStep.setTransactionAttribute(this.transactionAttribute); + chunkOrientedStep.setInterruptionPolicy(this.interruptionPolicy); + if (this.retryPolicy == null) { + if (!this.retryableExceptions.isEmpty() || this.retryLimit > 0) { + this.retryPolicy = RetryPolicy.builder() + .maxAttempts(this.retryLimit) + .includes(this.retryableExceptions) + .build(); + } + else { + this.retryPolicy = throwable -> false; + } + } + chunkOrientedStep.setRetryPolicy(this.retryPolicy); + if (this.skipPolicy == null) { + if (!this.skippableExceptions.isEmpty() || this.skipLimit > 0) { + this.skipPolicy = new LimitCheckingExceptionHierarchySkipPolicy(this.skippableExceptions, + this.skipLimit); + } + else { + this.skipPolicy = new AlwaysSkipItemSkipPolicy(); + } + } + chunkOrientedStep.setSkipPolicy(this.skipPolicy); + chunkOrientedStep.setFaultTolerant(this.faultTolerant); + if (this.asyncTaskExecutor != null) { + chunkOrientedStep.setTaskExecutor(this.asyncTaskExecutor); + } + streams.forEach(chunkOrientedStep::registerItemStream); + stepListeners.forEach(stepListener -> { + if (stepListener instanceof ItemReadListener) { + chunkOrientedStep.registerItemReadListener((ItemReadListener) stepListener); + } + if (stepListener instanceof ItemProcessListener) { + chunkOrientedStep.registerItemProcessListener((ItemProcessListener) stepListener); + } + if (stepListener instanceof ItemWriteListener) { + chunkOrientedStep.registerItemWriteListener((ItemWriteListener) stepListener); + } + if (stepListener instanceof ChunkListener) { + chunkOrientedStep.registerChunkListener((ChunkListener) stepListener); + } + }); + retryListeners.forEach(chunkOrientedStep::registerRetryListener); + skipListeners.forEach(chunkOrientedStep::registerSkipListener); + if (this.observationRegistry != null) { + chunkOrientedStep.setObservationRegistry(this.observationRegistry); + } + try { + chunkOrientedStep.afterPropertiesSet(); + } + catch (Exception e) { + throw new StepBuilderException("Unable to build a chunk-oriented step", e); + } + return chunkOrientedStep; + } + +} 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..f18c567eb3 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 @@ -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. @@ -25,11 +25,11 @@ import java.util.Map; import java.util.Set; -import org.springframework.batch.core.ChunkListener; -import org.springframework.batch.core.JobInterruptedException; -import org.springframework.batch.core.SkipListener; -import org.springframework.batch.core.StepExecutionListener; -import org.springframework.batch.core.StepListener; +import org.springframework.batch.core.listener.ChunkListener; +import org.springframework.batch.core.job.JobInterruptedException; +import org.springframework.batch.core.listener.SkipListener; +import org.springframework.batch.core.listener.StepExecutionListener; +import org.springframework.batch.core.listener.StepListener; import org.springframework.batch.core.annotation.OnSkipInProcess; import org.springframework.batch.core.annotation.OnSkipInRead; import org.springframework.batch.core.annotation.OnSkipInWrite; @@ -90,8 +90,13 @@ * @author Chris Schaefer * @author Michael Minella * @author Mahmoud Ben Hassine + * @author Ian Choi * @since 2.2 + * @deprecated Since 6.0, use + * {@link org.springframework.batch.core.step.builder.ChunkOrientedStepBuilder} instead. + * Scheduled for removal in 7.0. */ +@Deprecated(since = "6.0", forRemoval = true) public class FaultTolerantStepBuilder extends SimpleStepBuilder { private final ChunkMonitor chunkMonitor = new ChunkMonitor(); @@ -122,7 +127,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 +311,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) { @@ -554,8 +559,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) { @@ -570,11 +578,10 @@ else if (limitCheckingItemSkipPolicy != null) { protected BatchRetryTemplate createRetryOperations() { RetryPolicy retryPolicy = this.retryPolicy; - SimpleRetryPolicy simpleRetryPolicy = null; Map, Boolean> map = new HashMap<>(retryableExceptionClasses); map.put(ForceRollbackForWriteSkipException.class, true); - simpleRetryPolicy = new SimpleRetryPolicy(retryLimit, map); + SimpleRetryPolicy simpleRetryPolicy = new SimpleRetryPolicy(retryLimit, map); if (retryPolicy == null) { Assert.state(!(retryableExceptionClasses.isEmpty() && retryLimit > 0), @@ -597,10 +604,10 @@ else if ((!retryableExceptionClasses.isEmpty() && retryLimit > 0)) { // Coordinate the retry policy with the exception handler: RepeatOperations stepOperations = getStepOperations(); - if (stepOperations instanceof RepeatTemplate) { + if (stepOperations instanceof RepeatTemplate repeatTemplate) { SimpleRetryExceptionHandler exceptionHandler = new SimpleRetryExceptionHandler(retryPolicyWrapper, getExceptionHandler(), nonRetryableExceptionClasses); - ((RepeatTemplate) stepOperations).setExceptionHandler(exceptionHandler); + repeatTemplate.setExceptionHandler(exceptionHandler); } if (retryContextCache != null) { diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/FlowStepBuilder.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/FlowStepBuilder.java index d838c9075c..c0004d143b 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/FlowStepBuilder.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/FlowStepBuilder.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,7 +15,7 @@ */ package org.springframework.batch.core.step.builder; -import org.springframework.batch.core.Step; +import org.springframework.batch.core.step.Step; import org.springframework.batch.core.job.flow.Flow; import org.springframework.batch.core.job.flow.FlowStep; @@ -24,6 +24,7 @@ * nested flow composed of other steps. * * @author Dave Syer + * @author Mahmoud Ben Hassine * @since 2.2 */ public class FlowStepBuilder extends StepBuilderHelper { @@ -56,7 +57,7 @@ public FlowStepBuilder flow(Flow flow) { * @return a flow step */ public Step build() { - FlowStep step = new FlowStep(); + FlowStep step = new FlowStep(getJobRepository()); step.setName(getName()); step.setFlow(flow); super.enhance(step); diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/JobStepBuilder.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/JobStepBuilder.java index a9abb9663b..95b07c1387 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/JobStepBuilder.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/JobStepBuilder.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,10 +15,11 @@ */ package org.springframework.batch.core.step.builder; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.launch.JobLauncher; -import org.springframework.batch.core.launch.support.TaskExecutorJobLauncher; +import org.springframework.batch.core.configuration.support.MapJobRegistry; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.launch.JobOperator; +import org.springframework.batch.core.launch.support.TaskExecutorJobOperator; +import org.springframework.batch.core.step.Step; import org.springframework.batch.core.step.job.JobParametersExtractor; import org.springframework.batch.core.step.job.JobStep; @@ -27,13 +28,14 @@ * with parameters taken from the parent job or from the step execution. * * @author Dave Syer + * @author Mahmoud Ben Hassine * @since 2.2 */ public class JobStepBuilder extends StepBuilderHelper { private Job job; - private JobLauncher jobLauncher; + private JobOperator jobOperator; private JobParametersExtractor jobParametersExtractor; @@ -57,12 +59,12 @@ public JobStepBuilder job(Job job) { } /** - * Add a job launcher. Defaults to a simple job launcher. - * @param jobLauncher the job launcher to use + * Add a job operator. Defaults to a {@link TaskExecutorJobOperator}. + * @param jobOperator the job operator to use * @return this for fluent chaining */ - public JobStepBuilder launcher(JobLauncher jobLauncher) { - this.jobLauncher = jobLauncher; + public JobStepBuilder operator(JobOperator jobOperator) { + this.jobOperator = jobOperator; return this; } @@ -83,7 +85,7 @@ public JobStepBuilder parametersExtractor(JobParametersExtractor jobParametersEx */ public Step build() { - JobStep step = new JobStep(); + JobStep step = new JobStep(getJobRepository()); step.setName(getName()); super.enhance(step); if (job != null) { @@ -92,18 +94,19 @@ public Step build() { if (jobParametersExtractor != null) { step.setJobParametersExtractor(jobParametersExtractor); } - if (jobLauncher == null) { - TaskExecutorJobLauncher jobLauncher = new TaskExecutorJobLauncher(); - jobLauncher.setJobRepository(getJobRepository()); + if (jobOperator == null) { + TaskExecutorJobOperator jobOperator = new TaskExecutorJobOperator(); + jobOperator.setJobRepository(getJobRepository()); + jobOperator.setJobRegistry(new MapJobRegistry()); try { - jobLauncher.afterPropertiesSet(); + jobOperator.afterPropertiesSet(); } catch (Exception e) { throw new StepBuilderException(e); } - this.jobLauncher = jobLauncher; + this.jobOperator = jobOperator; } - step.setJobLauncher(jobLauncher); + step.setJobOperator(jobOperator); try { step.afterPropertiesSet(); } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/PartitionStepBuilder.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/PartitionStepBuilder.java index cd4ffa0cbb..c9afe1012d 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/PartitionStepBuilder.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/PartitionStepBuilder.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,13 +15,13 @@ */ package org.springframework.batch.core.step.builder; -import org.springframework.batch.core.Step; +import org.springframework.batch.core.step.Step; import org.springframework.batch.core.partition.PartitionHandler; import org.springframework.batch.core.partition.StepExecutionSplitter; -import org.springframework.batch.core.partition.support.PartitionStep; -import org.springframework.batch.core.partition.support.Partitioner; +import org.springframework.batch.core.partition.PartitionStep; +import org.springframework.batch.core.partition.Partitioner; import org.springframework.batch.core.partition.support.SimpleStepExecutionSplitter; -import org.springframework.batch.core.partition.support.StepExecutionAggregator; +import org.springframework.batch.core.partition.StepExecutionAggregator; import org.springframework.batch.core.partition.support.TaskExecutorPartitionHandler; import org.springframework.core.task.SyncTaskExecutor; import org.springframework.core.task.TaskExecutor; @@ -159,7 +159,7 @@ public PartitionStepBuilder aggregator(StepExecutionAggregator aggregator) { } public Step build() { - PartitionStep step = new PartitionStep(); + PartitionStep step = new PartitionStep(getJobRepository()); step.setName(getName()); super.enhance(step); diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/SimpleStepBuilder.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/SimpleStepBuilder.java index fed10c44a1..bd711e81e1 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/SimpleStepBuilder.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/SimpleStepBuilder.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. @@ -24,12 +24,12 @@ import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.Metrics; -import org.springframework.batch.core.ChunkListener; -import org.springframework.batch.core.ItemProcessListener; -import org.springframework.batch.core.ItemReadListener; -import org.springframework.batch.core.ItemWriteListener; -import org.springframework.batch.core.StepExecutionListener; -import org.springframework.batch.core.StepListener; +import org.springframework.batch.core.listener.ChunkListener; +import org.springframework.batch.core.listener.ItemProcessListener; +import org.springframework.batch.core.listener.ItemReadListener; +import org.springframework.batch.core.listener.ItemWriteListener; +import org.springframework.batch.core.listener.StepExecutionListener; +import org.springframework.batch.core.listener.StepListener; import org.springframework.batch.core.annotation.AfterProcess; import org.springframework.batch.core.annotation.AfterRead; import org.springframework.batch.core.annotation.AfterWrite; @@ -66,7 +66,10 @@ * @author Mahmoud Ben Hassine * @author Parikshit Dutta * @since 2.2 + * @deprecated Since 6.0 in favor of {@link ChunkOrientedStepBuilder}. Scheduled for + * removal in 7.0. */ +@Deprecated(since = "6.0", forRemoval = true) public class SimpleStepBuilder extends AbstractTaskletStepBuilder> { private static final int DEFAULT_COMMIT_INTERVAL = 1; @@ -383,16 +386,16 @@ protected CompletionPolicy getChunkCompletionPolicy() { protected void registerAsStreamsAndListeners(ItemReader itemReader, ItemProcessor itemProcessor, ItemWriter itemWriter) { for (Object itemHandler : new Object[] { itemReader, itemWriter, itemProcessor }) { - if (itemHandler instanceof ItemStream) { - stream((ItemStream) itemHandler); + if (itemHandler instanceof ItemStream itemStream) { + stream(itemStream); } if (StepListenerFactoryBean.isListener(itemHandler)) { StepListener listener = StepListenerFactoryBean.getListener(itemHandler); - if (listener instanceof StepExecutionListener) { - listener((StepExecutionListener) listener); + if (listener instanceof StepExecutionListener stepExecutionListener) { + listener(stepExecutionListener); } - if (listener instanceof ChunkListener) { - listener((ChunkListener) listener); + if (listener instanceof ChunkListener chunkListener) { + listener(chunkListener); } if (listener instanceof ItemReadListener || listener instanceof ItemProcessListener || listener instanceof ItemWriteListener) { 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..9442429842 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. @@ -15,10 +15,10 @@ */ package org.springframework.batch.core.step.builder; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.Step; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.step.Step; import org.springframework.batch.core.job.flow.Flow; -import org.springframework.batch.core.partition.support.Partitioner; +import org.springframework.batch.core.partition.Partitioner; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.step.tasklet.Tasklet; import org.springframework.batch.repeat.CompletionPolicy; @@ -35,13 +35,13 @@ 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)} + * Initialize a step builder for a step with the given job repository. The name of the + * step will be set to the bean name by default. + * @param jobRepository the job repository to which the step should report to. + * @since 6.0 */ - @Deprecated(since = "5.0", forRemoval = true) - public StepBuilder(String name) { - super(name); + public StepBuilder(JobRepository jobRepository) { + super(jobRepository); } /** @@ -54,17 +54,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 @@ -77,24 +66,13 @@ public TaskletStepBuilder tasklet(Tasklet tasklet, PlatformTransactionManager tr } /** - * 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)} + * Build a step with a custom tasklet, not necessarily item processing. + * @param tasklet a tasklet + * @return a {@link TaskletStepBuilder} + * @since 6.0 */ - @Deprecated(since = "5.0", forRemoval = true) - public SimpleStepBuilder chunk(int chunkSize) { - return new SimpleStepBuilder(this).chunk(chunkSize); + public TaskletStepBuilder tasklet(Tasklet tasklet) { + return new TaskletStepBuilder(this).tasklet(tasklet); } /** @@ -114,32 +92,26 @@ public SimpleStepBuilder chunk(int chunkSize) { * @param the type of item to be processed as input * @param the type of item to be output * @since 5.0 + * @deprecated since 6.0, use {@link #chunk(int)} instead. Scheduled for removal in + * 7.0. */ + @Deprecated(since = "6.0", forRemoval = true) public SimpleStepBuilder chunk(int chunkSize, PlatformTransactionManager transactionManager) { 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} + * Build a step that processes items in chunks with the size provided. To extend the + * step to being fault-tolerant, call the + * {@link ChunkOrientedStepBuilder#faultTolerant()} method on the builder. + * @param chunkSize the chunk size (commit interval) + * @return a {@link ChunkOrientedStepBuilder} for method chaining * @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)} + * @since 6.0 */ - @Deprecated(since = "5.0", forRemoval = true) - public SimpleStepBuilder chunk(CompletionPolicy completionPolicy) { - return new SimpleStepBuilder(this).chunk(completionPolicy); + public ChunkOrientedStepBuilder chunk(int chunkSize) { + return new ChunkOrientedStepBuilder<>(this, chunkSize); } /** @@ -160,7 +132,10 @@ public SimpleStepBuilder chunk(CompletionPolicy completionPolicy) { * @param the type of item to be processed as input * @param the type of item to be output * @since 5.0 + * @deprecated since 6.0, use {@link #chunk(int)} instead. Scheduled for removal in + * 7.0. */ + @Deprecated(since = "6.0", forRemoval = true) public SimpleStepBuilder chunk(CompletionPolicy completionPolicy, PlatformTransactionManager transactionManager) { return new SimpleStepBuilder(this).transactionManager(transactionManager).chunk(completionPolicy); diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/StepBuilderException.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/StepBuilderException.java index 540c8970ac..3dfbff7bea 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/StepBuilderException.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/StepBuilderException.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,10 @@ */ public class StepBuilderException extends RuntimeException { + public StepBuilderException(String message, Throwable cause) { + super(message, cause); + } + public StepBuilderException(Exception e) { super(e); } 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..45568cee55 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. @@ -21,18 +21,14 @@ import java.util.List; import java.util.Set; -import io.micrometer.core.instrument.MeterRegistry; -import io.micrometer.core.instrument.Metrics; import io.micrometer.observation.ObservationRegistry; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.batch.core.StepExecutionListener; +import org.springframework.batch.core.listener.StepExecutionListener; import org.springframework.batch.core.annotation.AfterStep; import org.springframework.batch.core.annotation.BeforeStep; import org.springframework.batch.core.listener.StepListenerFactoryBean; -import org.springframework.batch.core.observability.BatchStepObservationConvention; -import org.springframework.batch.core.observability.DefaultBatchStepObservationConvention; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.step.AbstractStep; import org.springframework.batch.support.ReflectionUtils; @@ -54,14 +50,13 @@ 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)} + * Create a new {@link StepBuilderHelper} with the given job repository. + * @param jobRepository the job repository + * @since 6.0 */ - @Deprecated(since = "5.1", forRemoval = true) - public StepBuilderHelper(String name) { + public StepBuilderHelper(JobRepository jobRepository) { this.properties = new CommonStepProperties(); - properties.name = name; + properties.jobRepository = jobRepository; } /** @@ -85,39 +80,11 @@ 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) - * @return this to enable fluent chaining - * @since 5.1 - */ - public B observationConvention(BatchStepObservationConvention observationConvention) { - properties.observationConvention = observationConvention; - return self(); - } - public B observationRegistry(ObservationRegistry observationRegistry) { properties.observationRegistry = observationRegistry; return self(); } - public B meterRegistry(MeterRegistry meterRegistry) { - properties.meterRegistry = meterRegistry; - return self(); - } - public B startLimit(int startLimit) { properties.startLimit = startLimit; return self(); @@ -169,21 +136,11 @@ protected boolean isAllowStartIfComplete() { protected void enhance(AbstractStep step) { step.setJobRepository(properties.getJobRepository()); - BatchStepObservationConvention observationConvention = properties.getObservationConvention(); - if (observationConvention != null) { - step.setObservationConvention(observationConvention); - } - ObservationRegistry observationRegistry = properties.getObservationRegistry(); if (observationRegistry != null) { step.setObservationRegistry(observationRegistry); } - MeterRegistry meterRegistry = properties.getMeterRegistry(); - if (meterRegistry != null) { - step.setMeterRegistry(meterRegistry); - } - Boolean allowStartIfComplete = properties.allowStartIfComplete; if (allowStartIfComplete != null) { step.setAllowStartIfComplete(allowStartIfComplete); @@ -199,6 +156,8 @@ protected void enhance(AbstractStep step) { public static class CommonStepProperties { + private String name; + private List stepExecutionListeners = new ArrayList<>(); private int startLimit = Integer.MAX_VALUE; @@ -207,12 +166,8 @@ public static class CommonStepProperties { private JobRepository jobRepository; - private BatchStepObservationConvention observationConvention = new DefaultBatchStepObservationConvention(); - private ObservationRegistry observationRegistry = ObservationRegistry.NOOP; - private MeterRegistry meterRegistry = Metrics.globalRegistry; - public CommonStepProperties() { } @@ -221,9 +176,7 @@ public CommonStepProperties(CommonStepProperties properties) { this.startLimit = properties.startLimit; this.allowStartIfComplete = properties.allowStartIfComplete; this.jobRepository = properties.jobRepository; - this.observationConvention = properties.observationConvention; this.observationRegistry = properties.observationRegistry; - this.meterRegistry = properties.meterRegistry; this.stepExecutionListeners = new ArrayList<>(properties.stepExecutionListeners); } @@ -235,14 +188,6 @@ public void setJobRepository(JobRepository jobRepository) { this.jobRepository = jobRepository; } - public BatchStepObservationConvention getObservationConvention() { - return observationConvention; - } - - public void setObservationConvention(BatchStepObservationConvention observationConvention) { - this.observationConvention = observationConvention; - } - public ObservationRegistry getObservationRegistry() { return observationRegistry; } @@ -251,14 +196,6 @@ public void setObservationRegistry(ObservationRegistry observationRegistry) { this.observationRegistry = observationRegistry; } - public MeterRegistry getMeterRegistry() { - return meterRegistry; - } - - public void setMeterRegistry(MeterRegistry meterRegistry) { - this.meterRegistry = meterRegistry; - } - public String getName() { return name; } @@ -295,8 +232,6 @@ public void setAllowStartIfComplete(Boolean allowStartIfComplete) { this.allowStartIfComplete = allowStartIfComplete; } - private String name; - } } 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..d21ccc5a31 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. @@ -41,23 +41,21 @@ public TaskletStepBuilder(StepBuilderHelper parent) { /** * @param tasklet the tasklet to use * @return this for fluent chaining - * @deprecated use - * {@link TaskletStepBuilder#tasklet(Tasklet, PlatformTransactionManager)} + * @since 5.0 */ - @Deprecated(since = "5.0", forRemoval = true) - public TaskletStepBuilder tasklet(Tasklet tasklet) { + public TaskletStepBuilder tasklet(Tasklet tasklet, PlatformTransactionManager transactionManager) { this.tasklet = tasklet; + super.transactionManager(transactionManager); return this; } /** * @param tasklet the tasklet to use * @return this for fluent chaining - * @since 5.0 + * @since 6.0 */ - public TaskletStepBuilder tasklet(Tasklet tasklet, PlatformTransactionManager transactionManager) { + public TaskletStepBuilder tasklet(Tasklet tasklet) { this.tasklet = tasklet; - super.transactionManager(transactionManager); return this; } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/factory/BatchListenerFactoryHelper.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/factory/BatchListenerFactoryHelper.java index 901ca75b3e..0204b7dfea 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/factory/BatchListenerFactoryHelper.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/factory/BatchListenerFactoryHelper.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2007 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,14 +18,15 @@ import java.util.ArrayList; import java.util.List; -import org.springframework.batch.core.StepListener; +import org.springframework.batch.core.listener.StepListener; /** * Package private helper for step factory beans. - * + * * @author Dave Syer - * + * @deprecated Since 6.0 with no replacement. Scheduled for removal in 7.0. */ +@Deprecated(since = "6.0", forRemoval = true) abstract class BatchListenerFactoryHelper { public static List getListeners(StepListener[] listeners, Class cls) { 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..e85c960622 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-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,8 +21,8 @@ import java.util.HashSet; import java.util.Map; -import org.springframework.batch.core.SkipListener; -import org.springframework.batch.core.Step; +import org.springframework.batch.core.listener.SkipListener; +import org.springframework.batch.core.step.Step; import org.springframework.batch.core.step.builder.FaultTolerantStepBuilder; import org.springframework.batch.core.step.builder.SimpleStepBuilder; import org.springframework.batch.core.step.builder.StepBuilder; @@ -47,8 +47,10 @@ * @author Dave Syer * @author Robert Kasanicky * @author Morten Andersen-Gott - * + * @author Ian Choi + * @deprecated Since 6.0 with no replacement. Scheduled for removal in 7.0. */ +@Deprecated(since = "6.0", forRemoval = true) public class FaultTolerantStepFactoryBean extends SimpleStepFactoryBean { private Map, Boolean> skippableExceptionClasses = new HashMap<>(); @@ -61,7 +63,7 @@ public class FaultTolerantStepFactoryBean extends SimpleStepFactoryBean implements FactoryBean, BeanNameAware { private String name; @@ -107,7 +108,8 @@ public class SimpleStepFactoryBean implements FactoryBean, BeanNameA private CompletionPolicy chunkCompletionPolicy; - private int throttleLimit = TaskExecutorRepeatTemplate.DEFAULT_THROTTLE_LIMIT; + @SuppressWarnings("unused") + private final int throttleLimit = TaskExecutorRepeatTemplate.DEFAULT_THROTTLE_LIMIT; private boolean isReaderTransactionalQueue = false; @@ -441,20 +443,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 +470,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 +478,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/java/org/springframework/batch/core/step/item/BatchRetryTemplate.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/BatchRetryTemplate.java index 4d16fbf665..8004deb3f1 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/BatchRetryTemplate.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/BatchRetryTemplate.java @@ -53,8 +53,9 @@ * * @author Dave Syer * @author Mahmoud Ben Hassine - * + * @deprecated Since 6.0 with no replacement. Scheduled for removal in 7.0. */ +@Deprecated(since = "6.0", forRemoval = true) public class BatchRetryTemplate implements RetryOperations { private static class BatchRetryState extends DefaultRetryState { diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkMonitor.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkMonitor.java index 47ac071075..a1a4044b05 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkMonitor.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkMonitor.java @@ -34,7 +34,9 @@ * @author Mahmoud Ben Hassine * @author Seungrae Kim * @since 2.0 + * @deprecated Since 6.0 with no replacement. Scheduled for removal in 7.0. */ +@Deprecated(since = "6.0", forRemoval = true) public class ChunkMonitor extends ItemStreamSupport { private final Log logger = LogFactory.getLog(getClass()); @@ -58,7 +60,7 @@ public ChunkMonitorData(int offset, int chunkSize) { private final CompositeItemStream stream = new CompositeItemStream(); - private final ThreadLocal holder = new ThreadLocal<>(); + private static final ThreadLocal holder = new ThreadLocal<>(); private ItemReader reader; diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkOrientedStep.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkOrientedStep.java new file mode 100644 index 0000000000..7be821f0cf --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkOrientedStep.java @@ -0,0 +1,771 @@ +/* + * Copyright 2025-present 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.step.item; + +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.Future; + +import io.micrometer.observation.Observation; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; + +import org.springframework.batch.core.job.JobInterruptedException; +import org.springframework.batch.core.listener.ChunkListener; +import org.springframework.batch.core.listener.CompositeChunkListener; +import org.springframework.batch.core.listener.CompositeItemProcessListener; +import org.springframework.batch.core.listener.CompositeItemReadListener; +import org.springframework.batch.core.listener.CompositeItemWriteListener; +import org.springframework.batch.core.listener.CompositeSkipListener; +import org.springframework.batch.core.listener.ItemProcessListener; +import org.springframework.batch.core.listener.ItemReadListener; +import org.springframework.batch.core.listener.ItemWriteListener; +import org.springframework.batch.core.listener.SkipListener; +import org.springframework.batch.core.observability.BatchMetrics; +import org.springframework.batch.core.observability.jfr.events.step.chunk.ChunkScanEvent; +import org.springframework.batch.core.observability.jfr.events.step.chunk.ChunkTransactionEvent; +import org.springframework.batch.core.observability.jfr.events.step.chunk.ChunkWriteEvent; +import org.springframework.batch.core.observability.jfr.events.step.chunk.ItemProcessEvent; +import org.springframework.batch.core.observability.jfr.events.step.chunk.ItemReadEvent; +import org.springframework.batch.core.scope.context.StepContext; +import org.springframework.batch.core.scope.context.StepSynchronizationManager; +import org.springframework.batch.core.step.StepContribution; +import org.springframework.batch.core.step.StepExecution; +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.core.step.AbstractStep; +import org.springframework.batch.core.step.FatalStepExecutionException; +import org.springframework.batch.core.step.StepInterruptionPolicy; +import org.springframework.batch.core.step.ThreadStepInterruptionPolicy; +import org.springframework.batch.core.step.skip.AlwaysSkipItemSkipPolicy; +import org.springframework.batch.core.step.skip.SkipListenerFailedException; +import org.springframework.batch.core.step.skip.SkipPolicy; +import org.springframework.batch.item.Chunk; +import org.springframework.batch.item.ExecutionContext; +import org.springframework.batch.item.ItemProcessor; +import org.springframework.batch.item.ItemReader; +import org.springframework.batch.item.ItemStream; +import org.springframework.batch.item.ItemWriter; +import org.springframework.batch.item.support.CompositeItemStream; +import org.springframework.batch.support.transaction.ResourcelessTransactionManager; +import org.springframework.core.retry.RetryException; +import org.springframework.core.retry.RetryListener; +import org.springframework.core.retry.RetryPolicy; +import org.springframework.core.retry.RetryTemplate; +import org.springframework.core.retry.Retryable; +import org.springframework.core.retry.support.CompositeRetryListener; +import org.springframework.core.task.AsyncTaskExecutor; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.interceptor.DefaultTransactionAttribute; +import org.springframework.transaction.interceptor.TransactionAttribute; +import org.springframework.transaction.support.TransactionTemplate; +import org.springframework.util.Assert; + +import static org.springframework.batch.core.observability.BatchMetrics.METRICS_PREFIX; + +/** + * Step implementation for the chunk-oriented processing model. This class also supports + * faut-tolerance features (retry and skip) as well as concurrent item processing when a + * {@link AsyncTaskExecutor} is provided. + * + * @param type of input items + * @param type of output items + * @author Mahmoud Ben Hassine + * @since 6.0 + */ +public class ChunkOrientedStep extends AbstractStep { + + private static final Log logger = LogFactory.getLog(ChunkOrientedStep.class.getName()); + + /* + * Step Input / Output parameters + */ + private final ItemReader itemReader; + + private final CompositeItemReadListener compositeItemReadListener = new CompositeItemReadListener<>(); + + @SuppressWarnings("unchecked") + private ItemProcessor itemProcessor = item -> (O) item; + + private final CompositeItemProcessListener compositeItemProcessListener = new CompositeItemProcessListener<>(); + + private final ItemWriter itemWriter; + + private final CompositeItemWriteListener compositeItemWriteListener = new CompositeItemWriteListener<>(); + + /* + * Step state / interruption parameters + */ + private final CompositeItemStream compositeItemStream = new CompositeItemStream(); + + private StepInterruptionPolicy interruptionPolicy = new ThreadStepInterruptionPolicy(); + + /* + * Transaction related parameters + */ + private PlatformTransactionManager transactionManager; + + private TransactionTemplate transactionTemplate; + + private TransactionAttribute transactionAttribute; + + /* + * Chunk related parameters + */ + private final int chunkSize; + + private final ChunkTracker chunkTracker = new ChunkTracker(); + + private final CompositeChunkListener compositeChunkListener = new CompositeChunkListener<>(); + + /* + * Fault-tolerance parameters + */ + private boolean faultTolerant = false; + + private RetryPolicy retryPolicy = throwable -> false; + + private RetryTemplate retryTemplate; + + private final CompositeRetryListener compositeRetryListener = new CompositeRetryListener(); + + private SkipPolicy skipPolicy = new AlwaysSkipItemSkipPolicy(); + + private final CompositeSkipListener compositeSkipListener = new CompositeSkipListener<>(); + + /* + * Concurrency parameters + */ + private AsyncTaskExecutor taskExecutor; + + /** + * Create a new {@link ChunkOrientedStep}. + * @param name the name of the step + * @param chunkSize the size of the chunk to process + * @param itemReader the item reader to read items + * @param itemWriter the item writer to write items + * @param jobRepository the job repository to use for this step + */ + public ChunkOrientedStep(String name, int chunkSize, ItemReader itemReader, ItemWriter itemWriter, + JobRepository jobRepository) { + super(jobRepository); + this.chunkSize = chunkSize; + this.itemReader = itemReader; + this.itemWriter = itemWriter; + setName(name); + } + + /** + * Set the item processor to use for processing items. + * @param itemProcessor the item processor to set + */ + public void setItemProcessor(ItemProcessor itemProcessor) { + Assert.notNull(itemProcessor, "Item processor must not be null"); + this.itemProcessor = itemProcessor; + } + + /** + * Set the step interruption policy to use for checking if the step should be + * interrupted. Checked at chunk boundaries. Defaults to + * {@link ThreadStepInterruptionPolicy}. + */ + public void setInterruptionPolicy(StepInterruptionPolicy interruptionPolicy) { + Assert.notNull(interruptionPolicy, "Interruption policy must not be null"); + this.interruptionPolicy = interruptionPolicy; + } + + /** + * Register an {@link ItemStream} with this step. The stream will be opened and closed + * as part of the step's lifecycle. + * @param stream the item stream to register + */ + public void registerItemStream(ItemStream stream) { + Assert.notNull(stream, "Item stream must not be null"); + this.compositeItemStream.register(stream); + } + + /** + * Set the {@link ItemReadListener} to be notified of item read events. + * @param itemReadListener the item read listener to set + */ + public void registerItemReadListener(ItemReadListener itemReadListener) { + Assert.notNull(itemReadListener, "Item read listener must not be null"); + this.compositeItemReadListener.register(itemReadListener); + } + + /** + * Set the {@link ItemProcessListener} to be notified of item processing events. + * @param itemProcessListener the item process listener to set + */ + public void registerItemProcessListener(ItemProcessListener itemProcessListener) { + Assert.notNull(itemProcessListener, "Item process listener must not be null"); + this.compositeItemProcessListener.register(itemProcessListener); + } + + /** + * Set the {@link ItemWriteListener} to be notified of item write events. + * @param itemWriteListener the item write listener to set + */ + public void registerItemWriteListener(ItemWriteListener itemWriteListener) { + Assert.notNull(itemWriteListener, "Item write listener must not be null"); + this.compositeItemWriteListener.register(itemWriteListener); + } + + /** + * Set the {@link ChunkListener} to be notified of chunk processing events. + * @param chunkListener the chunk listener to set + */ + public void registerChunkListener(ChunkListener chunkListener) { + Assert.notNull(chunkListener, "Chunk listener must not be null"); + this.compositeChunkListener.register(chunkListener); + } + + /** + * Set the {@link PlatformTransactionManager} to use for the chunk-oriented tasklet. + * Defaults to a {@link ResourcelessTransactionManager}. + * @param transactionManager a transaction manager set, must not be null. + */ + public void setTransactionManager(PlatformTransactionManager transactionManager) { + Assert.notNull(transactionManager, "Transaction manager must not be null"); + this.transactionManager = transactionManager; + } + + /** + * Set the transaction attribute for this step. + * @param transactionAttribute the transaction attribute to set + */ + public void setTransactionAttribute(TransactionAttribute transactionAttribute) { + Assert.notNull(transactionAttribute, "Transaction attribute must not be null"); + this.transactionAttribute = transactionAttribute; + } + + /** + * Mark this step as fault-tolerant. When set to true, the step will handle retrying + * and skipping items that failed according to the configured retry and skip policies. + * If set to false, any exception during item processing will cause the step to fail + * immediately. + * @param faultTolerant true to enable fault-tolerant processing, false otherwise + */ + public void setFaultTolerant(boolean faultTolerant) { + this.faultTolerant = faultTolerant; + } + + /** + * Set the {@link AsyncTaskExecutor} to use for processing items asynchronously. + * @param asyncTaskExecutor the asynchronous task executor to set + */ + public void setTaskExecutor(AsyncTaskExecutor asyncTaskExecutor) { + Assert.notNull(asyncTaskExecutor, "Task executor must not be null"); + this.taskExecutor = asyncTaskExecutor; + } + + /** + * Set the {@link RetryPolicy} for this step. + * @param retryPolicy the retry policy to set + */ + public void setRetryPolicy(RetryPolicy retryPolicy) { + Assert.notNull(retryPolicy, "Retry policy must not be null"); + this.retryPolicy = retryPolicy; + } + + /** + * Register a {@link RetryListener} to be notified of item retry events. + * @param retryListener the retry listener to register + */ + public void registerRetryListener(RetryListener retryListener) { + Assert.notNull(retryListener, "Retry listener must not be null"); + this.compositeRetryListener.addListener(retryListener); + } + + /** + * Set the skip policy for this step. The skip policy will be used to determine + * whether an item should be skipped or not when an exception occurs during item + * processing. + * @param skipPolicy the skip policy to set. Defaults to + * {@link AlwaysSkipItemSkipPolicy}. + */ + public void setSkipPolicy(SkipPolicy skipPolicy) { + Assert.notNull(skipPolicy, "Skip policy must not be null"); + this.skipPolicy = skipPolicy; + } + + /** + * register a {@link SkipListener} to be notified of item skip events. + * @param skipListener the skip listener to register + */ + public void registerSkipListener(SkipListener skipListener) { + Assert.notNull(skipListener, "Skip listener must not be null"); + this.compositeSkipListener.register(skipListener); + } + + @Override + public void afterPropertiesSet() throws Exception { + super.afterPropertiesSet(); + if (this.transactionManager == null) { + logger.info("No transaction manager has been set. Defaulting to ResourcelessTransactionManager."); + this.transactionManager = new ResourcelessTransactionManager(); + } + if (this.transactionAttribute == null) { + logger.info("No transaction attribute has been set. Defaulting to DefaultTransactionAttribute."); + this.transactionAttribute = new DefaultTransactionAttribute(); + } + Assert.isTrue(this.chunkSize > 0, "Chunk size must be greater than 0"); + Assert.notNull(this.itemReader, "Item reader must not be null"); + Assert.notNull(this.itemWriter, "Item writer must not be null"); + if (this.itemReader instanceof ItemStream itemStream) { + this.compositeItemStream.register(itemStream); + } + if (this.itemWriter instanceof ItemStream itemStream) { + this.compositeItemStream.register(itemStream); + } + if (this.itemProcessor instanceof ItemStream itemStream) { + this.compositeItemStream.register(itemStream); + } + this.transactionTemplate = new TransactionTemplate(this.transactionManager, this.transactionAttribute); + if (this.faultTolerant) { + this.retryTemplate = new RetryTemplate(); + this.retryTemplate.setRetryPolicy(this.retryPolicy); + this.retryTemplate.setRetryListener(this.compositeRetryListener); + } + } + + @Override + protected void open(ExecutionContext executionContext) throws Exception { + this.compositeItemStream.open(executionContext); + } + + @Override + protected void close(ExecutionContext executionContext) throws Exception { + this.compositeItemStream.close(); + } + + @Override + protected void doExecute(StepExecution stepExecution) throws Exception { + stepExecution.getExecutionContext().put(STEP_TYPE_KEY, this.getClass().getName()); + while (this.chunkTracker.moreItems() && !interrupted(stepExecution)) { + // process next chunk in its own transaction + this.transactionTemplate.executeWithoutResult(transactionStatus -> { + ChunkTransactionEvent chunkTransactionEvent = new ChunkTransactionEvent(stepExecution.getStepName(), + stepExecution.getId()); + chunkTransactionEvent.begin(); + StepContribution contribution = stepExecution.createStepContribution(); + processNextChunk(transactionStatus, contribution, stepExecution); + chunkTransactionEvent.transactionStatus = transactionStatus.isRollbackOnly() + ? BatchMetrics.STATUS_ROLLED_BACK : BatchMetrics.STATUS_COMMITTED; + chunkTransactionEvent.commit(); + }); + } + } + + private void processNextChunk(TransactionStatus status, StepContribution contribution, + StepExecution stepExecution) { + if (isConcurrent()) { + processChunkConcurrently(status, contribution, stepExecution); + } + else { + processChunkSequentially(status, contribution, stepExecution); + } + } + + private void processChunkConcurrently(TransactionStatus status, StepContribution contribution, + StepExecution stepExecution) { + List> itemProcessingTasks = new LinkedList<>(); + try { + // read items and submit concurrent item processing tasks + for (int i = 0; i < this.chunkSize; i++) { + I item = readItem(contribution); + if (item != null) { + Future itemProcessingFuture = this.taskExecutor.submit(() -> processItem(item, contribution)); + itemProcessingTasks.add(itemProcessingFuture); + } + } + // exclude empty chunks (when the total items is a multiple of the chunk size) + if (itemProcessingTasks.isEmpty()) { + return; + } + + // collect processed items + Chunk processedChunk = new Chunk<>(); + for (Future future : itemProcessingTasks) { + O processedItem = future.get(); + if (processedItem != null) { + processedChunk.add(processedItem); + } + } + + // write processed items + writeChunk(processedChunk, contribution); + stepExecution.incrementCommitCount(); + } + catch (Exception e) { + logger.error("Rolling back chunk transaction", e); + status.setRollbackOnly(); + stepExecution.incrementRollbackCount(); + throw new FatalStepExecutionException("Unable to process chunk", e); + } + finally { + // apply contribution and update streams + stepExecution.apply(contribution); + this.compositeItemStream.update(stepExecution.getExecutionContext()); + } + + } + + private void processChunkSequentially(TransactionStatus status, StepContribution contribution, + StepExecution stepExecution) { + Chunk inputChunk = new Chunk<>(); + Chunk processedChunk = new Chunk<>(); + try { + inputChunk = readChunk(contribution); + if (inputChunk.isEmpty()) { + return; + } + compositeChunkListener.beforeChunk(inputChunk); + processedChunk = processChunk(inputChunk, contribution); + writeChunk(processedChunk, contribution); + compositeChunkListener.afterChunk(processedChunk); + stepExecution.incrementCommitCount(); + } + catch (Exception e) { + logger.error("Rolling back chunk transaction", e); + status.setRollbackOnly(); + stepExecution.incrementRollbackCount(); + compositeChunkListener.onChunkError(e, processedChunk); + throw new FatalStepExecutionException("Unable to process chunk", e); + } + finally { + // apply contribution and update streams + stepExecution.apply(contribution); + compositeItemStream.update(stepExecution.getExecutionContext()); + } + } + + /* + * Check if the step has been interrupted either internally via user defined policy or + * externally via job operator. This will be checked at chunk boundaries. + */ + private boolean interrupted(StepExecution stepExecution) { + // check internal interruption via user defined policy + try { + this.interruptionPolicy.checkInterrupted(stepExecution); + } + catch (JobInterruptedException exception) { + return true; + } + // check external interruption via job operator + if (stepExecution.isTerminateOnly()) { + return true; + } + return false; + } + + private Chunk readChunk(StepContribution contribution) throws Exception { + Chunk chunk = new Chunk<>(); + for (int i = 0; i < chunkSize; i++) { + I item = readItem(contribution); + if (item != null) { + chunk.add(item); + } + } + return chunk; + } + + @Nullable private I readItem(StepContribution contribution) throws Exception { + ItemReadEvent itemReadEvent = new ItemReadEvent(contribution.getStepExecution().getStepName(), + contribution.getStepExecution().getId()); + String fullyQualifiedMetricName = BatchMetrics.METRICS_PREFIX + "item.read"; + Observation observation = Observation.createNotStarted(fullyQualifiedMetricName, this.observationRegistry) + .lowCardinalityKeyValue(fullyQualifiedMetricName + ".job.name", + contribution.getStepExecution().getJobExecution().getJobInstance().getJobName()) + .lowCardinalityKeyValue(fullyQualifiedMetricName + ".step.name", + contribution.getStepExecution().getStepName()) + .start(); + itemReadEvent.begin(); + I item = null; + try (var scope = observation.openScope()) { + this.compositeItemReadListener.beforeRead(); + item = doRead(); + if (item == null) { + this.chunkTracker.noMoreItems(); + } + else { + contribution.incrementReadCount(); + this.compositeItemReadListener.afterRead(item); + } + itemReadEvent.itemReadStatus = BatchMetrics.STATUS_SUCCESS; + observation.lowCardinalityKeyValue(fullyQualifiedMetricName + ".status", BatchMetrics.STATUS_SUCCESS); + } + catch (Exception exception) { + this.compositeItemReadListener.onReadError(exception); + if (this.faultTolerant && exception instanceof RetryException retryException) { + doSkipInRead(retryException, contribution); + } + else { + throw exception; + } + itemReadEvent.itemReadStatus = BatchMetrics.STATUS_FAILURE; + observation.lowCardinalityKeyValue(fullyQualifiedMetricName + ".status", BatchMetrics.STATUS_FAILURE); + observation.error(exception); + } + finally { + itemReadEvent.commit(); + observation.stop(); + } + return item; + } + + @Nullable private I doRead() throws Exception { + if (this.faultTolerant) { + Retryable retryableRead = new Retryable<>() { + @Override + public @Nullable I execute() throws Throwable { + return itemReader.read(); + } + + @Override + public String getName() { + return "Retryable read operation"; + } + }; + return this.retryTemplate.execute(retryableRead); + } + else { + return this.itemReader.read(); + } + } + + private void doSkipInRead(RetryException retryException, StepContribution contribution) { + Throwable cause = retryException.getCause(); + if (this.skipPolicy.shouldSkip(cause, contribution.getStepSkipCount())) { + try { + this.compositeSkipListener.onSkipInRead(cause); + contribution.incrementReadSkipCount(); + } + catch (Throwable throwable) { + throw new SkipListenerFailedException("Unable to apply onSkipInRead", throwable); + } + } + } + + private Chunk processChunk(Chunk chunk, StepContribution contribution) throws Exception { + Chunk processedChunk = new Chunk<>(); + for (I item : chunk) { + O processedItem = processItem(item, contribution); + if (processedItem != null) { + processedChunk.add(processedItem); + } + } + return processedChunk; + } + + private O processItem(I item, StepContribution contribution) throws Exception { + ItemProcessEvent itemProcessEvent = new ItemProcessEvent(contribution.getStepExecution().getStepName(), + contribution.getStepExecution().getId()); + String fullyQualifiedMetricName = METRICS_PREFIX + "item.process"; + Observation observation = Observation.createNotStarted(fullyQualifiedMetricName, this.observationRegistry) + .lowCardinalityKeyValue(fullyQualifiedMetricName + ".job.name", + contribution.getStepExecution().getJobExecution().getJobInstance().getJobName()) + .lowCardinalityKeyValue(fullyQualifiedMetricName + ".step.name", + contribution.getStepExecution().getStepName()) + .start(); + itemProcessEvent.begin(); + O processedItem = null; + try (var scope = observation.openScope()) { + this.compositeItemProcessListener.beforeProcess(item); + processedItem = doProcess(item); + if (processedItem == null) { + contribution.incrementFilterCount(); + } + this.compositeItemProcessListener.afterProcess(item, processedItem); + itemProcessEvent.itemProcessStatus = BatchMetrics.STATUS_SUCCESS; + observation.lowCardinalityKeyValue(fullyQualifiedMetricName + ".status", BatchMetrics.STATUS_SUCCESS); + } + catch (Exception exception) { + this.compositeItemProcessListener.onProcessError(item, exception); + if (this.faultTolerant && exception instanceof RetryException retryException) { + doSkipInProcess(item, retryException, contribution); + } + else { + throw exception; + } + itemProcessEvent.itemProcessStatus = BatchMetrics.STATUS_FAILURE; + observation.lowCardinalityKeyValue(fullyQualifiedMetricName + ".status", BatchMetrics.STATUS_FAILURE); + observation.error(exception); + } + finally { + itemProcessEvent.commit(); + observation.stop(); + } + return processedItem; + } + + @Nullable private O doProcess(I item) throws Exception { + if (this.faultTolerant) { + Retryable retryableProcess = new Retryable<>() { + @Override + public @Nullable O execute() throws Throwable { + StepContext context = StepSynchronizationManager.getContext(); + final StepExecution stepExecution = context == null ? null : context.getStepExecution(); + if (isConcurrent() && stepExecution != null) { + StepSynchronizationManager.register(stepExecution); + } + try { + return itemProcessor.process(item); + } + finally { + if (isConcurrent() && stepExecution != null) { + StepSynchronizationManager.close(); + } + } + } + + @Override + public String getName() { + return "Retryable process operation"; + } + }; + return this.retryTemplate.execute(retryableProcess); + } + else { + return this.itemProcessor.process(item); + } + } + + private void doSkipInProcess(I item, RetryException retryException, StepContribution contribution) { + Throwable cause = retryException.getCause(); + if (this.skipPolicy.shouldSkip(cause, contribution.getStepSkipCount())) { + try { + this.compositeSkipListener.onSkipInProcess(item, retryException.getCause()); + contribution.incrementProcessSkipCount(); + } + catch (Throwable throwable) { + throw new SkipListenerFailedException("Unable to apply onSkipInProcess", throwable); + } + } + } + + private void writeChunk(Chunk chunk, StepContribution contribution) throws Exception { + ChunkWriteEvent chunkWriteEvent = new ChunkWriteEvent(contribution.getStepExecution().getStepName(), + contribution.getStepExecution().getId(), chunk.size()); + String fullyQualifiedMetricName = METRICS_PREFIX + "chunk.write"; + Observation observation = Observation.createNotStarted(fullyQualifiedMetricName, this.observationRegistry) + .lowCardinalityKeyValue(fullyQualifiedMetricName + ".job.name", + contribution.getStepExecution().getJobExecution().getJobInstance().getJobName()) + .lowCardinalityKeyValue(fullyQualifiedMetricName + ".step.name", + contribution.getStepExecution().getStepName()) + .start(); + chunkWriteEvent.begin(); + try (var scope = observation.openScope()) { + this.compositeItemWriteListener.beforeWrite(chunk); + doWrite(chunk); + contribution.incrementWriteCount(chunk.size()); + this.compositeItemWriteListener.afterWrite(chunk); + chunkWriteEvent.chunkWriteStatus = BatchMetrics.STATUS_SUCCESS; + observation.lowCardinalityKeyValue(fullyQualifiedMetricName + ".status", BatchMetrics.STATUS_SUCCESS); + } + catch (Exception exception) { + this.compositeItemWriteListener.onWriteError(exception, chunk); + chunkWriteEvent.chunkWriteStatus = BatchMetrics.STATUS_FAILURE; + observation.lowCardinalityKeyValue(fullyQualifiedMetricName + ".status", BatchMetrics.STATUS_FAILURE); + observation.error(exception); + if (this.faultTolerant && exception instanceof RetryException retryException) { + logger.info("Retry exhausted while attempting to write items, scanning the chunk", retryException); + ChunkScanEvent chunkScanEvent = new ChunkScanEvent(contribution.getStepExecution().getStepName(), + contribution.getStepExecution().getId()); + chunkScanEvent.begin(); + scan(chunk, contribution); + chunkScanEvent.skipCount = contribution.getSkipCount(); + chunkScanEvent.commit(); + logger.info("Chunk scan completed"); + } + else { + throw exception; + } + } + finally { + chunkWriteEvent.commit(); + observation.stop(); + } + } + + private void doWrite(Chunk chunk) throws Exception { + if (this.faultTolerant) { + Retryable retryableWrite = new Retryable<>() { + @Override + public @Nullable Void execute() throws Throwable { + itemWriter.write(chunk); + return null; + } + + @Override + public String getName() { + return "Retryable write operation"; + } + }; + this.retryTemplate.execute(retryableWrite); + } + else { + this.itemWriter.write(chunk); + } + } + + private void scan(Chunk chunk, StepContribution contribution) { + for (O item : chunk) { + Chunk singleItemChunk = new Chunk<>(item); + try { + this.compositeItemWriteListener.beforeWrite(singleItemChunk); + this.itemWriter.write(singleItemChunk); + contribution.incrementWriteCount(singleItemChunk.size()); + this.compositeItemWriteListener.afterWrite(singleItemChunk); + } + catch (Exception exception) { + if (this.skipPolicy.shouldSkip(exception, contribution.getStepSkipCount())) { + try { + this.compositeSkipListener.onSkipInWrite(item, exception); + contribution.incrementWriteSkipCount(); + } + catch (Throwable throwable) { + throw new SkipListenerFailedException("Unable to apply onSkipInWrite", throwable); + } + } + else { + logger.error("Failed to write item: " + item, exception); + this.compositeItemWriteListener.onWriteError(exception, singleItemChunk); + } + } + } + } + + private boolean isConcurrent() { + return this.taskExecutor != null; + } + + private static class ChunkTracker { + + private boolean moreItems = true; + + void noMoreItems() { + this.moreItems = false; + } + + boolean moreItems() { + return this.moreItems; + } + + } + +} \ No newline at end of file diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkOrientedTasklet.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkOrientedTasklet.java index ef29d45e2b..d6a7bb47c8 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkOrientedTasklet.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkOrientedTasklet.java @@ -18,7 +18,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.batch.core.StepContribution; +import org.springframework.batch.core.step.StepContribution; import org.springframework.batch.core.scope.context.ChunkContext; import org.springframework.batch.core.step.tasklet.Tasklet; import org.springframework.batch.item.Chunk; @@ -31,7 +31,11 @@ * @author Dave Syer * @author Mahmoud Ben Hassine * @param input item type + * @deprecated Since 6.0, use + * {@link org.springframework.batch.core.step.item.ChunkOrientedStep} instead. Scheduled + * for removal in 7.0. */ +@Deprecated(since = "6.0", forRemoval = true) public class ChunkOrientedTasklet implements Tasklet { private static final String INPUTS_KEY = "INPUTS"; 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..757f8422b5 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. @@ -16,14 +16,18 @@ package org.springframework.batch.core.step.item; -import org.springframework.batch.core.StepContribution; +import org.springframework.batch.core.step.StepContribution; import org.springframework.batch.item.Chunk; /** * Interface defined for processing {@link org.springframework.batch.item.Chunk}s. * + * @author Kyeonghoon Lee (Add FunctionalInterface annotation) * @since 2.0 + * @deprecated Since 6.0 with no replacement. Scheduled for removal in 7.0. */ +@Deprecated(since = "6.0", forRemoval = true) +@FunctionalInterface public interface ChunkProcessor { void process(StepContribution contribution, Chunk chunk) throws Exception; diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkProvider.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkProvider.java index f713af61fe..ceb2ef825b 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkProvider.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkProvider.java @@ -16,7 +16,7 @@ package org.springframework.batch.core.step.item; -import org.springframework.batch.core.StepContribution; +import org.springframework.batch.core.step.StepContribution; import org.springframework.batch.item.Chunk; /** @@ -25,7 +25,9 @@ * * @since 2.0 * @see ChunkOrientedTasklet + * @deprecated Since 6.0 with no replacement. Scheduled for removal in 7.0. */ +@Deprecated(since = "6.0", forRemoval = true) public interface ChunkProvider { Chunk provide(StepContribution contribution) throws Exception; diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/DefaultItemFailureHandler.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/DefaultItemFailureHandler.java index e51403aeb6..8d47d4e3f9 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/DefaultItemFailureHandler.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/DefaultItemFailureHandler.java @@ -29,8 +29,9 @@ * * @author Lucas Ward * @author Mahmoud Ben Hassine - * + * @deprecated Since 6.0 with no replacement. Scheduled for removal in 7.0. */ +@Deprecated(since = "6.0", forRemoval = true) public class DefaultItemFailureHandler extends ItemListenerSupport { protected static final Log logger = LogFactory.getLog(DefaultItemFailureHandler.class); diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/FaultTolerantChunkProcessor.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/FaultTolerantChunkProcessor.java index ecb797111c..0f47d1a0cd 100755 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/FaultTolerantChunkProcessor.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/FaultTolerantChunkProcessor.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. @@ -25,7 +25,8 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.batch.core.StepContribution; +import org.springframework.batch.core.observability.micrometer.MicrometerMetrics; +import org.springframework.batch.core.step.StepContribution; import org.springframework.batch.core.listener.StepListenerFailedException; import org.springframework.batch.core.observability.BatchMetrics; import org.springframework.batch.core.step.skip.LimitCheckingItemSkipPolicy; @@ -50,7 +51,11 @@ * FaultTolerant implementation of the {@link ChunkProcessor} interface, that allows for * skipping or retry of items that cause exceptions during writing. * + * @deprecated Since 6.0, use + * {@link org.springframework.batch.core.step.item.ChunkOrientedStep} instead. Scheduled + * for removal in 7.0. */ +@Deprecated(since = "6.0", forRemoval = true) public class FaultTolerantChunkProcessor extends SimpleChunkProcessor { private SkipPolicy itemProcessSkipPolicy = new LimitCheckingItemSkipPolicy(); @@ -82,17 +87,17 @@ public void setKeyGenerator(KeyGenerator keyGenerator) { } /** - * @param SkipPolicy the {@link SkipPolicy} for item processing + * @param skipPolicy the {@link SkipPolicy} for item processing */ - public void setProcessSkipPolicy(SkipPolicy SkipPolicy) { - this.itemProcessSkipPolicy = SkipPolicy; + public void setProcessSkipPolicy(SkipPolicy skipPolicy) { + this.itemProcessSkipPolicy = skipPolicy; } /** - * @param SkipPolicy the {@link SkipPolicy} for item writing + * @param skipPolicy the {@link SkipPolicy} for item writing */ - public void setWriteSkipPolicy(SkipPolicy SkipPolicy) { - this.itemWriteSkipPolicy = SkipPolicy; + public void setWriteSkipPolicy(SkipPolicy skipPolicy) { + this.itemWriteSkipPolicy = skipPolicy; } /** @@ -216,7 +221,7 @@ protected Chunk transform(final StepContribution contribution, Chunk input final I item = iterator.next(); RetryCallback retryCallback = context -> { - Timer.Sample sample = BatchMetrics.createTimerSample(meterRegistry); + Timer.Sample sample = MicrometerMetrics.createTimerSample(meterRegistry); String status = BatchMetrics.STATUS_SUCCESS; O output = null; try { @@ -325,7 +330,7 @@ protected void write(final StepContribution contribution, final Chunk inputs, if (!data.scanning()) { chunkMonitor.setChunkSize(inputs.size()); - Timer.Sample sample = BatchMetrics.createTimerSample(meterRegistry); + Timer.Sample sample = MicrometerMetrics.createTimerSample(meterRegistry); String status = BatchMetrics.STATUS_SUCCESS; try { doWrite(outputs); @@ -347,6 +352,7 @@ protected void write(final StepContribution contribution, final Chunk inputs, stopTimer(sample, contribution.getStepExecution(), "chunk.write", status, "Chunk writing"); } contribution.incrementWriteCount(outputs.size()); + contribution.incrementWriteSkipCount(outputs.getSkipsSize()); } else { scan(contribution, inputs, outputs, chunkMonitor, false); @@ -489,7 +495,7 @@ private boolean shouldSkip(SkipPolicy policy, Throwable e, long skipCount) { throw ex; } catch (RuntimeException ex) { - throw new SkipListenerFailedException("Fatal exception in SkipPolicy.", ex, e); + throw new SkipListenerFailedException("Fatal exception in skipPolicy.", ex, e); } } @@ -526,11 +532,11 @@ private void checkSkipPolicy(Chunk.ChunkIterator inputIterator, Chunk.Chun throw new RetryException("Non-skippable exception in recoverer", e); } else { - if (e instanceof Exception) { - throw (Exception) e; + if (e instanceof Exception exception) { + throw exception; } - else if (e instanceof Error) { - throw (Error) e; + else if (e instanceof Error error) { + throw error; } else { throw new RetryException("Non-skippable throwable in recoverer", e); diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/FaultTolerantChunkProvider.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/FaultTolerantChunkProvider.java index 768bf0f793..1dfe0e5c4d 100755 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/FaultTolerantChunkProvider.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/FaultTolerantChunkProvider.java @@ -16,7 +16,7 @@ package org.springframework.batch.core.step.item; -import org.springframework.batch.core.StepContribution; +import org.springframework.batch.core.step.StepContribution; import org.springframework.batch.core.step.skip.LimitCheckingItemSkipPolicy; import org.springframework.batch.core.step.skip.NonSkippableReadException; import org.springframework.batch.core.step.skip.SkipException; @@ -33,7 +33,11 @@ * FaultTolerant implementation of the {@link ChunkProvider} interface, that allows for * skipping or retry of items that cause exceptions during reading or processing. * + * @deprecated Since 6.0, use + * {@link org.springframework.batch.core.step.item.ChunkOrientedStep} instead. Scheduled + * for removal in 7.0. */ +@Deprecated(since = "6.0", forRemoval = true) public class FaultTolerantChunkProvider extends SimpleChunkProvider { /** diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ForceRollbackForWriteSkipException.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ForceRollbackForWriteSkipException.java index 0873d5dcaf..90a9f651ea 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ForceRollbackForWriteSkipException.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ForceRollbackForWriteSkipException.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. @@ -22,8 +22,9 @@ * * @author Dave Syer * @author Mahmoud Ben Hassine - * + * @deprecated since 6.0 with no replacement. Scheduled for removal in 7.0. */ +@Deprecated(since = "6.0", forRemoval = true) public class ForceRollbackForWriteSkipException extends RuntimeException { public ForceRollbackForWriteSkipException(String msg, Throwable cause) { diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/KeyGenerator.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/KeyGenerator.java index ee705659f3..b1926cc90f 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/KeyGenerator.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/KeyGenerator.java @@ -21,8 +21,10 @@ * * @author Dave Syer * @author Taeik Lim - * + * @deprecated Since 6.0 in favor of equals/hashcode in a wrapper type if needed. + * Scheduled for removal in 7.0. */ +@Deprecated(since = "6.0", forRemoval = true) @FunctionalInterface public interface KeyGenerator { diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/SimpleChunkProcessor.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/SimpleChunkProcessor.java index 101945bb22..459f3ccc65 100755 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/SimpleChunkProcessor.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/SimpleChunkProcessor.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,9 +23,10 @@ import io.micrometer.core.instrument.Tag; import io.micrometer.core.instrument.Timer; -import org.springframework.batch.core.StepContribution; -import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.StepListener; +import org.springframework.batch.core.observability.micrometer.MicrometerMetrics; +import org.springframework.batch.core.step.StepContribution; +import org.springframework.batch.core.step.StepExecution; +import org.springframework.batch.core.listener.StepListener; import org.springframework.batch.core.listener.MulticasterBatchListener; import org.springframework.batch.core.observability.BatchMetrics; import org.springframework.batch.item.Chunk; @@ -40,7 +41,11 @@ * writing and processing. Any exceptions encountered will be rethrown. * * @see ChunkOrientedTasklet + * @deprecated Since 6.0, use + * {@link org.springframework.batch.core.step.item.ChunkOrientedStep} instead. Scheduled + * for removal in 7.0. */ +@Deprecated(since = "6.0", forRemoval = true) public class SimpleChunkProcessor implements ChunkProcessor, InitializingBean { private ItemProcessor itemProcessor; @@ -291,7 +296,7 @@ protected Chunk getAdjustedOutputs(Chunk inputs, Chunk outputs) { * @throws Exception if there is a problem */ protected void write(StepContribution contribution, Chunk inputs, Chunk outputs) throws Exception { - Timer.Sample sample = BatchMetrics.createTimerSample(this.meterRegistry); + Timer.Sample sample = MicrometerMetrics.createTimerSample(this.meterRegistry); String status = BatchMetrics.STATUS_SUCCESS; try { doWrite(outputs); @@ -309,6 +314,7 @@ protected void write(StepContribution contribution, Chunk inputs, Chunk ou stopTimer(sample, contribution.getStepExecution(), "chunk.write", status, "Chunk writing"); } contribution.incrementWriteCount(outputs.size()); + contribution.incrementWriteSkipCount(outputs.getSkipsSize()); } protected Chunk transform(StepContribution contribution, Chunk inputs) throws Exception { @@ -316,7 +322,7 @@ protected Chunk transform(StepContribution contribution, Chunk inputs) thr for (Chunk.ChunkIterator iterator = inputs.iterator(); iterator.hasNext();) { final I item = iterator.next(); O output; - Timer.Sample sample = BatchMetrics.createTimerSample(this.meterRegistry); + Timer.Sample sample = MicrometerMetrics.createTimerSample(this.meterRegistry); String status = BatchMetrics.STATUS_SUCCESS; try { output = doProcess(item); @@ -349,7 +355,7 @@ protected Chunk transform(StepContribution contribution, Chunk inputs) thr protected void stopTimer(Timer.Sample sample, StepExecution stepExecution, String metricName, String status, String description) { String fullyQualifiedMetricName = BatchMetrics.METRICS_PREFIX + metricName; - sample.stop(BatchMetrics.createTimer(this.meterRegistry, metricName, description + " duration", + sample.stop(MicrometerMetrics.createTimer(this.meterRegistry, metricName, description + " duration", Tag.of(fullyQualifiedMetricName + ".job.name", stepExecution.getJobExecution().getJobInstance().getJobName()), Tag.of(fullyQualifiedMetricName + ".step.name", stepExecution.getStepName()), diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/SimpleChunkProvider.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/SimpleChunkProvider.java index 3308d334b6..d5a5db7b11 100755 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/SimpleChunkProvider.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/SimpleChunkProvider.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,9 +24,11 @@ import io.micrometer.core.instrument.Timer; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.batch.core.StepContribution; -import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.StepListener; + +import org.springframework.batch.core.observability.micrometer.MicrometerMetrics; +import org.springframework.batch.core.step.StepContribution; +import org.springframework.batch.core.step.StepExecution; +import org.springframework.batch.core.listener.StepListener; import org.springframework.batch.core.listener.MulticasterBatchListener; import org.springframework.batch.core.observability.BatchMetrics; import org.springframework.batch.item.Chunk; @@ -43,7 +45,11 @@ * @author Michael Minella * @author Mahmoud Ben Hassine * @see ChunkOrientedTasklet + * @deprecated Since 6.0, use + * {@link org.springframework.batch.core.step.item.ChunkOrientedStep} instead. Scheduled + * for removal in 7.0. */ +@Deprecated(since = "6.0", forRemoval = true) public class SimpleChunkProvider implements ChunkProvider { protected final Log logger = LogFactory.getLog(getClass()); @@ -155,7 +161,7 @@ public Chunk provide(final StepContribution contribution) throws Exception { private void stopTimer(Timer.Sample sample, StepExecution stepExecution, String status) { String fullyQualifiedMetricName = BatchMetrics.METRICS_PREFIX + "item.read"; - sample.stop(BatchMetrics.createTimer(this.meterRegistry, "item.read", "Item reading duration", + sample.stop(MicrometerMetrics.createTimer(this.meterRegistry, "item.read", "Item reading duration", Tag.of(fullyQualifiedMetricName + ".job.name", stepExecution.getJobExecution().getJobInstance().getJobName()), Tag.of(fullyQualifiedMetricName + ".step.name", stepExecution.getStepName()), diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/SimpleRetryExceptionHandler.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/SimpleRetryExceptionHandler.java index 85c1d8a128..6d802f11f1 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/SimpleRetryExceptionHandler.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/SimpleRetryExceptionHandler.java @@ -34,8 +34,11 @@ * exception handling to another {@link ExceptionHandler}. * * @author Dave Syer - * + * @deprecated Since 6.0, use + * {@link org.springframework.batch.core.step.item.ChunkOrientedStep} instead. Scheduled + * for removal in 7.0. */ +@Deprecated(since = "6.0", forRemoval = true) public class SimpleRetryExceptionHandler implements RetryListener, ExceptionHandler { /** diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/SkipOverflowException.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/SkipOverflowException.java index 3c512af1af..409a580511 100755 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/SkipOverflowException.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/SkipOverflowException.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. @@ -21,8 +21,9 @@ /** * @author Dave Syer * @author Mahmoud Ben Hassine - * + * @deprecated since 6.0 with no replacement. Scheduled for removal in 7.0. */ +@Deprecated(since = "6.0", forRemoval = true) public class SkipOverflowException extends SkipException { /** diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/job/DefaultJobParametersExtractor.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/job/DefaultJobParametersExtractor.java index f37de435a5..dcbdb70ac3 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/job/DefaultJobParametersExtractor.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/job/DefaultJobParametersExtractor.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. @@ -21,11 +21,11 @@ import java.util.Properties; import java.util.Set; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobParameter; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.JobParametersBuilder; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.parameters.JobParameter; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.converter.DefaultJobParametersConverter; import org.springframework.batch.core.converter.JobParametersConverter; import org.springframework.batch.item.ExecutionContext; @@ -83,7 +83,7 @@ else if (jobParameters.containsKey(key)) { builder.addJobParameter(key, jobParameters.get(key)); } } - builder.addJobParameters(this.jobParametersConverter.getJobParameters(properties)); + builder.addJobParameters(convert(properties)); return builder.toJobParameters(); } @@ -99,10 +99,24 @@ public void setUseAllParentParameters(boolean useAllParentParameters) { /** * Set the {@link JobParametersConverter} to use. * @param jobParametersConverter the converter to use. Must not be {@code null}. + * @deprecated since 6.0 in favor of {@link #convert(Properties)}, scheduled for + * removal in 6.2 or later. */ + @Deprecated(since = "6.0", forRemoval = true) public void setJobParametersConverter(@NonNull JobParametersConverter jobParametersConverter) { Assert.notNull(jobParametersConverter, "jobParametersConverter must not be null"); this.jobParametersConverter = jobParametersConverter; } + /** + * Convert the given {@link Properties} to {@link JobParameters}. + * @param properties the properties to convert + * @return the converted job parameters + * + * @since 6.0 + */ + protected JobParameters convert(Properties properties) { + return this.jobParametersConverter.getJobParameters(properties); + } + } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/job/JobParametersExtractor.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/job/JobParametersExtractor.java index ecc5f86b81..a365d31c0e 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/job/JobParametersExtractor.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/job/JobParametersExtractor.java @@ -15,9 +15,9 @@ */ package org.springframework.batch.core.step.job; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.step.StepExecution; /** * Strategy interface for translating a {@link StepExecution} into {@link JobParameters}. diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/job/JobStep.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/job/JobStep.java index 2176fbe73d..da7f628a41 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/job/JobStep.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/job/JobStep.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2008 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. @@ -17,13 +17,14 @@ import org.springframework.batch.core.BatchStatus; import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.UnexpectedJobExecutionException; -import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.launch.JobOperator; +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.step.StepExecution; +import org.springframework.batch.core.job.UnexpectedJobExecutionException; import org.springframework.batch.core.step.AbstractStep; import org.springframework.batch.item.ExecutionContext; import org.springframework.util.Assert; @@ -36,6 +37,7 @@ * worker in a parallel or partitioned execution. * * @author Dave Syer + * @author Mahmoud Ben Hassine * */ public class JobStep extends AbstractStep { @@ -47,14 +49,23 @@ public class JobStep extends AbstractStep { private Job job; - private JobLauncher jobLauncher; + private JobOperator jobOperator; private JobParametersExtractor jobParametersExtractor = new DefaultJobParametersExtractor(); + /** + * Create a new instance of a {@link JobStep} with the given job repository. + * @param jobRepository the job repository to use. Must not be null. + * @since 6.0 + */ + public JobStep(JobRepository jobRepository) { + super(jobRepository); + } + @Override public void afterPropertiesSet() throws Exception { super.afterPropertiesSet(); - Assert.state(jobLauncher != null, "A JobLauncher must be provided"); + Assert.state(jobOperator != null, "A JobOperator must be provided"); Assert.state(job != null, "A Job must be provided"); } @@ -67,11 +78,11 @@ public void setJob(Job job) { } /** - * A {@link JobLauncher} is required to be able to run the enclosed {@link Job}. - * @param jobLauncher the {@link JobLauncher} to set + * A {@link JobOperator} is required to be able to start the enclosed {@link Job}. + * @param jobOperator the {@link JobOperator} to set */ - public void setJobLauncher(JobLauncher jobLauncher) { - this.jobLauncher = jobLauncher; + public void setJobOperator(JobOperator jobOperator) { + this.jobOperator = jobOperator; } /** @@ -86,7 +97,7 @@ public void setJobParametersExtractor(JobParametersExtractor jobParametersExtrac } /** - * Execute the job provided by delegating to the {@link JobLauncher} to prevent + * Execute the job provided by delegating to the {@link JobOperator} to prevent * duplicate executions. The job parameters will be generated by the * {@link JobParametersExtractor} provided (if any), otherwise empty. On a restart, * the job parameters will be the same as the last (failed) execution. @@ -109,7 +120,7 @@ protected void doExecute(StepExecution stepExecution) throws Exception { executionContext.put(JOB_PARAMETERS_KEY, jobParameters); } - JobExecution jobExecution = jobLauncher.run(job, jobParameters); + JobExecution jobExecution = jobOperator.start(job, jobParameters); stepExecution.setExitStatus(determineStepExitStatus(stepExecution, jobExecution)); diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/skip/LimitCheckingExceptionHierarchySkipPolicy.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/skip/LimitCheckingExceptionHierarchySkipPolicy.java new file mode 100644 index 0000000000..58492c91a0 --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/skip/LimitCheckingExceptionHierarchySkipPolicy.java @@ -0,0 +1,74 @@ +/* + * Copyright 2025-present 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.step.skip; + +import java.util.HashSet; +import java.util.Set; + +import org.springframework.util.Assert; + +/** + * A composite {@link SkipPolicy} that checks if the exception is assignable from one of + * the given skippable exceptions, and counts the number of skips to not exceed a given + * limit. + * + * @author Mahmoud Ben Hassine + * @since 6.0 + */ +public class LimitCheckingExceptionHierarchySkipPolicy implements SkipPolicy { + + private Set> skippableExceptions = new HashSet<>(); + + private long skipLimit = -1; + + /** + * Create a new {@link LimitCheckingExceptionHierarchySkipPolicy} instance. + * @param skippableExceptions exception classes that can be skipped (non-critical) + * @param skipLimit the number of skippable exceptions that are allowed to be skipped + */ + public LimitCheckingExceptionHierarchySkipPolicy(Set> skippableExceptions, + long skipLimit) { + Assert.notEmpty(skippableExceptions, "The skippableExceptions must not be empty"); + Assert.isTrue(skipLimit > 0, "The skipLimit must be greater than zero"); + this.skippableExceptions = skippableExceptions; + this.skipLimit = skipLimit; + } + + @Override + public boolean shouldSkip(Throwable t, long skipCount) throws SkipLimitExceededException { + if (!isSkippable(t)) { + return false; + } + if (skipCount < this.skipLimit) { + return true; + } + else { + throw new SkipLimitExceededException(this.skipLimit, t); + } + } + + private boolean isSkippable(Throwable t) { + boolean isSkippable = false; + for (Class skippableException : this.skippableExceptions) { + if (skippableException.isAssignableFrom(t.getClass())) { + isSkippable = true; + break; + } + } + return isSkippable; + } + +} \ No newline at end of file diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/skip/LimitCheckingItemSkipPolicy.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/skip/LimitCheckingItemSkipPolicy.java index fb515ea969..ceeedc374a 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/skip/LimitCheckingItemSkipPolicy.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/skip/LimitCheckingItemSkipPolicy.java @@ -19,8 +19,8 @@ import java.util.Collections; import java.util.Map; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.item.file.FlatFileParseException; import org.springframework.classify.BinaryExceptionClassifier; import org.springframework.classify.Classifier; @@ -49,7 +49,10 @@ * @author Dave Syer * @author Dan Garrette * @author Mahmoud Ben Hassine + * @deprecated Since 6.0, use {@link LimitCheckingExceptionHierarchySkipPolicy} instead. + * Scheduled for removal in 7.0. */ +@Deprecated(since = "6.0", forRemoval = true) public class LimitCheckingItemSkipPolicy implements SkipPolicy { private long skipLimit; diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/skip/SkipException.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/skip/SkipException.java index 969eae5a6a..09b65ad2c4 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/skip/SkipException.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/skip/SkipException.java @@ -15,7 +15,7 @@ */ package org.springframework.batch.core.step.skip; -import org.springframework.batch.core.UnexpectedJobExecutionException; +import org.springframework.batch.core.job.UnexpectedJobExecutionException; /** * Base exception indicating that the skip has failed or caused a failure. diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/skip/SkipLimitExceededException.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/skip/SkipLimitExceededException.java index 5fc31c05f6..33fd4ac9db 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/skip/SkipLimitExceededException.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/skip/SkipLimitExceededException.java @@ -15,9 +15,11 @@ */ package org.springframework.batch.core.step.skip; +import org.springframework.batch.core.step.Step; + /** - * Exception indicating that the skip limit for a particular - * {@link org.springframework.batch.core.Step} has been exceeded. + * Exception indicating that the skip limit for a particular {@link Step} has been + * exceeded. * * @author Ben Hale * @author Lucas Ward diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/skip/SkipListenerFailedException.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/skip/SkipListenerFailedException.java index d883ccf485..8af3a4904b 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/skip/SkipListenerFailedException.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/skip/SkipListenerFailedException.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. @@ -15,8 +15,8 @@ */ package org.springframework.batch.core.step.skip; -import org.springframework.batch.core.SkipListener; -import org.springframework.batch.core.UnexpectedJobExecutionException; +import org.springframework.batch.core.listener.SkipListener; +import org.springframework.batch.core.job.UnexpectedJobExecutionException; /** * Special exception to indicate a failure in a skip listener. These need special @@ -28,6 +28,16 @@ */ public class SkipListenerFailedException extends UnexpectedJobExecutionException { + /** + * Create a new {@link SkipListenerFailedException}. + * @param message the exception message + * @param throwable the error that was thrown by a {@link SkipListener} + * @since 6.0 + */ + public SkipListenerFailedException(String message, Throwable throwable) { + super(message, throwable); + } + /** * @param message describes the error to the user * @param ex the exception that was thrown by a {@link SkipListener} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/skip/SkipPolicy.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/skip/SkipPolicy.java index 15486d82b7..e1eb831d7c 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/skip/SkipPolicy.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/skip/SkipPolicy.java @@ -28,9 +28,9 @@ public interface SkipPolicy { /** * Returns true or false, indicating whether or not processing should continue with - * the given throwable. Clients may use {@code skipCount<0} to probe for exception + * the given throwable. Clients may use {@code skipCount < 0} to probe for exception * types that are skippable, so implementations should be able to handle gracefully - * the case where {@code skipCount<0}. Implementations should avoid throwing any + * the case where {@code skipCount < 0}. Implementations should avoid throwing any * undeclared exceptions. * @param t exception encountered while processing * @param skipCount currently running count of skips diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/skip/SkipPolicyFailedException.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/skip/SkipPolicyFailedException.java index f5ce18e446..d38c09beba 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/skip/SkipPolicyFailedException.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/skip/SkipPolicyFailedException.java @@ -15,7 +15,7 @@ */ package org.springframework.batch.core.step.skip; -import org.springframework.batch.core.UnexpectedJobExecutionException; +import org.springframework.batch.core.job.UnexpectedJobExecutionException; /** * Special exception to indicate a failure in a skip policy. These need special treatment diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/tasklet/CallableTaskletAdapter.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/tasklet/CallableTaskletAdapter.java index de610aaa22..d22f2e7360 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/tasklet/CallableTaskletAdapter.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/tasklet/CallableTaskletAdapter.java @@ -17,7 +17,7 @@ import java.util.concurrent.Callable; -import org.springframework.batch.core.StepContribution; +import org.springframework.batch.core.step.StepContribution; import org.springframework.batch.core.scope.context.ChunkContext; import org.springframework.batch.repeat.RepeatStatus; import org.springframework.beans.factory.InitializingBean; diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/tasklet/MethodInvokingTaskletAdapter.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/tasklet/MethodInvokingTaskletAdapter.java index 86206538f4..c1ff049150 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/tasklet/MethodInvokingTaskletAdapter.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/tasklet/MethodInvokingTaskletAdapter.java @@ -16,7 +16,7 @@ package org.springframework.batch.core.step.tasklet; import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.StepContribution; +import org.springframework.batch.core.step.StepContribution; import org.springframework.batch.core.scope.context.ChunkContext; import org.springframework.batch.item.adapter.AbstractMethodInvokingDelegator; import org.springframework.batch.repeat.RepeatStatus; @@ -59,8 +59,8 @@ public RepeatStatus execute(StepContribution contribution, ChunkContext chunkCon * @return an {@link ExitStatus} consistent with the result */ protected ExitStatus mapResult(Object result) { - if (result instanceof ExitStatus) { - return (ExitStatus) result; + if (result instanceof ExitStatus exitStatus) { + return exitStatus; } return ExitStatus.COMPLETED; } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/tasklet/StoppableTasklet.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/tasklet/StoppableTasklet.java index 4d604afd6d..23e022d787 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/tasklet/StoppableTasklet.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/tasklet/StoppableTasklet.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-present 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,7 +15,9 @@ */ package org.springframework.batch.core.step.tasklet; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.launch.JobOperator; +import org.springframework.batch.core.step.StepContribution; /** * An extension to the {@link Tasklet} interface to allow users to add logic for stopping @@ -24,10 +26,11 @@ * will attempt to call the stop method on any currently running StoppableTasklet. The * call to {@link StoppableTasklet#stop()} will be from a thread other than the thread * executing - * {@link org.springframework.batch.core.step.tasklet.Tasklet#execute(org.springframework.batch.core.StepContribution, org.springframework.batch.core.scope.context.ChunkContext)} + * {@link org.springframework.batch.core.step.tasklet.Tasklet#execute(StepContribution, org.springframework.batch.core.scope.context.ChunkContext)} * so the appropriate thread safety and visibility controls should be put in place. * * @author Will Schipp + * @author Hyunsang Han * @since 3.0 */ public interface StoppableTasklet extends Tasklet { @@ -35,7 +38,20 @@ public interface StoppableTasklet extends Tasklet { /** * Used to signal that the job this {@link Tasklet} is executing within has been * requested to stop. + * @deprecated Since 6.0, use {@link #stop(StepExecution)} instead. */ + @Deprecated(since = "6.0", forRemoval = true) void stop(); + /** + * Used to signal that the job should stop, providing access to the current + * {@link StepExecution} context. + * @param stepExecution the current {@link StepExecution} context in which the job is + * being executed + * @since 6.0 + */ + default void stop(StepExecution stepExecution) { + stop(); + } + } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/tasklet/SystemCommandTasklet.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/tasklet/SystemCommandTasklet.java index 4499279e8e..467eb473c9 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/tasklet/SystemCommandTasklet.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/tasklet/SystemCommandTasklet.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,12 +23,12 @@ import org.apache.commons.logging.LogFactory; import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobInterruptedException; -import org.springframework.batch.core.StepContribution; -import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.StepExecutionListener; -import org.springframework.batch.core.explore.JobExplorer; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobInterruptedException; +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.core.step.StepContribution; +import org.springframework.batch.core.step.StepExecution; +import org.springframework.batch.core.listener.StepExecutionListener; import org.springframework.batch.core.scope.context.ChunkContext; import org.springframework.batch.repeat.RepeatStatus; import org.springframework.beans.factory.InitializingBean; @@ -61,6 +61,7 @@ * @author Will Schipp * @author Mahmoud Ben Hassine * @author Injae Kim + * @author Hyunsang Han */ public class SystemCommandTasklet implements StepExecutionListener, StoppableTasklet, InitializingBean { @@ -88,7 +89,7 @@ public class SystemCommandTasklet implements StepExecutionListener, StoppableTas private volatile boolean stopped = false; - private JobExplorer jobExplorer; + private JobRepository jobRepository; private boolean stoppable = false; @@ -113,7 +114,7 @@ public RepeatStatus execute(StepContribution contribution, ChunkContext chunkCon Thread.sleep(checkInterval);// moved to the end of the logic if (stoppable) { - JobExecution jobExecution = jobExplorer + JobExecution jobExecution = jobRepository .getJobExecution(chunkContext.getStepContext().getStepExecution().getJobExecutionId()); if (jobExecution.isStopping()) { @@ -201,11 +202,11 @@ public void afterPropertiesSet() throws Exception { Assert.state(systemProcessExitCodeMapper != null, "SystemProcessExitCodeMapper must be set"); Assert.state(timeout > 0, "timeout value must be greater than zero"); Assert.state(taskExecutor != null, "taskExecutor is required"); - stoppable = jobExplorer != null; + stoppable = jobRepository != null; } - public void setJobExplorer(JobExplorer jobExplorer) { - this.jobExplorer = jobExplorer; + public void setJobRepository(JobRepository jobRepository) { + this.jobRepository = jobRepository; } /** @@ -275,4 +276,25 @@ public void stop() { stopped = true; } + /** + * Interrupts the execution of the system command if the given {@link StepExecution} + * matches the current execution context. This method allows for granular control over + * stopping specific step executions, ensuring that only the intended command is + * halted. + *

+ * This method will interrupt the thread executing the system command only if + * {@link #setInterruptOnCancel(boolean)} has been set to true. Otherwise, the + * underlying command will be allowed to finish before the tasklet ends. + * @param stepExecution the current {@link StepExecution} context; the execution is + * interrupted if it matches the ongoing one. + * @since 6.0 + * @see StoppableTasklet#stop(StepExecution) + */ + @Override + public void stop(StepExecution stepExecution) { + if (stepExecution.equals(this.execution)) { + this.stopped = true; + } + } + } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/tasklet/Tasklet.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/tasklet/Tasklet.java index 651091eeaa..c0f9c6a56e 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/tasklet/Tasklet.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/tasklet/Tasklet.java @@ -15,7 +15,7 @@ */ package org.springframework.batch.core.step.tasklet; -import org.springframework.batch.core.StepContribution; +import org.springframework.batch.core.step.StepContribution; import org.springframework.batch.core.scope.context.ChunkContext; import org.springframework.batch.repeat.RepeatStatus; import org.springframework.lang.Nullable; diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/tasklet/TaskletStep.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/tasklet/TaskletStep.java index aaf192beb2..f7b53733ec 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/tasklet/TaskletStep.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/tasklet/TaskletStep.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,11 +18,12 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.batch.core.BatchStatus; -import org.springframework.batch.core.ChunkListener; -import org.springframework.batch.core.JobInterruptedException; -import org.springframework.batch.core.StepContribution; -import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.StepExecutionListener; +import org.springframework.batch.core.listener.ChunkListener; +import org.springframework.batch.core.job.JobInterruptedException; +import org.springframework.batch.core.observability.jfr.events.step.tasklet.TaskletExecutionEvent; +import org.springframework.batch.core.step.StepContribution; +import org.springframework.batch.core.step.StepExecution; +import org.springframework.batch.core.listener.StepExecutionListener; import org.springframework.batch.core.listener.CompositeChunkListener; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.scope.context.ChunkContext; @@ -40,6 +41,7 @@ import org.springframework.batch.repeat.RepeatOperations; import org.springframework.batch.repeat.RepeatStatus; import org.springframework.batch.repeat.support.RepeatTemplate; +import org.springframework.batch.support.transaction.ResourcelessTransactionManager; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.interceptor.DefaultTransactionAttribute; @@ -48,7 +50,6 @@ import org.springframework.transaction.support.TransactionSynchronization; import org.springframework.transaction.support.TransactionSynchronizationManager; import org.springframework.transaction.support.TransactionTemplate; -import org.springframework.util.Assert; import java.util.concurrent.Semaphore; @@ -102,22 +103,41 @@ public boolean rollbackOn(Throwable ex) { /** * Default constructor. + * @deprecated since 6.0 for removal in 7.0. Use {@link #TaskletStep(JobRepository)} + * instead. */ + @Deprecated(since = "6.0", forRemoval = true) public TaskletStep() { - this(null); + super(); } /** + * Create a new instance with the given name. + * @deprecated since 6.0 for removal in 7.0. Use {@link #TaskletStep(JobRepository)} + * instead. * @param name the name for the {@link TaskletStep} */ + @Deprecated(since = "6.0", forRemoval = true) public TaskletStep(String name) { super(name); } + /** + * Create a new instance with the given name and job repository. + * @param jobRepository the job repository to use. Must not be null. + * @since 6.0 + */ + public TaskletStep(JobRepository jobRepository) { + super(jobRepository); + } + @Override public void afterPropertiesSet() throws Exception { super.afterPropertiesSet(); - Assert.state(transactionManager != null, "A transaction manager must be provided"); + if (this.transactionManager == null) { + logger.info("No transaction manager has been set. Defaulting to ResourcelessTransactionManager."); + this.transactionManager = new ResourcelessTransactionManager(); + } } /** @@ -142,8 +162,8 @@ public void setTransactionAttribute(TransactionAttribute transactionAttribute) { */ public void setTasklet(Tasklet tasklet) { this.tasklet = tasklet; - if (tasklet instanceof StepExecutionListener) { - registerStepExecutionListener((StepExecutionListener) tasklet); + if (tasklet instanceof StepExecutionListener stepExecutionListener) { + registerStepExecutionListener(stepExecutionListener); } } @@ -222,9 +242,12 @@ public void setInterruptionPolicy(StepInterruptionPolicy interruptionPolicy) { */ @Override protected void doExecute(StepExecution stepExecution) throws Exception { - stepExecution.getExecutionContext().put(TASKLET_TYPE_KEY, tasklet.getClass().getName()); + String taskletType = tasklet.getClass().getName(); + stepExecution.getExecutionContext().put(TASKLET_TYPE_KEY, taskletType); stepExecution.getExecutionContext().put(STEP_TYPE_KEY, this.getClass().getName()); - + TaskletExecutionEvent taskletExecutionEvent = new TaskletExecutionEvent(stepExecution.getStepName(), + stepExecution.getId(), taskletType); + taskletExecutionEvent.begin(); stream.update(stepExecution.getExecutionContext()); getJobRepository().updateExecutionContext(stepExecution); @@ -266,6 +289,8 @@ public RepeatStatus doInChunkContext(RepeatContext repeatContext, ChunkContext c }); + taskletExecutionEvent.taskletStatus = stepExecution.getExitStatus().getExitCode(); + taskletExecutionEvent.commit(); } /** 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-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 5cf435ab68..65e8ac5ef9 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"> @@ -80,7 +80,7 @@ - + @@ -246,7 +246,7 @@ @@ -265,20 +265,6 @@ - - - - - - - - - - - + - + - - + @@ -563,7 +549,7 @@ - + @@ -575,7 +561,7 @@ - + @@ -587,7 +573,7 @@ - + @@ -680,16 +666,6 @@ - - - - - diff --git a/spring-batch-core/src/main/resources/org/springframework/batch/core/migration/2.2/migration-db2.sql b/spring-batch-core/src/main/resources/org/springframework/batch/core/migration/2.2/migration-db2.sql index e52da193b6..fbb92b8168 100644 --- a/spring-batch-core/src/main/resources/org/springframework/batch/core/migration/2.2/migration-db2.sql +++ b/spring-batch-core/src/main/resources/org/springframework/batch/core/migration/2.2/migration-db2.sql @@ -1,15 +1,15 @@ -- create the requisite table -CREATE TABLE BATCH_JOB_EXECUTION_PARAMS ( - JOB_EXECUTION_ID BIGINT NOT NULL , - TYPE_CD VARCHAR(6) NOT NULL , - KEY_NAME VARCHAR(100) NOT NULL , - STRING_VAL VARCHAR(250) , - DATE_VAL TIMESTAMP DEFAULT NULL , - LONG_VAL BIGINT , - DOUBLE_VAL DOUBLE PRECISION , - IDENTIFYING CHAR(1) NOT NULL , +CREATE TABLE BATCH_JOB_EXECUTION_PARAMS ( + JOB_EXECUTION_ID BIGINT NOT NULL, + TYPE_CD VARCHAR(6) NOT NULL, + KEY_NAME VARCHAR(100) NOT NULL, + STRING_VAL VARCHAR(250), + DATE_VAL TIMESTAMP DEFAULT NULL, + LONG_VAL BIGINT, + DOUBLE_VAL DOUBLE PRECISION, + IDENTIFYING CHAR(1) NOT NULL, constraint JOB_EXEC_PARAMS_FK foreign key (JOB_EXECUTION_ID) references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID) ) ; diff --git a/spring-batch-core/src/main/resources/org/springframework/batch/core/migration/2.2/migration-derby.sql b/spring-batch-core/src/main/resources/org/springframework/batch/core/migration/2.2/migration-derby.sql index e52da193b6..fbb92b8168 100644 --- a/spring-batch-core/src/main/resources/org/springframework/batch/core/migration/2.2/migration-derby.sql +++ b/spring-batch-core/src/main/resources/org/springframework/batch/core/migration/2.2/migration-derby.sql @@ -1,15 +1,15 @@ -- create the requisite table -CREATE TABLE BATCH_JOB_EXECUTION_PARAMS ( - JOB_EXECUTION_ID BIGINT NOT NULL , - TYPE_CD VARCHAR(6) NOT NULL , - KEY_NAME VARCHAR(100) NOT NULL , - STRING_VAL VARCHAR(250) , - DATE_VAL TIMESTAMP DEFAULT NULL , - LONG_VAL BIGINT , - DOUBLE_VAL DOUBLE PRECISION , - IDENTIFYING CHAR(1) NOT NULL , +CREATE TABLE BATCH_JOB_EXECUTION_PARAMS ( + JOB_EXECUTION_ID BIGINT NOT NULL, + TYPE_CD VARCHAR(6) NOT NULL, + KEY_NAME VARCHAR(100) NOT NULL, + STRING_VAL VARCHAR(250), + DATE_VAL TIMESTAMP DEFAULT NULL, + LONG_VAL BIGINT, + DOUBLE_VAL DOUBLE PRECISION, + IDENTIFYING CHAR(1) NOT NULL, constraint JOB_EXEC_PARAMS_FK foreign key (JOB_EXECUTION_ID) references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID) ) ; diff --git a/spring-batch-core/src/main/resources/org/springframework/batch/core/migration/2.2/migration-h2.sql b/spring-batch-core/src/main/resources/org/springframework/batch/core/migration/2.2/migration-h2.sql index e52da193b6..fbb92b8168 100644 --- a/spring-batch-core/src/main/resources/org/springframework/batch/core/migration/2.2/migration-h2.sql +++ b/spring-batch-core/src/main/resources/org/springframework/batch/core/migration/2.2/migration-h2.sql @@ -1,15 +1,15 @@ -- create the requisite table -CREATE TABLE BATCH_JOB_EXECUTION_PARAMS ( - JOB_EXECUTION_ID BIGINT NOT NULL , - TYPE_CD VARCHAR(6) NOT NULL , - KEY_NAME VARCHAR(100) NOT NULL , - STRING_VAL VARCHAR(250) , - DATE_VAL TIMESTAMP DEFAULT NULL , - LONG_VAL BIGINT , - DOUBLE_VAL DOUBLE PRECISION , - IDENTIFYING CHAR(1) NOT NULL , +CREATE TABLE BATCH_JOB_EXECUTION_PARAMS ( + JOB_EXECUTION_ID BIGINT NOT NULL, + TYPE_CD VARCHAR(6) NOT NULL, + KEY_NAME VARCHAR(100) NOT NULL, + STRING_VAL VARCHAR(250), + DATE_VAL TIMESTAMP DEFAULT NULL, + LONG_VAL BIGINT, + DOUBLE_VAL DOUBLE PRECISION, + IDENTIFYING CHAR(1) NOT NULL, constraint JOB_EXEC_PARAMS_FK foreign key (JOB_EXECUTION_ID) references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID) ) ; diff --git a/spring-batch-core/src/main/resources/org/springframework/batch/core/migration/2.2/migration-hsqldb.sql b/spring-batch-core/src/main/resources/org/springframework/batch/core/migration/2.2/migration-hsqldb.sql index e52da193b6..fbb92b8168 100644 --- a/spring-batch-core/src/main/resources/org/springframework/batch/core/migration/2.2/migration-hsqldb.sql +++ b/spring-batch-core/src/main/resources/org/springframework/batch/core/migration/2.2/migration-hsqldb.sql @@ -1,15 +1,15 @@ -- create the requisite table -CREATE TABLE BATCH_JOB_EXECUTION_PARAMS ( - JOB_EXECUTION_ID BIGINT NOT NULL , - TYPE_CD VARCHAR(6) NOT NULL , - KEY_NAME VARCHAR(100) NOT NULL , - STRING_VAL VARCHAR(250) , - DATE_VAL TIMESTAMP DEFAULT NULL , - LONG_VAL BIGINT , - DOUBLE_VAL DOUBLE PRECISION , - IDENTIFYING CHAR(1) NOT NULL , +CREATE TABLE BATCH_JOB_EXECUTION_PARAMS ( + JOB_EXECUTION_ID BIGINT NOT NULL, + TYPE_CD VARCHAR(6) NOT NULL, + KEY_NAME VARCHAR(100) NOT NULL, + STRING_VAL VARCHAR(250), + DATE_VAL TIMESTAMP DEFAULT NULL, + LONG_VAL BIGINT, + DOUBLE_VAL DOUBLE PRECISION, + IDENTIFYING CHAR(1) NOT NULL, constraint JOB_EXEC_PARAMS_FK foreign key (JOB_EXECUTION_ID) references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID) ) ; diff --git a/spring-batch-core/src/main/resources/org/springframework/batch/core/migration/2.2/migration-mysql.sql b/spring-batch-core/src/main/resources/org/springframework/batch/core/migration/2.2/migration-mysql.sql index 56eaa20d15..ee6d1fcb30 100644 --- a/spring-batch-core/src/main/resources/org/springframework/batch/core/migration/2.2/migration-mysql.sql +++ b/spring-batch-core/src/main/resources/org/springframework/batch/core/migration/2.2/migration-mysql.sql @@ -1,15 +1,15 @@ -- create the requisite table -CREATE TABLE BATCH_JOB_EXECUTION_PARAMS ( - JOB_EXECUTION_ID BIGINT NOT NULL , - TYPE_CD VARCHAR(6) NOT NULL , - KEY_NAME VARCHAR(100) NOT NULL , - STRING_VAL VARCHAR(250) , - DATE_VAL DATETIME DEFAULT NULL , - LONG_VAL BIGINT , - DOUBLE_VAL DOUBLE PRECISION , - IDENTIFYING CHAR(1) NOT NULL , +CREATE TABLE BATCH_JOB_EXECUTION_PARAMS ( + JOB_EXECUTION_ID BIGINT NOT NULL, + TYPE_CD VARCHAR(6) NOT NULL, + KEY_NAME VARCHAR(100) NOT NULL, + STRING_VAL VARCHAR(250), + DATE_VAL DATETIME DEFAULT NULL, + LONG_VAL BIGINT, + DOUBLE_VAL DOUBLE PRECISION, + IDENTIFYING CHAR(1) NOT NULL, constraint JOB_EXEC_PARAMS_FK foreign key (JOB_EXECUTION_ID) references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID) ) ENGINE=InnoDB; diff --git a/spring-batch-core/src/main/resources/org/springframework/batch/core/migration/2.2/migration-oracle.sql b/spring-batch-core/src/main/resources/org/springframework/batch/core/migration/2.2/migration-oracle.sql index dd3d074daf..27f54d3f6c 100644 --- a/spring-batch-core/src/main/resources/org/springframework/batch/core/migration/2.2/migration-oracle.sql +++ b/spring-batch-core/src/main/resources/org/springframework/batch/core/migration/2.2/migration-oracle.sql @@ -1,15 +1,15 @@ -- create the requisite table -CREATE TABLE BATCH_JOB_EXECUTION_PARAMS ( - JOB_EXECUTION_ID NUMBER(19,0) NOT NULL , - TYPE_CD VARCHAR2(6) NOT NULL , - KEY_NAME VARCHAR2(100) NOT NULL , - STRING_VAL VARCHAR2(250) , - DATE_VAL TIMESTAMP DEFAULT NULL , - LONG_VAL NUMBER(19,0) , - DOUBLE_VAL NUMBER , - IDENTIFYING CHAR(1) NOT NULL , +CREATE TABLE BATCH_JOB_EXECUTION_PARAMS ( + JOB_EXECUTION_ID NUMBER(19,0) NOT NULL, + TYPE_CD VARCHAR2(6) NOT NULL, + KEY_NAME VARCHAR2(100) NOT NULL, + STRING_VAL VARCHAR2(250), + DATE_VAL TIMESTAMP DEFAULT NULL, + LONG_VAL NUMBER(19,0), + DOUBLE_VAL NUMBER, + IDENTIFYING CHAR(1) NOT NULL, constraint JOB_EXEC_PARAMS_FK foreign key (JOB_EXECUTION_ID) references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID) ) ; diff --git a/spring-batch-core/src/main/resources/org/springframework/batch/core/migration/2.2/migration-postgresql.sql b/spring-batch-core/src/main/resources/org/springframework/batch/core/migration/2.2/migration-postgresql.sql index e52da193b6..fbb92b8168 100644 --- a/spring-batch-core/src/main/resources/org/springframework/batch/core/migration/2.2/migration-postgresql.sql +++ b/spring-batch-core/src/main/resources/org/springframework/batch/core/migration/2.2/migration-postgresql.sql @@ -1,15 +1,15 @@ -- create the requisite table -CREATE TABLE BATCH_JOB_EXECUTION_PARAMS ( - JOB_EXECUTION_ID BIGINT NOT NULL , - TYPE_CD VARCHAR(6) NOT NULL , - KEY_NAME VARCHAR(100) NOT NULL , - STRING_VAL VARCHAR(250) , - DATE_VAL TIMESTAMP DEFAULT NULL , - LONG_VAL BIGINT , - DOUBLE_VAL DOUBLE PRECISION , - IDENTIFYING CHAR(1) NOT NULL , +CREATE TABLE BATCH_JOB_EXECUTION_PARAMS ( + JOB_EXECUTION_ID BIGINT NOT NULL, + TYPE_CD VARCHAR(6) NOT NULL, + KEY_NAME VARCHAR(100) NOT NULL, + STRING_VAL VARCHAR(250), + DATE_VAL TIMESTAMP DEFAULT NULL, + LONG_VAL BIGINT, + DOUBLE_VAL DOUBLE PRECISION, + IDENTIFYING CHAR(1) NOT NULL, constraint JOB_EXEC_PARAMS_FK foreign key (JOB_EXECUTION_ID) references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID) ) ; diff --git a/spring-batch-core/src/main/resources/org/springframework/batch/core/migration/2.2/migration-sqlf.sql b/spring-batch-core/src/main/resources/org/springframework/batch/core/migration/2.2/migration-sqlf.sql index e52da193b6..fbb92b8168 100644 --- a/spring-batch-core/src/main/resources/org/springframework/batch/core/migration/2.2/migration-sqlf.sql +++ b/spring-batch-core/src/main/resources/org/springframework/batch/core/migration/2.2/migration-sqlf.sql @@ -1,15 +1,15 @@ -- create the requisite table -CREATE TABLE BATCH_JOB_EXECUTION_PARAMS ( - JOB_EXECUTION_ID BIGINT NOT NULL , - TYPE_CD VARCHAR(6) NOT NULL , - KEY_NAME VARCHAR(100) NOT NULL , - STRING_VAL VARCHAR(250) , - DATE_VAL TIMESTAMP DEFAULT NULL , - LONG_VAL BIGINT , - DOUBLE_VAL DOUBLE PRECISION , - IDENTIFYING CHAR(1) NOT NULL , +CREATE TABLE BATCH_JOB_EXECUTION_PARAMS ( + JOB_EXECUTION_ID BIGINT NOT NULL, + TYPE_CD VARCHAR(6) NOT NULL, + KEY_NAME VARCHAR(100) NOT NULL, + STRING_VAL VARCHAR(250), + DATE_VAL TIMESTAMP DEFAULT NULL, + LONG_VAL BIGINT, + DOUBLE_VAL DOUBLE PRECISION, + IDENTIFYING CHAR(1) NOT NULL, constraint JOB_EXEC_PARAMS_FK foreign key (JOB_EXECUTION_ID) references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID) ) ; diff --git a/spring-batch-core/src/main/resources/org/springframework/batch/core/migration/2.2/migration-sqlserver.sql b/spring-batch-core/src/main/resources/org/springframework/batch/core/migration/2.2/migration-sqlserver.sql index 4c24789ee8..e0cb8e524e 100644 --- a/spring-batch-core/src/main/resources/org/springframework/batch/core/migration/2.2/migration-sqlserver.sql +++ b/spring-batch-core/src/main/resources/org/springframework/batch/core/migration/2.2/migration-sqlserver.sql @@ -1,15 +1,15 @@ -- create the requisite table -CREATE TABLE BATCH_JOB_EXECUTION_PARAMS ( - JOB_EXECUTION_ID BIGINT NOT NULL , - TYPE_CD VARCHAR(6) NOT NULL , - KEY_NAME VARCHAR(100) NOT NULL , - STRING_VAL VARCHAR(250) , - DATE_VAL DATETIME DEFAULT NULL , - LONG_VAL BIGINT , - DOUBLE_VAL DOUBLE PRECISION , - IDENTIFYING CHAR(1) NOT NULL , +CREATE TABLE BATCH_JOB_EXECUTION_PARAMS ( + JOB_EXECUTION_ID BIGINT NOT NULL, + TYPE_CD VARCHAR(6) NOT NULL, + KEY_NAME VARCHAR(100) NOT NULL, + STRING_VAL VARCHAR(250), + DATE_VAL DATETIME DEFAULT NULL, + LONG_VAL BIGINT, + DOUBLE_VAL DOUBLE PRECISION, + IDENTIFYING CHAR(1) NOT NULL, constraint JOB_EXEC_PARAMS_FK foreign key (JOB_EXECUTION_ID) references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID) ) ; diff --git a/spring-batch-core/src/main/resources/org/springframework/batch/core/migration/2.2/migration-sybase.sql b/spring-batch-core/src/main/resources/org/springframework/batch/core/migration/2.2/migration-sybase.sql index d8def25a63..cf0e5dafca 100644 --- a/spring-batch-core/src/main/resources/org/springframework/batch/core/migration/2.2/migration-sybase.sql +++ b/spring-batch-core/src/main/resources/org/springframework/batch/core/migration/2.2/migration-sybase.sql @@ -1,15 +1,15 @@ -- create the requisite table -CREATE TABLE BATCH_JOB_EXECUTION_PARAMS ( - JOB_EXECUTION_ID BIGINT NOT NULL , - TYPE_CD VARCHAR(6) NOT NULL , - KEY_NAME VARCHAR(100) NOT NULL , +CREATE TABLE BATCH_JOB_EXECUTION_PARAMS ( + JOB_EXECUTION_ID BIGINT NOT NULL, + TYPE_CD VARCHAR(6) NOT NULL, + KEY_NAME VARCHAR(100) NOT NULL, STRING_VAL VARCHAR(250) NULL, DATE_VAL DATETIME DEFAULT NULL NULL, LONG_VAL BIGINT NULL, DOUBLE_VAL DOUBLE PRECISION NULL, - IDENTIFYING CHAR(1) NOT NULL , + IDENTIFYING CHAR(1) NOT NULL, constraint JOB_EXEC_PARAMS_FK foreign key (JOB_EXECUTION_ID) references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID) ) ; diff --git a/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-db2.sql b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-db2.sql index c883c354c9..ff3ca6bcba 100644 --- a/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-db2.sql +++ b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-db2.sql @@ -1,78 +1,78 @@ -- Autogenerated: do not edit this file -CREATE TABLE BATCH_JOB_INSTANCE ( - JOB_INSTANCE_ID BIGINT NOT NULL PRIMARY KEY , - VERSION BIGINT , +CREATE TABLE BATCH_JOB_INSTANCE ( + JOB_INSTANCE_ID BIGINT NOT NULL PRIMARY KEY, + VERSION BIGINT, JOB_NAME VARCHAR(100) NOT NULL, JOB_KEY VARCHAR(32) NOT NULL, constraint JOB_INST_UN unique (JOB_NAME, JOB_KEY) ) ; -CREATE TABLE BATCH_JOB_EXECUTION ( - JOB_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY , - VERSION BIGINT , +CREATE TABLE BATCH_JOB_EXECUTION ( + JOB_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY, + VERSION BIGINT, JOB_INSTANCE_ID BIGINT NOT NULL, CREATE_TIME TIMESTAMP(9) NOT NULL, - START_TIME TIMESTAMP(9) DEFAULT NULL , - END_TIME TIMESTAMP(9) DEFAULT NULL , - STATUS VARCHAR(10) , - EXIT_CODE VARCHAR(2500) , - EXIT_MESSAGE VARCHAR(2500) , + START_TIME TIMESTAMP(9) DEFAULT NULL, + END_TIME TIMESTAMP(9) DEFAULT NULL, + STATUS VARCHAR(10), + EXIT_CODE VARCHAR(2500), + EXIT_MESSAGE VARCHAR(2500), LAST_UPDATED TIMESTAMP(9), constraint JOB_INST_EXEC_FK foreign key (JOB_INSTANCE_ID) references BATCH_JOB_INSTANCE(JOB_INSTANCE_ID) ) ; -CREATE TABLE BATCH_JOB_EXECUTION_PARAMS ( - JOB_EXECUTION_ID BIGINT NOT NULL , - PARAMETER_NAME VARCHAR(100) NOT NULL , - PARAMETER_TYPE VARCHAR(100) NOT NULL , - PARAMETER_VALUE VARCHAR(2500) , - IDENTIFYING CHAR(1) NOT NULL , +CREATE TABLE BATCH_JOB_EXECUTION_PARAMS ( + JOB_EXECUTION_ID BIGINT NOT NULL, + PARAMETER_NAME VARCHAR(100) NOT NULL, + PARAMETER_TYPE VARCHAR(100) NOT NULL, + PARAMETER_VALUE VARCHAR(2500), + IDENTIFYING CHAR(1) NOT NULL, constraint JOB_EXEC_PARAMS_FK foreign key (JOB_EXECUTION_ID) references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID) ) ; -CREATE TABLE BATCH_STEP_EXECUTION ( - STEP_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY , +CREATE TABLE BATCH_STEP_EXECUTION ( + STEP_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY, VERSION BIGINT NOT NULL, STEP_NAME VARCHAR(100) NOT NULL, JOB_EXECUTION_ID BIGINT NOT NULL, CREATE_TIME TIMESTAMP(9) NOT NULL, - START_TIME TIMESTAMP(9) DEFAULT NULL , - END_TIME TIMESTAMP(9) DEFAULT NULL , - STATUS VARCHAR(10) , - COMMIT_COUNT BIGINT , - READ_COUNT BIGINT , - FILTER_COUNT BIGINT , - WRITE_COUNT BIGINT , - READ_SKIP_COUNT BIGINT , - WRITE_SKIP_COUNT BIGINT , - PROCESS_SKIP_COUNT BIGINT , - ROLLBACK_COUNT BIGINT , - EXIT_CODE VARCHAR(2500) , - EXIT_MESSAGE VARCHAR(2500) , + START_TIME TIMESTAMP(9) DEFAULT NULL, + END_TIME TIMESTAMP(9) DEFAULT NULL, + STATUS VARCHAR(10), + COMMIT_COUNT BIGINT, + READ_COUNT BIGINT, + FILTER_COUNT BIGINT, + WRITE_COUNT BIGINT, + READ_SKIP_COUNT BIGINT, + WRITE_SKIP_COUNT BIGINT, + PROCESS_SKIP_COUNT BIGINT, + ROLLBACK_COUNT BIGINT, + EXIT_CODE VARCHAR(2500), + EXIT_MESSAGE VARCHAR(2500), LAST_UPDATED TIMESTAMP(9), constraint JOB_EXEC_STEP_FK foreign key (JOB_EXECUTION_ID) references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID) ) ; -CREATE TABLE BATCH_STEP_EXECUTION_CONTEXT ( +CREATE TABLE BATCH_STEP_EXECUTION_CONTEXT ( STEP_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY, SHORT_CONTEXT VARCHAR(2500) NOT NULL, - SERIALIZED_CONTEXT CLOB , + SERIALIZED_CONTEXT CLOB, constraint STEP_EXEC_CTX_FK foreign key (STEP_EXECUTION_ID) references BATCH_STEP_EXECUTION(STEP_EXECUTION_ID) ) ; -CREATE TABLE BATCH_JOB_EXECUTION_CONTEXT ( +CREATE TABLE BATCH_JOB_EXECUTION_CONTEXT ( JOB_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY, SHORT_CONTEXT VARCHAR(2500) NOT NULL, - SERIALIZED_CONTEXT CLOB , + SERIALIZED_CONTEXT CLOB, constraint JOB_EXEC_CTX_FK foreign key (JOB_EXECUTION_ID) references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID) ) ; CREATE SEQUENCE BATCH_STEP_EXECUTION_SEQ AS BIGINT MAXVALUE 9223372036854775807 NO CYCLE; CREATE SEQUENCE BATCH_JOB_EXECUTION_SEQ AS BIGINT MAXVALUE 9223372036854775807 NO CYCLE; -CREATE SEQUENCE BATCH_JOB_SEQ AS BIGINT MAXVALUE 9223372036854775807 NO CYCLE; +CREATE SEQUENCE BATCH_JOB_INSTANCE_SEQ AS BIGINT MAXVALUE 9223372036854775807 NO CYCLE; diff --git a/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-derby.sql b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-derby.sql index c9510a5bc3..19257051df 100644 --- a/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-derby.sql +++ b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-derby.sql @@ -1,78 +1,78 @@ -- Autogenerated: do not edit this file -CREATE TABLE BATCH_JOB_INSTANCE ( +CREATE TABLE BATCH_JOB_INSTANCE ( JOB_INSTANCE_ID BIGINT NOT NULL PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY, - VERSION BIGINT , + VERSION BIGINT, JOB_NAME VARCHAR(100) NOT NULL, JOB_KEY VARCHAR(32) NOT NULL, constraint JOB_INST_UN unique (JOB_NAME, JOB_KEY) ) ; -CREATE TABLE BATCH_JOB_EXECUTION ( +CREATE TABLE BATCH_JOB_EXECUTION ( JOB_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY, - VERSION BIGINT , + VERSION BIGINT, JOB_INSTANCE_ID BIGINT NOT NULL, CREATE_TIME TIMESTAMP NOT NULL, - START_TIME TIMESTAMP DEFAULT NULL , - END_TIME TIMESTAMP DEFAULT NULL , - STATUS VARCHAR(10) , - EXIT_CODE VARCHAR(2500) , - EXIT_MESSAGE VARCHAR(2500) , + START_TIME TIMESTAMP DEFAULT NULL, + END_TIME TIMESTAMP DEFAULT NULL, + STATUS VARCHAR(10), + EXIT_CODE VARCHAR(2500), + EXIT_MESSAGE VARCHAR(2500), LAST_UPDATED TIMESTAMP, constraint JOB_INST_EXEC_FK foreign key (JOB_INSTANCE_ID) references BATCH_JOB_INSTANCE(JOB_INSTANCE_ID) ) ; -CREATE TABLE BATCH_JOB_EXECUTION_PARAMS ( - JOB_EXECUTION_ID BIGINT NOT NULL , - PARAMETER_NAME VARCHAR(100) NOT NULL , - PARAMETER_TYPE VARCHAR(100) NOT NULL , - PARAMETER_VALUE VARCHAR(2500) , - IDENTIFYING CHAR(1) NOT NULL , +CREATE TABLE BATCH_JOB_EXECUTION_PARAMS ( + JOB_EXECUTION_ID BIGINT NOT NULL, + PARAMETER_NAME VARCHAR(100) NOT NULL, + PARAMETER_TYPE VARCHAR(100) NOT NULL, + PARAMETER_VALUE VARCHAR(2500), + IDENTIFYING CHAR(1) NOT NULL, constraint JOB_EXEC_PARAMS_FK foreign key (JOB_EXECUTION_ID) references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID) ) ; -CREATE TABLE BATCH_STEP_EXECUTION ( +CREATE TABLE BATCH_STEP_EXECUTION ( STEP_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY, VERSION BIGINT NOT NULL, STEP_NAME VARCHAR(100) NOT NULL, JOB_EXECUTION_ID BIGINT NOT NULL, CREATE_TIME TIMESTAMP NOT NULL, - START_TIME TIMESTAMP DEFAULT NULL , - END_TIME TIMESTAMP DEFAULT NULL , - STATUS VARCHAR(10) , - COMMIT_COUNT BIGINT , - READ_COUNT BIGINT , - FILTER_COUNT BIGINT , - WRITE_COUNT BIGINT , - READ_SKIP_COUNT BIGINT , - WRITE_SKIP_COUNT BIGINT , - PROCESS_SKIP_COUNT BIGINT , - ROLLBACK_COUNT BIGINT , - EXIT_CODE VARCHAR(2500) , - EXIT_MESSAGE VARCHAR(2500) , + START_TIME TIMESTAMP DEFAULT NULL, + END_TIME TIMESTAMP DEFAULT NULL, + STATUS VARCHAR(10), + COMMIT_COUNT BIGINT, + READ_COUNT BIGINT, + FILTER_COUNT BIGINT, + WRITE_COUNT BIGINT, + READ_SKIP_COUNT BIGINT, + WRITE_SKIP_COUNT BIGINT, + PROCESS_SKIP_COUNT BIGINT, + ROLLBACK_COUNT BIGINT, + EXIT_CODE VARCHAR(2500), + EXIT_MESSAGE VARCHAR(2500), LAST_UPDATED TIMESTAMP, constraint JOB_EXEC_STEP_FK foreign key (JOB_EXECUTION_ID) references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID) ) ; -CREATE TABLE BATCH_STEP_EXECUTION_CONTEXT ( +CREATE TABLE BATCH_STEP_EXECUTION_CONTEXT ( STEP_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY, SHORT_CONTEXT VARCHAR(2500) NOT NULL, - SERIALIZED_CONTEXT CLOB , + SERIALIZED_CONTEXT CLOB, constraint STEP_EXEC_CTX_FK foreign key (STEP_EXECUTION_ID) references BATCH_STEP_EXECUTION(STEP_EXECUTION_ID) ) ; -CREATE TABLE BATCH_JOB_EXECUTION_CONTEXT ( +CREATE TABLE BATCH_JOB_EXECUTION_CONTEXT ( JOB_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY, SHORT_CONTEXT VARCHAR(2500) NOT NULL, - SERIALIZED_CONTEXT CLOB , + SERIALIZED_CONTEXT CLOB, constraint JOB_EXEC_CTX_FK foreign key (JOB_EXECUTION_ID) references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID) ) ; CREATE TABLE BATCH_STEP_EXECUTION_SEQ (ID BIGINT PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY, DUMMY VARCHAR(1)); CREATE TABLE BATCH_JOB_EXECUTION_SEQ (ID BIGINT PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY, DUMMY VARCHAR(1)); -CREATE TABLE BATCH_JOB_SEQ (ID BIGINT PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY, DUMMY VARCHAR(1)); +CREATE TABLE BATCH_JOB_INSTANCE_SEQ (ID BIGINT PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY, DUMMY VARCHAR(1)); diff --git a/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-drop-db2.sql b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-drop-db2.sql index 247ae20760..45af0ff8ce 100644 --- a/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-drop-db2.sql +++ b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-drop-db2.sql @@ -9,4 +9,4 @@ DROP TABLE BATCH_JOB_INSTANCE; DROP SEQUENCE BATCH_STEP_EXECUTION_SEQ; DROP SEQUENCE BATCH_JOB_EXECUTION_SEQ; -DROP SEQUENCE BATCH_JOB_SEQ; +DROP SEQUENCE BATCH_JOB_INSTANCE_SEQ; diff --git a/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-drop-derby.sql b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-drop-derby.sql index d217ec569b..7239c559e1 100644 --- a/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-drop-derby.sql +++ b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-drop-derby.sql @@ -9,4 +9,4 @@ DROP TABLE BATCH_JOB_INSTANCE; DROP TABLE BATCH_STEP_EXECUTION_SEQ; DROP TABLE BATCH_JOB_EXECUTION_SEQ; -DROP TABLE BATCH_JOB_SEQ; +DROP TABLE BATCH_JOB_INSTANCE_SEQ; diff --git a/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-drop-h2.sql b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-drop-h2.sql index c057fa608f..2e773da4f4 100644 --- a/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-drop-h2.sql +++ b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-drop-h2.sql @@ -9,4 +9,4 @@ DROP TABLE BATCH_JOB_INSTANCE IF EXISTS; DROP SEQUENCE BATCH_STEP_EXECUTION_SEQ IF EXISTS; DROP SEQUENCE BATCH_JOB_EXECUTION_SEQ IF EXISTS; -DROP SEQUENCE BATCH_JOB_SEQ IF EXISTS; +DROP SEQUENCE BATCH_JOB_INSTANCE_SEQ IF EXISTS; diff --git a/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-drop-hana.sql b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-drop-hana.sql index 944db3ec39..17f3127108 100644 --- a/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-drop-hana.sql +++ b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-drop-hana.sql @@ -8,4 +8,4 @@ DROP TABLE BATCH_JOB_INSTANCE ; DROP SEQUENCE BATCH_STEP_EXECUTION_SEQ ; DROP SEQUENCE BATCH_JOB_EXECUTION_SEQ ; -DROP SEQUENCE BATCH_JOB_SEQ ; +DROP SEQUENCE BATCH_JOB_INSTANCE_SEQ ; diff --git a/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-drop-hsqldb.sql b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-drop-hsqldb.sql index 53ec12ddf3..d87e6a5602 100644 --- a/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-drop-hsqldb.sql +++ b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-drop-hsqldb.sql @@ -9,4 +9,4 @@ DROP TABLE BATCH_JOB_INSTANCE IF EXISTS; DROP TABLE BATCH_STEP_EXECUTION_SEQ IF EXISTS; DROP TABLE BATCH_JOB_EXECUTION_SEQ IF EXISTS; -DROP TABLE BATCH_JOB_SEQ IF EXISTS; +DROP TABLE BATCH_JOB_INSTANCE_SEQ IF EXISTS; diff --git a/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-drop-mariadb.sql b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-drop-mariadb.sql index f78e4b607a..320ba02855 100644 --- a/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-drop-mariadb.sql +++ b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-drop-mariadb.sql @@ -7,4 +7,4 @@ DROP TABLE IF EXISTS BATCH_JOB_INSTANCE; DROP SEQUENCE IF EXISTS BATCH_STEP_EXECUTION_SEQ; DROP SEQUENCE IF EXISTS BATCH_JOB_EXECUTION_SEQ; -DROP SEQUENCE IF EXISTS BATCH_JOB_SEQ; +DROP SEQUENCE IF EXISTS BATCH_JOB_INSTANCE_SEQ; 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-drop-mysql.sql b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-drop-mysql.sql index dc51cdacdf..0b837bacce 100644 --- a/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-drop-mysql.sql +++ b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-drop-mysql.sql @@ -9,4 +9,4 @@ DROP TABLE IF EXISTS BATCH_JOB_INSTANCE; DROP TABLE IF EXISTS BATCH_STEP_EXECUTION_SEQ; DROP TABLE IF EXISTS BATCH_JOB_EXECUTION_SEQ; -DROP TABLE IF EXISTS BATCH_JOB_SEQ; +DROP TABLE IF EXISTS BATCH_JOB_INSTANCE_SEQ; diff --git a/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-drop-oracle.sql b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-drop-oracle.sql index 247ae20760..45af0ff8ce 100644 --- a/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-drop-oracle.sql +++ b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-drop-oracle.sql @@ -9,4 +9,4 @@ DROP TABLE BATCH_JOB_INSTANCE; DROP SEQUENCE BATCH_STEP_EXECUTION_SEQ; DROP SEQUENCE BATCH_JOB_EXECUTION_SEQ; -DROP SEQUENCE BATCH_JOB_SEQ; +DROP SEQUENCE BATCH_JOB_INSTANCE_SEQ; diff --git a/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-drop-postgresql.sql b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-drop-postgresql.sql index 852211838b..ea0d571f15 100644 --- a/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-drop-postgresql.sql +++ b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-drop-postgresql.sql @@ -8,4 +8,4 @@ DROP TABLE IF EXISTS BATCH_JOB_INSTANCE; DROP SEQUENCE IF EXISTS BATCH_STEP_EXECUTION_SEQ; DROP SEQUENCE IF EXISTS BATCH_JOB_EXECUTION_SEQ; -DROP SEQUENCE IF EXISTS BATCH_JOB_SEQ; +DROP SEQUENCE IF EXISTS BATCH_JOB_INSTANCE_SEQ; diff --git a/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-drop-sqlite.sql b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-drop-sqlite.sql index b40b16ab5a..50a6ffae30 100644 --- a/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-drop-sqlite.sql +++ b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-drop-sqlite.sql @@ -9,4 +9,4 @@ DROP TABLE IF EXISTS BATCH_JOB_INSTANCE; DROP TABLE IF EXISTS BATCH_STEP_EXECUTION_SEQ; DROP TABLE IF EXISTS BATCH_JOB_EXECUTION_SEQ; -DROP TABLE IF EXISTS BATCH_JOB_SEQ; +DROP TABLE IF EXISTS BATCH_JOB_INSTANCE_SEQ; diff --git a/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-drop-sqlserver.sql b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-drop-sqlserver.sql index 247ae20760..45af0ff8ce 100644 --- a/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-drop-sqlserver.sql +++ b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-drop-sqlserver.sql @@ -9,4 +9,4 @@ DROP TABLE BATCH_JOB_INSTANCE; DROP SEQUENCE BATCH_STEP_EXECUTION_SEQ; DROP SEQUENCE BATCH_JOB_EXECUTION_SEQ; -DROP SEQUENCE BATCH_JOB_SEQ; +DROP SEQUENCE BATCH_JOB_INSTANCE_SEQ; diff --git a/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-drop-sybase.sql b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-drop-sybase.sql index d217ec569b..7239c559e1 100644 --- a/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-drop-sybase.sql +++ b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-drop-sybase.sql @@ -9,4 +9,4 @@ DROP TABLE BATCH_JOB_INSTANCE; DROP TABLE BATCH_STEP_EXECUTION_SEQ; DROP TABLE BATCH_JOB_EXECUTION_SEQ; -DROP TABLE BATCH_JOB_SEQ; +DROP TABLE BATCH_JOB_INSTANCE_SEQ; diff --git a/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-h2.sql b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-h2.sql index dc51c373ca..016818782c 100644 --- a/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-h2.sql +++ b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-h2.sql @@ -1,78 +1,78 @@ -- Autogenerated: do not edit this file -CREATE TABLE BATCH_JOB_INSTANCE ( - JOB_INSTANCE_ID BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY , - VERSION BIGINT , +CREATE TABLE BATCH_JOB_INSTANCE ( + JOB_INSTANCE_ID BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + VERSION BIGINT, JOB_NAME VARCHAR(100) NOT NULL, JOB_KEY VARCHAR(32) NOT NULL, constraint JOB_INST_UN unique (JOB_NAME, JOB_KEY) ) ; -CREATE TABLE BATCH_JOB_EXECUTION ( - JOB_EXECUTION_ID BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY , - VERSION BIGINT , +CREATE TABLE BATCH_JOB_EXECUTION ( + JOB_EXECUTION_ID BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + VERSION BIGINT, JOB_INSTANCE_ID BIGINT NOT NULL, CREATE_TIME TIMESTAMP(9) NOT NULL, - START_TIME TIMESTAMP(9) DEFAULT NULL , - END_TIME TIMESTAMP(9) DEFAULT NULL , - STATUS VARCHAR(10) , - EXIT_CODE VARCHAR(2500) , - EXIT_MESSAGE VARCHAR(2500) , + START_TIME TIMESTAMP(9) DEFAULT NULL, + END_TIME TIMESTAMP(9) DEFAULT NULL, + STATUS VARCHAR(10), + EXIT_CODE VARCHAR(2500), + EXIT_MESSAGE VARCHAR(2500), LAST_UPDATED TIMESTAMP(9), constraint JOB_INST_EXEC_FK foreign key (JOB_INSTANCE_ID) references BATCH_JOB_INSTANCE(JOB_INSTANCE_ID) ) ; -CREATE TABLE BATCH_JOB_EXECUTION_PARAMS ( - JOB_EXECUTION_ID BIGINT NOT NULL , - PARAMETER_NAME VARCHAR(100) NOT NULL , - PARAMETER_TYPE VARCHAR(100) NOT NULL , - PARAMETER_VALUE VARCHAR(2500) , - IDENTIFYING CHAR(1) NOT NULL , +CREATE TABLE BATCH_JOB_EXECUTION_PARAMS ( + JOB_EXECUTION_ID BIGINT NOT NULL, + PARAMETER_NAME VARCHAR(100) NOT NULL, + PARAMETER_TYPE VARCHAR(100) NOT NULL, + PARAMETER_VALUE VARCHAR(2500), + IDENTIFYING CHAR(1) NOT NULL, constraint JOB_EXEC_PARAMS_FK foreign key (JOB_EXECUTION_ID) references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID) ) ; -CREATE TABLE BATCH_STEP_EXECUTION ( - STEP_EXECUTION_ID BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY , +CREATE TABLE BATCH_STEP_EXECUTION ( + STEP_EXECUTION_ID BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, VERSION BIGINT NOT NULL, STEP_NAME VARCHAR(100) NOT NULL, JOB_EXECUTION_ID BIGINT NOT NULL, CREATE_TIME TIMESTAMP(9) NOT NULL, - START_TIME TIMESTAMP(9) DEFAULT NULL , - END_TIME TIMESTAMP(9) DEFAULT NULL , - STATUS VARCHAR(10) , - COMMIT_COUNT BIGINT , - READ_COUNT BIGINT , - FILTER_COUNT BIGINT , - WRITE_COUNT BIGINT , - READ_SKIP_COUNT BIGINT , - WRITE_SKIP_COUNT BIGINT , - PROCESS_SKIP_COUNT BIGINT , - ROLLBACK_COUNT BIGINT , - EXIT_CODE VARCHAR(2500) , - EXIT_MESSAGE VARCHAR(2500) , + START_TIME TIMESTAMP(9) DEFAULT NULL, + END_TIME TIMESTAMP(9) DEFAULT NULL, + STATUS VARCHAR(10), + COMMIT_COUNT BIGINT, + READ_COUNT BIGINT, + FILTER_COUNT BIGINT, + WRITE_COUNT BIGINT, + READ_SKIP_COUNT BIGINT, + WRITE_SKIP_COUNT BIGINT, + PROCESS_SKIP_COUNT BIGINT, + ROLLBACK_COUNT BIGINT, + EXIT_CODE VARCHAR(2500), + EXIT_MESSAGE VARCHAR(2500), LAST_UPDATED TIMESTAMP(9), constraint JOB_EXEC_STEP_FK foreign key (JOB_EXECUTION_ID) references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID) ) ; -CREATE TABLE BATCH_STEP_EXECUTION_CONTEXT ( +CREATE TABLE BATCH_STEP_EXECUTION_CONTEXT ( STEP_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY, SHORT_CONTEXT VARCHAR(2500) NOT NULL, - SERIALIZED_CONTEXT LONGVARCHAR , + SERIALIZED_CONTEXT LONGVARCHAR, constraint STEP_EXEC_CTX_FK foreign key (STEP_EXECUTION_ID) references BATCH_STEP_EXECUTION(STEP_EXECUTION_ID) ) ; -CREATE TABLE BATCH_JOB_EXECUTION_CONTEXT ( +CREATE TABLE BATCH_JOB_EXECUTION_CONTEXT ( JOB_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY, SHORT_CONTEXT VARCHAR(2500) NOT NULL, - SERIALIZED_CONTEXT LONGVARCHAR , + SERIALIZED_CONTEXT LONGVARCHAR, constraint JOB_EXEC_CTX_FK foreign key (JOB_EXECUTION_ID) references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID) ) ; CREATE SEQUENCE BATCH_STEP_EXECUTION_SEQ; CREATE SEQUENCE BATCH_JOB_EXECUTION_SEQ; -CREATE SEQUENCE BATCH_JOB_SEQ; +CREATE SEQUENCE BATCH_JOB_INSTANCE_SEQ; diff --git a/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-hana.sql b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-hana.sql index 8dc2dc843c..851cf12577 100644 --- a/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-hana.sql +++ b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-hana.sql @@ -1,78 +1,78 @@ -- Autogenerated: do not edit this file -CREATE TABLE BATCH_JOB_INSTANCE ( +CREATE TABLE BATCH_JOB_INSTANCE ( JOB_INSTANCE_ID BIGINT NOT NULL PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY, - VERSION BIGINT , + VERSION BIGINT, JOB_NAME VARCHAR(100) NOT NULL, JOB_KEY VARCHAR(32) NOT NULL, constraint JOB_INST_UN unique (JOB_NAME, JOB_KEY) ) ; -CREATE TABLE BATCH_JOB_EXECUTION ( +CREATE TABLE BATCH_JOB_EXECUTION ( JOB_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY, - VERSION BIGINT , + VERSION BIGINT, JOB_INSTANCE_ID BIGINT NOT NULL, CREATE_TIME TIMESTAMP NOT NULL, - START_TIME TIMESTAMP DEFAULT NULL , - END_TIME TIMESTAMP DEFAULT NULL , - STATUS VARCHAR(10) , - EXIT_CODE VARCHAR(2500) , - EXIT_MESSAGE VARCHAR(2500) , + START_TIME TIMESTAMP DEFAULT NULL, + END_TIME TIMESTAMP DEFAULT NULL, + STATUS VARCHAR(10), + EXIT_CODE VARCHAR(2500), + EXIT_MESSAGE VARCHAR(2500), LAST_UPDATED TIMESTAMP, constraint JOB_INST_EXEC_FK foreign key (JOB_INSTANCE_ID) references BATCH_JOB_INSTANCE(JOB_INSTANCE_ID) ) ; -CREATE TABLE BATCH_JOB_EXECUTION_PARAMS ( - JOB_EXECUTION_ID BIGINT NOT NULL , - PARAMETER_NAME VARCHAR(100) NOT NULL , - PARAMETER_TYPE VARCHAR(100) NOT NULL , - PARAMETER_VALUE VARCHAR(2500) , - IDENTIFYING VARCHAR(1) NOT NULL , +CREATE TABLE BATCH_JOB_EXECUTION_PARAMS ( + JOB_EXECUTION_ID BIGINT NOT NULL, + PARAMETER_NAME VARCHAR(100) NOT NULL, + PARAMETER_TYPE VARCHAR(100) NOT NULL, + PARAMETER_VALUE VARCHAR(2500), + IDENTIFYING VARCHAR(1) NOT NULL, constraint JOB_EXEC_PARAMS_FK foreign key (JOB_EXECUTION_ID) references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID) ) ; -CREATE TABLE BATCH_STEP_EXECUTION ( +CREATE TABLE BATCH_STEP_EXECUTION ( STEP_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY, VERSION BIGINT NOT NULL, STEP_NAME VARCHAR(100) NOT NULL, JOB_EXECUTION_ID BIGINT NOT NULL, - CREATE_TIME TIMESTAMP NOT NULL , - START_TIME TIMESTAMP DEFAULT NULL , - END_TIME TIMESTAMP DEFAULT NULL , - STATUS VARCHAR(10) , - COMMIT_COUNT BIGINT , - READ_COUNT BIGINT , - FILTER_COUNT BIGINT , - WRITE_COUNT BIGINT , - READ_SKIP_COUNT BIGINT , - WRITE_SKIP_COUNT BIGINT , - PROCESS_SKIP_COUNT BIGINT , - ROLLBACK_COUNT BIGINT , - EXIT_CODE VARCHAR(2500) , - EXIT_MESSAGE VARCHAR(2500) , + CREATE_TIME TIMESTAMP NOT NULL, + START_TIME TIMESTAMP DEFAULT NULL, + END_TIME TIMESTAMP DEFAULT NULL, + STATUS VARCHAR(10), + COMMIT_COUNT BIGINT, + READ_COUNT BIGINT, + FILTER_COUNT BIGINT, + WRITE_COUNT BIGINT, + READ_SKIP_COUNT BIGINT, + WRITE_SKIP_COUNT BIGINT, + PROCESS_SKIP_COUNT BIGINT, + ROLLBACK_COUNT BIGINT, + EXIT_CODE VARCHAR(2500), + EXIT_MESSAGE VARCHAR(2500), LAST_UPDATED TIMESTAMP, constraint JOB_EXEC_STEP_FK foreign key (JOB_EXECUTION_ID) references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID) ) ; -CREATE TABLE BATCH_STEP_EXECUTION_CONTEXT ( +CREATE TABLE BATCH_STEP_EXECUTION_CONTEXT ( STEP_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY, SHORT_CONTEXT VARCHAR(2500) NOT NULL, - SERIALIZED_CONTEXT CLOB , + SERIALIZED_CONTEXT CLOB, constraint STEP_EXEC_CTX_FK foreign key (STEP_EXECUTION_ID) references BATCH_STEP_EXECUTION(STEP_EXECUTION_ID) ) ; -CREATE TABLE BATCH_JOB_EXECUTION_CONTEXT ( +CREATE TABLE BATCH_JOB_EXECUTION_CONTEXT ( JOB_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY, SHORT_CONTEXT VARCHAR(2500) NOT NULL, - SERIALIZED_CONTEXT CLOB , + SERIALIZED_CONTEXT CLOB, constraint JOB_EXEC_CTX_FK foreign key (JOB_EXECUTION_ID) references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID) ) ; CREATE SEQUENCE BATCH_STEP_EXECUTION_SEQ START WITH 0 MINVALUE 0 NO CYCLE; CREATE SEQUENCE BATCH_JOB_EXECUTION_SEQ START WITH 0 MINVALUE 0 NO CYCLE; -CREATE SEQUENCE BATCH_JOB_SEQ START WITH 0 MINVALUE 0 NO CYCLE; +CREATE SEQUENCE BATCH_JOB_INSTANCE_SEQ START WITH 0 MINVALUE 0 NO CYCLE; diff --git a/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-hsqldb.sql b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-hsqldb.sql index 758699f152..be5d1908e4 100644 --- a/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-hsqldb.sql +++ b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-hsqldb.sql @@ -1,74 +1,74 @@ -- Autogenerated: do not edit this file -CREATE TABLE BATCH_JOB_INSTANCE ( - JOB_INSTANCE_ID BIGINT IDENTITY NOT NULL PRIMARY KEY , - VERSION BIGINT , +CREATE TABLE BATCH_JOB_INSTANCE ( + JOB_INSTANCE_ID BIGINT IDENTITY NOT NULL PRIMARY KEY, + VERSION BIGINT, JOB_NAME VARCHAR(100) NOT NULL, JOB_KEY VARCHAR(32) NOT NULL, constraint JOB_INST_UN unique (JOB_NAME, JOB_KEY) ) ; -CREATE TABLE BATCH_JOB_EXECUTION ( - JOB_EXECUTION_ID BIGINT IDENTITY NOT NULL PRIMARY KEY , - VERSION BIGINT , +CREATE TABLE BATCH_JOB_EXECUTION ( + JOB_EXECUTION_ID BIGINT IDENTITY NOT NULL PRIMARY KEY, + VERSION BIGINT, JOB_INSTANCE_ID BIGINT NOT NULL, CREATE_TIME TIMESTAMP(9) NOT NULL, - START_TIME TIMESTAMP(9) DEFAULT NULL , - END_TIME TIMESTAMP(9) DEFAULT NULL , - STATUS VARCHAR(10) , - EXIT_CODE VARCHAR(2500) , - EXIT_MESSAGE VARCHAR(2500) , + START_TIME TIMESTAMP(9) DEFAULT NULL, + END_TIME TIMESTAMP(9) DEFAULT NULL, + STATUS VARCHAR(10), + EXIT_CODE VARCHAR(2500), + EXIT_MESSAGE VARCHAR(2500), LAST_UPDATED TIMESTAMP(9), constraint JOB_INST_EXEC_FK foreign key (JOB_INSTANCE_ID) references BATCH_JOB_INSTANCE(JOB_INSTANCE_ID) ) ; -CREATE TABLE BATCH_JOB_EXECUTION_PARAMS ( - JOB_EXECUTION_ID BIGINT NOT NULL , - PARAMETER_NAME VARCHAR(100) NOT NULL , - PARAMETER_TYPE VARCHAR(100) NOT NULL , - PARAMETER_VALUE VARCHAR(2500) , - IDENTIFYING CHAR(1) NOT NULL , +CREATE TABLE BATCH_JOB_EXECUTION_PARAMS ( + JOB_EXECUTION_ID BIGINT NOT NULL, + PARAMETER_NAME VARCHAR(100) NOT NULL, + PARAMETER_TYPE VARCHAR(100) NOT NULL, + PARAMETER_VALUE VARCHAR(2500), + IDENTIFYING CHAR(1) NOT NULL, constraint JOB_EXEC_PARAMS_FK foreign key (JOB_EXECUTION_ID) references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID) ) ; -CREATE TABLE BATCH_STEP_EXECUTION ( - STEP_EXECUTION_ID BIGINT IDENTITY NOT NULL PRIMARY KEY , +CREATE TABLE BATCH_STEP_EXECUTION ( + STEP_EXECUTION_ID BIGINT IDENTITY NOT NULL PRIMARY KEY, VERSION BIGINT NOT NULL, STEP_NAME VARCHAR(100) NOT NULL, JOB_EXECUTION_ID BIGINT NOT NULL, - CREATE_TIME TIMESTAMP(9) NOT NULL , - START_TIME TIMESTAMP(9) DEFAULT NULL , - END_TIME TIMESTAMP(9) DEFAULT NULL , - STATUS VARCHAR(10) , - COMMIT_COUNT BIGINT , - READ_COUNT BIGINT , - FILTER_COUNT BIGINT , - WRITE_COUNT BIGINT , - READ_SKIP_COUNT BIGINT , - WRITE_SKIP_COUNT BIGINT , - PROCESS_SKIP_COUNT BIGINT , - ROLLBACK_COUNT BIGINT , - EXIT_CODE VARCHAR(2500) , - EXIT_MESSAGE VARCHAR(2500) , + CREATE_TIME TIMESTAMP(9) NOT NULL, + START_TIME TIMESTAMP(9) DEFAULT NULL, + END_TIME TIMESTAMP(9) DEFAULT NULL, + STATUS VARCHAR(10), + COMMIT_COUNT BIGINT, + READ_COUNT BIGINT, + FILTER_COUNT BIGINT, + WRITE_COUNT BIGINT, + READ_SKIP_COUNT BIGINT, + WRITE_SKIP_COUNT BIGINT, + PROCESS_SKIP_COUNT BIGINT, + ROLLBACK_COUNT BIGINT, + EXIT_CODE VARCHAR(2500), + EXIT_MESSAGE VARCHAR(2500), LAST_UPDATED TIMESTAMP(9), constraint JOB_EXEC_STEP_FK foreign key (JOB_EXECUTION_ID) references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID) ) ; -CREATE TABLE BATCH_STEP_EXECUTION_CONTEXT ( +CREATE TABLE BATCH_STEP_EXECUTION_CONTEXT ( STEP_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY, SHORT_CONTEXT VARCHAR(2500) NOT NULL, - SERIALIZED_CONTEXT LONGVARCHAR , + SERIALIZED_CONTEXT LONGVARCHAR, constraint STEP_EXEC_CTX_FK foreign key (STEP_EXECUTION_ID) references BATCH_STEP_EXECUTION(STEP_EXECUTION_ID) ) ; -CREATE TABLE BATCH_JOB_EXECUTION_CONTEXT ( +CREATE TABLE BATCH_JOB_EXECUTION_CONTEXT ( JOB_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY, SHORT_CONTEXT VARCHAR(2500) NOT NULL, - SERIALIZED_CONTEXT LONGVARCHAR , + SERIALIZED_CONTEXT LONGVARCHAR, constraint JOB_EXEC_CTX_FK foreign key (JOB_EXECUTION_ID) references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID) ) ; @@ -79,6 +79,6 @@ CREATE TABLE BATCH_STEP_EXECUTION_SEQ ( CREATE TABLE BATCH_JOB_EXECUTION_SEQ ( ID BIGINT IDENTITY ); -CREATE TABLE BATCH_JOB_SEQ ( +CREATE TABLE BATCH_JOB_INSTANCE_SEQ ( ID BIGINT IDENTITY ); diff --git a/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-mariadb.sql b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-mariadb.sql index 31f585fbc0..2efb285abd 100644 --- a/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-mariadb.sql +++ b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-mariadb.sql @@ -1,78 +1,78 @@ -CREATE TABLE BATCH_JOB_INSTANCE ( - JOB_INSTANCE_ID BIGINT NOT NULL PRIMARY KEY , - VERSION BIGINT , +CREATE TABLE BATCH_JOB_INSTANCE ( + JOB_INSTANCE_ID BIGINT NOT NULL PRIMARY KEY, + VERSION BIGINT, JOB_NAME VARCHAR(100) NOT NULL, JOB_KEY VARCHAR(32) NOT NULL, constraint JOB_INST_UN unique (JOB_NAME, JOB_KEY) ) ENGINE=InnoDB; -CREATE TABLE BATCH_JOB_EXECUTION ( - JOB_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY , - VERSION BIGINT , +CREATE TABLE BATCH_JOB_EXECUTION ( + JOB_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY, + VERSION BIGINT, JOB_INSTANCE_ID BIGINT NOT NULL, CREATE_TIME DATETIME(6) NOT NULL, - START_TIME DATETIME(6) DEFAULT NULL , - END_TIME DATETIME(6) DEFAULT NULL , - STATUS VARCHAR(10) , - EXIT_CODE VARCHAR(2500) , - EXIT_MESSAGE VARCHAR(2500) , + START_TIME DATETIME(6) DEFAULT NULL, + END_TIME DATETIME(6) DEFAULT NULL, + STATUS VARCHAR(10), + EXIT_CODE VARCHAR(2500), + EXIT_MESSAGE VARCHAR(2500), LAST_UPDATED DATETIME(6), constraint JOB_INST_EXEC_FK foreign key (JOB_INSTANCE_ID) references BATCH_JOB_INSTANCE(JOB_INSTANCE_ID) ) ENGINE=InnoDB; -CREATE TABLE BATCH_JOB_EXECUTION_PARAMS ( - JOB_EXECUTION_ID BIGINT NOT NULL , - PARAMETER_NAME VARCHAR(100) NOT NULL , - PARAMETER_TYPE VARCHAR(100) NOT NULL , - PARAMETER_VALUE VARCHAR(2500) , - IDENTIFYING CHAR(1) NOT NULL , +CREATE TABLE BATCH_JOB_EXECUTION_PARAMS ( + JOB_EXECUTION_ID BIGINT NOT NULL, + PARAMETER_NAME VARCHAR(100) NOT NULL, + PARAMETER_TYPE VARCHAR(100) NOT NULL, + PARAMETER_VALUE VARCHAR(2500), + IDENTIFYING CHAR(1) NOT NULL, constraint JOB_EXEC_PARAMS_FK foreign key (JOB_EXECUTION_ID) references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID) ) ENGINE=InnoDB; -CREATE TABLE BATCH_STEP_EXECUTION ( - STEP_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY , +CREATE TABLE BATCH_STEP_EXECUTION ( + STEP_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY, VERSION BIGINT NOT NULL, STEP_NAME VARCHAR(100) NOT NULL, JOB_EXECUTION_ID BIGINT NOT NULL, CREATE_TIME DATETIME(6) NOT NULL, - START_TIME DATETIME(6) DEFAULT NULL , - END_TIME DATETIME(6) DEFAULT NULL , - STATUS VARCHAR(10) , - COMMIT_COUNT BIGINT , - READ_COUNT BIGINT , - FILTER_COUNT BIGINT , - WRITE_COUNT BIGINT , - READ_SKIP_COUNT BIGINT , - WRITE_SKIP_COUNT BIGINT , - PROCESS_SKIP_COUNT BIGINT , - ROLLBACK_COUNT BIGINT , - EXIT_CODE VARCHAR(2500) , - EXIT_MESSAGE VARCHAR(2500) , + START_TIME DATETIME(6) DEFAULT NULL, + END_TIME DATETIME(6) DEFAULT NULL, + STATUS VARCHAR(10), + COMMIT_COUNT BIGINT, + READ_COUNT BIGINT, + FILTER_COUNT BIGINT, + WRITE_COUNT BIGINT, + READ_SKIP_COUNT BIGINT, + WRITE_SKIP_COUNT BIGINT, + PROCESS_SKIP_COUNT BIGINT, + ROLLBACK_COUNT BIGINT, + EXIT_CODE VARCHAR(2500), + EXIT_MESSAGE VARCHAR(2500), LAST_UPDATED DATETIME(6), constraint JOB_EXEC_STEP_FK foreign key (JOB_EXECUTION_ID) references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID) ) ENGINE=InnoDB; -CREATE TABLE BATCH_STEP_EXECUTION_CONTEXT ( +CREATE TABLE BATCH_STEP_EXECUTION_CONTEXT ( STEP_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY, SHORT_CONTEXT VARCHAR(2500) NOT NULL, - SERIALIZED_CONTEXT TEXT , + SERIALIZED_CONTEXT TEXT, constraint STEP_EXEC_CTX_FK foreign key (STEP_EXECUTION_ID) references BATCH_STEP_EXECUTION(STEP_EXECUTION_ID) ) ENGINE=InnoDB; -CREATE TABLE BATCH_JOB_EXECUTION_CONTEXT ( +CREATE TABLE BATCH_JOB_EXECUTION_CONTEXT ( JOB_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY, SHORT_CONTEXT VARCHAR(2500) NOT NULL, - SERIALIZED_CONTEXT TEXT , + SERIALIZED_CONTEXT TEXT, constraint JOB_EXEC_CTX_FK foreign key (JOB_EXECUTION_ID) references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID) ) ENGINE=InnoDB; CREATE SEQUENCE BATCH_STEP_EXECUTION_SEQ START WITH 1 MINVALUE 1 MAXVALUE 9223372036854775806 INCREMENT BY 1 NOCACHE NOCYCLE ENGINE=InnoDB; CREATE SEQUENCE BATCH_JOB_EXECUTION_SEQ START WITH 1 MINVALUE 1 MAXVALUE 9223372036854775806 INCREMENT BY 1 NOCACHE NOCYCLE ENGINE=InnoDB; -CREATE SEQUENCE BATCH_JOB_SEQ START WITH 1 MINVALUE 1 MAXVALUE 9223372036854775806 INCREMENT BY 1 NOCACHE NOCYCLE ENGINE=InnoDB; +CREATE SEQUENCE BATCH_JOB_INSTANCE_SEQ START WITH 1 MINVALUE 1 MAXVALUE 9223372036854775806 INCREMENT BY 1 NOCACHE NOCYCLE ENGINE=InnoDB; 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..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 @@ -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( {"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/main/resources/org/springframework/batch/core/schema-mysql.sql b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-mysql.sql index 197ef3ff02..c36bfb1940 100644 --- a/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-mysql.sql +++ b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-mysql.sql @@ -1,74 +1,74 @@ -- Autogenerated: do not edit this file -CREATE TABLE BATCH_JOB_INSTANCE ( - JOB_INSTANCE_ID BIGINT NOT NULL PRIMARY KEY , - VERSION BIGINT , +CREATE TABLE BATCH_JOB_INSTANCE ( + JOB_INSTANCE_ID BIGINT NOT NULL PRIMARY KEY, + VERSION BIGINT, JOB_NAME VARCHAR(100) NOT NULL, JOB_KEY VARCHAR(32) NOT NULL, constraint JOB_INST_UN unique (JOB_NAME, JOB_KEY) ) ENGINE=InnoDB; -CREATE TABLE BATCH_JOB_EXECUTION ( - JOB_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY , - VERSION BIGINT , +CREATE TABLE BATCH_JOB_EXECUTION ( + JOB_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY, + VERSION BIGINT, JOB_INSTANCE_ID BIGINT NOT NULL, CREATE_TIME DATETIME(6) NOT NULL, - START_TIME DATETIME(6) DEFAULT NULL , - END_TIME DATETIME(6) DEFAULT NULL , - STATUS VARCHAR(10) , - EXIT_CODE VARCHAR(2500) , - EXIT_MESSAGE VARCHAR(2500) , + START_TIME DATETIME(6) DEFAULT NULL, + END_TIME DATETIME(6) DEFAULT NULL, + STATUS VARCHAR(10), + EXIT_CODE VARCHAR(2500), + EXIT_MESSAGE VARCHAR(2500), LAST_UPDATED DATETIME(6), constraint JOB_INST_EXEC_FK foreign key (JOB_INSTANCE_ID) references BATCH_JOB_INSTANCE(JOB_INSTANCE_ID) ) ENGINE=InnoDB; -CREATE TABLE BATCH_JOB_EXECUTION_PARAMS ( - JOB_EXECUTION_ID BIGINT NOT NULL , - PARAMETER_NAME VARCHAR(100) NOT NULL , - PARAMETER_TYPE VARCHAR(100) NOT NULL , - PARAMETER_VALUE VARCHAR(2500) , - IDENTIFYING CHAR(1) NOT NULL , +CREATE TABLE BATCH_JOB_EXECUTION_PARAMS ( + JOB_EXECUTION_ID BIGINT NOT NULL, + PARAMETER_NAME VARCHAR(100) NOT NULL, + PARAMETER_TYPE VARCHAR(100) NOT NULL, + PARAMETER_VALUE VARCHAR(2500), + IDENTIFYING CHAR(1) NOT NULL, constraint JOB_EXEC_PARAMS_FK foreign key (JOB_EXECUTION_ID) references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID) ) ENGINE=InnoDB; -CREATE TABLE BATCH_STEP_EXECUTION ( - STEP_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY , +CREATE TABLE BATCH_STEP_EXECUTION ( + STEP_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY, VERSION BIGINT NOT NULL, STEP_NAME VARCHAR(100) NOT NULL, JOB_EXECUTION_ID BIGINT NOT NULL, CREATE_TIME DATETIME(6) NOT NULL, - START_TIME DATETIME(6) DEFAULT NULL , - END_TIME DATETIME(6) DEFAULT NULL , - STATUS VARCHAR(10) , - COMMIT_COUNT BIGINT , - READ_COUNT BIGINT , - FILTER_COUNT BIGINT , - WRITE_COUNT BIGINT , - READ_SKIP_COUNT BIGINT , - WRITE_SKIP_COUNT BIGINT , - PROCESS_SKIP_COUNT BIGINT , - ROLLBACK_COUNT BIGINT , - EXIT_CODE VARCHAR(2500) , - EXIT_MESSAGE VARCHAR(2500) , + START_TIME DATETIME(6) DEFAULT NULL, + END_TIME DATETIME(6) DEFAULT NULL, + STATUS VARCHAR(10), + COMMIT_COUNT BIGINT, + READ_COUNT BIGINT, + FILTER_COUNT BIGINT, + WRITE_COUNT BIGINT, + READ_SKIP_COUNT BIGINT, + WRITE_SKIP_COUNT BIGINT, + PROCESS_SKIP_COUNT BIGINT, + ROLLBACK_COUNT BIGINT, + EXIT_CODE VARCHAR(2500), + EXIT_MESSAGE VARCHAR(2500), LAST_UPDATED DATETIME(6), constraint JOB_EXEC_STEP_FK foreign key (JOB_EXECUTION_ID) references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID) ) ENGINE=InnoDB; -CREATE TABLE BATCH_STEP_EXECUTION_CONTEXT ( +CREATE TABLE BATCH_STEP_EXECUTION_CONTEXT ( STEP_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY, SHORT_CONTEXT VARCHAR(2500) NOT NULL, - SERIALIZED_CONTEXT TEXT , + SERIALIZED_CONTEXT TEXT, constraint STEP_EXEC_CTX_FK foreign key (STEP_EXECUTION_ID) references BATCH_STEP_EXECUTION(STEP_EXECUTION_ID) ) ENGINE=InnoDB; -CREATE TABLE BATCH_JOB_EXECUTION_CONTEXT ( +CREATE TABLE BATCH_JOB_EXECUTION_CONTEXT ( JOB_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY, SHORT_CONTEXT VARCHAR(2500) NOT NULL, - SERIALIZED_CONTEXT TEXT , + SERIALIZED_CONTEXT TEXT, constraint JOB_EXEC_CTX_FK foreign key (JOB_EXECUTION_ID) references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID) ) ENGINE=InnoDB; @@ -89,10 +89,10 @@ CREATE TABLE BATCH_JOB_EXECUTION_SEQ ( INSERT INTO BATCH_JOB_EXECUTION_SEQ (ID, UNIQUE_KEY) select * from (select 0 as ID, '0' as UNIQUE_KEY) as tmp where not exists(select * from BATCH_JOB_EXECUTION_SEQ); -CREATE TABLE BATCH_JOB_SEQ ( +CREATE TABLE BATCH_JOB_INSTANCE_SEQ ( ID BIGINT NOT NULL, UNIQUE_KEY CHAR(1) NOT NULL, constraint UNIQUE_KEY_UN unique (UNIQUE_KEY) ) ENGINE=InnoDB; -INSERT INTO BATCH_JOB_SEQ (ID, UNIQUE_KEY) select * from (select 0 as ID, '0' as UNIQUE_KEY) as tmp where not exists(select * from BATCH_JOB_SEQ); +INSERT INTO BATCH_JOB_INSTANCE_SEQ (ID, UNIQUE_KEY) select * from (select 0 as ID, '0' as UNIQUE_KEY) as tmp where not exists(select * from BATCH_JOB_INSTANCE_SEQ); diff --git a/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-oracle.sql b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-oracle.sql index 73deea519e..41e2f868bc 100644 --- a/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-oracle.sql +++ b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-oracle.sql @@ -1,78 +1,78 @@ -- Autogenerated: do not edit this file -CREATE TABLE BATCH_JOB_INSTANCE ( - JOB_INSTANCE_ID NUMBER(19,0) NOT NULL PRIMARY KEY , - VERSION NUMBER(19,0) , +CREATE TABLE BATCH_JOB_INSTANCE ( + JOB_INSTANCE_ID NUMBER(19,0) NOT NULL PRIMARY KEY, + VERSION NUMBER(19,0), JOB_NAME VARCHAR2(100 char) NOT NULL, JOB_KEY VARCHAR2(32 char) NOT NULL, constraint JOB_INST_UN unique (JOB_NAME, JOB_KEY) ) SEGMENT CREATION IMMEDIATE; -CREATE TABLE BATCH_JOB_EXECUTION ( - JOB_EXECUTION_ID NUMBER(19,0) NOT NULL PRIMARY KEY , - VERSION NUMBER(19,0) , +CREATE TABLE BATCH_JOB_EXECUTION ( + JOB_EXECUTION_ID NUMBER(19,0) NOT NULL PRIMARY KEY, + VERSION NUMBER(19,0), JOB_INSTANCE_ID NUMBER(19,0) NOT NULL, CREATE_TIME TIMESTAMP(9) NOT NULL, - START_TIME TIMESTAMP(9) DEFAULT NULL , - END_TIME TIMESTAMP(9) DEFAULT NULL , - STATUS VARCHAR2(10 char) , - EXIT_CODE VARCHAR2(2500 char) , - EXIT_MESSAGE VARCHAR2(2500 char) , + START_TIME TIMESTAMP(9) DEFAULT NULL, + END_TIME TIMESTAMP(9) DEFAULT NULL, + STATUS VARCHAR2(10 char), + EXIT_CODE VARCHAR2(2500 char), + EXIT_MESSAGE VARCHAR2(2500 char), LAST_UPDATED TIMESTAMP(9), constraint JOB_INST_EXEC_FK foreign key (JOB_INSTANCE_ID) references BATCH_JOB_INSTANCE(JOB_INSTANCE_ID) ) SEGMENT CREATION IMMEDIATE; -CREATE TABLE BATCH_JOB_EXECUTION_PARAMS ( - JOB_EXECUTION_ID NUMBER(19,0) NOT NULL , - PARAMETER_NAME VARCHAR(100 char) NOT NULL , - PARAMETER_TYPE VARCHAR(100 char) NOT NULL , - PARAMETER_VALUE VARCHAR(2500 char) , - IDENTIFYING CHAR(1) NOT NULL , +CREATE TABLE BATCH_JOB_EXECUTION_PARAMS ( + JOB_EXECUTION_ID NUMBER(19,0) NOT NULL, + PARAMETER_NAME VARCHAR(100 char) NOT NULL, + PARAMETER_TYPE VARCHAR(100 char) NOT NULL, + PARAMETER_VALUE VARCHAR(2500 char), + IDENTIFYING CHAR(1) NOT NULL, constraint JOB_EXEC_PARAMS_FK foreign key (JOB_EXECUTION_ID) references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID) ) SEGMENT CREATION IMMEDIATE; -CREATE TABLE BATCH_STEP_EXECUTION ( - STEP_EXECUTION_ID NUMBER(19,0) NOT NULL PRIMARY KEY , +CREATE TABLE BATCH_STEP_EXECUTION ( + STEP_EXECUTION_ID NUMBER(19,0) NOT NULL PRIMARY KEY, VERSION NUMBER(19,0) NOT NULL, STEP_NAME VARCHAR2(100 char) NOT NULL, JOB_EXECUTION_ID NUMBER(19,0) NOT NULL, CREATE_TIME TIMESTAMP(9) NOT NULL, - START_TIME TIMESTAMP(9) DEFAULT NULL , - END_TIME TIMESTAMP(9) DEFAULT NULL , - STATUS VARCHAR2(10 char) , - COMMIT_COUNT NUMBER(19,0) , - READ_COUNT NUMBER(19,0) , - FILTER_COUNT NUMBER(19,0) , - WRITE_COUNT NUMBER(19,0) , - READ_SKIP_COUNT NUMBER(19,0) , - WRITE_SKIP_COUNT NUMBER(19,0) , - PROCESS_SKIP_COUNT NUMBER(19,0) , - ROLLBACK_COUNT NUMBER(19,0) , - EXIT_CODE VARCHAR2(2500 char) , - EXIT_MESSAGE VARCHAR2(2500 char) , + START_TIME TIMESTAMP(9) DEFAULT NULL, + END_TIME TIMESTAMP(9) DEFAULT NULL, + STATUS VARCHAR2(10 char), + COMMIT_COUNT NUMBER(19,0), + READ_COUNT NUMBER(19,0), + FILTER_COUNT NUMBER(19,0), + WRITE_COUNT NUMBER(19,0), + READ_SKIP_COUNT NUMBER(19,0), + WRITE_SKIP_COUNT NUMBER(19,0), + PROCESS_SKIP_COUNT NUMBER(19,0), + ROLLBACK_COUNT NUMBER(19,0), + EXIT_CODE VARCHAR2(2500 char), + EXIT_MESSAGE VARCHAR2(2500 char), LAST_UPDATED TIMESTAMP(9), constraint JOB_EXEC_STEP_FK foreign key (JOB_EXECUTION_ID) references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID) ) SEGMENT CREATION IMMEDIATE; -CREATE TABLE BATCH_STEP_EXECUTION_CONTEXT ( +CREATE TABLE BATCH_STEP_EXECUTION_CONTEXT ( STEP_EXECUTION_ID NUMBER(19,0) NOT NULL PRIMARY KEY, SHORT_CONTEXT VARCHAR2(2500 char) NOT NULL, - SERIALIZED_CONTEXT CLOB , + SERIALIZED_CONTEXT CLOB, constraint STEP_EXEC_CTX_FK foreign key (STEP_EXECUTION_ID) references BATCH_STEP_EXECUTION(STEP_EXECUTION_ID) ) SEGMENT CREATION IMMEDIATE; -CREATE TABLE BATCH_JOB_EXECUTION_CONTEXT ( +CREATE TABLE BATCH_JOB_EXECUTION_CONTEXT ( JOB_EXECUTION_ID NUMBER(19,0) NOT NULL PRIMARY KEY, SHORT_CONTEXT VARCHAR2(2500 char) NOT NULL, - SERIALIZED_CONTEXT CLOB , + SERIALIZED_CONTEXT CLOB, constraint JOB_EXEC_CTX_FK foreign key (JOB_EXECUTION_ID) references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID) ) SEGMENT CREATION IMMEDIATE; CREATE SEQUENCE BATCH_STEP_EXECUTION_SEQ START WITH 0 MINVALUE 0 MAXVALUE 9223372036854775807 ORDER NOCYCLE; CREATE SEQUENCE BATCH_JOB_EXECUTION_SEQ START WITH 0 MINVALUE 0 MAXVALUE 9223372036854775807 ORDER NOCYCLE; -CREATE SEQUENCE BATCH_JOB_SEQ START WITH 0 MINVALUE 0 MAXVALUE 9223372036854775807 ORDER NOCYCLE; +CREATE SEQUENCE BATCH_JOB_INSTANCE_SEQ START WITH 0 MINVALUE 0 MAXVALUE 9223372036854775807 ORDER NOCYCLE; diff --git a/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-postgresql.sql b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-postgresql.sql index dd146f1f0f..097a9d6c14 100644 --- a/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-postgresql.sql +++ b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-postgresql.sql @@ -1,78 +1,78 @@ -- Autogenerated: do not edit this file -CREATE TABLE BATCH_JOB_INSTANCE ( - JOB_INSTANCE_ID BIGINT NOT NULL PRIMARY KEY , - VERSION BIGINT , +CREATE TABLE BATCH_JOB_INSTANCE ( + JOB_INSTANCE_ID BIGINT NOT NULL PRIMARY KEY, + VERSION BIGINT, JOB_NAME VARCHAR(100) NOT NULL, JOB_KEY VARCHAR(32) NOT NULL, constraint JOB_INST_UN unique (JOB_NAME, JOB_KEY) ) ; -CREATE TABLE BATCH_JOB_EXECUTION ( - JOB_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY , - VERSION BIGINT , +CREATE TABLE BATCH_JOB_EXECUTION ( + JOB_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY, + VERSION BIGINT, JOB_INSTANCE_ID BIGINT NOT NULL, CREATE_TIME TIMESTAMP NOT NULL, - START_TIME TIMESTAMP DEFAULT NULL , - END_TIME TIMESTAMP DEFAULT NULL , - STATUS VARCHAR(10) , - EXIT_CODE VARCHAR(2500) , - EXIT_MESSAGE VARCHAR(2500) , + START_TIME TIMESTAMP DEFAULT NULL, + END_TIME TIMESTAMP DEFAULT NULL, + STATUS VARCHAR(10), + EXIT_CODE VARCHAR(2500), + EXIT_MESSAGE VARCHAR(2500), LAST_UPDATED TIMESTAMP, constraint JOB_INST_EXEC_FK foreign key (JOB_INSTANCE_ID) references BATCH_JOB_INSTANCE(JOB_INSTANCE_ID) ) ; -CREATE TABLE BATCH_JOB_EXECUTION_PARAMS ( - JOB_EXECUTION_ID BIGINT NOT NULL , - PARAMETER_NAME VARCHAR(100) NOT NULL , - PARAMETER_TYPE VARCHAR(100) NOT NULL , - PARAMETER_VALUE VARCHAR(2500) , - IDENTIFYING CHAR(1) NOT NULL , +CREATE TABLE BATCH_JOB_EXECUTION_PARAMS ( + JOB_EXECUTION_ID BIGINT NOT NULL, + PARAMETER_NAME VARCHAR(100) NOT NULL, + PARAMETER_TYPE VARCHAR(100) NOT NULL, + PARAMETER_VALUE VARCHAR(2500), + IDENTIFYING CHAR(1) NOT NULL, constraint JOB_EXEC_PARAMS_FK foreign key (JOB_EXECUTION_ID) references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID) ) ; -CREATE TABLE BATCH_STEP_EXECUTION ( - STEP_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY , +CREATE TABLE BATCH_STEP_EXECUTION ( + STEP_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY, VERSION BIGINT NOT NULL, STEP_NAME VARCHAR(100) NOT NULL, JOB_EXECUTION_ID BIGINT NOT NULL, CREATE_TIME TIMESTAMP NOT NULL, - START_TIME TIMESTAMP DEFAULT NULL , - END_TIME TIMESTAMP DEFAULT NULL , - STATUS VARCHAR(10) , - COMMIT_COUNT BIGINT , - READ_COUNT BIGINT , - FILTER_COUNT BIGINT , - WRITE_COUNT BIGINT , - READ_SKIP_COUNT BIGINT , - WRITE_SKIP_COUNT BIGINT , - PROCESS_SKIP_COUNT BIGINT , - ROLLBACK_COUNT BIGINT , - EXIT_CODE VARCHAR(2500) , - EXIT_MESSAGE VARCHAR(2500) , + START_TIME TIMESTAMP DEFAULT NULL, + END_TIME TIMESTAMP DEFAULT NULL, + STATUS VARCHAR(10), + COMMIT_COUNT BIGINT, + READ_COUNT BIGINT, + FILTER_COUNT BIGINT, + WRITE_COUNT BIGINT, + READ_SKIP_COUNT BIGINT, + WRITE_SKIP_COUNT BIGINT, + PROCESS_SKIP_COUNT BIGINT, + ROLLBACK_COUNT BIGINT, + EXIT_CODE VARCHAR(2500), + EXIT_MESSAGE VARCHAR(2500), LAST_UPDATED TIMESTAMP, constraint JOB_EXEC_STEP_FK foreign key (JOB_EXECUTION_ID) references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID) ) ; -CREATE TABLE BATCH_STEP_EXECUTION_CONTEXT ( +CREATE TABLE BATCH_STEP_EXECUTION_CONTEXT ( STEP_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY, SHORT_CONTEXT VARCHAR(2500) NOT NULL, - SERIALIZED_CONTEXT TEXT , + SERIALIZED_CONTEXT TEXT, constraint STEP_EXEC_CTX_FK foreign key (STEP_EXECUTION_ID) references BATCH_STEP_EXECUTION(STEP_EXECUTION_ID) ) ; -CREATE TABLE BATCH_JOB_EXECUTION_CONTEXT ( +CREATE TABLE BATCH_JOB_EXECUTION_CONTEXT ( JOB_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY, SHORT_CONTEXT VARCHAR(2500) NOT NULL, - SERIALIZED_CONTEXT TEXT , + SERIALIZED_CONTEXT TEXT, constraint JOB_EXEC_CTX_FK foreign key (JOB_EXECUTION_ID) references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID) ) ; CREATE SEQUENCE BATCH_STEP_EXECUTION_SEQ MAXVALUE 9223372036854775807 NO CYCLE; CREATE SEQUENCE BATCH_JOB_EXECUTION_SEQ MAXVALUE 9223372036854775807 NO CYCLE; -CREATE SEQUENCE BATCH_JOB_SEQ MAXVALUE 9223372036854775807 NO CYCLE; +CREATE SEQUENCE BATCH_JOB_INSTANCE_SEQ MAXVALUE 9223372036854775807 NO CYCLE; diff --git a/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-sqlite.sql b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-sqlite.sql index 5a39c09e2a..d18745daa4 100644 --- a/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-sqlite.sql +++ b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-sqlite.sql @@ -1,74 +1,74 @@ -- Autogenerated: do not edit this file -CREATE TABLE BATCH_JOB_INSTANCE ( +CREATE TABLE BATCH_JOB_INSTANCE ( JOB_INSTANCE_ID INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - VERSION INTEGER , + VERSION INTEGER, JOB_NAME VARCHAR(100) NOT NULL, JOB_KEY VARCHAR(32) NOT NULL, constraint JOB_INST_UN unique (JOB_NAME, JOB_KEY) ) ; -CREATE TABLE BATCH_JOB_EXECUTION ( +CREATE TABLE BATCH_JOB_EXECUTION ( JOB_EXECUTION_ID INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - VERSION INTEGER , + VERSION INTEGER, JOB_INSTANCE_ID INTEGER NOT NULL, CREATE_TIME TIMESTAMP NOT NULL, - START_TIME TIMESTAMP DEFAULT NULL , - END_TIME TIMESTAMP DEFAULT NULL , - STATUS VARCHAR(10) , - EXIT_CODE VARCHAR(100) , - EXIT_MESSAGE VARCHAR(2500) , + START_TIME TIMESTAMP DEFAULT NULL, + END_TIME TIMESTAMP DEFAULT NULL, + STATUS VARCHAR(10), + EXIT_CODE VARCHAR(100), + EXIT_MESSAGE VARCHAR(2500), LAST_UPDATED TIMESTAMP, constraint JOB_INST_EXEC_FK foreign key (JOB_INSTANCE_ID) references BATCH_JOB_INSTANCE(JOB_INSTANCE_ID) ) ; -CREATE TABLE BATCH_JOB_EXECUTION_PARAMS ( - JOB_EXECUTION_ID INTEGER NOT NULL , - PARAMETER_NAME VARCHAR(100) NOT NULL , - PARAMETER_TYPE VARCHAR(100) NOT NULL , - PARAMETER_VALUE VARCHAR(2500) , - IDENTIFYING CHAR(1) NOT NULL , +CREATE TABLE BATCH_JOB_EXECUTION_PARAMS ( + JOB_EXECUTION_ID INTEGER NOT NULL, + PARAMETER_NAME VARCHAR(100) NOT NULL, + PARAMETER_TYPE VARCHAR(100) NOT NULL, + PARAMETER_VALUE VARCHAR(2500), + IDENTIFYING CHAR(1) NOT NULL, constraint JOB_EXEC_PARAMS_FK foreign key (JOB_EXECUTION_ID) references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID) ) ; -CREATE TABLE BATCH_STEP_EXECUTION ( +CREATE TABLE BATCH_STEP_EXECUTION ( STEP_EXECUTION_ID INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, VERSION INTEGER NOT NULL, STEP_NAME VARCHAR(100) NOT NULL, JOB_EXECUTION_ID INTEGER NOT NULL, CREATE_TIME TIMESTAMP NOT NULL, - START_TIME TIMESTAMP DEFAULT NULL , - END_TIME TIMESTAMP DEFAULT NULL , - STATUS VARCHAR(10) , - COMMIT_COUNT INTEGER , - READ_COUNT INTEGER , - FILTER_COUNT INTEGER , - WRITE_COUNT INTEGER , - READ_SKIP_COUNT INTEGER , - WRITE_SKIP_COUNT INTEGER , - PROCESS_SKIP_COUNT INTEGER , - ROLLBACK_COUNT INTEGER , - EXIT_CODE VARCHAR(100) , - EXIT_MESSAGE VARCHAR(2500) , + START_TIME TIMESTAMP DEFAULT NULL, + END_TIME TIMESTAMP DEFAULT NULL, + STATUS VARCHAR(10), + COMMIT_COUNT INTEGER, + READ_COUNT INTEGER, + FILTER_COUNT INTEGER, + WRITE_COUNT INTEGER, + READ_SKIP_COUNT INTEGER, + WRITE_SKIP_COUNT INTEGER, + PROCESS_SKIP_COUNT INTEGER, + ROLLBACK_COUNT INTEGER, + EXIT_CODE VARCHAR(100), + EXIT_MESSAGE VARCHAR(2500), LAST_UPDATED TIMESTAMP, constraint JOB_EXEC_STEP_FK foreign key (JOB_EXECUTION_ID) references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID) ) ; -CREATE TABLE BATCH_STEP_EXECUTION_CONTEXT ( +CREATE TABLE BATCH_STEP_EXECUTION_CONTEXT ( STEP_EXECUTION_ID INTEGER NOT NULL PRIMARY KEY, SHORT_CONTEXT VARCHAR(2500) NOT NULL, - SERIALIZED_CONTEXT CLOB , + SERIALIZED_CONTEXT CLOB, constraint STEP_EXEC_CTX_FK foreign key (STEP_EXECUTION_ID) references BATCH_STEP_EXECUTION(STEP_EXECUTION_ID) ) ; -CREATE TABLE BATCH_JOB_EXECUTION_CONTEXT ( +CREATE TABLE BATCH_JOB_EXECUTION_CONTEXT ( JOB_EXECUTION_ID INTEGER NOT NULL PRIMARY KEY, SHORT_CONTEXT VARCHAR(2500) NOT NULL, - SERIALIZED_CONTEXT CLOB , + SERIALIZED_CONTEXT CLOB, constraint JOB_EXEC_CTX_FK foreign key (JOB_EXECUTION_ID) references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID) ) ; @@ -79,6 +79,6 @@ CREATE TABLE BATCH_STEP_EXECUTION_SEQ ( CREATE TABLE BATCH_JOB_EXECUTION_SEQ ( ID INTEGER PRIMARY KEY AUTOINCREMENT ); -CREATE TABLE BATCH_JOB_SEQ ( +CREATE TABLE BATCH_JOB_INSTANCE_SEQ ( ID INTEGER PRIMARY KEY AUTOINCREMENT ); diff --git a/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-sqlserver.sql b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-sqlserver.sql index 70c89664c0..cbb5958e1f 100644 --- a/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-sqlserver.sql +++ b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-sqlserver.sql @@ -1,20 +1,20 @@ -- Autogenerated: do not edit this file -CREATE TABLE BATCH_JOB_INSTANCE ( - JOB_INSTANCE_ID BIGINT NOT NULL PRIMARY KEY , +CREATE TABLE BATCH_JOB_INSTANCE ( + JOB_INSTANCE_ID BIGINT NOT NULL PRIMARY KEY, VERSION BIGINT NULL, JOB_NAME VARCHAR(100) NOT NULL, JOB_KEY VARCHAR(32) NOT NULL, constraint JOB_INST_UN unique (JOB_NAME, JOB_KEY) ) ; -CREATE TABLE BATCH_JOB_EXECUTION ( - JOB_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY , +CREATE TABLE BATCH_JOB_EXECUTION ( + JOB_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY, VERSION BIGINT NULL, JOB_INSTANCE_ID BIGINT NOT NULL, CREATE_TIME DATETIME NOT NULL, - START_TIME DATETIME DEFAULT NULL , - END_TIME DATETIME DEFAULT NULL , + START_TIME DATETIME DEFAULT NULL, + END_TIME DATETIME DEFAULT NULL, STATUS VARCHAR(10) NULL, EXIT_CODE VARCHAR(2500) NULL, EXIT_MESSAGE VARCHAR(2500) NULL, @@ -23,24 +23,24 @@ CREATE TABLE BATCH_JOB_EXECUTION ( references BATCH_JOB_INSTANCE(JOB_INSTANCE_ID) ) ; -CREATE TABLE BATCH_JOB_EXECUTION_PARAMS ( - JOB_EXECUTION_ID BIGINT NOT NULL , - PARAMETER_NAME VARCHAR(100) NOT NULL , - PARAMETER_TYPE VARCHAR(100) NOT NULL , - PARAMETER_VALUE VARCHAR(2500) , - IDENTIFYING CHAR(1) NOT NULL , +CREATE TABLE BATCH_JOB_EXECUTION_PARAMS ( + JOB_EXECUTION_ID BIGINT NOT NULL, + PARAMETER_NAME VARCHAR(100) NOT NULL, + PARAMETER_TYPE VARCHAR(100) NOT NULL, + PARAMETER_VALUE VARCHAR(2500), + IDENTIFYING CHAR(1) NOT NULL, constraint JOB_EXEC_PARAMS_FK foreign key (JOB_EXECUTION_ID) references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID) ) ; -CREATE TABLE BATCH_STEP_EXECUTION ( - STEP_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY , +CREATE TABLE BATCH_STEP_EXECUTION ( + STEP_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY, VERSION BIGINT NOT NULL, STEP_NAME VARCHAR(100) NOT NULL, JOB_EXECUTION_ID BIGINT NOT NULL, CREATE_TIME DATETIME NOT NULL, - START_TIME DATETIME DEFAULT NULL , - END_TIME DATETIME DEFAULT NULL , + START_TIME DATETIME DEFAULT NULL, + END_TIME DATETIME DEFAULT NULL, STATUS VARCHAR(10) NULL, COMMIT_COUNT BIGINT NULL, READ_COUNT BIGINT NULL, @@ -57,7 +57,7 @@ CREATE TABLE BATCH_STEP_EXECUTION ( references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID) ) ; -CREATE TABLE BATCH_STEP_EXECUTION_CONTEXT ( +CREATE TABLE BATCH_STEP_EXECUTION_CONTEXT ( STEP_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY, SHORT_CONTEXT VARCHAR(2500) NOT NULL, SERIALIZED_CONTEXT VARCHAR(MAX) NULL, @@ -65,7 +65,7 @@ CREATE TABLE BATCH_STEP_EXECUTION_CONTEXT ( references BATCH_STEP_EXECUTION(STEP_EXECUTION_ID) ) ; -CREATE TABLE BATCH_JOB_EXECUTION_CONTEXT ( +CREATE TABLE BATCH_JOB_EXECUTION_CONTEXT ( JOB_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY, SHORT_CONTEXT VARCHAR(2500) NOT NULL, SERIALIZED_CONTEXT VARCHAR(MAX) NULL, @@ -75,4 +75,4 @@ CREATE TABLE BATCH_JOB_EXECUTION_CONTEXT ( CREATE SEQUENCE BATCH_STEP_EXECUTION_SEQ START WITH 0 MINVALUE 0 MAXVALUE 9223372036854775807 NO CACHE NO CYCLE; CREATE SEQUENCE BATCH_JOB_EXECUTION_SEQ START WITH 0 MINVALUE 0 MAXVALUE 9223372036854775807 NO CACHE NO CYCLE; -CREATE SEQUENCE BATCH_JOB_SEQ START WITH 0 MINVALUE 0 MAXVALUE 9223372036854775807 NO CACHE NO CYCLE; +CREATE SEQUENCE BATCH_JOB_INSTANCE_SEQ START WITH 0 MINVALUE 0 MAXVALUE 9223372036854775807 NO CACHE NO CYCLE; diff --git a/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-sybase.sql b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-sybase.sql index 15b221970e..9fee098206 100644 --- a/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-sybase.sql +++ b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-sybase.sql @@ -1,15 +1,15 @@ -- Autogenerated: do not edit this file -CREATE TABLE BATCH_JOB_INSTANCE ( - JOB_INSTANCE_ID BIGINT NOT NULL PRIMARY KEY , +CREATE TABLE BATCH_JOB_INSTANCE ( + JOB_INSTANCE_ID BIGINT NOT NULL PRIMARY KEY, VERSION BIGINT NULL, JOB_NAME VARCHAR(100) NOT NULL, JOB_KEY VARCHAR(32) NOT NULL, constraint JOB_INST_UN unique (JOB_NAME, JOB_KEY) ) ; -CREATE TABLE BATCH_JOB_EXECUTION ( - JOB_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY , +CREATE TABLE BATCH_JOB_EXECUTION ( + JOB_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY, VERSION BIGINT NULL, JOB_INSTANCE_ID BIGINT NOT NULL, CREATE_TIME DATETIME NOT NULL, @@ -23,18 +23,18 @@ CREATE TABLE BATCH_JOB_EXECUTION ( references BATCH_JOB_INSTANCE(JOB_INSTANCE_ID) ) ; -CREATE TABLE BATCH_JOB_EXECUTION_PARAMS ( - JOB_EXECUTION_ID BIGINT NOT NULL , - PARAMETER_NAME VARCHAR(100) NOT NULL , - PARAMETER_TYPE VARCHAR(100) NOT NULL , - PARAMETER_VALUE VARCHAR(2500) , - IDENTIFYING CHAR(1) NOT NULL , +CREATE TABLE BATCH_JOB_EXECUTION_PARAMS ( + JOB_EXECUTION_ID BIGINT NOT NULL, + PARAMETER_NAME VARCHAR(100) NOT NULL, + PARAMETER_TYPE VARCHAR(100) NOT NULL, + PARAMETER_VALUE VARCHAR(2500), + IDENTIFYING CHAR(1) NOT NULL, constraint JOB_EXEC_PARAMS_FK foreign key (JOB_EXECUTION_ID) references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID) ) ; -CREATE TABLE BATCH_STEP_EXECUTION ( - STEP_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY , +CREATE TABLE BATCH_STEP_EXECUTION ( + STEP_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY, VERSION BIGINT NOT NULL, STEP_NAME VARCHAR(100) NOT NULL, JOB_EXECUTION_ID BIGINT NOT NULL, @@ -57,7 +57,7 @@ CREATE TABLE BATCH_STEP_EXECUTION ( references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID) ) ; -CREATE TABLE BATCH_STEP_EXECUTION_CONTEXT ( +CREATE TABLE BATCH_STEP_EXECUTION_CONTEXT ( STEP_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY, SHORT_CONTEXT VARCHAR(2500) NOT NULL, SERIALIZED_CONTEXT TEXT NULL, @@ -65,7 +65,7 @@ CREATE TABLE BATCH_STEP_EXECUTION_CONTEXT ( references BATCH_STEP_EXECUTION(STEP_EXECUTION_ID) ) ; -CREATE TABLE BATCH_JOB_EXECUTION_CONTEXT ( +CREATE TABLE BATCH_JOB_EXECUTION_CONTEXT ( JOB_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY, SHORT_CONTEXT VARCHAR(2500) NOT NULL, SERIALIZED_CONTEXT TEXT NULL, @@ -75,4 +75,4 @@ CREATE TABLE BATCH_JOB_EXECUTION_CONTEXT ( CREATE TABLE BATCH_STEP_EXECUTION_SEQ (ID BIGINT IDENTITY); CREATE TABLE BATCH_JOB_EXECUTION_SEQ (ID BIGINT IDENTITY); -CREATE TABLE BATCH_JOB_SEQ (ID BIGINT IDENTITY); +CREATE TABLE BATCH_JOB_INSTANCE_SEQ (ID BIGINT IDENTITY); 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..f9a1335a00 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. @@ -20,9 +20,14 @@ import org.junit.jupiter.api.Test; +import org.springframework.batch.core.job.DefaultJobKeyGenerator; +import org.springframework.batch.core.job.JobKeyGenerator; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; + class DefaultJobKeyGeneratorTests { - private final JobKeyGenerator jobKeyGenerator = new DefaultJobKeyGenerator(); + private final JobKeyGenerator jobKeyGenerator = new DefaultJobKeyGenerator(); @Test void testNullParameters() { @@ -65,4 +70,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); + } + } 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..17a736d1db 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-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,17 +15,25 @@ */ 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 + * @author JiWon Seo * */ class ExitStatusTests { @@ -146,7 +154,7 @@ void testAddExitDescription() { } @Test - void testAddExitDescriptionWIthStacktrace() { + void testAddExitDescriptionWithStacktrace() { ExitStatus status = ExitStatus.EXECUTING.addExitDescription(new RuntimeException("Foo")); assertNotSame(ExitStatus.EXECUTING, status); String description = status.getExitDescription(); @@ -175,8 +183,15 @@ void testAddExitCodeWithDescription() { } @Test - void testUnknownIsRunning() { + void testIsRunning() { + // running statuses + assertTrue(ExitStatus.EXECUTING.isRunning()); assertTrue(ExitStatus.UNKNOWN.isRunning()); + // non running statuses + assertFalse(ExitStatus.COMPLETED.isRunning()); + assertFalse(ExitStatus.FAILED.isRunning()); + assertFalse(ExitStatus.STOPPED.isRunning()); + assertFalse(ExitStatus.NOOP.isRunning()); } @Test @@ -186,4 +201,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"))); + } + } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/JobExecutionExceptionTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/JobExecutionExceptionTests.java index 7fa33e9258..7be87bdfc5 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/JobExecutionExceptionTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/JobExecutionExceptionTests.java @@ -15,6 +15,8 @@ */ package org.springframework.batch.core; +import org.springframework.batch.core.job.JobExecutionException; + /** * @author Dave Syer * diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/JobExecutionTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/JobExecutionTests.java index 07f4d8a265..85c1519e69 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/JobExecutionTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/JobExecutionTests.java @@ -21,6 +21,10 @@ import org.junit.jupiter.api.Test; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobInstance; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.step.StepExecution; import org.springframework.util.SerializationUtils; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -45,7 +49,7 @@ void testJobExecution() { } /** - * Test method for {@link org.springframework.batch.core.JobExecution#getEndTime()}. + * Test method for {@link JobExecution#getEndTime()}. */ @Test void testGetEndTime() { @@ -56,7 +60,7 @@ void testGetEndTime() { } /** - * Test method for {@link org.springframework.batch.core.JobExecution#isRunning()}. + * Test method for {@link JobExecution#isRunning()}. */ @Test void testIsRunning() { @@ -73,7 +77,7 @@ void testIsRunning() { } /** - * Test method for {@link org.springframework.batch.core.JobExecution#getStartTime()}. + * Test method for {@link JobExecution#getStartTime()}. */ @Test void testGetStartTime() { @@ -83,7 +87,7 @@ void testGetStartTime() { } /** - * Test method for {@link org.springframework.batch.core.JobExecution#getStatus()}. + * Test method for {@link JobExecution#getStatus()}. */ @Test void testGetStatus() { @@ -93,7 +97,7 @@ void testGetStatus() { } /** - * Test method for {@link org.springframework.batch.core.JobExecution#getStatus()}. + * Test method for {@link JobExecution#getStatus()}. */ @Test void testUpgradeStatus() { @@ -103,7 +107,7 @@ void testUpgradeStatus() { } /** - * Test method for {@link org.springframework.batch.core.JobExecution#getStatus()}. + * Test method for {@link JobExecution#getStatus()}. */ @Test void testDowngradeStatus() { @@ -113,7 +117,7 @@ void testDowngradeStatus() { } /** - * Test method for {@link org.springframework.batch.core.JobExecution#getJobId()}. + * Test method for {@link JobExecution#getJobId()}. */ @Test void testGetJobId() { @@ -123,7 +127,7 @@ void testGetJobId() { } /** - * Test method for {@link org.springframework.batch.core.JobExecution#getJobId()}. + * Test method for {@link JobExecution#getJobId()}. */ @Test void testGetJobIdForNullJob() { @@ -132,7 +136,7 @@ void testGetJobIdForNullJob() { } /** - * Test method for {@link org.springframework.batch.core.JobExecution#getJobId()}. + * Test method for {@link JobExecution#getJobId()}. */ @Test void testGetJob() { @@ -140,8 +144,7 @@ void testGetJob() { } /** - * Test method for - * {@link org.springframework.batch.core.JobExecution#getExitStatus()}. + * Test method for {@link JobExecution#getExitStatus()}. */ @Test void testGetExitCode() { diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/JobInstanceTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/JobInstanceTests.java index a53f4dfcc5..aa8d3e91e2 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/JobInstanceTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/JobInstanceTests.java @@ -19,6 +19,8 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import org.junit.jupiter.api.Test; + +import org.springframework.batch.core.job.JobInstance; import org.springframework.util.SerializationUtils; /** diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/JobInterruptedExceptionTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/JobInterruptedExceptionTests.java index 43d76decd9..f535ede863 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/JobInterruptedExceptionTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/JobInterruptedExceptionTests.java @@ -15,6 +15,8 @@ */ package org.springframework.batch.core; +import org.springframework.batch.core.job.JobInterruptedException; + /** * @author Dave Syer * diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/JobParameterTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/JobParameterTests.java index ed830088f2..37be2c146b 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/JobParameterTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/JobParameterTests.java @@ -23,6 +23,8 @@ import org.junit.jupiter.api.Test; +import org.springframework.batch.core.job.parameters.JobParameter; + /** * @author Lucas Ward * @author Mahmoud Ben Hassine diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/JobParametersBuilderTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/JobParametersBuilderTests.java index 220dfc4724..72acc40035 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/JobParametersBuilderTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/JobParametersBuilderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 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. @@ -26,17 +26,15 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.explore.JobExplorer; -import org.springframework.batch.core.job.SimpleJob; -import org.springframework.batch.core.launch.support.RunIdIncrementer; +import org.springframework.batch.core.job.*; +import org.springframework.batch.core.job.parameters.JobParameter; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; import static org.assertj.core.api.Assertions.assertThat; 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.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; /** * @author Lucas Ward @@ -51,8 +49,6 @@ class JobParametersBuilderTests { private SimpleJob job; - private JobExplorer jobExplorer; - private List jobInstanceList; private List jobExecutionList; @@ -62,10 +58,9 @@ class JobParametersBuilderTests { @BeforeEach void initialize() { this.job = new SimpleJob("simpleJob"); - this.jobExplorer = mock(); this.jobInstanceList = new ArrayList<>(1); this.jobExecutionList = new ArrayList<>(1); - this.parametersBuilder = new JobParametersBuilder(this.jobExplorer); + this.parametersBuilder = new JobParametersBuilder(); } @Test @@ -159,90 +154,4 @@ void testAddJobParameter() { assertEquals("bar", parameters.get("foo").getValue()); } - @Test - void testGetNextJobParametersFirstRun() { - job.setJobParametersIncrementer(new RunIdIncrementer()); - initializeForNextJobParameters(); - this.parametersBuilder.getNextJobParameters(this.job); - defaultNextJobParametersVerify(this.parametersBuilder.toJobParameters(), 4); - } - - @Test - void testGetNextJobParametersNoIncrementer() { - initializeForNextJobParameters(); - final Exception expectedException = assertThrows(IllegalArgumentException.class, - () -> this.parametersBuilder.getNextJobParameters(this.job)); - assertEquals("No job parameters incrementer found for job=simpleJob", expectedException.getMessage()); - } - - @Test - void testGetNextJobParameters() { - this.job.setJobParametersIncrementer(new RunIdIncrementer()); - this.jobInstanceList.add(new JobInstance(1L, "simpleJobInstance")); - this.jobExecutionList.add(getJobExecution(this.jobInstanceList.get(0), null)); - when(this.jobExplorer.getJobInstances("simpleJob", 0, 1)).thenReturn(this.jobInstanceList); - when(this.jobExplorer.getJobExecutions(any())).thenReturn(this.jobExecutionList); - initializeForNextJobParameters(); - this.parametersBuilder.getNextJobParameters(this.job); - defaultNextJobParametersVerify(this.parametersBuilder.toJobParameters(), 4); - } - - @Test - void testGetNextJobParametersRestartable() { - this.job.setRestartable(true); - this.job.setJobParametersIncrementer(new RunIdIncrementer()); - this.jobInstanceList.add(new JobInstance(1L, "simpleJobInstance")); - this.jobExecutionList.add(getJobExecution(this.jobInstanceList.get(0), BatchStatus.FAILED)); - when(this.jobExplorer.getJobInstances("simpleJob", 0, 1)).thenReturn(this.jobInstanceList); - when(this.jobExplorer.getJobExecutions(any())).thenReturn(this.jobExecutionList); - initializeForNextJobParameters(); - this.parametersBuilder.addLong("NON_IDENTIFYING_LONG", 1L, false); - this.parametersBuilder.getNextJobParameters(this.job); - baseJobParametersVerify(this.parametersBuilder.toJobParameters(), 5); - } - - @Test - void testGetNextJobParametersNoPreviousExecution() { - this.job.setJobParametersIncrementer(new RunIdIncrementer()); - this.jobInstanceList.add(new JobInstance(1L, "simpleJobInstance")); - when(this.jobExplorer.getJobInstances("simpleJob", 0, 1)).thenReturn(this.jobInstanceList); - when(this.jobExplorer.getJobExecutions(any())).thenReturn(this.jobExecutionList); - initializeForNextJobParameters(); - this.parametersBuilder.getNextJobParameters(this.job); - baseJobParametersVerify(this.parametersBuilder.toJobParameters(), 4); - } - - @Test - void testMissingJobExplorer() { - this.parametersBuilder = new JobParametersBuilder(); - assertThrows(IllegalStateException.class, () -> this.parametersBuilder.getNextJobParameters(this.job)); - } - - private void initializeForNextJobParameters() { - this.parametersBuilder.addDate("SCHEDULE_DATE", date); - this.parametersBuilder.addLong("LONG", 1L); - this.parametersBuilder.addString("STRING", "string value"); - } - - private void defaultNextJobParametersVerify(JobParameters parameters, int paramCount) { - baseJobParametersVerify(parameters, paramCount); - assertEquals(1, parameters.getLong("run.id")); - } - - private void baseJobParametersVerify(JobParameters parameters, int paramCount) { - assertEquals(date, parameters.getDate("SCHEDULE_DATE")); - assertEquals(1L, parameters.getLong("LONG").longValue()); - assertEquals("string value", parameters.getString("STRING")); - assertEquals(paramCount, parameters.getParameters().size()); - } - - private JobExecution getJobExecution(JobInstance jobInstance, BatchStatus batchStatus) { - JobExecution jobExecution = new JobExecution(jobInstance, 1L, null); - if (batchStatus != null) { - jobExecution.setStatus(batchStatus); - } - return jobExecution; - - } - } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/JobParametersTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/JobParametersTests.java index 28c70c7615..c26e860afe 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/JobParametersTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/JobParametersTests.java @@ -28,6 +28,9 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; + +import org.springframework.batch.core.job.parameters.JobParameter; +import org.springframework.batch.core.job.parameters.JobParameters; import org.springframework.util.SerializationUtils; /** diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/SpringBatchVersionTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/SpringBatchVersionTests.java index c4fe2dcbf6..ec9f904daf 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/SpringBatchVersionTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/SpringBatchVersionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2022 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. @@ -20,8 +20,13 @@ import org.junit.jupiter.api.Test; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; import org.springframework.batch.core.job.builder.JobBuilder; -import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.launch.JobOperator; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.step.builder.StepBuilder; import org.springframework.batch.repeat.RepeatStatus; @@ -47,7 +52,7 @@ public class SpringBatchVersionTests { @Autowired - private JobLauncher jobLauncher; + private JobOperator jobOperator; @Autowired private Job job; @@ -58,7 +63,7 @@ void testBatchVersionInExecutionContext() throws Exception { JobParameters jobParameters = new JobParametersBuilder().toJobParameters(); // when - JobExecution jobExecution = this.jobLauncher.run(this.job, jobParameters); + JobExecution jobExecution = this.jobOperator.start(this.job, jobParameters); // then assertNotNull(jobExecution); @@ -73,6 +78,7 @@ void testBatchVersionInExecutionContext() throws Exception { @Configuration @EnableBatchProcessing + @EnableJdbcJobRepository static class TestConfiguration { @Bean diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/SpringBeanJobTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/SpringBeanJobTests.java index c47fd72c0c..a4a38c2c2a 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/SpringBeanJobTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/SpringBeanJobTests.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. @@ -50,7 +50,7 @@ void testBeanNameWithBeanDefinition() { context.registerBeanDefinition("bean", new RootBeanDefinition(JobSupport.class, args, null)); context.refresh(); - JobSupport configuration = (JobSupport) context.getBean("bean"); + JobSupport configuration = context.getBean("bean", JobSupport.class); assertNotNull(configuration.getName()); assertEquals("foo", configuration.getName()); configuration.setBeanName("bar"); @@ -66,7 +66,7 @@ void testBeanNameWithParentBeanDefinition() { context.registerBeanDefinition("parent", new RootBeanDefinition(JobSupport.class, args, null)); context.registerBeanDefinition("bean", new ChildBeanDefinition("parent")); context.refresh(); - JobSupport configuration = (JobSupport) context.getBean("bean"); + JobSupport configuration = context.getBean("bean", JobSupport.class); assertNotNull(configuration.getName()); assertEquals("bar", configuration.getName()); configuration.setBeanName("foo"); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/StepContributionTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/StepContributionTests.java index cc39b005da..7a5269a20a 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/StepContributionTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/StepContributionTests.java @@ -17,6 +17,9 @@ import org.junit.jupiter.api.Test; +import org.springframework.batch.core.step.StepContribution; +import org.springframework.batch.core.step.StepExecution; + import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/StepExecutionTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/StepExecutionTests.java index da2589236f..966ff8a70f 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/StepExecutionTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/StepExecutionTests.java @@ -29,6 +29,13 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; + +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobInstance; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.step.StepContribution; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.step.StepSupport; import org.springframework.batch.item.ExecutionContext; import org.springframework.util.SerializationUtils; @@ -62,7 +69,7 @@ void testStepExecutionWithNullId() { } /** - * Test method for {@link org.springframework.batch.core.JobExecution#getEndTime()}. + * Test method for {@link JobExecution#getEndTime()}. */ @Test void testGetEndTime() { @@ -84,7 +91,7 @@ void testGetCreateTime() { } /** - * Test method for {@link org.springframework.batch.core.JobExecution#getStatus()}. + * Test method for {@link JobExecution#getStatus()}. */ @Test void testGetStatus() { @@ -94,7 +101,7 @@ void testGetStatus() { } /** - * Test method for {@link org.springframework.batch.core.JobExecution#getJobId()}. + * Test method for {@link JobExecution#getJobId()}. */ @Test void testGetJobId() { @@ -102,8 +109,7 @@ void testGetJobId() { } /** - * Test method for - * {@link org.springframework.batch.core.JobExecution#getExitStatus()}. + * Test method for {@link JobExecution#getExitStatus()}. */ @Test void testGetExitCode() { @@ -113,8 +119,7 @@ void testGetExitCode() { } /** - * Test method for - * {@link org.springframework.batch.core.StepExecution#getCommitCount()}. + * Test method for {@link StepExecution#getCommitCount()}. */ @Test void testGetCommitCount() { @@ -280,7 +285,7 @@ void testAddException() { } /** - * Test method for {@link org.springframework.batch.core.JobExecution#getStatus()}. + * Test method for {@link JobExecution#getStatus()}. */ @Test void testDowngradeStatus() { diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/BatchRegistrarTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/BatchRegistrarTests.java index c8ce09889a..39b848ef10 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/BatchRegistrarTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/BatchRegistrarTests.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. @@ -24,22 +24,19 @@ import org.springframework.aop.Advisor; import org.springframework.aop.framework.Advised; -import org.springframework.batch.core.DefaultJobKeyGenerator; -import org.springframework.batch.core.JobKeyGenerator; +import org.springframework.batch.core.job.DefaultJobKeyGenerator; +import org.springframework.batch.core.job.JobKeyGenerator; import org.springframework.batch.core.configuration.JobRegistry; -import org.springframework.batch.core.configuration.support.JobRegistrySmartInitializingSingleton; import org.springframework.batch.core.converter.DefaultJobParametersConverter; import org.springframework.batch.core.converter.JobParametersConverter; import org.springframework.batch.core.converter.JsonJobParametersConverter; -import org.springframework.batch.core.explore.JobExplorer; -import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.job.parameters.JobParameters; import org.springframework.batch.core.launch.JobOperator; import org.springframework.batch.core.repository.JobRepository; -import org.springframework.batch.core.repository.dao.JdbcExecutionContextDao; -import org.springframework.batch.core.repository.dao.JdbcJobExecutionDao; -import org.springframework.batch.core.repository.dao.JdbcJobInstanceDao; -import org.springframework.batch.core.repository.dao.JdbcStepExecutionDao; -import org.springframework.beans.factory.BeanCreationException; +import org.springframework.batch.core.repository.dao.jdbc.JdbcExecutionContextDao; +import org.springframework.batch.core.repository.dao.jdbc.JdbcJobExecutionDao; +import org.springframework.batch.core.repository.dao.jdbc.JdbcJobInstanceDao; +import org.springframework.batch.core.repository.dao.jdbc.JdbcStepExecutionDao; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -58,32 +55,14 @@ */ class BatchRegistrarTests { - @Test - @DisplayName("When no datasource is provided, then an BeanCreationException should be thrown") - void testMissingDataSource() { - Assertions.assertThrows(BeanCreationException.class, - () -> new AnnotationConfigApplicationContext(JobConfigurationWithoutDataSource.class)); - } - - @Test - @DisplayName("When no transaction manager is provided, then an BeanCreationException should be thrown") - void testMissingTransactionManager() { - Assertions.assertThrows(BeanCreationException.class, - () -> new AnnotationConfigApplicationContext(JobConfigurationWithoutTransactionManager.class)); - } - @Test @DisplayName("When custom beans are provided, then default ones should not be used") void testConfigurationWithUserDefinedBeans() { var context = new AnnotationConfigApplicationContext(JobConfigurationWithUserDefinedInfrastructureBeans.class); Assertions.assertTrue(Mockito.mockingDetails(context.getBean(JobRepository.class)).isMock()); - Assertions.assertTrue(Mockito.mockingDetails(context.getBean(JobExplorer.class)).isMock()); - Assertions.assertTrue(Mockito.mockingDetails(context.getBean(JobLauncher.class)).isMock()); Assertions.assertTrue(Mockito.mockingDetails(context.getBean(JobRegistry.class)).isMock()); Assertions.assertTrue(Mockito.mockingDetails(context.getBean(JobOperator.class)).isMock()); - Assertions - .assertTrue(Mockito.mockingDetails(context.getBean(JobRegistrySmartInitializingSingleton.class)).isMock()); } @Test @@ -161,21 +140,12 @@ void testDefaultInfrastructureBeansRegistration() { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(JobConfiguration.class); // when - JobLauncher jobLauncher = context.getBean(JobLauncher.class); JobRepository jobRepository = context.getBean(JobRepository.class); - JobExplorer jobExplorer = context.getBean(JobExplorer.class); - JobRegistry jobRegistry = context.getBean(JobRegistry.class); JobOperator jobOperator = context.getBean(JobOperator.class); - JobRegistrySmartInitializingSingleton jobRegistrySmartInitializingSingleton = context - .getBean(JobRegistrySmartInitializingSingleton.class); // then - Assertions.assertNotNull(jobLauncher); Assertions.assertNotNull(jobRepository); - Assertions.assertNotNull(jobExplorer); - Assertions.assertNotNull(jobRegistry); Assertions.assertNotNull(jobOperator); - Assertions.assertNotNull(jobRegistrySmartInitializingSingleton); } @Test @@ -232,23 +202,6 @@ public void testCustomJobParametersConverterConfiguration() { Assertions.assertEquals(JsonJobParametersConverter.class, jobParametersConverter.getClass()); } - @Configuration - @EnableBatchProcessing - public static class JobConfigurationWithoutDataSource { - - } - - @Configuration - @EnableBatchProcessing - public static class JobConfigurationWithoutTransactionManager { - - @Bean - public DataSource dataSource() { - return Mockito.mock(); - } - - } - @Configuration @EnableBatchProcessing public static class JobConfigurationWithUserDefinedInfrastructureBeans { @@ -258,16 +211,6 @@ public JobRepository jobRepository() { return Mockito.mock(); } - @Bean - public JobExplorer jobExplorer() { - return Mockito.mock(); - } - - @Bean - public JobLauncher jobLauncher() { - return Mockito.mock(); - } - @Bean public JobRegistry jobRegistry() { return Mockito.mock(); @@ -278,15 +221,11 @@ public JobOperator jobOperator() { return Mockito.mock(); } - @Bean - public JobRegistrySmartInitializingSingleton jobRegistrySmartInitializingSingleton() { - return Mockito.mock(); - } - } @Configuration @EnableBatchProcessing + @EnableJdbcJobRepository public static class JobConfiguration { @Bean @@ -305,7 +244,8 @@ public JdbcTransactionManager transactionManager(DataSource dataSource) { } @Configuration - @EnableBatchProcessing(dataSourceRef = "batchDataSource", transactionManagerRef = "batchTransactionManager") + @EnableBatchProcessing(transactionManagerRef = "batchTransactionManager") + @EnableJdbcJobRepository(dataSourceRef = "batchDataSource", transactionManagerRef = "batchTransactionManager") public static class JobConfigurationWithCustomBeanNames { @Bean @@ -325,6 +265,7 @@ public JdbcTransactionManager batchTransactionManager(DataSource dataSource) { @Configuration @EnableBatchProcessing + @EnableJdbcJobRepository public static class CustomJobKeyGeneratorConfiguration { @Bean @@ -345,10 +286,10 @@ public JobKeyGenerator jobKeyGenerator() { return new TestCustomJobKeyGenerator(); } - private class TestCustomJobKeyGenerator implements JobKeyGenerator { + private static class TestCustomJobKeyGenerator implements JobKeyGenerator { @Override - public String generateKey(Object source) { + public String generateKey(JobParameters source) { return "1"; } @@ -358,6 +299,7 @@ public String generateKey(Object source) { @Configuration @EnableBatchProcessing + @EnableJdbcJobRepository public static class CustomJobParametersConverterConfiguration { @Bean diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/InlineDataSourceDefinitionTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/InlineDataSourceDefinitionTests.java index 5d94d737b1..176948fb91 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/InlineDataSourceDefinitionTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/InlineDataSourceDefinitionTests.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. @@ -21,11 +21,11 @@ import org.junit.jupiter.api.Test; import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; import org.springframework.batch.core.job.builder.JobBuilder; -import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.launch.JobOperator; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.step.builder.StepBuilder; import org.springframework.batch.repeat.RepeatStatus; @@ -49,8 +49,8 @@ class InlineDataSourceDefinitionTests { void testInlineDataSourceDefinition() throws Exception { ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyJobConfiguration.class); Job job = applicationContext.getBean(Job.class); - JobLauncher jobLauncher = applicationContext.getBean(JobLauncher.class); - JobExecution jobExecution = jobLauncher.run(job, new JobParameters()); + JobOperator jobOperator = applicationContext.getBean(JobOperator.class); + JobExecution jobExecution = jobOperator.start(job, new JobParameters()); assertEquals(ExitStatus.COMPLETED, jobExecution.getExitStatus()); } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/JobBuilderConfigurationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/JobBuilderConfigurationTests.java index 15abea0a85..3d5ba56c9b 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/JobBuilderConfigurationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/JobBuilderConfigurationTests.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. @@ -21,14 +21,14 @@ 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.JobParametersBuilder; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepContribution; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.step.StepContribution; import org.springframework.batch.core.job.builder.JobBuilder; import org.springframework.batch.core.job.builder.SimpleJobBuilder; -import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.launch.JobOperator; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.scope.context.ChunkContext; import org.springframework.batch.core.step.builder.StepBuilder; @@ -86,8 +86,8 @@ private void testJob(String jobName, BatchStatus status, int stepExecutionCount, configs[0] = DataSourceConfiguration.class; AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(configs); Job job = jobName == null ? context.getBean(Job.class) : context.getBean(jobName, Job.class); - JobLauncher jobLauncher = context.getBean(JobLauncher.class); - JobExecution execution = jobLauncher.run(job, + JobOperator jobOperator = context.getBean(JobOperator.class); + JobExecution execution = jobOperator.start(job, new JobParametersBuilder().addLong("run.id", (long) (Math.random() * Long.MAX_VALUE)) .toJobParameters()); assertEquals(status, execution.getStatus()); @@ -98,6 +98,7 @@ private void testJob(String jobName, BatchStatus status, int stepExecutionCount, @Configuration @EnableBatchProcessing + @EnableJdbcJobRepository @Import(DataSourceConfiguration.class) public static class TestConfiguration { @@ -165,6 +166,7 @@ protected Step step3(JobRepository jobRepository) throws Exception { @Configuration @EnableBatchProcessing + @EnableJdbcJobRepository @Import(DataSourceConfiguration.class) public static class BeansConfigurer { diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/JobLoaderConfigurationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/JobLoaderConfigurationTests.java index d0e1bab83c..c5a056c694 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/JobLoaderConfigurationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/JobLoaderConfigurationTests.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. @@ -19,22 +19,23 @@ import jakarta.annotation.PostConstruct; +import org.junit.jupiter.api.Disabled; 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.JobParametersBuilder; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepContribution; -import org.springframework.batch.core.configuration.JobLocator; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.step.StepContribution; +import org.springframework.batch.core.configuration.JobRegistry; import org.springframework.batch.core.configuration.support.ApplicationContextFactory; import org.springframework.batch.core.configuration.support.AutomaticJobRegistrar; import org.springframework.batch.core.configuration.support.GenericApplicationContextFactory; -import org.springframework.batch.core.explore.JobExplorer; +import org.springframework.batch.core.repository.explore.JobExplorer; import org.springframework.batch.core.job.builder.JobBuilder; import org.springframework.batch.core.job.builder.SimpleJobBuilder; -import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.launch.JobOperator; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.scope.context.ChunkContext; import org.springframework.batch.core.step.builder.StepBuilder; @@ -52,6 +53,7 @@ * @author Mahmoud Ben Hassine * */ +@Disabled class JobLoaderConfigurationTests { @Test @@ -71,9 +73,9 @@ private void testJob(String jobName, BatchStatus status, int stepExecutionCount, System.arraycopy(config, 0, configs, 1, config.length); configs[0] = DataSourceConfiguration.class; AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(configs); - Job job = jobName == null ? context.getBean(Job.class) : context.getBean(JobLocator.class).getJob(jobName); - JobLauncher jobLauncher = context.getBean(JobLauncher.class); - JobExecution execution = jobLauncher.run(job, + Job job = jobName == null ? context.getBean(Job.class) : context.getBean(JobRegistry.class).getJob(jobName); + JobOperator jobOperator = context.getBean(JobOperator.class); + JobExecution execution = jobOperator.start(job, new JobParametersBuilder().addLong("run.id", (long) (Math.random() * Long.MAX_VALUE)) .toJobParameters()); assertEquals(status, execution.getStatus()); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/JobScopeConfigurationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/JobScopeConfigurationTests.java index af0b5fc1a6..1328c19622 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/JobScopeConfigurationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/JobScopeConfigurationTests.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. @@ -22,9 +22,9 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobInstance; -import org.springframework.batch.core.StepContribution; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobInstance; +import org.springframework.batch.core.step.StepContribution; import org.springframework.batch.core.scope.context.ChunkContext; import org.springframework.batch.core.scope.context.JobSynchronizationManager; import org.springframework.batch.core.step.tasklet.Tasklet; @@ -82,7 +82,7 @@ void testXmlJobScopeWithInheritance() throws Exception { context = new ClassPathXmlApplicationContext( "org/springframework/batch/core/configuration/annotation/JobScopeConfigurationTestsInheritance-context.xml"); JobSynchronizationManager.register(jobExecution); - SimpleHolder value = (SimpleHolder) context.getBean("child"); + SimpleHolder value = context.getBean("child", SimpleHolder.class); assertEquals("JOB", value.call()); } @@ -97,9 +97,9 @@ void testJobScopeWithProxyTargetClass() throws Exception { void testStepScopeXmlImportUsingNamespace() throws Exception { init(JobScopeConfigurationXmlImportUsingNamespace.class); - SimpleHolder value = (SimpleHolder) context.getBean("xmlValue"); + SimpleHolder value = context.getBean("xmlValue", SimpleHolder.class); assertEquals("JOB", value.call()); - value = (SimpleHolder) context.getBean("javaValue"); + value = context.getBean("javaValue", SimpleHolder.class); assertEquals("JOB", value.call()); } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/StepScopeConfigurationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/StepScopeConfigurationTests.java index 5774e00314..b7f60b6d2e 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/StepScopeConfigurationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/StepScopeConfigurationTests.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. @@ -22,8 +22,8 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.StepContribution; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.step.StepContribution; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.scope.context.ChunkContext; import org.springframework.batch.core.scope.context.StepSynchronizationManager; import org.springframework.batch.core.step.tasklet.Tasklet; @@ -81,7 +81,7 @@ void testXmlStepScopeWithInheritance() throws Exception { context = new ClassPathXmlApplicationContext( "org/springframework/batch/core/configuration/annotation/StepScopeConfigurationTestsInheritance-context.xml"); StepSynchronizationManager.register(stepExecution); - SimpleHolder value = (SimpleHolder) context.getBean("child"); + SimpleHolder value = context.getBean("child", SimpleHolder.class); assertEquals("STEP", value.call()); } @@ -96,9 +96,9 @@ void testStepScopeWithProxyTargetClass() throws Exception { void testStepScopeXmlImportUsingNamespace() throws Exception { init(StepScopeConfigurationXmlImportUsingNamespace.class); - SimpleHolder value = (SimpleHolder) context.getBean("xmlValue"); + SimpleHolder value = context.getBean("xmlValue", SimpleHolder.class); assertEquals("STEP", value.call()); - value = (SimpleHolder) context.getBean("javaValue"); + value = context.getBean("javaValue", SimpleHolder.class); assertEquals("STEP", value.call()); } @@ -109,9 +109,9 @@ void testStepScopeXmlImportUsingNamespace() throws Exception { public void testStepScopeUsingNamespaceAutoregisterBeans() throws Exception { init(StepScopeConfigurationTestsUsingNamespaceAutoregisterBeans.class); - ISimpleHolder value = (ISimpleHolder) context.getBean("xmlValue"); + ISimpleHolder value = context.getBean("xmlValue", ISimpleHolder.class); assertEquals("STEP", value.call()); - value = (ISimpleHolder) context.getBean("javaValue"); + value = context.getBean("javaValue", ISimpleHolder.class); assertEquals("STEP", value.call()); } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/ApplicationContextJobFactoryTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/ApplicationContextJobFactoryTests.java index e12b9e8ffd..a24e1fb372 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/ApplicationContextJobFactoryTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/ApplicationContextJobFactoryTests.java @@ -69,8 +69,8 @@ private static class TestBeanPostProcessor implements BeanPostProcessor { @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { - if (bean instanceof JobSupport) { - ((JobSupport) bean).setName("bar"); + if (bean instanceof JobSupport jobSupport) { + jobSupport.setName("bar"); } return bean; } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/AutomaticJobRegistrarContextTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/AutomaticJobRegistrarContextTests.java index 5af7467e00..240c39998e 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/AutomaticJobRegistrarContextTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/AutomaticJobRegistrarContextTests.java @@ -21,7 +21,7 @@ import java.util.Collection; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.Job; +import org.springframework.batch.core.job.Job; import org.springframework.batch.core.configuration.JobRegistry; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/AutomaticJobRegistrarTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/AutomaticJobRegistrarTests.java index 61112f4768..4d880b30e4 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/AutomaticJobRegistrarTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/AutomaticJobRegistrarTests.java @@ -17,6 +17,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -26,7 +27,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mockito; -import org.springframework.batch.core.Job; +import org.springframework.batch.core.job.Job; import org.springframework.beans.factory.BeanCreationException; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; @@ -58,7 +59,7 @@ void setUp() { @Test void testOrderedImplemented() { - assertTrue(registrar instanceof Ordered); + assertInstanceOf(Ordered.class, registrar); assertEquals(Ordered.LOWEST_PRECEDENCE, registrar.getOrder()); registrar.setOrder(1); assertEquals(1, registrar.getOrder()); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/DefaultBatchConfigurationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/DefaultBatchConfigurationTests.java index b660c40b83..e5651e9c63 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/DefaultBatchConfigurationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/DefaultBatchConfigurationTests.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. @@ -23,21 +23,17 @@ import org.junit.jupiter.api.Test; import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.configuration.JobRegistry; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.step.Step; import org.springframework.batch.core.configuration.xml.DummyJobRepository; -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.launch.JobOperator; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.step.builder.StepBuilder; import org.springframework.batch.core.step.tasklet.Tasklet; import org.springframework.batch.repeat.RepeatStatus; -import org.springframework.beans.factory.BeanCreationException; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -56,27 +52,15 @@ void testDefaultConfiguration() throws Exception { // given AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyJobConfiguration.class); Job job = context.getBean(Job.class); - JobLauncher jobLauncher = context.getBean(JobLauncher.class); + JobOperator jobOperator = context.getBean(JobOperator.class); // when - JobExecution jobExecution = jobLauncher.run(job, new JobParameters()); + JobExecution jobExecution = jobOperator.start(job, new JobParameters()); // then Assertions.assertEquals(ExitStatus.COMPLETED, jobExecution.getExitStatus()); } - @Test - void testConfigurationWithoutDataSource() { - Assertions.assertThrows(BeanCreationException.class, - () -> new AnnotationConfigApplicationContext(MyJobConfigurationWithoutDataSource.class)); - } - - @Test - void testConfigurationWithoutTransactionManager() { - Assertions.assertThrows(BeanCreationException.class, - () -> new AnnotationConfigApplicationContext(MyJobConfigurationWithoutTransactionManager.class)); - } - @Test void testConfigurationWithCustomInfrastructureBean() { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( @@ -85,9 +69,6 @@ void testConfigurationWithCustomInfrastructureBean() { Assertions.assertEquals(1, jobRepositories.size()); JobRepository jobRepository = jobRepositories.entrySet().iterator().next().getValue(); Assertions.assertInstanceOf(DummyJobRepository.class, jobRepository); - Map jobRegistrySmartInitializingSingletonMap = context - .getBeansOfType(JobRegistrySmartInitializingSingleton.class); - Assertions.assertEquals(1, jobRegistrySmartInitializingSingletonMap.size()); } @Test @@ -96,35 +77,16 @@ void testDefaultInfrastructureBeansRegistration() { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyJobConfiguration.class); // when - JobLauncher jobLauncher = context.getBean(JobLauncher.class); JobRepository jobRepository = context.getBean(JobRepository.class); - JobExplorer jobExplorer = context.getBean(JobExplorer.class); - JobRegistry jobRegistry = context.getBean(JobRegistry.class); JobOperator jobOperator = context.getBean(JobOperator.class); - JobRegistrySmartInitializingSingleton jobRegistrySmartInitializingSingleton = context - .getBean(JobRegistrySmartInitializingSingleton.class); // then - Assertions.assertNotNull(jobLauncher); Assertions.assertNotNull(jobRepository); - Assertions.assertNotNull(jobExplorer); - Assertions.assertNotNull(jobRegistry); Assertions.assertNotNull(jobOperator); - Assertions.assertNotNull(jobRegistrySmartInitializingSingleton); - } - - @Configuration - static class MyJobConfigurationWithoutDataSource extends DefaultBatchConfiguration { - } @Configuration - static class MyJobConfigurationWithoutTransactionManager extends DefaultBatchConfiguration { - - } - - @Configuration - static class MyJobConfiguration extends DefaultBatchConfiguration { + static class MyJobConfiguration extends JdbcDefaultBatchConfiguration { @Bean public Step myStep(JobRepository jobRepository, PlatformTransactionManager transactionManager) { @@ -161,13 +123,6 @@ public JobRepository jobRepository() { return new DummyJobRepository(); } - @Bean - public JobRegistrySmartInitializingSingleton jobRegistrySmartInitializingSingleton(JobRegistry jobRegistry) { - JobRegistrySmartInitializingSingleton smartInitializingSingleton = new JobRegistrySmartInitializingSingleton(); - smartInitializingSingleton.setJobRegistry(jobRegistry); - return smartInitializingSingleton; - } - } } \ No newline at end of file diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/DefaultJobLoaderTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/DefaultJobLoaderTests.java index e0c3802992..84f13be9eb 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/DefaultJobLoaderTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/DefaultJobLoaderTests.java @@ -24,11 +24,11 @@ import java.util.Map; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobParametersIncrementer; -import org.springframework.batch.core.JobParametersValidator; -import org.springframework.batch.core.Step; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParametersIncrementer; +import org.springframework.batch.core.job.parameters.JobParametersValidator; +import org.springframework.batch.core.step.Step; import org.springframework.batch.core.configuration.DuplicateJobException; import org.springframework.batch.core.configuration.JobRegistry; import org.springframework.batch.core.configuration.StepRegistry; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/GenericApplicationContextFactoryTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/GenericApplicationContextFactoryTests.java index afc3b6dd75..02cdf62588 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/GenericApplicationContextFactoryTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/GenericApplicationContextFactoryTests.java @@ -23,7 +23,7 @@ import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.Job; +import org.springframework.batch.core.job.Job; import org.springframework.batch.core.job.JobSupport; import org.springframework.beans.BeansException; import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor; @@ -189,18 +189,6 @@ void testDifferentResourceTypes() { assertThrows(IllegalArgumentException.class, factory::createApplicationContext); } - @Test - void testPackageScanning() { - GenericApplicationContextFactory factory = new GenericApplicationContextFactory( - "org.springframework.batch.core.configuration.support"); - ConfigurableApplicationContext context = factory.createApplicationContext(); - - assertEquals(context.getBean("bean1"), "bean1"); - assertEquals(context.getBean("bean2"), "bean2"); - assertEquals(context.getBean("bean3"), "bean3"); - assertEquals(context.getBean("bean4"), "bean4"); - } - @Test void testMultipleConfigurationClasses() { GenericApplicationContextFactory factory = new GenericApplicationContextFactory(Configuration1.class, diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/GroupAwareJobTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/GroupAwareJobTests.java index ad1af070fe..ae5286866b 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/GroupAwareJobTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/GroupAwareJobTests.java @@ -18,7 +18,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.Job; +import org.springframework.batch.core.job.Job; import org.springframework.batch.core.job.JobSupport; /** diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/JobFactoryRegistrationListenerTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/JobFactoryRegistrationListenerTests.java index d834a3e981..e55894c0ac 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/JobFactoryRegistrationListenerTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/JobFactoryRegistrationListenerTests.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. @@ -18,11 +18,13 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.Job; +import org.springframework.batch.core.job.Job; import org.springframework.batch.core.configuration.JobFactory; +import org.springframework.batch.core.test.repository.JobSupport; /** * @author Dave Syer + * @author Mahmoud Ben Hassine * */ class JobFactoryRegistrationListenerTests { @@ -37,7 +39,7 @@ void testBind() throws Exception { listener.bind(new JobFactory() { @Override public Job createJob() { - return null; + return new JobSupport("foo"); } @Override @@ -54,7 +56,7 @@ void testUnbind() throws Exception { listener.unbind(new JobFactory() { @Override public Job createJob() { - return null; + return new JobSupport("foo"); } @Override 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/support/JobRegistryIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/JobRegistryIntegrationTests.java index 7586b62ebe..3f87b9f1fc 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/JobRegistryIntegrationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/JobRegistryIntegrationTests.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. @@ -18,13 +18,14 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.Job; +import org.springframework.batch.core.job.Job; import org.springframework.batch.core.configuration.JobRegistry; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; /** * @author Dave Syer + * @author Mahmoud Ben Hassine * */ @SpringJUnitConfig diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/JobRegistrySmartInitializingSingletonTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/JobRegistrySmartInitializingSingletonTests.java index f6db1e0187..d99bbfda65 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/JobRegistrySmartInitializingSingletonTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/JobRegistrySmartInitializingSingletonTests.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. @@ -20,17 +20,15 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.Job; +import org.springframework.batch.core.job.Job; import org.springframework.batch.core.configuration.DuplicateJobException; import org.springframework.batch.core.configuration.JobRegistry; import org.springframework.batch.core.job.JobSupport; import org.springframework.beans.FatalBeanException; import org.springframework.beans.factory.ListableBeanFactory; -import org.springframework.context.support.ClassPathXmlApplicationContext; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertInstanceOf; -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.lenient; @@ -40,6 +38,7 @@ * @author Henning Pöttker * @author Mahmoud Ben Hassine */ +@SuppressWarnings("removal") class JobRegistrySmartInitializingSingletonTests { private final JobRegistry jobRegistry = new MapJobRegistry(); @@ -95,28 +94,4 @@ void testUnregisterOnDestroy() throws Exception { assertTrue(jobRegistry.getJobNames().isEmpty()); } - @Test - void testExecutionWithApplicationContext() throws Exception { - var context = new ClassPathXmlApplicationContext("test-context-with-smart-initializing-singleton.xml", - getClass()); - var registry = context.getBean("registry", JobRegistry.class); - Collection jobNames = registry.getJobNames(); - String[] names = context.getBeanNamesForType(JobSupport.class); - int count = names.length; - // Each concrete bean of type JobConfiguration is registered... - assertEquals(count, jobNames.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/support/MapJobRegistryTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/MapJobRegistryTests.java index d35c5ff7d9..a40965f599 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/MapJobRegistryTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/MapJobRegistryTests.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,21 +15,19 @@ */ package org.springframework.batch.core.configuration.support; -import java.util.Collection; - import org.junit.jupiter.api.Test; +import org.springframework.batch.core.job.Job; import org.springframework.batch.core.configuration.DuplicateJobException; -import org.springframework.batch.core.configuration.JobFactory; import org.springframework.batch.core.job.JobSupport; import org.springframework.batch.core.launch.NoSuchJobException; -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 static org.junit.jupiter.api.Assertions.*; /** * @author Dave Syer + * @author Mahmoud Ben Hassine * */ class MapJobRegistryTests { @@ -38,7 +36,7 @@ class MapJobRegistryTests { @Test void testUnregister() throws Exception { - registry.register(new ReferenceJobFactory(new JobSupport("foo"))); + registry.register(new JobSupport("foo")); assertNotNull(registry.getJob("foo")); registry.unregister("foo"); Exception exception = assertThrows(NoSuchJobException.class, () -> registry.getJob("foo")); @@ -47,28 +45,30 @@ void testUnregister() throws Exception { @Test void testReplaceDuplicateConfiguration() throws Exception { - registry.register(new ReferenceJobFactory(new JobSupport("foo"))); - JobFactory jobFactory = new ReferenceJobFactory(new JobSupport("foo")); - Exception exception = assertThrows(DuplicateJobException.class, () -> registry.register(jobFactory)); + registry.register(new JobSupport("foo")); + Job job = new JobSupport("foo"); + Exception exception = assertThrows(DuplicateJobException.class, () -> registry.register(job)); assertTrue(exception.getMessage().contains("foo")); } @Test void testRealDuplicateConfiguration() throws Exception { - JobFactory jobFactory = new ReferenceJobFactory(new JobSupport("foo")); - registry.register(jobFactory); - Exception exception = assertThrows(DuplicateJobException.class, () -> registry.register(jobFactory)); + Job job = new JobSupport("foo"); + registry.register(job); + Exception exception = assertThrows(DuplicateJobException.class, () -> registry.register(job)); assertTrue(exception.getMessage().contains("foo")); } @Test void testGetJobConfigurations() throws Exception { - JobFactory jobFactory = new ReferenceJobFactory(new JobSupport("foo")); - registry.register(jobFactory); - registry.register(new ReferenceJobFactory(new JobSupport("bar"))); + Job job1 = new JobSupport("foo"); + Job job2 = new JobSupport("bar"); + registry.register(job1); + registry.register(job2); Collection configurations = registry.getJobNames(); assertEquals(2, configurations.size()); - assertTrue(configurations.contains(jobFactory.getJobName())); + assertTrue(configurations.contains(job1.getName())); + assertTrue(configurations.contains(job2.getName())); } } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/MapStepRegistryTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/MapStepRegistryTests.java index 394132cb31..518265fde1 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/MapStepRegistryTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/MapStepRegistryTests.java @@ -23,7 +23,7 @@ import java.util.HashSet; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.Step; +import org.springframework.batch.core.step.Step; import org.springframework.batch.core.configuration.DuplicateJobException; import org.springframework.batch.core.configuration.StepRegistry; import org.springframework.batch.core.launch.NoSuchJobException; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/AbstractJobParserTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/AbstractJobParserTests.java index d22a663a74..14dcd6ce6c 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/AbstractJobParserTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/AbstractJobParserTests.java @@ -19,10 +19,10 @@ import org.junit.jupiter.api.BeforeEach; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobParametersBuilder; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException; import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException; import org.springframework.batch.core.repository.JobRepository; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/BranchStepJobParserTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/BranchStepJobParserTests.java index dfbc44e4aa..7ca3c6764d 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/BranchStepJobParserTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/BranchStepJobParserTests.java @@ -23,10 +23,10 @@ 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.StepExecution; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.repository.JobRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/ChunkElementParserTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/ChunkElementParserTests.java index c973344bd6..9ae42b196b 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/ChunkElementParserTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/ChunkElementParserTests.java @@ -22,7 +22,7 @@ import org.junit.jupiter.api.Test; -import org.springframework.batch.core.Step; +import org.springframework.batch.core.step.Step; import org.springframework.batch.core.step.item.SimpleChunkProcessor; import org.springframework.batch.core.step.skip.SkipPolicy; import org.springframework.batch.core.step.tasklet.TaskletStep; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/DecisionJobParserTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/DecisionJobParserTests.java index f8b89f76b0..e06c0d9b41 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/DecisionJobParserTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/DecisionJobParserTests.java @@ -20,10 +20,10 @@ 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.StepExecution; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.job.flow.FlowExecutionStatus; import org.springframework.batch.core.job.flow.JobExecutionDecider; import org.springframework.batch.core.repository.JobRepository; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/DefaultFailureJobParserTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/DefaultFailureJobParserTests.java index b50661c214..8121054513 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/DefaultFailureJobParserTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/DefaultFailureJobParserTests.java @@ -21,8 +21,8 @@ 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.batch.core.job.JobExecution; +import org.springframework.batch.core.step.StepExecution; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; /** diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/DefaultSuccessJobParserTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/DefaultSuccessJobParserTests.java index 3859bf4968..842e0df10b 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/DefaultSuccessJobParserTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/DefaultSuccessJobParserTests.java @@ -20,8 +20,8 @@ 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.batch.core.job.JobExecution; +import org.springframework.batch.core.step.StepExecution; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; /** diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/DefaultUnknownJobParserTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/DefaultUnknownJobParserTests.java index 8a6ee6d6cd..c768f78760 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/DefaultUnknownJobParserTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/DefaultUnknownJobParserTests.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. @@ -20,9 +20,9 @@ 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.batch.core.StepExecutionListener; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.step.StepExecution; +import org.springframework.batch.core.listener.StepExecutionListener; import org.springframework.lang.Nullable; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/DummyChunkListener.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/DummyChunkListener.java index 88dc428693..89f2df5f28 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/DummyChunkListener.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/DummyChunkListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2021 the original author or authors. + * Copyright 2021-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,7 +15,7 @@ */ package org.springframework.batch.core.configuration.xml; -import org.springframework.batch.core.ChunkListener; +import org.springframework.batch.core.listener.ChunkListener; /** * @author Mahmoud Ben Hassine diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/DummyJobExecutionListener.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/DummyJobExecutionListener.java index 81898be730..350cbf4a7b 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/DummyJobExecutionListener.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/DummyJobExecutionListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2021 the original author or authors. + * Copyright 2021-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,7 +15,7 @@ */ package org.springframework.batch.core.configuration.xml; -import org.springframework.batch.core.JobExecutionListener; +import org.springframework.batch.core.listener.JobExecutionListener; /** * @author Mahmoud Ben Hassine diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/DummyJobRepository.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/DummyJobRepository.java index b6745f7c19..a79f2af2c9 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/DummyJobRepository.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/DummyJobRepository.java @@ -17,10 +17,10 @@ import java.util.Collection; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobInstance; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobInstance; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException; import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException; import org.springframework.batch.core.repository.JobRepository; @@ -81,6 +81,7 @@ public long getStepExecutionCount(JobInstance jobInstance, String stepName) { return 0; } + @SuppressWarnings("removal") @Override public boolean isJobInstanceExists(String jobName, JobParameters jobParameters) { return false; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/DummyStep.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/DummyStep.java index 8b59289a05..d3c99b12c6 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/DummyStep.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/DummyStep.java @@ -15,9 +15,9 @@ */ package org.springframework.batch.core.configuration.xml; -import org.springframework.batch.core.JobInterruptedException; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobInterruptedException; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.step.StepExecution; import org.springframework.beans.factory.BeanNameAware; /** diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/DummyStepExecutionListener.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/DummyStepExecutionListener.java index 2479d50856..da077ce582 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/DummyStepExecutionListener.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/DummyStepExecutionListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2021 the original author or authors. + * Copyright 2021-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,7 +15,7 @@ */ package org.springframework.batch.core.configuration.xml; -import org.springframework.batch.core.StepExecutionListener; +import org.springframework.batch.core.listener.StepExecutionListener; /** * @author Mahmoud Ben Hassine diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/DummyTasklet.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/DummyTasklet.java index aefe4480b6..4cfc78c19a 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/DummyTasklet.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/DummyTasklet.java @@ -15,7 +15,7 @@ */ package org.springframework.batch.core.configuration.xml; -import org.springframework.batch.core.StepContribution; +import org.springframework.batch.core.step.StepContribution; import org.springframework.batch.core.scope.context.ChunkContext; import org.springframework.batch.core.step.tasklet.Tasklet; import org.springframework.batch.repeat.RepeatStatus; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/EndTransitionDefaultStatusJobParserTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/EndTransitionDefaultStatusJobParserTests.java index 5a1ee0ca6d..2aa37f0fca 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/EndTransitionDefaultStatusJobParserTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/EndTransitionDefaultStatusJobParserTests.java @@ -21,8 +21,8 @@ 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.batch.core.job.JobExecution; +import org.springframework.batch.core.step.StepExecution; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; /** diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/EndTransitionJobParserTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/EndTransitionJobParserTests.java index 5cf04b2469..4452dd9114 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/EndTransitionJobParserTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/EndTransitionJobParserTests.java @@ -22,8 +22,8 @@ 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.batch.core.job.JobExecution; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/FailTransitionDefaultStatusJobParserTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/FailTransitionDefaultStatusJobParserTests.java index 17fabe7979..7475838888 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/FailTransitionDefaultStatusJobParserTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/FailTransitionDefaultStatusJobParserTests.java @@ -21,8 +21,8 @@ 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.batch.core.job.JobExecution; +import org.springframework.batch.core.step.StepExecution; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; /** diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/FailTransitionJobParserTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/FailTransitionJobParserTests.java index aae2910a6b..aad0d7f89c 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/FailTransitionJobParserTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/FailTransitionJobParserTests.java @@ -21,8 +21,8 @@ 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.batch.core.job.JobExecution; +import org.springframework.batch.core.step.StepExecution; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; /** diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/FailingTasklet.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/FailingTasklet.java index 3935809b29..8c346f3a72 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/FailingTasklet.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/FailingTasklet.java @@ -15,7 +15,7 @@ */ package org.springframework.batch.core.configuration.xml; -import org.springframework.batch.core.StepContribution; +import org.springframework.batch.core.step.StepContribution; import org.springframework.batch.core.scope.context.ChunkContext; import org.springframework.batch.repeat.RepeatStatus; import org.springframework.lang.Nullable; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/FlowJobParserTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/FlowJobParserTests.java index f204a28825..ba221ca4cf 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/FlowJobParserTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/FlowJobParserTests.java @@ -23,10 +23,10 @@ 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.StepExecution; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.repository.JobRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/FlowStepParserTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/FlowStepParserTests.java index 2ee4e500a0..97a55e82cf 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/FlowStepParserTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/FlowStepParserTests.java @@ -23,10 +23,10 @@ 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.StepExecution; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.job.flow.FlowExecutionStatus; import org.springframework.batch.core.job.flow.JobExecutionDecider; import org.springframework.batch.core.repository.JobRepository; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/InlineItemHandlerParserTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/InlineItemHandlerParserTests.java index 5acc06b074..2153226933 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/InlineItemHandlerParserTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/InlineItemHandlerParserTests.java @@ -22,8 +22,8 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.scope.context.StepSynchronizationManager; import org.springframework.batch.item.ItemReader; import org.springframework.batch.item.adapter.ItemProcessorAdapter; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/InterruptibleTasklet.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/InterruptibleTasklet.java index 54ad4111a7..f25827d724 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/InterruptibleTasklet.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/InterruptibleTasklet.java @@ -15,7 +15,7 @@ */ package org.springframework.batch.core.configuration.xml; -import org.springframework.batch.core.StepContribution; +import org.springframework.batch.core.step.StepContribution; import org.springframework.batch.core.scope.context.ChunkContext; import org.springframework.batch.repeat.RepeatStatus; import org.springframework.lang.Nullable; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/JobExecutionListenerMethodAttributeParserTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/JobExecutionListenerMethodAttributeParserTests.java index 2814f7bcb1..81d7fe56fc 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/JobExecutionListenerMethodAttributeParserTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/JobExecutionListenerMethodAttributeParserTests.java @@ -18,9 +18,9 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobParametersBuilder; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; import org.springframework.batch.core.repository.JobRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/JobExecutionListenerParserTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/JobExecutionListenerParserTests.java index 8f9d3a195d..ab1475053d 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/JobExecutionListenerParserTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/JobExecutionListenerParserTests.java @@ -18,9 +18,9 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobParametersBuilder; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; import org.springframework.batch.core.annotation.AfterJob; import org.springframework.batch.core.annotation.BeforeJob; import org.springframework.batch.core.repository.JobRepository; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/JobParserParentAttributeTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/JobParserParentAttributeTests.java index d04c078171..51aa57ee1e 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/JobParserParentAttributeTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/JobParserParentAttributeTests.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. @@ -23,8 +23,8 @@ import org.junit.jupiter.api.Test; import org.springframework.aop.framework.Advised; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobExecutionListener; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.listener.JobExecutionListener; import org.springframework.batch.core.job.AbstractJob; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.repository.support.SimpleJobRepository; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/JobParserValidatorTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/JobParserValidatorTests.java index 54e3b79887..9fbb1d7188 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/JobParserValidatorTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/JobParserValidatorTests.java @@ -23,12 +23,12 @@ import java.util.Collection; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.JobParametersInvalidException; -import org.springframework.batch.core.JobParametersValidator; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersInvalidException; +import org.springframework.batch.core.job.parameters.JobParametersValidator; import org.springframework.batch.core.job.AbstractJob; -import org.springframework.batch.core.job.DefaultJobParametersValidator; +import org.springframework.batch.core.job.parameters.DefaultJobParametersValidator; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/JobRegistryJobParserTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/JobRegistryJobParserTests.java index 9c6f4cf623..40f3a07ec9 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/JobRegistryJobParserTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/JobRegistryJobParserTests.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. @@ -18,8 +18,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.configuration.ListableJobLocator; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.configuration.JobRegistry; import org.springframework.beans.BeansException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; @@ -34,7 +34,7 @@ public class JobRegistryJobParserTests implements ApplicationContextAware { @Autowired - private ListableJobLocator jobRegistry; + private JobRegistry jobRegistry; private ApplicationContext applicationContext; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/JobStepParserTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/JobStepParserTests.java index dff6c73368..f96b5b9d91 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/JobStepParserTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/JobStepParserTests.java @@ -23,10 +23,10 @@ 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.StepExecution; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.repository.JobRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/NameStoringTasklet.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/NameStoringTasklet.java index 9299154c38..aa30a73bac 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/NameStoringTasklet.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/NameStoringTasklet.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2021 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. @@ -17,9 +17,9 @@ import java.util.List; -import org.springframework.batch.core.StepContribution; -import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.StepExecutionListener; +import org.springframework.batch.core.step.StepContribution; +import org.springframework.batch.core.step.StepExecution; +import org.springframework.batch.core.listener.StepExecutionListener; import org.springframework.batch.core.scope.context.ChunkContext; import org.springframework.batch.core.step.tasklet.Tasklet; import org.springframework.batch.repeat.RepeatStatus; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/NamespacePrefixedJobParserTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/NamespacePrefixedJobParserTests.java index f097fb88ce..75ed2061f9 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/NamespacePrefixedJobParserTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/NamespacePrefixedJobParserTests.java @@ -20,9 +20,9 @@ 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.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; import org.springframework.batch.core.repository.JobRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/NextAttributeJobParserTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/NextAttributeJobParserTests.java index d510ca3f4c..cee92506e3 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/NextAttributeJobParserTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/NextAttributeJobParserTests.java @@ -21,8 +21,8 @@ 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.batch.core.job.JobExecution; +import org.springframework.batch.core.step.StepExecution; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; /** diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/NextAttributeUnknownJobParserTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/NextAttributeUnknownJobParserTests.java index 3720442df6..d415a4183a 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/NextAttributeUnknownJobParserTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/NextAttributeUnknownJobParserTests.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. @@ -20,9 +20,9 @@ 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.batch.core.StepExecutionListener; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.step.StepExecution; +import org.springframework.batch.core.listener.StepExecutionListener; import org.springframework.lang.Nullable; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/NoopTasklet.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/NoopTasklet.java index 25e37694e8..91821305ec 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/NoopTasklet.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/NoopTasklet.java @@ -16,7 +16,7 @@ package org.springframework.batch.core.configuration.xml; import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.StepContribution; +import org.springframework.batch.core.step.StepContribution; import org.springframework.batch.core.scope.context.ChunkContext; import org.springframework.batch.repeat.RepeatStatus; import org.springframework.lang.Nullable; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/OneStepJobParserTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/OneStepJobParserTests.java index f1a470d2fc..cbafd2bf9e 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/OneStepJobParserTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/OneStepJobParserTests.java @@ -20,9 +20,9 @@ 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.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; import org.springframework.batch.core.repository.JobRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/ParentStepFactoryBeanParserTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/ParentStepFactoryBeanParserTests.java index 9913385164..72042011c9 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/ParentStepFactoryBeanParserTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/ParentStepFactoryBeanParserTests.java @@ -19,7 +19,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.Step; +import org.springframework.batch.core.step.Step; import org.springframework.batch.core.step.item.FaultTolerantChunkProcessor; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/PartitionStepParserTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/PartitionStepParserTests.java index 263a59b701..84ee4830e6 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/PartitionStepParserTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/PartitionStepParserTests.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. @@ -28,14 +28,14 @@ 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.StepExecution; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.partition.PartitionHandler; import org.springframework.batch.core.partition.StepExecutionSplitter; -import org.springframework.batch.core.partition.support.PartitionStep; -import org.springframework.batch.core.partition.support.StepExecutionAggregator; +import org.springframework.batch.core.partition.PartitionStep; +import org.springframework.batch.core.partition.StepExecutionAggregator; import org.springframework.batch.core.partition.support.TaskExecutorPartitionHandler; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.step.tasklet.TaskletStep; @@ -145,7 +145,7 @@ void testNestedPartitionStepStepReference() throws Throwable { String stepExecutionName = se.getStepName(); // the partitioned step if (stepExecutionName.equalsIgnoreCase("j3s1")) { - PartitionStep partitionStep = (PartitionStep) this.applicationContext.getBean(stepExecutionName); + PartitionStep partitionStep = this.applicationContext.getBean(stepExecutionName, PartitionStep.class); // prove that the reference in the {@link // TaskExecutorPartitionHandler} is the step configured inline TaskExecutorPartitionHandler taskExecutorPartitionHandler = accessPrivateField(partitionStep, @@ -184,7 +184,7 @@ void testNestedPartitionStep() throws Throwable { String stepExecutionName = se.getStepName(); if (stepExecutionName.equalsIgnoreCase("j4s1")) { // the partitioned // step - PartitionStep partitionStep = (PartitionStep) this.applicationContext.getBean(stepExecutionName); + PartitionStep partitionStep = this.applicationContext.getBean(stepExecutionName, PartitionStep.class); // prove that the reference in the {@link // TaskExecutorPartitionHandler} is the step configured inline diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/PartitionStepWithFlowParserTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/PartitionStepWithFlowParserTests.java index a2391387a4..3cfddf378c 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/PartitionStepWithFlowParserTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/PartitionStepWithFlowParserTests.java @@ -25,10 +25,10 @@ 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.JobParametersBuilder; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.job.flow.FlowExecutionStatus; import org.springframework.batch.core.job.flow.JobExecutionDecider; import org.springframework.batch.core.repository.JobRepository; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/PartitionStepWithLateBindingParserTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/PartitionStepWithLateBindingParserTests.java index ee6c0fb2c5..42eb76f253 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/PartitionStepWithLateBindingParserTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/PartitionStepWithLateBindingParserTests.java @@ -25,10 +25,10 @@ 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.JobParametersBuilder; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.repository.JobRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/PartitionStepWithNonDefaultTransactionManagerParserTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/PartitionStepWithNonDefaultTransactionManagerParserTests.java index 9508171ecd..5fd3030261 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/PartitionStepWithNonDefaultTransactionManagerParserTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/PartitionStepWithNonDefaultTransactionManagerParserTests.java @@ -20,9 +20,9 @@ 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.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; import org.springframework.batch.core.repository.JobRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/RepositoryJobParserTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/RepositoryJobParserTests.java index 0a8cad596e..75f56c10f8 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/RepositoryJobParserTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/RepositoryJobParserTests.java @@ -20,9 +20,9 @@ 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.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; import org.springframework.batch.core.repository.JobRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/SplitDifferentResultsFailFirstJobParserTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/SplitDifferentResultsFailFirstJobParserTests.java index 79e04e3a94..c9ec64a409 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/SplitDifferentResultsFailFirstJobParserTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/SplitDifferentResultsFailFirstJobParserTests.java @@ -20,8 +20,8 @@ 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.batch.core.job.JobExecution; +import org.springframework.batch.core.step.StepExecution; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; /** diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/SplitDifferentResultsFailSecondJobParserTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/SplitDifferentResultsFailSecondJobParserTests.java index 97ece09a08..a4595bf6bc 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/SplitDifferentResultsFailSecondJobParserTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/SplitDifferentResultsFailSecondJobParserTests.java @@ -21,8 +21,8 @@ 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.batch.core.job.JobExecution; +import org.springframework.batch.core.step.StepExecution; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; /** diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/SplitInterruptedJobParserTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/SplitInterruptedJobParserTests.java index 202febb595..3531792e40 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/SplitInterruptedJobParserTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/SplitInterruptedJobParserTests.java @@ -21,8 +21,8 @@ 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.batch.core.job.JobExecution; +import org.springframework.batch.core.step.StepExecution; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; /** diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/SplitJobParserTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/SplitJobParserTests.java index 85240859b7..21efe906c9 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/SplitJobParserTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/SplitJobParserTests.java @@ -23,9 +23,9 @@ 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.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.step.StepLocator; import org.springframework.beans.factory.annotation.Autowired; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/SplitNestedJobParserTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/SplitNestedJobParserTests.java index ed59f4cb29..a092b10d46 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/SplitNestedJobParserTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/SplitNestedJobParserTests.java @@ -20,9 +20,9 @@ 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.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; import org.springframework.batch.core.repository.JobRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StepListenerInStepParserTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StepListenerInStepParserTests.java index caf6f2f99c..c42c54caee 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StepListenerInStepParserTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StepListenerInStepParserTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 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. @@ -23,8 +23,8 @@ import org.junit.jupiter.api.Test; import org.springframework.aop.framework.Advised; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepListener; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.listener.StepListener; import org.springframework.batch.core.listener.ItemListenerSupport; import org.springframework.batch.core.step.tasklet.TaskletStep; import org.springframework.beans.factory.BeanFactory; @@ -45,7 +45,7 @@ class StepListenerInStepParserTests { @Test void testListenersAtStepLevel() throws Exception { - Step step = (Step) beanFactory.getBean("s1"); + Step step = beanFactory.getBean("s1", Step.class); List list = getListeners(step); assertEquals(1, list.size()); assertTrue(list.get(0) instanceof DummyStepExecutionListener); @@ -54,7 +54,7 @@ void testListenersAtStepLevel() throws Exception { @Test // TODO: BATCH-1689 (expected=BeanCreationException.class) void testListenersAtStepLevelWrongType() throws Exception { - Step step = (Step) beanFactory.getBean("s2"); + Step step = beanFactory.getBean("s2", Step.class); List list = getListeners(step); assertEquals(1, list.size()); assertTrue(list.get(0) instanceof DummyChunkListener); @@ -62,7 +62,7 @@ void testListenersAtStepLevelWrongType() throws Exception { @Test void testListenersAtTaskletAndStepLevels() throws Exception { - Step step = (Step) beanFactory.getBean("s3"); + Step step = beanFactory.getBean("s3", Step.class); List list = getListeners(step); assertEquals(2, list.size()); assertTrue(list.get(0) instanceof DummyStepExecutionListener); @@ -71,7 +71,7 @@ void testListenersAtTaskletAndStepLevels() throws Exception { @Test void testListenersAtChunkAndStepLevels() throws Exception { - Step step = (Step) beanFactory.getBean("s4"); + Step step = beanFactory.getBean("s4", Step.class); List list = getListeners(step); assertEquals(2, list.size()); assertTrue(list.get(0) instanceof DummyStepExecutionListener); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StepListenerMethodAttributeParserTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StepListenerMethodAttributeParserTests.java index 95306b992d..da931c25bc 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StepListenerMethodAttributeParserTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StepListenerMethodAttributeParserTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 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. @@ -23,8 +23,8 @@ import org.junit.jupiter.api.Test; import org.springframework.aop.framework.Advised; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepExecutionListener; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.listener.StepExecutionListener; import org.springframework.batch.core.step.tasklet.TaskletStep; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StepListenerParserTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StepListenerParserTests.java index 0a257e01d1..3c117021a7 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StepListenerParserTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StepListenerParserTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 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. @@ -23,8 +23,8 @@ import org.junit.jupiter.api.Test; import org.springframework.aop.framework.Advised; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepListener; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.listener.StepListener; import org.springframework.batch.core.listener.CompositeStepExecutionListener; import org.springframework.batch.core.listener.ItemListenerSupport; import org.springframework.batch.core.step.tasklet.TaskletStep; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StepNameTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StepNameTests.java index 6932f31c12..82550d788e 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StepNameTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StepNameTests.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. @@ -27,7 +27,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -import org.springframework.batch.core.Job; +import org.springframework.batch.core.job.Job; import org.springframework.batch.core.step.StepLocator; import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.parsing.BeanDefinitionParsingException; @@ -61,7 +61,7 @@ void testStepNames(Resource resource) throws Exception { for (String name : stepLocators.keySet()) { StepLocator stepLocator = stepLocators.get(name); Collection stepNames = stepLocator.getStepNames(); - Job job = (Job) context.getBean(name); + Job job = context.getBean(name, Job.class); String jobName = job.getName(); assertFalse(stepNames.isEmpty(), "Job has no steps: " + jobName); for (String registeredName : stepNames) { 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..c096e8ebb7 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. @@ -23,17 +23,16 @@ import org.springframework.aop.framework.Advised; import org.springframework.aop.framework.ProxyFactory; -import org.springframework.batch.core.StepExecutionListener; -import org.springframework.batch.core.StepListener; +import org.springframework.batch.core.listener.StepExecutionListener; +import org.springframework.batch.core.listener.StepListener; import org.springframework.batch.core.job.flow.FlowStep; import org.springframework.batch.core.job.flow.support.SimpleFlow; import org.springframework.batch.core.partition.PartitionHandler; -import org.springframework.batch.core.partition.support.PartitionStep; +import org.springframework.batch.core.partition.PartitionStep; import org.springframework.batch.core.partition.support.SimplePartitioner; import org.springframework.batch.core.partition.support.TaskExecutorPartitionHandler; import org.springframework.batch.core.step.JobRepositorySupport; import org.springframework.batch.core.step.StepSupport; -import org.springframework.batch.core.step.builder.StepBuilderException; import org.springframework.batch.core.step.item.ChunkOrientedTasklet; import org.springframework.batch.core.step.tasklet.TaskletStep; import org.springframework.batch.item.ItemStream; @@ -63,7 +62,7 @@ class StepParserStepFactoryBeanTests { @Test void testNothingSet() { StepParserStepFactoryBean fb = new StepParserStepFactoryBean<>(); - assertThrows(StepBuilderException.class, fb::getObject); + assertThrows(IllegalArgumentException.class, fb::getObject); } @Test @@ -98,7 +97,7 @@ void testSkipLimitSet() { StepParserStepFactoryBean fb = new StepParserStepFactoryBean<>(); fb.setName("step"); fb.setSkipLimit(5); - assertThrows(StepBuilderException.class, fb::getObject); + assertThrows(IllegalArgumentException.class, fb::getObject); } @Test @@ -239,7 +238,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 +249,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..2f61eeea5f 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. @@ -28,8 +28,8 @@ import org.junit.jupiter.api.Test; import org.springframework.aop.framework.Advised; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepExecutionListener; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.listener.StepExecutionListener; import org.springframework.batch.core.job.AbstractJob; import org.springframework.batch.core.listener.CompositeStepExecutionListener; import org.springframework.batch.core.repository.JobRepository; @@ -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 @@ -98,7 +96,7 @@ void testStepParserBeanName() { "org/springframework/batch/core/configuration/xml/StepParserBeanNameTests-context.xml"); Map beans = ctx.getBeansOfType(Step.class); assertTrue(beans.containsKey("s1"), "'s1' bean not found"); - Step s1 = (Step) ctx.getBean("s1"); + Step s1 = ctx.getBean("s1", Step.class); assertEquals("s1", s1.getName(), "wrong name"); } @@ -114,7 +112,7 @@ void testStepParserCommitInterval() throws Exception { "org/springframework/batch/core/configuration/xml/StepParserCommitIntervalTests-context.xml"); Map beans = ctx.getBeansOfType(Step.class); assertTrue(beans.containsKey("s1"), "'s1' bean not found"); - Step s1 = (Step) ctx.getBean("s1"); + Step s1 = ctx.getBean("s1", Step.class); CompletionPolicy completionPolicy = getCompletionPolicy(s1); assertTrue(completionPolicy instanceof SimpleCompletionPolicy); assertEquals(25, ReflectionTestUtils.getField(completionPolicy, "chunkSize")); @@ -126,7 +124,7 @@ void testStepParserCompletionPolicy() throws Exception { "org/springframework/batch/core/configuration/xml/StepParserCompletionPolicyTests-context.xml"); Map beans = ctx.getBeansOfType(Step.class); assertTrue(beans.containsKey("s1"), "'s1' bean not found"); - Step s1 = (Step) ctx.getBean("s1"); + Step s1 = ctx.getBean("s1", Step.class); CompletionPolicy completionPolicy = getCompletionPolicy(s1); assertTrue(completionPolicy instanceof DummyCompletionPolicy); } @@ -212,7 +210,7 @@ private void validateTransactionAttributesInherited(String stepName, Application @SuppressWarnings("unchecked") private List getListeners(String stepName, ApplicationContext ctx) throws Exception { assertTrue(ctx.containsBean(stepName)); - Step step = (Step) ctx.getBean(stepName); + Step step = ctx.getBean(stepName, Step.class); assertTrue(step instanceof TaskletStep); Object compositeListener = ReflectionTestUtils.getField(step, "stepExecutionListener"); Object composite = ReflectionTestUtils.getField(compositeListener, "list"); @@ -236,7 +234,7 @@ private StepExecutionListener getListener(String stepName, ApplicationContext ct private DefaultTransactionAttribute getTransactionAttribute(ApplicationContext ctx, String stepName) { assertTrue(ctx.containsBean(stepName)); - Step step = (Step) ctx.getBean(stepName); + Step step = ctx.getBean(stepName, Step.class); assertTrue(step instanceof TaskletStep); Object transactionAttribute = ReflectionTestUtils.getField(step, "transactionAttribute"); return (DefaultTransactionAttribute) transactionAttribute; @@ -252,7 +250,7 @@ void testInheritFromBean() { private Tasklet getTasklet(String stepName, ApplicationContext ctx) { assertTrue(ctx.containsBean(stepName)); - Step step = (Step) ctx.getBean(stepName); + Step step = ctx.getBean(stepName, Step.class); assertTrue(step instanceof TaskletStep); Object tasklet = ReflectionTestUtils.getField(step, "tasklet"); assertTrue(tasklet instanceof Tasklet); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StepWithBasicProcessTaskJobParserTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StepWithBasicProcessTaskJobParserTests.java index 2ad66f701d..829776a52f 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StepWithBasicProcessTaskJobParserTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StepWithBasicProcessTaskJobParserTests.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. @@ -23,10 +23,10 @@ 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.StepExecutionListener; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.listener.StepExecutionListener; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.item.ItemStream; import org.springframework.beans.factory.annotation.Autowired; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StepWithFaultTolerantProcessTaskJobParserTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StepWithFaultTolerantProcessTaskJobParserTests.java index 169b58aea5..3c33d19d4c 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StepWithFaultTolerantProcessTaskJobParserTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StepWithFaultTolerantProcessTaskJobParserTests.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. @@ -23,10 +23,10 @@ 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.StepExecutionListener; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.listener.StepExecutionListener; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.item.ItemStream; import org.springframework.beans.factory.annotation.Autowired; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StepWithPojoListenerJobParserTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StepWithPojoListenerJobParserTests.java index 3d50e524d0..3255444c42 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StepWithPojoListenerJobParserTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StepWithPojoListenerJobParserTests.java @@ -21,9 +21,9 @@ 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.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; import org.springframework.batch.core.repository.JobRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StepWithSimpleTaskJobParserTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StepWithSimpleTaskJobParserTests.java index bafa0abeb9..808d4e49d8 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StepWithSimpleTaskJobParserTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StepWithSimpleTaskJobParserTests.java @@ -22,10 +22,10 @@ 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.Step; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.step.Step; import org.springframework.batch.core.job.flow.FlowJob; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.step.tasklet.TaskletStep; 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..86b65cff11 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. @@ -20,8 +20,8 @@ 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.batch.core.job.JobExecution; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException; import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException; import org.springframework.batch.core.repository.JobRestartException; @@ -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..219896fd9d 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. @@ -19,8 +19,8 @@ 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.batch.core.job.JobExecution; +import org.springframework.batch.core.step.StepExecution; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; /** @@ -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..6dcee7f325 --- /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.job.JobExecution; +import org.springframework.batch.core.step.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..b0e223b724 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. @@ -19,8 +19,8 @@ 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.batch.core.job.JobExecution; +import org.springframework.batch.core.step.StepExecution; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; /** @@ -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..36dd0f289a 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. @@ -19,8 +19,8 @@ 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.batch.core.job.JobExecution; +import org.springframework.batch.core.step.StepExecution; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; /** @@ -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..098468a562 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. @@ -21,8 +21,8 @@ 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.batch.core.job.JobExecution; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.job.flow.FlowExecutionStatus; import org.springframework.batch.core.job.flow.JobExecutionDecider; import org.springframework.lang.Nullable; @@ -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/java/org/springframework/batch/core/configuration/xml/StopRestartOnCompletedStepJobParserTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StopRestartOnCompletedStepJobParserTests.java index 4fa77cfc13..c7672d1f95 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StopRestartOnCompletedStepJobParserTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StopRestartOnCompletedStepJobParserTests.java @@ -20,8 +20,8 @@ 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.batch.core.job.JobExecution; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException; import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException; import org.springframework.batch.core.repository.JobRestartException; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StopRestartOnFailedStepJobParserTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StopRestartOnFailedStepJobParserTests.java index 128e018b9b..6bf45eb632 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StopRestartOnFailedStepJobParserTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StopRestartOnFailedStepJobParserTests.java @@ -20,8 +20,8 @@ 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.batch.core.job.JobExecution; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException; import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException; import org.springframework.batch.core.repository.JobRestartException; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/TaskletParserAdapterTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/TaskletParserAdapterTests.java index 16da4b2237..495e1fdc55 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/TaskletParserAdapterTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/TaskletParserAdapterTests.java @@ -20,9 +20,9 @@ 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.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; import org.springframework.batch.core.repository.JobRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/TaskletParserBeanPropertiesTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/TaskletParserBeanPropertiesTests.java index cc19f41faf..6342f79c77 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/TaskletParserBeanPropertiesTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/TaskletParserBeanPropertiesTests.java @@ -19,10 +19,10 @@ 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.Step; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.step.Step; import org.springframework.batch.core.job.flow.FlowJob; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.step.tasklet.TaskletStep; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/TaskletStepAllowStartIfCompleteTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/TaskletStepAllowStartIfCompleteTests.java index 11823defd7..75a6d3e2ef 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/TaskletStepAllowStartIfCompleteTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/TaskletStepAllowStartIfCompleteTests.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. @@ -19,9 +19,9 @@ import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobParametersBuilder; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.step.AbstractStep; import org.springframework.beans.factory.annotation.Autowired; @@ -46,7 +46,7 @@ class TaskletStepAllowStartIfCompleteTests { @Test void test() throws Exception { // retrieve the step from the context and see that it's allow is set - AbstractStep abstractStep = (AbstractStep) context.getBean("simpleJob.step1"); + AbstractStep abstractStep = context.getBean("simpleJob.step1", AbstractStep.class); assertTrue(abstractStep.isAllowStartIfComplete()); } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/TestCustomStatusListener.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/TestCustomStatusListener.java index e60356ccbc..e5796f1525 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/TestCustomStatusListener.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/TestCustomStatusListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2009-2019 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. @@ -16,8 +16,8 @@ package org.springframework.batch.core.configuration.xml; import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.StepExecutionListener; +import org.springframework.batch.core.step.StepExecution; +import org.springframework.batch.core.listener.StepExecutionListener; import org.springframework.lang.Nullable; public class TestCustomStatusListener extends AbstractTestComponent implements StepExecutionListener { diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/TestIncrementer.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/TestIncrementer.java index 97927c9444..4bcc6957ea 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/TestIncrementer.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/TestIncrementer.java @@ -15,8 +15,8 @@ */ package org.springframework.batch.core.configuration.xml; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.JobParametersIncrementer; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersIncrementer; import org.springframework.lang.Nullable; public class TestIncrementer implements JobParametersIncrementer { diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/TestListener.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/TestListener.java index fecb31502d..c5489a053a 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/TestListener.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/TestListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2019 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. @@ -16,8 +16,8 @@ package org.springframework.batch.core.configuration.xml; import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.StepExecutionListener; +import org.springframework.batch.core.step.StepExecution; +import org.springframework.batch.core.listener.StepExecutionListener; import org.springframework.batch.core.annotation.AfterRead; import org.springframework.lang.Nullable; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/TestTasklet.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/TestTasklet.java index cab9cf1e91..4b123fe2ba 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/TestTasklet.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/TestTasklet.java @@ -15,7 +15,7 @@ */ package org.springframework.batch.core.configuration.xml; -import org.springframework.batch.core.StepContribution; +import org.springframework.batch.core.step.StepContribution; import org.springframework.batch.core.scope.context.ChunkContext; import org.springframework.batch.core.step.tasklet.Tasklet; import org.springframework.batch.repeat.RepeatStatus; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/TwoStepJobParserTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/TwoStepJobParserTests.java index c8e8d755e0..e6c63570a8 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/TwoStepJobParserTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/TwoStepJobParserTests.java @@ -20,9 +20,9 @@ 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.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; import org.springframework.batch.core.repository.JobRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/converter/DefaultJobParametersConverterTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/converter/DefaultJobParametersConverterTests.java index d39b9ad5eb..e573447179 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/converter/DefaultJobParametersConverterTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/converter/DefaultJobParametersConverterTests.java @@ -20,9 +20,9 @@ import org.junit.jupiter.api.Test; -import org.springframework.batch.core.JobParameter; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.JobParametersBuilder; +import org.springframework.batch.core.job.parameters.JobParameter; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; import org.springframework.util.StringUtils; import static org.junit.jupiter.api.Assertions.assertEquals; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/converter/JobParametersConverterSupport.java b/spring-batch-core/src/test/java/org/springframework/batch/core/converter/JobParametersConverterSupport.java index c28bdff263..6038ef378f 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/converter/JobParametersConverterSupport.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/converter/JobParametersConverterSupport.java @@ -18,9 +18,9 @@ import java.util.Map; import java.util.Properties; -import org.springframework.batch.core.JobParameter; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.JobParametersBuilder; +import org.springframework.batch.core.job.parameters.JobParameter; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; import org.springframework.lang.Nullable; public class JobParametersConverterSupport implements JobParametersConverter { diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/converter/JsonJobParametersConverterTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/converter/JsonJobParametersConverterTests.java index 82b168b059..0f60717a07 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/converter/JsonJobParametersConverterTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/converter/JsonJobParametersConverterTests.java @@ -18,7 +18,7 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.JobParameter; +import org.springframework.batch.core.job.parameters.JobParameter; /** * @author Mahmoud Ben Hassine diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/explore/support/JobExplorerFactoryBeanTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/explore/support/JobExplorerFactoryBeanTests.java index a36229a824..d8194ee137 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/explore/support/JobExplorerFactoryBeanTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/explore/support/JobExplorerFactoryBeanTests.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. @@ -17,6 +17,7 @@ import javax.sql.DataSource; +import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -24,9 +25,11 @@ import org.springframework.aop.Advisor; import org.springframework.aop.framework.Advised; -import org.springframework.batch.core.DefaultJobKeyGenerator; -import org.springframework.batch.core.JobKeyGenerator; -import org.springframework.batch.core.explore.JobExplorer; +import org.springframework.batch.core.job.DefaultJobKeyGenerator; +import org.springframework.batch.core.job.JobKeyGenerator; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.repository.explore.JobExplorer; +import org.springframework.batch.core.repository.explore.support.JobExplorerFactoryBean; import org.springframework.jdbc.core.JdbcOperations; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.test.util.ReflectionTestUtils; @@ -46,6 +49,7 @@ * @author Mahmoud Ben Hassine * */ +@SuppressWarnings("removal") class JobExplorerFactoryBeanTests { private JobExplorerFactoryBean factory; @@ -146,10 +150,10 @@ public void testCustomJobKeyGenerator() throws Exception { Assertions.assertEquals(CustomJobKeyGenerator.class, jobKeyGenerator.getClass()); } - class CustomJobKeyGenerator implements JobKeyGenerator { + static class CustomJobKeyGenerator implements JobKeyGenerator { @Override - public String generateKey(String source) { + public @NotNull String generateKey(@NotNull JobParameters source) { return "1"; } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/explore/support/SimpleJobExplorerIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/explore/support/SimpleJobExplorerIntegrationTests.java index 57f990bc21..e0808765b0 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/explore/support/SimpleJobExplorerIntegrationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/explore/support/SimpleJobExplorerIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 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. @@ -24,18 +24,19 @@ 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.JobInstance; -import org.springframework.batch.core.JobInterruptedException; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.JobParametersBuilder; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.UnexpectedJobExecutionException; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobInstance; +import org.springframework.batch.core.job.JobInterruptedException; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.step.StepExecution; +import org.springframework.batch.core.job.UnexpectedJobExecutionException; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; import org.springframework.batch.core.configuration.xml.DummyStep; -import org.springframework.batch.core.explore.JobExplorer; +import org.springframework.batch.core.repository.explore.JobExplorer; import org.springframework.batch.core.job.builder.JobBuilder; import org.springframework.batch.core.job.flow.FlowExecutionStatus; import org.springframework.batch.core.job.flow.FlowStep; @@ -43,11 +44,12 @@ import org.springframework.batch.core.job.flow.support.StateTransition; import org.springframework.batch.core.job.flow.support.state.EndState; import org.springframework.batch.core.job.flow.support.state.StepState; -import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.launch.JobOperator; import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException; import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.repository.JobRestartException; +import org.springframework.batch.core.repository.explore.support.JobExplorerFactoryBean; import org.springframework.batch.core.step.builder.StepBuilder; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; @@ -80,6 +82,7 @@ class SimpleJobExplorerIntegrationTests { */ @Configuration @EnableBatchProcessing + @EnableJdbcJobRepository static class Config { @Bean @@ -87,6 +90,7 @@ public JobExplorer jobExplorer() throws Exception { return jobExplorerFactoryBean().getObject(); } + @SuppressWarnings("removal") @Bean public JobExplorerFactoryBean jobExplorerFactoryBean() { JobExplorerFactoryBean jobExplorerFactoryBean = new JobExplorerFactoryBean(); @@ -146,7 +150,7 @@ public Job job(JobRepository jobRepository) { private FlowStep flowStep; @Autowired - private JobLauncher jobLauncher; + private JobOperator jobOperator; @Autowired private Job job; @@ -170,7 +174,7 @@ void testGetStepExecution() throws JobExecutionAlreadyRunningException, JobResta @Test void getLastJobExecutionShouldFetchStepExecutions() throws Exception { - this.jobLauncher.run(this.job, new JobParameters()); + this.jobOperator.start(this.job, new JobParameters()); JobInstance lastJobInstance = this.jobExplorer.getLastJobInstance("job"); JobExecution lastJobExecution = this.jobExplorer.getLastJobExecution(lastJobInstance); assertEquals(1, lastJobExecution.getStepExecutions().size()); @@ -186,6 +190,7 @@ void getLastJobExecutionShouldFetchStepExecutions() throws Exception { @Configuration @EnableBatchProcessing + @EnableJdbcJobRepository static class JobConfiguration { @Bean @@ -219,7 +224,7 @@ public JdbcTransactionManager transactionManager(DataSource dataSource) { void retrievedJobExecutionsShouldHaveTheirOwnParameters() throws Exception { // given ApplicationContext context = new AnnotationConfigApplicationContext(JobConfiguration.class); - JobLauncher jobLauncher = context.getBean(JobLauncher.class); + JobOperator jobOperator = context.getBean(JobOperator.class); JobExplorer jobExplorer = context.getBean(JobExplorer.class); Job job = context.getBean(Job.class); long id = 1L; @@ -231,8 +236,8 @@ void retrievedJobExecutionsShouldHaveTheirOwnParameters() throws Exception { .toJobParameters(); // when - JobExecution jobExecution1 = jobLauncher.run(job, jobParameters1); - JobExecution jobExecution2 = jobLauncher.run(job, jobParameters2); + JobExecution jobExecution1 = jobOperator.start(job, jobParameters1); + JobExecution jobExecution2 = jobOperator.start(job, jobParameters2); // then Assertions.assertEquals(jobExecution1.getJobInstance(), jobExecution2.getJobInstance()); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/explore/support/SimpleJobExplorerTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/explore/support/SimpleJobExplorerTests.java index 7e7122d0b0..49fd8b0551 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/explore/support/SimpleJobExplorerTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/explore/support/SimpleJobExplorerTests.java @@ -27,15 +27,16 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobInstance; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobInstance; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.launch.NoSuchJobException; import org.springframework.batch.core.repository.dao.ExecutionContextDao; 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.core.repository.explore.support.SimpleJobExplorer; /** * Test {@link SimpleJobExplorer}. diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/job/CompositeJobParametersValidatorTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/job/CompositeJobParametersValidatorTests.java index 93694fc764..0b9b3aceaf 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/job/CompositeJobParametersValidatorTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/job/CompositeJobParametersValidatorTests.java @@ -23,9 +23,11 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.JobParametersInvalidException; -import org.springframework.batch.core.JobParametersValidator; + +import org.springframework.batch.core.job.parameters.CompositeJobParametersValidator; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersInvalidException; +import org.springframework.batch.core.job.parameters.JobParametersValidator; class CompositeJobParametersValidatorTests { diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/job/DefaultJobParametersValidatorTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/job/DefaultJobParametersValidatorTests.java index ccd88adb99..b5e1971772 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/job/DefaultJobParametersValidatorTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/job/DefaultJobParametersValidatorTests.java @@ -16,9 +16,11 @@ package org.springframework.batch.core.job; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.JobParametersBuilder; -import org.springframework.batch.core.JobParametersInvalidException; + +import org.springframework.batch.core.job.parameters.DefaultJobParametersValidator; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; +import org.springframework.batch.core.job.parameters.JobParametersInvalidException; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertThrows; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/job/ExtendedAbstractJobTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/job/ExtendedAbstractJobTests.java index 79a5684b1f..cbcdc661bd 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/job/ExtendedAbstractJobTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/job/ExtendedAbstractJobTests.java @@ -18,15 +18,13 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.batch.core.BatchStatus; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobExecutionException; -import org.springframework.batch.core.JobInterruptedException; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.JobParametersInvalidException; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.parameters.DefaultJobParametersValidator; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersInvalidException; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.repository.JobRepository; -import org.springframework.batch.core.repository.support.JobRepositoryFactoryBean; +import org.springframework.batch.core.repository.support.JdbcJobRepositoryFactoryBean; import org.springframework.batch.core.step.StepSupport; import org.springframework.jdbc.support.JdbcTransactionManager; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; @@ -60,7 +58,7 @@ void setUp() throws Exception { .addScript("/org/springframework/batch/core/schema-drop-hsqldb.sql") .addScript("/org/springframework/batch/core/schema-hsqldb.sql") .build(); - JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean(); + JdbcJobRepositoryFactoryBean factory = new JdbcJobRepositoryFactoryBean(); factory.setDataSource(embeddedDatabase); factory.setTransactionManager(new JdbcTransactionManager(embeddedDatabase)); factory.afterPropertiesSet(); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/job/JobSupport.java b/spring-batch-core/src/test/java/org/springframework/batch/core/job/JobSupport.java index c5e32aa0f1..9a313389fb 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/job/JobSupport.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/job/JobSupport.java @@ -21,11 +21,9 @@ import java.util.List; import java.util.Map; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobParametersValidator; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.UnexpectedJobExecutionException; +import org.springframework.batch.core.job.parameters.DefaultJobParametersValidator; +import org.springframework.batch.core.job.parameters.JobParametersValidator; +import org.springframework.batch.core.step.Step; import org.springframework.batch.core.step.NoSuchStepException; import org.springframework.batch.core.step.StepLocator; import org.springframework.beans.factory.BeanNameAware; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/job/SimpleJobFailureTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/job/SimpleJobFailureTests.java index ae96ea0182..e1ec6449cd 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/job/SimpleJobFailureTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/job/SimpleJobFailureTests.java @@ -22,14 +22,11 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.batch.core.BatchStatus; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobInterruptedException; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.UnexpectedJobExecutionException; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.repository.JobRepository; -import org.springframework.batch.core.repository.support.JobRepositoryFactoryBean; +import org.springframework.batch.core.repository.support.JdbcJobRepositoryFactoryBean; import org.springframework.batch.core.step.StepSupport; import org.springframework.jdbc.support.JdbcTransactionManager; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; @@ -55,7 +52,7 @@ void init() throws Exception { .addScript("/org/springframework/batch/core/schema-drop-hsqldb.sql") .addScript("/org/springframework/batch/core/schema-hsqldb.sql") .build(); - JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean(); + JdbcJobRepositoryFactoryBean factory = new JdbcJobRepositoryFactoryBean(); factory.setDataSource(embeddedDatabase); factory.setTransactionManager(new JdbcTransactionManager(embeddedDatabase)); factory.afterPropertiesSet(); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/job/SimpleJobTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/job/SimpleJobTests.java index c850e8c77f..20180708e2 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/job/SimpleJobTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/job/SimpleJobTests.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,31 +24,19 @@ import java.util.List; import io.micrometer.core.instrument.Metrics; -import io.micrometer.core.instrument.Tag; -import io.micrometer.core.instrument.Tags; -import io.micrometer.core.instrument.observation.DefaultMeterObservationHandler; -import io.micrometer.core.tck.MeterRegistryAssert; -import io.micrometer.observation.ObservationRegistry; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; 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.JobExecutionListener; -import org.springframework.batch.core.JobInstance; -import org.springframework.batch.core.JobInterruptedException; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.JobParametersBuilder; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.UnexpectedJobExecutionException; -import org.springframework.batch.core.explore.JobExplorer; -import org.springframework.batch.core.explore.support.JobExplorerFactoryBean; -import org.springframework.batch.core.observability.BatchJobObservation; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; +import org.springframework.batch.core.listener.JobExecutionListener; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.repository.JobRepository; -import org.springframework.batch.core.repository.support.JobRepositoryFactoryBean; +import org.springframework.batch.core.repository.support.JdbcJobRepositoryFactoryBean; import org.springframework.batch.core.step.StepSupport; import org.springframework.batch.item.ExecutionContext; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; @@ -75,8 +63,6 @@ class SimpleJobTests { private JobRepository jobRepository; - private JobExplorer jobExplorer; - private final List list = new ArrayList<>(); private JobInstance jobInstance; @@ -103,24 +89,14 @@ void setUp() throws Exception { .generateUniqueName(true) .build(); JdbcTransactionManager transactionManager = new JdbcTransactionManager(embeddedDatabase); - JobRepositoryFactoryBean repositoryFactoryBean = new JobRepositoryFactoryBean(); + JdbcJobRepositoryFactoryBean repositoryFactoryBean = new JdbcJobRepositoryFactoryBean(); repositoryFactoryBean.setDataSource(embeddedDatabase); repositoryFactoryBean.setTransactionManager(transactionManager); repositoryFactoryBean.afterPropertiesSet(); this.jobRepository = repositoryFactoryBean.getObject(); - JobExplorerFactoryBean explorerFactoryBean = new JobExplorerFactoryBean(); - explorerFactoryBean.setDataSource(embeddedDatabase); - explorerFactoryBean.setTransactionManager(transactionManager); - explorerFactoryBean.afterPropertiesSet(); - this.jobExplorer = explorerFactoryBean.getObject(); job = new SimpleJob(); job.setJobRepository(jobRepository); - ObservationRegistry observationRegistry = ObservationRegistry.create(); - observationRegistry.observationConfig() - .observationHandler(new DefaultMeterObservationHandler(Metrics.globalRegistry)); - job.setObservationRegistry(observationRegistry); - step1 = new StubStep("TestStep1", jobRepository); step1.setCallback(() -> list.add("default")); step2 = new StubStep("TestStep2", jobRepository); @@ -159,7 +135,7 @@ void testGetSteps() { } /** - * Test method for {@link SimpleJob#addStep(org.springframework.batch.core.Step)}. + * Test method for {@link SimpleJob#addStep(Step)}. */ @Test void testAddStep() { @@ -178,7 +154,7 @@ void testExitStatusReturned() { Step testStep = new Step() { @Override - public void execute(StepExecution stepExecution) throws JobInterruptedException { + public void execute(StepExecution stepExecution) { stepExecution.setExitStatus(customStatus); } @@ -192,10 +168,6 @@ public int getStartLimit() { return 1; } - @Override - public boolean isAllowStartIfComplete() { - return false; - } }; List steps = new ArrayList<>(); steps.add(testStep); @@ -216,12 +188,6 @@ void testRunNormally() { assertEquals(1, step1.passedInJobContext.size()); assertFalse(step2.passedInJobContext.isEmpty()); - - // Observability - MeterRegistryAssert.assertThat(Metrics.globalRegistry) - .hasTimerWithNameAndTags(BatchJobObservation.BATCH_JOB_OBSERVATION.getName(), - Tags.of(Tag.of("error", "none"), Tag.of("spring.batch.job.name", "testJob"), - Tag.of("spring.batch.job.status", "COMPLETED"))); } @AfterEach @@ -494,10 +460,10 @@ void testGetMultipleJobParameters() throws Exception { JobExecution jobexecution = jobRepository.createJobExecution(job.getName(), firstJobParameters); job.execute(jobexecution); - List jobExecutionList = jobExplorer.getJobExecutions(jobexecution.getJobInstance()); + List jobExecutionList = jobRepository.getJobExecutions(jobexecution.getJobInstance()); - assertEquals(jobExecutionList.size(), 1); - assertEquals(jobExecutionList.get(0).getJobParameters().getString("JobExecutionParameter"), "first"); + assertEquals(1, jobExecutionList.size()); + assertEquals("first", jobExecutionList.get(0).getJobParameters().getString("JobExecutionParameter")); JobParameters secondJobParameters = new JobParametersBuilder() .addString("JobExecutionParameter", "second", false) @@ -505,11 +471,11 @@ void testGetMultipleJobParameters() throws Exception { jobexecution = jobRepository.createJobExecution(job.getName(), secondJobParameters); job.execute(jobexecution); - jobExecutionList = jobExplorer.getJobExecutions(jobexecution.getJobInstance()); + jobExecutionList = jobRepository.getJobExecutions(jobexecution.getJobInstance()); - assertEquals(jobExecutionList.size(), 2); - assertEquals(jobExecutionList.get(0).getJobParameters().getString("JobExecutionParameter"), "second"); - assertEquals(jobExecutionList.get(1).getJobParameters().getString("JobExecutionParameter"), "first"); + assertEquals(2, jobExecutionList.size()); + assertEquals("second", jobExecutionList.get(0).getJobParameters().getString("JobExecutionParameter")); + assertEquals("first", jobExecutionList.get(1).getJobParameters().getString("JobExecutionParameter")); } @@ -519,7 +485,7 @@ void testGetMultipleJobParameters() throws Exception { private void checkRepository(BatchStatus status, ExitStatus exitStatus) { assertEquals(jobInstance, this.jobRepository.getLastJobExecution(job.getName(), jobParameters).getJobInstance()); - JobExecution jobExecution = this.jobExplorer.getJobExecutions(jobInstance).get(0); + JobExecution jobExecution = this.jobRepository.getJobExecutions(jobInstance).get(0); assertEquals(jobInstance.getId(), jobExecution.getJobId()); assertEquals(status, jobExecution.getStatus()); if (exitStatus != null) { @@ -570,11 +536,11 @@ public void execute(StepExecution stepExecution) jobRepository.update(stepExecution); jobRepository.updateExecutionContext(stepExecution); - if (exception instanceof JobInterruptedException) { + if (exception instanceof JobInterruptedException jobInterruptedException) { stepExecution.setExitStatus(ExitStatus.FAILED); - stepExecution.setStatus(((JobInterruptedException) exception).getStatus()); + stepExecution.setStatus(jobInterruptedException.getStatus()); stepExecution.addFailureException(exception); - throw (JobInterruptedException) exception; + throw jobInterruptedException; } if (exception instanceof RuntimeException) { stepExecution.setExitStatus(ExitStatus.FAILED); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/job/SimpleStepHandlerTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/job/SimpleStepHandlerTests.java index 3653b305cc..8e28b15caf 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/job/SimpleStepHandlerTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/job/SimpleStepHandlerTests.java @@ -23,12 +23,10 @@ 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.JobInterruptedException; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.repository.JobRepository; -import org.springframework.batch.core.repository.support.JobRepositoryFactoryBean; +import org.springframework.batch.core.repository.support.JdbcJobRepositoryFactoryBean; import org.springframework.batch.core.step.StepSupport; import org.springframework.jdbc.support.JdbcTransactionManager; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; @@ -53,7 +51,7 @@ void setUp() throws Exception { .addScript("/org/springframework/batch/core/schema-drop-hsqldb.sql") .addScript("/org/springframework/batch/core/schema-hsqldb.sql") .build(); - JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean(); + JdbcJobRepositoryFactoryBean factory = new JdbcJobRepositoryFactoryBean(); factory.setDataSource(embeddedDatabase); factory.setTransactionManager(new JdbcTransactionManager(embeddedDatabase)); factory.afterPropertiesSet(); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/job/builder/FlowBuilderTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/job/builder/FlowBuilderTests.java index dd47b49999..88834c2c62 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/job/builder/FlowBuilderTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/job/builder/FlowBuilderTests.java @@ -21,11 +21,10 @@ import org.springframework.batch.core.BatchStatus; import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobInterruptedException; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.UnexpectedJobExecutionException; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.step.StepExecution; +import org.springframework.batch.core.job.UnexpectedJobExecutionException; import org.springframework.batch.core.job.SimpleStepHandler; import org.springframework.batch.core.job.flow.Flow; import org.springframework.batch.core.job.flow.FlowExecution; @@ -57,7 +56,7 @@ void testNext() throws Exception { .start(new JobFlowExecutor(jobRepository, new SimpleStepHandler(jobRepository), execution)); Iterator stepExecutions = execution.getStepExecutions().iterator(); - assertEquals(stepExecutions.next().getStepName(), "stepA"); + assertEquals("stepA", stepExecutions.next().getStepName()); assertFalse(stepExecutions.hasNext()); } @@ -74,9 +73,9 @@ void testMultipleNext() throws Exception { .start(new JobFlowExecutor(jobRepository, new SimpleStepHandler(jobRepository), execution)); Iterator stepExecutions = execution.getStepExecutions().iterator(); - assertEquals(stepExecutions.next().getStepName(), "stepA"); - assertEquals(stepExecutions.next().getStepName(), "stepB"); - assertEquals(stepExecutions.next().getStepName(), "stepC"); + assertEquals("stepA", stepExecutions.next().getStepName()); + assertEquals("stepB", stepExecutions.next().getStepName()); + assertEquals("stepC", stepExecutions.next().getStepName()); assertFalse(stepExecutions.hasNext()); } @@ -91,7 +90,7 @@ void testStart() throws Exception { .start(new JobFlowExecutor(jobRepository, new SimpleStepHandler(jobRepository), execution)); Iterator stepExecutions = execution.getStepExecutions().iterator(); - assertEquals(stepExecutions.next().getStepName(), "stepA"); + assertEquals("stepA", stepExecutions.next().getStepName()); assertFalse(stepExecutions.hasNext()); } @@ -106,7 +105,7 @@ void testFrom() throws Exception { .start(new JobFlowExecutor(jobRepository, new SimpleStepHandler(jobRepository), execution)); Iterator stepExecutions = execution.getStepExecutions().iterator(); - assertEquals(stepExecutions.next().getStepName(), "stepA"); + assertEquals("stepA", stepExecutions.next().getStepName()); assertFalse(stepExecutions.hasNext()); } @@ -118,23 +117,20 @@ void testTransitionOrdering() throws Exception { StepSupport stepA = new StepSupport("stepA") { @Override - public void execute(StepExecution stepExecution) - throws JobInterruptedException, UnexpectedJobExecutionException { + public void execute(StepExecution stepExecution) throws UnexpectedJobExecutionException { stepExecution.setExitStatus(ExitStatus.FAILED); } }; StepSupport stepB = new StepSupport("stepB") { @Override - public void execute(StepExecution stepExecution) - throws JobInterruptedException, UnexpectedJobExecutionException { + public void execute(StepExecution stepExecution) throws UnexpectedJobExecutionException { } }; StepSupport stepC = new StepSupport("stepC") { @Override - public void execute(StepExecution stepExecution) - throws JobInterruptedException, UnexpectedJobExecutionException { + public void execute(StepExecution stepExecution) throws UnexpectedJobExecutionException { } }; @@ -148,16 +144,15 @@ public void execute(StepExecution stepExecution) .start(new JobFlowExecutor(jobRepository, new SimpleStepHandler(jobRepository), execution)); Iterator stepExecutions = execution.getStepExecutions().iterator(); - assertEquals(stepExecutions.next().getStepName(), "stepA"); - assertEquals(stepExecutions.next().getStepName(), "stepC"); + assertEquals("stepA", stepExecutions.next().getStepName()); + assertEquals("stepC", stepExecutions.next().getStepName()); assertFalse(stepExecutions.hasNext()); } private static StepSupport createCompleteStep(String name) { return new StepSupport(name) { @Override - public void execute(StepExecution stepExecution) - throws JobInterruptedException, UnexpectedJobExecutionException { + public void execute(StepExecution stepExecution) throws UnexpectedJobExecutionException { stepExecution.upgradeStatus(BatchStatus.COMPLETED); stepExecution.setExitStatus(ExitStatus.COMPLETED); } 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..1c24477e31 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-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,35 +16,41 @@ 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; import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobInterruptedException; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.JobParametersBuilder; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.UnexpectedJobExecutionException; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobInterruptedException; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.step.StepExecution; +import org.springframework.batch.core.job.UnexpectedJobExecutionException; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; import org.springframework.batch.core.configuration.annotation.JobScope; import org.springframework.batch.core.job.flow.Flow; import org.springframework.batch.core.job.flow.FlowExecutionStatus; import org.springframework.batch.core.job.flow.JobExecutionDecider; import org.springframework.batch.core.job.flow.support.SimpleFlow; -import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.launch.JobOperator; import org.springframework.batch.core.repository.JobRepository; -import org.springframework.batch.core.repository.support.JobRepositoryFactoryBean; +import org.springframework.batch.core.repository.support.JdbcJobRepositoryFactoryBean; 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; @@ -114,7 +120,7 @@ void init() throws Exception { .addScript("/org/springframework/batch/core/schema-drop-hsqldb.sql") .addScript("/org/springframework/batch/core/schema-hsqldb.sql") .build(); - JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean(); + JdbcJobRepositoryFactoryBean factory = new JdbcJobRepositoryFactoryBean(); factory.setDataSource(embeddedDatabase); factory.setTransactionManager(new JdbcTransactionManager(embeddedDatabase)); factory.afterPropertiesSet(); @@ -372,12 +378,12 @@ void testBuildWithStopAndRestart() throws Exception { void testBuildWithJobScopedStep() throws Exception { // given ApplicationContext context = new AnnotationConfigApplicationContext(JobConfiguration.class); - JobLauncher jobLauncher = context.getBean(JobLauncher.class); + JobOperator jobOperator = context.getBean(JobOperator.class); Job job = context.getBean(Job.class); JobParameters jobParameters = new JobParametersBuilder().addLong("chunkSize", 2L).toJobParameters(); // when - JobExecution jobExecution = jobLauncher.run(job, jobParameters); + JobExecution jobExecution = jobOperator.start(job, jobParameters); // then assertEquals(ExitStatus.COMPLETED, jobExecution.getExitStatus()); @@ -385,6 +391,7 @@ void testBuildWithJobScopedStep() throws Exception { @EnableBatchProcessing @Configuration + @EnableJdbcJobRepository static class JobConfiguration { @Bean @@ -419,4 +426,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); + } + } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/job/builder/JobBuilderTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/job/builder/JobBuilderTests.java index 9678a8e4cc..ee1c2f83ff 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/job/builder/JobBuilderTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/job/builder/JobBuilderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2022 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. @@ -20,14 +20,15 @@ import org.junit.jupiter.api.Test; import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobExecutionListener; -import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.listener.JobExecutionListener; +import org.springframework.batch.core.job.parameters.JobParameters; import org.springframework.batch.core.annotation.AfterJob; import org.springframework.batch.core.annotation.BeforeJob; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; -import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.launch.JobOperator; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.step.builder.StepBuilder; import org.springframework.batch.repeat.RepeatStatus; @@ -50,11 +51,11 @@ class JobBuilderTests { void testListeners() throws Exception { // given ApplicationContext context = new AnnotationConfigApplicationContext(MyJobConfiguration.class); - JobLauncher jobLauncher = context.getBean(JobLauncher.class); + JobOperator jobOperator = context.getBean(JobOperator.class); Job job = context.getBean(Job.class); // when - JobExecution jobExecution = jobLauncher.run(job, new JobParameters()); + JobExecution jobExecution = jobOperator.start(job, new JobParameters()); // then assertEquals(ExitStatus.COMPLETED, jobExecution.getExitStatus()); @@ -67,6 +68,7 @@ void testListeners() throws Exception { @Configuration @EnableBatchProcessing + @EnableJdbcJobRepository static class MyJobConfiguration { @Bean diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/job/flow/FlowJobFailureTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/job/flow/FlowJobFailureTests.java index 4d4b00fbd7..b1d52c16a9 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/job/flow/FlowJobFailureTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/job/flow/FlowJobFailureTests.java @@ -24,17 +24,17 @@ 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.JobInterruptedException; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.UnexpectedJobExecutionException; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobInterruptedException; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.step.StepExecution; +import org.springframework.batch.core.job.UnexpectedJobExecutionException; import org.springframework.batch.core.job.flow.support.SimpleFlow; import org.springframework.batch.core.job.flow.support.StateTransition; import org.springframework.batch.core.job.flow.support.state.EndState; import org.springframework.batch.core.job.flow.support.state.StepState; import org.springframework.batch.core.repository.JobRepository; -import org.springframework.batch.core.repository.support.JobRepositoryFactoryBean; +import org.springframework.batch.core.repository.support.JdbcJobRepositoryFactoryBean; import org.springframework.batch.core.step.StepSupport; import org.springframework.jdbc.support.JdbcTransactionManager; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; @@ -60,7 +60,7 @@ void init() throws Exception { .addScript("/org/springframework/batch/core/schema-drop-hsqldb.sql") .addScript("/org/springframework/batch/core/schema-hsqldb.sql") .build(); - JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean(); + JdbcJobRepositoryFactoryBean factory = new JdbcJobRepositoryFactoryBean(); factory.setDataSource(embeddedDatabase); factory.setTransactionManager(new JdbcTransactionManager(embeddedDatabase)); factory.afterPropertiesSet(); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/job/flow/FlowJobTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/job/flow/FlowJobTests.java index 349497096f..cbe37f0ad1 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/job/flow/FlowJobTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/job/flow/FlowJobTests.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. @@ -19,14 +19,12 @@ 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.JobInstance; -import org.springframework.batch.core.JobInterruptedException; -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.explore.support.JobExplorerFactoryBean; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobInstance; +import org.springframework.batch.core.job.JobInterruptedException; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.job.flow.support.DefaultStateTransitionComparator; import org.springframework.batch.core.job.flow.support.SimpleFlow; import org.springframework.batch.core.job.flow.support.StateTransition; @@ -36,7 +34,7 @@ import org.springframework.batch.core.job.flow.support.state.SplitState; import org.springframework.batch.core.job.flow.support.state.StepState; import org.springframework.batch.core.repository.JobRepository; -import org.springframework.batch.core.repository.support.JobRepositoryFactoryBean; +import org.springframework.batch.core.repository.support.JdbcJobRepositoryFactoryBean; import org.springframework.batch.core.step.StepSupport; import org.springframework.jdbc.support.JdbcTransactionManager; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; @@ -66,8 +64,6 @@ public class FlowJobTests { private JobRepository jobRepository; - private JobExplorer jobExplorer; - private boolean fail = false; @BeforeEach @@ -77,19 +73,13 @@ void setUp() throws Exception { .addScript("/org/springframework/batch/core/schema-hsqldb.sql") .build(); JdbcTransactionManager transactionManager = new JdbcTransactionManager(embeddedDatabase); - JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean(); + JdbcJobRepositoryFactoryBean factory = new JdbcJobRepositoryFactoryBean(); factory.setDataSource(embeddedDatabase); factory.setTransactionManager(transactionManager); factory.afterPropertiesSet(); this.jobRepository = factory.getObject(); job.setJobRepository(this.jobRepository); this.jobExecution = this.jobRepository.createJobExecution("job", new JobParameters()); - - JobExplorerFactoryBean explorerFactoryBean = new JobExplorerFactoryBean(); - explorerFactoryBean.setDataSource(embeddedDatabase); - explorerFactoryBean.setTransactionManager(transactionManager); - explorerFactoryBean.afterPropertiesSet(); - this.jobExplorer = explorerFactoryBean.getObject(); } @Test @@ -714,7 +704,7 @@ private StepExecution getStepExecution(JobExecution jobExecution, String stepNam private void checkRepository(BatchStatus status, ExitStatus exitStatus) { JobInstance jobInstance = this.jobExecution.getJobInstance(); - JobExecution other = this.jobExplorer.getJobExecutions(jobInstance).get(0); + JobExecution other = this.jobRepository.getJobExecutions(jobInstance).get(0); assertEquals(jobInstance.getId(), other.getJobId()); assertEquals(status, other.getStatus()); if (exitStatus != null) { diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/job/flow/FlowStepTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/job/flow/FlowStepTests.java index 005349f98f..a80128108b 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/job/flow/FlowStepTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/job/flow/FlowStepTests.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. @@ -26,16 +26,16 @@ 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.JobInterruptedException; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobInterruptedException; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.job.flow.support.SimpleFlow; import org.springframework.batch.core.job.flow.support.StateTransition; import org.springframework.batch.core.job.flow.support.state.EndState; import org.springframework.batch.core.job.flow.support.state.StepState; import org.springframework.batch.core.repository.JobRepository; -import org.springframework.batch.core.repository.support.JobRepositoryFactoryBean; +import org.springframework.batch.core.repository.support.JdbcJobRepositoryFactoryBean; import org.springframework.batch.core.step.StepSupport; import org.springframework.jdbc.support.JdbcTransactionManager; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; @@ -58,7 +58,7 @@ void setUp() throws Exception { .addScript("/org/springframework/batch/core/schema-drop-hsqldb.sql") .addScript("/org/springframework/batch/core/schema-hsqldb.sql") .build(); - JobRepositoryFactoryBean jobRepositoryFactoryBean = new JobRepositoryFactoryBean(); + JdbcJobRepositoryFactoryBean jobRepositoryFactoryBean = new JdbcJobRepositoryFactoryBean(); jobRepositoryFactoryBean.setDataSource(embeddedDatabase); jobRepositoryFactoryBean.setTransactionManager(new JdbcTransactionManager(embeddedDatabase)); jobRepositoryFactoryBean.afterPropertiesSet(); @@ -68,16 +68,14 @@ void setUp() throws Exception { @Test void testAfterPropertiesSet() { - FlowStep step = new FlowStep(); - step.setJobRepository(jobRepository); + FlowStep step = new FlowStep(jobRepository); assertThrows(IllegalStateException.class, step::afterPropertiesSet); } @Test void testDoExecute() throws Exception { - FlowStep step = new FlowStep(); - step.setJobRepository(jobRepository); + FlowStep step = new FlowStep(jobRepository); SimpleFlow flow = new SimpleFlow("job"); List transitions = new ArrayList<>(); @@ -108,8 +106,7 @@ void testDoExecute() throws Exception { @Test void testDoExecuteAndFail() throws Exception { - FlowStep step = new FlowStep(); - step.setJobRepository(jobRepository); + FlowStep step = new FlowStep(jobRepository); SimpleFlow flow = new SimpleFlow("job"); List transitions = new ArrayList<>(); @@ -141,8 +138,7 @@ void testDoExecuteAndFail() throws Exception { @Test void testExecuteWithParentContext() throws Exception { - FlowStep step = new FlowStep(); - step.setJobRepository(jobRepository); + FlowStep step = new FlowStep(jobRepository); SimpleFlow flow = new SimpleFlow("job"); List transitions = new ArrayList<>(); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/job/flow/StateSupport.java b/spring-batch-core/src/test/java/org/springframework/batch/core/job/flow/StateSupport.java index 2b1217657c..e5179e4c83 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/job/flow/StateSupport.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/job/flow/StateSupport.java @@ -15,7 +15,7 @@ */ package org.springframework.batch.core.job.flow; -import org.springframework.batch.core.JobExecution; +import org.springframework.batch.core.job.JobExecution; import org.springframework.batch.core.job.flow.support.state.AbstractState; /** diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/job/flow/support/JobFlowExecutorSupport.java b/spring-batch-core/src/test/java/org/springframework/batch/core/job/flow/support/JobFlowExecutorSupport.java index 8a61a173e0..2d0d955fbd 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/job/flow/support/JobFlowExecutorSupport.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/job/flow/support/JobFlowExecutorSupport.java @@ -16,11 +16,11 @@ package org.springframework.batch.core.job.flow.support; import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobInterruptedException; -import org.springframework.batch.core.StartLimitExceededException; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobInterruptedException; +import org.springframework.batch.core.job.StartLimitExceededException; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.job.flow.FlowExecution; import org.springframework.batch.core.job.flow.FlowExecutionStatus; import org.springframework.batch.core.job.flow.FlowExecutor; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/job/flow/support/state/EndStateTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/job/flow/support/state/EndStateTests.java index ada6772359..10ab4d6c40 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/job/flow/support/state/EndStateTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/job/flow/support/state/EndStateTests.java @@ -20,7 +20,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.batch.core.BatchStatus; -import org.springframework.batch.core.JobExecution; +import org.springframework.batch.core.job.JobExecution; import org.springframework.batch.core.job.flow.FlowExecutionStatus; import org.springframework.batch.core.job.flow.support.JobFlowExecutorSupport; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/launch/JobLauncherIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/launch/JobLauncherIntegrationTests.java index 2111ebc35a..9be2acf1b1 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/launch/JobLauncherIntegrationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/launch/JobLauncherIntegrationTests.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. @@ -17,16 +17,14 @@ import static org.junit.jupiter.api.Assertions.assertEquals; -import java.util.Calendar; - import javax.sql.DataSource; 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.repository.dao.JdbcJobExecutionDao; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; +import org.springframework.batch.core.repository.dao.jdbc.JdbcJobExecutionDao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; @@ -66,7 +64,6 @@ private JobExecution launch(boolean start, long jobExecutionId) throws Exception if (start) { - Calendar c = Calendar.getInstance(); JobParametersBuilder builder = new JobParametersBuilder(); builder.addString("name", "foo"); JobParameters jobParameters = builder.toJobParameters(); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/launch/TaskExecutorJobLauncherTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/launch/TaskExecutorJobLauncherTests.java index 2d8863826e..a0e96314ed 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/launch/TaskExecutorJobLauncherTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/launch/TaskExecutorJobLauncherTests.java @@ -25,15 +25,15 @@ import org.springframework.batch.core.BatchStatus; import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobInstance; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.JobParametersBuilder; -import org.springframework.batch.core.JobParametersInvalidException; -import org.springframework.batch.core.JobParametersValidator; -import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.job.DefaultJobParametersValidator; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobInstance; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; +import org.springframework.batch.core.job.parameters.JobParametersInvalidException; +import org.springframework.batch.core.job.parameters.JobParametersValidator; +import org.springframework.batch.core.step.StepExecution; +import org.springframework.batch.core.job.parameters.DefaultJobParametersValidator; import org.springframework.batch.core.job.JobSupport; import org.springframework.batch.core.launch.support.TaskExecutorJobLauncher; import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/CommandLineJobOperatorTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/CommandLineJobOperatorTests.java new file mode 100644 index 0000000000..31a4610e3a --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/CommandLineJobOperatorTests.java @@ -0,0 +1,151 @@ +/* + * 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.core.launch.support; + +import java.util.Properties; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import org.springframework.batch.core.configuration.JobRegistry; +import org.springframework.batch.core.converter.JobParametersConverter; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.launch.JobOperator; +import org.springframework.batch.core.repository.JobRepository; + +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link CommandLineJobOperator}. + * + * @author Mahmoud Ben Hassine + * @author Yejeong Ham + */ +class CommandLineJobOperatorTests { + + private final JobOperator jobOperator = mock(); + + private final JobRepository jobRepository = mock(); + + private final JobRegistry jobRegistry = mock(); + + private final JobParametersConverter jobParametersConverter = mock(); + + private final ExitCodeMapper exitCodeMapper = mock(); + + private CommandLineJobOperator commandLineJobOperator; + + @BeforeEach + void setUp() { + commandLineJobOperator = new CommandLineJobOperator(jobOperator, jobRepository, jobRegistry); + commandLineJobOperator.setJobParametersConverter(jobParametersConverter); + commandLineJobOperator.setExitCodeMapper(exitCodeMapper); + } + + @Test + void start() throws Exception { + // given + String jobName = "job"; + Properties parameters = new Properties(); + Job job = mock(); + JobParameters jobParameters = mock(); + + // when + Mockito.when(jobRegistry.getJob(jobName)).thenReturn(job); + Mockito.when(jobParametersConverter.getJobParameters(parameters)).thenReturn(jobParameters); + this.commandLineJobOperator.start(jobName, parameters); + + // then + Mockito.verify(jobRegistry).getJob(jobName); + Mockito.verify(jobParametersConverter).getJobParameters(parameters); + Mockito.verify(jobOperator).start(job, jobParameters); + } + + @Test + void startNextInstance() throws Exception { + // given + String jobName = "job"; + Job job = mock(); + + // when + Mockito.when(jobRegistry.getJob(jobName)).thenReturn(job); + this.commandLineJobOperator.startNextInstance(jobName); + + // then + Mockito.verify(jobRegistry).getJob(jobName); + Mockito.verify(jobOperator).startNextInstance(job); + } + + @Test + void stop() throws Exception { + // given + long jobExecutionId = 1; + JobExecution jobExecution = mock(); + + // when + Mockito.when(jobRepository.getJobExecution(jobExecutionId)).thenReturn(jobExecution); + this.commandLineJobOperator.stop(jobExecutionId); + + // then + Mockito.verify(jobOperator).stop(jobExecution); + } + + @Test + void restart() throws Exception { + // given + long jobExecutionId = 1; + JobExecution jobExecution = mock(); + + // when + Mockito.when(jobRepository.getJobExecution(jobExecutionId)).thenReturn(jobExecution); + this.commandLineJobOperator.restart(jobExecutionId); + + // then + Mockito.verify(jobOperator).restart(jobExecution); + } + + @Test + void abandon() throws Exception { + // given + long jobExecutionId = 1; + JobExecution jobExecution = mock(); + + // when + Mockito.when(jobRepository.getJobExecution(jobExecutionId)).thenReturn(jobExecution); + this.commandLineJobOperator.abandon(jobExecutionId); + + // then + Mockito.verify(jobOperator).abandon(jobExecution); + } + + @Test + void recover() { + // given + long jobExecutionId = 1; + JobExecution jobExecution = mock(); + + // when + Mockito.when(jobRepository.getJobExecution(jobExecutionId)).thenReturn(jobExecution); + this.commandLineJobOperator.recover(jobExecutionId); + + // then + Mockito.verify(jobOperator).recover(jobExecution); + } + +} \ No newline at end of file diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/CommandLineJobRunnerTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/CommandLineJobRunnerTests.java index 625145f0e2..d1eb434ba8 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/CommandLineJobRunnerTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/CommandLineJobRunnerTests.java @@ -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. @@ -27,19 +27,20 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.batch.core.BatchStatus; import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobInstance; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.JobParametersBuilder; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobInstance; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.converter.DefaultJobParametersConverter; import org.springframework.batch.core.converter.JobParametersConverter; -import org.springframework.batch.core.explore.JobExplorer; +import org.springframework.batch.core.repository.explore.JobExplorer; import org.springframework.batch.core.launch.JobLauncher; import org.springframework.batch.core.launch.NoSuchJobException; import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException; @@ -58,6 +59,7 @@ * @author Mahmoud Ben Hassine * @author Parikshit Dutta */ +@Disabled("Disabled until we replace the stub batch infrastructure with a JDBC one") class CommandLineJobRunnerTests { private String jobPath = ClassUtils.addResourcePathToPackagePath(CommandLineJobRunnerTests.class, @@ -136,8 +138,8 @@ void testWrongJobName() throws Exception { assertEquals(1, StubSystemExiter.status); String errorMessage = CommandLineJobRunner.getErrorMessage(); assertTrue( - (errorMessage.contains("No bean named 'no-such-job' is defined") - || (errorMessage.contains("No bean named 'no-such-job' available"))), + errorMessage.contains("No bean named 'no-such-job' is defined") + || errorMessage.contains("No bean named 'no-such-job' available"), "Wrong error message: " + errorMessage); } @@ -215,7 +217,7 @@ public int read() { void testWithStdinParameters() throws Throwable { String[] args = new String[] { jobPath, jobName }; System.setIn(new InputStream() { - final char[] input = ("foo=bar\nspam=bucket").toCharArray(); + final char[] input = "foo=bar\nspam=bucket".toCharArray(); int index = 0; @@ -246,7 +248,7 @@ void testWithInvalidParameters() throws Throwable { @Test void testStop() throws Throwable { String[] args = new String[] { jobPath, "-stop", jobName }; - StubJobExplorer.jobInstances = Arrays.asList(new JobInstance(3L, jobName)); + StubJobExplorer.jobInstances = List.of(new JobInstance(3L, jobName)); CommandLineJobRunner.main(args); assertEquals(1, StubSystemExiter.status); } @@ -254,7 +256,7 @@ void testStop() throws Throwable { @Test void testStopFailed() throws Throwable { String[] args = new String[] { jobPath, "-stop", jobName }; - StubJobExplorer.jobInstances = Arrays.asList(new JobInstance(0L, jobName)); + StubJobExplorer.jobInstances = List.of(new JobInstance(0L, jobName)); CommandLineJobRunner.main(args); assertEquals(1, StubSystemExiter.status); } @@ -262,7 +264,7 @@ void testStopFailed() throws Throwable { @Test void testStopFailedAndRestarted() throws Throwable { String[] args = new String[] { jobPath, "-stop", jobName }; - StubJobExplorer.jobInstances = Arrays.asList(new JobInstance(5L, jobName)); + StubJobExplorer.jobInstances = List.of(new JobInstance(5L, jobName)); CommandLineJobRunner.main(args); assertEquals(1, StubSystemExiter.status); } @@ -271,7 +273,7 @@ void testStopFailedAndRestarted() throws Throwable { void testStopRestarted() throws Throwable { String[] args = new String[] { jobPath, "-stop", jobName }; JobInstance jobInstance = new JobInstance(3L, jobName); - StubJobExplorer.jobInstances = Arrays.asList(jobInstance); + StubJobExplorer.jobInstances = List.of(jobInstance); CommandLineJobRunner.main(args); assertEquals(1, StubSystemExiter.status); } @@ -279,7 +281,7 @@ void testStopRestarted() throws Throwable { @Test void testAbandon() throws Throwable { String[] args = new String[] { jobPath, "-abandon", jobName }; - StubJobExplorer.jobInstances = Arrays.asList(new JobInstance(2L, jobName)); + StubJobExplorer.jobInstances = List.of(new JobInstance(2L, jobName)); CommandLineJobRunner.main(args); assertEquals(0, StubSystemExiter.status); } @@ -287,7 +289,7 @@ void testAbandon() throws Throwable { @Test void testAbandonRunning() throws Throwable { String[] args = new String[] { jobPath, "-abandon", jobName }; - StubJobExplorer.jobInstances = Arrays.asList(new JobInstance(3L, jobName)); + StubJobExplorer.jobInstances = List.of(new JobInstance(3L, jobName)); CommandLineJobRunner.main(args); assertEquals(1, StubSystemExiter.status); } @@ -295,7 +297,7 @@ void testAbandonRunning() throws Throwable { @Test void testAbandonAbandoned() throws Throwable { String[] args = new String[] { jobPath, "-abandon", jobName }; - StubJobExplorer.jobInstances = Arrays.asList(new JobInstance(4L, jobName)); + StubJobExplorer.jobInstances = List.of(new JobInstance(4L, jobName)); CommandLineJobRunner.main(args); assertEquals(1, StubSystemExiter.status); } @@ -305,7 +307,7 @@ void testRestart() throws Throwable { String[] args = new String[] { jobPath, "-restart", jobName }; JobParameters jobParameters = new JobParametersBuilder().addString("foo", "bar").toJobParameters(); JobInstance jobInstance = new JobInstance(0L, jobName); - StubJobExplorer.jobInstances = Arrays.asList(jobInstance); + StubJobExplorer.jobInstances = List.of(jobInstance); StubJobExplorer.jobParameters = jobParameters; CommandLineJobRunner.main(args); assertEquals(0, StubSystemExiter.status); @@ -340,7 +342,7 @@ void testRestartExecutionNotFailed() throws Throwable { @Test void testRestartNotFailed() throws Throwable { String[] args = new String[] { jobPath, "-restart", jobName }; - StubJobExplorer.jobInstances = Arrays.asList(new JobInstance(123L, jobName)); + StubJobExplorer.jobInstances = List.of(new JobInstance(123L, jobName)); CommandLineJobRunner.main(args); assertEquals(1, StubSystemExiter.status); String errorMessage = CommandLineJobRunner.getErrorMessage(); @@ -351,13 +353,12 @@ void testRestartNotFailed() throws Throwable { @Test void testNext() throws Throwable { String[] args = new String[] { jobPath, "-next", jobName, "bar=foo" }; - JobParameters jobParameters = new JobParametersBuilder().addString("foo", "bar") - .addString("bar", "foo") - .toJobParameters(); - StubJobExplorer.jobInstances = Arrays.asList(new JobInstance(2L, jobName)); + StubJobExplorer.jobInstances = List.of(new JobInstance(2L, jobName)); CommandLineJobRunner.main(args); assertEquals(0, StubSystemExiter.status); - jobParameters = new JobParametersBuilder().addString("foo", "spam").addString("bar", "foo").toJobParameters(); + JobParameters jobParameters = new JobParametersBuilder().addString("foo", "spam") + .addString("bar", "foo") + .toJobParameters(); assertEquals(jobParameters, StubJobLauncher.jobParameters); } @@ -480,25 +481,25 @@ public JobExecution getJobExecution(@Nullable Long executionId) { @Override public List getJobExecutions(JobInstance jobInstance) { if (jobInstance.getId() == 0) { - return Arrays.asList(createJobExecution(jobInstance, BatchStatus.FAILED)); + return List.of(createJobExecution(jobInstance, BatchStatus.FAILED)); } if (jobInstance.getId() == 1) { return null; } if (jobInstance.getId() == 2) { - return Arrays.asList(createJobExecution(jobInstance, BatchStatus.STOPPED)); + return List.of(createJobExecution(jobInstance, BatchStatus.STOPPED)); } if (jobInstance.getId() == 3) { - return Arrays.asList(createJobExecution(jobInstance, BatchStatus.STARTED)); + return List.of(createJobExecution(jobInstance, BatchStatus.STARTED)); } if (jobInstance.getId() == 4) { - return Arrays.asList(createJobExecution(jobInstance, BatchStatus.ABANDONED)); + return List.of(createJobExecution(jobInstance, BatchStatus.ABANDONED)); } if (jobInstance.getId() == 5) { return Arrays.asList(createJobExecution(jobInstance, BatchStatus.STARTED), createJobExecution(jobInstance, BatchStatus.FAILED)); } - return Arrays.asList(createJobExecution(jobInstance, BatchStatus.COMPLETED)); + return List.of(createJobExecution(jobInstance, BatchStatus.COMPLETED)); } private JobExecution createJobExecution(JobInstance jobInstance, BatchStatus status) { @@ -556,6 +557,7 @@ public List getJobNames() { throw new UnsupportedOperationException(); } + @SuppressWarnings("removal") @Override public List findJobInstancesByJobName(String jobName, int start, int count) { throw new UnsupportedOperationException(); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/DataFieldMaxValueJobParametersIncrementerTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/DataFieldMaxValueJobParametersIncrementerTests.java index a56457d06f..65b3283f83 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/DataFieldMaxValueJobParametersIncrementerTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/DataFieldMaxValueJobParametersIncrementerTests.java @@ -17,8 +17,8 @@ import org.junit.jupiter.api.Test; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.JobParametersBuilder; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; import org.springframework.jdbc.support.incrementer.DataFieldMaxValueIncrementer; import static org.junit.jupiter.api.Assertions.assertEquals; 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..6a9315326c 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 @@ -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. @@ -23,10 +23,9 @@ import org.springframework.aop.framework.Advised; import org.springframework.batch.core.configuration.JobRegistry; import org.springframework.batch.core.converter.JobParametersConverter; -import org.springframework.batch.core.explore.JobExplorer; -import org.springframework.batch.core.launch.JobLauncher; import org.springframework.batch.core.launch.JobOperator; import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.support.transaction.ResourcelessTransactionManager; import org.springframework.test.util.AopTestUtils; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.interceptor.TransactionAttributeSource; @@ -43,12 +42,8 @@ class JobOperatorFactoryBeanTests { private final JobRepository jobRepository = Mockito.mock(); - private final JobLauncher jobLauncher = Mockito.mock(); - private final JobRegistry jobRegistry = Mockito.mock(); - private final JobExplorer jobExplorer = Mockito.mock(); - private final JobParametersConverter jobParametersConverter = Mockito.mock(); @Test @@ -56,8 +51,6 @@ public void testJobOperatorCreation() throws Exception { // given 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); @@ -69,18 +62,36 @@ public void testJobOperatorCreation() throws Exception { // then Assertions.assertNotNull(jobOperator); Object targetObject = AopTestUtils.getTargetObject(jobOperator); - Assertions.assertInstanceOf(SimpleJobOperator.class, targetObject); + Assertions.assertInstanceOf(TaskExecutorJobOperator.class, targetObject); Assertions.assertEquals(this.transactionManager, getTransactionManagerSetOnJobOperator(jobOperator)); } + @Test + public void testDefaultTransactionManagerConfiguration() throws Exception { + // given + JobOperatorFactoryBean jobOperatorFactoryBean = new JobOperatorFactoryBean(); + jobOperatorFactoryBean.setJobRegistry(this.jobRegistry); + jobOperatorFactoryBean.setJobRepository(this.jobRepository); + jobOperatorFactoryBean.setJobParametersConverter(this.jobParametersConverter); + jobOperatorFactoryBean.afterPropertiesSet(); + + // when + JobOperator jobOperator = jobOperatorFactoryBean.getObject(); + + // then + Assertions.assertNotNull(jobOperator); + Object targetObject = AopTestUtils.getTargetObject(jobOperator); + Assertions.assertInstanceOf(TaskExecutorJobOperator.class, targetObject); + Assertions.assertInstanceOf(ResourcelessTransactionManager.class, + getTransactionManagerSetOnJobOperator(jobOperator)); + } + @Test public void testCustomTransactionAttributesSource() throws Exception { // given TransactionAttributeSource transactionAttributeSource = Mockito.mock(); 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); @@ -97,10 +108,7 @@ public void testCustomTransactionAttributesSource() throws Exception { } private PlatformTransactionManager getTransactionManagerSetOnJobOperator(JobOperator jobOperator) { - Advised target = (Advised) jobOperator; // proxy created by - // AbstractJobOperatorFactoryBean - Advisor[] advisors = target.getAdvisors(); - for (Advisor advisor : advisors) { + for (Advisor advisor : ((Advised) jobOperator).getAdvisors()) { if (advisor.getAdvice() instanceof TransactionInterceptor transactionInterceptor) { return (PlatformTransactionManager) transactionInterceptor.getTransactionManager(); } @@ -109,10 +117,7 @@ private PlatformTransactionManager getTransactionManagerSetOnJobOperator(JobOper } private TransactionAttributeSource getTransactionAttributesSourceSetOnJobOperator(JobOperator jobOperator) { - Advised target = (Advised) jobOperator; // proxy created by - // AbstractJobOperatorFactoryBean - Advisor[] advisors = target.getAdvisors(); - for (Advisor advisor : advisors) { + for (Advisor advisor : ((Advised) jobOperator).getAdvisors()) { if (advisor.getAdvice() instanceof TransactionInterceptor transactionInterceptor) { return transactionInterceptor.getTransactionAttributeSource(); } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/RunIdIncrementerTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/RunIdIncrementerTests.java index e14eb0b65e..6515b60ace 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/RunIdIncrementerTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/RunIdIncrementerTests.java @@ -19,8 +19,8 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.JobParametersBuilder; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; /** * @author Dave Syer diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/StubJobLauncher.java b/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/StubJobLauncher.java index aafbb361a7..e17793b35a 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/StubJobLauncher.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/StubJobLauncher.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2012 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. @@ -15,11 +15,13 @@ */ package org.springframework.batch.core.launch.support; -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.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersInvalidException; import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException; +import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException; +import org.springframework.batch.core.repository.JobRestartException; /** * Mock Job Launcher. Normally, something like EasyMock would be used to mock an @@ -29,7 +31,7 @@ * @author Lucas Ward * */ -public class StubJobLauncher implements JobLauncher { +public class StubJobLauncher extends TaskExecutorJobOperator { public static final int RUN_NO_ARGS = 0; @@ -48,7 +50,8 @@ public boolean isRunning() { } @Override - public JobExecution run(Job job, JobParameters jobParameters) throws JobExecutionAlreadyRunningException { + public JobExecution run(Job job, JobParameters jobParameters) throws JobExecutionAlreadyRunningException, + JobRestartException, JobInstanceAlreadyCompleteException, JobParametersInvalidException { lastRunCalled = RUN_JOB_IDENTIFIER; return returnValue; } 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/TaskExecutorJobOperatorTests.java similarity index 69% rename from spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/SimpleJobOperatorTests.java rename to spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/TaskExecutorJobOperatorTests.java index d5d3951c2c..a35e5ea63e 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/TaskExecutorJobOperatorTests.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. @@ -28,19 +28,18 @@ 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.JobExecutionException; -import org.springframework.batch.core.JobInstance; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.JobParametersIncrementer; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepContribution; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobExecutionException; +import org.springframework.batch.core.job.JobInstance; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersIncrementer; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.step.StepContribution; import org.springframework.batch.core.configuration.JobRegistry; import org.springframework.batch.core.configuration.support.MapJobRegistry; import org.springframework.batch.core.converter.DefaultJobParametersConverter; import org.springframework.batch.core.converter.JobParametersConverter; -import org.springframework.batch.core.explore.JobExplorer; import org.springframework.batch.core.job.AbstractJob; import org.springframework.batch.core.job.JobSupport; import org.springframework.batch.core.launch.JobInstanceAlreadyExistsException; @@ -58,12 +57,13 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; /** @@ -71,16 +71,15 @@ * @author Will Schipp * @author Mahmoud Ben Hassine * @author Jinwoo Bae - * + * @author Yejeong Ham */ -class SimpleJobOperatorTests { +@SuppressWarnings("removal") +class TaskExecutorJobOperatorTests { - private SimpleJobOperator jobOperator; + private TaskExecutorJobOperator jobOperator; protected Job job; - private JobExplorer jobExplorer; - private JobRepository jobRepository; private JobParameters jobParameters; @@ -100,7 +99,12 @@ public JobParametersIncrementer getJobParametersIncrementer() { } }; - jobOperator = new SimpleJobOperator(); + jobOperator = new TaskExecutorJobOperator() { + @Override + public JobExecution run(Job job, JobParameters jobParameters) { + return new JobExecution(new JobInstance(123L, job.getName()), 999L, jobParameters); + } + }; jobOperator.setJobRegistry(new MapJobRegistry() { @Override @@ -117,13 +121,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); @@ -146,21 +143,21 @@ public Properties getProperties(@Nullable JobParameters params) { @Test void testMandatoryProperties() { - jobOperator = new SimpleJobOperator(); + jobOperator = new TaskExecutorJobOperator(); assertThrows(IllegalStateException.class, jobOperator::afterPropertiesSet); } /** * Test method for - * {@link org.springframework.batch.core.launch.support.SimpleJobOperator#startNextInstance(java.lang.String)} + * {@link org.springframework.batch.core.launch.support.TaskExecutorJobOperator#startNextInstance(java.lang.String)} * . */ @Test 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()); @@ -182,17 +179,23 @@ void testStartNewInstanceAlreadyExists() { Properties properties = new Properties(); properties.setProperty("a", "b"); jobParameters = new JobParameters(); - when(jobRepository.isJobInstanceExists("foo", jobParameters)).thenReturn(true); - jobRepository.isJobInstanceExists("foo", jobParameters); + JobInstance jobInstance = new JobInstance(123L, "foo"); + when(jobRepository.getJobInstance("foo", jobParameters)).thenReturn(jobInstance); assertThrows(JobInstanceAlreadyExistsException.class, () -> jobOperator.start("foo", properties)); } + @Test + void testStartWithIncrementer() throws Exception { + jobOperator.start(job, new JobParameters()); + verify(jobRepository).getLastJobInstance("foo"); + } + @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 +204,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 +213,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 +225,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 +233,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 +241,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 +250,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 +265,7 @@ void testGetJobParametersSunnyDay() throws Exception { @Test void testGetJobParametersNoSuchExecution() { - jobExplorer.getJobExecution(111L); + jobRepository.getJobExecution(111L); assertThrows(NoSuchJobExecutionException.class, () -> jobOperator.getParameters(111L)); } @@ -270,8 +273,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 +282,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 +294,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 +312,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 +330,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()); @@ -345,15 +348,12 @@ void testStopTasklet() throws Exception { job.taskletStep = taskletStep; JobRegistry jobRegistry = mock(); - TaskletStep step = mock(); - 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()); @@ -363,18 +363,14 @@ void testStopTasklet() throws Exception { void testStopTaskletWhenJobNotRegistered() throws Exception { JobInstance jobInstance = new JobInstance(123L, job.getName()); JobExecution jobExecution = new JobExecution(jobInstance, 111L, jobParameters); - StoppableTasklet tasklet = mock(); JobRegistry jobRegistry = mock(); - TaskletStep step = mock(); - 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); assertEquals(BatchStatus.STOPPING, jobExecution.getStatus()); - verify(tasklet, never()).stop(); } @Test @@ -400,15 +396,12 @@ public void stop() { job.taskletStep = taskletStep; JobRegistry jobRegistry = mock(); - TaskletStep step = mock(); - 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 +412,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,11 +424,59 @@ 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)); } + @Test + void testRecoverStartedJob() { + JobInstance jobInstance = new JobInstance(123L, job.getName()); + JobExecution jobExecution = new JobExecution(jobInstance, 111L, jobParameters); + jobExecution.setStatus(BatchStatus.STARTED); + jobExecution.createStepExecution("step1").setStatus(BatchStatus.STARTED); + when(jobRepository.getJobExecution(111L)).thenReturn(jobExecution); + JobExecution recover = jobOperator.recover(jobExecution); + assertEquals(BatchStatus.FAILED, recover.getStatus()); + assertNotNull(recover.getEndTime()); + assertTrue(recover.getExecutionContext().containsKey("recovered")); + } + + @Test + void testRecoverStoppingStep() { + JobInstance jobInstance = new JobInstance(123L, job.getName()); + JobExecution jobExecution = new JobExecution(jobInstance, 111L, jobParameters); + jobExecution.setStatus(BatchStatus.STARTED); + jobExecution.createStepExecution("step1").setStatus(BatchStatus.STOPPING); + when(jobRepository.getJobExecution(111L)).thenReturn(jobExecution); + JobExecution recover = jobOperator.recover(jobExecution); + assertEquals(BatchStatus.FAILED, recover.getStatus()); + assertNotNull(recover.getEndTime()); + assertTrue(recover.getExecutionContext().containsKey("recovered")); + } + + @Test + void testRecoverAbandonedJob() { + JobInstance jobInstance = new JobInstance(123L, job.getName()); + JobExecution jobExecution = new JobExecution(jobInstance, 111L, jobParameters); + jobExecution.setStatus(BatchStatus.ABANDONED); + when(jobRepository.getJobExecution(111L)).thenReturn(jobExecution); + JobExecution recover = jobOperator.recover(jobExecution); + assertSame(recover, jobExecution); + verifyNoInteractions(jobRepository); + } + + @Test + void testRecoverCompletedJob() { + JobInstance jobInstance = new JobInstance(123L, job.getName()); + JobExecution jobExecution = new JobExecution(jobInstance, 111L, jobParameters); + jobExecution.setStatus(BatchStatus.COMPLETED); + when(jobRepository.getJobExecution(111L)).thenReturn(jobExecution); + JobExecution recover = jobOperator.recover(jobExecution); + assertSame(recover, jobExecution); + verifyNoInteractions(jobRepository); + } + static class MockJob extends AbstractJob { private TaskletStep taskletStep; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/TestJobParametersIncrementer.java b/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/TestJobParametersIncrementer.java index 1ad3759d87..8119ae249f 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/TestJobParametersIncrementer.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/TestJobParametersIncrementer.java @@ -15,9 +15,9 @@ */ package org.springframework.batch.core.launch.support; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.JobParametersBuilder; -import org.springframework.batch.core.JobParametersIncrementer; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; +import org.springframework.batch.core.job.parameters.JobParametersIncrementer; import org.springframework.lang.Nullable; public class TestJobParametersIncrementer implements JobParametersIncrementer { diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/listener/CompositeChunkListenerTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/listener/CompositeChunkListenerTests.java index 1ae514a1a1..6d3cda5dfd 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/listener/CompositeChunkListenerTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/listener/CompositeChunkListenerTests.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. @@ -19,7 +19,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.ChunkListener; + import org.springframework.batch.core.scope.context.ChunkContext; /** diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/listener/CompositeItemProcessListenerTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/listener/CompositeItemProcessListenerTests.java index c9703acad0..6f86fc5d86 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/listener/CompositeItemProcessListenerTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/listener/CompositeItemProcessListenerTests.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. @@ -21,7 +21,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.ItemProcessListener; /** * @author Dave Syer diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/listener/CompositeItemReadListenerTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/listener/CompositeItemReadListenerTests.java index ac9bd41bac..f06a95f116 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/listener/CompositeItemReadListenerTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/listener/CompositeItemReadListenerTests.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. @@ -17,11 +17,10 @@ import static org.mockito.Mockito.mock; -import java.util.ArrayList; +import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.ItemReadListener; /** * @author Lucas Ward @@ -67,11 +66,7 @@ void testOnReadError() { @Test void testSetListeners() { - compositeListener.setListeners(new ArrayList<>() { - { - add(listener); - } - }); + compositeListener.setListeners(List.of(listener)); listener.beforeRead(); compositeListener.beforeRead(); } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/listener/CompositeItemWriteListenerTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/listener/CompositeItemWriteListenerTests.java index 6531b4ef35..3740ecf94d 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/listener/CompositeItemWriteListenerTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/listener/CompositeItemWriteListenerTests.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. @@ -15,12 +15,11 @@ */ package org.springframework.batch.core.listener; -import java.util.ArrayList; +import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.ItemWriteListener; import org.springframework.batch.item.Chunk; import static org.mockito.Mockito.mock; @@ -69,11 +68,7 @@ void testOnWriteError() { @Test void testSetListeners() { - compositeListener.setListeners(new ArrayList<>() { - { - add(listener); - } - }); + compositeListener.setListeners(List.of(listener)); Chunk item = Chunk.of(new Object()); listener.beforeWrite(item); compositeListener.beforeWrite(item); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/listener/CompositeJobExecutionListenerTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/listener/CompositeJobExecutionListenerTests.java index 0ee3e43c1a..376c80473e 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/listener/CompositeJobExecutionListenerTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/listener/CompositeJobExecutionListenerTests.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. @@ -20,9 +20,8 @@ import java.util.List; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobExecutionListener; -import org.springframework.batch.core.JobInstance; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobInstance; import static org.junit.jupiter.api.Assertions.assertEquals; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/listener/CompositeStepExecutionListenerTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/listener/CompositeStepExecutionListenerTests.java index e705523169..d8c9ce4aca 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/listener/CompositeStepExecutionListenerTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/listener/CompositeStepExecutionListenerTests.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. @@ -20,9 +20,8 @@ import org.junit.jupiter.api.Test; import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.StepExecutionListener; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.step.StepExecution; import org.springframework.lang.Nullable; import static org.junit.jupiter.api.Assertions.assertEquals; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/listener/ExecutionContextPromotionListenerTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/listener/ExecutionContextPromotionListenerTests.java index b0100489b0..b7b16aac36 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/listener/ExecutionContextPromotionListenerTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/listener/ExecutionContextPromotionListenerTests.java @@ -18,8 +18,8 @@ import org.junit.jupiter.api.Test; import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.step.StepExecution; import org.springframework.util.Assert; import static org.junit.jupiter.api.Assertions.assertEquals; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/listener/ItemListenerErrorTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/listener/ItemListenerErrorTests.java index a89485aa1a..231f9f22a4 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/listener/ItemListenerErrorTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/listener/ItemListenerErrorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2023 the original author or authors. + * Copyright 2015-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,16 +26,15 @@ import org.junit.jupiter.api.Test; import org.springframework.batch.core.BatchStatus; -import org.springframework.batch.core.ItemProcessListener; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; import org.springframework.batch.core.job.builder.JobBuilder; -import org.springframework.batch.core.launch.JobLauncher; -import org.springframework.batch.core.launch.support.RunIdIncrementer; +import org.springframework.batch.core.launch.JobOperator; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.step.builder.StepBuilder; import org.springframework.batch.item.Chunk; @@ -75,7 +74,7 @@ class ItemListenerErrorTests { private FailingItemWriter writer; @Autowired - private JobLauncher jobLauncher; + private JobOperator jobOperator; @Autowired private Job job; @@ -94,7 +93,7 @@ void testOnWriteError() throws Exception { listener.setMethodToThrowExceptionFrom("onWriteError"); writer.setGoingToFail(true); - JobExecution execution = jobLauncher.run(job, new JobParameters()); + JobExecution execution = jobOperator.start(job, new JobParameters()); assertEquals(BatchStatus.COMPLETED, execution.getStatus()); } @@ -104,7 +103,7 @@ void testOnReadError() throws Exception { listener.setMethodToThrowExceptionFrom("onReadError"); reader.setGoingToFail(true); - JobExecution execution = jobLauncher.run(job, new JobParameters()); + JobExecution execution = jobOperator.start(job, new JobParameters()); assertEquals(BatchStatus.FAILED, execution.getStatus()); StepExecution stepExecution = execution.getStepExecutions().iterator().next(); assertEquals(0, stepExecution.getReadCount()); @@ -123,17 +122,18 @@ void testOnProcessError() throws Exception { listener.setMethodToThrowExceptionFrom("onProcessError"); processor.setGoingToFail(true); - JobExecution execution = jobLauncher.run(job, new JobParameters()); + JobExecution execution = jobOperator.start(job, new JobParameters()); assertEquals(BatchStatus.COMPLETED, execution.getStatus()); } @Configuration @EnableBatchProcessing + @EnableJdbcJobRepository public static class BatchConfiguration { @Bean public Job testJob(JobRepository jobRepository, Step testStep) { - return new JobBuilder("testJob", jobRepository).incrementer(new RunIdIncrementer()).start(testStep).build(); + return new JobBuilder("testJob", jobRepository).start(testStep).build(); } @Bean diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/listener/JobListenerFactoryBeanTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/listener/JobListenerFactoryBeanTests.java index 59bdb90e40..51e7ec1083 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/listener/JobListenerFactoryBeanTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/listener/JobListenerFactoryBeanTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 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. @@ -28,8 +28,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.aop.framework.ProxyFactory; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobExecutionListener; +import org.springframework.batch.core.job.JobExecution; import org.springframework.batch.core.annotation.AfterJob; import org.springframework.batch.core.annotation.BeforeJob; import org.springframework.batch.core.configuration.xml.AbstractTestComponent; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/listener/JobParameterExecutionContextCopyListenerTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/listener/JobParameterExecutionContextCopyListenerTests.java index a3d29cd071..0f3ca9df6e 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/listener/JobParameterExecutionContextCopyListenerTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/listener/JobParameterExecutionContextCopyListenerTests.java @@ -20,11 +20,11 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobInstance; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.JobParametersBuilder; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobInstance; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; +import org.springframework.batch.core.step.StepExecution; /** * @author Dave Syer diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/listener/MulticasterBatchListenerTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/listener/MulticasterBatchListenerTests.java index 0a8fedd77f..e45d29eb58 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/listener/MulticasterBatchListenerTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/listener/MulticasterBatchListenerTests.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,18 +16,16 @@ package org.springframework.batch.core.listener; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.Arrays; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.SkipListener; -import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.StepListener; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.annotation.AfterChunk; import org.springframework.batch.core.annotation.AfterProcess; import org.springframework.batch.core.annotation.AfterRead; @@ -77,7 +75,7 @@ public ExitStatus afterStep(StepExecution stepExecution) { /** * Test method for - * {@link org.springframework.batch.core.listener.MulticasterBatchListener#register(org.springframework.batch.core.StepListener)} + * {@link org.springframework.batch.core.listener.MulticasterBatchListener#register(StepListener)} * . */ @Test @@ -98,7 +96,7 @@ public ExitStatus afterStep(StepExecution stepExecution) { /** * Test method for - * {@link org.springframework.batch.core.listener.MulticasterBatchListener#afterStep(org.springframework.batch.core.StepExecution)} + * {@link org.springframework.batch.core.listener.MulticasterBatchListener#afterStep(StepExecution)} * . */ @Test @@ -112,7 +110,7 @@ void testAfterStepFails() { /** * Test method for - * {@link org.springframework.batch.core.listener.MulticasterBatchListener#beforeStep(org.springframework.batch.core.StepExecution)} + * {@link org.springframework.batch.core.listener.MulticasterBatchListener#beforeStep(StepExecution)} * . */ @Test @@ -123,7 +121,7 @@ void testBeforeStep() { /** * Test method for - * {@link org.springframework.batch.core.listener.MulticasterBatchListener#beforeStep(org.springframework.batch.core.StepExecution)} + * {@link org.springframework.batch.core.listener.MulticasterBatchListener#beforeStep(StepExecution)} * . */ @Test @@ -142,7 +140,7 @@ void testBeforeStepFails() { */ @Test void testAfterChunk() { - multicast.afterChunk(null); + multicast.afterChunk((ChunkContext) null); assertEquals(1, count); } @@ -154,7 +152,8 @@ void testAfterChunk() { @Test void testAfterChunkFails() { error = true; - Exception exception = assertThrows(StepListenerFailedException.class, () -> multicast.afterChunk(null)); + Exception exception = assertThrows(StepListenerFailedException.class, + () -> multicast.afterChunk((ChunkContext) null)); String message = exception.getCause().getMessage(); assertEquals("listener error", message, "Wrong message: " + message); assertEquals(1, count); @@ -167,7 +166,7 @@ void testAfterChunkFails() { */ @Test void testBeforeChunk() { - multicast.beforeChunk(null); + multicast.beforeChunk((ChunkContext) null); assertEquals(1, count); } @@ -179,7 +178,8 @@ void testBeforeChunk() { @Test void testBeforeChunkFails() { error = true; - Exception exception = assertThrows(StepListenerFailedException.class, () -> multicast.beforeChunk(null)); + Exception exception = assertThrows(StepListenerFailedException.class, + () -> multicast.beforeChunk((ChunkContext) null)); String message = exception.getCause().getMessage(); assertEquals("listener error", message, "Wrong message: " + message); assertEquals(1, count); @@ -459,7 +459,7 @@ void testBeforeReadFails_withAnnotatedListener() { Exception exception = assertThrows(StepListenerFailedException.class, multicast::beforeRead); Throwable cause = exception.getCause(); String message = cause.getMessage(); - assertTrue(cause instanceof IllegalStateException); + assertInstanceOf(IllegalStateException.class, cause); assertEquals("listener error", message, "Wrong message: " + message); } @@ -471,7 +471,7 @@ void testAfterReadFails_withAnnotatedListener() { Exception exception = assertThrows(StepListenerFailedException.class, () -> multicast.afterRead(null)); Throwable cause = exception.getCause(); String message = cause.getMessage(); - assertTrue(cause instanceof IllegalStateException); + assertInstanceOf(IllegalStateException.class, cause); assertEquals("listener error", message, "Wrong message: " + message); } @@ -483,7 +483,7 @@ void testBeforeProcessFails_withAnnotatedListener() { Exception exception = assertThrows(StepListenerFailedException.class, () -> multicast.beforeProcess(null)); Throwable cause = exception.getCause(); String message = cause.getMessage(); - assertTrue(cause instanceof IllegalStateException); + assertInstanceOf(IllegalStateException.class, cause); assertEquals("listener error", message, "Wrong message: " + message); } @@ -495,7 +495,7 @@ void testAfterProcessFails_withAnnotatedListener() { Exception exception = assertThrows(StepListenerFailedException.class, () -> multicast.afterProcess(null, null)); Throwable cause = exception.getCause(); String message = cause.getMessage(); - assertTrue(cause instanceof IllegalStateException); + assertInstanceOf(IllegalStateException.class, cause); assertEquals("listener error", message, "Wrong message: " + message); } @@ -507,7 +507,7 @@ void testBeforeWriteFails_withAnnotatedListener() { Exception exception = assertThrows(StepListenerFailedException.class, () -> multicast.beforeWrite(null)); Throwable cause = exception.getCause(); String message = cause.getMessage(); - assertTrue(cause instanceof IllegalStateException); + assertInstanceOf(IllegalStateException.class, cause); assertEquals("listener error", message, "Wrong message: " + message); } @@ -519,7 +519,7 @@ void testAfterWriteFails_withAnnotatedListener() { Exception exception = assertThrows(StepListenerFailedException.class, () -> multicast.afterWrite(null)); Throwable cause = exception.getCause(); String message = cause.getMessage(); - assertTrue(cause instanceof IllegalStateException); + assertInstanceOf(IllegalStateException.class, cause); assertEquals("listener error", message, "Wrong message: " + message); } @@ -528,10 +528,11 @@ void testBeforeChunkFails_withAnnotatedListener() { StepListener listener = StepListenerFactoryBean.getListener(new AnnotationBasedStepListener()); multicast.register(listener); - Exception exception = assertThrows(StepListenerFailedException.class, () -> multicast.beforeChunk(null)); + Exception exception = assertThrows(StepListenerFailedException.class, + () -> multicast.beforeChunk((ChunkContext) null)); Throwable cause = exception.getCause(); String message = cause.getMessage(); - assertTrue(cause instanceof IllegalStateException); + assertInstanceOf(IllegalStateException.class, cause); assertEquals("listener error", message, "Wrong message: " + message); } @@ -540,10 +541,11 @@ void testAfterChunkFails_withAnnotatedListener() { StepListener listener = StepListenerFactoryBean.getListener(new AnnotationBasedStepListener()); multicast.register(listener); - Exception exception = assertThrows(StepListenerFailedException.class, () -> multicast.afterChunk(null)); + Exception exception = assertThrows(StepListenerFailedException.class, + () -> multicast.afterChunk((ChunkContext) null)); Throwable cause = exception.getCause(); String message = cause.getMessage(); - assertTrue(cause instanceof IllegalStateException); + assertInstanceOf(IllegalStateException.class, cause); assertEquals("listener error", message, "Wrong message: " + message); } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/listener/StepListenerFactoryBeanTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/listener/StepListenerFactoryBeanTests.java index f01c8b2fa4..c09240e96c 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/listener/StepListenerFactoryBeanTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/listener/StepListenerFactoryBeanTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 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. @@ -24,16 +24,9 @@ import org.junit.jupiter.api.Test; import org.springframework.aop.framework.ProxyFactory; -import org.springframework.batch.core.ChunkListener; import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.ItemProcessListener; -import org.springframework.batch.core.ItemReadListener; -import org.springframework.batch.core.ItemWriteListener; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.SkipListener; -import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.StepExecutionListener; -import org.springframework.batch.core.StepListener; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.annotation.AfterChunk; import org.springframework.batch.core.annotation.AfterChunkError; import org.springframework.batch.core.annotation.AfterProcess; @@ -58,6 +51,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.springframework.batch.core.listener.StepListenerMetaData.AFTER_STEP; @@ -91,8 +85,8 @@ void testStepAndChunk() { StepListener listener = (StepListener) factoryBean.getObject(); ((StepExecutionListener) listener).beforeStep(stepExecution); ((StepExecutionListener) listener).afterStep(stepExecution); - ((ChunkListener) listener).beforeChunk(null); - ((ChunkListener) listener).afterChunk(null); + ((ChunkListener) listener).beforeChunk((ChunkContext) null); + ((ChunkListener) listener).afterChunk((ChunkContext) null); ((ChunkListener) listener).afterChunkError(new ChunkContext(null)); ((ItemReadListener) listener).beforeRead(); ((ItemReadListener) listener).afterRead(readItem); @@ -156,7 +150,7 @@ void testVanillaInterface() { MultipleAfterStep delegate = new MultipleAfterStep(); factoryBean.setDelegate(delegate); Object listener = factoryBean.getObject(); - assertTrue(listener instanceof StepExecutionListener); + assertInstanceOf(StepExecutionListener.class, listener); ((StepExecutionListener) listener).beforeStep(stepExecution); assertEquals(1, delegate.callcount); } @@ -167,7 +161,7 @@ void testVanillaInterfaceWithProxy() { ProxyFactory factory = new ProxyFactory(delegate); factoryBean.setDelegate(factory.getProxy()); Object listener = factoryBean.getObject(); - assertTrue(listener instanceof StepExecutionListener); + assertInstanceOf(StepExecutionListener.class, listener); ((StepExecutionListener) listener).beforeStep(stepExecution); assertEquals(1, delegate.callcount); } @@ -176,7 +170,7 @@ void testVanillaInterfaceWithProxy() { void testFactoryMethod() { MultipleAfterStep delegate = new MultipleAfterStep(); Object listener = StepListenerFactoryBean.getListener(delegate); - assertTrue(listener instanceof StepExecutionListener); + assertInstanceOf(StepExecutionListener.class, listener); assertFalse(listener instanceof ChunkListener); ((StepExecutionListener) listener).beforeStep(stepExecution); assertEquals(1, delegate.callcount); @@ -186,7 +180,7 @@ void testFactoryMethod() { void testAnnotationsWithOrdered() { Object delegate = new Ordered() { @BeforeStep - public void foo(StepExecution execution) { + public void foo(@SuppressWarnings("unused") StepExecution execution) { } @Override @@ -195,7 +189,7 @@ public int getOrder() { } }; StepListener listener = StepListenerFactoryBean.getListener(delegate); - assertTrue(listener instanceof Ordered, "Listener is not of correct type"); + assertInstanceOf(Ordered.class, listener, "Listener is not of correct type"); assertEquals(3, ((Ordered) listener).getOrder()); } @@ -203,15 +197,15 @@ public int getOrder() { void testProxiedAnnotationsFactoryMethod() { Object delegate = new InitializingBean() { @BeforeStep - public void foo(StepExecution execution) { + public void foo(@SuppressWarnings("unused") StepExecution execution) { } @Override - public void afterPropertiesSet() throws Exception { + public void afterPropertiesSet() { } }; ProxyFactory factory = new ProxyFactory(delegate); - assertTrue(StepListenerFactoryBean.getListener(factory.getProxy()) instanceof StepExecutionListener, + assertInstanceOf(StepExecutionListener.class, StepListenerFactoryBean.getListener(factory.getProxy()), "Listener is not of correct type"); } @@ -224,7 +218,7 @@ void testInterfaceIsListener() { void testAnnotationsIsListener() { assertTrue(StepListenerFactoryBean.isListener(new Object() { @BeforeStep - public void foo(StepExecution execution) { + public void foo(@SuppressWarnings("unused") StepExecution execution) { } })); } @@ -242,11 +236,11 @@ void testProxyWithNoTarget() { void testProxiedAnnotationsIsListener() { Object delegate = new InitializingBean() { @BeforeStep - public void foo(StepExecution execution) { + public void foo(@SuppressWarnings("unused") StepExecution execution) { } @Override - public void afterPropertiesSet() throws Exception { + public void afterPropertiesSet() { } }; ProxyFactory factory = new ProxyFactory(delegate); @@ -264,7 +258,7 @@ void testMixedIsListener() { void testNonListener() { Object delegate = new Object(); factoryBean.setDelegate(delegate); - assertTrue(factoryBean.getObject() instanceof StepListener); + assertInstanceOf(StepListener.class, factoryBean.getObject()); } @Test @@ -303,7 +297,7 @@ public void aMethod(Chunk chunk) { void testWrongSignatureAnnotation() { AbstractTestComponent delegate = new AbstractTestComponent() { @AfterWrite - public void aMethod(Integer item) { + public void aMethod(@SuppressWarnings("unused") Integer item) { executed = true; } }; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/observability/BatchMetricsTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/observability/BatchMetricsTests.java index 760f235668..3ff6e0f2dc 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/observability/BatchMetricsTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/observability/BatchMetricsTests.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. @@ -18,38 +18,9 @@ import java.time.Duration; import java.time.LocalDateTime; import java.time.temporal.ChronoUnit; -import java.util.Arrays; -import java.util.List; -import javax.sql.DataSource; - -import io.micrometer.core.instrument.Meter; -import io.micrometer.core.instrument.Metrics; -import io.micrometer.core.instrument.observation.DefaultMeterObservationHandler; -import io.micrometer.observation.ObservationRegistry; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobParameters; -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.launch.JobLauncher; -import org.springframework.batch.core.repository.JobRepository; -import org.springframework.batch.core.step.builder.StepBuilder; -import org.springframework.batch.item.support.ListItemReader; -import org.springframework.batch.repeat.RepeatStatus; -import org.springframework.context.ApplicationContext; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; -import org.springframework.jdbc.support.JdbcTransactionManager; -import org.springframework.transaction.PlatformTransactionManager; - -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -59,8 +30,6 @@ */ class BatchMetricsTests { - private static final int EXPECTED_SPRING_BATCH_METRICS = 11; - @Test void testCalculateDuration() { LocalDateTime startTime = LocalDateTime.now(); @@ -133,180 +102,4 @@ void testFormatNullDuration() { assertTrue(formattedDuration.isEmpty()); } - @Test - void testBatchMetrics() throws Exception { - // given - ApplicationContext context = new AnnotationConfigApplicationContext(MyJobConfiguration.class); - JobLauncher jobLauncher = context.getBean(JobLauncher.class); - Job job = context.getBean(Job.class); - - // when - JobExecution jobExecution = jobLauncher.run(job, new JobParameters()); - - // then - assertEquals(ExitStatus.COMPLETED, jobExecution.getExitStatus()); - List meters = Metrics.globalRegistry.getMeters(); - assertTrue(meters.size() >= EXPECTED_SPRING_BATCH_METRICS); - - // Job metrics - - assertDoesNotThrow(() -> Metrics.globalRegistry.get("spring.batch.job.launch.count").counter(), - "There should be a meter of type COUNTER named spring.batch.job.launch.count registered in the global registry"); - - assertDoesNotThrow( - () -> Metrics.globalRegistry.get("spring.batch.job") - .tag("spring.batch.job.name", "job") - .tag("spring.batch.job.status", "COMPLETED") - .timer(), - "There should be a meter of type TIMER named spring.batch.job registered in the global registry"); - - assertDoesNotThrow( - () -> Metrics.globalRegistry.get("spring.batch.job.active") - .tag("spring.batch.job.active.name", "job") - .longTaskTimer(), - "There should be a meter of type LONG_TASK_TIMER named spring.batch.job.active" - + " registered in the global registry"); - - // Step 1 (tasklet) metrics - - assertDoesNotThrow( - () -> Metrics.globalRegistry.get("spring.batch.step") - .tag("spring.batch.step.name", "step1") - .tag("spring.batch.step.job.name", "job") - .tag("spring.batch.step.status", "COMPLETED") - .timer(), - "There should be a meter of type TIMER named spring.batch.step registered in the global registry"); - - // Step 2 (simple chunk-oriented) metrics - - assertDoesNotThrow( - () -> Metrics.globalRegistry.get("spring.batch.step") - .tag("spring.batch.step.name", "step2") - .tag("spring.batch.step.job.name", "job") - .tag("spring.batch.step.status", "COMPLETED") - .timer(), - "There should be a meter of type TIMER named spring.batch.step registered in the global registry"); - - assertDoesNotThrow( - () -> Metrics.globalRegistry.get("spring.batch.item.read") - .tag("spring.batch.item.read.job.name", "job") - .tag("spring.batch.item.read.step.name", "step2") - .tag("spring.batch.item.read.status", "SUCCESS") - .timer(), - "There should be a meter of type TIMER named spring.batch.item.read registered in the global registry"); - - assertDoesNotThrow( - () -> Metrics.globalRegistry.get("spring.batch.item.process") - .tag("spring.batch.item.process.job.name", "job") - .tag("spring.batch.item.process.step.name", "step2") - .tag("spring.batch.item.process.status", "SUCCESS") - .timer(), - "There should be a meter of type TIMER named spring.batch.item.process registered in the global registry"); - - assertDoesNotThrow( - () -> Metrics.globalRegistry.get("spring.batch.chunk.write") - .tag("spring.batch.chunk.write.job.name", "job") - .tag("spring.batch.chunk.write.step.name", "step2") - .tag("spring.batch.chunk.write.status", "SUCCESS") - .timer(), - "There should be a meter of type TIMER named spring.batch.chunk.write registered in the global registry"); - - // Step 3 (fault-tolerant chunk-oriented) metrics - - assertDoesNotThrow( - () -> Metrics.globalRegistry.get("spring.batch.step") - .tag("spring.batch.step.name", "step3") - .tag("spring.batch.step.job.name", "job") - .tag("spring.batch.step.status", "COMPLETED") - .timer(), - "There should be a meter of type TIMER named spring.batch.step registered in the global registry"); - - assertDoesNotThrow( - () -> Metrics.globalRegistry.get("spring.batch.item.read") - .tag("spring.batch.item.read.job.name", "job") - .tag("spring.batch.item.read.step.name", "step3") - .tag("spring.batch.item.read.status", "SUCCESS") - .timer(), - "There should be a meter of type TIMER named spring.batch.item.read registered in the global registry"); - - assertDoesNotThrow( - () -> Metrics.globalRegistry.get("spring.batch.item.process") - .tag("spring.batch.item.process.job.name", "job") - .tag("spring.batch.item.process.step.name", "step3") - .tag("spring.batch.item.process.status", "SUCCESS") - .timer(), - "There should be a meter of type TIMER named spring.batch.item.process registered in the global registry"); - - assertDoesNotThrow( - () -> Metrics.globalRegistry.get("spring.batch.chunk.write") - .tag("spring.batch.chunk.write.job.name", "job") - .tag("spring.batch.chunk.write.step.name", "step3") - .tag("spring.batch.chunk.write.status", "SUCCESS") - .timer(), - "There should be a meter of type TIMER named spring.batch.chunk.write registered in the global registry"); - } - - @Configuration - @EnableBatchProcessing - static class MyJobConfiguration { - - @Bean - public Step step1(JobRepository jobRepository, PlatformTransactionManager transactionManager) { - return new StepBuilder("step1", jobRepository) - .tasklet((contribution, chunkContext) -> RepeatStatus.FINISHED, transactionManager) - .build(); - } - - @Bean - public Step step2(JobRepository jobRepository, PlatformTransactionManager transactionManager) { - return new StepBuilder("step2", jobRepository).chunk(2, transactionManager) - .reader(new ListItemReader<>(Arrays.asList(1, 2, 3, 4, 5))) - .writer(items -> { - }) - .build(); - } - - @Bean - public Step step3(JobRepository jobRepository, PlatformTransactionManager transactionManager) { - return new StepBuilder("step3", jobRepository).chunk(2, transactionManager) - .reader(new ListItemReader<>(Arrays.asList(6, 7, 8, 9, 10))) - .writer(items -> { - }) - .faultTolerant() - .skip(Exception.class) - .skipLimit(3) - .build(); - } - - @Bean - public Job job(JobRepository jobRepository, PlatformTransactionManager transactionManager) { - return new JobBuilder("job", jobRepository).start(step1(jobRepository, transactionManager)) - .next(step2(jobRepository, transactionManager)) - .next(step3(jobRepository, transactionManager)) - .build(); - } - - @Bean - public ObservationRegistry observationRegistry() { - ObservationRegistry observationRegistry = ObservationRegistry.create(); - observationRegistry.observationConfig() - .observationHandler(new DefaultMeterObservationHandler(Metrics.globalRegistry)); - return observationRegistry; - } - - @Bean - public DataSource dataSource() { - return new EmbeddedDatabaseBuilder().addScript("/org/springframework/batch/core/schema-drop-hsqldb.sql") - .addScript("/org/springframework/batch/core/schema-hsqldb.sql") - .generateUniqueName(true) - .build(); - } - - @Bean - public JdbcTransactionManager transactionManager(DataSource dataSource) { - return new JdbcTransactionManager(dataSource); - } - - } - } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/observability/micrometer/MicrometerMetricsTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/observability/micrometer/MicrometerMetricsTests.java new file mode 100644 index 0000000000..c900db35ba --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/observability/micrometer/MicrometerMetricsTests.java @@ -0,0 +1,105 @@ +/* + * Copyright 2025-present 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 + * + * http://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.observability.micrometer; + +import java.util.List; + +import io.micrometer.core.instrument.Meter; +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.observation.DefaultMeterObservationHandler; +import io.micrometer.core.instrument.simple.SimpleMeterRegistry; +import io.micrometer.observation.ObservationRegistry; +import org.junit.jupiter.api.Test; + +import org.springframework.batch.core.ExitStatus; +import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.builder.JobBuilder; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.launch.JobOperator; +import org.springframework.batch.core.observability.BatchMetrics; +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.core.step.builder.StepBuilder; +import org.springframework.batch.repeat.RepeatStatus; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class MicrometerMetricsTests { + + @Test + void testMicrometerMetrics() throws Exception { + // given + ApplicationContext context = new AnnotationConfigApplicationContext(MyJobConfiguration.class); + JobOperator jobOperator = context.getBean(JobOperator.class); + Job job = context.getBean(Job.class); + MeterRegistry meterRegistry = context.getBean(MeterRegistry.class); + int expectedJobMetricsCount = 2; + + // when + JobExecution jobExecution = jobOperator.start(job, new JobParameters()); + + // then + assertEquals(ExitStatus.COMPLETED, jobExecution.getExitStatus()); + List meters = meterRegistry.getMeters(); + assertTrue(meters.size() >= expectedJobMetricsCount); + assertDoesNotThrow(() -> meterRegistry.get(BatchMetrics.METRICS_PREFIX + "job.launch.count").timer(), + "There should be a meter of type TIMER named spring.batch.job.launch.count registered in the meter registry"); + assertEquals(1, meterRegistry.get(BatchMetrics.METRICS_PREFIX + "job.launch.count").timer().count()); + assertDoesNotThrow( + () -> meterRegistry.get(BatchMetrics.METRICS_PREFIX + "job") + .tag(BatchMetrics.METRICS_PREFIX + "job.name", "job") + .tag(BatchMetrics.METRICS_PREFIX + "job.status", "COMPLETED") + .timer(), + "There should be a meter of type TIMER named spring.batch.job registered in the meter registry"); + + } + + @Configuration + @EnableBatchProcessing + static class MyJobConfiguration { + + @Bean + public Job job(JobRepository jobRepository) { + return new JobBuilder("job", jobRepository) + .start(new StepBuilder("step", jobRepository) + .tasklet((contribution, chunkContext) -> RepeatStatus.FINISHED) + .build()) + .build(); + } + + @Bean + public MeterRegistry meterRegistry() { + return new SimpleMeterRegistry(); + } + + @Bean + public ObservationRegistry observationRegistry(MeterRegistry meterRegistry) { + ObservationRegistry observationRegistry = ObservationRegistry.create(); + observationRegistry.observationConfig() + .observationHandler(new DefaultMeterObservationHandler(meterRegistry)); + return observationRegistry; + } + + } + +} \ No newline at end of file diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/observability/ObservabilitySampleStepTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/observability/micrometer/MicrometerTracingTests.java similarity index 61% rename from spring-batch-core/src/test/java/org/springframework/batch/core/observability/ObservabilitySampleStepTests.java rename to spring-batch-core/src/test/java/org/springframework/batch/core/observability/micrometer/MicrometerTracingTests.java index 75bdb93bd0..52f5065ad9 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/observability/ObservabilitySampleStepTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/observability/micrometer/MicrometerTracingTests.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. @@ -13,15 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.batch.core.observability; +package org.springframework.batch.core.observability.micrometer; import java.util.UUID; -import javax.sql.DataSource; - import io.micrometer.core.instrument.MeterRegistry; -import io.micrometer.core.instrument.Metrics; import io.micrometer.core.instrument.observation.DefaultMeterObservationHandler; +import io.micrometer.core.instrument.simple.SimpleMeterRegistry; import io.micrometer.core.tck.MeterRegistryAssert; import io.micrometer.observation.ObservationRegistry; import io.micrometer.tracing.test.SampleTestRunner; @@ -31,44 +29,45 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.batch.core.ExitStatus; -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.Step; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; +import org.springframework.batch.core.observability.BatchMetrics; +import org.springframework.batch.core.step.Step; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; import org.springframework.batch.core.job.builder.JobBuilder; -import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.launch.JobOperator; 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.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.jdbc.support.JdbcTransactionManager; import org.springframework.test.context.junit.jupiter.SpringExtension; @ExtendWith(SpringExtension.class) -class ObservabilitySampleStepTests extends SampleTestRunner { +class MicrometerTracingTests extends SampleTestRunner { @Autowired private Job job; @Autowired - private JobLauncher jobLauncher; + private JobOperator jobOperator; + + @Autowired + private MeterRegistry meterRegistry; @Autowired private ObservationRegistry observationRegistry; - ObservabilitySampleStepTests() { + MicrometerTracingTests() { super(SampleRunnerConfig.builder().build()); } @Override protected MeterRegistry createMeterRegistry() { - return Metrics.globalRegistry; + return this.meterRegistry; } @Override @@ -79,7 +78,7 @@ protected ObservationRegistry createObservationRegistry() { @AfterEach @Override protected void closeMeterRegistry() { - Metrics.globalRegistry.clear(); + this.meterRegistry.clear(); } @Override @@ -90,7 +89,7 @@ public SampleTestRunnerConsumer yourCode() { .toJobParameters(); // when - JobExecution jobExecution = this.jobLauncher.run(this.job, jobParameters); + JobExecution jobExecution = this.jobOperator.start(this.job, jobParameters); // then Assertions.assertThat(jobExecution.getExitStatus()).isEqualTo(ExitStatus.COMPLETED); @@ -98,13 +97,13 @@ public SampleTestRunnerConsumer yourCode() { // and SpansAssert.assertThat(bb.getFinishedSpans()) .haveSameTraceId() - .hasASpanWithName("job") - .hasASpanWithName("step"); + .hasASpanWithName(BatchMetrics.METRICS_PREFIX + "job") + .hasASpanWithName(BatchMetrics.METRICS_PREFIX + "step"); // and MeterRegistryAssert.assertThat(meterRegistry) - .hasTimerWithName("spring.batch.job") - .hasTimerWithName("spring.batch.step"); + .hasMeterWithName(BatchMetrics.METRICS_PREFIX + "job") + .hasMeterWithName(BatchMetrics.METRICS_PREFIX + "step"); }; } @@ -113,17 +112,21 @@ public SampleTestRunnerConsumer yourCode() { static class TestConfig { @Bean - public ObservationRegistry observationRegistry() { + public MeterRegistry meterRegistry() { + return new SimpleMeterRegistry(); + } + + @Bean + public ObservationRegistry observationRegistry(MeterRegistry meterRegistry) { ObservationRegistry observationRegistry = ObservationRegistry.create(); observationRegistry.observationConfig() - .observationHandler(new DefaultMeterObservationHandler(Metrics.globalRegistry)); + .observationHandler(new DefaultMeterObservationHandler(meterRegistry)); return observationRegistry; } @Bean - public Step step(JobRepository jobRepository, JdbcTransactionManager transactionManager) { - return new StepBuilder("step", jobRepository) - .tasklet((contribution, chunkContext) -> RepeatStatus.FINISHED, transactionManager) + public Step step(JobRepository jobRepository) { + return new StepBuilder("step", jobRepository).tasklet((contribution, chunkContext) -> RepeatStatus.FINISHED) .build(); } @@ -132,19 +135,6 @@ public Job job(JobRepository jobRepository, Step step) { return new JobBuilder("job", jobRepository).start(step).build(); } - @Bean - public DataSource dataSource() { - return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.HSQL) - .addScript("/org/springframework/batch/core/schema-drop-hsqldb.sql") - .addScript("/org/springframework/batch/core/schema-hsqldb.sql") - .build(); - } - - @Bean - public JdbcTransactionManager transactionManager(DataSource dataSource) { - return new JdbcTransactionManager(dataSource); - } - } } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/partition/MinMaxPartitioner.java b/spring-batch-core/src/test/java/org/springframework/batch/core/partition/MinMaxPartitioner.java index d46112f34c..76f5b7c4f9 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/partition/MinMaxPartitioner.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/partition/MinMaxPartitioner.java @@ -34,7 +34,7 @@ public Map partition(int gridSize) { int range = total / gridSize; int i = 0; for (ExecutionContext context : partition.values()) { - int min = (i++) * range; + int min = i++ * range; int max = Math.min(total, i * range); context.putInt("min", min); context.putInt("max", max); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/partition/support/PartitionStepTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/partition/PartitionStepTests.java similarity index 90% rename from spring-batch-core/src/test/java/org/springframework/batch/core/partition/support/PartitionStepTests.java rename to spring-batch-core/src/test/java/org/springframework/batch/core/partition/PartitionStepTests.java index 0325f97b14..1347db84d4 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/partition/support/PartitionStepTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/partition/PartitionStepTests.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. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.batch.core.partition.support; +package org.springframework.batch.core.partition; import java.time.LocalDateTime; import java.util.Arrays; @@ -26,11 +26,14 @@ import org.springframework.batch.core.BatchStatus; import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.step.StepExecution; +import org.springframework.batch.core.partition.support.DefaultStepExecutionAggregator; +import org.springframework.batch.core.partition.support.SimplePartitioner; +import org.springframework.batch.core.partition.support.SimpleStepExecutionSplitter; import org.springframework.batch.core.repository.JobRepository; -import org.springframework.batch.core.repository.support.JobRepositoryFactoryBean; +import org.springframework.batch.core.repository.support.JdbcJobRepositoryFactoryBean; import org.springframework.jdbc.support.JdbcTransactionManager; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; @@ -44,7 +47,7 @@ */ class PartitionStepTests { - private final PartitionStep step = new PartitionStep(); + private PartitionStep step; private JobRepository jobRepository; @@ -54,12 +57,12 @@ void setUp() throws Exception { .addScript("/org/springframework/batch/core/schema-drop-hsqldb.sql") .addScript("/org/springframework/batch/core/schema-hsqldb.sql") .build(); - JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean(); + JdbcJobRepositoryFactoryBean factory = new JdbcJobRepositoryFactoryBean(); factory.setDataSource(embeddedDatabase); factory.setTransactionManager(new JdbcTransactionManager(embeddedDatabase)); factory.afterPropertiesSet(); jobRepository = factory.getObject(); - step.setJobRepository(jobRepository); + step = new PartitionStep(jobRepository); step.setName("partitioned"); } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/partition/RestartIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/partition/RestartIntegrationTests.java index 1ef92483da..67e3ddc664 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/partition/RestartIntegrationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/partition/RestartIntegrationTests.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. @@ -24,11 +24,11 @@ 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.JobParametersBuilder; -import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; +import org.springframework.batch.core.launch.JobOperator; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; @@ -43,7 +43,7 @@ public class RestartIntegrationTests { @Autowired - private JobLauncher jobLauncher; + private JobOperator jobOperator; @Autowired private Job job; @@ -57,7 +57,7 @@ public void setDataSource(DataSource dataSource) { @Test void testSimpleProperties() { - assertNotNull(jobLauncher); + assertNotNull(jobOperator); } @BeforeEach @@ -79,13 +79,13 @@ void testLaunchJob() throws Exception { "STEP_NAME like 'step1:partition%'"); ExampleItemWriter.clear(); - JobExecution execution = jobLauncher.run(job, jobParameters); + JobExecution execution = jobOperator.start(job, jobParameters); assertEquals(BatchStatus.FAILED, execution.getStatus()); // Only 4 because the others were in the failed step execution assertEquals(4, ExampleItemWriter.getItems().size()); ExampleItemWriter.clear(); - assertNotNull(jobLauncher.run(job, jobParameters)); + assertNotNull(jobOperator.start(job, jobParameters)); // Only 4 because the others were processed in the first attempt assertEquals(4, ExampleItemWriter.getItems().size()); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/partition/VanillaIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/partition/VanillaIntegrationTests.java index 735436b439..cc73cc22a7 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/partition/VanillaIntegrationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/partition/VanillaIntegrationTests.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. @@ -21,9 +21,9 @@ import javax.sql.DataSource; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.launch.JobOperator; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; @@ -38,7 +38,7 @@ public class VanillaIntegrationTests { @Autowired - private JobLauncher jobLauncher; + private JobOperator jobOperator; @Autowired private Job job; @@ -52,7 +52,7 @@ public void setDataSource(DataSource dataSource) { @Test void testSimpleProperties() { - assertNotNull(jobLauncher); + assertNotNull(jobOperator); } @Test @@ -61,7 +61,7 @@ void testLaunchJob() throws Exception { "STEP_NAME='step1:manager'"); int beforePartition = JdbcTestUtils.countRowsInTableWhere(jdbcTemplate, "BATCH_STEP_EXECUTION", "STEP_NAME like 'step1:partition%'"); - assertNotNull(jobLauncher.run(job, new JobParameters())); + assertNotNull(jobOperator.start(job, new JobParameters())); int afterManager = JdbcTestUtils.countRowsInTableWhere(jdbcTemplate, "BATCH_STEP_EXECUTION", "STEP_NAME='step1:manager'"); int afterPartition = JdbcTestUtils.countRowsInTableWhere(jdbcTemplate, "BATCH_STEP_EXECUTION", diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/partition/support/DefaultStepExecutionAggregatorTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/partition/support/DefaultStepExecutionAggregatorTests.java index 83b422f17e..ed986147cf 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/partition/support/DefaultStepExecutionAggregatorTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/partition/support/DefaultStepExecutionAggregatorTests.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. @@ -18,8 +18,9 @@ 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.batch.core.job.JobExecution; +import org.springframework.batch.core.step.StepExecution; +import org.springframework.batch.core.partition.StepExecutionAggregator; import java.util.Arrays; import java.util.Collections; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/partition/support/RemoteStepExecutionAggregatorTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/partition/support/RemoteStepExecutionAggregatorTests.java index 808cbe78dd..3be4d42a54 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/partition/support/RemoteStepExecutionAggregatorTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/partition/support/RemoteStepExecutionAggregatorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2022 the original author or authors. + * Copyright 2011-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,11 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.batch.core.BatchStatus; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.explore.support.JobExplorerFactoryBean; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.repository.JobRepository; -import org.springframework.batch.core.repository.support.JobRepositoryFactoryBean; +import org.springframework.batch.core.repository.support.JdbcJobRepositoryFactoryBean; import org.springframework.jdbc.support.JdbcTransactionManager; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; @@ -55,16 +54,12 @@ void init() throws Exception { .addScript("/org/springframework/batch/core/schema-hsqldb.sql") .build(); JdbcTransactionManager transactionManager = new JdbcTransactionManager(embeddedDatabase); - JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean(); + JdbcJobRepositoryFactoryBean factory = new JdbcJobRepositoryFactoryBean(); factory.setDataSource(embeddedDatabase); factory.setTransactionManager(transactionManager); factory.afterPropertiesSet(); JobRepository jobRepository = factory.getObject(); - JobExplorerFactoryBean explorerFactoryBean = new JobExplorerFactoryBean(); - explorerFactoryBean.setDataSource(embeddedDatabase); - explorerFactoryBean.setTransactionManager(transactionManager); - explorerFactoryBean.afterPropertiesSet(); - aggregator.setJobExplorer(explorerFactoryBean.getObject()); + aggregator.setJobRepository(jobRepository); jobExecution = jobRepository.createJobExecution("job", new JobParameters()); result = jobExecution.createStepExecution("aggregate"); stepExecution1 = jobExecution.createStepExecution("foo:1"); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/partition/support/SimpleStepExecutionSplitterTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/partition/support/SimpleStepExecutionSplitterTests.java index 2a17a56645..17536392fe 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/partition/support/SimpleStepExecutionSplitterTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/partition/support/SimpleStepExecutionSplitterTests.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. @@ -26,14 +26,16 @@ import org.junit.jupiter.api.Test; import org.springframework.batch.core.BatchStatus; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobExecutionException; -import org.springframework.batch.core.JobInstance; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobExecutionException; +import org.springframework.batch.core.job.JobInstance; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.step.StepExecution; +import org.springframework.batch.core.partition.PartitionNameProvider; +import org.springframework.batch.core.partition.Partitioner; import org.springframework.batch.core.repository.JobRepository; -import org.springframework.batch.core.repository.support.JobRepositoryFactoryBean; +import org.springframework.batch.core.repository.support.JdbcJobRepositoryFactoryBean; import org.springframework.batch.core.step.tasklet.TaskletStep; import org.springframework.batch.item.ExecutionContext; import org.springframework.jdbc.support.JdbcTransactionManager; @@ -59,7 +61,7 @@ void setUp() throws Exception { .addScript("/org/springframework/batch/core/schema-drop-hsqldb.sql") .addScript("/org/springframework/batch/core/schema-hsqldb.sql") .build(); - JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean(); + JdbcJobRepositoryFactoryBean factory = new JdbcJobRepositoryFactoryBean(); factory.setDataSource(embeddedDatabase); factory.setTransactionManager(new JdbcTransactionManager(embeddedDatabase)); factory.afterPropertiesSet(); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/partition/support/TaskExecutorPartitionHandlerTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/partition/support/TaskExecutorPartitionHandlerTests.java index 909f1a5425..63c581dae3 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/partition/support/TaskExecutorPartitionHandlerTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/partition/support/TaskExecutorPartitionHandlerTests.java @@ -27,10 +27,10 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobExecutionException; -import org.springframework.batch.core.JobInterruptedException; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobExecutionException; +import org.springframework.batch.core.job.JobInterruptedException; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.partition.StepExecutionSplitter; import org.springframework.batch.core.step.StepSupport; import org.springframework.core.task.SimpleAsyncTaskExecutor; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/AbstractExecutionContextDaoTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/AbstractExecutionContextDaoTests.java index 6dd41d114e..8bb2fd9c67 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/AbstractExecutionContextDaoTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/AbstractExecutionContextDaoTests.java @@ -24,10 +24,10 @@ import org.junit.jupiter.api.Test; import org.springframework.batch.core.BatchStatus; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobInstance; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobInstance; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.item.ExecutionContext; import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests; import org.springframework.transaction.annotation.Transactional; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/AbstractExecutionContextSerializerTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/AbstractExecutionContextSerializerTests.java index c427cfffe6..86f5dfb5dc 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/AbstractExecutionContextSerializerTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/AbstractExecutionContextSerializerTests.java @@ -17,8 +17,8 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.JobParameter; -import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.job.parameters.JobParameter; +import org.springframework.batch.core.job.parameters.JobParameters; import org.springframework.batch.core.repository.ExecutionContextSerializer; import java.io.ByteArrayInputStream; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/AbstractJobDaoTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/AbstractJobDaoTests.java index d4381fb949..e5bcf7cc37 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/AbstractJobDaoTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/AbstractJobDaoTests.java @@ -32,10 +32,10 @@ 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.JobInstance; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.JobParametersBuilder; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobInstance; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.transaction.annotation.Transactional; @@ -52,7 +52,7 @@ public abstract class AbstractJobDaoTests { protected JobExecutionDao jobExecutionDao; protected JobParameters jobParameters = new JobParametersBuilder().addString("job.key", "jobKey") - .addLong("long", (long) 1) + .addLong("long", 1L) .addDouble("double", 7.7) .toJobParameters(); @@ -191,7 +191,7 @@ void testSaveJobExecution() { void testUpdateInvalidJobExecution() { // id is invalid - JobExecution execution = new JobExecution(jobInstance, (long) 29432, jobParameters); + JobExecution execution = new JobExecution(jobInstance, 29432L, jobParameters); execution.incrementVersion(); assertThrows(NoSuchObjectException.class, () -> jobExecutionDao.updateJobExecution(execution)); } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/AbstractJobExecutionDaoTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/AbstractJobExecutionDaoTests.java index 2ebbee0fa7..b19d97ee39 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/AbstractJobExecutionDaoTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/AbstractJobExecutionDaoTests.java @@ -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. @@ -27,10 +27,11 @@ import org.springframework.batch.core.BatchStatus; import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobInstance; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobInstance; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.step.StepExecution; +import org.springframework.batch.core.repository.dao.jdbc.JdbcJobExecutionDao; import org.springframework.dao.OptimisticLockingFailureException; import org.springframework.transaction.annotation.Transactional; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/AbstractJobInstanceDaoTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/AbstractJobInstanceDaoTests.java index 9f463d7362..649d9d081e 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/AbstractJobInstanceDaoTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/AbstractJobInstanceDaoTests.java @@ -27,9 +27,9 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.JobInstance; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.JobParametersBuilder; +import org.springframework.batch.core.job.JobInstance; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; import org.springframework.transaction.annotation.Transactional; public abstract class AbstractJobInstanceDaoTests { @@ -239,7 +239,7 @@ void testCreateDuplicateInstance() { @Test void testCreationAddsVersion() { - JobInstance jobInstance = new JobInstance((long) 1, "testVersionAndId"); + JobInstance jobInstance = new JobInstance(1L, "testVersionAndId"); assertNull(jobInstance.getVersion()); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/AbstractStepExecutionDaoTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/AbstractStepExecutionDaoTests.java index 2e0c6c5a10..961184f658 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/AbstractStepExecutionDaoTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/AbstractStepExecutionDaoTests.java @@ -27,11 +27,11 @@ import org.junit.jupiter.api.Test; import org.springframework.batch.core.BatchStatus; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobInstance; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobInstance; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.step.StepSupport; import org.springframework.dao.OptimisticLockingFailureException; @@ -224,7 +224,7 @@ void testSaveAndFindExecution() { @Transactional @Test void testGetForNotExistingJobExecution() { - assertNull(dao.getStepExecution(new JobExecution(jobInstance, (long) 777, new JobParameters()), 11L)); + assertNull(dao.getStepExecution(new JobExecution(jobInstance, 777L, new JobParameters()), 11L)); } /** @@ -233,7 +233,7 @@ void testGetForNotExistingJobExecution() { @Transactional @Test void testSaveExecutionWithIdAlreadySet() { - stepExecution.setId((long) 7); + stepExecution.setId(7L); assertThrows(IllegalArgumentException.class, () -> dao.saveStepExecution(stepExecution)); } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/OptimisticLockingFailureTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/OptimisticLockingFailureTests.java index 7443fbc3fc..e9d562f4b6 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/OptimisticLockingFailureTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/OptimisticLockingFailureTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2023 the original author or authors. + * Copyright 2014-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,13 +22,12 @@ 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.JobParametersBuilder; -import org.springframework.batch.core.StepContribution; -import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; +import org.springframework.batch.core.step.StepContribution; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.launch.JobOperator; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.scope.context.ChunkContext; @@ -47,17 +46,17 @@ class OptimisticLockingFailureTests { private static final Set END_STATUSES = EnumSet.of(BatchStatus.COMPLETED, BatchStatus.FAILED, BatchStatus.STOPPED); + @SuppressWarnings("removal") @Test void testAsyncStopOfStartingJob() throws Exception { ApplicationContext applicationContext = new ClassPathXmlApplicationContext( "org/springframework/batch/core/repository/dao/OptimisticLockingFailureTests-context.xml"); Job job = applicationContext.getBean(Job.class); - JobLauncher jobLauncher = applicationContext.getBean(JobLauncher.class); JobOperator jobOperator = applicationContext.getBean(JobOperator.class); JobRepository jobRepository = applicationContext.getBean(JobRepository.class); JobParameters jobParameters = new JobParametersBuilder().addLong("test", 1L).toJobParameters(); - JobExecution jobExecution = jobLauncher.run(job, jobParameters); + JobExecution jobExecution = jobOperator.start(job, jobParameters); Thread.sleep(1000); @@ -81,7 +80,7 @@ void testAsyncStopOfStartingJob() throws Exception { assertEquals(jobExecutionStatus, BatchStatus.STOPPED, "Job execution status should be STOPPED but got:" + jobExecutionStatus); - JobExecution restartJobExecution = jobLauncher.run(job, jobParameters); + JobExecution restartJobExecution = jobOperator.start(job, jobParameters); Thread.sleep(1000); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/TablePrefixTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/TablePrefixTests.java index cf532620c1..20618c2c08 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/TablePrefixTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/TablePrefixTests.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. @@ -21,11 +21,11 @@ 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.StepContribution; -import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.step.StepContribution; +import org.springframework.batch.core.launch.JobOperator; import org.springframework.batch.core.scope.context.ChunkContext; import org.springframework.batch.core.step.tasklet.Tasklet; import org.springframework.batch.repeat.RepeatStatus; @@ -39,7 +39,7 @@ class TablePrefixTests { @Autowired - private JobLauncher jobLauncher; + private JobOperator jobOperator; @Autowired private Job job; @@ -53,7 +53,7 @@ public void setDataSource(DataSource dataSource) { @Test void testJobLaunch() throws Exception { - JobExecution jobExecution = jobLauncher.run(job, new JobParameters()); + JobExecution jobExecution = jobOperator.start(job, new JobParameters()); assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus()); assertEquals(1, JdbcTestUtils.countRowsInTable(jdbcTemplate, "PREFIX_JOB_INSTANCE")); } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/JobExecutionListenerSupport.java b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/jdbc/CustomJobKeyGenerator.java similarity index 51% rename from spring-batch-core/src/main/java/org/springframework/batch/core/listener/JobExecutionListenerSupport.java rename to spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/jdbc/CustomJobKeyGenerator.java index fe54e5a6b6..be67ecfdc5 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/JobExecutionListenerSupport.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/jdbc/CustomJobKeyGenerator.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-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. @@ -13,25 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.batch.core.listener; +package org.springframework.batch.core.repository.dao.jdbc; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobExecutionListener; +import org.jetbrains.annotations.NotNull; +import org.springframework.batch.core.job.JobKeyGenerator; +import org.springframework.batch.core.job.parameters.JobParameters; -/** - * @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) { - } +public class CustomJobKeyGenerator implements JobKeyGenerator { @Override - public void beforeJob(JobExecution jobExecution) { + public @NotNull String generateKey(@NotNull JobParameters source) { + return "1"; } -} +} \ No newline at end of file diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/JdbcExecutionContextDaoTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/jdbc/JdbcExecutionContextDaoTests.java similarity index 91% rename from spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/JdbcExecutionContextDaoTests.java rename to spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/jdbc/JdbcExecutionContextDaoTests.java index b7f69611bc..c0b8404b16 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/JdbcExecutionContextDaoTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/jdbc/JdbcExecutionContextDaoTests.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. @@ -13,11 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.batch.core.repository.dao; +package org.springframework.batch.core.repository.dao.jdbc; import org.junit.jupiter.api.Test; -import org.springframework.jdbc.core.JdbcOperations; +import org.springframework.batch.core.repository.dao.*; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; import static org.junit.jupiter.api.Assertions.assertEquals; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/JdbcJobDaoQueryTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/jdbc/JdbcJobDaoQueryTests.java similarity index 88% rename from spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/JdbcJobDaoQueryTests.java rename to spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/jdbc/JdbcJobDaoQueryTests.java index 38ae4477eb..cd09c036e9 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/JdbcJobDaoQueryTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/jdbc/JdbcJobDaoQueryTests.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. @@ -13,16 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.batch.core.repository.dao; +package org.springframework.batch.core.repository.dao.jdbc; import java.util.ArrayList; import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobInstance; -import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobInstance; +import org.springframework.batch.core.job.parameters.JobParameters; import org.springframework.dao.DataAccessException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.support.incrementer.DataFieldMaxValueIncrementer; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/JdbcJobDaoTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/jdbc/JdbcJobDaoTests.java similarity index 89% rename from spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/JdbcJobDaoTests.java rename to spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/jdbc/JdbcJobDaoTests.java index f7ad628d30..34b7b3ea28 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/JdbcJobDaoTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/jdbc/JdbcJobDaoTests.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. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.batch.core.repository.dao; +package org.springframework.batch.core.repository.dao.jdbc; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -24,6 +24,8 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.batch.core.ExitStatus; +import org.springframework.batch.core.repository.dao.AbstractJdbcBatchMetadataDao; +import org.springframework.batch.core.repository.dao.AbstractJobDaoTests; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; import org.springframework.transaction.annotation.Transactional; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/JdbcJobExecutionDaoTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/jdbc/JdbcJobExecutionDaoTests.java similarity index 87% rename from spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/JdbcJobExecutionDaoTests.java rename to spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/jdbc/JdbcJobExecutionDaoTests.java index 67ceaeb5e4..e112d731b9 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/JdbcJobExecutionDaoTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/jdbc/JdbcJobExecutionDaoTests.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. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.batch.core.repository.dao; +package org.springframework.batch.core.repository.dao.jdbc; import java.time.LocalDate; import java.time.LocalDateTime; @@ -27,10 +27,14 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobParameter; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.JobParametersBuilder; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameter; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; +import org.springframework.batch.core.repository.dao.AbstractJobExecutionDaoTests; +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.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/JdbcJobInstanceDaoCustomTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/jdbc/JdbcJobInstanceDaoCustomTests.java similarity index 84% rename from spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/JdbcJobInstanceDaoCustomTests.java rename to spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/jdbc/JdbcJobInstanceDaoCustomTests.java index 9bf44049fb..e954eed026 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/JdbcJobInstanceDaoCustomTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/jdbc/JdbcJobInstanceDaoCustomTests.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. @@ -13,12 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.batch.core.repository.dao; +package org.springframework.batch.core.repository.dao.jdbc; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.JobKeyGenerator; +import org.springframework.batch.core.job.JobKeyGenerator; +import org.springframework.batch.core.repository.dao.JobInstanceDao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; @@ -49,12 +50,3 @@ public void testCustomJobKeyGeneratorIsUsed() { } } - -class CustomJobKeyGenerator implements JobKeyGenerator { - - @Override - public String generateKey(String source) { - return "1"; - } - -} diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/JdbcJobInstanceDaoTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/jdbc/JdbcJobInstanceDaoTests.java similarity index 84% rename from spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/JdbcJobInstanceDaoTests.java rename to spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/jdbc/JdbcJobInstanceDaoTests.java index e1a6f0dbd9..ede1f59d58 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/JdbcJobInstanceDaoTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/jdbc/JdbcJobInstanceDaoTests.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. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.batch.core.repository.dao; +package org.springframework.batch.core.repository.dao.jdbc; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -28,11 +28,14 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.DefaultJobKeyGenerator; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobInstance; -import org.springframework.batch.core.JobKeyGenerator; -import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.job.DefaultJobKeyGenerator; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobInstance; +import org.springframework.batch.core.job.JobKeyGenerator; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.repository.dao.AbstractJobInstanceDaoTests; +import org.springframework.batch.core.repository.dao.JobExecutionDao; +import org.springframework.batch.core.repository.dao.JobInstanceDao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; @@ -95,7 +98,7 @@ void testJobInstanceWildcard() { dao.createJobInstance("anotherJob", new JobParameters()); dao.createJobInstance("someJob", new JobParameters()); - List jobInstances = dao.findJobInstancesByName("*Job", 0, 2); + List jobInstances = dao.getJobInstances("*Job", 0, 2); assertEquals(2, jobInstances.size()); for (JobInstance instance : jobInstances) { diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/JdbcStepExecutionDaoTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/jdbc/JdbcStepExecutionDaoTests.java similarity index 86% rename from spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/JdbcStepExecutionDaoTests.java rename to spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/jdbc/JdbcStepExecutionDaoTests.java index f4651a2479..857f176fec 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/JdbcStepExecutionDaoTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/jdbc/JdbcStepExecutionDaoTests.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. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.batch.core.repository.dao; +package org.springframework.batch.core.repository.dao.jdbc; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; @@ -21,8 +21,10 @@ import org.junit.jupiter.api.Test; import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.core.repository.dao.AbstractStepExecutionDaoTests; +import org.springframework.batch.core.repository.dao.StepExecutionDao; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; import org.springframework.transaction.annotation.Transactional; @@ -31,14 +33,14 @@ class JdbcStepExecutionDaoTests extends AbstractStepExecutionDaoTests { @Override protected StepExecutionDao getStepExecutionDao() { - return (StepExecutionDao) applicationContext.getBean("stepExecutionDao"); + return applicationContext.getBean("stepExecutionDao", StepExecutionDao.class); } @Override protected JobRepository getJobRepository() { deleteFromTables("BATCH_JOB_EXECUTION_CONTEXT", "BATCH_STEP_EXECUTION_CONTEXT", "BATCH_STEP_EXECUTION", "BATCH_JOB_EXECUTION_PARAMS", "BATCH_JOB_EXECUTION", "BATCH_JOB_INSTANCE"); - return (JobRepository) applicationContext.getBean("jobRepository"); + return applicationContext.getBean("jobRepository", JobRepository.class); } /** 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..4963d7db21 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. @@ -22,7 +22,6 @@ import javax.sql.DataSource; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; @@ -30,9 +29,9 @@ import org.springframework.aop.Advisor; import org.springframework.aop.framework.Advised; -import org.springframework.batch.core.DefaultJobKeyGenerator; -import org.springframework.batch.core.JobKeyGenerator; -import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.job.DefaultJobKeyGenerator; +import org.springframework.batch.core.job.JobKeyGenerator; +import org.springframework.batch.core.job.parameters.JobParameters; import org.springframework.batch.core.repository.ExecutionContextSerializer; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.repository.dao.DefaultExecutionContextSerializer; @@ -42,8 +41,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; @@ -64,8 +61,10 @@ * @author Mahmoud Ben Hassine * */ +@SuppressWarnings("removal") class JobRepositoryFactoryBeanTests { + @SuppressWarnings("removal") private JobRepositoryFactoryBean factory; private DataFieldMaxValueIncrementerFactory incrementerFactory; @@ -101,56 +100,15 @@ void testNoDatabaseType() throws Exception { when(incrementerFactory.isSupportedIncrementerType("ORACLE")).thenReturn(true); when(incrementerFactory.getSupportedIncrementerTypes()).thenReturn(new String[0]); - 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.afterPropertiesSet(); - factory.getObject(); - - } - - @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")) + when(incrementerFactory.getIncrementer("ORACLE", tablePrefix + "JOB_INSTANCE_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")); + factory.getObject(); } @@ -162,7 +120,8 @@ void tesDefaultSerializer() throws Exception { incrementerFactory = mock(); when(incrementerFactory.isSupportedIncrementerType("ORACLE")).thenReturn(true); - when(incrementerFactory.getIncrementer("ORACLE", tablePrefix + "JOB_SEQ")).thenReturn(new StubIncrementer()); + when(incrementerFactory.getIncrementer("ORACLE", tablePrefix + "JOB_INSTANCE_SEQ")) + .thenReturn(new StubIncrementer()); when(incrementerFactory.getIncrementer("ORACLE", tablePrefix + "JOB_EXECUTION_SEQ")) .thenReturn(new StubIncrementer()); when(incrementerFactory.getIncrementer("ORACLE", tablePrefix + "STEP_EXECUTION_SEQ")) @@ -182,7 +141,8 @@ void testCustomSerializer() throws Exception { incrementerFactory = mock(); when(incrementerFactory.isSupportedIncrementerType("ORACLE")).thenReturn(true); - when(incrementerFactory.getIncrementer("ORACLE", tablePrefix + "JOB_SEQ")).thenReturn(new StubIncrementer()); + when(incrementerFactory.getIncrementer("ORACLE", tablePrefix + "JOB_INSTANCE_SEQ")) + .thenReturn(new StubIncrementer()); when(incrementerFactory.getIncrementer("ORACLE", tablePrefix + "JOB_EXECUTION_SEQ")) .thenReturn(new StubIncrementer()); when(incrementerFactory.getIncrementer("ORACLE", tablePrefix + "STEP_EXECUTION_SEQ")) @@ -203,7 +163,8 @@ void testDefaultJdbcOperations() throws Exception { incrementerFactory = mock(); when(incrementerFactory.isSupportedIncrementerType("ORACLE")).thenReturn(true); - when(incrementerFactory.getIncrementer("ORACLE", tablePrefix + "JOB_SEQ")).thenReturn(new StubIncrementer()); + when(incrementerFactory.getIncrementer("ORACLE", tablePrefix + "JOB_INSTANCE_SEQ")) + .thenReturn(new StubIncrementer()); when(incrementerFactory.getIncrementer("ORACLE", tablePrefix + "JOB_EXECUTION_SEQ")) .thenReturn(new StubIncrementer()); when(incrementerFactory.getIncrementer("ORACLE", tablePrefix + "STEP_EXECUTION_SEQ")) @@ -223,7 +184,8 @@ void testCustomJdbcOperations() throws Exception { incrementerFactory = mock(); when(incrementerFactory.isSupportedIncrementerType("ORACLE")).thenReturn(true); - when(incrementerFactory.getIncrementer("ORACLE", tablePrefix + "JOB_SEQ")).thenReturn(new StubIncrementer()); + when(incrementerFactory.getIncrementer("ORACLE", tablePrefix + "JOB_INSTANCE_SEQ")) + .thenReturn(new StubIncrementer()); when(incrementerFactory.getIncrementer("ORACLE", tablePrefix + "JOB_EXECUTION_SEQ")) .thenReturn(new StubIncrementer()); when(incrementerFactory.getIncrementer("ORACLE", tablePrefix + "STEP_EXECUTION_SEQ")) @@ -282,7 +244,7 @@ void testCreateRepository() throws Exception { when(incrementerFactory.isSupportedIncrementerType("HSQL")).thenReturn(true); when(incrementerFactory.getSupportedIncrementerTypes()).thenReturn(new String[0]); - when(incrementerFactory.getIncrementer(databaseType, tablePrefix + "JOB_SEQ")) + when(incrementerFactory.getIncrementer(databaseType, tablePrefix + "JOB_INSTANCE_SEQ")) .thenReturn(new StubIncrementer()); when(incrementerFactory.getIncrementer(databaseType, tablePrefix + "JOB_EXECUTION_SEQ")) .thenReturn(new StubIncrementer()); @@ -355,8 +317,7 @@ public void testCustomTransactionAttributesSource() throws Exception { Advisor[] advisors = target.getAdvisors(); for (Advisor advisor : advisors) { if (advisor.getAdvice() instanceof TransactionInterceptor transactionInterceptor) { - Assertions.assertEquals(transactionAttributeSource, - transactionInterceptor.getTransactionAttributeSource()); + assertEquals(transactionAttributeSource, transactionInterceptor.getTransactionAttributeSource()); } } } @@ -379,7 +340,7 @@ void testCustomLobType() throws Exception { public void testDefaultJobKeyGenerator() throws Exception { testCreateRepository(); JobKeyGenerator jobKeyGenerator = (JobKeyGenerator) ReflectionTestUtils.getField(factory, "jobKeyGenerator"); - Assertions.assertEquals(DefaultJobKeyGenerator.class, jobKeyGenerator.getClass()); + assertEquals(DefaultJobKeyGenerator.class, jobKeyGenerator.getClass()); } @Test @@ -387,13 +348,13 @@ public void testCustomJobKeyGenerator() throws Exception { factory.setJobKeyGenerator(new CustomJobKeyGenerator()); testCreateRepository(); JobKeyGenerator jobKeyGenerator = (JobKeyGenerator) ReflectionTestUtils.getField(factory, "jobKeyGenerator"); - Assertions.assertEquals(CustomJobKeyGenerator.class, jobKeyGenerator.getClass()); + assertEquals(CustomJobKeyGenerator.class, jobKeyGenerator.getClass()); } - class CustomJobKeyGenerator implements JobKeyGenerator { + static class CustomJobKeyGenerator implements JobKeyGenerator { @Override - public String generateKey(String source) { + public String generateKey(JobParameters source) { return "1"; } 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..33a1d2b8b6 --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoDBIntegrationTestConfiguration.java @@ -0,0 +1,94 @@ +/* + * 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. + * 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 org.springframework.batch.core.job.Job; +import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; +import org.springframework.batch.core.configuration.annotation.EnableMongoJobRepository; +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.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.testcontainers.containers.MongoDBContainer; +import org.testcontainers.utility.DockerImageName; + +/** + * @author Mahmoud Ben Hassine + * @author Yanming Zhou + */ +@Configuration +@EnableBatchProcessing +@EnableMongoJobRepository +class MongoDBIntegrationTestConfiguration { + + private static final DockerImageName MONGODB_IMAGE = DockerImageName.parse("mongo:8.0.11"); + + @Bean(initMethod = "start") + public MongoDBContainer mongoDBContainer() { + return new MongoDBContainer(MONGODB_IMAGE); + } + + @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 MongoDatabaseFactory mongoDatabaseFactory(MongoDBContainer mongoDBContainer) { + return new SimpleMongoClientDatabaseFactory(mongoDBContainer.getConnectionString() + "/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..dc832c80a6 --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoDBJobExplorerIntegrationTests.java @@ -0,0 +1,105 @@ +/* + * 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. + * 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.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.core.step.StepExecution; +import org.springframework.batch.core.launch.JobOperator; +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.junit.jupiter.SpringJUnitConfig; +import org.testcontainers.junit.jupiter.Testcontainers; + +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 + * @author Yanming Zhou + */ +@DirtiesContext +@Testcontainers(disabledWithoutDocker = true) +@SpringJUnitConfig(MongoDBIntegrationTestConfiguration.class) +public class MongoDBJobExplorerIntegrationTests { + + @Autowired + private JobRepository jobRepository; + + @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 JobOperator jobOperator, @Autowired Job job, + @Autowired JobRepository jobRepository) throws Exception { + // given + JobParameters jobParameters = new JobParametersBuilder().addString("name", "testGetJobExecutionById") + .addLocalDateTime("runtime", LocalDateTime.now()) + .toJobParameters(); + JobExecution jobExecution = jobOperator.start(job, jobParameters); + + // when + JobExecution actual = jobRepository.getJobExecution(jobExecution.getId()); + + // then + assertNotNull(actual); + assertNotNull(actual.getJobInstance()); + assertEquals(jobExecution.getJobId(), actual.getJobId()); + assertFalse(actual.getExecutionContext().isEmpty()); + } + + @Test + void testGetStepExecutionByIds(@Autowired JobOperator jobOperator, @Autowired Job job, + @Autowired JobRepository jobRepository) throws Exception { + // given + JobParameters jobParameters = new JobParametersBuilder().addString("name", "testGetStepExecutionByIds") + .addLocalDateTime("runtime", LocalDateTime.now()) + .toJobParameters(); + JobExecution jobExecution = jobOperator.start(job, jobParameters); + StepExecution stepExecution = jobExecution.getStepExecutions().stream().findFirst().orElseThrow(); + + // when + StepExecution actual = jobRepository.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 3499b51939..9e72075de4 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. @@ -16,82 +16,83 @@ 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; 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.testcontainers.containers.MongoDBContainer; -import org.testcontainers.junit.jupiter.Container; +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.junit.jupiter.SpringJUnitConfig; import org.testcontainers.junit.jupiter.Testcontainers; -import org.testcontainers.utility.DockerImageName; import org.springframework.batch.core.ExitStatus; -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.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.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; +import org.springframework.batch.core.launch.JobOperator; 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 + * @author Yanming Zhou */ +@DirtiesContext @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"); - - @Container - public static MongoDBContainer mongodb = new MongoDBContainer(MONGODB_IMAGE); - @Autowired private MongoTemplate mongoTemplate; + @SuppressWarnings("removal") @BeforeEach public void setUp() { + // collections 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)); + // sequences + 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))); + // 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 - void testJobExecution(@Autowired JobLauncher jobLauncher, @Autowired Job job) throws Exception { + void testJobExecution(@Autowired JobOperator jobOperator, @Autowired Job job) throws Exception { // given JobParameters jobParameters = new JobParametersBuilder().addString("name", "foo") .addLocalDateTime("runtime", LocalDateTime.now()) .toJobParameters(); // when - JobExecution jobExecution = jobLauncher.run(job, jobParameters); + JobExecution jobExecution = jobOperator.start(job, jobParameters); // then Assertions.assertNotNull(jobExecution); @@ -117,64 +118,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..d282ff5304 --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoExecutionContextDaoIntegrationTests.java @@ -0,0 +1,146 @@ +/* + * 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. + * 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.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; +import org.springframework.batch.core.step.StepExecution; +import org.springframework.batch.core.launch.JobOperator; +import org.springframework.batch.core.repository.dao.ExecutionContextDao; +import org.springframework.batch.core.repository.dao.mongodb.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.annotation.DirtiesContext; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; +import org.testcontainers.junit.jupiter.Testcontainers; + +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 + * @author Yanming Zhou + */ +@DirtiesContext +@Testcontainers(disabledWithoutDocker = true) +@SpringJUnitConfig({ MongoDBIntegrationTestConfiguration.class, ExecutionContextDaoConfiguration.class }) +public class MongoExecutionContextDaoIntegrationTests { + + @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 JobOperator jobOperator, @Autowired Job job, + @Autowired ExecutionContextDao executionContextDao) throws Exception { + // given + JobParameters jobParameters = new JobParametersBuilder().addString("name", "testSaveJobExecution") + .addLocalDateTime("runtime", LocalDateTime.now()) + .toJobParameters(); + JobExecution jobExecution = jobOperator.start(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 JobOperator jobOperator, @Autowired Job job, + @Autowired ExecutionContextDao executionContextDao) throws Exception { + // given + JobParameters jobParameters = new JobParametersBuilder().addString("name", "testSaveJobExecution") + .addLocalDateTime("runtime", LocalDateTime.now()) + .toJobParameters(); + JobExecution jobExecution = jobOperator.start(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); + } + + } + +} diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/ResourcelessJobRepositoryTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/ResourcelessJobRepositoryTests.java index 9e5f6d6386..923a2a44f5 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/ResourcelessJobRepositoryTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/ResourcelessJobRepositoryTests.java @@ -17,9 +17,9 @@ import org.junit.jupiter.api.Test; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobInstance; -import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobInstance; +import org.springframework.batch.core.job.parameters.JobParameters; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/SimpleJobRepositoryIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/SimpleJobRepositoryIntegrationTests.java index eb81dbd246..3d431d0d98 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/SimpleJobRepositoryIntegrationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/SimpleJobRepositoryIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 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. @@ -17,11 +17,11 @@ import org.junit.jupiter.api.Test; import org.springframework.batch.core.BatchStatus; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.JobParametersBuilder; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.job.JobSupport; import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException; import org.springframework.batch.core.step.StepSupport; @@ -47,7 +47,7 @@ * @author Dimitrios Liapis * @author Mahmoud Ben Hassine */ -@SpringJUnitConfig(locations = "/org/springframework/batch/core/repository/dao/sql-dao-test.xml") +@SpringJUnitConfig(locations = "/org/springframework/batch/core/repository/dao/jdbc/sql-dao-test.xml") class SimpleJobRepositoryIntegrationTests { @Autowired @@ -258,7 +258,7 @@ void testDeleteJobInstance() throws Exception { jobRepository.deleteJobInstance(jobExecution.getJobInstance()); - assertEquals(0, jobRepository.findJobInstancesByName(job.getName(), 0, 1).size()); + assertEquals(0, jobRepository.getJobInstances(job.getName(), 0, 1).size()); assertNull(jobRepository.getLastJobExecution(job.getName(), jobParameters)); } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/SimpleJobRepositoryProxyTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/SimpleJobRepositoryProxyTests.java index 2bf2f0271c..93969bed1b 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/SimpleJobRepositoryProxyTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/SimpleJobRepositoryProxyTests.java @@ -23,8 +23,8 @@ import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; import org.springframework.batch.core.job.JobSupport; import org.springframework.batch.core.repository.JobRepository; import org.springframework.beans.factory.annotation.Autowired; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/SimpleJobRepositoryTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/SimpleJobRepositoryTests.java index 5b903d0f14..5a9de77e0f 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/SimpleJobRepositoryTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/SimpleJobRepositoryTests.java @@ -17,9 +17,9 @@ package org.springframework.batch.core.repository.support; 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 static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -34,12 +34,12 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.batch.core.BatchStatus; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobInstance; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.JobParametersBuilder; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobInstance; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.job.JobSupport; import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException; import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException; @@ -140,6 +140,7 @@ void testGetJobNames() { verify(this.jobInstanceDao).getJobNames(); } + @SuppressWarnings("removal") @Test void testFindJobInstancesByName() { // given @@ -151,9 +152,10 @@ void testFindJobInstancesByName() { this.jobRepository.findJobInstancesByName(jobName, start, count); // then - verify(this.jobInstanceDao).findJobInstancesByName(jobName, start, count); + verify(this.jobInstanceDao).getJobInstances(jobName, start, count); } + @SuppressWarnings("removal") @Test void testFindJobExecutions() { // when @@ -253,14 +255,14 @@ void testInterrupted() { @Test void testIsJobInstanceFalse() { jobInstanceDao.getJobInstance("foo", new JobParameters()); - assertFalse(jobRepository.isJobInstanceExists("foo", new JobParameters())); + assertNull(jobRepository.getJobInstance("foo", new JobParameters())); } @Test void testIsJobInstanceTrue() { when(jobInstanceDao.getJobInstance("foo", new JobParameters())).thenReturn(jobInstance); jobInstanceDao.getJobInstance("foo", new JobParameters()); - assertTrue(jobRepository.isJobInstanceExists("foo", new JobParameters())); + assertNotNull(jobRepository.getJobInstance("foo", new JobParameters())); } @Test diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/resource/StepExecutionSimpleCompletionPolicyTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/resource/StepExecutionSimpleCompletionPolicyTests.java index fc33b3a5a6..bd89fe7a44 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/resource/StepExecutionSimpleCompletionPolicyTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/resource/StepExecutionSimpleCompletionPolicyTests.java @@ -18,12 +18,12 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobInstance; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.JobParametersBuilder; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobInstance; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.step.StepSupport; import org.springframework.batch.repeat.RepeatContext; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/scope/AsyncJobScopeIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/scope/AsyncJobScopeIntegrationTests.java index 5af17368ec..2b531764f6 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/scope/AsyncJobScopeIntegrationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/scope/AsyncJobScopeIntegrationTests.java @@ -26,7 +26,7 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.JobExecution; +import org.springframework.batch.core.job.JobExecution; import org.springframework.batch.core.scope.context.JobContext; import org.springframework.batch.core.scope.context.JobSynchronizationManager; import org.springframework.batch.item.ExecutionContext; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/scope/AsyncStepScopeIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/scope/AsyncStepScopeIntegrationTests.java index bb84eb9b7d..04fa28dd9f 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/scope/AsyncStepScopeIntegrationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/scope/AsyncStepScopeIntegrationTests.java @@ -26,8 +26,8 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.scope.context.StepContext; import org.springframework.batch.core.scope.context.StepSynchronizationManager; import org.springframework.batch.item.ExecutionContext; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/scope/JobScopeDestructionCallbackIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/scope/JobScopeDestructionCallbackIntegrationTests.java index 348b77bc29..4377536f88 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/scope/JobScopeDestructionCallbackIntegrationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/scope/JobScopeDestructionCallbackIntegrationTests.java @@ -21,8 +21,8 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobExecution; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/scope/JobScopeIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/scope/JobScopeIntegrationTests.java index 84e5ab9b9e..5258529ef3 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/scope/JobScopeIntegrationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/scope/JobScopeIntegrationTests.java @@ -23,8 +23,8 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobExecution; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; import org.springframework.batch.core.scope.context.JobSynchronizationManager; import org.springframework.batch.item.ExecutionContext; import org.springframework.beans.factory.annotation.Autowired; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/scope/JobScopeNestedIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/scope/JobScopeNestedIntegrationTests.java index c9073a2161..204fdd8870 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/scope/JobScopeNestedIntegrationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/scope/JobScopeNestedIntegrationTests.java @@ -19,7 +19,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.Job; +import org.springframework.batch.core.job.Job; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/scope/JobScopePlaceholderIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/scope/JobScopePlaceholderIntegrationTests.java index 7a18220450..7ab1f0cc46 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/scope/JobScopePlaceholderIntegrationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/scope/JobScopePlaceholderIntegrationTests.java @@ -20,7 +20,7 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.JobExecution; +import org.springframework.batch.core.job.JobExecution; import org.springframework.batch.core.scope.context.JobSynchronizationManager; import org.springframework.batch.item.ExecutionContext; import org.springframework.beans.BeansException; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/scope/JobScopeProxyTargetClassIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/scope/JobScopeProxyTargetClassIntegrationTests.java index 11e91fefb0..53696e40c8 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/scope/JobScopeProxyTargetClassIntegrationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/scope/JobScopeProxyTargetClassIntegrationTests.java @@ -20,7 +20,7 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.JobExecution; +import org.springframework.batch.core.job.JobExecution; import org.springframework.batch.core.scope.context.JobSynchronizationManager; import org.springframework.batch.item.ExecutionContext; import org.springframework.beans.BeansException; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/scope/JobScopeTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/scope/JobScopeTests.java index 1250859098..4508fa8104 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/scope/JobScopeTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/scope/JobScopeTests.java @@ -27,7 +27,7 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.JobExecution; +import org.springframework.batch.core.job.JobExecution; import org.springframework.batch.core.scope.context.JobContext; import org.springframework.batch.core.scope.context.JobSynchronizationManager; import org.springframework.beans.factory.ObjectFactory; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/scope/JobStartupRunner.java b/spring-batch-core/src/test/java/org/springframework/batch/core/scope/JobStartupRunner.java index fd91622710..e79eab55dd 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/scope/JobStartupRunner.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/scope/JobStartupRunner.java @@ -15,8 +15,8 @@ */ package org.springframework.batch.core.scope; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobExecution; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; import org.springframework.beans.factory.InitializingBean; public class JobStartupRunner implements InitializingBean { diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/scope/StepScopeClassIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/scope/StepScopeClassIntegrationTests.java index d9e0f7723e..3e96fa6934 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/scope/StepScopeClassIntegrationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/scope/StepScopeClassIntegrationTests.java @@ -21,8 +21,8 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.scope.context.StepSynchronizationManager; import org.springframework.batch.item.ExecutionContext; import org.springframework.beans.BeansException; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/scope/StepScopeDestructionCallbackIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/scope/StepScopeDestructionCallbackIntegrationTests.java index a1d262de18..d8d80b551a 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/scope/StepScopeDestructionCallbackIntegrationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/scope/StepScopeDestructionCallbackIntegrationTests.java @@ -21,9 +21,9 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.step.StepExecution; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/scope/StepScopeIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/scope/StepScopeIntegrationTests.java index 6e7e759519..8fe01964cf 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/scope/StepScopeIntegrationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/scope/StepScopeIntegrationTests.java @@ -23,9 +23,9 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.scope.context.StepSynchronizationManager; import org.springframework.batch.item.ExecutionContext; import org.springframework.beans.factory.annotation.Autowired; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/scope/StepScopeNestedIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/scope/StepScopeNestedIntegrationTests.java index 176e071e47..7424d8d3ed 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/scope/StepScopeNestedIntegrationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/scope/StepScopeNestedIntegrationTests.java @@ -19,7 +19,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.Step; +import org.springframework.batch.core.step.Step; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/scope/StepScopePerformanceTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/scope/StepScopePerformanceTests.java index 2212eefa16..d478ace432 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/scope/StepScopePerformanceTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/scope/StepScopePerformanceTests.java @@ -20,8 +20,8 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.scope.context.StepSynchronizationManager; import org.springframework.batch.item.ExecutionContext; import org.springframework.batch.item.ItemStreamReader; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/scope/StepScopePlaceholderIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/scope/StepScopePlaceholderIntegrationTests.java index 95ee5e44ee..dc396259c3 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/scope/StepScopePlaceholderIntegrationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/scope/StepScopePlaceholderIntegrationTests.java @@ -20,8 +20,8 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.scope.context.StepSynchronizationManager; import org.springframework.batch.item.ExecutionContext; import org.springframework.beans.BeansException; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/scope/StepScopeProxyTargetClassIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/scope/StepScopeProxyTargetClassIntegrationTests.java index f63a84ae26..17ed09ef19 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/scope/StepScopeProxyTargetClassIntegrationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/scope/StepScopeProxyTargetClassIntegrationTests.java @@ -20,8 +20,8 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.scope.context.StepSynchronizationManager; import org.springframework.batch.item.ExecutionContext; import org.springframework.beans.BeansException; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/scope/StepScopeProxyTargetClassOverrideIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/scope/StepScopeProxyTargetClassOverrideIntegrationTests.java index 6fb2d04ebb..95aa48e816 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/scope/StepScopeProxyTargetClassOverrideIntegrationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/scope/StepScopeProxyTargetClassOverrideIntegrationTests.java @@ -23,9 +23,9 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.aop.support.AopUtils; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.scope.context.StepSynchronizationManager; import org.springframework.batch.item.ExecutionContext; import org.springframework.beans.BeansException; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/scope/StepScopeTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/scope/StepScopeTests.java index 2309d10045..f17d3ff861 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/scope/StepScopeTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/scope/StepScopeTests.java @@ -28,8 +28,8 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.scope.context.StepContext; import org.springframework.batch.core.scope.context.StepSynchronizationManager; import org.springframework.context.support.StaticApplicationContext; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/scope/StepStartupRunner.java b/spring-batch-core/src/test/java/org/springframework/batch/core/scope/StepStartupRunner.java index 058ec0848e..949119df5d 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/scope/StepStartupRunner.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/scope/StepStartupRunner.java @@ -15,9 +15,9 @@ */ package org.springframework.batch.core.scope; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.step.StepExecution; import org.springframework.beans.factory.InitializingBean; public class StepStartupRunner implements InitializingBean { diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/scope/TestJob.java b/spring-batch-core/src/test/java/org/springframework/batch/core/scope/TestJob.java index 8639add606..aadec6567a 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/scope/TestJob.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/scope/TestJob.java @@ -15,10 +15,10 @@ */ package org.springframework.batch.core.scope; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobParametersIncrementer; -import org.springframework.batch.core.JobParametersValidator; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParametersIncrementer; +import org.springframework.batch.core.job.parameters.JobParametersValidator; import org.springframework.batch.core.scope.context.JobContext; import org.springframework.batch.core.scope.context.JobSynchronizationManager; import org.springframework.lang.Nullable; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/scope/TestStep.java b/spring-batch-core/src/test/java/org/springframework/batch/core/scope/TestStep.java index eed227ffcf..77eb0b14a2 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/scope/TestStep.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/scope/TestStep.java @@ -15,9 +15,9 @@ */ package org.springframework.batch.core.scope; -import org.springframework.batch.core.JobInterruptedException; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobInterruptedException; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.scope.context.StepContext; import org.springframework.batch.core.scope.context.StepSynchronizationManager; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/scope/context/ChunkContextTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/scope/context/ChunkContextTests.java index 6024ce0592..1418a44a33 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/scope/context/ChunkContextTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/scope/context/ChunkContextTests.java @@ -23,10 +23,10 @@ import java.util.Collections; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobInstance; -import org.springframework.batch.core.JobParameter; -import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobInstance; +import org.springframework.batch.core.job.parameters.JobParameter; +import org.springframework.batch.core.job.parameters.JobParameters; /** * @author Dave Syer diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/scope/context/InternalBeanStepScopeIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/scope/context/InternalBeanStepScopeIntegrationTests.java index 97134be952..13ea70d5d4 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/scope/context/InternalBeanStepScopeIntegrationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/scope/context/InternalBeanStepScopeIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2022 the original author or authors. + * Copyright 2014-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,10 +17,10 @@ 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.JobParametersBuilder; -import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; +import org.springframework.batch.core.launch.JobOperator; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; @@ -36,9 +36,9 @@ void testCommitIntervalJobParameter() throws Exception { ApplicationContext context = new ClassPathXmlApplicationContext( "/org/springframework/batch/core/scope/context/CommitIntervalJobParameter-context.xml"); Job job = context.getBean(Job.class); - JobLauncher launcher = context.getBean(JobLauncher.class); + JobOperator jobOperator = context.getBean(JobOperator.class); - JobExecution execution = launcher.run(job, + JobExecution execution = jobOperator.start(job, new JobParametersBuilder().addLong("commit.interval", 1l).toJobParameters()); assertEquals(BatchStatus.COMPLETED, execution.getStatus()); @@ -51,9 +51,9 @@ void testInvalidCommitIntervalJobParameter() throws Exception { ApplicationContext context = new ClassPathXmlApplicationContext( "/org/springframework/batch/core/scope/context/CommitIntervalJobParameter-context.xml"); Job job = context.getBean(Job.class); - JobLauncher launcher = context.getBean(JobLauncher.class); + JobOperator jobOperator = context.getBean(JobOperator.class); - JobExecution execution = launcher.run(job, + JobExecution execution = jobOperator.start(job, new JobParametersBuilder().addLong("commit.intervall", 1l).toJobParameters()); assertEquals(BatchStatus.FAILED, execution.getStatus()); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/scope/context/JobContextTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/scope/context/JobContextTests.java index 588b6c57bf..58be0978af 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/scope/context/JobContextTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/scope/context/JobContextTests.java @@ -26,10 +26,10 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobInstance; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.JobParametersBuilder; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobInstance; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; import org.springframework.batch.item.ExecutionContext; /** diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/scope/context/JobSynchronizationManagerTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/scope/context/JobSynchronizationManagerTests.java index 66a9bce9f3..70173d9a5f 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/scope/context/JobSynchronizationManagerTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/scope/context/JobSynchronizationManagerTests.java @@ -29,7 +29,7 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.JobExecution; +import org.springframework.batch.core.job.JobExecution; /** * JobSynchronizationManagerTests. diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/scope/context/StepContextRepeatCallbackTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/scope/context/StepContextRepeatCallbackTests.java index 6cb8576a0c..97c574336e 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/scope/context/StepContextRepeatCallbackTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/scope/context/StepContextRepeatCallbackTests.java @@ -22,8 +22,8 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.repeat.RepeatContext; import org.springframework.batch.repeat.RepeatStatus; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/scope/context/StepContextTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/scope/context/StepContextTests.java index f7f52153fd..56422ba6d6 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/scope/context/StepContextTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/scope/context/StepContextTests.java @@ -25,11 +25,11 @@ import java.util.List; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobInstance; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.JobParametersBuilder; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobInstance; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.item.ExecutionContext; /** diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/scope/context/StepSynchronizationManagerTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/scope/context/StepSynchronizationManagerTests.java index a7c7158c47..76551e5e9a 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/scope/context/StepSynchronizationManagerTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/scope/context/StepSynchronizationManagerTests.java @@ -29,8 +29,8 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.step.StepExecution; class StepSynchronizationManagerTests { diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/AbstractStepTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/AbstractStepTests.java index 8d761fa197..6291e4f731 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/AbstractStepTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/AbstractStepTests.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. @@ -20,11 +20,10 @@ import org.junit.jupiter.api.Test; import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobInstance; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.StepExecutionListener; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobInstance; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.listener.StepExecutionListener; import org.springframework.batch.core.repository.JobRepository; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -39,13 +38,13 @@ class AbstractStepTests { void testEndTimeInListener() throws Exception { // given StepExecution execution = new StepExecution("step", - new JobExecution(new JobInstance(1L, "job"), new JobParameters())); - AbstractStep tested = new AbstractStep() { + new JobExecution(new JobInstance(1L, "job"), 0L, new JobParameters()), 0L); + JobRepository jobRepository = mock(); + AbstractStep tested = new AbstractStep(jobRepository) { @Override protected void doExecute(StepExecution stepExecution) { } }; - JobRepository jobRepository = mock(); Listener stepListener = new Listener(); tested.setStepExecutionListeners(new StepExecutionListener[] { stepListener }); tested.setJobRepository(jobRepository); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/JobRepositorySupport.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/JobRepositorySupport.java index 18fc0a7e95..a22dee0969 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/JobRepositorySupport.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/JobRepositorySupport.java @@ -17,10 +17,9 @@ import java.util.Collection; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobInstance; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobInstance; +import org.springframework.batch.core.job.parameters.JobParameters; import org.springframework.batch.core.repository.JobRepository; import org.springframework.lang.Nullable; @@ -71,6 +70,7 @@ public void update(StepExecution stepExecution) { public void updateExecutionContext(StepExecution stepExecution) { } + @SuppressWarnings("removal") @Override public boolean isJobInstanceExists(String jobName, JobParameters jobParameters) { return false; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/NoWorkFoundStepExecutionListenerTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/NoWorkFoundStepExecutionListenerTests.java index ca6216cda2..2838ab3f67 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/NoWorkFoundStepExecutionListenerTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/NoWorkFoundStepExecutionListenerTests.java @@ -20,10 +20,9 @@ import org.junit.jupiter.api.Test; import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobInstance; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobInstance; +import org.springframework.batch.core.job.parameters.JobParameters; /** * Tests for {@link NoWorkFoundStepExecutionListener}. diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/NonAbstractStepTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/NonAbstractStepTests.java index 5fa6aa5166..f74cab28e7 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/NonAbstractStepTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/NonAbstractStepTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2009-2023 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. @@ -18,24 +18,16 @@ import java.util.ArrayList; import java.util.List; -import io.micrometer.core.instrument.Metrics; -import io.micrometer.core.instrument.Tag; -import io.micrometer.core.instrument.Tags; -import io.micrometer.core.instrument.observation.DefaultMeterObservationHandler; -import io.micrometer.core.tck.MeterRegistryAssert; -import io.micrometer.observation.ObservationRegistry; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; 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.JobInstance; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.StepExecutionListener; -import org.springframework.batch.core.observability.BatchStepObservation; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobInstance; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.listener.StepExecutionListener; +import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.item.ExecutionContext; import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -51,7 +43,7 @@ */ class NonAbstractStepTests { - AbstractStep tested = new EventTrackingStep(); + AbstractStep tested; StepExecutionListener listener1 = new EventTrackingListener("listener1"); @@ -64,15 +56,15 @@ class NonAbstractStepTests { */ final List events = new ArrayList<>(); - final StepExecution execution = new StepExecution(tested.getName(), - new JobExecution(new JobInstance(1L, "jobName"), new JobParameters())); + StepExecution execution; /** * Fills the events list when abstract methods are called. */ private class EventTrackingStep extends AbstractStep { - public EventTrackingStep() { + public EventTrackingStep(JobRepository jobRepository) { + super(jobRepository); setBeanName("eventTrackingStep"); } @@ -156,13 +148,15 @@ public void add(StepExecution stepExecution) { @BeforeEach void setUp() { - tested.setJobRepository(repository); + tested = new EventTrackingStep(repository); + execution = new StepExecution(tested.getName(), + new JobExecution(new JobInstance(1L, "jobName"), 0L, new JobParameters()), 0L); repository.add(execution); } @Test void testBeanName() { - AbstractStep step = new AbstractStep() { + AbstractStep step = new AbstractStep(repository) { @Override protected void doExecute(StepExecution stepExecution) throws Exception { } @@ -174,7 +168,7 @@ protected void doExecute(StepExecution stepExecution) throws Exception { @Test void testName() { - AbstractStep step = new AbstractStep() { + AbstractStep step = new AbstractStep(repository) { @Override protected void doExecute(StepExecution stepExecution) throws Exception { } @@ -193,11 +187,6 @@ protected void doExecute(StepExecution stepExecution) throws Exception { void testExecute() throws Exception { tested.setStepExecutionListeners(new StepExecutionListener[] { listener1, listener2 }); - ObservationRegistry observationRegistry = ObservationRegistry.create(); - observationRegistry.observationConfig() - .observationHandler(new DefaultMeterObservationHandler(Metrics.globalRegistry)); - tested.setObservationRegistry(observationRegistry); - tested.execute(execution); int i = 0; @@ -216,23 +205,11 @@ void testExecute() throws Exception { "Execution context modifications made by listener should be persisted"); assertTrue(repository.saved.containsKey("afterStep"), "Execution context modifications made by listener should be persisted"); - - // Observability - MeterRegistryAssert.assertThat(Metrics.globalRegistry) - .hasTimerWithNameAndTags(BatchStepObservation.BATCH_STEP_OBSERVATION.getName(), - Tags.of(Tag.of("error", "none"), Tag.of("spring.batch.step.job.name", "jobName"), - Tag.of("spring.batch.step.name", "eventTrackingStep"), - Tag.of("spring.batch.step.status", "COMPLETED"))); - } - - @AfterEach - void cleanup() { - Metrics.globalRegistry.clear(); } @Test void testFailure() throws Exception { - tested = new EventTrackingStep() { + tested = new EventTrackingStep(repository) { @Override protected void doExecute(StepExecution context) throws Exception { super.doExecute(context); @@ -270,7 +247,7 @@ protected void doExecute(StepExecution context) throws Exception { */ @Test void testStoppedStep() throws Exception { - tested = new EventTrackingStep() { + tested = new EventTrackingStep(repository) { @Override protected void doExecute(StepExecution context) throws Exception { context.setTerminateOnly(); @@ -303,7 +280,7 @@ protected void doExecute(StepExecution context) throws Exception { @Test void testStoppedStepWithCustomStatus() throws Exception { - tested = new EventTrackingStep() { + tested = new EventTrackingStep(repository) { @Override protected void doExecute(StepExecution context) throws Exception { super.doExecute(context); @@ -330,7 +307,7 @@ protected void doExecute(StepExecution context) throws Exception { */ @Test void testFailureInSavingExecutionContext() throws Exception { - tested = new EventTrackingStep() { + tested = new EventTrackingStep(repository) { @Override protected void doExecute(StepExecution context) throws Exception { super.doExecute(context); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/RestartInPriorStepTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/RestartInPriorStepTests.java index f0816f42ca..77cb0938d3 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/RestartInPriorStepTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/RestartInPriorStepTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 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. @@ -19,14 +19,12 @@ import org.junit.jupiter.api.Test; import org.springframework.batch.core.BatchStatus; import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.StepContribution; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; import org.springframework.batch.core.job.flow.FlowExecutionStatus; import org.springframework.batch.core.job.flow.JobExecutionDecider; -import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.launch.JobOperator; import org.springframework.batch.core.scope.context.ChunkContext; import org.springframework.batch.core.step.tasklet.Tasklet; import org.springframework.batch.repeat.RepeatStatus; @@ -49,19 +47,19 @@ class RestartInPriorStepTests { @Autowired - private JobLauncher jobLauncher; + private JobOperator jobOperator; @Autowired private Job job; @Test void test() throws Exception { - JobExecution run1 = jobLauncher.run(job, new JobParameters()); + JobExecution run1 = jobOperator.start(job, new JobParameters()); assertEquals(BatchStatus.STOPPED, run1.getStatus()); assertEquals(2, run1.getStepExecutions().size()); - JobExecution run2 = jobLauncher.run(job, new JobParameters()); + JobExecution run2 = jobOperator.start(job, new JobParameters()); assertEquals(BatchStatus.COMPLETED, run2.getStatus()); assertEquals(6, run2.getStepExecutions().size()); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/RestartLoopTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/RestartLoopTests.java index ee88bbefd5..feb51018e2 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/RestartLoopTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/RestartLoopTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2022 the original author or authors. + * Copyright 2014-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,10 @@ 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.StepContribution; -import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.launch.JobOperator; import org.springframework.batch.core.scope.context.ChunkContext; import org.springframework.batch.core.step.tasklet.Tasklet; import org.springframework.batch.repeat.RepeatStatus; @@ -41,17 +40,17 @@ class RestartLoopTests { private Job job; @Autowired - private JobLauncher jobLauncher; + private JobOperator jobOperator; @Test void test() throws Exception { // Run 1 - JobExecution jobExecution1 = jobLauncher.run(job, new JobParameters()); + JobExecution jobExecution1 = jobOperator.start(job, new JobParameters()); assertEquals(BatchStatus.STOPPED, jobExecution1.getStatus()); // Run 2 - JobExecution jobExecution2 = jobLauncher.run(job, new JobParameters()); + JobExecution jobExecution2 = jobOperator.start(job, new JobParameters()); assertEquals(BatchStatus.STOPPED, jobExecution2.getStatus()); } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/StepLocatorStepFactoryBeanTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/StepLocatorStepFactoryBeanTests.java index 7af0831ce5..db1a99fe3b 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/StepLocatorStepFactoryBeanTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/StepLocatorStepFactoryBeanTests.java @@ -19,9 +19,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.JobInterruptedException; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobInterruptedException; import org.springframework.batch.core.job.SimpleJob; /** @@ -74,7 +72,7 @@ public void execute(StepExecution stepExecution) throws JobInterruptedException @Test void testGetObjectType() { - assertTrue((new StepLocatorStepFactoryBean()).getObjectType().isAssignableFrom(Step.class)); + assertTrue(new StepLocatorStepFactoryBean().getObjectType().isAssignableFrom(Step.class)); } } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/StepSupport.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/StepSupport.java index 571b199c5f..287e84dfd0 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/StepSupport.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/StepSupport.java @@ -15,10 +15,8 @@ */ package org.springframework.batch.core.step; -import org.springframework.batch.core.JobInterruptedException; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.UnexpectedJobExecutionException; +import org.springframework.batch.core.job.JobInterruptedException; +import org.springframework.batch.core.job.UnexpectedJobExecutionException; import org.springframework.beans.factory.BeanNameAware; /** @@ -113,7 +111,7 @@ public void setAllowStartIfComplete(boolean allowStartIfComplete) { * Not supported but provided so that tests can easily create a step. * @throws UnsupportedOperationException always * - * @see org.springframework.batch.core.Step#execute(org.springframework.batch.core.StepExecution) + * @see Step#execute(StepExecution) */ @Override public void execute(StepExecution stepExecution) throws JobInterruptedException, UnexpectedJobExecutionException { diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/ThreadStepInterruptionPolicyTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/ThreadStepInterruptionPolicyTests.java index 205d393574..53a7fb9d55 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/ThreadStepInterruptionPolicyTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/ThreadStepInterruptionPolicyTests.java @@ -16,8 +16,7 @@ package org.springframework.batch.core.step; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.JobInterruptedException; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobInterruptedException; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertThrows; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/builder/FaultTolerantStepBuilderTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/builder/FaultTolerantStepBuilderTests.java index 1fadefc974..61d73d0355 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/builder/FaultTolerantStepBuilderTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/builder/FaultTolerantStepBuilderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2021-2022 the original author or authors. + * Copyright 2021-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,11 +17,12 @@ import org.junit.jupiter.api.Test; -import org.springframework.batch.core.Step; +import org.springframework.batch.core.step.Step; import org.springframework.batch.core.configuration.xml.DummyItemReader; import org.springframework.batch.core.configuration.xml.DummyItemWriter; import org.springframework.batch.core.configuration.xml.DummyJobRepository; import org.springframework.batch.support.transaction.ResourcelessTransactionManager; +import java.lang.reflect.Field; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -53,4 +54,16 @@ void testAnnotationBasedStepExecutionListenerRegistration() { assertNotNull(step); } + @Test + void testSkipLimitDefaultValue() throws NoSuchFieldException, IllegalAccessException { + FaultTolerantStepBuilder 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-core/src/test/java/org/springframework/batch/core/step/builder/RegisterMultiListenerTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/builder/RegisterMultiListenerTests.java index 83292b4f56..496c1cf2f7 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/builder/RegisterMultiListenerTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/builder/RegisterMultiListenerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 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. @@ -21,20 +21,21 @@ import org.junit.jupiter.api.Test; import org.springframework.batch.core.BatchStatus; -import org.springframework.batch.core.ChunkListener; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; +import org.springframework.batch.core.listener.ChunkListener; import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.ItemWriteListener; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.listener.ItemWriteListener; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; import org.springframework.batch.core.PooledEmbeddedDataSource; -import org.springframework.batch.core.SkipListener; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.StepExecutionListener; +import org.springframework.batch.core.listener.SkipListener; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.step.StepExecution; +import org.springframework.batch.core.listener.StepExecutionListener; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; import org.springframework.batch.core.job.builder.JobBuilder; -import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.launch.JobOperator; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.scope.context.ChunkContext; import org.springframework.batch.item.Chunk; @@ -67,7 +68,7 @@ class RegisterMultiListenerTests { @Autowired - private JobLauncher jobLauncher; + private JobOperator jobOperator; @Autowired private Job job; @@ -79,7 +80,7 @@ class RegisterMultiListenerTests { @AfterEach void tearDown() { - jobLauncher = null; + jobOperator = null; job = null; callChecker = null; @@ -98,7 +99,7 @@ void tearDown() { void testMultiListenerFaultTolerantStep() throws Exception { bootstrap(MultiListenerFaultTolerantTestConfiguration.class); - JobExecution execution = jobLauncher.run(job, new JobParameters()); + JobExecution execution = jobOperator.start(job, new JobParameters()); assertEquals(BatchStatus.COMPLETED, execution.getStatus()); assertEquals(1, callChecker.beforeStepCalled); assertEquals(6, callChecker.beforeChunkCalled); @@ -110,7 +111,7 @@ void testMultiListenerFaultTolerantStep() throws Exception { void testMultiListenerSimpleStep() throws Exception { bootstrap(MultiListenerTestConfiguration.class); - JobExecution execution = jobLauncher.run(job, new JobParameters()); + JobExecution execution = jobOperator.start(job, new JobParameters()); assertEquals(BatchStatus.FAILED, execution.getStatus()); assertEquals(1, callChecker.beforeStepCalled); assertEquals(1, callChecker.beforeChunkCalled); @@ -179,6 +180,7 @@ public ItemWriter writer() { @Configuration @EnableBatchProcessing + @EnableJdbcJobRepository public static class MultiListenerFaultTolerantTestConfiguration extends MultiListenerTestConfigurationSupport { @Bean @@ -215,6 +217,7 @@ public Step step(JobRepository jobRepository) { @Configuration @EnableBatchProcessing + @EnableJdbcJobRepository public static class MultiListenerTestConfiguration extends MultiListenerTestConfigurationSupport { @Bean 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..123333e587 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. @@ -23,12 +23,12 @@ import org.junit.jupiter.api.Test; import org.springframework.batch.core.BatchStatus; -import org.springframework.batch.core.ChunkListener; +import org.springframework.batch.core.listener.ChunkListener; import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.ItemReadListener; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.StepExecutionListener; +import org.springframework.batch.core.listener.ItemReadListener; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.step.StepExecution; +import org.springframework.batch.core.listener.StepExecutionListener; import org.springframework.batch.core.annotation.AfterChunk; import org.springframework.batch.core.annotation.AfterChunkError; import org.springframework.batch.core.annotation.AfterProcess; @@ -44,7 +44,7 @@ import org.springframework.batch.core.configuration.xml.DummyItemWriter; import org.springframework.batch.core.job.SimpleJob; import org.springframework.batch.core.repository.JobRepository; -import org.springframework.batch.core.repository.support.JobRepositoryFactoryBean; +import org.springframework.batch.core.repository.support.JdbcJobRepositoryFactoryBean; import org.springframework.batch.item.ItemReader; import org.springframework.batch.item.ItemStreamSupport; import org.springframework.batch.item.support.ListItemReader; @@ -83,7 +83,7 @@ void setUp() throws Exception { .addScript("/org/springframework/batch/core/schema-hsqldb.sql") .build(); JdbcTransactionManager transactionManager = new JdbcTransactionManager(embeddedDatabase); - JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean(); + JdbcJobRepositoryFactoryBean factory = new JdbcJobRepositoryFactoryBean(); factory.setDataSource(embeddedDatabase); factory.setTransactionManager(transactionManager); factory.afterPropertiesSet(); @@ -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/AbstractExceptionThrowingItemHandlerStub.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/AbstractExceptionThrowingItemHandlerStub.java index f7bdfaeaf4..466f7bbf14 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/AbstractExceptionThrowingItemHandlerStub.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/AbstractExceptionThrowingItemHandlerStub.java @@ -66,11 +66,11 @@ public void clearFailures() { protected void checkFailure(T item) throws Exception { if (isFailure(item)) { Throwable t = getException("Intended Failure: " + item); - if (t instanceof Exception) { - throw (Exception) t; + if (t instanceof Exception e) { + throw e; } - if (t instanceof Error) { - throw (Error) t; + if (t instanceof Error error) { + throw error; } throw new IllegalStateException("Unexpected non-Error Throwable"); } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/ChunkOrientedStepIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/ChunkOrientedStepIntegrationTests.java new file mode 100644 index 0000000000..1153506210 --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/ChunkOrientedStepIntegrationTests.java @@ -0,0 +1,482 @@ +/* + * Copyright 2025-present 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.step.item; + +import java.util.Map; +import java.util.Set; + +import javax.sql.DataSource; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import org.springframework.batch.core.ExitStatus; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; +import org.springframework.batch.core.step.FatalStepExecutionException; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.step.StepExecution; +import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; +import org.springframework.batch.core.configuration.annotation.StepScope; +import org.springframework.batch.core.job.builder.JobBuilder; +import org.springframework.batch.core.launch.JobOperator; +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.core.step.builder.ChunkOrientedStepBuilder; +import org.springframework.batch.core.step.skip.LimitCheckingExceptionHierarchySkipPolicy; +import org.springframework.batch.core.step.skip.SkipLimitExceededException; +import org.springframework.batch.item.ItemProcessor; +import org.springframework.batch.item.ItemReader; +import org.springframework.batch.item.ItemWriter; +import org.springframework.batch.item.database.JdbcBatchItemWriter; +import org.springframework.batch.item.database.builder.JdbcBatchItemWriterBuilder; +import org.springframework.batch.item.file.FlatFileItemReader; +import org.springframework.batch.item.file.FlatFileParseException; +import org.springframework.batch.item.file.builder.FlatFileItemReaderBuilder; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.Resource; +import org.springframework.core.retry.RetryPolicy; +import org.springframework.core.task.SimpleAsyncTaskExecutor; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; +import org.springframework.jdbc.support.JdbcTransactionManager; +import org.springframework.test.jdbc.JdbcTestUtils; + +/** + * Integration tests for {@link ChunkOrientedStep}. + * + * @author Mahmoud Ben Hassine + */ +public class ChunkOrientedStepIntegrationTests { + + // TODO use parameterized tests for serial and concurrent steps + // The outcome should be the same for both + + @Test + void testChunkOrientedStep() throws Exception { + // given + ApplicationContext context = new AnnotationConfigApplicationContext(TestConfiguration.class, + ChunkOrientedStepConfiguration.class); + JobOperator jobOperator = context.getBean(JobOperator.class); + Job job = context.getBean(Job.class); + JdbcTemplate jdbcTemplate = context.getBean(JdbcTemplate.class); + + // when + JobParameters jobParameters = new JobParametersBuilder().addString("file", "data/persons.csv") + .toJobParameters(); + JobExecution jobExecution = jobOperator.start(job, jobParameters); + + // then + Assertions.assertEquals(ExitStatus.COMPLETED, jobExecution.getExitStatus()); + StepExecution stepExecution = jobExecution.getStepExecutions().iterator().next(); + Assertions.assertEquals(ExitStatus.COMPLETED, stepExecution.getExitStatus()); + Assertions.assertEquals(5, stepExecution.getReadCount()); + Assertions.assertEquals(5, stepExecution.getWriteCount()); + Assertions.assertEquals(3, stepExecution.getCommitCount()); + Assertions.assertEquals(0, stepExecution.getRollbackCount()); + Assertions.assertEquals(5, JdbcTestUtils.countRowsInTable(jdbcTemplate, "person_target")); + } + + @Test + void testConcurrentChunkOrientedStep() throws Exception { + // given + ApplicationContext context = new AnnotationConfigApplicationContext(TestConfiguration.class, + ConcurrentChunkOrientedStepConfiguration.class); + JobOperator jobOperator = context.getBean(JobOperator.class); + Job job = context.getBean(Job.class); + JdbcTemplate jdbcTemplate = context.getBean(JdbcTemplate.class); + + // when + JobParameters jobParameters = new JobParametersBuilder().addString("file", "data/persons.csv") + .toJobParameters(); + JobExecution jobExecution = jobOperator.start(job, jobParameters); + + // then + Assertions.assertEquals(ExitStatus.COMPLETED, jobExecution.getExitStatus()); + StepExecution stepExecution = jobExecution.getStepExecutions().iterator().next(); + Assertions.assertEquals(ExitStatus.COMPLETED, stepExecution.getExitStatus()); + Assertions.assertEquals(5, stepExecution.getReadCount()); + Assertions.assertEquals(5, stepExecution.getWriteCount()); + Assertions.assertEquals(3, stepExecution.getCommitCount()); + Assertions.assertEquals(0, stepExecution.getRollbackCount()); + Assertions.assertEquals(5, JdbcTestUtils.countRowsInTable(jdbcTemplate, "person_target")); + } + + @Test + void testChunkOrientedStepFailure() throws Exception { + // given + System.setProperty("fail", "true"); + ApplicationContext context = new AnnotationConfigApplicationContext(TestConfiguration.class, + ChunkOrientedStepConfiguration.class); + JobOperator jobOperator = context.getBean(JobOperator.class); + Job job = context.getBean(Job.class); + JdbcTemplate jdbcTemplate = context.getBean(JdbcTemplate.class); + + // when + JobParameters jobParameters = new JobParametersBuilder().addString("file", "data/persons.csv") + .toJobParameters(); + JobExecution jobExecution = jobOperator.start(job, jobParameters); + + // then + Assertions.assertEquals(ExitStatus.FAILED.getExitCode(), jobExecution.getExitStatus().getExitCode()); + StepExecution stepExecution = jobExecution.getStepExecutions().iterator().next(); + ExitStatus stepExecutionExitStatus = stepExecution.getExitStatus(); + Assertions.assertEquals(ExitStatus.FAILED.getExitCode(), stepExecutionExitStatus.getExitCode()); + Assertions.assertTrue(stepExecutionExitStatus.getExitDescription() + .contains("Unable to process item Person[id=1, name=foo1]")); + Assertions.assertEquals(2, stepExecution.getReadCount()); + Assertions.assertEquals(0, stepExecution.getWriteCount()); + Assertions.assertEquals(0, stepExecution.getCommitCount()); + Assertions.assertEquals(1, stepExecution.getRollbackCount()); + Assertions.assertEquals(0, JdbcTestUtils.countRowsInTable(jdbcTemplate, "person_target")); + System.clearProperty("fail"); + } + + @Test + void testConcurrentChunkOrientedStepFailure() throws Exception { + // given + System.setProperty("fail", "true"); + ApplicationContext context = new AnnotationConfigApplicationContext(TestConfiguration.class, + ConcurrentChunkOrientedStepConfiguration.class); + JobOperator jobOperator = context.getBean(JobOperator.class); + Job job = context.getBean(Job.class); + JdbcTemplate jdbcTemplate = context.getBean(JdbcTemplate.class); + + // when + JobParameters jobParameters = new JobParametersBuilder().addString("file", "data/persons.csv") + .toJobParameters(); + JobExecution jobExecution = jobOperator.start(job, jobParameters); + + // then + Assertions.assertEquals(ExitStatus.FAILED.getExitCode(), jobExecution.getExitStatus().getExitCode()); + StepExecution stepExecution = jobExecution.getStepExecutions().iterator().next(); + ExitStatus stepExecutionExitStatus = stepExecution.getExitStatus(); + Assertions.assertEquals(ExitStatus.FAILED.getExitCode(), stepExecutionExitStatus.getExitCode()); + Assertions.assertTrue(stepExecutionExitStatus.getExitDescription() + .contains("Unable to process item Person[id=1, name=foo1]")); + Assertions.assertEquals(2, stepExecution.getReadCount()); + Assertions.assertEquals(0, stepExecution.getWriteCount()); + Assertions.assertEquals(0, stepExecution.getCommitCount()); + Assertions.assertEquals(1, stepExecution.getRollbackCount()); + Assertions.assertEquals(0, JdbcTestUtils.countRowsInTable(jdbcTemplate, "person_target")); + System.clearProperty("fail"); + } + + @Test + void testFaultTolerantChunkOrientedStep() throws Exception { + // given + System.setProperty("skipLimit", "3"); + ApplicationContext context = new AnnotationConfigApplicationContext(TestConfiguration.class, + FaultTolerantChunkOrientedStepConfiguration.class); + JobOperator jobOperator = context.getBean(JobOperator.class); + Job job = context.getBean(Job.class); + JdbcTemplate jdbcTemplate = context.getBean(JdbcTemplate.class); + + // when + JobParameters jobParameters = new JobParametersBuilder().addString("file", "data/persons-bad-data.csv") + .toJobParameters(); + JobExecution jobExecution = jobOperator.start(job, jobParameters); + + // then + Assertions.assertEquals(ExitStatus.COMPLETED.getExitCode(), jobExecution.getExitStatus().getExitCode()); + StepExecution stepExecution = jobExecution.getStepExecutions().iterator().next(); + ExitStatus stepExecutionExitStatus = stepExecution.getExitStatus(); + Assertions.assertEquals(ExitStatus.COMPLETED.getExitCode(), stepExecutionExitStatus.getExitCode()); + Assertions.assertEquals(4, stepExecution.getReadCount()); + Assertions.assertEquals(3, stepExecution.getWriteCount()); + Assertions.assertEquals(3, stepExecution.getCommitCount()); + Assertions.assertEquals(0, stepExecution.getRollbackCount()); + Assertions.assertEquals(2, stepExecution.getReadSkipCount()); + Assertions.assertEquals(1, stepExecution.getWriteSkipCount()); + Assertions.assertEquals(3, stepExecution.getSkipCount()); + Assertions.assertEquals(3, JdbcTestUtils.countRowsInTable(jdbcTemplate, "person_target")); + System.clearProperty("skipLimit"); + } + + @Test + void testConcurrentFaultTolerantChunkOrientedStep() throws Exception { + // given + System.setProperty("skipLimit", "3"); + ApplicationContext context = new AnnotationConfigApplicationContext(TestConfiguration.class, + ConcurrentFaultTolerantChunkOrientedStepConfiguration.class); + JobOperator jobOperator = context.getBean(JobOperator.class); + Job job = context.getBean(Job.class); + JdbcTemplate jdbcTemplate = context.getBean(JdbcTemplate.class); + + // when + JobParameters jobParameters = new JobParametersBuilder().addString("file", "data/persons-bad-data.csv") + .toJobParameters(); + JobExecution jobExecution = jobOperator.start(job, jobParameters); + + // then + Assertions.assertEquals(ExitStatus.COMPLETED.getExitCode(), jobExecution.getExitStatus().getExitCode()); + StepExecution stepExecution = jobExecution.getStepExecutions().iterator().next(); + ExitStatus stepExecutionExitStatus = stepExecution.getExitStatus(); + Assertions.assertEquals(ExitStatus.COMPLETED.getExitCode(), stepExecutionExitStatus.getExitCode()); + Assertions.assertEquals(4, stepExecution.getReadCount()); + Assertions.assertEquals(3, stepExecution.getWriteCount()); + Assertions.assertEquals(3, stepExecution.getCommitCount()); + Assertions.assertEquals(0, stepExecution.getRollbackCount()); + Assertions.assertEquals(2, stepExecution.getReadSkipCount()); + Assertions.assertEquals(1, stepExecution.getWriteSkipCount()); + Assertions.assertEquals(3, stepExecution.getSkipCount()); + Assertions.assertEquals(3, JdbcTestUtils.countRowsInTable(jdbcTemplate, "person_target")); + System.clearProperty("skipLimit"); + } + + @Test + void testFaultTolerantChunkOrientedStepFailure() throws Exception { + // given + System.setProperty("skipLimit", "1"); + ApplicationContext context = new AnnotationConfigApplicationContext(TestConfiguration.class, + FaultTolerantChunkOrientedStepConfiguration.class); + JobOperator jobOperator = context.getBean(JobOperator.class); + Job job = context.getBean(Job.class); + JdbcTemplate jdbcTemplate = context.getBean(JdbcTemplate.class); + + // when + JobParameters jobParameters = new JobParametersBuilder().addString("file", "data/persons-bad-data.csv") + .toJobParameters(); + JobExecution jobExecution = jobOperator.start(job, jobParameters); + + // then + Assertions.assertEquals(ExitStatus.FAILED.getExitCode(), jobExecution.getExitStatus().getExitCode()); + StepExecution stepExecution = jobExecution.getStepExecutions().iterator().next(); + ExitStatus stepExecutionExitStatus = stepExecution.getExitStatus(); + Assertions.assertEquals(ExitStatus.FAILED.getExitCode(), stepExecutionExitStatus.getExitCode()); + Throwable failureException = stepExecution.getFailureExceptions().iterator().next(); + Assertions.assertInstanceOf(FatalStepExecutionException.class, failureException); + Assertions.assertInstanceOf(SkipLimitExceededException.class, failureException.getCause()); + Assertions.assertEquals(3, stepExecution.getReadCount()); + Assertions.assertEquals(2, stepExecution.getWriteCount()); + Assertions.assertEquals(1, stepExecution.getCommitCount()); + Assertions.assertEquals(1, stepExecution.getRollbackCount()); + Assertions.assertEquals(1, stepExecution.getReadSkipCount()); + Assertions.assertEquals(0, stepExecution.getWriteSkipCount()); + Assertions.assertEquals(1, stepExecution.getSkipCount()); + Assertions.assertEquals(2, JdbcTestUtils.countRowsInTable(jdbcTemplate, "person_target")); + System.clearProperty("skipLimit"); + } + + @Test + void testConcurrentFaultTolerantChunkOrientedStepFailure() throws Exception { + // given + System.setProperty("skipLimit", "1"); + ApplicationContext context = new AnnotationConfigApplicationContext(TestConfiguration.class, + ConcurrentFaultTolerantChunkOrientedStepConfiguration.class); + JobOperator jobOperator = context.getBean(JobOperator.class); + Job job = context.getBean(Job.class); + JdbcTemplate jdbcTemplate = context.getBean(JdbcTemplate.class); + + // when + JobParameters jobParameters = new JobParametersBuilder().addString("file", "data/persons-bad-data.csv") + .toJobParameters(); + JobExecution jobExecution = jobOperator.start(job, jobParameters); + + // then + Assertions.assertEquals(ExitStatus.FAILED.getExitCode(), jobExecution.getExitStatus().getExitCode()); + StepExecution stepExecution = jobExecution.getStepExecutions().iterator().next(); + ExitStatus stepExecutionExitStatus = stepExecution.getExitStatus(); + Assertions.assertEquals(ExitStatus.FAILED.getExitCode(), stepExecutionExitStatus.getExitCode()); + Throwable failureException = stepExecution.getFailureExceptions().iterator().next(); + Assertions.assertInstanceOf(FatalStepExecutionException.class, failureException); + Assertions.assertInstanceOf(SkipLimitExceededException.class, failureException.getCause()); + Assertions.assertEquals(3, stepExecution.getReadCount()); + Assertions.assertEquals(2, stepExecution.getWriteCount()); + Assertions.assertEquals(1, stepExecution.getCommitCount()); + Assertions.assertEquals(1, stepExecution.getRollbackCount()); + Assertions.assertEquals(1, stepExecution.getReadSkipCount()); + Assertions.assertEquals(0, stepExecution.getWriteSkipCount()); + Assertions.assertEquals(1, stepExecution.getSkipCount()); + Assertions.assertEquals(2, JdbcTestUtils.countRowsInTable(jdbcTemplate, "person_target")); + System.clearProperty("skipLimit"); + } + + record Person(int id, String name) { + } + + @Configuration + @EnableBatchProcessing + @EnableJdbcJobRepository + static class TestConfiguration { + + @Bean + @StepScope + public FlatFileItemReader itemReader(@Value("#{jobParameters['file']}") Resource file) { + return new FlatFileItemReaderBuilder().name("personItemReader") + .resource(file) + .delimited() + .names("id", "name") + .targetType(Person.class) + .build(); + } + + @Bean + public ItemProcessor itemProcessor() { + return item -> { + if (System.getProperty("fail") != null) { + throw new Exception("Unable to process item " + item); + } + return new Person(item.id(), item.name().toUpperCase()); + }; + } + + @Bean + public JdbcBatchItemWriter itemWriter() { + String sql = "insert into person_target (id, name) values (:id, :name)"; + return new JdbcBatchItemWriterBuilder().dataSource(dataSource()).sql(sql).beanMapped().build(); + } + + @Bean + public Job job(JobRepository jobRepository, Step step) { + return new JobBuilder("job", jobRepository).start(step).build(); + } + + @Bean + public DataSource dataSource() { + return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.H2) + .addScript("/org/springframework/batch/core/schema-drop-h2.sql") + .addScript("/org/springframework/batch/core/schema-h2.sql") + .addScript("schema.sql") + .generateUniqueName(true) + .build(); + } + + @Bean + public JdbcTransactionManager transactionManager(DataSource dataSource) { + return new JdbcTransactionManager(dataSource); + } + + @Bean + public JdbcTemplate jdbcTemplate(DataSource dataSource) { + return new JdbcTemplate(dataSource); + } + + } + + @Configuration + static class ChunkOrientedStepConfiguration { + + @Bean + public Step chunkOrientedStep(JobRepository jobRepository, JdbcTransactionManager transactionManager, + ItemReader itemReader, ItemProcessor itemProcessor, + ItemWriter itemWriter) { + return new ChunkOrientedStepBuilder(jobRepository, 2).reader(itemReader) + .processor(itemProcessor) + .writer(itemWriter) + .transactionManager(transactionManager) + .build(); + } + + } + + @Configuration + static class ConcurrentChunkOrientedStepConfiguration { + + @Bean + public Step concurrentChunkOrientedStep(JobRepository jobRepository, JdbcTransactionManager transactionManager, + ItemReader itemReader, ItemProcessor itemProcessor, + ItemWriter itemWriter) { + return new ChunkOrientedStepBuilder(jobRepository, 2).reader(itemReader) + .processor(itemProcessor) + .writer(itemWriter) + .transactionManager(transactionManager) + .taskExecutor(new SimpleAsyncTaskExecutor()) + .build(); + } + + } + + @Configuration + static class FaultTolerantChunkOrientedStepConfiguration { + + @Bean + public Step faulTolerantChunkOrientedStep(JobRepository jobRepository, + JdbcTransactionManager transactionManager, ItemReader itemReader, + ItemProcessor itemProcessor, ItemWriter itemWriter) { + // retry policy configuration + int retryLimit = 3; + Set> nonRetrybaleExceptions = Set.of(FlatFileParseException.class, + DataIntegrityViolationException.class); + RetryPolicy retryPolicy = RetryPolicy.builder() + .maxAttempts(retryLimit) + .excludes(nonRetrybaleExceptions) + .build(); + + // skip policy configuration + int skipLimit = Integer.parseInt(System.getProperty("skipLimit")); + Set> skippableExceptions = Set.of(FlatFileParseException.class, + DataIntegrityViolationException.class); + LimitCheckingExceptionHierarchySkipPolicy skipPolicy = new LimitCheckingExceptionHierarchySkipPolicy( + skippableExceptions, skipLimit); + + return new ChunkOrientedStepBuilder(jobRepository, 2).reader(itemReader) + .processor(itemProcessor) + .writer(itemWriter) + .transactionManager(transactionManager) + .faultTolerant() + .retryPolicy(retryPolicy) + .skipPolicy(skipPolicy) + .build(); + } + + } + + @Configuration + static class ConcurrentFaultTolerantChunkOrientedStepConfiguration { + + @Bean + public Step concurrentFaulTolerantChunkOrientedStep(JobRepository jobRepository, + JdbcTransactionManager transactionManager, ItemReader itemReader, + ItemProcessor itemProcessor, ItemWriter itemWriter) { + // retry policy configuration + int retryLimit = 3; + Set> nonRetrybaleExceptions = Set.of(FlatFileParseException.class, + DataIntegrityViolationException.class); + RetryPolicy retryPolicy = RetryPolicy.builder() + .maxAttempts(retryLimit) + .excludes(nonRetrybaleExceptions) + .build(); + + // skip policy configuration + int skipLimit = Integer.parseInt(System.getProperty("skipLimit")); + Set> skippableExceptions = Set.of(FlatFileParseException.class, + DataIntegrityViolationException.class); + LimitCheckingExceptionHierarchySkipPolicy skipPolicy = new LimitCheckingExceptionHierarchySkipPolicy( + skippableExceptions, skipLimit); + + return new ChunkOrientedStepBuilder(jobRepository, 2).reader(itemReader) + .processor(itemProcessor) + .writer(itemWriter) + .transactionManager(transactionManager) + .taskExecutor(new SimpleAsyncTaskExecutor()) + .faultTolerant() + .retryPolicy(retryPolicy) + .skipPolicy(skipPolicy) + .build(); + } + + } + +} \ No newline at end of file diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/ChunkOrientedStepObservabilityIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/ChunkOrientedStepObservabilityIntegrationTests.java new file mode 100644 index 0000000000..f2e0d495fc --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/ChunkOrientedStepObservabilityIntegrationTests.java @@ -0,0 +1,185 @@ +/* + * Copyright 2025-present 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.step.item; + +import java.util.List; + +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.observation.DefaultMeterObservationHandler; +import io.micrometer.core.instrument.simple.SimpleMeterRegistry; +import io.micrometer.observation.ObservationRegistry; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import org.springframework.batch.core.ExitStatus; +import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; +import org.springframework.batch.core.configuration.support.DefaultBatchConfiguration; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.builder.JobBuilder; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.launch.JobOperator; +import org.springframework.batch.core.observability.BatchMetrics; +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.step.builder.ChunkOrientedStepBuilder; +import org.springframework.batch.item.support.ListItemReader; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +/** + * Integration tests for observability features in {@link ChunkOrientedStep}. + * + * @author Mahmoud Ben Hassine + */ +public class ChunkOrientedStepObservabilityIntegrationTests { + + @Test + void testChunkOrientedStepMetricsWihDeclarativeApproach() throws Exception { + // given + ApplicationContext context = new AnnotationConfigApplicationContext(DeclarativeTestConfiguration.class); + SimpleMeterRegistry meterRegistry = context.getBean(SimpleMeterRegistry.class); + JobOperator jobOperator = context.getBean(JobOperator.class); + Job job = context.getBean(Job.class); + + // when + JobExecution jobExecution = jobOperator.start(job, new JobParameters()); + + // then + Assertions.assertEquals(ExitStatus.COMPLETED, jobExecution.getExitStatus()); + assertMetrics(meterRegistry); + } + + @Test + void testChunkOrientedStepMetricsWihProgrammaticApproach() throws Exception { + // given + ApplicationContext context = new AnnotationConfigApplicationContext(ProgrammaticTestConfiguration.class); + SimpleMeterRegistry meterRegistry = context.getBean(SimpleMeterRegistry.class); + JobOperator jobOperator = context.getBean(JobOperator.class); + Job job = context.getBean(Job.class); + + // when + JobExecution jobExecution = jobOperator.start(job, new JobParameters()); + + // then + Assertions.assertEquals(ExitStatus.COMPLETED, jobExecution.getExitStatus()); + assertMetrics(meterRegistry); + } + + private static void assertMetrics(SimpleMeterRegistry meterRegistry) { + Assertions.assertEquals(12, meterRegistry.getMeters().size()); + assertDoesNotThrow( + () -> meterRegistry.get(BatchMetrics.METRICS_PREFIX + "item.read") + .tag(BatchMetrics.METRICS_PREFIX + "item.read.job.name", "job") + .tag(BatchMetrics.METRICS_PREFIX + "item.read.step.name", "step") + .tag(BatchMetrics.METRICS_PREFIX + "item.read.status", "SUCCESS") + .timer(), + "There should be a meter of type TIMER named spring.batch.item.read registered in the meter registry"); + assertDoesNotThrow( + () -> meterRegistry.get(BatchMetrics.METRICS_PREFIX + "item.process") + .tag(BatchMetrics.METRICS_PREFIX + "item.process.job.name", "job") + .tag(BatchMetrics.METRICS_PREFIX + "item.process.step.name", "step") + .tag(BatchMetrics.METRICS_PREFIX + "item.process.status", "SUCCESS") + .timer(), + "There should be a meter of type TIMER named spring.batch.item.process registered in the meter registry"); + assertDoesNotThrow( + () -> meterRegistry.get(BatchMetrics.METRICS_PREFIX + "chunk.write") + .tag(BatchMetrics.METRICS_PREFIX + "chunk.write.job.name", "job") + .tag(BatchMetrics.METRICS_PREFIX + "chunk.write.step.name", "step") + .tag(BatchMetrics.METRICS_PREFIX + "chunk.write.status", "SUCCESS") + .timer(), + "There should be a meter of type TIMER named spring.batch.chunk.write registered in the meter registry"); + } + + @Configuration + @EnableBatchProcessing + static class DeclarativeTestConfiguration { + + @Bean + public Job job(JobRepository jobRepository, Step step) { + return new JobBuilder(jobRepository).start(step).build(); + } + + @Bean + public Step step(JobRepository jobRepository, ObservationRegistry observationRegistry) { + return new ChunkOrientedStepBuilder(jobRepository, 2) + .reader(new ListItemReader<>(List.of("one", "two", "three", "four", "five"))) + .processor(String::toUpperCase) + .writer(items -> { + }) + .observationRegistry(observationRegistry) + .build(); + } + + @Bean + public SimpleMeterRegistry meterRegistry() { + return new SimpleMeterRegistry(); + } + + @Bean + public ObservationRegistry observationRegistry(MeterRegistry meterRegistry) { + ObservationRegistry observationRegistry = ObservationRegistry.create(); + observationRegistry.observationConfig() + .observationHandler(new DefaultMeterObservationHandler(meterRegistry)); + return observationRegistry; + } + + } + + @Configuration + static class ProgrammaticTestConfiguration extends DefaultBatchConfiguration { + + @Bean + public Job job(JobRepository jobRepository, Step step) { + return new JobBuilder(jobRepository).start(step).build(); + } + + @Bean + public Step step(JobRepository jobRepository, ObservationRegistry observationRegistry) { + return new ChunkOrientedStepBuilder(jobRepository, 2) + .reader(new ListItemReader<>(List.of("one", "two", "three", "four", "five"))) + .processor(String::toUpperCase) + .writer(items -> { + }) + .observationRegistry(observationRegistry) + .build(); + } + + @Override + protected ObservationRegistry getObservationRegistry() { + return observationRegistry(meterRegistry()); + } + + @Bean + public SimpleMeterRegistry meterRegistry() { + return new SimpleMeterRegistry(); + } + + @Bean + public ObservationRegistry observationRegistry(MeterRegistry meterRegistry) { + ObservationRegistry observationRegistry = ObservationRegistry.create(); + observationRegistry.observationConfig() + .observationHandler(new DefaultMeterObservationHandler(meterRegistry)); + return observationRegistry; + } + + } + +} \ No newline at end of file diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/ChunkOrientedTaskletTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/ChunkOrientedTaskletTests.java index 5ed3526e8a..aa300351be 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/ChunkOrientedTaskletTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/ChunkOrientedTaskletTests.java @@ -21,11 +21,11 @@ import org.junit.jupiter.api.Test; import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobInstance; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.StepContribution; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobInstance; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.step.StepContribution; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.scope.context.ChunkContext; import org.springframework.batch.item.Chunk; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/ExceptionThrowingTaskletStub.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/ExceptionThrowingTaskletStub.java index ffb7c777d8..c28242853c 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/ExceptionThrowingTaskletStub.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/ExceptionThrowingTaskletStub.java @@ -20,7 +20,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.batch.core.StepContribution; +import org.springframework.batch.core.step.StepContribution; import org.springframework.batch.core.scope.context.ChunkContext; import org.springframework.batch.core.step.tasklet.Tasklet; import org.springframework.batch.repeat.RepeatStatus; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/FaultTolerantChunkProcessorTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/FaultTolerantChunkProcessorTests.java index d30e06917e..da90e2021b 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/FaultTolerantChunkProcessorTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/FaultTolerantChunkProcessorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 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,11 +30,11 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobInstance; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.StepContribution; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobInstance; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.step.StepContribution; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.listener.ItemListenerSupport; import org.springframework.batch.core.step.skip.AlwaysSkipItemSkipPolicy; import org.springframework.batch.core.step.skip.LimitCheckingItemSkipPolicy; @@ -230,6 +230,25 @@ void testWriteSkipOnException() throws Exception { assertEquals(0, contribution.getFilterCount()); } + @Test + void testWriteSkipOnIteratorRemove() throws Exception { + processor.setItemWriter(chunk -> { + Chunk.ChunkIterator iterator = chunk.iterator(); + while (iterator.hasNext()) { + String item = iterator.next(); + if (item.equals("skip")) { + iterator.remove((Exception) null); + } + } + }); + Chunk inputs = new Chunk<>(Arrays.asList("3", "skip", "2")); + processor.process(contribution, inputs); + assertEquals(1, contribution.getSkipCount()); + assertEquals(2, contribution.getWriteCount()); + assertEquals(1, contribution.getWriteSkipCount()); + assertEquals(0, contribution.getFilterCount()); + } + @Test void testWriteSkipOnExceptionWithTrivialChunk() throws Exception { processor.setWriteSkipPolicy(new AlwaysSkipItemSkipPolicy()); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/FaultTolerantChunkProviderTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/FaultTolerantChunkProviderTests.java index a1b45c1fee..6fb2a27b26 100755 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/FaultTolerantChunkProviderTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/FaultTolerantChunkProviderTests.java @@ -22,19 +22,15 @@ import java.util.Collections; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobInstance; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.StepContribution; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobInstance; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.step.StepContribution; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.step.skip.LimitCheckingItemSkipPolicy; import org.springframework.batch.item.Chunk; -import org.springframework.batch.item.ItemReader; -import org.springframework.batch.item.ParseException; -import org.springframework.batch.item.UnexpectedInputException; import org.springframework.batch.item.support.ListItemReader; import org.springframework.batch.repeat.support.RepeatTemplate; -import org.springframework.lang.Nullable; class FaultTolerantChunkProviderTests { @@ -54,12 +50,8 @@ void testProvide() throws Exception { @Test void testProvideWithOverflow() throws Exception { - provider = new FaultTolerantChunkProvider<>(new ItemReader<>() { - @Nullable - @Override - public String read() throws Exception, UnexpectedInputException, ParseException { - throw new RuntimeException("Planned"); - } + provider = new FaultTolerantChunkProvider<>(() -> { + throw new RuntimeException("Planned"); }, new RepeatTemplate()); provider.setSkipPolicy(new LimitCheckingItemSkipPolicy(Integer.MAX_VALUE, Collections., Boolean>singletonMap(Exception.class, Boolean.TRUE))); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/FaultTolerantExceptionClassesTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/FaultTolerantExceptionClassesTests.java index ec486a7636..d1ac234abd 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/FaultTolerantExceptionClassesTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/FaultTolerantExceptionClassesTests.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. @@ -18,12 +18,12 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.batch.core.BatchStatus; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobParametersBuilder; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.job.SimpleJob; -import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.launch.JobOperator; import org.springframework.batch.core.repository.JobRepository; import org.springframework.beans.BeansException; import org.springframework.beans.factory.annotation.Autowired; @@ -52,7 +52,7 @@ public class FaultTolerantExceptionClassesTests implements ApplicationContextAwa private JobRepository jobRepository; @Autowired - private JobLauncher jobLauncher; + private JobOperator jobOperator; @Autowired private SkipReaderStub reader; @@ -339,10 +339,10 @@ private StepExecution launchStep(String stepName) throws Exception { job.setJobRepository(jobRepository); List stepsToExecute = new ArrayList<>(); - stepsToExecute.add((Step) applicationContext.getBean(stepName)); + stepsToExecute.add(applicationContext.getBean(stepName, Step.class)); job.setSteps(stepsToExecute); - JobExecution jobExecution = jobLauncher.run(job, + JobExecution jobExecution = jobOperator.start(job, new JobParametersBuilder().addString("uuid", UUID.randomUUID().toString()).toJobParameters()); return jobExecution.getStepExecutions().iterator().next(); } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/FaultTolerantStepFactoryBeanNonBufferingTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/FaultTolerantStepFactoryBeanNonBufferingTests.java index 78e138272c..dcec489a5c 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/FaultTolerantStepFactoryBeanNonBufferingTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/FaultTolerantStepFactoryBeanNonBufferingTests.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. @@ -27,12 +27,12 @@ import org.junit.jupiter.api.Test; import org.springframework.batch.core.BatchStatus; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobInstance; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.SkipListener; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobInstance; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.listener.SkipListener; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.step.JobRepositorySupport; import org.springframework.batch.core.step.factory.FaultTolerantStepFactoryBean; import org.springframework.batch.item.Chunk; @@ -79,7 +79,7 @@ void setUp() throws Exception { factory.setIsReaderTransactionalQueue(true); JobInstance jobInstance = new JobInstance(1L, "skipJob"); - jobExecution = new JobExecution(jobInstance, new JobParameters()); + jobExecution = new JobExecution(jobInstance, 0L, new JobParameters()); } /** @@ -87,7 +87,6 @@ void setUp() throws Exception { */ @Test void testSkip() throws Exception { - @SuppressWarnings("unchecked") SkipListener skipListener = mock(); skipListener.onSkipInWrite("3", exception); skipListener.onSkipInWrite("4", exception); @@ -95,7 +94,7 @@ void testSkip() throws Exception { factory.setListeners(new SkipListener[] { skipListener }); Step step = factory.getObject(); - StepExecution stepExecution = new StepExecution(step.getName(), jobExecution); + StepExecution stepExecution = new StepExecution(step.getName(), jobExecution, 0L); step.execute(stepExecution); assertEquals(BatchStatus.COMPLETED, stepExecution.getStatus()); @@ -142,7 +141,7 @@ public SkipWriterStub(Collection failures) { } @Override - public void write(Chunk items) throws Exception { + public void write(Chunk items) { logger.debug("Writing: " + items); for (String item : items) { if (failures.contains(item)) { diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/FaultTolerantStepFactoryBeanRetryTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/FaultTolerantStepFactoryBeanRetryTests.java index 9f5ec22ea7..823b79e30d 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/FaultTolerantStepFactoryBeanRetryTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/FaultTolerantStepFactoryBeanRetryTests.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. @@ -30,15 +30,15 @@ import org.springframework.batch.core.BatchStatus; import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.JobParametersBuilder; -import org.springframework.batch.core.SkipListener; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.StepListener; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; +import org.springframework.batch.core.listener.SkipListener; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.step.StepExecution; +import org.springframework.batch.core.listener.StepListener; import org.springframework.batch.core.repository.JobRepository; -import org.springframework.batch.core.repository.support.JobRepositoryFactoryBean; +import org.springframework.batch.core.repository.support.JdbcJobRepositoryFactoryBean; import org.springframework.batch.core.step.AbstractStep; import org.springframework.batch.core.step.factory.FaultTolerantStepFactoryBean; import org.springframework.batch.item.ExecutionContext; @@ -103,7 +103,7 @@ void setUp() throws Exception { .generateUniqueName(true) .build(); JdbcTransactionManager transactionManager = new JdbcTransactionManager(embeddedDatabase); - JobRepositoryFactoryBean repositoryFactoryBean = new JobRepositoryFactoryBean(); + JdbcJobRepositoryFactoryBean repositoryFactoryBean = new JdbcJobRepositoryFactoryBean(); repositoryFactoryBean.setDataSource(embeddedDatabase); repositoryFactoryBean.setTransactionManager(transactionManager); repositoryFactoryBean.afterPropertiesSet(); @@ -661,19 +661,15 @@ void testCacheLimitWithRetry() throws Exception { factory.setSkipLimit(10); // set the cache limit stupidly low factory.setRetryContextCache(new MapRetryContextCache(0)); - ItemReader provider = new ItemReader<>() { - @Nullable - @Override - public String read() { - String item = String.valueOf(count); - provided.add(item); - count++; - if (count >= 10) { - // prevent infinite loop in worst case scenario - return null; - } - return item; + ItemReader provider = () -> { + String item = String.valueOf(count); + provided.add(item); + count++; + if (count >= 10) { + // prevent infinite loop in worst case scenario + return null; } + return item; }; ItemWriter itemWriter = chunk -> { processed.addAll(chunk.getItems()); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/FaultTolerantStepFactoryBeanRollbackTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/FaultTolerantStepFactoryBeanRollbackTests.java index 66060e11b6..fbdf90973c 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/FaultTolerantStepFactoryBeanRollbackTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/FaultTolerantStepFactoryBeanRollbackTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2009-2023 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. @@ -28,14 +28,14 @@ import org.junit.jupiter.api.Test; import org.springframework.batch.core.BatchStatus; -import org.springframework.batch.core.ChunkListener; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.StepListener; +import org.springframework.batch.core.listener.ChunkListener; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.step.StepExecution; +import org.springframework.batch.core.listener.StepListener; import org.springframework.batch.core.repository.JobRepository; -import org.springframework.batch.core.repository.support.JobRepositoryFactoryBean; +import org.springframework.batch.core.repository.support.JdbcJobRepositoryFactoryBean; import org.springframework.batch.core.scope.context.ChunkContext; import org.springframework.batch.core.step.FatalStepExecutionException; import org.springframework.batch.core.step.factory.FaultTolerantStepFactoryBean; @@ -107,7 +107,7 @@ void setUp() throws Exception { .addScript("/org/springframework/batch/core/schema-drop-hsqldb.sql") .addScript("/org/springframework/batch/core/schema-hsqldb.sql") .build(); - JobRepositoryFactoryBean repositoryFactory = new JobRepositoryFactoryBean(); + JdbcJobRepositoryFactoryBean repositoryFactory = new JdbcJobRepositoryFactoryBean(); repositoryFactory.setDataSource(embeddedDatabase); repositoryFactory.setTransactionManager(new JdbcTransactionManager(embeddedDatabase)); repositoryFactory.afterPropertiesSet(); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/FaultTolerantStepFactoryBeanTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/FaultTolerantStepFactoryBeanTests.java index 121cafadc1..b79b7a876d 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/FaultTolerantStepFactoryBeanTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/FaultTolerantStepFactoryBeanTests.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. @@ -31,19 +31,19 @@ import org.springframework.aop.framework.ProxyFactory; import org.springframework.batch.core.BatchStatus; -import org.springframework.batch.core.ChunkListener; +import org.springframework.batch.core.listener.ChunkListener; import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.ItemProcessListener; -import org.springframework.batch.core.ItemReadListener; -import org.springframework.batch.core.ItemWriteListener; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.SkipListener; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.StepListener; +import org.springframework.batch.core.listener.ItemProcessListener; +import org.springframework.batch.core.listener.ItemReadListener; +import org.springframework.batch.core.listener.ItemWriteListener; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.listener.SkipListener; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.step.StepExecution; +import org.springframework.batch.core.listener.StepListener; import org.springframework.batch.core.repository.JobRepository; -import org.springframework.batch.core.repository.support.JobRepositoryFactoryBean; +import org.springframework.batch.core.repository.support.JdbcJobRepositoryFactoryBean; import org.springframework.batch.core.scope.context.ChunkContext; import org.springframework.batch.core.step.factory.FaultTolerantStepFactoryBean; import org.springframework.batch.core.step.skip.LimitCheckingItemSkipPolicy; @@ -134,7 +134,7 @@ void setUp() throws Exception { factory .setSkippableExceptionClasses(getExceptionMap(SkippableException.class, SkippableRuntimeException.class)); - JobRepositoryFactoryBean repositoryFactoryBean = new JobRepositoryFactoryBean(); + JdbcJobRepositoryFactoryBean repositoryFactoryBean = new JdbcJobRepositoryFactoryBean(); repositoryFactoryBean.setDataSource(embeddedDatabase); repositoryFactoryBean.setTransactionManager(transactionManager); repositoryFactoryBean.setMaxVarCharLength(20000); @@ -151,6 +151,7 @@ void setUp() throws Exception { void testMandatoryReader() { // given factory = new FaultTolerantStepFactoryBean<>(); + factory.setBeanName("test"); factory.setItemWriter(writer); // when @@ -164,6 +165,7 @@ void testMandatoryReader() { void testMandatoryWriter() { // given factory = new FaultTolerantStepFactoryBean<>(); + factory.setBeanName("test"); factory.setItemReader(reader); // when diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/FaultTolerantStepFactoryBeanUnexpectedRollbackTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/FaultTolerantStepFactoryBeanUnexpectedRollbackTests.java index e8f64b6cb3..1470eff530 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/FaultTolerantStepFactoryBeanUnexpectedRollbackTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/FaultTolerantStepFactoryBeanUnexpectedRollbackTests.java @@ -19,12 +19,12 @@ import org.apache.commons.logging.LogFactory; import org.junit.jupiter.api.Test; import org.springframework.batch.core.BatchStatus; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.repository.JobRepository; -import org.springframework.batch.core.repository.support.JobRepositoryFactoryBean; +import org.springframework.batch.core.repository.support.JdbcJobRepositoryFactoryBean; import org.springframework.batch.core.step.factory.FaultTolerantStepFactoryBean; import org.springframework.batch.item.ItemReader; import org.springframework.batch.item.support.ListItemReader; @@ -82,7 +82,7 @@ protected void doCommit(DefaultTransactionStatus status) throws TransactionExcep ItemReader reader = new ListItemReader<>(Arrays.asList("1", "2")); factory.setItemReader(reader); - JobRepositoryFactoryBean repositoryFactory = new JobRepositoryFactoryBean(); + JdbcJobRepositoryFactoryBean repositoryFactory = new JdbcJobRepositoryFactoryBean(); repositoryFactory.setDataSource(dataSource); repositoryFactory.setTransactionManager(transactionManager); repositoryFactory.afterPropertiesSet(); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/RepeatOperationsStepFactoryBeanTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/RepeatOperationsStepFactoryBeanTests.java index f476e4e72c..c4755b2fd6 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/RepeatOperationsStepFactoryBeanTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/RepeatOperationsStepFactoryBeanTests.java @@ -21,11 +21,11 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobInstance; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobInstance; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.launch.EmptyItemWriter; import org.springframework.batch.core.step.JobRepositorySupport; import org.springframework.batch.core.step.factory.SimpleStepFactoryBean; @@ -49,7 +49,7 @@ class RepeatOperationsStepFactoryBeanTests { private List list; - private final JobExecution jobExecution = new JobExecution(new JobInstance(0L, "job"), new JobParameters()); + private final JobExecution jobExecution = new JobExecution(new JobInstance(0L, "job"), 0L, new JobParameters()); @BeforeEach void setUp() { @@ -86,7 +86,7 @@ void testStepOperationsWithoutChunkListener() throws Exception { }); Step step = factory.getObject(); - step.execute(new StepExecution(step.getName(), jobExecution)); + step.execute(new StepExecution(step.getName(), jobExecution, 0L)); assertEquals(1, list.size()); } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/ScriptItemProcessorTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/ScriptItemProcessorTests.java index fef38db05d..e7c2ac5085 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/ScriptItemProcessorTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/ScriptItemProcessorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2022 the original author or authors. + * Copyright 2014-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,9 +17,9 @@ import org.junit.jupiter.api.Test; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.launch.JobOperator; import org.springframework.batch.item.Chunk; import org.springframework.batch.item.ItemWriter; import org.springframework.beans.factory.annotation.Autowired; @@ -43,11 +43,11 @@ class ScriptItemProcessorTests { private Job job; @Autowired - private JobLauncher jobLauncher; + private JobOperator jobOperator; @Test void testScriptProcessorJob() throws Exception { - jobLauncher.run(job, new JobParameters()); + jobOperator.start(job, new JobParameters()); } public static class TestItemWriter implements ItemWriter { diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/SimpleChunkProcessorTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/SimpleChunkProcessorTests.java index 5ebcb49ced..1ce79fa40d 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/SimpleChunkProcessorTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/SimpleChunkProcessorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 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. @@ -24,11 +24,11 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobInstance; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.StepContribution; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobInstance; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.step.StepContribution; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.item.Chunk; import org.springframework.batch.item.ItemProcessor; import org.springframework.batch.item.ItemWriter; @@ -51,7 +51,16 @@ public void write(Chunk chunk) throws Exception { if (chunk.getItems().contains("fail")) { throw new RuntimeException("Planned failure!"); } - list.addAll(chunk.getItems()); + Chunk.ChunkIterator iterator = chunk.iterator(); + while (iterator.hasNext()) { + String item = iterator.next(); + if (item.equals("skip")) { + iterator.remove((Exception) null); + } + else { + list.add(item); + } + } } }); @@ -88,4 +97,15 @@ void testTransform() throws Exception { assertTrue(outputs.isEnd()); } + @Test + void testWriteWithSkip() throws Exception { + Chunk inputs = new Chunk<>(); + inputs.add("foo"); + inputs.add("skip"); + inputs.add("bar"); + processor.process(contribution, inputs); + assertEquals(2, contribution.getWriteCount()); + assertEquals(1, contribution.getWriteSkipCount()); + } + } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/SimpleChunkProviderTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/SimpleChunkProviderTests.java index 0525a0cb4e..15e7d913aa 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/SimpleChunkProviderTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/SimpleChunkProviderTests.java @@ -21,11 +21,11 @@ import java.util.Arrays; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobInstance; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.StepContribution; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobInstance; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.step.StepContribution; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.item.Chunk; import org.springframework.batch.item.support.ListItemReader; import org.springframework.batch.repeat.support.RepeatTemplate; @@ -49,14 +49,12 @@ void testProvide() throws Exception { void testProvideWithOverflow() throws Exception { provider = new SimpleChunkProvider<>(new ListItemReader<>(Arrays.asList("foo", "bar")), new RepeatTemplate()) { @Override - protected String read(StepContribution contribution, Chunk chunk) - throws SkipOverflowException, Exception { + protected String read(StepContribution contribution, Chunk chunk) { chunk.skip(new RuntimeException("Planned")); throw new SkipOverflowException("Overflow"); } }; - Chunk chunk = null; - chunk = provider.provide(contribution); + Chunk chunk = provider.provide(contribution); assertNotNull(chunk); assertEquals(0, chunk.getItems().size()); assertEquals(1, chunk.getErrors().size()); 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..5fdb390198 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. @@ -29,20 +29,19 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.batch.core.BatchStatus; -import org.springframework.batch.core.ChunkListener; -import org.springframework.batch.core.ItemProcessListener; -import org.springframework.batch.core.ItemReadListener; -import org.springframework.batch.core.ItemWriteListener; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.StepListener; +import org.springframework.batch.core.listener.ChunkListener; +import org.springframework.batch.core.listener.ItemProcessListener; +import org.springframework.batch.core.listener.ItemReadListener; +import org.springframework.batch.core.listener.ItemWriteListener; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.listener.StepListener; import org.springframework.batch.core.job.SimpleJob; import org.springframework.batch.core.listener.ItemListenerSupport; import org.springframework.batch.core.listener.StepListenerSupport; import org.springframework.batch.core.repository.JobRepository; -import org.springframework.batch.core.repository.support.JobRepositoryFactoryBean; +import org.springframework.batch.core.repository.support.JdbcJobRepositoryFactoryBean; import org.springframework.batch.core.scope.context.ChunkContext; import org.springframework.batch.core.step.AbstractStep; import org.springframework.batch.core.step.factory.SimpleStepFactoryBean; @@ -54,7 +53,6 @@ import org.springframework.batch.repeat.exception.SimpleLimitExceptionHandler; import org.springframework.batch.repeat.policy.SimpleCompletionPolicy; import org.springframework.batch.support.transaction.ResourcelessTransactionManager; -import org.springframework.core.task.SimpleAsyncTaskExecutor; import org.springframework.jdbc.support.JdbcTransactionManager; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; @@ -85,7 +83,7 @@ void setUp() throws Exception { .generateUniqueName(true) .build(); JdbcTransactionManager transactionManager = new JdbcTransactionManager(embeddedDatabase); - JobRepositoryFactoryBean repositoryFactoryBean = new JobRepositoryFactoryBean(); + JdbcJobRepositoryFactoryBean repositoryFactoryBean = new JdbcJobRepositoryFactoryBean(); repositoryFactoryBean.setDataSource(embeddedDatabase); repositoryFactoryBean.setTransactionManager(transactionManager); repositoryFactoryBean.afterPropertiesSet(); @@ -98,6 +96,7 @@ void setUp() throws Exception { @Test void testMandatoryProperties() { SimpleStepFactoryBean factoryBean = new SimpleStepFactoryBean<>(); + factoryBean.setBeanName("test"); assertThrows(IllegalStateException.class, factoryBean::getObject); } @@ -105,6 +104,7 @@ void testMandatoryProperties() { void testMandatoryReader() { // given SimpleStepFactoryBean factory = new SimpleStepFactoryBean<>(); + factory.setBeanName("test"); factory.setItemWriter(writer); // when @@ -118,6 +118,7 @@ void testMandatoryReader() { void testMandatoryWriter() { // given SimpleStepFactoryBean factory = new SimpleStepFactoryBean<>(); + factory.setBeanName("test"); factory.setItemReader(reader); // when @@ -146,25 +147,6 @@ void testSimpleJob() throws Exception { assertTrue(written.contains("foo")); } - @Test - void testSimpleConcurrentJob() throws Exception { - - SimpleStepFactoryBean factory = getStepFactory("foo", "bar"); - factory.setTaskExecutor(new SimpleAsyncTaskExecutor()); - factory.setThrottleLimit(1); - - AbstractStep step = (AbstractStep) factory.getObject(); - step.setName("step1"); - - JobExecution jobExecution = repository.createJobExecution(job.getName(), new JobParameters()); - StepExecution stepExecution = jobExecution.createStepExecution(step.getName()); - repository.add(stepExecution); - step.execute(stepExecution); - assertEquals(BatchStatus.COMPLETED, stepExecution.getStatus()); - assertEquals(2, written.size()); - assertTrue(written.contains("foo")); - } - @Test void testSimpleJobWithItemListeners() throws Exception { 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..ecef7d0f2b 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. @@ -18,14 +18,14 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobInstance; -import org.springframework.batch.core.JobInterruptedException; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepContribution; -import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.StepExecutionListener; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobInstance; +import org.springframework.batch.core.job.JobInterruptedException; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.step.StepContribution; +import org.springframework.batch.core.step.StepExecution; +import org.springframework.batch.core.listener.StepExecutionListener; import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException; import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException; import org.springframework.batch.core.repository.JobRepository; @@ -63,6 +63,7 @@ * @author David Turanski * @author Mahmoud Ben Hassine * @author Parikshit Dutta + * @author Elimelec Burghelea */ class TaskletStepExceptionTests { @@ -85,8 +86,8 @@ void init() { taskletStep.setTransactionManager(new ResourcelessTransactionManager()); JobInstance jobInstance = new JobInstance(1L, "testJob"); - JobExecution jobExecution = new JobExecution(jobInstance, new JobParameters()); - stepExecution = new StepExecution("testStep", jobExecution); + JobExecution jobExecution = new JobExecution(jobInstance, 0L, new JobParameters()); + stepExecution = new StepExecution("testStep", jobExecution, 0L); } @Test @@ -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()); } @@ -537,6 +538,7 @@ public long getStepExecutionCount(JobInstance jobInstance, String stepName) { return 0; } + @SuppressWarnings("removal") @Override public boolean isJobInstanceExists(String jobName, JobParameters jobParameters) { return false; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/job/DefaultJobParametersExtractorJobParametersTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/job/DefaultJobParametersExtractorJobParametersTests.java index 4cd06412fb..41ade41057 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/job/DefaultJobParametersExtractorJobParametersTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/job/DefaultJobParametersExtractorJobParametersTests.java @@ -21,10 +21,10 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobInstance; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobInstance; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.converter.DefaultJobParametersConverter; import org.springframework.core.convert.support.DefaultConversionService; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/job/DefaultJobParametersExtractorTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/job/DefaultJobParametersExtractorTests.java index c94a6d2de5..6af358f34b 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/job/DefaultJobParametersExtractorTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/job/DefaultJobParametersExtractorTests.java @@ -17,11 +17,11 @@ import org.junit.jupiter.api.Test; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobParameter; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.JobParametersBuilder; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameter; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; +import org.springframework.batch.core.step.StepExecution; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/job/JobStepTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/job/JobStepTests.java index 1201625bff..b074af8d33 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/job/JobStepTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/job/JobStepTests.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,14 +22,15 @@ import org.springframework.batch.core.BatchStatus; import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.UnexpectedJobExecutionException; +import org.springframework.batch.core.configuration.support.MapJobRegistry; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.launch.support.TaskExecutorJobOperator; +import org.springframework.batch.core.step.StepExecution; +import org.springframework.batch.core.job.UnexpectedJobExecutionException; import org.springframework.batch.core.job.JobSupport; -import org.springframework.batch.core.launch.support.TaskExecutorJobLauncher; import org.springframework.batch.core.repository.JobRepository; -import org.springframework.batch.core.repository.support.JobRepositoryFactoryBean; +import org.springframework.batch.core.repository.support.JdbcJobRepositoryFactoryBean; import org.springframework.batch.item.ExecutionContext; import org.springframework.jdbc.support.JdbcTransactionManager; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; @@ -46,7 +47,7 @@ */ class JobStepTests { - private final JobStep step = new JobStep(); + private JobStep step; private StepExecution stepExecution; @@ -54,24 +55,25 @@ class JobStepTests { @BeforeEach void setUp() throws Exception { - step.setName("step"); EmbeddedDatabase embeddedDatabase = new EmbeddedDatabaseBuilder() .addScript("/org/springframework/batch/core/schema-drop-hsqldb.sql") .addScript("/org/springframework/batch/core/schema-hsqldb.sql") .build(); - JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean(); + JdbcJobRepositoryFactoryBean factory = new JdbcJobRepositoryFactoryBean(); factory.setDataSource(embeddedDatabase); factory.setTransactionManager(new JdbcTransactionManager(embeddedDatabase)); factory.afterPropertiesSet(); jobRepository = factory.getObject(); - step.setJobRepository(jobRepository); + step = new JobStep(jobRepository); + step.setName("step"); JobExecution jobExecution = jobRepository.createJobExecution("job", new JobParameters()); stepExecution = jobExecution.createStepExecution("step"); jobRepository.add(stepExecution); - TaskExecutorJobLauncher jobLauncher = new TaskExecutorJobLauncher(); - jobLauncher.setJobRepository(jobRepository); - jobLauncher.afterPropertiesSet(); - step.setJobLauncher(jobLauncher); + TaskExecutorJobOperator jobOperator = new TaskExecutorJobOperator(); + jobOperator.setJobRepository(jobRepository); + jobOperator.setJobRegistry(new MapJobRegistry()); + jobOperator.afterPropertiesSet(); + step.setJobOperator(jobOperator); } @Test @@ -80,16 +82,15 @@ void testAfterPropertiesSet() { } @Test - void testAfterPropertiesSetWithNoLauncher() { + void testAfterPropertiesSetWithNoOperator() { step.setJob(new JobSupport("child")); - step.setJobLauncher(null); + step.setJobOperator(null); assertThrows(IllegalStateException.class, step::afterPropertiesSet); } /** * Test method for - * {@link org.springframework.batch.core.step.AbstractStep#execute(org.springframework.batch.core.StepExecution)} - * . + * {@link org.springframework.batch.core.step.AbstractStep#execute(StepExecution)} . */ @Test void testExecuteSunnyDay() throws Exception { diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/skip/ReprocessExceptionTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/skip/ReprocessExceptionTests.java index 6322128526..44cc848ea0 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/skip/ReprocessExceptionTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/skip/ReprocessExceptionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2023 the original author or authors. + * Copyright 2014-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,10 +18,10 @@ 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.JobParametersBuilder; -import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; +import org.springframework.batch.core.launch.JobOperator; import org.springframework.batch.item.Chunk; import org.springframework.batch.item.ItemProcessor; import org.springframework.batch.item.ItemWriter; @@ -42,11 +42,11 @@ public class ReprocessExceptionTests { public Job job; @Autowired - public JobLauncher jobLauncher; + public JobOperator jobOperator; @Test void testReprocessException() throws Exception { - JobExecution execution = jobLauncher.run(job, new JobParametersBuilder().toJobParameters()); + JobExecution execution = jobOperator.start(job, new JobParametersBuilder().toJobParameters()); assertEquals(BatchStatus.COMPLETED, execution.getStatus()); } 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..2d9a293c8f 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. @@ -27,11 +27,11 @@ 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.JobParameter; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameter; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.job.JobSupport; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.item.ItemReader; @@ -53,7 +53,7 @@ * @author Mahmoud Ben Hassine * */ -@SpringJUnitConfig(locations = "/org/springframework/batch/core/repository/dao/sql-dao-test.xml") +@SpringJUnitConfig(locations = "/org/springframework/batch/core/repository/dao/jdbc/sql-dao-test.xml") class AsyncChunkOrientedStepIntegrationTests { private TaskletStep step; @@ -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..09d7d1935e 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. @@ -28,9 +28,9 @@ import org.apache.commons.logging.LogFactory; import org.junit.jupiter.api.Test; import org.springframework.batch.core.BatchStatus; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.step.JobRepositorySupport; import org.springframework.batch.item.ExecutionContext; @@ -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); @@ -120,7 +119,7 @@ void testStepExecutionUpdates() throws Exception { JobExecution jobExecution = jobRepository.createJobExecution("JOB", new JobParameters()); StepExecution stepExecution = jobExecution.createStepExecution(step.getName()); - + stepExecution.setId(0L); step.execute(stepExecution); assertEquals(BatchStatus.COMPLETED, stepExecution.getStatus()); @@ -149,6 +148,7 @@ void testStepExecutionFails() throws Exception { JobExecution jobExecution = jobRepository.createJobExecution("JOB", new JobParameters()); StepExecution stepExecution = jobExecution.createStepExecution(step.getName()); + stepExecution.setId(0L); step.execute(stepExecution); @@ -179,7 +179,7 @@ void testStepExecutionFailsWithProcessor() throws Exception { JobExecution jobExecution = jobRepository.createJobExecution("JOB", new JobParameters()); StepExecution stepExecution = jobExecution.createStepExecution(step.getName()); - + stepExecution.setId(0L); step.execute(stepExecution); assertEquals(BatchStatus.FAILED, stepExecution.getStatus()); @@ -201,6 +201,7 @@ void testStepExecutionFailsOnLastItem() throws Exception { JobExecution jobExecution = jobRepository.createJobExecution("JOB", new JobParameters()); StepExecution stepExecution = jobExecution.createStepExecution(step.getName()); + stepExecution.setId(0L); step.execute(stepExecution); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/tasklet/ChunkOrientedStepIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/tasklet/ChunkOrientedStepIntegrationTests.java index 07d5b9448a..572b452e35 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/tasklet/ChunkOrientedStepIntegrationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/tasklet/ChunkOrientedStepIntegrationTests.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. @@ -19,11 +19,11 @@ import org.junit.jupiter.api.Disabled; 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.JobParameter; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameter; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.job.JobSupport; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.item.ExecutionContext; @@ -47,7 +47,7 @@ * @author Dave Syer * */ -@SpringJUnitConfig(locations = "/org/springframework/batch/core/repository/dao/sql-dao-test.xml") +@SpringJUnitConfig(locations = "/org/springframework/batch/core/repository/dao/jdbc/sql-dao-test.xml") class ChunkOrientedStepIntegrationTests { private TaskletStep step; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/tasklet/ConfigurableSystemProcessExitCodeMapperTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/tasklet/ConfigurableSystemProcessExitCodeMapperTests.java index 7e23e13dfa..4a10f757cf 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/tasklet/ConfigurableSystemProcessExitCodeMapperTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/tasklet/ConfigurableSystemProcessExitCodeMapperTests.java @@ -36,16 +36,13 @@ class ConfigurableSystemProcessExitCodeMapperTests { */ @Test void testMapping() { - Map mappings = new HashMap<>() { - { - put(0, ExitStatus.COMPLETED); - put(1, ExitStatus.FAILED); - put(2, ExitStatus.EXECUTING); - put(3, ExitStatus.NOOP); - put(4, ExitStatus.UNKNOWN); - put(ConfigurableSystemProcessExitCodeMapper.ELSE_KEY, ExitStatus.UNKNOWN); - } - }; + Map mappings = Map.of( // + 0, ExitStatus.COMPLETED, // + 1, ExitStatus.FAILED, // + 2, ExitStatus.EXECUTING, // + 3, ExitStatus.NOOP, // + 4, ExitStatus.UNKNOWN, // + ConfigurableSystemProcessExitCodeMapper.ELSE_KEY, ExitStatus.UNKNOWN); mapper.setMappings(mappings); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/tasklet/MethodInvokingTaskletAdapterTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/tasklet/MethodInvokingTaskletAdapterTests.java index 246e91bdc1..11fc1964c1 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/tasklet/MethodInvokingTaskletAdapterTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/tasklet/MethodInvokingTaskletAdapterTests.java @@ -18,8 +18,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.StepContribution; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.step.StepContribution; import org.springframework.batch.core.scope.context.ChunkContext; import org.springframework.batch.repeat.RepeatStatus; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/tasklet/StepExecutorInterruptionTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/tasklet/StepExecutorInterruptionTests.java index b8bc295ff9..951e29a5a7 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/tasklet/StepExecutorInterruptionTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/tasklet/StepExecutorInterruptionTests.java @@ -23,24 +23,22 @@ import org.junit.jupiter.api.Test; import org.springframework.batch.core.BatchStatus; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobInterruptedException; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobInterruptedException; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.job.JobSupport; import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException; import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.repository.JobRestartException; -import org.springframework.batch.core.repository.support.JobRepositoryFactoryBean; -import org.springframework.batch.item.ItemReader; +import org.springframework.batch.core.repository.support.JdbcJobRepositoryFactoryBean; import org.springframework.batch.item.ItemWriter; import org.springframework.batch.repeat.policy.SimpleCompletionPolicy; import org.springframework.batch.repeat.support.RepeatTemplate; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; import org.springframework.jdbc.support.JdbcTransactionManager; -import org.springframework.lang.Nullable; import org.springframework.transaction.PlatformTransactionManager; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -71,7 +69,7 @@ void setUp() throws Exception { .generateUniqueName(true) .build(); this.transactionManager = new JdbcTransactionManager(embeddedDatabase); - JobRepositoryFactoryBean repositoryFactoryBean = new JobRepositoryFactoryBean(); + JdbcJobRepositoryFactoryBean repositoryFactoryBean = new JdbcJobRepositoryFactoryBean(); repositoryFactoryBean.setDataSource(embeddedDatabase); repositoryFactoryBean.setTransactionManager(this.transactionManager); repositoryFactoryBean.afterPropertiesSet(); @@ -103,22 +101,18 @@ void testInterruptStep() throws Exception { RepeatTemplate template = new RepeatTemplate(); // N.B, If we don't set the completion policy it might run forever template.setCompletionPolicy(new SimpleCompletionPolicy(2)); - step.setTasklet(new TestingChunkOrientedTasklet<>(new ItemReader<>() { - @Nullable - @Override - public Object read() throws Exception { - // do something non-trivial (and not Thread.sleep()) - double foo = 1; - for (int i = 2; i < 250; i++) { - foo = foo * i; - } - - if (foo != 1) { - return foo; - } - else { - return null; - } + step.setTasklet(new TestingChunkOrientedTasklet<>(() -> { + // do something non-trivial (and not Thread.sleep()) + double foo = 1; + for (int i = 2; i < 250; i++) { + foo = foo * i; + } + + if (foo != 1) { + return foo; + } + else { + return null; } }, itemWriter, template)); @@ -166,13 +160,7 @@ public void release() { Thread processingThread = createThread(stepExecution); - step.setTasklet(new TestingChunkOrientedTasklet<>(new ItemReader<>() { - @Nullable - @Override - public Object read() throws Exception { - return null; - } - }, itemWriter)); + step.setTasklet(new TestingChunkOrientedTasklet<>(() -> null, itemWriter)); processingThread.start(); Thread.sleep(100); @@ -212,12 +200,8 @@ public void release() { } }); - step.setTasklet(new TestingChunkOrientedTasklet<>(new ItemReader<>() { - @Nullable - @Override - public Object read() throws Exception { - throw new RuntimeException("Planned!"); - } + step.setTasklet(new TestingChunkOrientedTasklet<>(() -> { + throw new RuntimeException("Planned!"); }, itemWriter)); jobRepository.add(stepExecution); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/tasklet/StepHandlerAdapterTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/tasklet/StepHandlerAdapterTests.java index 420bc1c9da..a0257c3d00 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/tasklet/StepHandlerAdapterTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/tasklet/StepHandlerAdapterTests.java @@ -20,11 +20,11 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobInstance; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.StepContribution; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobInstance; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.step.StepContribution; +import org.springframework.batch.core.step.StepExecution; /** * @author Dave Syer diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/tasklet/SystemCommandTaskletIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/tasklet/SystemCommandTaskletIntegrationTests.java index b703d91ee3..a7b6ced8bb 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/tasklet/SystemCommandTaskletIntegrationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/tasklet/SystemCommandTaskletIntegrationTests.java @@ -26,13 +26,13 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.batch.core.BatchStatus; import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobInstance; -import org.springframework.batch.core.JobInterruptedException; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.StepContribution; -import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.explore.JobExplorer; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobInstance; +import org.springframework.batch.core.job.JobInterruptedException; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.core.step.StepContribution; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.scope.context.ChunkContext; import org.springframework.batch.core.scope.context.StepContext; import org.springframework.batch.repeat.RepeatStatus; @@ -61,7 +61,7 @@ class SystemCommandTaskletIntegrationTests { new JobExecution(new JobInstance(1L, "systemCommandJob"), 1L, new JobParameters())); @Mock - private JobExplorer jobExplorer; + private JobRepository jobRepository; @BeforeEach void setUp() throws Exception { @@ -184,7 +184,7 @@ void testInterruption() throws Exception { * Command Runner is required to be set. */ @Test - public void testCommandRunnerNotSet() throws Exception { + public void testCommandRunnerNotSet() { tasklet.setCommandRunner(null); assertThrows(IllegalStateException.class, tasklet::afterPropertiesSet); } @@ -194,7 +194,10 @@ public void testCommandRunnerNotSet() throws Exception { */ @Test void testCommandNotSet() { - tasklet.setCommand(null); + tasklet.setCommand(); + assertThrows(IllegalStateException.class, tasklet::afterPropertiesSet); + + tasklet.setCommand((String[]) null); assertThrows(IllegalStateException.class, tasklet::afterPropertiesSet); tasklet.setCommand(""); @@ -244,14 +247,14 @@ void testWorkingDirectory() throws Exception { @Test void testStopped() throws Exception { initializeTasklet(); - tasklet.setJobExplorer(jobExplorer); + tasklet.setJobRepository(jobRepository); tasklet.afterPropertiesSet(); tasklet.beforeStep(stepExecution); JobExecution stoppedJobExecution = new JobExecution(stepExecution.getJobExecution()); stoppedJobExecution.setStatus(BatchStatus.STOPPING); - when(jobExplorer.getJobExecution(1L)).thenReturn(stepExecution.getJobExecution(), + when(jobRepository.getJobExecution(1L)).thenReturn(stepExecution.getJobExecution(), stepExecution.getJobExecution(), stoppedJobExecution); String[] command = isRunningOnWindows() ? new String[] { "ping", "127.0.0.1", "-n", "5" } 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..1eb9934642 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. @@ -31,17 +31,17 @@ import org.junit.jupiter.api.Test; import org.springframework.batch.core.BatchStatus; import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobInstance; -import org.springframework.batch.core.JobInterruptedException; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.StepContribution; -import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.StepExecutionListener; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobInstance; +import org.springframework.batch.core.job.JobInterruptedException; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.step.StepContribution; +import org.springframework.batch.core.step.StepExecution; +import org.springframework.batch.core.listener.StepExecutionListener; import org.springframework.batch.core.job.JobSupport; import org.springframework.batch.core.repository.JobRepository; -import org.springframework.batch.core.repository.support.JobRepositoryFactoryBean; +import org.springframework.batch.core.repository.support.JdbcJobRepositoryFactoryBean; import org.springframework.batch.core.scope.context.ChunkContext; import org.springframework.batch.core.step.JobRepositorySupport; import org.springframework.batch.core.step.StepInterruptionPolicy; @@ -127,8 +127,8 @@ void setUp() throws Exception { @Test void testStepExecutor() throws Exception { - JobExecution jobExecutionContext = new JobExecution(jobInstance, jobParameters); - StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext); + JobExecution jobExecutionContext = new JobExecution(jobInstance, 0L, jobParameters); + StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext, 0L); step.execute(stepExecution); assertEquals(1, processed.size()); assertEquals(1, stepExecution.getReadCount()); @@ -137,10 +137,10 @@ void testStepExecutor() throws Exception { @Test void testCommitCount_Even() throws Exception { - JobExecution jobExecutionContext = new JobExecution(jobInstance, jobParameters); + JobExecution jobExecutionContext = new JobExecution(jobInstance, 0L, jobParameters); step = getStep(new String[] { "foo", "bar", "spam", "eggs" }, 2); step.setTransactionManager(transactionManager); - StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext); + StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext, 0L); step.execute(stepExecution); assertEquals(4, processed.size()); assertEquals(4, stepExecution.getReadCount()); @@ -151,10 +151,10 @@ void testCommitCount_Even() throws Exception { @Test void testCommitCount_Uneven() throws Exception { - JobExecution jobExecutionContext = new JobExecution(jobInstance, jobParameters); + JobExecution jobExecutionContext = new JobExecution(jobInstance, 0L, jobParameters); step = getStep(new String[] { "foo", "bar", "spam" }, 2); step.setTransactionManager(transactionManager); - StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext); + StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext, 0L); step.execute(stepExecution); assertEquals(3, processed.size()); assertEquals(3, stepExecution.getReadCount()); @@ -164,8 +164,8 @@ void testCommitCount_Uneven() throws Exception { @Test void testEmptyReader() throws Exception { - JobExecution jobExecutionContext = new JobExecution(jobInstance, jobParameters); - StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext); + JobExecution jobExecutionContext = new JobExecution(jobInstance, 0L, jobParameters); + StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext, 0L); step = getStep(new String[0]); step.setTasklet(new TestingChunkOrientedTasklet<>(getReader(new String[0]), itemWriter, new RepeatTemplate())); step.setStepOperations(new RepeatTemplate()); @@ -184,8 +184,8 @@ void testEmptyReader() throws Exception { @Test void testStepExecutionUpdates() throws Exception { - JobExecution jobExecution = new JobExecution(jobInstance, jobParameters); - StepExecution stepExecution = new StepExecution(step.getName(), jobExecution); + JobExecution jobExecution = new JobExecution(jobInstance, 0L, jobParameters); + StepExecution stepExecution = new StepExecution(step.getName(), jobExecution, 0L); step.setStepOperations(new RepeatTemplate()); @@ -205,8 +205,8 @@ void testStepExecutionUpdates() throws Exception { @Test void testStepExecutionUpdateFailure() throws Exception { - JobExecution jobExecution = new JobExecution(jobInstance, jobParameters); - StepExecution stepExecution = new StepExecution(step.getName(), jobExecution); + JobExecution jobExecution = new JobExecution(jobInstance, 0L, jobParameters); + StepExecution stepExecution = new StepExecution(step.getName(), jobExecution, 0L); JobRepository repository = new JobRepositoryFailedUpdateStub(); @@ -224,7 +224,7 @@ void testRepository() throws Exception { .addScript("/org/springframework/batch/core/schema-hsqldb.sql") .build(); JdbcTransactionManager transactionManager = new JdbcTransactionManager(embeddedDatabase); - JobRepositoryFactoryBean repositoryFactoryBean = new JobRepositoryFactoryBean(); + JdbcJobRepositoryFactoryBean repositoryFactoryBean = new JdbcJobRepositoryFactoryBean(); repositoryFactoryBean.setDataSource(embeddedDatabase); repositoryFactoryBean.setTransactionManager(transactionManager); repositoryFactoryBean.afterPropertiesSet(); @@ -242,19 +242,13 @@ void testRepository() throws Exception { @Test void testIncrementRollbackCount() { - ItemReader itemReader = new ItemReader<>() { - - @Nullable - @Override - public String read() throws Exception { - throw new RuntimeException(); - } - + ItemReader itemReader = () -> { + throw new RuntimeException(); }; step.setTasklet(new TestingChunkOrientedTasklet<>(itemReader, itemWriter)); - JobExecution jobExecutionContext = new JobExecution(jobInstance, jobParameters); - StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext); + JobExecution jobExecutionContext = new JobExecution(jobInstance, 0L, jobParameters); + StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext, 0L); try { step.execute(stepExecution); @@ -268,20 +262,14 @@ public String read() throws Exception { @Test void testExitCodeDefaultClassification() { - ItemReader itemReader = new ItemReader<>() { - - @Nullable - @Override - public String read() throws Exception { - throw new RuntimeException(); - - } + ItemReader itemReader = () -> { + throw new RuntimeException(); }; step.setTasklet(new TestingChunkOrientedTasklet<>(itemReader, itemWriter)); - JobExecution jobExecutionContext = new JobExecution(jobInstance, jobParameters); - StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext); + JobExecution jobExecutionContext = new JobExecution(jobInstance, 0L, jobParameters); + StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext, 0L); try { step.execute(stepExecution); @@ -295,14 +283,8 @@ public String read() throws Exception { @Test void testExitCodeCustomClassification() { - ItemReader itemReader = new ItemReader<>() { - - @Nullable - @Override - public String read() throws Exception { - throw new RuntimeException(); - - } + ItemReader itemReader = () -> { + throw new RuntimeException(); }; @@ -314,8 +296,8 @@ public ExitStatus afterStep(StepExecution stepExecution) { return ExitStatus.FAILED.addExitDescription("FOO"); } }); - JobExecution jobExecutionContext = new JobExecution(jobInstance, jobParameters); - StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext); + JobExecution jobExecutionContext = new JobExecution(jobInstance, 0L, jobParameters); + StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext, 0L); try { step.execute(stepExecution); @@ -337,8 +319,8 @@ void testNonRestartedJob() throws Exception { MockRestartableItemReader tasklet = new MockRestartableItemReader(); step.setTasklet(new TestingChunkOrientedTasklet<>(tasklet, itemWriter)); step.registerStream(tasklet); - JobExecution jobExecutionContext = new JobExecution(jobInstance, jobParameters); - StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext); + JobExecution jobExecutionContext = new JobExecution(jobInstance, 0L, jobParameters); + StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext, 0L); step.execute(stepExecution); @@ -348,8 +330,8 @@ void testNonRestartedJob() throws Exception { @Test void testSuccessfulExecutionWithExecutionContext() throws Exception { - final JobExecution jobExecution = new JobExecution(jobInstance, jobParameters); - final StepExecution stepExecution = new StepExecution(step.getName(), jobExecution); + final JobExecution jobExecution = new JobExecution(jobInstance, 0L, jobParameters); + final StepExecution stepExecution = new StepExecution(step.getName(), jobExecution, 0L); step.setJobRepository(new JobRepositorySupport() { @Override public void updateExecutionContext(StepExecution stepExecution) { @@ -365,8 +347,8 @@ public void updateExecutionContext(StepExecution stepExecution) { @Test void testSuccessfulExecutionWithFailureOnSaveOfExecutionContext() throws Exception { - final JobExecution jobExecution = new JobExecution(jobInstance, jobParameters); - final StepExecution stepExecution = new StepExecution(step.getName(), jobExecution); + final JobExecution jobExecution = new JobExecution(jobInstance, 0L, jobParameters); + final StepExecution stepExecution = new StepExecution(step.getName(), jobExecution, 0L); step.setJobRepository(new JobRepositorySupport() { private int counter = 0; @@ -394,8 +376,8 @@ public void updateExecutionContext(StepExecution stepExecution) { void testNoSaveExecutionAttributesRestartableJob() { MockRestartableItemReader tasklet = new MockRestartableItemReader(); step.setTasklet(new TestingChunkOrientedTasklet<>(tasklet, itemWriter)); - JobExecution jobExecutionContext = new JobExecution(jobInstance, jobParameters); - StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext); + JobExecution jobExecutionContext = new JobExecution(jobInstance, 0L, jobParameters); + StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext, 0L); assertDoesNotThrow(() -> step.execute(stepExecution)); assertFalse(tasklet.isRestoreFromCalled()); @@ -407,15 +389,9 @@ void testNoSaveExecutionAttributesRestartableJob() { */ @Test void testRestartJobOnNonRestartableTasklet() throws Exception { - step.setTasklet(new TestingChunkOrientedTasklet<>(new ItemReader<>() { - @Nullable - @Override - public String read() throws Exception { - return "foo"; - } - }, itemWriter)); - JobExecution jobExecution = new JobExecution(jobInstance, jobParameters); - StepExecution stepExecution = new StepExecution(step.getName(), jobExecution); + step.setTasklet(new TestingChunkOrientedTasklet<>(() -> "foo", itemWriter)); + JobExecution jobExecution = new JobExecution(jobInstance, 0L, jobParameters); + StepExecution stepExecution = new StepExecution(step.getName(), jobExecution, 0L); step.execute(stepExecution); } @@ -437,8 +413,8 @@ public void update(ExecutionContext executionContext) { }; step.setTasklet(new TestingChunkOrientedTasklet<>(reader, itemWriter)); step.registerStream(reader); - JobExecution jobExecution = new JobExecution(jobInstance, jobParameters); - StepExecution stepExecution = new StepExecution(step.getName(), jobExecution); + JobExecution jobExecution = new JobExecution(jobInstance, 0L, jobParameters); + StepExecution stepExecution = new StepExecution(step.getName(), jobExecution, 0L); assertFalse(stepExecution.getExecutionContext().containsKey("foo")); @@ -458,8 +434,8 @@ public void update(ExecutionContext executionContext) { executionContext.putString("foo", "bar"); } } }); - JobExecution jobExecution = new JobExecution(jobInstance, jobParameters); - StepExecution stepExecution = new StepExecution(step.getName(), jobExecution); + JobExecution jobExecution = new JobExecution(jobInstance, 0L, jobParameters); + StepExecution stepExecution = new StepExecution(step.getName(), jobExecution, 0L); assertFalse(stepExecution.getExecutionContext().containsKey("foo")); @@ -483,8 +459,8 @@ public ExitStatus afterStep(StepExecution stepExecution) { return null; } }); - JobExecution jobExecution = new JobExecution(jobInstance, jobParameters); - StepExecution stepExecution = new StepExecution(step.getName(), jobExecution); + JobExecution jobExecution = new JobExecution(jobInstance, 0L, jobParameters); + StepExecution stepExecution = new StepExecution(step.getName(), jobExecution, 0L); step.execute(stepExecution); assertEquals(2, list.size()); } @@ -505,7 +481,8 @@ public void open(ExecutionContext executionContext) throws ItemStreamException { }; step.setStreams(new ItemStream[] { reader }); step.registerStepExecutionListener(reader); - StepExecution stepExecution = new StepExecution(step.getName(), new JobExecution(jobInstance, jobParameters)); + StepExecution stepExecution = new StepExecution(step.getName(), + new JobExecution(jobInstance, 0L, jobParameters), 0L); step.execute(stepExecution); assertEquals(1, list.size()); } @@ -528,8 +505,8 @@ public ExitStatus afterStep(StepExecution stepExecution) { stepTemplate.setCompletionPolicy(new SimpleCompletionPolicy(5)); step.setStepOperations(stepTemplate); - JobExecution jobExecution = new JobExecution(jobInstance, jobParameters); - StepExecution stepExecution = new StepExecution(step.getName(), jobExecution); + JobExecution jobExecution = new JobExecution(jobInstance, 0L, jobParameters); + StepExecution stepExecution = new StepExecution(step.getName(), jobExecution, 0L); step.execute(stepExecution); assertEquals(1, list.size()); ExitStatus returnedStatus = stepExecution.getExitStatus(); @@ -554,8 +531,8 @@ public String read() throws RuntimeException { throw new RuntimeException("FOO"); } }, itemWriter)); - JobExecution jobExecution = new JobExecution(jobInstance, jobParameters); - StepExecution stepExecution = new StepExecution(step.getName(), jobExecution); + JobExecution jobExecution = new JobExecution(jobInstance, 0L, jobParameters); + StepExecution stepExecution = new StepExecution(step.getName(), jobExecution, 0L); step.execute(stepExecution); assertEquals("FOO", stepExecution.getFailureExceptions().get(0).getMessage()); assertEquals(1, list.size()); @@ -578,8 +555,8 @@ public void update(ExecutionContext executionContext) { }; step.setTasklet(new TestingChunkOrientedTasklet<>(reader, itemWriter)); step.setStreams(new ItemStream[] { reader }); - JobExecution jobExecution = new JobExecution(jobInstance, jobParameters); - StepExecution stepExecution = new StepExecution(step.getName(), jobExecution); + JobExecution jobExecution = new JobExecution(jobInstance, 0L, jobParameters); + StepExecution stepExecution = new StepExecution(step.getName(), jobExecution, 0L); assertFalse(stepExecution.getExecutionContext().containsKey("foo")); @@ -599,21 +576,15 @@ void testStatusForInterruptedException() throws Exception { step.setInterruptionPolicy(interruptionPolicy); - ItemReader itemReader = new ItemReader<>() { - - @Nullable - @Override - public String read() throws Exception { - throw new RuntimeException(); - - } + ItemReader itemReader = () -> { + throw new RuntimeException(); }; step.setTasklet(new TestingChunkOrientedTasklet<>(itemReader, itemWriter)); - JobExecution jobExecutionContext = new JobExecution(jobInstance, jobParameters); - StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext); + JobExecution jobExecutionContext = new JobExecution(jobInstance, 0L, jobParameters); + StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext, 0L); stepExecution.setExecutionContext(foobarEc); @@ -627,18 +598,14 @@ public String read() throws Exception { @Test void testStatusForNormalFailure() throws Exception { - ItemReader itemReader = new ItemReader<>() { - @Nullable - @Override - public String read() throws Exception { - // Trigger a rollback - throw new RuntimeException("Foo"); - } + ItemReader itemReader = () -> { + // Trigger a rollback + throw new RuntimeException("Foo"); }; step.setTasklet(new TestingChunkOrientedTasklet<>(itemReader, itemWriter)); - JobExecution jobExecutionContext = new JobExecution(jobInstance, jobParameters); - StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext); + JobExecution jobExecutionContext = new JobExecution(jobInstance, 0L, jobParameters); + StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext, 0L); stepExecution.setExecutionContext(foobarEc); // step.setLastExecution(stepExecution); @@ -652,18 +619,14 @@ public String read() throws Exception { @Test void testStatusForErrorFailure() throws Exception { - ItemReader itemReader = new ItemReader<>() { - @Nullable - @Override - public String read() throws Exception { - // Trigger a rollback - throw new Error("Foo"); - } + ItemReader itemReader = () -> { + // Trigger a rollback + throw new Error("Foo"); }; step.setTasklet(new TestingChunkOrientedTasklet<>(itemReader, itemWriter)); - JobExecution jobExecutionContext = new JobExecution(jobInstance, jobParameters); - StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext); + JobExecution jobExecutionContext = new JobExecution(jobInstance, 0L, jobParameters); + StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext, 0L); stepExecution.setExecutionContext(foobarEc); // step.setLastExecution(stepExecution); @@ -678,13 +641,9 @@ public String read() throws Exception { @Test void testStatusForResetFailedException() throws Exception { - ItemReader itemReader = new ItemReader<>() { - @Nullable - @Override - public String read() throws Exception { - // Trigger a rollback - throw new RuntimeException("Foo"); - } + ItemReader itemReader = () -> { + // Trigger a rollback + throw new RuntimeException("Foo"); }; step.setTasklet(new TestingChunkOrientedTasklet<>(itemReader, itemWriter)); step.setTransactionManager(new ResourcelessTransactionManager() { @@ -695,8 +654,8 @@ protected void doRollback(DefaultTransactionStatus status) throws TransactionExc } }); - JobExecution jobExecutionContext = new JobExecution(jobInstance, jobParameters); - StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext); + JobExecution jobExecutionContext = new JobExecution(jobInstance, 0L, jobParameters); + StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext, 0L); stepExecution.setExecutionContext(foobarEc); // step.setLastExecution(stepExecution); @@ -725,8 +684,8 @@ protected void doRollback(DefaultTransactionStatus status) throws TransactionExc } }); - JobExecution jobExecutionContext = new JobExecution(jobInstance, jobParameters); - StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext); + JobExecution jobExecutionContext = new JobExecution(jobInstance, 0L, jobParameters); + StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext, 0L); stepExecution.setExecutionContext(foobarEc); // step.setLastExecution(stepExecution); @@ -750,8 +709,8 @@ public void close() throws ItemStreamException { } } }); - JobExecution jobExecutionContext = new JobExecution(jobInstance, jobParameters); - StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext); + JobExecution jobExecutionContext = new JobExecution(jobInstance, 0L, jobParameters); + StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext, 0L); step.execute(stepExecution); // The job actually completed, but the streams couldn't be closed. @@ -761,7 +720,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 @@ -778,8 +737,8 @@ public void close() throws ItemStreamException { step.setTasklet(new TestingChunkOrientedTasklet<>(itemReader, itemWriter)); step.registerStream(itemReader); - JobExecution jobExecutionContext = new JobExecution(jobInstance, jobParameters); - StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext); + JobExecution jobExecutionContext = new JobExecution(jobInstance, 0L, jobParameters); + StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext, 0L); stepExecution.setExecutionContext(foobarEc); // step.setLastExecution(stepExecution); @@ -791,7 +750,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()); } /** @@ -811,7 +770,8 @@ public String read() throws RuntimeException { step.setTasklet(new TestingChunkOrientedTasklet<>(reader, itemWriter)); step.registerStream(reader); - StepExecution stepExecution = new StepExecution(step.getName(), new JobExecution(jobInstance, jobParameters)); + StepExecution stepExecution = new StepExecution(step.getName(), + new JobExecution(jobInstance, 0L, jobParameters), 0L); step.execute(stepExecution); assertEquals(BatchStatus.FAILED, stepExecution.getStatus()); @@ -830,8 +790,8 @@ void testStepToCompletion() throws Exception { template.setCompletionPolicy(new DefaultResultCompletionPolicy()); step.setStepOperations(template); - JobExecution jobExecutionContext = new JobExecution(jobInstance, jobParameters); - StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext); + JobExecution jobExecutionContext = new JobExecution(jobInstance, 0L, jobParameters); + StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext, 0L); step.execute(stepExecution); assertEquals(3, processed.size()); @@ -852,7 +812,8 @@ public ExitStatus afterStep(StepExecution stepExecution) { } }; step.setStepExecutionListeners(new StepExecutionListener[] { listener }); - StepExecution stepExecution = new StepExecution(step.getName(), new JobExecution(jobInstance, jobParameters)); + StepExecution stepExecution = new StepExecution(step.getName(), + new JobExecution(jobInstance, 0L, jobParameters), 0L); step.execute(stepExecution); assertEquals(BatchStatus.COMPLETED, stepExecution.getStatus()); @@ -870,8 +831,8 @@ public RepeatStatus execute(StepContribution contribution, ChunkContext chunkCon } }); - JobExecution jobExecutionContext = new JobExecution(jobInstance, jobParameters); - StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext); + JobExecution jobExecutionContext = new JobExecution(jobInstance, 0L, jobParameters); + StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext, 0L); DefaultTransactionAttribute transactionAttribute = new DefaultTransactionAttribute() { @Override @@ -894,8 +855,8 @@ public RepeatStatus execute(StepContribution contribution, ChunkContext chunkCon return null; } }); - JobExecution jobExecutionContext = new JobExecution(jobInstance, jobParameters); - StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext); + JobExecution jobExecutionContext = new JobExecution(jobInstance, 0L, jobParameters); + StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext, 0L); step.execute(stepExecution); assertEquals(BatchStatus.COMPLETED, stepExecution.getStatus()); } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/test/concurrent/ConcurrentTransactionTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/test/concurrent/ConcurrentTransactionTests.java index f8bbf9ebd6..1afb9f3c70 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/test/concurrent/ConcurrentTransactionTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/test/concurrent/ConcurrentTransactionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2023 the original author or authors. + * Copyright 2015-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,18 +25,18 @@ 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.Step; -import org.springframework.batch.core.StepContribution; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.step.StepContribution; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; import org.springframework.batch.core.job.builder.FlowBuilder; import org.springframework.batch.core.job.builder.JobBuilder; import org.springframework.batch.core.job.flow.Flow; -import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.launch.JobOperator; import org.springframework.batch.core.repository.JobRepository; -import org.springframework.batch.core.repository.support.JobRepositoryFactoryBean; +import org.springframework.batch.core.repository.support.JdbcJobRepositoryFactoryBean; import org.springframework.batch.core.scope.context.ChunkContext; import org.springframework.batch.core.step.builder.StepBuilder; import org.springframework.batch.core.step.tasklet.Tasklet; @@ -76,13 +76,13 @@ class ConcurrentTransactionTests { private Job concurrentJob; @Autowired - private JobLauncher jobLauncher; + private JobOperator jobOperator; @DirtiesContext @Test void testConcurrentLongRunningJobExecutions() throws Exception { - JobExecution jobExecution = jobLauncher.run(concurrentJob, new JobParameters()); + JobExecution jobExecution = jobOperator.start(concurrentJob, new JobParameters()); assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus()); } @@ -160,7 +160,7 @@ public Job concurrentJob(JobRepository jobRepository, PlatformTransactionManager @Bean public JobRepository jobRepository(DataSource dataSource, PlatformTransactionManager transactionManager) throws Exception { - JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean(); + JdbcJobRepositoryFactoryBean factory = new JdbcJobRepositoryFactoryBean(); factory.setDataSource(dataSource); factory.setIsolationLevelForCreateEnum(Isolation.READ_COMMITTED); factory.setTransactionManager(transactionManager); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/test/football/FootballJobIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/test/football/FootballJobIntegrationTests.java index 03c6ab6330..c77e28482b 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/test/football/FootballJobIntegrationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/test/football/FootballJobIntegrationTests.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. @@ -22,11 +22,11 @@ import org.apache.commons.logging.LogFactory; 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.JobParametersBuilder; -import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; +import org.springframework.batch.core.step.StepExecution; +import org.springframework.batch.core.launch.JobOperator; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; @@ -42,14 +42,14 @@ public class FootballJobIntegrationTests { private final Log logger = LogFactory.getLog(getClass()); @Autowired - private JobLauncher jobLauncher; + private JobOperator jobOperator; @Autowired private Job job; @Test void testLaunchJob() throws Exception { - JobExecution execution = jobLauncher.run(job, + JobExecution execution = jobOperator.start(job, new JobParametersBuilder().addLong("commit.interval", 10L).toJobParameters()); assertEquals(BatchStatus.COMPLETED, execution.getStatus()); for (StepExecution stepExecution : execution.getStepExecutions()) { diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/test/football/FootballJobSkipIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/test/football/FootballJobSkipIntegrationTests.java index ac63f1209d..8a66cf8d54 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/test/football/FootballJobSkipIntegrationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/test/football/FootballJobSkipIntegrationTests.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. @@ -22,11 +22,11 @@ import org.apache.commons.logging.LogFactory; 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.JobParametersBuilder; -import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; +import org.springframework.batch.core.step.StepExecution; +import org.springframework.batch.core.launch.JobOperator; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; @@ -42,14 +42,14 @@ public class FootballJobSkipIntegrationTests { private final Log logger = LogFactory.getLog(getClass()); @Autowired - private JobLauncher jobLauncher; + private JobOperator jobOperator; @Autowired private Job job; @Test void testLaunchJob() throws Exception { - JobExecution execution = jobLauncher.run(job, + JobExecution execution = jobOperator.start(job, new JobParametersBuilder().addLong("skip.limit", 0L).toJobParameters()); assertEquals(BatchStatus.COMPLETED, execution.getStatus()); for (StepExecution stepExecution : execution.getStepExecutions()) { @@ -58,7 +58,7 @@ void testLaunchJob() throws Exception { // They all skip on the second execution because of a primary key // violation long retryLimit = 2L; - execution = jobLauncher.run(job, + execution = jobOperator.start(job, new JobParametersBuilder().addLong("skip.limit", 100000L) .addLong("retry.limit", retryLimit) .toJobParameters()); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/test/football/ParallelJobIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/test/football/ParallelJobIntegrationTests.java index 8a97177365..2b4fb86e28 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/test/football/ParallelJobIntegrationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/test/football/ParallelJobIntegrationTests.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. @@ -25,11 +25,11 @@ 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.JobParametersBuilder; -import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; +import org.springframework.batch.core.step.StepExecution; +import org.springframework.batch.core.launch.JobOperator; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; @@ -46,7 +46,7 @@ public class ParallelJobIntegrationTests { private final Log logger = LogFactory.getLog(getClass()); @Autowired - private JobLauncher jobLauncher; + private JobOperator jobOperator; private JdbcTemplate jdbcTemplate; @@ -65,7 +65,7 @@ void clear() { @Test void testLaunchJob() throws Exception { - JobExecution execution = jobLauncher.run(job, new JobParametersBuilder().toJobParameters()); + JobExecution execution = jobOperator.start(job, new JobParametersBuilder().toJobParameters()); assertEquals(BatchStatus.COMPLETED, execution.getStatus()); for (StepExecution stepExecution : execution.getStepExecutions()) { logger.info("Processed: " + stepExecution); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/test/ldif/LdifReaderTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/test/ldif/LdifReaderTests.java index 2d5ad4aa46..0f02ca7065 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/test/ldif/LdifReaderTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/test/ldif/LdifReaderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2005-2022 the original author or authors. + * Copyright 2005-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,10 +26,10 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.batch.core.ExitStatus; -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.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.launch.JobOperator; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.core.io.ClassPathResource; @@ -46,7 +46,7 @@ public class LdifReaderTests { private final Resource actual; @Autowired - private JobLauncher jobLauncher; + private JobOperator jobOperator; @Autowired @Qualifier("job1") @@ -68,7 +68,7 @@ void checkFiles() { @Test void testValidRun() throws Exception { - JobExecution jobExecution = jobLauncher.run(job1, new JobParameters()); + JobExecution jobExecution = jobOperator.start(job1, new JobParameters()); // Ensure job completed successfully. Assert.isTrue(jobExecution.getExitStatus().equals(ExitStatus.COMPLETED), @@ -81,7 +81,7 @@ void testValidRun() throws Exception { @Test void testResourceNotExists() throws Exception { - JobExecution jobExecution = jobLauncher.run(job2, new JobParameters()); + JobExecution jobExecution = jobOperator.start(job2, new JobParameters()); Assert.isTrue(jobExecution.getExitStatus().getExitCode().equals("FAILED"), "The job exit status is not FAILED."); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/test/ldif/MappingLdifReaderTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/test/ldif/MappingLdifReaderTests.java index e2c977d7ba..fa5d91fd61 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/test/ldif/MappingLdifReaderTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/test/ldif/MappingLdifReaderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2005-2022 the original author or authors. + * Copyright 2005-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. @@ -27,10 +27,10 @@ import org.slf4j.LoggerFactory; import org.springframework.batch.core.ExitStatus; -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.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.launch.JobOperator; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.core.io.ClassPathResource; @@ -49,7 +49,7 @@ public class MappingLdifReaderTests { private final Resource actual; @Autowired - private JobLauncher launcher; + private JobOperator jobOperator; @Autowired @Qualifier("job1") @@ -71,7 +71,7 @@ void checkFiles() { @Test void testValidRun() throws Exception { - JobExecution jobExecution = launcher.run(job1, new JobParameters()); + JobExecution jobExecution = jobOperator.start(job1, new JobParameters()); // Ensure job completed successfully. Assert.isTrue(jobExecution.getExitStatus().equals(ExitStatus.COMPLETED), @@ -84,7 +84,7 @@ void testValidRun() throws Exception { @Test void testResourceNotExists() throws Exception { - JobExecution jobExecution = launcher.run(job2, new JobParameters()); + JobExecution jobExecution = jobOperator.start(job2, new JobParameters()); Assert.isTrue(jobExecution.getExitStatus().getExitCode().equals("FAILED"), "The job exit status is not FAILED."); 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..853873dfb9 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. @@ -19,20 +19,22 @@ import com.ibm.db2.jcc.DB2SimpleDataSource; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; import org.testcontainers.containers.Db2Container; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; import org.testcontainers.utility.DockerImageName; import org.springframework.batch.core.ExitStatus; -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.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; import org.springframework.batch.core.job.builder.JobBuilder; -import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.launch.JobOperator; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.step.builder.StepBuilder; import org.springframework.batch.repeat.RepeatStatus; @@ -54,10 +56,11 @@ */ @Testcontainers(disabledWithoutDocker = true) @SpringJUnitConfig +@Disabled("https://github.com/spring-projects/spring-batch/issues/4828") 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:12.1.0.0"); @Container public static Db2Container db2 = new Db2Container(DB2_IMAGE).acceptLicense(); @@ -66,7 +69,7 @@ class Db2JobRepositoryIntegrationTests { private DataSource dataSource; @Autowired - private JobLauncher jobLauncher; + private JobOperator jobOperator; @Autowired private Job job; @@ -84,7 +87,7 @@ void testJobExecution() throws Exception { JobParameters jobParameters = new JobParametersBuilder().toJobParameters(); // when - JobExecution jobExecution = this.jobLauncher.run(this.job, jobParameters); + JobExecution jobExecution = this.jobOperator.start(this.job, jobParameters); // then assertNotNull(jobExecution); @@ -93,6 +96,7 @@ void testJobExecution() throws Exception { @Configuration @EnableBatchProcessing + @EnableJdbcJobRepository static class TestConfiguration { @Bean diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/test/repository/DerbyJobRepositoryIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/test/repository/DerbyJobRepositoryIntegrationTests.java index fdd8cde55e..98a15ce829 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/test/repository/DerbyJobRepositoryIntegrationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/test/repository/DerbyJobRepositoryIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2022 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. @@ -20,13 +20,14 @@ import org.junit.jupiter.api.Test; import org.springframework.batch.core.ExitStatus; -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.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; import org.springframework.batch.core.job.builder.JobBuilder; -import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.launch.JobOperator; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.step.builder.StepBuilder; import org.springframework.batch.repeat.RepeatStatus; @@ -49,7 +50,7 @@ class DerbyJobRepositoryIntegrationTests { @Autowired - private JobLauncher jobLauncher; + private JobOperator jobOperator; @Autowired private Job job; @@ -60,7 +61,7 @@ void testJobExecution() throws Exception { JobParameters jobParameters = new JobParametersBuilder().toJobParameters(); // when - JobExecution jobExecution = this.jobLauncher.run(this.job, jobParameters); + JobExecution jobExecution = this.jobOperator.start(this.job, jobParameters); // then assertNotNull(jobExecution); @@ -69,6 +70,7 @@ void testJobExecution() throws Exception { @Configuration @EnableBatchProcessing + @EnableJdbcJobRepository static class TestConfiguration { @Bean diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/test/repository/H2CompatibilityModeJobRepositoryIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/test/repository/H2CompatibilityModeJobRepositoryIntegrationTests.java index 759488e890..93127d8f17 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/test/repository/H2CompatibilityModeJobRepositoryIntegrationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/test/repository/H2CompatibilityModeJobRepositoryIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2022 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. @@ -24,11 +24,12 @@ import org.junit.jupiter.params.provider.EnumSource; import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.parameters.JobParameters; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; import org.springframework.batch.core.job.builder.JobBuilder; -import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.launch.JobOperator; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.step.builder.StepBuilder; import org.springframework.batch.repeat.RepeatStatus; @@ -59,10 +60,10 @@ void testJobExecution(ModeEnum compatibilityMode) throws Exception { context.register(TestConfiguration.class); context.registerBean(DataSource.class, () -> buildDataSource(compatibilityMode)); context.refresh(); - var jobLauncher = context.getBean(JobLauncher.class); + var jobOperator = context.getBean(JobOperator.class); var job = context.getBean(Job.class); - var jobExecution = jobLauncher.run(job, new JobParameters()); + var jobExecution = jobOperator.start(job, new JobParameters()); assertNotNull(jobExecution); assertEquals(ExitStatus.COMPLETED, jobExecution.getExitStatus()); @@ -84,6 +85,7 @@ private static DataSource buildDataSource(ModeEnum compatibilityMode) { @Configuration @EnableBatchProcessing + @EnableJdbcJobRepository static class TestConfiguration { @Bean diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/test/repository/H2JobRepositoryIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/test/repository/H2JobRepositoryIntegrationTests.java index 33c02601be..3aad5e8f55 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/test/repository/H2JobRepositoryIntegrationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/test/repository/H2JobRepositoryIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2022 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. @@ -20,13 +20,14 @@ import org.junit.jupiter.api.Test; import org.springframework.batch.core.ExitStatus; -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.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; import org.springframework.batch.core.job.builder.JobBuilder; -import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.launch.JobOperator; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.step.builder.StepBuilder; import org.springframework.batch.repeat.RepeatStatus; @@ -49,7 +50,7 @@ class H2JobRepositoryIntegrationTests { @Autowired - private JobLauncher jobLauncher; + private JobOperator jobOperator; @Autowired private Job job; @@ -60,7 +61,7 @@ void testJobExecution() throws Exception { JobParameters jobParameters = new JobParametersBuilder().toJobParameters(); // when - JobExecution jobExecution = this.jobLauncher.run(this.job, jobParameters); + JobExecution jobExecution = this.jobOperator.start(this.job, jobParameters); // then assertNotNull(jobExecution); @@ -69,6 +70,7 @@ void testJobExecution() throws Exception { @Configuration @EnableBatchProcessing + @EnableJdbcJobRepository static class TestConfiguration { @Bean diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/test/repository/HANAJobRepositoryIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/test/repository/HANAJobRepositoryIntegrationTests.java index 5b8d8a0a6e..78ac4104f0 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/test/repository/HANAJobRepositoryIntegrationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/test/repository/HANAJobRepositoryIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2023 the original author or authors. + * Copyright 2023-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,13 +28,14 @@ import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.batch.core.ExitStatus; -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.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; import org.springframework.batch.core.job.builder.JobBuilder; -import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.launch.JobOperator; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.step.builder.StepBuilder; import org.springframework.batch.repeat.RepeatStatus; @@ -85,7 +86,7 @@ class HANAJobRepositoryIntegrationTests { private DataSource dataSource; @Autowired - private JobLauncher jobLauncher; + private JobOperator jobOperator; @Autowired private Job job; @@ -103,7 +104,7 @@ void testJobExecution() throws Exception { JobParameters jobParameters = new JobParametersBuilder().toJobParameters(); // when - JobExecution jobExecution = this.jobLauncher.run(this.job, jobParameters); + JobExecution jobExecution = this.jobOperator.start(this.job, jobParameters); // then assertNotNull(jobExecution); @@ -112,6 +113,7 @@ void testJobExecution() throws Exception { @Configuration @EnableBatchProcessing + @EnableJdbcJobRepository static class TestConfiguration { @Bean diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/test/repository/HSQLDBJobRepositoryIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/test/repository/HSQLDBJobRepositoryIntegrationTests.java index d71b6550e8..9e8d5fd447 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/test/repository/HSQLDBJobRepositoryIntegrationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/test/repository/HSQLDBJobRepositoryIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2022 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. @@ -20,13 +20,14 @@ import org.junit.jupiter.api.Test; import org.springframework.batch.core.ExitStatus; -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.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; import org.springframework.batch.core.job.builder.JobBuilder; -import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.launch.JobOperator; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.step.builder.StepBuilder; import org.springframework.batch.repeat.RepeatStatus; @@ -49,7 +50,7 @@ class HSQLDBJobRepositoryIntegrationTests { @Autowired - private JobLauncher jobLauncher; + private JobOperator jobOperator; @Autowired private Job job; @@ -60,7 +61,7 @@ void testJobExecution() throws Exception { JobParameters jobParameters = new JobParametersBuilder().toJobParameters(); // when - JobExecution jobExecution = this.jobLauncher.run(this.job, jobParameters); + JobExecution jobExecution = this.jobOperator.start(this.job, jobParameters); // then assertNotNull(jobExecution); @@ -69,6 +70,7 @@ void testJobExecution() throws Exception { @Configuration @EnableBatchProcessing + @EnableJdbcJobRepository static class TestConfiguration { @Bean diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/test/repository/JdbcJobRepositoryTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/test/repository/JdbcJobRepositoryTests.java index 0ef2de816a..6d94031637 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/test/repository/JdbcJobRepositoryTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/test/repository/JdbcJobRepositoryTests.java @@ -28,8 +28,8 @@ import org.junit.jupiter.api.Test; import org.springframework.batch.core.BatchStatus; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException; import org.springframework.batch.core.repository.JobRepository; import org.springframework.beans.factory.annotation.Autowired; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/test/repository/JobSupport.java b/spring-batch-core/src/test/java/org/springframework/batch/core/test/repository/JobSupport.java index 78ca3ff809..0155a1cde7 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/test/repository/JobSupport.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/test/repository/JobSupport.java @@ -19,12 +19,12 @@ import java.util.ArrayList; import java.util.List; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobParametersValidator; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.UnexpectedJobExecutionException; -import org.springframework.batch.core.job.DefaultJobParametersValidator; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParametersValidator; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.job.UnexpectedJobExecutionException; +import org.springframework.batch.core.job.parameters.DefaultJobParametersValidator; import org.springframework.beans.factory.BeanNameAware; import org.springframework.util.ClassUtils; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/test/repository/MariaDBJobRepositoryIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/test/repository/MariaDBJobRepositoryIntegrationTests.java index 3ca4527da3..d4e6cdfcac 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/test/repository/MariaDBJobRepositoryIntegrationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/test/repository/MariaDBJobRepositoryIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2023 the original author or authors. + * Copyright 2023-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. @@ -20,19 +20,20 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mariadb.jdbc.MariaDbDataSource; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; import org.testcontainers.containers.MariaDBContainer; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; import org.testcontainers.utility.DockerImageName; import org.springframework.batch.core.ExitStatus; -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.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; import org.springframework.batch.core.job.builder.JobBuilder; -import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.launch.JobOperator; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.step.builder.StepBuilder; import org.springframework.batch.repeat.RepeatStatus; @@ -57,7 +58,7 @@ class MariaDBJobRepositoryIntegrationTests { // TODO find the best way to externalize and manage image versions - private static final DockerImageName MARIADB_IMAGE = DockerImageName.parse("mariadb:10.9.3"); + private static final DockerImageName MARIADB_IMAGE = DockerImageName.parse("mariadb:11.8.2"); @Container public static MariaDBContainer mariaDBContainer = new MariaDBContainer<>(MARIADB_IMAGE); @@ -66,7 +67,7 @@ class MariaDBJobRepositoryIntegrationTests { private DataSource dataSource; @Autowired - private JobLauncher jobLauncher; + private JobOperator jobOperator; @Autowired private Job job; @@ -84,7 +85,7 @@ void testJobExecution() throws Exception { JobParameters jobParameters = new JobParametersBuilder().toJobParameters(); // when - JobExecution jobExecution = this.jobLauncher.run(this.job, jobParameters); + JobExecution jobExecution = this.jobOperator.start(this.job, jobParameters); // then assertNotNull(jobExecution); @@ -93,6 +94,7 @@ void testJobExecution() throws Exception { @Configuration @EnableBatchProcessing + @EnableJdbcJobRepository static class TestConfiguration { @Bean diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/test/repository/MySQLJdbcJobRepositoryIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/test/repository/MySQLJdbcJobRepositoryIntegrationTests.java index 67009a0465..1d124b27af 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/test/repository/MySQLJdbcJobRepositoryIntegrationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/test/repository/MySQLJdbcJobRepositoryIntegrationTests.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. @@ -25,18 +25,18 @@ import com.mysql.cj.jdbc.MysqlDataSource; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; import org.testcontainers.containers.MySQLContainer; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; import org.testcontainers.utility.DockerImageName; -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.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; import org.springframework.batch.core.job.builder.JobBuilder; -import org.springframework.batch.core.launch.JobLauncher; import org.springframework.batch.core.launch.JobOperator; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.step.builder.StepBuilder; @@ -63,7 +63,7 @@ class MySQLJdbcJobRepositoryIntegrationTests { // TODO find the best way to externalize and manage image versions // when implementing https://github.com/spring-projects/spring-batch/issues/3092 - private static final DockerImageName MYSQL_IMAGE = DockerImageName.parse("mysql:8.0.31"); + private static final DockerImageName MYSQL_IMAGE = DockerImageName.parse("mysql:9.2.0"); @Container public static MySQLContainer mysql = new MySQLContainer<>(MYSQL_IMAGE); @@ -71,9 +71,6 @@ class MySQLJdbcJobRepositoryIntegrationTests { @Autowired private DataSource dataSource; - @Autowired - private JobLauncher jobLauncher; - @Autowired private JobOperator jobOperator; @@ -100,6 +97,7 @@ void setUp() { * Note the issue does not happen if the parameter is of type Long (when using * addLong("date", date.getTime()) for instance). */ + @SuppressWarnings("removal") @Test void testDateMillisecondPrecision() throws Exception { // given @@ -107,7 +105,7 @@ void testDateMillisecondPrecision() throws Exception { JobParameters jobParameters = new JobParametersBuilder().addDate("date", date).toJobParameters(); // when - JobExecution jobExecution = this.jobLauncher.run(this.job, jobParameters); + JobExecution jobExecution = this.jobOperator.start(this.job, jobParameters); this.jobOperator.restart(jobExecution.getId()); // should load the date parameter // with fractional seconds // precision here @@ -121,6 +119,7 @@ void testDateMillisecondPrecision() throws Exception { @Configuration @EnableBatchProcessing + @EnableJdbcJobRepository static class TestConfiguration { @Bean diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/test/repository/MySQLJobRepositoryIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/test/repository/MySQLJobRepositoryIntegrationTests.java index 24bd70d9dc..413f52b6da 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/test/repository/MySQLJobRepositoryIntegrationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/test/repository/MySQLJobRepositoryIntegrationTests.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. @@ -20,19 +20,20 @@ import com.mysql.cj.jdbc.MysqlDataSource; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; import org.testcontainers.containers.MySQLContainer; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; import org.testcontainers.utility.DockerImageName; import org.springframework.batch.core.ExitStatus; -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.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; import org.springframework.batch.core.job.builder.JobBuilder; -import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.launch.JobOperator; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.step.builder.StepBuilder; import org.springframework.batch.repeat.RepeatStatus; @@ -57,7 +58,7 @@ class MySQLJobRepositoryIntegrationTests { // TODO find the best way to externalize and manage image versions - private static final DockerImageName MYSQL_IMAGE = DockerImageName.parse("mysql:8.0.31"); + private static final DockerImageName MYSQL_IMAGE = DockerImageName.parse("mysql:9.2.0"); @Container public static MySQLContainer mysql = new MySQLContainer<>(MYSQL_IMAGE); @@ -66,7 +67,7 @@ class MySQLJobRepositoryIntegrationTests { private DataSource dataSource; @Autowired - private JobLauncher jobLauncher; + private JobOperator jobOperator; @Autowired private Job job; @@ -84,7 +85,7 @@ void testJobExecution() throws Exception { JobParameters jobParameters = new JobParametersBuilder().toJobParameters(); // when - JobExecution jobExecution = this.jobLauncher.run(this.job, jobParameters); + JobExecution jobExecution = this.jobOperator.start(this.job, jobParameters); // then assertNotNull(jobExecution); @@ -93,6 +94,7 @@ void testJobExecution() throws Exception { @Configuration @EnableBatchProcessing + @EnableJdbcJobRepository static class TestConfiguration { @Bean diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/test/repository/OracleJobRepositoryIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/test/repository/OracleJobRepositoryIntegrationTests.java index e46fe91d0b..2fc2ee1f0d 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/test/repository/OracleJobRepositoryIntegrationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/test/repository/OracleJobRepositoryIntegrationTests.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. @@ -21,19 +21,20 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; import org.testcontainers.containers.OracleContainer; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; import org.testcontainers.utility.DockerImageName; import org.springframework.batch.core.ExitStatus; -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.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; import org.springframework.batch.core.job.builder.JobBuilder; -import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.launch.JobOperator; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.step.builder.StepBuilder; import org.springframework.batch.repeat.RepeatStatus; @@ -74,7 +75,7 @@ class OracleJobRepositoryIntegrationTests { private DataSource dataSource; @Autowired - private JobLauncher jobLauncher; + private JobOperator jobOperator; @Autowired private Job job; @@ -92,7 +93,7 @@ void testJobExecution() throws Exception { JobParameters jobParameters = new JobParametersBuilder().toJobParameters(); // when - JobExecution jobExecution = this.jobLauncher.run(this.job, jobParameters); + JobExecution jobExecution = this.jobOperator.start(this.job, jobParameters); // then assertNotNull(jobExecution); @@ -101,6 +102,7 @@ void testJobExecution() throws Exception { @Configuration @EnableBatchProcessing + @EnableJdbcJobRepository static class TestConfiguration { @Bean diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/test/repository/PostgreSQLJobRepositoryIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/test/repository/PostgreSQLJobRepositoryIntegrationTests.java index ec555014d5..69c175294a 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/test/repository/PostgreSQLJobRepositoryIntegrationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/test/repository/PostgreSQLJobRepositoryIntegrationTests.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. @@ -20,19 +20,20 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.postgresql.ds.PGSimpleDataSource; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; import org.testcontainers.containers.PostgreSQLContainer; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; import org.testcontainers.utility.DockerImageName; import org.springframework.batch.core.ExitStatus; -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.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; import org.springframework.batch.core.job.builder.JobBuilder; -import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.launch.JobOperator; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.step.builder.StepBuilder; import org.springframework.batch.repeat.RepeatStatus; @@ -57,7 +58,7 @@ class PostgreSQLJobRepositoryIntegrationTests { // TODO find the best way to externalize and manage image versions - private static final DockerImageName POSTGRESQL_IMAGE = DockerImageName.parse("postgres:13.3"); + private static final DockerImageName POSTGRESQL_IMAGE = DockerImageName.parse("postgres:17.5"); @Container public static PostgreSQLContainer postgres = new PostgreSQLContainer<>(POSTGRESQL_IMAGE); @@ -66,7 +67,7 @@ class PostgreSQLJobRepositoryIntegrationTests { private DataSource dataSource; @Autowired - private JobLauncher jobLauncher; + private JobOperator jobOperator; @Autowired private Job job; @@ -84,7 +85,7 @@ void testJobExecution() throws Exception { JobParameters jobParameters = new JobParametersBuilder().toJobParameters(); // when - JobExecution jobExecution = this.jobLauncher.run(this.job, jobParameters); + JobExecution jobExecution = this.jobOperator.start(this.job, jobParameters); // then assertNotNull(jobExecution); @@ -93,6 +94,7 @@ void testJobExecution() throws Exception { @Configuration @EnableBatchProcessing + @EnableJdbcJobRepository static class TestConfiguration { @Bean diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/test/repository/SQLServerJobRepositoryIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/test/repository/SQLServerJobRepositoryIntegrationTests.java index b8373688f1..d2fdc9e49f 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/test/repository/SQLServerJobRepositoryIntegrationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/test/repository/SQLServerJobRepositoryIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2024 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. @@ -19,20 +19,22 @@ import com.microsoft.sqlserver.jdbc.SQLServerDataSource; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; import org.testcontainers.containers.MSSQLServerContainer; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; import org.testcontainers.utility.DockerImageName; import org.springframework.batch.core.ExitStatus; -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.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; import org.springframework.batch.core.job.builder.JobBuilder; -import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.launch.JobOperator; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.step.builder.StepBuilder; import org.springframework.batch.repeat.RepeatStatus; @@ -54,6 +56,7 @@ */ @Testcontainers(disabledWithoutDocker = true) @SpringJUnitConfig +@Disabled("https://github.com/spring-projects/spring-batch/issues/4828") class SQLServerJobRepositoryIntegrationTests { // TODO find the best way to externalize and manage image versions @@ -67,7 +70,7 @@ class SQLServerJobRepositoryIntegrationTests { private DataSource dataSource; @Autowired - private JobLauncher jobLauncher; + private JobOperator jobOperator; @Autowired private Job job; @@ -85,7 +88,7 @@ void testJobExecution() throws Exception { JobParameters jobParameters = new JobParametersBuilder().toJobParameters(); // when - JobExecution jobExecution = this.jobLauncher.run(this.job, jobParameters); + JobExecution jobExecution = this.jobOperator.start(this.job, jobParameters); // then assertNotNull(jobExecution); @@ -94,6 +97,7 @@ void testJobExecution() throws Exception { @Configuration @EnableBatchProcessing + @EnableJdbcJobRepository static class TestConfiguration { @Bean diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/test/repository/SQLiteJobRepositoryIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/test/repository/SQLiteJobRepositoryIntegrationTests.java index 5c7a9676fa..e01e5d85ad 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/test/repository/SQLiteJobRepositoryIntegrationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/test/repository/SQLiteJobRepositoryIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2022 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. @@ -18,16 +18,17 @@ import javax.sql.DataSource; import org.junit.jupiter.api.Test; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; import org.sqlite.SQLiteDataSource; import org.springframework.batch.core.ExitStatus; -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.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; import org.springframework.batch.core.job.builder.JobBuilder; -import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.launch.JobOperator; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.step.builder.StepBuilder; import org.springframework.batch.repeat.RepeatStatus; @@ -50,7 +51,7 @@ class SQLiteJobRepositoryIntegrationTests { @Autowired - private JobLauncher jobLauncher; + private JobOperator jobOperator; @Autowired private Job job; @@ -61,7 +62,7 @@ void testJobExecution() throws Exception { JobParameters jobParameters = new JobParametersBuilder().toJobParameters(); // when - JobExecution jobExecution = this.jobLauncher.run(this.job, jobParameters); + JobExecution jobExecution = this.jobOperator.start(this.job, jobParameters); // then assertNotNull(jobExecution); @@ -70,6 +71,7 @@ void testJobExecution() throws Exception { @Configuration @EnableBatchProcessing + @EnableJdbcJobRepository static class TestConfiguration { @Bean diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/test/repository/SybaseJobRepositoryIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/test/repository/SybaseJobRepositoryIntegrationTests.java index e8c4076d68..89753baebf 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/test/repository/SybaseJobRepositoryIntegrationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/test/repository/SybaseJobRepositoryIntegrationTests.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. @@ -23,13 +23,14 @@ import org.junit.jupiter.api.Test; import org.springframework.batch.core.ExitStatus; -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.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; import org.springframework.batch.core.job.builder.JobBuilder; -import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.launch.JobOperator; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.step.builder.StepBuilder; import org.springframework.batch.repeat.RepeatStatus; @@ -64,7 +65,7 @@ class SybaseJobRepositoryIntegrationTests { private DataSource dataSource; @Autowired - private JobLauncher jobLauncher; + private JobOperator jobOperator; @Autowired private Job job; @@ -82,7 +83,7 @@ void testJobExecution() throws Exception { JobParameters jobParameters = new JobParametersBuilder().toJobParameters(); // when - JobExecution jobExecution = this.jobLauncher.run(this.job, jobParameters); + JobExecution jobExecution = this.jobOperator.start(this.job, jobParameters); // then assertNotNull(jobExecution); @@ -91,6 +92,7 @@ void testJobExecution() throws Exception { @Configuration @EnableBatchProcessing + @EnableJdbcJobRepository static class TestConfiguration { // FIXME Configuration parameters are hard-coded for the moment, to update once 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..8aa43c8d8a 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,31 +16,29 @@ 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; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.step.factory.FaultTolerantStepFactoryBean; import org.springframework.batch.item.Chunk; 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,9 +46,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}. @@ -69,12 +67,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 +79,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 +95,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 +113,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 +134,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 +157,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 +171,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 +187,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 +199,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..fc57e0b3d7 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,30 +19,29 @@ 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; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.step.factory.FaultTolerantStepFactoryBean; import org.springframework.batch.item.Chunk; 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,7 +49,6 @@ 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; @@ -70,12 +68,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 +80,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 +91,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 +109,7 @@ void testUpdatesNoRollback() throws Exception { } @Test + @Timeout(value = 30) void testMultithreadedSkipInWriter() throws Throwable { ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor(); @@ -126,11 +119,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 +129,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 +165,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 +182,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 +209,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 +232,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"); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/test/step/FaultTolerantStepIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/test/step/FaultTolerantStepIntegrationTests.java index 32579d8208..e4c8cadca9 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/test/step/FaultTolerantStepIntegrationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/test/step/FaultTolerantStepIntegrationTests.java @@ -25,10 +25,10 @@ import org.springframework.batch.core.BatchStatus; import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.step.builder.FaultTolerantStepBuilder; import org.springframework.batch.core.step.builder.StepBuilder; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/test/step/StepExecutionSerializationUtilsTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/test/step/StepExecutionSerializationUtilsTests.java index b288afb4bb..a96fc2ab89 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/test/step/StepExecutionSerializationUtilsTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/test/step/StepExecutionSerializationUtilsTests.java @@ -25,10 +25,10 @@ import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobInstance; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobInstance; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.step.StepExecution; import org.springframework.util.SerializationUtils; /** diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/test/timeout/SleepingTasklet.java b/spring-batch-core/src/test/java/org/springframework/batch/core/test/timeout/SleepingTasklet.java index fbaca85de5..8ca60d90fa 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/test/timeout/SleepingTasklet.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/test/timeout/SleepingTasklet.java @@ -15,7 +15,7 @@ */ package org.springframework.batch.core.test.timeout; -import org.springframework.batch.core.StepContribution; +import org.springframework.batch.core.step.StepContribution; import org.springframework.batch.core.scope.context.ChunkContext; import org.springframework.batch.core.step.tasklet.Tasklet; import org.springframework.batch.repeat.RepeatStatus; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/test/timeout/TimeoutJobIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/test/timeout/TimeoutJobIntegrationTests.java index 3a28174745..3c62fa8689 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/test/timeout/TimeoutJobIntegrationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/test/timeout/TimeoutJobIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2023 the original author or authors. + * Copyright 2014-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,10 +19,10 @@ 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.JobParametersBuilder; -import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; +import org.springframework.batch.core.launch.JobOperator; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; @@ -31,7 +31,7 @@ public class TimeoutJobIntegrationTests { @Autowired - private JobLauncher jobLauncher; + private JobOperator jobOperator; @Autowired @Qualifier("chunkTimeoutJob") @@ -43,14 +43,14 @@ public class TimeoutJobIntegrationTests { @Test void testChunkTimeoutShouldFail() throws Exception { - JobExecution execution = jobLauncher.run(chunkTimeoutJob, + JobExecution execution = jobOperator.start(chunkTimeoutJob, new JobParametersBuilder().addLong("id", System.currentTimeMillis()).toJobParameters()); assertEquals(BatchStatus.FAILED, execution.getStatus()); } @Test void testTaskletTimeoutShouldFail() throws Exception { - JobExecution execution = jobLauncher.run(taskletTimeoutJob, + JobExecution execution = jobOperator.start(taskletTimeoutJob, new JobParametersBuilder().addLong("id", System.currentTimeMillis()).toJobParameters()); assertEquals(BatchStatus.FAILED, execution.getStatus()); } diff --git a/spring-batch-core/src/test/resources/applicationContext-test2.xml b/spring-batch-core/src/test/resources/applicationContext-test2.xml index fe27a3211d..00d6787268 100644 --- a/spring-batch-core/src/test/resources/applicationContext-test2.xml +++ b/spring-batch-core/src/test/resources/applicationContext-test2.xml @@ -48,11 +48,6 @@ - - - - - diff --git a/spring-batch-core/src/test/resources/data/persons-bad-data.csv b/spring-batch-core/src/test/resources/data/persons-bad-data.csv new file mode 100644 index 0000000000..6a9ea149e3 --- /dev/null +++ b/spring-batch-core/src/test/resources/data/persons-bad-data.csv @@ -0,0 +1,6 @@ +1,foo1 +2,foo2 +f,foo3 +4,foooo4 +f,foo5 +6,foo6 \ No newline at end of file diff --git a/spring-batch-core/src/test/resources/data/persons.csv b/spring-batch-core/src/test/resources/data/persons.csv new file mode 100644 index 0000000000..99f62d7a3e --- /dev/null +++ b/spring-batch-core/src/test/resources/data/persons.csv @@ -0,0 +1,5 @@ +1,foo1 +2,foo2 +3,foo3 +4,foo4 +5,foo5 \ No newline at end of file diff --git a/spring-batch-core/src/test/resources/foo.sql b/spring-batch-core/src/test/resources/foo.sql index 1c32c772d2..24dc334b8e 100644 --- a/spring-batch-core/src/test/resources/foo.sql +++ b/spring-batch-core/src/test/resources/foo.sql @@ -9,9 +9,9 @@ CREATE TABLE T_FOOS ( ALTER TABLE T_FOOS ADD PRIMARY KEY (ID); -CREATE TABLE ERROR_LOG ( - JOB_NAME CHAR(20) , - STEP_NAME CHAR(20) , +CREATE TABLE ERROR_LOG ( + JOB_NAME CHAR(20), + STEP_NAME CHAR(20), MESSAGE VARCHAR(300) NOT NULL ) ; diff --git a/spring-batch-core/src/test/resources/football-schema-hsqldb.sql b/spring-batch-core/src/test/resources/football-schema-hsqldb.sql index 3ab21d7c0e..d411108131 100644 --- a/spring-batch-core/src/test/resources/football-schema-hsqldb.sql +++ b/spring-batch-core/src/test/resources/football-schema-hsqldb.sql @@ -7,7 +7,7 @@ CREATE TABLE PLAYERS ( PLAYER_ID CHAR(8) NOT NULL PRIMARY KEY, LAST_NAME VARCHAR(35) NOT NULL, FIRST_NAME VARCHAR(25) NOT NULL, - POS VARCHAR(10) , + POS VARCHAR(10), YEAR_OF_BIRTH BIGINT NOT NULL, YEAR_DRAFTED BIGINT NOT NULL ) ; @@ -17,30 +17,30 @@ CREATE TABLE GAMES ( YEAR_NO BIGINT NOT NULL, TEAM CHAR(3) NOT NULL, WEEK BIGINT NOT NULL, - OPPONENT CHAR(3) , - COMPLETES BIGINT , - ATTEMPTS BIGINT , - PASSING_YARDS BIGINT , - PASSING_TD BIGINT , - INTERCEPTIONS BIGINT , - RUSHES BIGINT , - RUSH_YARDS BIGINT , - RECEPTIONS BIGINT , - RECEPTIONS_YARDS BIGINT , + OPPONENT CHAR(3), + COMPLETES BIGINT, + ATTEMPTS BIGINT, + PASSING_YARDS BIGINT, + PASSING_TD BIGINT, + INTERCEPTIONS BIGINT, + RUSHES BIGINT, + RUSH_YARDS BIGINT, + RECEPTIONS BIGINT, + RECEPTIONS_YARDS BIGINT, TOTAL_TD BIGINT ) ; -CREATE TABLE PLAYER_SUMMARY ( +CREATE TABLE PLAYER_SUMMARY ( ID CHAR(8) NOT NULL, YEAR_NO BIGINT NOT NULL, - COMPLETES BIGINT NOT NULL , - ATTEMPTS BIGINT NOT NULL , - PASSING_YARDS BIGINT NOT NULL , - PASSING_TD BIGINT NOT NULL , - INTERCEPTIONS BIGINT NOT NULL , - RUSHES BIGINT NOT NULL , - RUSH_YARDS BIGINT NOT NULL , - RECEPTIONS BIGINT NOT NULL , - RECEPTIONS_YARDS BIGINT NOT NULL , + COMPLETES BIGINT NOT NULL, + ATTEMPTS BIGINT NOT NULL, + PASSING_YARDS BIGINT NOT NULL, + PASSING_TD BIGINT NOT NULL, + INTERCEPTIONS BIGINT NOT NULL, + RUSHES BIGINT NOT NULL, + RUSH_YARDS BIGINT NOT NULL, + RECEPTIONS BIGINT NOT NULL, + RECEPTIONS_YARDS BIGINT NOT NULL, TOTAL_TD BIGINT NOT NULL ) ; 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..8dff8d97d8 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,13 +13,9 @@ - - - - - + diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/support/test-context-with-smart-initializing-singleton.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/support/test-context-with-smart-initializing-singleton.xml deleted file mode 100644 index 64ae6eed68..0000000000 --- a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/support/test-context-with-smart-initializing-singleton.xml +++ /dev/null @@ -1,76 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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..b9ad257144 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 @@ -1,18 +1,9 @@ - - - - + http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd"> 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..e73d1861f5 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,10 +11,4 @@ - - - - - - diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/InlineItemHandlerWithStepScopeParserTests-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/InlineItemHandlerWithStepScopeParserTests-context.xml index 75b47c1bfd..8ded3ad489 100644 --- a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/InlineItemHandlerWithStepScopeParserTests-context.xml +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/InlineItemHandlerWithStepScopeParserTests-context.xml @@ -9,7 +9,7 @@ - + diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/JobParserValidatorTests-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/JobParserValidatorTests-context.xml index 13a9cf20a4..ebf9882b5a 100644 --- a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/JobParserValidatorTests-context.xml +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/JobParserValidatorTests-context.xml @@ -20,7 +20,7 @@ - + @@ -33,7 +33,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..5af850502d 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 @@ -19,10 +19,6 @@ - - - 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/JobStepParserTests-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/JobStepParserTests-context.xml index aba01b79cf..7da0e02f18 100644 --- a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/JobStepParserTests-context.xml +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/JobStepParserTests-context.xml @@ -27,13 +27,16 @@ - + - + + + + \ No newline at end of file diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/RepositoryJobParserTests-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/RepositoryJobParserTests-context.xml index 70eea0caf3..836030e01e 100644 --- a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/RepositoryJobParserTests-context.xml +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/RepositoryJobParserTests-context.xml @@ -9,7 +9,7 @@ - + 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/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 @@ 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..fc7c37b53a 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 @@ -36,10 +36,4 @@ - - - - - - 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..2e99b7d2f0 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 @@ -1,22 +1,13 @@ + http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd"> - + - - - - 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..93c90d9998 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 @@ -1,16 +1,9 @@ - + http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd"> - - - - diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/partition/launch-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/partition/launch-context.xml index 19b72c493f..16e6ad60da 100644 --- a/spring-batch-core/src/test/resources/org/springframework/batch/core/partition/launch-context.xml +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/partition/launch-context.xml @@ -8,7 +8,7 @@ + class="org.springframework.batch.core.partition.PartitionStep"> @@ -62,12 +62,13 @@ - + + 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..c53bbc2dd2 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 @@ -37,29 +37,15 @@ - - - - - - - - - - - - - - - - + - - + + + diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/repository/dao/TablePrefixTests-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/repository/dao/TablePrefixTests-context.xml index b4cb71d746..e756500308 100644 --- a/spring-batch-core/src/test/resources/org/springframework/batch/core/repository/dao/TablePrefixTests-context.xml +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/repository/dao/TablePrefixTests-context.xml @@ -15,8 +15,8 @@ - - + + @@ -35,7 +35,7 @@ - + diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/repository/dao/sql-dao-custom-key-generator-test.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/repository/dao/jdbc/sql-dao-custom-key-generator-test.xml similarity index 91% rename from spring-batch-core/src/test/resources/org/springframework/batch/core/repository/dao/sql-dao-custom-key-generator-test.xml rename to spring-batch-core/src/test/resources/org/springframework/batch/core/repository/dao/jdbc/sql-dao-custom-key-generator-test.xml index d6c30b8372..efabd9d182 100644 --- a/spring-batch-core/src/test/resources/org/springframework/batch/core/repository/dao/sql-dao-custom-key-generator-test.xml +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/repository/dao/jdbc/sql-dao-custom-key-generator-test.xml @@ -26,25 +26,25 @@ - + - + - + - + - + @@ -57,7 +57,7 @@ - + diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/repository/dao/sql-dao-test.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/repository/dao/jdbc/sql-dao-test.xml similarity index 92% rename from spring-batch-core/src/test/resources/org/springframework/batch/core/repository/dao/sql-dao-test.xml rename to spring-batch-core/src/test/resources/org/springframework/batch/core/repository/dao/jdbc/sql-dao-test.xml index a219d0902f..bfac6b4842 100644 --- a/spring-batch-core/src/test/resources/org/springframework/batch/core/repository/dao/sql-dao-test.xml +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/repository/dao/jdbc/sql-dao-test.xml @@ -26,23 +26,23 @@ - + - + - + - + @@ -53,7 +53,7 @@ - + diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/repository/dao/schema-prefix-hsqldb.sql b/spring-batch-core/src/test/resources/org/springframework/batch/core/repository/dao/schema-prefix-hsqldb.sql index 72a8fc0917..72a5369af5 100644 --- a/spring-batch-core/src/test/resources/org/springframework/batch/core/repository/dao/schema-prefix-hsqldb.sql +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/repository/dao/schema-prefix-hsqldb.sql @@ -7,80 +7,80 @@ DROP TABLE PREFIX_JOB_INSTANCE IF EXISTS; DROP TABLE PREFIX_STEP_EXECUTION_SEQ IF EXISTS; DROP TABLE PREFIX_JOB_EXECUTION_SEQ IF EXISTS; -DROP TABLE PREFIX_JOB_SEQ IF EXISTS; +DROP TABLE PREFIX_JOB_INSTANCE_SEQ IF EXISTS; -CREATE TABLE PREFIX_JOB_INSTANCE ( - JOB_INSTANCE_ID BIGINT IDENTITY NOT NULL PRIMARY KEY , - VERSION BIGINT , +CREATE TABLE PREFIX_JOB_INSTANCE ( + JOB_INSTANCE_ID BIGINT IDENTITY NOT NULL PRIMARY KEY, + VERSION BIGINT, JOB_NAME VARCHAR(100) NOT NULL, JOB_KEY VARCHAR(32) NOT NULL, constraint PREFIX_JOB_INST_UN unique (JOB_NAME, JOB_KEY) ) ; -CREATE TABLE PREFIX_JOB_EXECUTION ( - JOB_EXECUTION_ID BIGINT IDENTITY NOT NULL PRIMARY KEY , - VERSION BIGINT , +CREATE TABLE PREFIX_JOB_EXECUTION ( + JOB_EXECUTION_ID BIGINT IDENTITY NOT NULL PRIMARY KEY, + VERSION BIGINT, JOB_INSTANCE_ID BIGINT NOT NULL, CREATE_TIME TIMESTAMP NOT NULL, - START_TIME TIMESTAMP DEFAULT NULL , - END_TIME TIMESTAMP DEFAULT NULL , - STATUS VARCHAR(10) , - EXIT_CODE VARCHAR(20) , - EXIT_MESSAGE VARCHAR(2500) , + START_TIME TIMESTAMP DEFAULT NULL, + END_TIME TIMESTAMP DEFAULT NULL, + STATUS VARCHAR(10), + EXIT_CODE VARCHAR(20), + EXIT_MESSAGE VARCHAR(2500), LAST_UPDATED TIMESTAMP, constraint PREFIX_JOB_INST_EXEC_FK foreign key (JOB_INSTANCE_ID) references PREFIX_JOB_INSTANCE(JOB_INSTANCE_ID) ) ; -CREATE TABLE PREFIX_JOB_EXECUTION_PARAMS ( - JOB_EXECUTION_ID BIGINT NOT NULL , - TYPE_CD VARCHAR(6) NOT NULL , - KEY_NAME VARCHAR(100) NOT NULL , - STRING_VAL VARCHAR(250) , - DATE_VAL TIMESTAMP DEFAULT NULL , - LONG_VAL BIGINT , - DOUBLE_VAL DOUBLE PRECISION , - IDENTIFYING CHAR(1) NOT NULL , +CREATE TABLE PREFIX_JOB_EXECUTION_PARAMS ( + JOB_EXECUTION_ID BIGINT NOT NULL, + TYPE_CD VARCHAR(6) NOT NULL, + KEY_NAME VARCHAR(100) NOT NULL, + STRING_VAL VARCHAR(250), + DATE_VAL TIMESTAMP DEFAULT NULL, + LONG_VAL BIGINT, + DOUBLE_VAL DOUBLE PRECISION, + IDENTIFYING CHAR(1) NOT NULL, constraint PREFIX_JOB_INST_PARAMS_FK foreign key (JOB_EXECUTION_ID) references PREFIX_JOB_EXECUTION(JOB_EXECUTION_ID) ) ; -CREATE TABLE PREFIX_STEP_EXECUTION ( - STEP_EXECUTION_ID BIGINT IDENTITY NOT NULL PRIMARY KEY , +CREATE TABLE PREFIX_STEP_EXECUTION ( + STEP_EXECUTION_ID BIGINT IDENTITY NOT NULL PRIMARY KEY, VERSION BIGINT NOT NULL, STEP_NAME VARCHAR(100) NOT NULL, JOB_EXECUTION_ID BIGINT NOT NULL, - CREATE_TIME TIMESTAMP NOT NULL , - START_TIME TIMESTAMP DEFAULT NULL , - END_TIME TIMESTAMP DEFAULT NULL , - STATUS VARCHAR(10) , - COMMIT_COUNT BIGINT , - READ_COUNT BIGINT , - FILTER_COUNT BIGINT , - WRITE_COUNT BIGINT , - READ_SKIP_COUNT BIGINT , - WRITE_SKIP_COUNT BIGINT , - PROCESS_SKIP_COUNT BIGINT , - ROLLBACK_COUNT BIGINT , - EXIT_CODE VARCHAR(20) , - EXIT_MESSAGE VARCHAR(2500) , + CREATE_TIME TIMESTAMP NOT NULL, + START_TIME TIMESTAMP DEFAULT NULL, + END_TIME TIMESTAMP DEFAULT NULL, + STATUS VARCHAR(10), + COMMIT_COUNT BIGINT, + READ_COUNT BIGINT, + FILTER_COUNT BIGINT, + WRITE_COUNT BIGINT, + READ_SKIP_COUNT BIGINT, + WRITE_SKIP_COUNT BIGINT, + PROCESS_SKIP_COUNT BIGINT, + ROLLBACK_COUNT BIGINT, + EXIT_CODE VARCHAR(20), + EXIT_MESSAGE VARCHAR(2500), LAST_UPDATED TIMESTAMP, constraint PREFIX_JOB_EXEC_STEP_FK foreign key (JOB_EXECUTION_ID) references PREFIX_JOB_EXECUTION(JOB_EXECUTION_ID) ) ; -CREATE TABLE PREFIX_STEP_EXECUTION_CONTEXT ( +CREATE TABLE PREFIX_STEP_EXECUTION_CONTEXT ( STEP_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY, SHORT_CONTEXT VARCHAR(2500) NOT NULL, - SERIALIZED_CONTEXT LONGVARCHAR , + SERIALIZED_CONTEXT LONGVARCHAR, constraint PREFIX_STEP_EXEC_CTX_FK foreign key (STEP_EXECUTION_ID) references PREFIX_STEP_EXECUTION(STEP_EXECUTION_ID) ) ; -CREATE TABLE PREFIX_JOB_EXECUTION_CONTEXT ( +CREATE TABLE PREFIX_JOB_EXECUTION_CONTEXT ( JOB_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY, SHORT_CONTEXT VARCHAR(2500) NOT NULL, - SERIALIZED_CONTEXT LONGVARCHAR , + SERIALIZED_CONTEXT LONGVARCHAR, constraint PREFIX_JOB_EXEC_CTX_FK foreign key (JOB_EXECUTION_ID) references PREFIX_JOB_EXECUTION(JOB_EXECUTION_ID) ) ; @@ -91,6 +91,6 @@ CREATE TABLE PREFIX_STEP_EXECUTION_SEQ ( CREATE TABLE PREFIX_JOB_EXECUTION_SEQ ( ID BIGINT IDENTITY ); -CREATE TABLE PREFIX_JOB_SEQ ( +CREATE TABLE PREFIX_JOB_INSTANCE_SEQ ( ID BIGINT IDENTITY ); diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/repository/support/SimpleJobRepositoryProxyTests-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/repository/support/SimpleJobRepositoryProxyTests-context.xml index de1393f9cb..fc469a68b6 100644 --- a/spring-batch-core/src/test/resources/org/springframework/batch/core/repository/support/SimpleJobRepositoryProxyTests-context.xml +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/repository/support/SimpleJobRepositoryProxyTests-context.xml @@ -6,7 +6,7 @@ - + diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/scope/context/CommitIntervalJobParameter-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/scope/context/CommitIntervalJobParameter-context.xml index 9aedb09cc9..3001f87717 100644 --- a/spring-batch-core/src/test/resources/org/springframework/batch/core/scope/context/CommitIntervalJobParameter-context.xml +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/scope/context/CommitIntervalJobParameter-context.xml @@ -9,8 +9,9 @@ - - + + - - + + \ No newline at end of file diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/step/item/FaultTolerantExceptionClassesTests-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/step/item/FaultTolerantExceptionClassesTests-context.xml index 9ff38cd470..88d4275ef5 100644 --- a/spring-batch-core/src/test/resources/org/springframework/batch/core/step/item/FaultTolerantExceptionClassesTests-context.xml +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/step/item/FaultTolerantExceptionClassesTests-context.xml @@ -142,7 +142,8 @@ - + diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/step/item/ScriptItemProcessorTests-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/step/item/ScriptItemProcessorTests-context.xml index 8062abe751..dda0ba4d01 100644 --- a/spring-batch-core/src/test/resources/org/springframework/batch/core/step/item/ScriptItemProcessorTests-context.xml +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/step/item/ScriptItemProcessorTests-context.xml @@ -19,14 +19,10 @@ - - - + + - - diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/step/skip/ReprocessExceptionTests-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/step/skip/ReprocessExceptionTests-context.xml index 05bf854a92..f85e093f5f 100644 --- a/spring-batch-core/src/test/resources/org/springframework/batch/core/step/skip/ReprocessExceptionTests-context.xml +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/step/skip/ReprocessExceptionTests-context.xml @@ -6,8 +6,9 @@ - - + + diff --git a/spring-batch-core/src/test/resources/schema-hsqldb-extended.sql b/spring-batch-core/src/test/resources/schema-hsqldb-extended.sql index 4eb969ebcb..1ca2477042 100644 --- a/spring-batch-core/src/test/resources/schema-hsqldb-extended.sql +++ b/spring-batch-core/src/test/resources/schema-hsqldb-extended.sql @@ -4,78 +4,78 @@ -- store and verify the stack traces of failure exceptions, -- which could be larger than the default 2500 characters. -CREATE TABLE BATCH_JOB_INSTANCE ( - JOB_INSTANCE_ID BIGINT IDENTITY NOT NULL PRIMARY KEY , - VERSION BIGINT , +CREATE TABLE BATCH_JOB_INSTANCE ( + JOB_INSTANCE_ID BIGINT IDENTITY NOT NULL PRIMARY KEY, + VERSION BIGINT, JOB_NAME VARCHAR(100) NOT NULL, JOB_KEY VARCHAR(32) NOT NULL, constraint JOB_INST_UN unique (JOB_NAME, JOB_KEY) ) ; -CREATE TABLE BATCH_JOB_EXECUTION ( - JOB_EXECUTION_ID BIGINT IDENTITY NOT NULL PRIMARY KEY , - VERSION BIGINT , +CREATE TABLE BATCH_JOB_EXECUTION ( + JOB_EXECUTION_ID BIGINT IDENTITY NOT NULL PRIMARY KEY, + VERSION BIGINT, JOB_INSTANCE_ID BIGINT NOT NULL, CREATE_TIME TIMESTAMP(9) NOT NULL, - START_TIME TIMESTAMP(9) DEFAULT NULL , - END_TIME TIMESTAMP(9) DEFAULT NULL , - STATUS VARCHAR(10) , - EXIT_CODE VARCHAR(20000) , - EXIT_MESSAGE VARCHAR(20000) , + START_TIME TIMESTAMP(9) DEFAULT NULL, + END_TIME TIMESTAMP(9) DEFAULT NULL, + STATUS VARCHAR(10), + EXIT_CODE VARCHAR(20000), + EXIT_MESSAGE VARCHAR(20000), LAST_UPDATED TIMESTAMP(9), constraint JOB_INST_EXEC_FK foreign key (JOB_INSTANCE_ID) references BATCH_JOB_INSTANCE(JOB_INSTANCE_ID) ) ; -CREATE TABLE BATCH_JOB_EXECUTION_PARAMS ( - JOB_EXECUTION_ID BIGINT NOT NULL , - TYPE_CD VARCHAR(6) NOT NULL , - KEY_NAME VARCHAR(100) NOT NULL , - STRING_VAL VARCHAR(250) , - DATE_VAL TIMESTAMP DEFAULT NULL , - LONG_VAL BIGINT , - DOUBLE_VAL DOUBLE PRECISION , - IDENTIFYING CHAR(1) NOT NULL , +CREATE TABLE BATCH_JOB_EXECUTION_PARAMS ( + JOB_EXECUTION_ID BIGINT NOT NULL, + TYPE_CD VARCHAR(6) NOT NULL, + KEY_NAME VARCHAR(100) NOT NULL, + STRING_VAL VARCHAR(250), + DATE_VAL TIMESTAMP DEFAULT NULL, + LONG_VAL BIGINT, + DOUBLE_VAL DOUBLE PRECISION, + IDENTIFYING CHAR(1) NOT NULL, constraint JOB_EXEC_PARAMS_FK foreign key (JOB_EXECUTION_ID) references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID) ) ; -CREATE TABLE BATCH_STEP_EXECUTION ( - STEP_EXECUTION_ID BIGINT IDENTITY NOT NULL PRIMARY KEY , +CREATE TABLE BATCH_STEP_EXECUTION ( + STEP_EXECUTION_ID BIGINT IDENTITY NOT NULL PRIMARY KEY, VERSION BIGINT NOT NULL, STEP_NAME VARCHAR(100) NOT NULL, JOB_EXECUTION_ID BIGINT NOT NULL, - CREATE_TIME TIMESTAMP(9) NOT NULL , - START_TIME TIMESTAMP(9) DEFAULT NULL , - END_TIME TIMESTAMP(9) DEFAULT NULL , - STATUS VARCHAR(10) , - COMMIT_COUNT BIGINT , - READ_COUNT BIGINT , - FILTER_COUNT BIGINT , - WRITE_COUNT BIGINT , - READ_SKIP_COUNT BIGINT , - WRITE_SKIP_COUNT BIGINT , - PROCESS_SKIP_COUNT BIGINT , - ROLLBACK_COUNT BIGINT , - EXIT_CODE VARCHAR(20000) , - EXIT_MESSAGE VARCHAR(20000) , + CREATE_TIME TIMESTAMP(9) NOT NULL, + START_TIME TIMESTAMP(9) DEFAULT NULL, + END_TIME TIMESTAMP(9) DEFAULT NULL, + STATUS VARCHAR(10), + COMMIT_COUNT BIGINT, + READ_COUNT BIGINT, + FILTER_COUNT BIGINT, + WRITE_COUNT BIGINT, + READ_SKIP_COUNT BIGINT, + WRITE_SKIP_COUNT BIGINT, + PROCESS_SKIP_COUNT BIGINT, + ROLLBACK_COUNT BIGINT, + EXIT_CODE VARCHAR(20000), + EXIT_MESSAGE VARCHAR(20000), LAST_UPDATED TIMESTAMP(9), constraint JOB_EXEC_STEP_FK foreign key (JOB_EXECUTION_ID) references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID) ) ; -CREATE TABLE BATCH_STEP_EXECUTION_CONTEXT ( +CREATE TABLE BATCH_STEP_EXECUTION_CONTEXT ( STEP_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY, SHORT_CONTEXT VARCHAR(20000) NOT NULL, - SERIALIZED_CONTEXT LONGVARCHAR , + SERIALIZED_CONTEXT LONGVARCHAR, constraint STEP_EXEC_CTX_FK foreign key (STEP_EXECUTION_ID) references BATCH_STEP_EXECUTION(STEP_EXECUTION_ID) ) ; -CREATE TABLE BATCH_JOB_EXECUTION_CONTEXT ( +CREATE TABLE BATCH_JOB_EXECUTION_CONTEXT ( JOB_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY, SHORT_CONTEXT VARCHAR(20000) NOT NULL, - SERIALIZED_CONTEXT LONGVARCHAR , + SERIALIZED_CONTEXT LONGVARCHAR, constraint JOB_EXEC_CTX_FK foreign key (JOB_EXECUTION_ID) references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID) ) ; @@ -86,6 +86,6 @@ CREATE TABLE BATCH_STEP_EXECUTION_SEQ ( CREATE TABLE BATCH_JOB_EXECUTION_SEQ ( ID BIGINT IDENTITY ); -CREATE TABLE BATCH_JOB_SEQ ( +CREATE TABLE BATCH_JOB_INSTANCE_SEQ ( ID BIGINT IDENTITY ); diff --git a/spring-batch-core/src/test/resources/schema.sql b/spring-batch-core/src/test/resources/schema.sql new file mode 100644 index 0000000000..5fc3ccb964 --- /dev/null +++ b/spring-batch-core/src/test/resources/schema.sql @@ -0,0 +1 @@ +create table person_target (id int primary key, name varchar(5)); \ No newline at end of file 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..de0eace8fd 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 @@ -6,30 +6,13 @@ - - - - - - - - - - - - + class="org.springframework.batch.core.launch.support.JobOperatorFactoryBean" + p:jobRepository-ref="jobRepository"/> diff --git a/spring-batch-docs/antora-playbook.yml b/spring-batch-docs/antora-playbook.yml index bbf8fac2a9..95741fd07c 100644 --- a/spring-batch-docs/antora-playbook.yml +++ b/spring-batch-docs/antora-playbook.yml @@ -1,5 +1,3 @@ -# PACKAGES antora@3.2.0-alpha.2 @antora/atlas-extension:1.0.0-alpha.1 @antora/collector-extension@1.0.0-alpha.3 @springio/antora-extensions@1.1.0-alpha.2 @asciidoctor/tabs@1.0.0-alpha.12 @opendevise/antora-release-line-extension@1.0.0-alpha.2 -# # The purpose of this Antora playbook is to build the docs in the current branch. antora: extensions: @@ -38,5 +36,5 @@ runtime: format: pretty ui: bundle: - url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.15/ui-bundle.zip + url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.18/ui-bundle.zip snapshot: true \ No newline at end of file diff --git a/spring-batch-docs/antora.yml b/spring-batch-docs/antora.yml index a3808cf652..78f3aa524f 100644 --- a/spring-batch-docs/antora.yml +++ b/spring-batch-docs/antora.yml @@ -6,6 +6,6 @@ nav: ext: collector: run: - command: mvn process-resources -pl spring-batch-docs -am + command: ./mvnw process-resources -pl spring-batch-docs -am scan: dir: ./target/classes/antora-resources diff --git a/spring-batch-docs/modules/ROOT/assets/images/job-launcher-sequence-async.png b/spring-batch-docs/modules/ROOT/assets/images/job-launcher-sequence-async.png index d2a7a40e3b..4425716a12 100644 Binary files a/spring-batch-docs/modules/ROOT/assets/images/job-launcher-sequence-async.png and b/spring-batch-docs/modules/ROOT/assets/images/job-launcher-sequence-async.png differ diff --git a/spring-batch-docs/modules/ROOT/assets/images/job-launcher-sequence-sync.png b/spring-batch-docs/modules/ROOT/assets/images/job-launcher-sequence-sync.png index 0461e7a25a..9f964a6d85 100644 Binary files a/spring-batch-docs/modules/ROOT/assets/images/job-launcher-sequence-sync.png and b/spring-batch-docs/modules/ROOT/assets/images/job-launcher-sequence-sync.png differ diff --git a/spring-batch-docs/modules/ROOT/assets/images/launch-from-request.png b/spring-batch-docs/modules/ROOT/assets/images/launch-from-request.png index f8e44288e7..53473cfa81 100644 Binary files a/spring-batch-docs/modules/ROOT/assets/images/launch-from-request.png and b/spring-batch-docs/modules/ROOT/assets/images/launch-from-request.png differ diff --git a/spring-batch-docs/modules/ROOT/assets/images/spring-batch-reference-model.png b/spring-batch-docs/modules/ROOT/assets/images/spring-batch-reference-model.png index 7062d470b4..d0a423ad0b 100644 Binary files a/spring-batch-docs/modules/ROOT/assets/images/spring-batch-reference-model.png and b/spring-batch-docs/modules/ROOT/assets/images/spring-batch-reference-model.png differ diff --git a/spring-batch-docs/modules/ROOT/nav.adoc b/spring-batch-docs/modules/ROOT/nav.adoc index f94c71cf5c..540c0f8a0d 100644 --- a/spring-batch-docs/modules/ROOT/nav.adoc +++ b/spring-batch-docs/modules/ROOT/nav.adoc @@ -4,10 +4,10 @@ * xref:whatsnew.adoc[] * xref:domain.adoc[] * xref:job.adoc[] -** xref:job/configuring.adoc[] -** xref:job/java-config.adoc[] +** xref:job/configuring-infrastructure.adoc[] +** xref:job/configuring-job.adoc[] ** xref:job/configuring-repository.adoc[] -** xref:job/configuring-launcher.adoc[] +** xref:job/configuring-operator.adoc[] ** xref:job/running.adoc[] ** xref:job/advanced-meta-data.adoc[] * xref:step.adoc[] @@ -53,8 +53,9 @@ ** xref:spring-batch-integration/launching-jobs-through-messages.adoc[] ** xref:spring-batch-integration/available-attributes-of-the-job-launching-gateway.adoc[] ** xref:spring-batch-integration/sub-elements.adoc[] -* xref:monitoring-and-metrics.adoc[] -* xref:tracing.adoc[] +* xref:spring-batch-observability.adoc[] +** xref:spring-batch-observability/micrometer.adoc[] +** xref:spring-batch-observability/jfr.adoc[] * Appendices ** xref:appendix.adoc[] ** xref:schema-appendix.adoc[] 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/common-patterns.adoc b/spring-batch-docs/modules/ROOT/pages/common-patterns.adoc index d5442d1ddb..d1b05fe799 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) { @@ -766,7 +766,7 @@ public Job job1(JobRepository jobRepository, Step step1, Step step2) { @Bean public Step step1(JobRepository jobRepository, PlatformTransactionManager transactionManager) { return new StepBuilder("step1", jobRepository) - .chunk(10, transactionManager) + .chunk(10).transactionManager(transactionManager) .reader(reader()) .writer(savingWriter()) .listener(promotionListener()) diff --git a/spring-batch-docs/modules/ROOT/pages/domain.adoc b/spring-batch-docs/modules/ROOT/pages/domain.adoc index a818a35a44..85b49af533 100644 --- a/spring-batch-docs/modules/ROOT/pages/domain.adoc +++ b/spring-batch-docs/modules/ROOT/pages/domain.adoc @@ -30,10 +30,10 @@ to address very complex processing needs. image::spring-batch-reference-model.png[Figure 2.1: Batch Stereotypes, scaledwidth="60%"] The preceding diagram highlights the key concepts that make up the domain language of -Spring Batch. A `Job` has one to many steps, each of which has exactly one `ItemReader`, -one `ItemProcessor`, and one `ItemWriter`. A job needs to be launched (with -`JobLauncher`), and metadata about the currently running process needs to be stored (in -`JobRepository`). +Spring Batch. A `Job` has one or more steps, each of which has exactly one `ItemReader`, +an optional `ItemProcessor`, and one `ItemWriter`. A job is operated (started, stopped, etc) +with a `JobOperator`, and metadata about the currently running process is stored in and +restored from a `JobRepository`. [[job]] == Job @@ -183,7 +183,7 @@ This field is empty if the job has yet to start. |`endTime` |A `java.time.LocalDateTime` representing the current system time when the execution finished, -regardless of whether or not it was successful. The field is empty if the job has yet to +regardless of whether it was successful or not. The field is empty if the job has yet to finish. |`exitStatus` @@ -323,7 +323,7 @@ formatting. A `Step` is a domain object that encapsulates an independent, sequential phase of a batch job. Therefore, every `Job` is composed entirely of one or more steps. A `Step` contains -all of the information necessary to define and control the actual batch processing. This +all the information necessary to define and control the actual batch processing. This is a necessarily vague description because the contents of any given `Step` are at the discretion of the developer writing a `Job`. A `Step` can be as simple or complex as the developer desires. A simple `Step` might load data from a file into the database, @@ -365,7 +365,7 @@ This field is empty if the step has yet to start. |`endTime` |A `java.time.LocalDateTime` representing the current system time when the execution finished, -regardless of whether or not it was successful. This field is empty if the step has yet to +regardless of whether it was successful or not. This field is empty if the step has yet to exit. |`exitStatus` @@ -569,24 +569,27 @@ with the `` tag, as the following example shows: ==== -[[joblauncher]] -== JobLauncher +[[jobOperator]] +== JobOperator -`JobLauncher` represents a simple interface for launching a `Job` with a given set of -`JobParameters`, as the following example shows: +`JobOperator` represents a simple interface for operations like starting, stopping and restarting +jobs, as the following example shows: [source, java] ---- -public interface JobLauncher { +public interface JobOperator { + + JobExecution start(Job job, JobParameters jobParameters) throws Exception; + JobExecution startNextInstance(Job job) throws Exception; + boolean stop(JobExecution jobExecution) throws Exception; + JobExecution restart(JobExecution jobExecution) throws Exception; + JobExecution abandon(JobExecution jobExecution) throws Exception; -public JobExecution run(Job job, JobParameters jobParameters) - throws JobExecutionAlreadyRunningException, JobRestartException, - JobInstanceAlreadyCompleteException, JobParametersInvalidException; } ---- -It is expected that implementations obtain a valid `JobExecution` from the -`JobRepository` and execute the `Job`. +A `Job` is started with a given set of `JobParameters`. It is expected that implementations obtain +a valid `JobExecution` from the `JobRepository` and execute the `Job`. [[itemreader]] == ItemReader diff --git a/spring-batch-docs/modules/ROOT/pages/footer/index-footer.adoc b/spring-batch-docs/modules/ROOT/pages/footer/index-footer.adoc deleted file mode 100644 index 8289b49e9a..0000000000 --- a/spring-batch-docs/modules/ROOT/pages/footer/index-footer.adoc +++ /dev/null @@ -1,10 +0,0 @@ -''' -Lucas Ward, Dave Syer, Thomas Risberg, Robert Kasanicky, Dan Garrette, Wayne Lund, -Michael Minella, Chris Schaefer, Gunnar Hillert, Glenn Renfro, Jay Bryant, Mahmoud Ben Hassine - -Copyright © 2009 - 2023 VMware, Inc. All Rights Reserved. - -Copies of this document may be made for your own use and for -distribution to others, provided that you do not charge any fee for such -copies and further provided that each copy contains this Copyright -Notice, whether distributed in print or electronically. diff --git a/spring-batch-docs/modules/ROOT/pages/index.adoc b/spring-batch-docs/modules/ROOT/pages/index.adoc index fb3d878723..0a28755815 100644 --- a/spring-batch-docs/modules/ROOT/pages/index.adoc +++ b/spring-batch-docs/modules/ROOT/pages/index.adoc @@ -9,7 +9,7 @@ xref:spring-batch-intro.adoc[Spring Batch Introduction] :: Background, usage scenarios, and general guidelines. xref:spring-batch-architecture.adoc[Spring Batch Architecture] :: Spring Batch architecture, general batch principles, batch processing strategies. -xref:whatsnew.adoc[What's new in Spring Batch 5.2] :: New features introduced in version 5.2. +xref:whatsnew.adoc[What's new in Spring Batch 6.0] :: New features introduced in version 6.0. xref:domain.adoc[The Domain Language of Batch] :: Core concepts and abstractions of the Batch domain language. xref:job.adoc[Configuring and Running a Job] :: Job configuration, execution, and @@ -28,9 +28,8 @@ xref:common-patterns.adoc#commonPatterns[Common Patterns] :: Common batch proces and guidelines. xref:spring-batch-integration.adoc[Spring Batch Integration] :: Integration between Spring Batch and Spring Integration projects. -xref:monitoring-and-metrics.adoc[Monitoring and metrics] :: Batch jobs +xref:spring-batch-observability.adoc[Spring Batch Observability] :: Batch jobs monitoring and metrics. -xref:tracing.adoc[Tracing] :: Tracing with Micrometer. The following appendices are available: 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..f5879ff57b 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 @@ -1,163 +1,30 @@ [[advancedMetaData]] = Advanced Metadata Usage -So far, both the `JobLauncher` and `JobRepository` interfaces have been -discussed. Together, they represent the simple launching of a job and basic -CRUD operations of batch domain objects: - -.Job Repository -image::job-repository.png[Job Repository, scaledwidth="60%"] - -A `JobLauncher` uses the -`JobRepository` to create new -`JobExecution` objects and run them. -`Job` and `Step` implementations -later use the same `JobRepository` for basic updates -of the same executions during the running of a `Job`. -The basic operations suffice for simple scenarios. However, in a large batch -environment with hundreds of batch jobs and complex scheduling -requirements, more advanced access to the metadata is required: - -.Advanced Job Repository Access -image::job-repository-advanced.png[Job Repository Advanced, scaledwidth="80%"] - -The `JobExplorer` and -`JobOperator` interfaces, which are discussed -in the coming sections, add additional functionality for querying and controlling the metadata. - -[[queryingRepository]] -== Querying the Repository - -The most basic need before any advanced features is the ability to -query the repository for existing executions. This functionality is -provided by the `JobExplorer` interface: - -[source, java] ----- -public interface JobExplorer { - - List getJobInstances(String jobName, int start, int count); - - JobExecution getJobExecution(Long executionId); - - StepExecution getStepExecution(Long jobExecutionId, Long stepExecutionId); - - JobInstance getJobInstance(Long instanceId); - - List getJobExecutions(JobInstance jobInstance); - - Set findRunningJobExecutions(String jobName); -} ----- - -As is evident from its method signatures, `JobExplorer` is a read-only version of -the `JobRepository`, and, like the `JobRepository`, it can be easily configured by using a -factory bean. - - -[tabs] -==== -Java:: -+ -The following example shows how to configure a `JobExplorer` in Java: -+ -.Java Configuration -[source, java] ----- -... -// This would reside in your DefaultBatchConfiguration extension -@Bean -public JobExplorer jobExplorer() throws Exception { - JobExplorerFactoryBean factoryBean = new JobExplorerFactoryBean(); - factoryBean.setDataSource(this.dataSource); - return factoryBean.getObject(); -} -... ----- - -XML:: -+ -The following example shows how to configure a `JobExplorer` in XML: -+ -.XML Configuration -[source, xml] ----- - ----- - -==== - - - -xref:job/configuring-repository.adoc#repositoryTablePrefix[Earlier in this chapter], we noted that you can modify the table prefix -of the `JobRepository` to allow for different versions or schemas. Because -the `JobExplorer` works with the same tables, it also needs the ability to set a prefix. - - -[tabs] -==== -Java:: -+ -The following example shows how to set the table prefix for a `JobExplorer` in Java: -+ -.Java Configuration -[source, java] ----- -... -// This would reside in your DefaultBatchConfiguration extension -@Bean -public JobExplorer jobExplorer() throws Exception { - JobExplorerFactoryBean factoryBean = new JobExplorerFactoryBean(); - factoryBean.setDataSource(this.dataSource); - factoryBean.setTablePrefix("SYSTEM."); - return factoryBean.getObject(); -} -... ----- - -XML:: -+ -The following example shows how to set the table prefix for a `JobExplorer` in XML: -+ -.XML Configuration -[source, xml] ----- - ----- - -==== - - [[jobregistry]] == JobRegistry -A `JobRegistry` (and its parent interface, `JobLocator`) is not mandatory, but it can be -useful if you want to keep track of which jobs are available in the context. It is also -useful for collecting jobs centrally in an application context when they have been created -elsewhere (for example, in child contexts). You can also use custom `JobRegistry` implementations -to manipulate the names and other properties of the jobs that are registered. +A `JobRegistry` is used to track which jobs are available in the context and can be operated by +the `JobOperator`. It is also useful for collecting jobs centrally in an application context when +they have been created elsewhere (for example, in child contexts). You can also use custom `JobRegistry` +implementations to manipulate the names and other properties of the jobs that are registered. There is only one implementation provided by the framework and this is based on a simple -map from job name to job instance. +map from job name to job instance, the `MapJobregistry`. [tabs] ==== Java:: + -When using `@EnableBatchProcessing`, a `JobRegistry` is provided for you. +When using `@EnableBatchProcessing`, a `MapJobregistry` is provided for you. The following example shows how to configure your own `JobRegistry`: + [source, java] ---- ... -// This is already provided via the @EnableBatchProcessing but can be customized via -// overriding the bean in the DefaultBatchConfiguration -@Override @Bean public JobRegistry jobRegistry() throws Exception { - return new MapJobRegistry(); + return new MyCustomJobRegistry(); } ... ---- @@ -173,289 +40,9 @@ The following example shows how to include a `JobRegistry` for a job defined in ==== -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 - -This is a `SmartInitializingSingleton` that registers all singleton jobs within the job registry. - -[tabs] -==== -Java:: -+ -The following example shows how to define a `JobRegistrySmartInitializingSingleton` in Java: -+ -.Java Configuration -[source, java] ----- -@Bean -public JobRegistrySmartInitializingSingleton jobRegistrySmartInitializingSingleton(JobRegistry jobRegistry) { - return new JobRegistrySmartInitializingSingleton(jobRegistry); -} ----- - -XML:: -+ -The following example shows how to define a `JobRegistrySmartInitializingSingleton` in XML: -+ -.XML Configuration -[source, xml] ----- - - - ----- - -==== - -[[automaticjobregistrar]] -=== AutomaticJobRegistrar - -This is a lifecycle component that creates child contexts and registers jobs from those -contexts as they are created. One advantage of doing this is that, while the job names in -the child contexts still have to be globally unique in the registry, their dependencies -can have "`natural`" names. So, for example, you can create a set of XML configuration files -that each have only one Job but that all have different definitions of an `ItemReader` with the -same bean name, such as `reader`. If all those files were imported into the same context, -the reader definitions would clash and override one another, but, with the automatic -registrar, this is avoided. This makes it easier to integrate jobs that have been contributed from -separate modules of an application. - -[tabs] -==== -Java:: -+ -The following example shows how to include the `AutomaticJobRegistrar` for a job defined -in Java: -+ -.Java Configuration -[source, java] ----- -@Bean -public AutomaticJobRegistrar registrar() { - - AutomaticJobRegistrar registrar = new AutomaticJobRegistrar(); - registrar.setJobLoader(jobLoader()); - registrar.setApplicationContextFactories(applicationContextFactories()); - registrar.afterPropertiesSet(); - return registrar; - -} ----- - -XML:: -+ -The following example shows how to include the `AutomaticJobRegistrar` for a job defined -in XML: -+ -.XML Configuration -[source, xml] ----- - - - - - - - - - - - - ----- - -==== - - - -The registrar has two mandatory properties: an array of -`ApplicationContextFactory` (created from a -convenient factory bean in the preceding example) and a -`JobLoader`. The `JobLoader` -is responsible for managing the lifecycle of the child contexts and -registering jobs in the `JobRegistry`. - -The `ApplicationContextFactory` is -responsible for creating the child context. The most common usage -is (as in the preceding example) to use a -`ClassPathXmlApplicationContextFactory`. One of -the features of this factory is that, by default, it copies some of the -configuration down from the parent context to the child. So, for -instance, you need not redefine the -`PropertyPlaceholderConfigurer` or AOP -configuration in the child, provided it should be the same as the -parent. - -You can use `AutomaticJobRegistrar` in -conjunction with a `JobRegistryBeanPostProcessor` -(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 -locations. - -[[JobOperator]] -== JobOperator - -As previously discussed, the `JobRepository` -provides CRUD operations on the meta-data, and the -`JobExplorer` provides read-only operations on the -metadata. However, those operations are most useful when used together -to perform common monitoring tasks such as stopping, restarting, or -summarizing a Job, as is commonly done by batch operators. Spring Batch -provides these types of operations in the -`JobOperator` interface: - -[source, java] ----- -public interface JobOperator { - - List getExecutions(long instanceId) throws NoSuchJobInstanceException; - - List getJobInstances(String jobName, int start, int count) - throws NoSuchJobException; - - Set getRunningExecutions(String jobName) throws NoSuchJobException; - - String getParameters(long executionId) throws NoSuchJobExecutionException; - - Long start(String jobName, String parameters) - throws NoSuchJobException, JobInstanceAlreadyExistsException; - - Long restart(long executionId) - throws JobInstanceAlreadyCompleteException, NoSuchJobExecutionException, - NoSuchJobException, JobRestartException; - - Long startNextInstance(String jobName) - throws NoSuchJobException, JobParametersNotFoundException, JobRestartException, - JobExecutionAlreadyRunningException, JobInstanceAlreadyCompleteException; - - boolean stop(long executionId) - throws NoSuchJobExecutionException, JobExecutionNotRunningException; - - String getSummary(long executionId) throws NoSuchJobExecutionException; - - Map getStepExecutionSummaries(long executionId) - throws NoSuchJobExecutionException; - - Set getJobNames(); - -} ----- - -The preceding operations represent methods from many different interfaces, such as -`JobLauncher`, `JobRepository`, `JobExplorer`, and `JobRegistry`. For this reason, the -provided implementation of `JobOperator` (`SimpleJobOperator`) has many dependencies. - - -[tabs] -==== -Java:: -+ -The following example shows a typical bean definition for `SimpleJobOperator` in Java: -+ -[source, java] ----- - /** - * All injected dependencies for this bean are provided by the @EnableBatchProcessing - * infrastructure out of the box. - */ - @Bean - public SimpleJobOperator jobOperator(JobExplorer jobExplorer, - JobRepository jobRepository, - JobRegistry jobRegistry, - JobLauncher jobLauncher) { - - SimpleJobOperator jobOperator = new SimpleJobOperator(); - jobOperator.setJobExplorer(jobExplorer); - jobOperator.setJobRepository(jobRepository); - jobOperator.setJobRegistry(jobRegistry); - jobOperator.setJobLauncher(jobLauncher); - - return jobOperator; - } ----- - -XML:: -+ -The following example shows a typical bean definition for `SimpleJobOperator` in XML: -+ -[source, xml] ----- - - - - - - - - - - ----- - -==== - - -As of version 5.0, the `@EnableBatchProcessing` annotation automatically registers a job operator bean -in the application context. - -NOTE: If you set the table prefix on the job repository, do not forget to set it on the job explorer as well. +The `MapJobRegistry` provided by Spring Batch is smart enough to populate itself with all the jobs +in the application context. However, if you are using a custom implementation of `JobRegistry`, you +need to populate it manually with the jobs that you want to operate through the job operator. [[JobParametersIncrementer]] == JobParametersIncrementer diff --git a/spring-batch-docs/modules/ROOT/pages/job/configuring-infrastructure.adoc b/spring-batch-docs/modules/ROOT/pages/job/configuring-infrastructure.adoc new file mode 100644 index 0000000000..846eba9983 --- /dev/null +++ b/spring-batch-docs/modules/ROOT/pages/job/configuring-infrastructure.adoc @@ -0,0 +1,77 @@ +[[infraConfig]] += Batch infrastructure Configuration + +As described earlier, Spring Batch relies on a number of infrastructure beans to operate jobs and steps, +including the `JobOperator` and the `JobRepository`. While it is possible to define these beans manually, it is much easier to use the +`@EnableBatchProcessing` annotation or the `DefaultBatchConfiguration` class to provide a base configuration. + +By default, Spring Batch will provide a resourceless batch infrastructure configuration, which is based on +the `ResourcelessJobRepository` implementation. If you want to use a database-backed job repository, you can +use the `@EnableJdbcJobRepository` / `@EnableMongoJobRepository` annotations or the equivalent classes +`JdbcDefaultBatchConfiguration` / `MongoDefaultBatchConfiguration` as described in the +xref:job/configuring-repository.adoc[Configuring a JobRepository] section. + +== Annotation-based Configuration + +The `@EnableBatchProcessing` annotation works similarly to other `@Enable*` annotations in the +Spring family. In this case, `@EnableBatchProcessing` provides a base configuration for +building batch jobs. Within this base configuration, an instance of `StepScope` and `JobScope` are +created, in addition to a number of beans being made available to be autowired: + +* `JobRepository`: a bean named `jobRepository` +* `JobOperator`: a bean named `jobOperator` +* `JobRegistry`: a bean named `jobRegistry` + +Here is an example of how to use the `@EnableBatchProcessing` annotation in a Java configuration class: + +[source, java] +---- +@Configuration +@EnableBatchProcessing +public class MyJobConfiguration { + + @Bean + public Job job(JobRepository jobRepository) { + return new JobBuilder("myJob", jobRepository) + //define job flow as needed + .build(); + } + +} +---- + +It is possible to customize the configuration of any infrastructure bean by using the attributes of +the `@EnableBatchProcessing` annotation. + +NOTE: Only one configuration class needs to have the `@EnableBatchProcessing` annotation. Once +you have a class annotated with it, you have all the configuration described earlier. + +== Programmatic Configuration + +Similarly to the annotation-based configuration, a programmatic way of configuring infrastructure +beans is provided through the `DefaultBatchConfiguration` class. This class provides the same beans +provided by `@EnableBatchProcessing` and can be used as a base class to configure batch jobs. +The following snippet is a typical example of how to use it: + +[source, java] +---- +@Configuration +class MyJobConfiguration extends DefaultBatchConfiguration { + + @Bean + public Job job(JobRepository jobRepository) { + return new JobBuilder("myJob", jobRepository) + // define job flow as needed + .build(); + } + +} +---- + +You can customize the configuration of any infrastructure bean by overriding the required setter. + +IMPORTANT: `@EnableBatchProcessing` should *not* be used with `DefaultBatchConfiguration`. You should +either use the declarative way of configuring Spring Batch through `@EnableBatchProcessing`, +or use the programmatic way of extending `DefaultBatchConfiguration`, but not both ways at +the same time. + diff --git a/spring-batch-docs/modules/ROOT/pages/job/configuring.adoc b/spring-batch-docs/modules/ROOT/pages/job/configuring-job.adoc similarity index 97% rename from spring-batch-docs/modules/ROOT/pages/job/configuring.adoc rename to spring-batch-docs/modules/ROOT/pages/job/configuring-job.adoc index 57f9bbb48c..fe6e95a3ea 100644 --- a/spring-batch-docs/modules/ROOT/pages/job/configuring.adoc +++ b/spring-batch-docs/modules/ROOT/pages/job/configuring-job.adoc @@ -21,8 +21,7 @@ public Job footballJob(JobRepository jobRepository) { } ---- + -A `Job` (and, typically, any `Step` within it) requires a `JobRepository`. The -configuration of the `JobRepository` is handled through the xref:job/java-config.adoc[`Java Configuration`]. +A `Job` (and, typically, any `Step` within it) requires a `JobRepository`. + The preceding example illustrates a `Job` that consists of three `Step` instances. The job related builders can also contain other elements that help with parallelization (`Split`), @@ -251,7 +250,7 @@ it with its own list of listeners to produce a - + @@ -259,12 +258,12 @@ 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]] diff --git a/spring-batch-docs/modules/ROOT/pages/job/configuring-launcher.adoc b/spring-batch-docs/modules/ROOT/pages/job/configuring-launcher.adoc deleted file mode 100644 index 828f393d24..0000000000 --- a/spring-batch-docs/modules/ROOT/pages/job/configuring-launcher.adoc +++ /dev/null @@ -1,120 +0,0 @@ -[[configuringJobLauncher]] -= Configuring a JobLauncher - - -[tabs] -==== -Java:: -+ -When you use `@EnableBatchProcessing`, a `JobRegistry` is provided for you. -This section describes how to configure your own. - -XML:: -+ -// FIXME what is the XML equivalent? -==== - - -The most basic implementation of the `JobLauncher` interface is the `TaskExecutorJobLauncher`. -Its only required dependency is a `JobRepository` (needed to obtain an execution). - - -[tabs] -==== -Java:: -+ -The following example shows a `TaskExecutorJobLauncher` in Java: -+ -.Java Configuration -[source, java] ----- -... -@Bean -public JobLauncher jobLauncher() throws Exception { - TaskExecutorJobLauncher jobLauncher = new TaskExecutorJobLauncher(); - jobLauncher.setJobRepository(jobRepository); - jobLauncher.afterPropertiesSet(); - return jobLauncher; -} -... ----- - -XML:: -+ -The following example shows a `TaskExecutorJobLauncher` in XML: -+ -.XML Configuration -[source, xml] ----- - - - ----- - -==== - - -Once a xref:domain.adoc[JobExecution] is obtained, it is passed to the -execute method of `Job`, ultimately returning the `JobExecution` to the caller, as -the following image shows: - -.Job Launcher Sequence -image::job-launcher-sequence-sync.png[Job Launcher Sequence, scaledwidth="60%"] - -The sequence is straightforward and works well when launched from a scheduler. However, -issues arise when trying to launch from an HTTP request. In this scenario, the launching -needs to be done asynchronously so that the `TaskExecutorJobLauncher` returns immediately to its -caller. This is because it is not good practice to keep an HTTP request open for the -amount of time needed by long running processes (such as batch jobs). The following image shows -an example sequence: - -.Asynchronous Job Launcher Sequence -image::job-launcher-sequence-async.png[Async Job Launcher Sequence, scaledwidth="60%"] - -You can configure the `TaskExecutorJobLauncher` to allow for this scenario by configuring a -`TaskExecutor`. - -[tabs] -==== -Java:: -+ -The following Java example configures a `TaskExecutorJobLauncher` to return immediately: -+ -.Java Configuration -[source, java] ----- -@Bean -public JobLauncher jobLauncher() { - TaskExecutorJobLauncher jobLauncher = new TaskExecutorJobLauncher(); - jobLauncher.setJobRepository(jobRepository()); - jobLauncher.setTaskExecutor(new SimpleAsyncTaskExecutor()); - jobLauncher.afterPropertiesSet(); - return jobLauncher; -} ----- - -XML:: -+ -The following XML example configures a `TaskExecutorJobLauncher` to return immediately: -+ -.XML Configuration -[source, xml] ----- - - - - - - ----- - -==== - - - -You can use any implementation of the spring `TaskExecutor` -interface to control how jobs are asynchronously -executed. - diff --git a/spring-batch-docs/modules/ROOT/pages/job/configuring-operator.adoc b/spring-batch-docs/modules/ROOT/pages/job/configuring-operator.adoc new file mode 100644 index 0000000000..fc0f61b9ba --- /dev/null +++ b/spring-batch-docs/modules/ROOT/pages/job/configuring-operator.adoc @@ -0,0 +1,103 @@ +[[configuringJobOperator]] += Configuring a JobOperator + +The most basic implementation of the `JobOperator` interface is the `TaskExecutorJobOperator`. +It requires only one dependency: a `JobRepository`. All other dependencies like `JobRegistry`, +`MeterRegistry`, `TransactionManager`, etc are optional. Spring Batch provides a factory bean +to simplify the configuration of this operator: `JobOperatorFactoryBean`. This factory bean +creates a transactional proxy around the `TaskExecutorJobOperator` to ensure that all its public methods +are executed within a transaction. + +[tabs] +==== +Java:: ++ +The following example shows how to configure a `TaskExecutorJobOperator` in Java: ++ +.Java Configuration +[source, java] +---- +... +@Bean +public JobOperatorFactoryBean jobOperator(JobRepository jobRepository) { + JobOperatorFactoryBean jobOperatorFactoryBean = new JobOperatorFactoryBean(); + jobOperator.setJobRepository(jobRepository); + return jobOperatorFactoryBean; +} +... +---- + +XML:: ++ +The following example shows how to configure a `TaskExecutorJobOperator` in XML: ++ +.XML Configuration +[source, xml] +---- + + + +---- + +==== + + +Once a xref:domain.adoc[JobExecution] is obtained, it is passed to the +execute method of `Job`, ultimately returning the `JobExecution` to the caller, as +the following image shows: + +.Job Launcher Sequence +image::job-launcher-sequence-sync.png[Job Launcher Sequence, scaledwidth="50%"] + +The sequence is straightforward and works well when launched from a scheduler. However, +issues arise when trying to launch from an HTTP request. In this scenario, the launching +needs to be done asynchronously so that the `TaskExecutorJobOperator` returns immediately to its +caller. This is because it is not good practice to keep an HTTP request open for the +amount of time needed by long running processes (such as batch jobs). The following image shows +an example sequence: + +.Asynchronous Job Launcher Sequence +image::job-launcher-sequence-async.png[Async Job Launcher Sequence, scaledwidth="50%"] + +You can configure the `TaskExecutorJobOperator` to allow for this scenario by configuring a +`TaskExecutor`. + +[tabs] +==== +Java:: ++ +The following Java example configures a `TaskExecutorJobOperator` to return immediately: ++ +.Java Configuration +[source, java] +---- +@Bean +public JobOperatorFactoryBean jobOperator(JobRepository jobRepository) { + JobOperatorFactoryBean jobOperatorFactoryBean = new JobOperatorFactoryBean(); + jobOperator.setJobRepository(jobRepository); + jobOperator.setTaskExecutor(new SimpleAsyncTaskExecutor()); + return jobOperatorFactoryBean; +} +---- + +XML:: ++ +The following XML example configures a `TaskExecutorJobOperator` to return immediately: ++ +.XML Configuration +[source, xml] +---- + + + + + + +---- + +==== + +You can use any implementation of the Spring `TaskExecutor` +interface to control how jobs are asynchronously +executed. + diff --git a/spring-batch-docs/modules/ROOT/pages/job/configuring-repository.adoc b/spring-batch-docs/modules/ROOT/pages/job/configuring-repository.adoc index 2fa64ab926..a8b4a1d4b4 100644 --- a/spring-batch-docs/modules/ROOT/pages/job/configuring-repository.adoc +++ b/spring-batch-docs/modules/ROOT/pages/job/configuring-repository.adoc @@ -1,26 +1,31 @@ [[configuringJobRepository]] = Configuring a JobRepository -As described earlier, the xref:job.adoc[`JobRepository`] is used for basic CRUD operations of the various persisted -domain objects within Spring Batch, such as `JobExecution` and `StepExecution`. -It is required by many of the major framework features, such as the `JobLauncher`, +As described the xref:job.adoc[earlier], the `JobRepository` is used for basic CRUD operations +of the various persisted domain objects within Spring Batch, such as `JobExecution` and `StepExecution`. +It is required by many of the major framework features, such as the `JobOperator`, `Job`, and `Step`. - [tabs] ==== Java:: + -When using `@EnableBatchProcessing`, a `JobRepository` is provided for you. -This section describes how to customize it. Configuration options of the job -repository can be specified through the attributes of the `@EnableBatchProcessing` -annotation, as shown in the following example: +When using `@EnableBatchProcessing`, a `ResourcelssJobRepository` is provided for you. +This section describes how to customize it. Spring Batch provides two implementations +of the `JobRepository` interface which are backed by a database: a JDBC implementation +(which can be used with any JDBC-compliant database) and a MongoDB implementation. These two +implementations are provided by the `@EnableJdbcJobRepository` and `@EnableMongoJobRepository` +annotations, respectively. ++ +The following example shows how to customize a JDBC-based job repository through the attributes +of the `@EnableJdbcJobRepository` annotation: + .Java Configuration [source, java] ---- @Configuration -@EnableBatchProcessing( +@EnableBatchProcessing +@EnableJdbcJobRepository( dataSourceRef = "batchDataSource", transactionManagerRef = "batchTransactionManager", tablePrefix = "BATCH_", @@ -60,9 +65,7 @@ configuration options available, as the following example shows: Other than the `id`, none of the configuration options listed earlier are required. If they are not set, the defaults shown earlier are used. The `max-varchar-length` defaults to `2500`, which is the length of the long -`VARCHAR` columns in the xref:schema-appendix.adoc#metaDataSchemaOverview[sample schema scripts] -. - +`VARCHAR` columns in the xref:schema-appendix.adoc#metaDataSchemaOverview[sample schema scripts]. ==== @@ -93,7 +96,8 @@ The following example shows how to override the isolation level in Java: [source, java] ---- @Configuration -@EnableBatchProcessing(isolationLevelForCreate = "ISOLATION_REPEATABLE_READ") +@EnableBatchProcessing +@EnableJdbcJobRepository(isolationLevelForCreate = "ISOLATION_REPEATABLE_READ") public class MyJobConfiguration { // job definition @@ -189,7 +193,8 @@ The following example shows how to change the table prefix in Java: [source, java] ---- @Configuration -@EnableBatchProcessing(tablePrefix = "SYSTEM.TEST_") +@EnableBatchProcessing +@EnableJdbcJobRepository(tablePrefix = "SYSTEM.TEST_") public class MyJobConfiguration { // job definition @@ -210,10 +215,6 @@ The following example shows how to change the table prefix in XML: ==== - - - - Given the preceding changes, every query to the metadata tables is prefixed with `SYSTEM.TEST_`. `BATCH_JOB_EXECUTION` is referred to as `SYSTEM.TEST_JOB_EXECUTION`. @@ -224,14 +225,14 @@ NOTE: Only the table prefix is configurable. The table and column names are not. If you use a database platform that is not in the list of supported platforms, you may be able to use one of the supported types, if the SQL variant is close enough. To do -this, you can use the raw `JobRepositoryFactoryBean` instead of the namespace shortcut and +this, you can use the raw `JdbcJobRepositoryFactoryBean` instead of the namespace shortcut and use it to set the database type to the closest match. [tabs] ==== Java:: + -The following example shows how to use `JobRepositoryFactoryBean` to set the database type +The following example shows how to use `JdbcJobRepositoryFactoryBean` to set the database type to the closest match in Java: + .Java Configuration @@ -239,7 +240,7 @@ to the closest match in Java: ---- @Bean public JobRepository jobRepository() throws Exception { - JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean(); + JdbcJobRepositoryFactoryBean factory = new JdbcJobRepositoryFactoryBean(); factory.setDataSource(dataSource); factory.setDatabaseType("db2"); factory.setTransactionManager(transactionManager); @@ -249,13 +250,13 @@ public JobRepository jobRepository() throws Exception { XML:: + -The following example shows how to use `JobRepositoryFactoryBean` to set the database type +The following example shows how to use `JdbcJobRepositoryFactoryBean` to set the database type to the closest match in XML: + .XML Configuration [source, xml] ---- - + @@ -264,7 +265,7 @@ to the closest match in XML: ==== -If the database type is not specified, the `JobRepositoryFactoryBean` tries to +If the database type is not specified, the `JdbcJobRepositoryFactoryBean` tries to auto-detect the database type from the `DataSource`. The major differences between platforms are mainly accounted for by the strategy for incrementing primary keys, so diff --git a/spring-batch-docs/modules/ROOT/pages/job/java-config.adoc b/spring-batch-docs/modules/ROOT/pages/job/java-config.adoc deleted file mode 100644 index 472650b763..0000000000 --- a/spring-batch-docs/modules/ROOT/pages/job/java-config.adoc +++ /dev/null @@ -1,106 +0,0 @@ -[[javaConfig]] -= Java Configuration - -Spring 3 brought the ability to configure applications with Java instead of XML. As of -Spring Batch 2.2.0, you can configure batch jobs by using the same Java configuration. -There are three components for the Java-based configuration: the `@EnableBatchProcessing` -annotation and two builders. - -The `@EnableBatchProcessing` annotation works similarly to the other `@Enable*` annotations in the -Spring family. In this case, `@EnableBatchProcessing` provides a base configuration for -building batch jobs. Within this base configuration, an instance of `StepScope` and `JobScope` are -created, in addition to a number of beans being made available to be autowired: - -* `JobRepository`: a bean named `jobRepository` -* `JobLauncher`: a bean named `jobLauncher` -* `JobRegistry`: a bean named `jobRegistry` -* `JobExplorer`: a bean named `jobExplorer` -* `JobOperator`: a bean named `jobOperator` - -The default implementation provides the beans mentioned in the preceding list and requires a `DataSource` -and a `PlatformTransactionManager` to be provided as beans within the context. The data source and transaction -manager are used by the `JobRepository` and `JobExplorer` instances. By default, the data source named `dataSource` -and the transaction manager named `transactionManager` will be used. You can customize any of these beans by using -the attributes of the `@EnableBatchProcessing` annotation. The following example shows how to provide a -custom data source and transaction manager: - -[source, java] ----- -@Configuration -@EnableBatchProcessing(dataSourceRef = "batchDataSource", transactionManagerRef = "batchTransactionManager") -public class MyJobConfiguration { - - @Bean - public DataSource batchDataSource() { - return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.HSQL) - .addScript("/org/springframework/batch/core/schema-hsqldb.sql") - .generateUniqueName(true).build(); - } - - @Bean - public JdbcTransactionManager batchTransactionManager(DataSource dataSource) { - return new JdbcTransactionManager(dataSource); - } - - @Bean - public Job job(JobRepository jobRepository) { - return new JobBuilder("myJob", jobRepository) - //define job flow as needed - .build(); - } - -} ----- - -NOTE: Only one configuration class needs to have the `@EnableBatchProcessing` annotation. Once -you have a class annotated with it, you have all of the configuration described earlier. - -Starting from v5.0, an alternative, programmatic way of configuring base infrastrucutre beans -is provided through the `DefaultBatchConfiguration` class. This class provides the same beans -provided by `@EnableBatchProcessing` and can be used as a base class to configure batch jobs. -The following snippet is a typical example of how to use it: - -[source, java] ----- -@Configuration -class MyJobConfiguration extends DefaultBatchConfiguration { - - @Bean - public Job job(JobRepository jobRepository) { - return new JobBuilder("job", jobRepository) - // define job flow as needed - .build(); - } - -} ----- - -The data source and transaction manager will be resolved from the application context -and set on the job repository and job explorer. You can customize the configuration -of any infrastructure bean by overriding the required setter. The following example -shows how to customize the character encoding for instance: - -[source, java] ----- -@Configuration -class MyJobConfiguration extends DefaultBatchConfiguration { - - @Bean - public Job job(JobRepository jobRepository) { - return new JobBuilder("job", jobRepository) - // define job flow as needed - .build(); - } - - @Override - protected Charset getCharset() { - return StandardCharsets.ISO_8859_1; - } -} ----- - -NOTE: `@EnableBatchProcessing` should *not* be used with `DefaultBatchConfiguration`. You should -either use the declarative way of configuring Spring Batch through `@EnableBatchProcessing`, -or use the programmatic way of extending `DefaultBatchConfiguration`, but not both ways at -the same time. - diff --git a/spring-batch-docs/modules/ROOT/pages/job/running.adoc b/spring-batch-docs/modules/ROOT/pages/job/running.adoc index 80114898fa..ac7dee6fe1 100644 --- a/spring-batch-docs/modules/ROOT/pages/job/running.adoc +++ b/spring-batch-docs/modules/ROOT/pages/job/running.adoc @@ -3,13 +3,13 @@ At a minimum, launching a batch job requires two things: the `Job` to be launched and a -`JobLauncher`. Both can be contained within the same +`JobOperator`. Both can be contained within the same context or different contexts. For example, if you launch jobs from the command line, a new JVM is instantiated for each `Job`. Thus, every -job has its own `JobLauncher`. However, if +job has its own `JobOperator`. However, if you run from within a web container that is within the scope of an `HttpRequest`, there is usually one -`JobLauncher` (configured for asynchronous job +`JobOperator` (configured for asynchronous job launching) that multiple requests invoke to launch their jobs. [[runningJobsFromCommandLine]] @@ -24,67 +24,50 @@ to launch a Java process besides a shell script, such as Perl, Ruby, or even build tools, such as Ant or Maven. However, because most people are familiar with shell scripts, this example focuses on them. -[[commandLineJobRunner]] -=== The CommandLineJobRunner +[[commandLineJobOperator]] +=== The CommandLineJobOperator Because the script launching the job must kick off a Java Virtual Machine, there needs to be a class with a `main` method to act as the primary entry point. Spring Batch provides an implementation that serves this purpose: -`CommandLineJobRunner`. Note +`CommandLineJobOperator`. Note that this is just one way to bootstrap your application. There are many ways to launch a Java process, and this class should in no way be -viewed as definitive. The `CommandLineJobRunner` +viewed as definitive. The `CommandLineJobOperator` performs four tasks: * Load the appropriate `ApplicationContext`. * Parse command line arguments into `JobParameters`. * Locate the appropriate job based on arguments. -* Use the `JobLauncher` provided in the application context to launch the job. +* Use the `JobOperator` provided in the application context to launch the job. All of these tasks are accomplished with only the arguments passed in. The following table describes the required arguments: -.CommandLineJobRunner arguments +.CommandLineJobOperator arguments |=============== -|`jobPath`|The location of the XML file that is used to +|`jobClass`|The fully qualified name of the job configuration class used to create an `ApplicationContext`. This file should contain everything needed to run the complete `Job`. -|`jobName`|The name of the job to be run. +|`operation`|The name of the operation to execute on the job. Can be one of [`start`, `startNextInstance`, `stop`, `restart`, `abandon`] +|`jobName` or `jobExecutionId`|Depending on the operation, this can be the name of the job to start or the execution ID of the job to stop, restart or abandon. |=============== -These arguments must be passed in, with the path first and the name second. All arguments -after these are considered to be job parameters, are turned into a `JobParameters` object, -and must be in the format of `name=value`. +When starting a job, all arguments after these are considered to be job parameters, are turned into a `JobParameters` object, +and must be in the format of `name=value`. In the case of stopping, restarting or abandoning a job, the `jobExecutionId` is +expected as the 4th argument, and all remaining arguments are ignored. - -[tabs] -==== -Java:: -+ The following example shows a date passed as a job parameter to a job defined in Java: -+ -[source] ----- - null, transactionManager) - .build(); - } -} ----- - -XML:: -+ -In most cases, you would want to use a manifest to declare your `main` class in a jar. However, -for simplicity, the class was used directly. This example uses the `EndOfDay` -example from the xref:domain.adoc[The Domain Language of Batch]. The first -argument is `endOfDayJob.xml`, which is the Spring ApplicationContext that contains the -`Job`. The second argument, `endOfDay,` represents the job name. The final argument, -`schedule.date=2007-05-05,java.time.LocalDate`, is converted into a `JobParameter` object of type -`java.time.LocalDate`. -+ -The following example shows a sample configuration for `endOfDay` in XML: -+ -[source, xml] ----- - - - - - - ----- - -==== - - - -The preceding example is overly simplistic, since there are many more requirements to a -run a batch job in Spring Batch in general, but it serves to show the two main -requirements of the `CommandLineJobRunner`: `Job` and `JobLauncher`. - +You can override this behavior by setting a custom `JobParametersConverter` on the `CommandLineJobOperator`. [[exitCodes]] @@ -199,9 +105,8 @@ detail in Chapter 5. For the purposes of discussing exit codes, the only important thing to know is that an `ExitStatus` has an exit code property that is set by the framework (or the developer) and is returned as part of the -`JobExecution` returned from the -`JobLauncher`. The -`CommandLineJobRunner` converts this string value +`JobExecution` returned from the `JobOperator`. The +`CommandLineJobOperator` converts this string value to a number by using the `ExitCodeMapper` interface: @@ -209,32 +114,20 @@ interface: ---- public interface ExitCodeMapper { - public int intValue(String exitCode); + int intValue(String exitCode); } ---- -The essential contract of an -`ExitCodeMapper` is that, given a string exit -code, a number representation will be returned. The default -implementation used by the job runner is the `SimpleJvmExitCodeMapper` +The essential contract of an `ExitCodeMapper` is that, given a string exit +code, a number representation will be returned. The default implementation +used by the job runner is the `SimpleJvmExitCodeMapper` that returns 0 for completion, 1 for generic errors, and 2 for any job runner errors such as not being able to find a `Job` in the provided context. If anything more complex than the three values above is needed, a custom implementation of the `ExitCodeMapper` interface -must be supplied. Because the -`CommandLineJobRunner` is the class that creates -an `ApplicationContext` and, thus, cannot be -'wired together', any values that need to be overwritten must be -autowired. This means that if an implementation of -`ExitCodeMapper` is found within the `BeanFactory`, -it is injected into the runner after the context is created. All -that needs to be done to provide your own -`ExitCodeMapper` is to declare the implementation -as a root level bean and ensure that it is part of the -`ApplicationContext` that is loaded by the -runner. +must be supplied by setting it on the `CommandLineJobOperator`. [[runningJobsFromWebContainer]] == Running Jobs from within a Web Container @@ -253,7 +146,7 @@ image::launch-from-request.png[Async Job Launcher Sequence from web container, s The controller in this case is a Spring MVC controller. See the Spring Framework Reference Guide for more about https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc[Spring MVC]. The controller launches a `Job` by using a -`JobLauncher` that has been configured to launch +`JobOperator` that has been configured to launch xref:job/running.adoc#runningJobsFromWebContainer[asynchronously], which immediately returns a `JobExecution`. The `Job` is likely still running. However, this @@ -264,17 +157,17 @@ shows an example: [source, java] ---- @Controller -public class JobLauncherController { +public class JobOperatorController { @Autowired - JobLauncher jobLauncher; + JobOperator jobOperator; @Autowired Job job; - @RequestMapping("/jobLauncher.html") + @RequestMapping("/jobOperator.html") public void handle() throws Exception{ - jobLauncher.run(job, new JobParameters()); + jobOperator.start(job, new JobParameters()); } } ---- diff --git a/spring-batch-docs/modules/ROOT/pages/monitoring-and-metrics.adoc b/spring-batch-docs/modules/ROOT/pages/monitoring-and-metrics.adoc deleted file mode 100644 index 0d22ebcabb..0000000000 --- a/spring-batch-docs/modules/ROOT/pages/monitoring-and-metrics.adoc +++ /dev/null @@ -1,85 +0,0 @@ - -[[monitoring-and-metrics]] -= Monitoring and metrics - - -Since version 4.2, Spring Batch provides support for batch monitoring and metrics -based on link:$$https://micrometer.io/$$[Micrometer]. This section describes -which metrics are provided out-of-the-box and how to contribute custom metrics. - -[[built-in-metrics]] -== Built-in metrics - -Metrics collection does not require any specific configuration. All metrics provided -by the framework are registered in -link:$$https://micrometer.io/docs/concepts#_global_registry$$[Micrometer's global registry] -under the `spring.batch` prefix. The following table explains all the metrics in details: - -|=============== -|__Metric Name__|__Type__|__Description__|__Tags__ -|`spring.batch.job`|`TIMER`|Duration of job execution|`name`, `status` -|`spring.batch.job.active`|`LONG_TASK_TIMER`|Currently active jobs|`name` -|`spring.batch.step`|`TIMER`|Duration of step execution|`name`, `job.name`, `status` -|`spring.batch.step.active`|`LONG_TASK_TIMER`|Currently active step|`name` -|`spring.batch.item.read`|`TIMER`|Duration of item reading|`job.name`, `step.name`, `status` -|`spring.batch.item.process`|`TIMER`|Duration of item processing|`job.name`, `step.name`, `status` -|`spring.batch.chunk.write`|`TIMER`|Duration of chunk writing|`job.name`, `step.name`, `status` -|=============== - -NOTE: The `status` tag can be either `SUCCESS` or `FAILURE`. - -[[custom-metrics]] -== Custom metrics - -If you want to use your own metrics in your custom components, we recommend using -Micrometer APIs directly. The following is an example of how to time a `Tasklet`: - -[source, java] ----- -import io.micrometer.core.instrument.Metrics; -import io.micrometer.core.instrument.Timer; - -import org.springframework.batch.core.StepContribution; -import org.springframework.batch.core.scope.context.ChunkContext; -import org.springframework.batch.core.step.tasklet.Tasklet; -import org.springframework.batch.repeat.RepeatStatus; - -public class MyTimedTasklet implements Tasklet { - - @Override - public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) { - Timer.Sample sample = Timer.start(Metrics.globalRegistry); - String status = "success"; - try { - // do some work - } catch (Exception e) { - // handle exception - status = "failure"; - } finally { - sample.stop(Timer.builder("my.tasklet.timer") - .description("Duration of MyTimedTasklet") - .tag("status", status) - .register(Metrics.globalRegistry)); - } - return RepeatStatus.FINISHED; - } -} ----- - -[[disabling-metrics]] -== Disabling Metrics - -Metrics collection is a concern similar to logging. Disabling logs is typically -done by configuring the logging library, and this is no different for metrics. -There is no feature in Spring Batch to disable Micrometer's metrics. This should -be done on Micrometer's side. Since Spring Batch stores metrics in the global -registry of Micrometer with the `spring.batch` prefix, you can configure -micrometer to ignore or deny batch metrics with the following snippet: - -[source, java] ----- -Metrics.globalRegistry.config().meterFilter(MeterFilter.denyNameStartsWith("spring.batch")) ----- - -See Micrometer's link:$$http://micrometer.io/docs/concepts#_meter_filters$$[reference documentation] -for more details. \ No newline at end of file diff --git a/spring-batch-docs/modules/ROOT/pages/processor.adoc b/spring-batch-docs/modules/ROOT/pages/processor.adoc index 02993846de..8e24984dcf 100644 --- a/spring-batch-docs/modules/ROOT/pages/processor.adoc +++ b/spring-batch-docs/modules/ROOT/pages/processor.adoc @@ -101,7 +101,7 @@ public Job ioSampleJob(JobRepository jobRepository, Step step1) { @Bean public Step step1(JobRepository jobRepository, PlatformTransactionManager transactionManager) { return new StepBuilder("step1", jobRepository) - .chunk(2, transactionManager) + .chunk(2).transactionManager(transactionManager) .reader(fooReader()) .processor(fooProcessor()) .writer(barWriter()) @@ -205,7 +205,7 @@ public Job ioSampleJob(JobRepository jobRepository, Step step1) { @Bean public Step step1(JobRepository jobRepository, PlatformTransactionManager transactionManager) { return new StepBuilder("step1", jobRepository) - .chunk(2, transactionManager) + .chunk(2).transactionManager(transactionManager) .reader(fooReader()) .processor(compositeProcessor()) .writer(foobarWriter()) diff --git a/spring-batch-docs/modules/ROOT/pages/readers-and-writers/delegate-pattern-registering.adoc b/spring-batch-docs/modules/ROOT/pages/readers-and-writers/delegate-pattern-registering.adoc index c89e4f2498..aaeb0f56b9 100644 --- a/spring-batch-docs/modules/ROOT/pages/readers-and-writers/delegate-pattern-registering.adoc +++ b/spring-batch-docs/modules/ROOT/pages/readers-and-writers/delegate-pattern-registering.adoc @@ -30,7 +30,7 @@ public Job ioSampleJob(JobRepository jobRepository, Step step1) { @Bean public Step step1(JobRepository jobRepository, PlatformTransactionManager transactionManager) { return new StepBuilder("step1", jobRepository) - .chunk(2, transactionManager) + .chunk(2).transactionManager(transactionManager) .reader(fooReader()) .processor(fooProcessor()) .writer(compositeItemWriter()) 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/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(); } ---- diff --git a/spring-batch-docs/modules/ROOT/pages/scalability.adoc b/spring-batch-docs/modules/ROOT/pages/scalability.adoc index b00353c4e6..697d9f2d77 100644 --- a/spring-batch-docs/modules/ROOT/pages/scalability.adoc +++ b/spring-batch-docs/modules/ROOT/pages/scalability.adoc @@ -52,7 +52,7 @@ public TaskExecutor taskExecutor() { @Bean public Step sampleStep(TaskExecutor taskExecutor, JobRepository jobRepository, PlatformTransactionManager transactionManager) { return new StepBuilder("sampleStep", jobRepository) - .chunk(10, transactionManager) + .chunk(10).transactionManager(transactionManager) .reader(itemReader()) .writer(itemWriter()) .taskExecutor(taskExecutor) @@ -103,7 +103,7 @@ follows: @Bean public Step sampleStep(TaskExecutor taskExecutor, JobRepository jobRepository, PlatformTransactionManager transactionManager) { return new StepBuilder("sampleStep", jobRepository) - .chunk(10, transactionManager) + .chunk(10).transactionManager(transactionManager) .reader(itemReader()) .writer(itemWriter()) .taskExecutor(taskExecutor) @@ -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-docs/modules/ROOT/pages/schema-appendix.adoc b/spring-batch-docs/modules/ROOT/pages/schema-appendix.adoc index 906152c009..dd232bdbdb 100644 --- a/spring-batch-docs/modules/ROOT/pages/schema-appendix.adoc +++ b/spring-batch-docs/modules/ROOT/pages/schema-appendix.adoc @@ -73,7 +73,7 @@ statements: ---- CREATE SEQUENCE BATCH_STEP_EXECUTION_SEQ; CREATE SEQUENCE BATCH_JOB_EXECUTION_SEQ; -CREATE SEQUENCE BATCH_JOB_SEQ; +CREATE SEQUENCE BATCH_JOB_INSTANCE_SEQ; ---- Many database vendors do not support sequences. In these cases, work-arounds are used, @@ -85,8 +85,8 @@ CREATE TABLE BATCH_STEP_EXECUTION_SEQ (ID BIGINT NOT NULL) type=InnoDB; INSERT INTO BATCH_STEP_EXECUTION_SEQ values(0); CREATE TABLE BATCH_JOB_EXECUTION_SEQ (ID BIGINT NOT NULL) type=InnoDB; INSERT INTO BATCH_JOB_EXECUTION_SEQ values(0); -CREATE TABLE BATCH_JOB_SEQ (ID BIGINT NOT NULL) type=InnoDB; -INSERT INTO BATCH_JOB_SEQ values(0); +CREATE TABLE BATCH_JOB_INSTANCE_SEQ (ID BIGINT NOT NULL) type=InnoDB; +INSERT INTO BATCH_JOB_INSTANCE_SEQ values(0); ---- In the preceding case, a table is used in place of each sequence. The Spring core class, diff --git a/spring-batch-docs/modules/ROOT/pages/spring-batch-architecture.adoc b/spring-batch-docs/modules/ROOT/pages/spring-batch-architecture.adoc index ea0d35f7c9..4999c89d66 100644 --- a/spring-batch-docs/modules/ROOT/pages/spring-batch-architecture.adoc +++ b/spring-batch-docs/modules/ROOT/pages/spring-batch-architecture.adoc @@ -13,7 +13,7 @@ This layered architecture highlights three major high-level components: Applicat Core, and Infrastructure. The application contains all batch jobs and custom code written by developers using Spring Batch. The Batch Core contains the core runtime classes necessary to launch and control a batch job. It includes implementations for -`JobLauncher`, `Job`, and `Step`. Both Application and Core are built on top of a common +`JobOperator`, `Job`, and `Step`. Both Application and Core are built on top of a common infrastructure. This infrastructure contains common readers and writers and services (such as the `RetryTemplate`), which are used both by application developers(readers and writers, such as `ItemReader` and `ItemWriter`), and the core framework itself (retry, 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 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..0a379b5076 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 @@ -116,7 +116,7 @@ The following example shows the how to create the notification integration beans @@ -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 diff --git a/spring-batch-docs/modules/ROOT/pages/spring-batch-observability.adoc b/spring-batch-docs/modules/ROOT/pages/spring-batch-observability.adoc new file mode 100644 index 0000000000..5c8f4135cf --- /dev/null +++ b/spring-batch-docs/modules/ROOT/pages/spring-batch-observability.adoc @@ -0,0 +1,11 @@ + +[[springBatchObservability]] += Spring Batch Observability + +Observability is a critical aspect of modern applications, and Spring Batch provides robust support for monitoring and tracing batch jobs. + +This section covers the integration of Spring Batch with popular observability tools such as Micrometer and Java Flight Recorder (JFR): + +[role="xmlContent"] +* xref:spring-batch-observability/micrometer.adoc[Micrometer Support] +* xref:spring-batch-observability/jfr.adoc[Java Flight Recorder Support] \ No newline at end of file diff --git a/spring-batch-docs/modules/ROOT/pages/spring-batch-observability/jfr.adoc b/spring-batch-docs/modules/ROOT/pages/spring-batch-observability/jfr.adoc new file mode 100644 index 0000000000..411f51b6d9 --- /dev/null +++ b/spring-batch-docs/modules/ROOT/pages/spring-batch-observability/jfr.adoc @@ -0,0 +1,13 @@ +[[jfr]] += Java Flight Recorder (JFR) support + +As of version 6, Spring Batch provides support for Java Flight Recorder (JFR) to help you monitor and troubleshoot batch jobs. JFR is a low-overhead, event-based profiling tool built into the Java Virtual Machine (JVM) that allows developers to collect detailed information about the performance and behavior of their applications. + +JFR can be enabled by adding the following JVM options when starting your Spring Batch application: + +[source, bash] +---- +java -XX:StartFlightRecording:filename=my-batch-job.jfr,dumponexit=true -jar my-batch-job.jar +---- + +Once JFR is enabled, Spring Batch will automatically create JFR events for key batch processing activities, such as job and step executions, item reads and writes, as well as transaction boundaries. These events can be viewed and analyzed using tools such as Java Mission Control (JMC) or other JFR-compatible tools. \ No newline at end of file diff --git a/spring-batch-docs/modules/ROOT/pages/spring-batch-observability/micrometer.adoc b/spring-batch-docs/modules/ROOT/pages/spring-batch-observability/micrometer.adoc new file mode 100644 index 0000000000..1d9836d2ac --- /dev/null +++ b/spring-batch-docs/modules/ROOT/pages/spring-batch-observability/micrometer.adoc @@ -0,0 +1,111 @@ +[[micrometer]] += Micrometer support + +[[monitoring-and-metrics]] +== Monitoring and metrics + +Since version 4.2, Spring Batch provides support for batch monitoring and metrics +based on link:$$https://micrometer.io/$$[Micrometer]. This section describes +which metrics are provided out-of-the-box and how to contribute custom metrics. + +[[built-in-metrics]] +== Built-in metrics + +Metrics collection is disabled by default. To enable it, you need to define a Micrometer +`ObservationRegistry` bean in your application context. Typically, you would need to define +which ObservationHandler to use. The following example shows how to register a `DefaultMeterObservationHandler` +that will store metrics in a `MeterRegistry` (for example, a Prometheus registry): + +[source, java] +---- +@Bean +public ObservationRegistry observationRegistry(MeterRegistry meterRegistry) { + ObservationRegistry observationRegistry = ObservationRegistry.create(); + observationRegistry.observationConfig() + .observationHandler(new DefaultMeterObservationHandler(meterRegistry)); + return observationRegistry; +} +---- + +Spring Batch specific metrics are registered under the `spring.batch` prefix. The following +table explains all the metrics in details: + +|=============== +|__Metric Name__|__Type__|__Description__|__Tags__ +|`spring.batch.job`|`TIMER`|Duration of job execution|`name`, `status` +|`spring.batch.job.active`|`LONG_TASK_TIMER`|Currently active job|`name` +|`spring.batch.step`|`TIMER`|Duration of step execution|`name`, `job.name`, `status` +|`spring.batch.step.active`|`LONG_TASK_TIMER`|Currently active step|`name` +|`spring.batch.item.read`|`TIMER`|Duration of item reading|`job.name`, `step.name`, `status` +|`spring.batch.item.process`|`TIMER`|Duration of item processing|`job.name`, `step.name`, `status` +|`spring.batch.chunk.write`|`TIMER`|Duration of chunk writing|`job.name`, `step.name`, `status` +|`spring.batch.job.launch.count`|`COUNTER`|Job launch count| N/A +|=============== + +NOTE: The `status` tag for jobs and steps is equal to the exit status. For item reading, processing +and writing, this `status` tag can be either `SUCCESS` or `FAILURE`. + +[[custom-metrics]] +== Custom metrics + +If you want to use your own metrics in your custom components, we recommend using +Micrometer APIs directly. The following is an example of how to time a `Tasklet`: + +[source, java] +---- +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationRegistry; + +import org.springframework.batch.core.StepContribution; +import org.springframework.batch.core.scope.context.ChunkContext; +import org.springframework.batch.core.step.tasklet.Tasklet; +import org.springframework.batch.repeat.RepeatStatus; + +public class MyTimedTasklet implements Tasklet { + + private ObservationRegistry observationRegistry; + + public MyTimedTasklet(ObservationRegistry observationRegistry) { + this.observationRegistry = observationRegistry; + } + + @Override + public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) { + Observation observation = Observation.start("my.tasklet.step", this.observationRegistry); + try (Observation.Scope scope = observation.openScope()) { + // do some work + return RepeatStatus.FINISHED; + } catch (Exception e) { + // handle exception + observation.error(exception); + } finally { + observation.stop(); + } + } +} +---- + +[[tracing]] +== Tracing + +As of version 5, Spring Batch provides tracing through Micrometer's `Observation` API. By default, tracing is disabled. +To enable it, you need to define an `ObservationRegistry` bean configured with an `ObservationHandler` that supports tracing, +such as `TracingAwareMeterObservationHandler`: + +[source, java] +---- +@Bean +public ObservationRegistry observationRegistry(MeterRegistry meterRegistry, Tracer tracer) { + DefaultMeterObservationHandler observationHandler = new DefaultMeterObservationHandler(meterRegistry); + ObservationRegistry observationRegistry = ObservationRegistry.create(); + observationRegistry.observationConfig() + .observationHandler(new TracingAwareMeterObservationHandler<>(observationHandler, tracer)); + return observationRegistry; +} +---- + +With that in place, Spring Batch will create a trace for each job execution and a span for each step execution. + +If you do not use `EnableBatchProcessing` or `DefaultBatchConfiguration`, you need to register a +`BatchObservabilityBeanPostProcessor` in your application context, which will automatically set Micrometer's observation +registry in observable batch artefacts. diff --git a/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/commit-interval.adoc b/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/commit-interval.adoc index 60528a2c28..dd56270eef 100644 --- a/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/commit-interval.adoc +++ b/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/commit-interval.adoc @@ -29,7 +29,7 @@ public Job sampleJob(JobRepository jobRepository, Step step1) { @Bean public Step step1(JobRepository jobRepository, PlatformTransactionManager transactionManager) { return new StepBuilder("step1", jobRepository) - .chunk(10, transactionManager) + .chunk(10).transactionManager(transactionManager) .reader(itemReader()) .writer(itemWriter()) .build(); 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..7eef3ac47c 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 @@ -22,7 +22,7 @@ The following Java example shows an example of using a skip limit: @Bean public Step step1(JobRepository jobRepository, PlatformTransactionManager transactionManager) { return new StepBuilder("step1", jobRepository) - .chunk(10, transactionManager) + .chunk(10).transactionManager(transactionManager) .reader(flatFileItemReader()) .writer(itemWriter()) .faultTolerant() @@ -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:: + @@ -81,7 +83,7 @@ The following Java example shows an example excluding a particular exception: @Bean public Step step1(JobRepository jobRepository, PlatformTransactionManager transactionManager) { return new StepBuilder("step1", jobRepository) - .chunk(10, transactionManager) + .chunk(10).transactionManager(transactionManager) .reader(flatFileItemReader()) .writer(itemWriter()) .faultTolerant() @@ -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:: + diff --git a/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/configuring.adoc b/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/configuring.adoc index 100782cd63..95b3af9a31 100644 --- a/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/configuring.adoc +++ b/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/configuring.adoc @@ -33,7 +33,7 @@ public Job sampleJob(JobRepository jobRepository, Step sampleStep) { public Step sampleStep(JobRepository jobRepository, // <2> PlatformTransactionManager transactionManager) { // <1> return new StepBuilder("sampleStep", jobRepository) - .chunk(10, transactionManager) // <3> + .chunk(10).transactionManager(transactionManager) // <3> .reader(itemReader()) .writer(itemWriter()) .build(); diff --git a/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/controlling-rollback.adoc b/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/controlling-rollback.adoc index 69b704c5ae..a4e21ee625 100644 --- a/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/controlling-rollback.adoc +++ b/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/controlling-rollback.adoc @@ -21,7 +21,7 @@ In Java, you can control rollback as follows: @Bean public Step step1(JobRepository jobRepository, PlatformTransactionManager transactionManager) { return new StepBuilder("step1", jobRepository) - .chunk(2, transactionManager) + .chunk(2).transactionManager(transactionManager) .reader(itemReader()) .writer(itemWriter()) .faultTolerant() @@ -75,7 +75,7 @@ The following example shows how to create a reader that does not buffer items in @Bean public Step step1(JobRepository jobRepository, PlatformTransactionManager transactionManager) { return new StepBuilder("step1", jobRepository) - .chunk(2, transactionManager) + .chunk(2).transactionManager(transactionManager) .reader(itemReader()) .writer(itemWriter()) .readerIsTransactionalQueue() 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 - + ---- 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..3bb0748bc2 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 @@ -27,7 +27,7 @@ The following example shows a listener applied at the chunk level in Java: @Bean public Step step1(JobRepository jobRepository, PlatformTransactionManager transactionManager) { return new StepBuilder("step1", jobRepository) - .chunk(10, transactionManager) + .chunk(10).transactionManager(transactionManager) .reader(reader()) .writer(writer()) .listener(chunkListener()) @@ -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: diff --git a/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/registering-item-streams.adoc b/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/registering-item-streams.adoc index 643087d7d6..ae2c2118ac 100644 --- a/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/registering-item-streams.adoc +++ b/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/registering-item-streams.adoc @@ -25,7 +25,7 @@ The following example shows how to register a `stream` on a `step` in Java: @Bean public Step step1(JobRepository jobRepository, PlatformTransactionManager transactionManager) { return new StepBuilder("step1", jobRepository) - .chunk(2, transactionManager) + .chunk(2).transactionManager(transactionManager) .reader(itemReader()) .writer(compositeItemWriter()) .stream(fileItemWriter1()) diff --git a/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/restart.adoc b/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/restart.adoc index 20e80bd72d..b59e949372 100644 --- a/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/restart.adoc +++ b/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/restart.adoc @@ -28,7 +28,7 @@ The following code fragment shows an example of a start limit configuration in J @Bean public Step step1(JobRepository jobRepository, PlatformTransactionManager transactionManager) { return new StepBuilder("step1", jobRepository) - .chunk(10, transactionManager) + .chunk(10).transactionManager(transactionManager) .reader(itemReader()) .writer(itemWriter()) .startLimit(1) @@ -80,7 +80,7 @@ The following code fragment shows how to define a restartable job in Java: @Bean public Step step1(JobRepository jobRepository, PlatformTransactionManager transactionManager) { return new StepBuilder("step1", jobRepository) - .chunk(10, transactionManager) + .chunk(10).transactionManager(transactionManager) .reader(itemReader()) .writer(itemWriter()) .allowStartIfComplete(true) @@ -132,7 +132,7 @@ public Job footballJob(JobRepository jobRepository, Step playerLoad, Step gameLo @Bean public Step playerLoad(JobRepository jobRepository, PlatformTransactionManager transactionManager) { return new StepBuilder("playerLoad", jobRepository) - .chunk(10, transactionManager) + .chunk(10).transactionManager(transactionManager) .reader(playerFileItemReader()) .writer(playerWriter()) .build(); @@ -142,7 +142,7 @@ public Step playerLoad(JobRepository jobRepository, PlatformTransactionManager t public Step gameLoad(JobRepository jobRepository, PlatformTransactionManager transactionManager) { return new StepBuilder("gameLoad", jobRepository) .allowStartIfComplete(true) - .chunk(10, transactionManager) + .chunk(10).transactionManager(transactionManager) .reader(gameFileItemReader()) .writer(gameWriter()) .build(); @@ -152,7 +152,7 @@ public Step gameLoad(JobRepository jobRepository, PlatformTransactionManager tra public Step playerSummarization(JobRepository jobRepository, PlatformTransactionManager transactionManager) { return new StepBuilder("playerSummarization", jobRepository) .startLimit(2) - .chunk(10, transactionManager) + .chunk(10).transactionManager(transactionManager) .reader(playerSummarizationSource()) .writer(summaryWriter()) .build(); diff --git a/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/retry-logic.adoc b/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/retry-logic.adoc index c841e94a7c..e81fcc228d 100644 --- a/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/retry-logic.adoc +++ b/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/retry-logic.adoc @@ -20,7 +20,7 @@ In Java, retry should be configured as follows: @Bean public Step step1(JobRepository jobRepository, PlatformTransactionManager transactionManager) { return new StepBuilder("step1", jobRepository) - .chunk(2, transactionManager) + .chunk(2).transactionManager(transactionManager) .reader(itemReader()) .writer(itemWriter()) .faultTolerant() diff --git a/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/transaction-attributes.adoc b/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/transaction-attributes.adoc index fdd7fcc327..f48f0e3895 100644 --- a/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/transaction-attributes.adoc +++ b/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/transaction-attributes.adoc @@ -25,7 +25,7 @@ public Step step1(JobRepository jobRepository, PlatformTransactionManager transa attribute.setTimeout(30); return new StepBuilder("step1", jobRepository) - .chunk(2, transactionManager) + .chunk(2).transactionManager(transactionManager) .reader(itemReader()) .writer(itemWriter()) .transactionAttribute(attribute) 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 diff --git a/spring-batch-docs/modules/ROOT/pages/testing.adoc b/spring-batch-docs/modules/ROOT/pages/testing.adoc index f6b3d7e523..2c63b27d61 100644 --- a/spring-batch-docs/modules/ROOT/pages/testing.adoc +++ b/spring-batch-docs/modules/ROOT/pages/testing.adoc @@ -18,11 +18,11 @@ For the unit test to run a batch job, the framework must load the job's * `@SpringJUnitConfig` indicates that the class should use Spring's JUnit facilities * `@SpringBatchTest` injects Spring Batch test utilities (such as the -`JobLauncherTestUtils` and `JobRepositoryTestUtils`) in the test context +`JobOperatorTestUtils` and `JobRepositoryTestUtils`) in the test context NOTE: If the test context contains a single `Job` bean definition, this -bean will be autowired in `JobLauncherTestUtils`. Otherwise, the job -under test should be manually set on the `JobLauncherTestUtils`. +bean will be autowired in `JobOperatorTestUtils`. Otherwise, the job +under test should be manually set on the `JobOperatorTestUtils`. [tabs] @@ -67,9 +67,9 @@ and verifies the end result. Consider an example of a batch job that reads from the database and writes to a flat file. The test method begins by setting up the database with test data. It clears the `CUSTOMER` table and then inserts 10 new records. The test then launches the `Job` by using the -`launchJob()` method. The `launchJob()` method is provided by the `JobLauncherTestUtils` -class. The `JobLauncherTestUtils` class also provides the `launchJob(JobParameters)` -method, which lets the test give particular parameters. The `launchJob()` method +`srartJob()` method. The `srartJob()` method is provided by the `JobOperatorTestUtils` +class. The `JobOperatorTestUtils` class also provides the `startJob(JobParameters)` +method, which lets the test give particular parameters. The `srartJob()` method returns the `JobExecution` object, which is useful for asserting particular information about the `Job` run. In the following case, the test verifies that the `Job` ended with a status of `COMPLETED`. @@ -89,7 +89,7 @@ The following listing shows an example with JUnit 5 in Java configuration style: public class SkipSampleFunctionalTests { @Autowired - private JobLauncherTestUtils jobLauncherTestUtils; + private JobOperatorTestUtils jobOperatorTestUtils; private JdbcTemplate jdbcTemplate; @@ -100,14 +100,14 @@ public class SkipSampleFunctionalTests { @Test public void testJob(@Autowired Job job) throws Exception { - this.jobLauncherTestUtils.setJob(job); + this.jobOperatorTestUtils.setJob(job); this.jdbcTemplate.update("delete from CUSTOMER"); for (int i = 1; i <= 10; i++) { this.jdbcTemplate.update("insert into CUSTOMER values (?, 0, ?, 100000)", i, "customer" + i); } - JobExecution jobExecution = jobLauncherTestUtils.launchJob(); + JobExecution jobExecution = jobOperatorTestUtils.startJob(); Assert.assertEquals("COMPLETED", jobExecution.getExitStatus().getExitCode()); @@ -129,7 +129,7 @@ The following listing shows an example with JUnit 5 in XML configuration style: public class SkipSampleFunctionalTests { @Autowired - private JobLauncherTestUtils jobLauncherTestUtils; + private JobOperatorTestUtils jobOperatorTestUtils; private JdbcTemplate jdbcTemplate; @@ -140,14 +140,14 @@ public class SkipSampleFunctionalTests { @Test public void testJob(@Autowired Job job) throws Exception { - this.jobLauncherTestUtils.setJob(job); + this.jobOperatorTestUtils.setJob(job); this.jdbcTemplate.update("delete from CUSTOMER"); for (int i = 1; i <= 10; i++) { this.jdbcTemplate.update("insert into CUSTOMER values (?, 0, ?, 100000)", i, "customer" + i); } - JobExecution jobExecution = jobLauncherTestUtils.launchJob(); + JobExecution jobExecution = jobOperatorTestUtils.startJob(); Assert.assertEquals("COMPLETED", jobExecution.getExitStatus().getExitCode()); @@ -162,15 +162,15 @@ public class SkipSampleFunctionalTests { For complex batch jobs, test cases in the end-to-end testing approach may become unmanageable. It these cases, it may be more useful to have test cases to test individual -steps on their own. The `JobLauncherTestUtils` class contains a method called `launchStep`, +steps on their own. The `JobOperatorTestUtils` class contains a method called `launchStep`, which takes a step name and runs just that particular `Step`. This approach allows for more targeted tests letting the test set up data for only that step and to validate its -results directly. The following example shows how to use the `launchStep` method to load a +results directly. The following example shows how to use the `startStep` method to start a `Step` by name: [source, java] ---- -JobExecution jobExecution = jobLauncherTestUtils.launchStep("loadFileStep"); +JobExecution jobExecution = jobOperatorTestUtils.startStep("loadFileStep"); ---- @@ -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 @@ -303,7 +283,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) { diff --git a/spring-batch-docs/modules/ROOT/pages/tracing.adoc b/spring-batch-docs/modules/ROOT/pages/tracing.adoc deleted file mode 100644 index 113190feeb..0000000000 --- a/spring-batch-docs/modules/ROOT/pages/tracing.adoc +++ /dev/null @@ -1,9 +0,0 @@ -[[tracing]] -= Tracing - -As of version 5, Spring Batch provides tracing through Micrometer's `Observation` API. By default, tracing is enabled -when using `@EnableBatchProcessing`. Spring Batch will create a trace for each job execution and a span for each -step execution. - -If you do not use `EnableBatchProcessing`, you need to register a `BatchObservabilityBeanPostProcessor` in your -application context, which will automatically setup Micrometer's observability in your jobs and steps beans. diff --git a/spring-batch-docs/modules/ROOT/pages/whatsnew.adoc b/spring-batch-docs/modules/ROOT/pages/whatsnew.adoc index d472af579f..4b4991aaa0 100644 --- a/spring-batch-docs/modules/ROOT/pages/whatsnew.adoc +++ b/spring-batch-docs/modules/ROOT/pages/whatsnew.adoc @@ -1,175 +1,196 @@ [[whatsNew]] -= What's new in Spring Batch 5.2 += What's new in Spring Batch 6 -This section highlights the major changes in Spring Batch 5.2. For the complete list of changes, please refer to the https://github.com/spring-projects/spring-batch/releases[release notes]. +This section highlights the major changes in Spring Batch 6.0. For the complete list of changes, please refer to the https://github.com/spring-projects/spring-batch/releases[release notes]. -Spring Batch 5.2 includes the following features: +Spring Batch 6.0 includes the following features and improvements: * xref:whatsnew.adoc#dependencies-upgrade[Dependencies upgrade] -* xref:whatsnew.adoc#mongodb-job-repository-support[MongoDB job repository support] -* xref:whatsnew.adoc#new-resourceless-job-repository[New resourceless job repository] -* xref:whatsnew.adoc#composite-item-reader-implementation[Composite Item Reader implementation] -* xref:whatsnew.adoc#new-adapters-for-java-util-function-apis[New adapters for java.util.function APIs] -* xref:whatsnew.adoc#concurrent-steps-with-blocking-queue-item-reader-and-writer[Concurrent steps with blocking queue item reader and writer] -* xref:whatsnew.adoc#query-hints-support[Query hints support in JPA item readers] -* xref:whatsnew.adoc#data-class-support[Data class support in JDBC item readers] -* xref:whatsnew.adoc#configurable-line-separator-in-recursivecollectionlineaggregator[Configurable line separator in RecursiveCollectionLineAggregator] -* xref:whatsnew.adoc#job-registration-improvements[Job registration improvements] +* xref:whatsnew.adoc#batch-infrastrucutre-configuration-improvements[Batch infrastructure configuration improvements] +* xref:whatsnew.adoc#new-implementation-of-the-chunk-oriented-processing-model[New implementation of the chunk-oriented processing model] +* xref:whatsnew.adoc#new-concurrency-model[New concurrency model] +* xref:whatsnew.adoc#new-command-line-operator[New command line operator] +* xref:whatsnew.adoc#ability-to-recover-failed-job-executions[Ability to recover failed job executions] +* xref:whatsnew.adoc#ability-to-stop-all-kind-of-steps[Ability to stop all kinds of steps] +* xref:whatsnew.adoc#observability-with-jfr[Observability support with the Java Flight Recorder (JFR)] +* xref:whatsnew.adoc#deprecations-and-pruning[Deprecations and pruning] [[dependencies-upgrade]] == Dependencies upgrade -In this release, the Spring dependencies are upgraded to the following versions: +In this major 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 AMQP 3.2.0 -* Spring Kafka 3.3.0 -* Micrometer 1.14.0 +* Spring Framework 7.0 +* Spring Integration 7.0 +* Spring Data 4.0 +* Spring LDAP 4.0 +* Spring AMQP 4.0 +* Spring Kafka 4.0 +* Micrometer 1.16 -[[mongodb-job-repository-support]] -== MongoDB job repository support +[[batch-infrastrucutre-configuration-improvements]] +== Batch infrastructure configuration improvements -This release introduces the first NoSQL job repository implementation which is backed by MongoDB. -Similar to relational job repository implementations, Spring Batch comes with a script to create the -necessary collections in MongoDB in order to save and retrieve batch meta-data. +=== New annotations and classes for batch infrastructure configuration -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`: +Before v6, the `@EnableBatchProcessing` annotation was tied to a JDBC-based infrastructure. This is not the case anymore. Two new annotations have been introduced to configure the underlying job repository: `@EnableJdbcJobRepository` and `@EnableMongoJobRepository`. -``` -@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(); +Starting from v6, `@EnableBatchProcessing` allows you to configure common attributes for the batch infrastructure, while store-specific attributes can be specified with the new dedicated annotations. + +Here is an example of how to use these annotations: + +[source, java] +---- +@EnableBatchProcessing(taskExecutorRef = "batchTaskExecutor") +@EnableJdbcJobRepository(dataSourceRef = "batchDataSource", transactionManagerRef = "batchTransactionManager") +class MyJobConfiguration { + + @Bean + public Job job(JobRepository jobRepository) { + return new JobBuilder("job", jobRepository) + // job flow omitted + .build(); + } } -``` +---- -Once the MongoDB job repository defined, you can inject it in any job or step as a regular job repository. -You can find a complete example in the https://github.com/spring-projects/spring-batch/blob/main/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoDBJobRepositoryIntegrationTests.java[MongoDBJobRepositoryIntegrationTests]. +Similarly, the programmatic model based on `DefaultBatchConfiguration` has been updated by introducing two new configuration classes to define store-specific attributes: `JdbcDefaultBatchConfiguration` and `MongoDefaultBatchConfiguration`. +These classes can be used to configure specific attributes of each job repository as well as other batch infrastructure beans programmatically. -[[new-resourceless-job-repository]] -== New resourceless job repository +=== Resourceless batch infrastructure by default -In v5, the in-memory Map-based job repository implementation was removed for several reasons. -The only job repository implementation that was left in Spring Batch was the JDBC implementation, which requires a data source. -While this works well with in-memory databases like H2 or HSQLDB, requiring a data source was a strong constraint -for many users of our community who used to use the Map-based repository without any additional dependency. +The `DefaultBatchConfiguration` class has been updated to provide a "resourceless" batch infrastructure by default (based on the `ResourcelessJobRepository` implementation introduced in v5.2). This means that it no longer requires an in-memory database (like H2 or HSQLDB) for the job repository, which was previously necessary for batch metadata storage. -In this release, we introduce a `JobRepository` implementation that does not use or store batch meta-data in any form -(not even in-memory). It is a "NoOp" implementation that throws away batch meta-data and does not interact with any resource -(hence the name "resourceless job repository", which is named after the "resourceless transaction manager"). +Moreover, this change will improve the default performance of batch applications when the meta-data is not used, as the `ResourcelessJobRepository` does not require any database connections or transactions. -This implementation is intended for use-cases where restartability is not required and where the execution context is not involved -in any way (like sharing data between steps through the execution context, or partitioned steps where partitions meta-data is -shared between the manager and workers through the execution context, etc). +Finally, this change will help to reduce the memory footprint of batch applications, as the in-memory database is no longer required for metadata storage. -This implementation is suitable for one-time jobs executed in their own JVM. It works with transactional steps (configured with -a `DataSourceTransactionManager` for instance) as well as non-transactional steps (configured with a `ResourcelessTransactionManager`). -The implementation is not thread-safe and should not be used in any concurrent environment. +=== Batch infrastructure configuration simplification -[[composite-item-reader-implementation]] -== Composite Item Reader implementation +Before v6, the typical configuration of a non-trivial Spring Batch application was quite complex and required a lot of beans: `JobRepository`, `JobLauncher`, `JobExplorer`, `JobOperator`, `JobRegistry`, `JobRegistrySmartInitializingSingleton` and so on. This required a lot of configuration code, like for example the need to configure the same execution context serializer on both the `JobRepository` and `JobExplorer`. -Similar to the `CompositeItemProcessor` and `CompositeItemWriter`, we introduce a new `CompositeItemReader` implementation -that is designed to read data sequentially from several sources having the same format. This is useful when data is spread -over different resources and writing a custom reader is not an option. +In this release, several changes have been made to simplify the batch infrastructure configuration: -A `CompositeItemReader` works like other composite artifacts, by delegating the reading operation to regular item readers -in order. Here is a quick example showing a composite reader that reads persons data from a flat file then from a database table: +* The `JobRepository` now extends the `JobExplorer` interface, so there is no need to define a separate `JobExplorer` bean. +* The `JobOperator` now extends the `JobLauncher` interface, so there is no need to define a separate `JobLauncher` bean. +* The `JobRegistry` is now optional, and smart enough to register jobs automatically, so there is no need to define a separate `JobRegistrySmartInitializingSingleton` bean. +* The transaction manager is now optional, and a default `ResourcelessTransactionManager` is used if none is provided. -``` -@Bean -public FlatFileItemReader itemReader1() { - return new FlatFileItemReaderBuilder() - .name("personFileItemReader") - .resource(new FileSystemResource("persons.csv")) - .delimited() - .names("id", "name") - .targetType(Person.class) - .build(); -} +This reduces the number of beans required for a typical batch application and simplifies the configuration code. + +[[new-implementation-of-the-chunk-oriented-processing-model]] +== New implementation of the chunk-oriented processing model + +This is not a new feature, but rather a new implementation of the chunk-oriented processing model. This new implementation was introduced as an experimental addition in version 5.1, and is now available as stable in version 6.0. + +The new implementation is provided in the `ChunkOrientedStep` class, which is a replacement for the `ChunkOrientedTasklet` / `TaskletStep` classes. +Here is an example of how to define a `ChunkOrientedStep` by using its builder: + +[source, java] +---- @Bean -public JdbcCursorItemReader itemReader2() { - String sql = "select * from persons"; - return new JdbcCursorItemReaderBuilder() - .name("personTableItemReader") - .dataSource(dataSource()) - .sql(sql) - .beanRowMapper(Person.class) - .build(); +public Step chunkOrientedStep(JobRepository jobRepository, JdbcTransactionManager transactionManager, + ItemReader itemReader, ItemProcessor itemProcessor, ItemWriter itemWriter) { + int chunkSize = 100; + return new ChunkOrientedStepBuilder(jobRepository, transactionManager, chunkSize) + .reader(itemReader) + .processor(itemProcessor) + .writer(itemWriter) + .build(); } +---- + +Moreover, fault-tolerance features were adapted as follows: + +- The retry feature is now based on the retry functionality introduced in https://docs.spring.io/spring/reference/7.0/core/resilience.html[Spring Framework 7], instead of the previous Spring Retry library +- The skip feature has been slightly adapted to the new implementation, which is now only based entirely on the `SkipPolicy` interface +Here is a quick example of how to use the retry and skip features with the new `ChunkOrientedStep`: + +[source, java] +---- @Bean -public CompositeItemReader itemReader() { - return new CompositeItemReader<>(Arrays.asList(itemReader1(), itemReader2())); +public Step faulTolerantChunkOrientedStep(JobRepository jobRepository, JdbcTransactionManager transactionManager, + ItemReader itemReader, ItemProcessor itemProcessor, ItemWriter itemWriter) { + + // retry policy configuration + int maxAttempts = 10; + var retrybaleExceptions = Set.of(TransientException.class); + RetryPolicy retryPolicy = RetryPolicy.builder() + .maxAttempts(maxAttempts) + .includes(retrybaleExceptions) + .build(); + + // skip policy configuration + int skipLimit = 50; + var skippableExceptions = Set.of(FlatFileParseException.class); + SkipPolicy skipPolicy = new LimitCheckingExceptionHierarchySkipPolicy(skippableExceptions, skipLimit); + + // step configuration + int chunkSize = 100; + return new ChunkOrientedStepBuilder(jobRepository, transactionManager, chunkSize) + .reader(itemReader) + .processor(itemProcessor) + .writer(itemWriter) + .faultTolerant() + .retryPolicy(retryPolicy) + .skipPolicy(skipPolicy) + .build(); } -``` +---- + +Please refer to the https://github.com/spring-projects/spring-batch/wiki/Spring-Batch-6.0-Migration-Guide[migration guide] for more details on how to migrate from the previous implementation to the new one. + +[[new-concurrency-model]] +== New concurrency model + +Prior to this release, the concurrency model based on the "parallel iteration" concept required a lot of state synchronization at different levels and had several limitations related to throttling and backpressure leading to confusing transaction semantics and poor performance. + +This release revisits that model and comes with a new, simplified approach to concurrency based on the producer-consumer pattern. A concurrent chunk-oriented step now uses a bounded internal queue between the producer thread and consumer threads. Items are put in the queue as soon as they are ready to be processed, and consumer threads take items from the queue as soon as they are available for processing. Once a chunk is ready to be written, the producer thread pauses until the chunk is written, and then resumes producing items. + +This new model is more efficient, easier to understand and provides better performance for concurrent executions. -[[new-adapters-for-java-util-function-apis]] -== New adapters for java.util.function APIs +[[new-command-line-operator]] +== New command line operator -Similar to `FucntionItemProcessor` that adapts a `java.util.function.Function` to an item processor, this release -introduces several new adapters for other `java.util.function` interfaces like `Supplier`, `Consumer` and `Predicate`. +Spring Batch provided a `CommandLineJobRunner` since version 1. While this runner served its purpose well over the years, it started to show some limitations when it comes to extensibility and customisation. Many issues like static initialisation, non-standard way of handling options and parameters, lack of extensibility, etc have been reported. -The newly added adapters are: `SupplierItemReader`, `ConsumerItemWriter` and `PredicateFilteringItemProcessor`. -For more details about these new adapters, please refer to the https://github.com/spring-projects/spring-batch/tree/main/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/function[org.springframework.batch.item.function] package. +Moreover, all these issues made it impossible to reuse that runner in Spring Boot, which resulted in duplicate code in both projects as well behaviour divergence (like job parameters incrementer behaviour differences) that is confusing to many users. -[[concurrent-steps-with-blocking-queue-item-reader-and-writer]] -== Concurrent steps with blocking queue item reader and writer +This release introduces a modern version of `CommandLineJobRunner`, named `CommandLineJobOperator`, that allows you to operate batch jobs from the command line (start, stop, restart and so on) and that is customisable, extensible and updated to the new changes introduced in Spring Batch 6. -The https://en.wikipedia.org/wiki/Staged_event-driven_architecture[staged event-driven architecture] (SEDA) is a -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. +[[ability-to-recover-failed-job-executions]] +== Ability to recover failed job executions -The only missing piece here is how to read 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. +Prior to this release, if a job execution fails abruptly, it was not possible to recover it without a manual database update. This was error-prone and not consistent across different job repositories (as it required a few SQL statements for JDBC databases and some custom statements for NoSQL stores). -[[query-hints-support]] -== Query hints support in JPA item readers +This release introduces a new method named `recover` in the `JobOperator` interface that allows you to recover failed job executions consistently across all job repositories. -Up until version 5.1, the JPA cursor and paging item readers did not support query hints (like the fetch size, timeout, etc). -Users were required to provide a custom query provider in order to specify custom hints. +[[ability-to-stop-all-kind-of-steps]] +== Ability to stop all kinds of steps -In this release, JPA readers and their respective builders were updated to accept query hints when defining the JPA query to use. +As of v5.2, it is only possible to externally stop `Tasklet` steps through `JobOperator#stop`. +If a custom `Step` implementation wants to handle external stop signals, it just can't. -[[data-class-support]] -== Data class support in JDBC item readers +This release adds a new interface, named `StoppableStep`, that extends `Step` and which can be implemented by any step that is able to handle stop signals. -This release introduces a new method in the builders of JDBC cursor and paging item readers that allows users to specify a -`DataClassRowMapper` when the type of items is a data class (Java record or Kotlin data class). +[[observability-with-jfr]] +== Observability with the Java Flight Recorder (JFR) -The new method named `dataRowMapper(TargetType.class)` is similar to the `beanRowMapper(TargetType.class)` and is designed -to make the configuration of row mappers consistent between regular classes (Java beans) and data classes (Java records). +In addition to the existing Micrometer metrics, Spring Batch 6.0 introduces support for the Java Flight Recorder (JFR) to provide enhanced observability capabilities. -[[configurable-line-separator-in-recursivecollectionlineaggregator]] -== Configurable line separator in RecursiveCollectionLineAggregator +JFR is a powerful profiling and event collection framework built into the Java Virtual Machine (JVM). It allows you to capture detailed information about the runtime behavior of your applications with minimal performance overhead. -Up until now, the line separator property in `RecursiveCollectionLineAggregator` was set to the System's line separator value. -While it is possible to change the value through a System property, this configuration style is not consistent with other properties -of batch artifacts. +This release introduces several JFR events to monitor key aspects of a batch job execution, including job and step executions, item reads and writes, as well as transaction boundaries. -This release introduces a new setter in `RecursiveCollectionLineAggregator` that allows users to configure a custom value of -the line separator without having to use System properties. +[[deprecations-and-pruning]] +== Deprecations and pruning -[[job-registration-improvements]] -== Job registration improvements +As with any major release, some features have been deprecated or removed in Spring Batch 6.0. The following changes are worth noting: -In version 5.1, the default configuration of batch infrastructure beans was updated to automatically populate the job registry -by defining a `JobRegistryBeanPostProcessor` bean in the application context. After a recent change in Spring Framework -that changed the log level in `BeanPostProcessorChecker`, several warnings related to the `JobRegistryBeanPostProcessor` were -logged in a typical Spring Batch application. These warnings are due to the `JobRegistryBeanPostProcessor` having a dependency -to a `JobRegistry` bean, which is not recommended and might cause bean lifecycle issues. +* All deprecated APIs and features from previous versions have been removed +* Modular configuration through `@EnableBatchProcessing(modular = true)` has been deprecated +* Several APIs have been deprecated in this version, in order to simplify the core API and reduce its scope -These issues have been resolved in this release by changing the mechanism of populating the `JobRegistry` from using a `BeanPostProcessor` -to using a `SmartInitializingSingleton`. The `JobRegistryBeanPostProcessor` is now deprecated in favor of the newly added `JobRegistrySmartInitializingSingleton`. +Fore more details, please refer to the https://github.com/spring-projects/spring-batch/wiki/Spring-Batch-6.0-Migration-Guide[migration guide]. \ No newline at end of file diff --git a/spring-batch-docs/pom.xml b/spring-batch-docs/pom.xml index cb4ed9387d..8a9524165b 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 + 6.0.0-SNAPSHOT spring-batch-docs Spring Batch Docs @@ -23,11 +23,11 @@ true - @antora/atlas-extension@1.0.0-alpha.1 - @antora/collector-extension@1.0.0-alpha.3 - @asciidoctor/tabs@1.0.0-beta.3 - @springio/antora-extensions@1.10.0 - @springio/asciidoctor-extensions@1.0.0-alpha.9 + @antora/atlas-extension@1.0.0-alpha.2 + @antora/collector-extension@1.0.1 + @asciidoctor/tabs@1.0.0-beta.6 + @springio/antora-extensions@1.14.7 + @springio/asciidoctor-extensions@1.0.0-alpha.17 diff --git a/spring-batch-docs/src/main/models/Figures.ppt b/spring-batch-docs/src/main/models/Figures.ppt old mode 100644 new mode 100755 index 8afbf84456..a04b310c38 Binary files a/spring-batch-docs/src/main/models/Figures.ppt and b/spring-batch-docs/src/main/models/Figures.ppt differ diff --git a/spring-batch-infrastructure/pom.xml b/spring-batch-infrastructure/pom.xml index 2f924ba9d6..ca268e592d 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 + 6.0.0-SNAPSHOT spring-batch-infrastructure jar @@ -69,22 +69,6 @@ ${spring-framework.version} true - - org.neo4j - neo4j-ogm-core - ${neo4j-ogm-core.version} - true - - - com.fasterxml.jackson.datatype - jackson-datatype-jdk8 - - - com.fasterxml.jackson.datatype - jackson-datatype-jsr310 - - - org.springframework.kafka spring-kafka @@ -264,9 +248,9 @@ test - org.junit.jupiter - junit-jupiter-api - ${junit-jupiter.version} + org.junit.platform + junit-platform-launcher + ${junit-platform-launcher.version} test @@ -347,6 +331,90 @@ ${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 + + + org.testcontainers + kafka + ${testcontainers.version} + test + com.thoughtworks.xstream xstream @@ -451,6 +519,48 @@ ${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 + + + io.lettuce + lettuce-core + ${lettuce.version} + test + + + redis.clients + jedis + ${jedis.version} + test + + + com.redis + testcontainers-redis + ${testcontainers-redis.version} + test + 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/java/org/springframework/batch/item/Chunk.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/Chunk.java index 52895ca79d..42c0b6bb82 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/Chunk.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/Chunk.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. @@ -26,7 +26,7 @@ /** * Encapsulation of a list of items to be processed and possibly a list of failed items to - * be skipped. To mark an item as skipped clients should iterate over the chunk using the + * be skipped. To mark an item as skipped, clients should iterate over the chunk using the * {@link #iterator()} method, and if there is a failure call * {@link Chunk.ChunkIterator#remove()} on the iterator. The skipped items are then * available through the chunk. @@ -64,6 +64,7 @@ public Chunk(List items) { this(items, null); } + @Deprecated(since = "6.0", forRemoval = true) public Chunk(List items, List> skips) { super(); if (items != null) { @@ -109,6 +110,7 @@ public List getItems() { /** * @return a copy of the skips as an unmodifiable list */ + @Deprecated(since = "6.0", forRemoval = true) public List> getSkips() { return Collections.unmodifiableList(skips); } @@ -116,6 +118,7 @@ public List> getSkips() { /** * @return a copy of the anonymous errors as an unmodifiable list */ + @Deprecated(since = "6.0", forRemoval = true) public List getErrors() { return Collections.unmodifiableList(errors); } @@ -125,12 +128,13 @@ public List getErrors() { * {@link ChunkIterator#remove()}. * @param e the exception that caused the skip */ + @Deprecated(since = "6.0", forRemoval = true) public void skip(Exception e) { errors.add(e); } /** - * @return true if there are no items in the chunk + * @return {@code true} if there are no items in the chunk */ public boolean isEmpty() { return items.isEmpty(); @@ -152,6 +156,14 @@ public int size() { return items.size(); } + /** + * @return the number of skipped items + */ + @Deprecated(since = "6.0", forRemoval = true) + public int getSkipsSize() { + return skips.size(); + } + /** * Flag to indicate if the source data is exhausted. * @@ -163,6 +175,7 @@ public int size() { *

* @return true if there is no more data to process */ + @Deprecated(since = "6.0", forRemoval = true) public boolean isEnd() { return end; } @@ -171,6 +184,7 @@ public boolean isEnd() { * Set the flag to say that this chunk represents an end of stream (there is no more * data to process). */ + @Deprecated(since = "6.0", forRemoval = true) public void setEnd() { this.end = true; } @@ -180,6 +194,7 @@ public void setEnd() { * to it. * @return the busy flag */ + @Deprecated(since = "6.0", forRemoval = true) public boolean isBusy() { return busy; } @@ -189,6 +204,7 @@ public boolean isBusy() { * flag is reset to false. * @param busy the flag to set */ + @Deprecated(since = "6.0", forRemoval = true) public void setBusy(boolean busy) { this.busy = busy; } @@ -196,14 +212,17 @@ public void setBusy(boolean busy) { /** * Clear only the skips list. */ + @Deprecated(since = "6.0", forRemoval = true) public void clearSkips() { skips.clear(); } + @Deprecated(since = "6.0", forRemoval = true) public Object getUserData() { return userData; } + @Deprecated(since = "6.0", forRemoval = true) public void setUserData(Object userData) { this.userData = userData; } 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/main/java/org/springframework/batch/item/SkipWrapper.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/SkipWrapper.java index e997d38b37..fe94f012e4 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/SkipWrapper.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/SkipWrapper.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. @@ -23,8 +23,9 @@ * * @author Dave Syer * @author Mahmoud Ben Hassine - * + * @deprecated since 6.0 with no replacement. Scheduled for removal in 7.0. */ +@Deprecated(since = "6.0", forRemoval = true) public class SkipWrapper { final private Throwable exception; diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/adapter/AbstractMethodInvokingDelegator.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/adapter/AbstractMethodInvokingDelegator.java index c60d31ef34..4a0665ab12 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/adapter/AbstractMethodInvokingDelegator.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/adapter/AbstractMethodInvokingDelegator.java @@ -29,9 +29,9 @@ import org.springframework.util.StringUtils; /** - * Superclass for delegating classes which dynamically call a custom method of injected - * object. Provides convenient API for dynamic method invocation shielding subclasses from - * low-level details and exception handling. + * Superclass for delegating classes which dynamically call a custom method of an injected + * object. Provides a convenient API for dynamic method invocation shielding subclasses + * from low-level details and exception handling. *

* {@link Exception}s thrown by a successfully invoked delegate method are re-thrown * without wrapping. In case the delegate method throws a {@link Throwable} that doesn't @@ -164,7 +164,7 @@ private boolean targetClassDeclaresTargetMethod() { if (arguments[j] == null) { continue; } - if (!(ClassUtils.isAssignableValue(params[j], arguments[j]))) { + if (!ClassUtils.isAssignableValue(params[j], arguments[j])) { argumentsMatchParameters = false; } } @@ -205,7 +205,7 @@ public void setTargetMethod(String targetMethod) { * will be supplied at runtime. */ public void setArguments(Object[] arguments) { - this.arguments = arguments == null ? null : Arrays.asList(arguments).toArray(); + this.arguments = arguments == null ? null : arguments.clone(); } /** diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/adapter/PropertyExtractingDelegatingItemWriter.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/adapter/PropertyExtractingDelegatingItemWriter.java index 75b35c7dab..545d44e888 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/adapter/PropertyExtractingDelegatingItemWriter.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/adapter/PropertyExtractingDelegatingItemWriter.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,8 +16,6 @@ package org.springframework.batch.item.adapter; -import java.util.Arrays; - import org.springframework.batch.item.Chunk; import org.springframework.batch.item.ItemWriter; import org.springframework.beans.BeanWrapper; @@ -77,8 +75,7 @@ public void afterPropertiesSet() throws Exception { * e.g. address.city */ public void setFieldsUsedAsTargetMethodArguments(String[] fieldsUsedAsMethodArguments) { - this.fieldsUsedAsTargetMethodArguments = Arrays.asList(fieldsUsedAsMethodArguments) - .toArray(new String[fieldsUsedAsMethodArguments.length]); + this.fieldsUsedAsTargetMethodArguments = fieldsUsedAsMethodArguments.clone(); } } 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/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..7018ee6244 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. @@ -16,6 +16,10 @@ package org.springframework.batch.item.data; +import static java.util.stream.Collectors.toList; + +import java.util.List; + import org.bson.Document; import org.bson.types.ObjectId; @@ -92,24 +96,13 @@ public enum Mode { private Mode mode = Mode.UPSERT; + private List primaryKeys = List.of(ID_KEY); + public MongoItemWriter() { super(); 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}. @@ -163,6 +156,27 @@ public String getCollection() { return collection; } + /** + * Set the primary keys to associate with the document being written. These fields + * should uniquely identify a single object. + * @param primaryKeys The primary keys to use. + * @since 5.2.3 + */ + public void setPrimaryKeys(List primaryKeys) { + Assert.notEmpty(primaryKeys, "The primaryKeys list must have one or more keys."); + + this.primaryKeys = primaryKeys; + } + + /** + * Get the list of primary keys associated with the document being written. + * @return the list of primary keys + * @since 5.2.3 + */ + public List getPrimaryKeys() { + return primaryKeys; + } + /** * If a transaction is active, buffer items to be written just before commit. * Otherwise write items using the provided template. @@ -213,9 +227,14 @@ private void remove(Chunk chunk) { for (Object item : chunk) { Document document = new Document(); mongoConverter.write(item, document); - Object objectId = document.get(ID_KEY); - if (objectId != null) { - Query query = new Query().addCriteria(Criteria.where(ID_KEY).is(objectId)); + + List criteriaList = primaryKeys.stream() + .filter(document::containsKey) + .map(key -> Criteria.where(key).is(document.get(key))) + .collect(toList()); + if (!criteriaList.isEmpty()) { + Query query = new Query(); + criteriaList.forEach(query::addCriteria); bulkOperations.remove(query); } } @@ -229,8 +248,21 @@ private void upsert(Chunk chunk) { for (Object item : chunk) { Document document = new Document(); mongoConverter.write(item, document); - Object objectId = document.get(ID_KEY) != null ? document.get(ID_KEY) : new ObjectId(); - Query query = new Query().addCriteria(Criteria.where(ID_KEY).is(objectId)); + + Query query = new Query(); + List criteriaList = primaryKeys.stream() + .filter(document::containsKey) + .map(key -> Criteria.where(key).is(document.get(key))) + .collect(toList()); + + if (criteriaList.isEmpty()) { + Object objectId = document.get(ID_KEY) != null ? document.get(ID_KEY) : new ObjectId(); + query.addCriteria(Criteria.where(ID_KEY).is(objectId)); + } + else { + criteriaList.forEach(query::addCriteria); + } + bulkOperations.replaceOne(query, document, upsert); } bulkOperations.execute(); 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/RepositoryItemReader.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/RepositoryItemReader.java index 98ce9941f3..2e12b299c0 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/RepositoryItemReader.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/RepositoryItemReader.java @@ -44,7 +44,7 @@ /** *

* A {@link org.springframework.batch.item.ItemReader} that reads records utilizing a - * {@link org.springframework.data.repository.PagingAndSortingRepository}. + * {@link PagingAndSortingRepository}. *

* *

@@ -54,9 +54,8 @@ *

* *

- * The reader must be configured with a - * {@link org.springframework.data.repository.PagingAndSortingRepository}, a - * {@link org.springframework.data.domain.Sort}, and a pageSize greater than 0. + * The reader must be configured with a {@link PagingAndSortingRepository}, a + * {@link Sort}, and a pageSize greater than 0. *

* *

@@ -134,8 +133,7 @@ public void setPageSize(int pageSize) { } /** - * The {@link org.springframework.data.repository.PagingAndSortingRepository} - * implementation used to read input from. + * The {@link PagingAndSortingRepository} implementation used to read input from. * @param repository underlying repository for input to be read from. */ public void setRepository(PagingAndSortingRepository repository) { 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..27f6f6b080 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. @@ -16,6 +16,8 @@ package org.springframework.batch.item.data.builder; +import java.util.List; + import org.springframework.batch.item.data.MongoItemWriter; import org.springframework.batch.item.data.MongoItemWriter.Mode; import org.springframework.data.mongodb.core.MongoOperations; @@ -37,22 +39,7 @@ 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; - } + private List primaryKeys = List.of(); /** * Set the operating {@link Mode} to be applied by this writer. Defaults to @@ -93,6 +80,32 @@ public MongoItemWriterBuilder collection(String collection) { return this; } + /** + * Set the primary keys to associate with the document being written. These fields + * should uniquely identify a single object. + * @param primaryKeys The keys to use. + * @see MongoItemWriter#setPrimaryKeys(List) + * @since 5.2.3 + */ + public MongoItemWriterBuilder primaryKeys(List primaryKeys) { + this.primaryKeys = List.copyOf(primaryKeys); + + return this; + } + + /** + * Set the primary keys to associate with the document being written. These fields + * should uniquely identify a single object. + * @param primaryKeys The keys to use. + * @see MongoItemWriter#setPrimaryKeys(List) + * @since 5.2.3 + */ + public MongoItemWriterBuilder primaryKeys(String... primaryKeys) { + this.primaryKeys = List.of(primaryKeys); + + return this; + } + /** * Validates and builds a {@link MongoItemWriter}. * @return a {@link MongoItemWriter} @@ -105,6 +118,10 @@ public MongoItemWriter build() { writer.setMode(this.mode); writer.setCollection(this.collection); + if (!this.primaryKeys.isEmpty()) { + writer.setPrimaryKeys(this.primaryKeys); + } + return writer; } 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/AbstractCursorItemReader.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/AbstractCursorItemReader.java index 92e23beb83..534339748d 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/AbstractCursorItemReader.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/AbstractCursorItemReader.java @@ -400,8 +400,8 @@ protected void doClose() throws Exception { this.con.setAutoCommit(this.initialConnectionAutoCommit); } - if (useSharedExtendedConnection && dataSource instanceof ExtendedConnectionDataSourceProxy) { - ((ExtendedConnectionDataSourceProxy) dataSource).stopCloseSuppression(this.con); + if (useSharedExtendedConnection && dataSource instanceof ExtendedConnectionDataSourceProxy dataSourceProxy) { + dataSourceProxy.stopCloseSuppression(this.con); if (!TransactionSynchronizationManager.isActualTransactionActive()) { DataSourceUtils.releaseConnection(con, dataSource); } diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/builder/JdbcCursorItemReaderBuilder.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/builder/JdbcCursorItemReaderBuilder.java index a4014536d5..48bb8b91b8 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/builder/JdbcCursorItemReaderBuilder.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/builder/JdbcCursorItemReaderBuilder.java @@ -164,7 +164,7 @@ public JdbcCursorItemReaderBuilder maxRows(int maxRows) { } /** - * The time in milliseconds for the query to timeout + * The time in seconds for the query to timeout * @param queryTimeout timeout * @return this instance for method chaining * @see JdbcCursorItemReader#setQueryTimeout(int) diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/AbstractSqlPagingQueryProvider.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/AbstractSqlPagingQueryProvider.java index dc78066fc5..b60cdfadf9 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/AbstractSqlPagingQueryProvider.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/AbstractSqlPagingQueryProvider.java @@ -146,7 +146,7 @@ public void setSortKeys(Map sortKeys) { /** * A Map<String, Boolean> of sort columns as the key and boolean for * ascending/descending (ascending = true). - * @return sortKey key to use to sort and limit page content + * @return keys to use to sort and limit page content */ @Override public Map getSortKeys() { @@ -214,7 +214,7 @@ public void init(DataSource dataSource) throws Exception { /** * Method generating the query string to be used for retrieving the pages following - * the first page. This method must be implemented in sub classes. + * the first page. This method must be implemented in subclasses. * @param pageSize number of rows to read per page * @return query string */ 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..8bafc6906f 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,13 +44,8 @@ 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(); + return "FETCH FIRST " + pageSize + " ROWS ONLY"; } } 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..ec640e0088 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 "FETCH FIRST " + pageSize + " ROWS ONLY"; } } diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/H2PagingQueryProvider.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/H2PagingQueryProvider.java index 5f358b7cd2..3de7e01f9a 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/H2PagingQueryProvider.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/H2PagingQueryProvider.java @@ -38,7 +38,7 @@ public String generateRemainingPagesQuery(int pageSize) { } private String buildLimitClause(int pageSize) { - return new StringBuilder().append("FETCH NEXT ").append(pageSize).append(" ROWS ONLY").toString(); + return "FETCH NEXT " + pageSize + " ROWS ONLY"; } } diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/HanaPagingQueryProvider.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/HanaPagingQueryProvider.java index 54bd06d23d..c74298b300 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/HanaPagingQueryProvider.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/HanaPagingQueryProvider.java @@ -44,7 +44,7 @@ public String generateRemainingPagesQuery(int pageSize) { } private String buildLimitClause(int pageSize) { - return new StringBuilder().append("LIMIT ").append(pageSize).toString(); + return "LIMIT " + pageSize; } } diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/HsqlPagingQueryProvider.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/HsqlPagingQueryProvider.java index 49e3741a4f..94d17b3257 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/HsqlPagingQueryProvider.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/HsqlPagingQueryProvider.java @@ -46,7 +46,7 @@ public String generateRemainingPagesQuery(int pageSize) { } private String buildTopClause(int pageSize) { - return new StringBuilder().append("TOP ").append(pageSize).toString(); + return "TOP " + pageSize; } } diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/MariaDBPagingQueryProvider.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/MariaDBPagingQueryProvider.java index 25f47b1506..cdbf4eb9d2 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/MariaDBPagingQueryProvider.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/MariaDBPagingQueryProvider.java @@ -44,7 +44,7 @@ public String generateRemainingPagesQuery(int pageSize) { } private String buildLimitClause(int pageSize) { - return new StringBuilder().append("LIMIT ").append(pageSize).toString(); + return "LIMIT " + pageSize; } } diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/MySqlPagingQueryProvider.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/MySqlPagingQueryProvider.java index ad2eba7cf4..0b8448d4ca 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/MySqlPagingQueryProvider.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/MySqlPagingQueryProvider.java @@ -45,7 +45,7 @@ public String generateRemainingPagesQuery(int pageSize) { } private String buildLimitClause(int pageSize) { - return new StringBuilder().append("LIMIT ").append(pageSize).toString(); + return "LIMIT " + pageSize; } } diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/OraclePagingQueryProvider.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/OraclePagingQueryProvider.java index 9958727437..5fd902821d 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/OraclePagingQueryProvider.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/OraclePagingQueryProvider.java @@ -38,7 +38,7 @@ public String generateRemainingPagesQuery(int pageSize) { } private String buildRowNumClause(int pageSize) { - return new StringBuilder().append("ROWNUM <= ").append(pageSize).toString(); + return "ROWNUM <= " + pageSize; } } diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/PostgresPagingQueryProvider.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/PostgresPagingQueryProvider.java index 4b65d2e3d9..fb3406180f 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/PostgresPagingQueryProvider.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/PostgresPagingQueryProvider.java @@ -50,7 +50,7 @@ public String generateRemainingPagesQuery(int pageSize) { } private String buildLimitClause(int pageSize) { - return new StringBuilder().append("LIMIT ").append(pageSize).toString(); + return "LIMIT " + pageSize; } } 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/SqlServerPagingQueryProvider.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SqlServerPagingQueryProvider.java index 51b879ed2a..5d0989f73d 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,13 +45,8 @@ 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(); + return "TOP " + pageSize; } } 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 1f75726aaf..0000000000 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SqlWindowingPagingQueryProvider.java +++ /dev/null @@ -1,128 +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.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 - */ -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/item/database/support/SqlitePagingQueryProvider.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SqlitePagingQueryProvider.java index cc44ef6a4a..01406388a6 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SqlitePagingQueryProvider.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SqlitePagingQueryProvider.java @@ -45,7 +45,7 @@ public String generateRemainingPagesQuery(int pageSize) { } private String buildLimitClause(int pageSize) { - return new StringBuilder().append("LIMIT ").append(pageSize).toString(); + return "LIMIT " + pageSize; } } 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..26261d1246 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,13 +45,8 @@ public String generateRemainingPagesQuery(int pageSize) { } } - @Override - protected Object getSubQueryAlias() { - return ""; - } - private String buildTopClause(int pageSize) { - return new StringBuilder().append("TOP ").append(pageSize).toString(); + return "TOP " + pageSize; } } diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/MultiResourceItemReader.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/MultiResourceItemReader.java index 341f4222eb..63ebf7617e 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/MultiResourceItemReader.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/MultiResourceItemReader.java @@ -141,8 +141,8 @@ private T readNextItem() throws Exception { private T readFromDelegate() throws Exception { T item = delegate.read(); - if (item instanceof ResourceAware) { - ((ResourceAware) item).setResource(resources[currentResource]); + if (item instanceof ResourceAware resourceAware) { + resourceAware.setResource(resources[currentResource]); } return item; } @@ -222,7 +222,7 @@ public void setDelegate(ResourceAwareItemReaderItemStream delegate) } /** - * Set the boolean indicating whether or not state should be saved in the provided + * Set the boolean indicating whether state should be saved in the provided * {@link ExecutionContext} during the {@link ItemStream} call to update. * @param saveState true to update ExecutionContext. False do not update * ExecutionContext. @@ -244,7 +244,7 @@ public void setComparator(Comparator comparator) { */ public void setResources(Resource[] resources) { Assert.notNull(resources, "The resources must not be null"); - this.resources = Arrays.asList(resources).toArray(new Resource[resources.length]); + this.resources = resources.clone(); } } 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..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-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,13 +34,15 @@ * {@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 - * {@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 * @author Mahmoud Ben Hassine + * @author Henning Pöttker */ public class MultiResourceItemWriter extends AbstractItemStreamItemWriter { @@ -70,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/main/java/org/springframework/batch/item/file/SimpleBinaryBufferedReaderFactory.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/SimpleBinaryBufferedReaderFactory.java index 6b8fede984..fe6f2e3f7c 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/SimpleBinaryBufferedReaderFactory.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/SimpleBinaryBufferedReaderFactory.java @@ -19,7 +19,6 @@ import java.io.IOException; import java.io.InputStreamReader; import java.io.Reader; -import java.io.UnsupportedEncodingException; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; @@ -53,7 +52,7 @@ public void setLineEnding(String lineEnding) { } @Override - public BufferedReader create(Resource resource, String encoding) throws UnsupportedEncodingException, IOException { + public BufferedReader create(Resource resource, String encoding) throws IOException { return new BinaryBufferedReader(new InputStreamReader(resource.getInputStream(), encoding), lineEnding); } diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/builder/FlatFileItemReaderBuilder.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/builder/FlatFileItemReaderBuilder.java index ab8601b18c..e52d4dbde9 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/builder/FlatFileItemReaderBuilder.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/builder/FlatFileItemReaderBuilder.java @@ -58,6 +58,8 @@ * @author Glenn Renfro * @author Mahmoud Ben Hassine * @author Drummond Dawson + * @author Patrick Baumgartner + * @author François Martin * @since 4.0 * @see FlatFileItemReader */ @@ -459,6 +461,9 @@ else if (this.delimitedBuilder != null) { throw new IllegalStateException("No LineTokenizer implementation was provided."); } + Assert.state(this.targetType == null || this.fieldSetMapper == null, + "Either a TargetType or FieldSetMapper can be set, can't be both."); + if (this.targetType != null || StringUtils.hasText(this.prototypeBeanName)) { if (this.targetType != null && this.targetType.isRecord()) { RecordFieldSetMapper mapper = new RecordFieldSetMapper<>(this.targetType); 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) */ diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/mapping/BeanWrapperFieldSetMapper.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/mapping/BeanWrapperFieldSetMapper.java index 1364f71445..81bfa97739 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/mapping/BeanWrapperFieldSetMapper.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/mapping/BeanWrapperFieldSetMapper.java @@ -291,7 +291,7 @@ private String findPropertyName(Object bean, String key) { // looking for a match. if (index > 0) { prefix = key.substring(0, index); - suffix = key.substring(index + 1, key.length()); + suffix = key.substring(index + 1); String nestedName = findPropertyName(bean, prefix); if (nestedName == null) { return null; @@ -424,9 +424,7 @@ public boolean equals(Object obj) { } else if (!cls.equals(other.cls)) return false; - if (distance != other.distance) - return false; - return true; + return distance == other.distance; } } diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/mapping/RecordFieldSetMapper.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/mapping/RecordFieldSetMapper.java index a86079cc0f..0eb449dab4 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/mapping/RecordFieldSetMapper.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/mapping/RecordFieldSetMapper.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2024 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. @@ -31,6 +31,7 @@ * * @param type of mapped items * @author Mahmoud Ben Hassine + * @author Seungyong Hong * @since 4.3 */ public class RecordFieldSetMapper implements FieldSetMapper { @@ -63,6 +64,10 @@ public RecordFieldSetMapper(Class targetType, ConversionService conversionSer this.constructorParameterNames = BeanUtils.getParameterNames(this.mappedConstructor); this.constructorParameterTypes = this.mappedConstructor.getParameterTypes(); } + else { + this.constructorParameterNames = new String[0]; + this.constructorParameterTypes = new Class[0]; + } } @Override diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/transform/AbstractLineTokenizer.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/transform/AbstractLineTokenizer.java index 5f53e851d7..b4fa1572fe 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/transform/AbstractLineTokenizer.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/transform/AbstractLineTokenizer.java @@ -96,7 +96,7 @@ public void setNames(String... names) { } /** - * @return true if column names have been specified + * @return {@code true} if column names have been specified * @see #setNames(String[]) */ public boolean hasNames() { @@ -121,7 +121,7 @@ public FieldSet tokenize(@Nullable String line) { List tokens = new ArrayList<>(doTokenize(line)); // if names are set and strict flag is false - if ((names.length != 0) && (!strict)) { + if (names.length != 0 && !strict) { adjustTokenCountIfNecessary(tokens); } diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/transform/BeanWrapperFieldExtractor.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/transform/BeanWrapperFieldExtractor.java index 083839d2ca..a7986901fa 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/transform/BeanWrapperFieldExtractor.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/transform/BeanWrapperFieldExtractor.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. @@ -17,7 +17,6 @@ package org.springframework.batch.item.file.transform; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import org.springframework.beans.BeanWrapper; @@ -41,12 +40,9 @@ public class BeanWrapperFieldExtractor implements FieldExtractor, Initiali */ public void setNames(String[] names) { Assert.notNull(names, "Names must be non-null"); - this.names = Arrays.asList(names).toArray(new String[names.length]); + this.names = names.clone(); } - /** - * @see org.springframework.batch.item.file.transform.FieldExtractor#extract(java.lang.Object) - */ @Override public Object[] extract(T item) { List values = new ArrayList<>(); diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/transform/ConversionException.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/transform/ConversionException.java index 1f59b0174d..f7fcf437eb 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/transform/ConversionException.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/transform/ConversionException.java @@ -20,6 +20,7 @@ * @author Mahmoud Ben Hassine * */ +@SuppressWarnings("unused") // FIXME no usage - should it be deprecated for removal? public class ConversionException extends RuntimeException { /** diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/transform/DefaultFieldSet.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/transform/DefaultFieldSet.java index 540a8236aa..c1c1d1b489 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/transform/DefaultFieldSet.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/transform/DefaultFieldSet.java @@ -67,9 +67,9 @@ public class DefaultFieldSet implements FieldSet { */ public final void setNumberFormat(NumberFormat numberFormat) { this.numberFormat = numberFormat; - if (numberFormat instanceof DecimalFormat) { - grouping = String.valueOf(((DecimalFormat) numberFormat).getDecimalFormatSymbols().getGroupingSeparator()); - decimal = String.valueOf(((DecimalFormat) numberFormat).getDecimalFormatSymbols().getDecimalSeparator()); + if (numberFormat instanceof DecimalFormat decimalFormat) { + grouping = String.valueOf(decimalFormat.getDecimalFormatSymbols().getGroupingSeparator()); + decimal = String.valueOf(decimalFormat.getDecimalFormatSymbols().getDecimalSeparator()); } } @@ -533,8 +533,8 @@ private Date parseDate(String readAndTrim, DateFormat dateFormat) { } catch (ParseException e) { String pattern; - if (dateFormat instanceof SimpleDateFormat) { - pattern = ((SimpleDateFormat) dateFormat).toPattern(); + if (dateFormat instanceof SimpleDateFormat simpleDateFormat) { + pattern = simpleDateFormat.toPattern(); } else { pattern = dateFormat.toString(); diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/transform/DelimitedLineTokenizer.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/transform/DelimitedLineTokenizer.java index 7fdf1fe7e6..bb14e462dd 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/transform/DelimitedLineTokenizer.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/transform/DelimitedLineTokenizer.java @@ -168,7 +168,7 @@ else if (!isEnd) { fieldCount++; - if (isEnd && (isDelimiter)) { + if (isEnd && isDelimiter) { if (includedFields == null || includedFields.contains(fieldCount)) { tokens.add(""); } diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/transform/PassThroughFieldExtractor.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/transform/PassThroughFieldExtractor.java index 98630c0216..f3b683a333 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/transform/PassThroughFieldExtractor.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/transform/PassThroughFieldExtractor.java @@ -59,8 +59,8 @@ public Object[] extract(T item) { return ((Map) item).values().toArray(); } - if (item instanceof FieldSet) { - return ((FieldSet) item).getValues(); + if (item instanceof FieldSet fieldSet) { + return fieldSet.getValues(); } return new Object[] { item }; diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/transform/RangeArrayPropertyEditor.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/transform/RangeArrayPropertyEditor.java index 67afcb946c..e382bfca8c 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/transform/RangeArrayPropertyEditor.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/transform/RangeArrayPropertyEditor.java @@ -76,12 +76,12 @@ public void setAsText(String text) throws IllegalArgumentException { int min; int max; - if ((range.length == 1) && (StringUtils.hasText(range[0]))) { + if (range.length == 1 && StringUtils.hasText(range[0])) { min = Integer.parseInt(range[0].trim()); // correct max value will be assigned later ranges[i] = new Range(min); } - else if ((range.length == 2) && (StringUtils.hasText(range[0])) && (StringUtils.hasText(range[1]))) { + else if (range.length == 2 && StringUtils.hasText(range[0]) && StringUtils.hasText(range[1])) { min = Integer.parseInt(range[0].trim()); max = Integer.parseInt(range[1].trim()); ranges[i] = new Range(min, max); diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/jms/JmsMethodArgumentsKeyGenerator.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/jms/JmsMethodArgumentsKeyGenerator.java index 7cd35de364..45669fab80 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/jms/JmsMethodArgumentsKeyGenerator.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/jms/JmsMethodArgumentsKeyGenerator.java @@ -43,9 +43,9 @@ public class JmsMethodArgumentsKeyGenerator implements MethodArgumentsKeyGenerat @Override public Object getKey(Object[] items) { for (Object item : items) { - if (item instanceof Message) { + if (item instanceof Message message) { try { - return ((Message) item).getJMSMessageID(); + return message.getJMSMessageID(); } catch (JMSException e) { throw new UnexpectedInputException("Could not extract message ID", e); diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/jms/JmsMethodInvocationRecoverer.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/jms/JmsMethodInvocationRecoverer.java index 2afb4399eb..dd9dcd6b3e 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/jms/JmsMethodInvocationRecoverer.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/jms/JmsMethodInvocationRecoverer.java @@ -43,11 +43,10 @@ public void setJmsTemplate(JmsOperations jmsTemplate) { } /** - * Send one message per item in the arguments list using the default destination of - * the jms template. If the recovery is successful {@code null} is returned. + * Send one message per item in the argument list using the default destination of the + * jms template. If the recovery is successful {@code null} is returned. * - * @see org.springframework.retry.interceptor.MethodInvocationRecoverer#recover(Object[], - * Throwable) + * @see MethodInvocationRecoverer#recover(Object[], Throwable) */ @Override @Nullable diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/jms/JmsNewMethodArgumentsIdentifier.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/jms/JmsNewMethodArgumentsIdentifier.java index d5090673a1..6385e6d17f 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/jms/JmsNewMethodArgumentsIdentifier.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/jms/JmsNewMethodArgumentsIdentifier.java @@ -42,9 +42,9 @@ public class JmsNewMethodArgumentsIdentifier implements NewMethodArgumentsIde public boolean isNew(Object[] args) { for (Object item : args) { - if (item instanceof Message) { + if (item instanceof Message message) { try { - return !((Message) item).getJMSRedelivered(); + return !message.getJMSRedelivered(); } catch (JMSException e) { throw new UnexpectedInputException("Could not extract message ID", e); diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/ldif/LdifReader.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/ldif/LdifReader.java index d80508b0d2..e619376332 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/ldif/LdifReader.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/ldif/LdifReader.java @@ -1,5 +1,5 @@ /* - * Copyright 2005-2023 the original author or authors. + * Copyright 2005-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,8 +16,8 @@ package org.springframework.batch.item.ldif; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.batch.item.file.ResourceAwareItemReaderItemStream; import org.springframework.batch.item.support.AbstractItemCountingItemStreamItemReader; import org.springframework.beans.factory.InitializingBean; @@ -66,7 +66,7 @@ public class LdifReader extends AbstractItemCountingItemStreamItemReader implements ResourceAwareItemReaderItemStream, InitializingBean { - private static final Logger LOG = LoggerFactory.getLogger(LdifReader.class); + private static final Log LOG = LogFactory.getLog(LdifReader.class); private Resource resource; diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/ldif/MappingLdifReader.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/ldif/MappingLdifReader.java index fb89008c3c..8ed127422b 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/ldif/MappingLdifReader.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/ldif/MappingLdifReader.java @@ -1,5 +1,5 @@ /* - * Copyright 2005-2023 the original author or authors. + * Copyright 2005-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,8 @@ */ package org.springframework.batch.item.ldif; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.batch.item.file.ResourceAwareItemReaderItemStream; import org.springframework.batch.item.support.AbstractItemCountingItemStreamItemReader; import org.springframework.beans.factory.InitializingBean; @@ -57,7 +57,7 @@ public class MappingLdifReader extends AbstractItemCountingItemStreamItemReader implements ResourceAwareItemReaderItemStream, InitializingBean { - private static final Logger LOG = LoggerFactory.getLogger(MappingLdifReader.class); + private static final Log LOG = LogFactory.getLog(MappingLdifReader.class); private Resource resource; 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..a726680f99 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); } } @@ -404,14 +404,12 @@ protected class OutputState { * @throws IOException If unable to get the offset position */ public long position() throws IOException { - long pos = 0; - if (fileChannel == null) { return 0; } outputBufferedWriter.flush(); - pos = fileChannel.position(); + long pos = fileChannel.position(); if (transactional) { pos += ((TransactionAwareBufferedWriter) outputBufferedWriter).getBufferSize(); } diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/support/AbstractItemCountingItemStreamItemReader.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/support/AbstractItemCountingItemStreamItemReader.java index c25fc437ce..d289404c47 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/support/AbstractItemCountingItemStreamItemReader.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/support/AbstractItemCountingItemStreamItemReader.java @@ -91,8 +91,8 @@ public T read() throws Exception { } currentItemCount++; T item = doRead(); - if (item instanceof ItemCountAware) { - ((ItemCountAware) item).setItemCount(currentItemCount); + if (item instanceof ItemCountAware itemCountAware) { + itemCountAware.setItemCount(currentItemCount); } return item; } 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..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 @@ -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,22 +28,23 @@ * implementation is not thread-safe. * * @author Mahmoud Ben Hassine + * @author Elimelec Burghelea * @param type of objects to read * @since 5.2 */ 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 +54,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); } } @@ -77,10 +79,30 @@ 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 { - for (ItemStreamReader delegate : delegates) { - delegate.close(); + List exceptions = new ArrayList<>(); + + for (ItemStreamReader delegate : delegates) { + 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/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..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 @@ -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,27 @@ 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. 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<>(); + 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/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..74b5c64878 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 { @@ -103,20 +105,40 @@ 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<>(); + for (ItemWriter writer : delegates) { - if (!ignoreItemStream && (writer instanceof ItemStream)) { - ((ItemStream) writer).close(); + if (!ignoreItemStream && (writer instanceof ItemStream itemStream)) { + 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; + } } @Override public void open(ExecutionContext executionContext) throws ItemStreamException { for (ItemWriter writer : delegates) { - if (!ignoreItemStream && (writer instanceof ItemStream)) { - ((ItemStream) writer).open(executionContext); + if (!ignoreItemStream && (writer instanceof ItemStream itemStream)) { + itemStream.open(executionContext); } } } @@ -124,8 +146,8 @@ public void open(ExecutionContext executionContext) throws ItemStreamException { @Override public void update(ExecutionContext executionContext) throws ItemStreamException { for (ItemWriter writer : delegates) { - if (!ignoreItemStream && (writer instanceof ItemStream)) { - ((ItemStream) writer).update(executionContext); + if (!ignoreItemStream && (writer instanceof ItemStream itemStream)) { + itemStream.update(executionContext); } } } diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/support/ScriptItemProcessor.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/support/ScriptItemProcessor.java index 44e563beed..8b86d78f13 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/support/ScriptItemProcessor.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/support/ScriptItemProcessor.java @@ -138,11 +138,11 @@ public void afterPropertiesSet() throws Exception { Assert.state(scriptSource == null || script == null, "Either a script source or script file must be provided, not both"); - if (scriptSource != null && scriptEvaluator instanceof StandardScriptEvaluator) { + if (scriptSource != null && scriptEvaluator instanceof StandardScriptEvaluator standardScriptEvaluator) { Assert.state(StringUtils.hasLength(language), "Language must be provided when using the default ScriptEvaluator and raw source code"); - ((StandardScriptEvaluator) scriptEvaluator).setLanguage(language); + standardScriptEvaluator.setLanguage(language); } } diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/support/SingleItemPeekableItemReader.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/support/SingleItemPeekableItemReader.java index 84e751e7f6..e1d25c8f0b 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/support/SingleItemPeekableItemReader.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/support/SingleItemPeekableItemReader.java @@ -101,10 +101,10 @@ public T peek() throws Exception { @Override public void close() throws ItemStreamException { next = null; - if (delegate instanceof ItemStream) { - ((ItemStream) delegate).close(); + if (delegate instanceof ItemStream itemStream) { + itemStream.close(); } - executionContext = new ExecutionContext(); + this.executionContext = new ExecutionContext(); } /** @@ -117,10 +117,10 @@ public void close() throws ItemStreamException { @Override public void open(ExecutionContext executionContext) throws ItemStreamException { next = null; - if (delegate instanceof ItemStream) { - ((ItemStream) delegate).open(executionContext); + if (delegate instanceof ItemStream itemStream) { + itemStream.open(executionContext); } - executionContext = new ExecutionContext(); + this.executionContext = new ExecutionContext(); } /** @@ -144,8 +144,8 @@ public void update(ExecutionContext executionContext) throws ItemStreamException } private void updateDelegate(ExecutionContext executionContext) { - if (delegate instanceof ItemStream) { - ((ItemStream) delegate).update(executionContext); + if (delegate instanceof ItemStream itemStream) { + itemStream.update(executionContext); } } 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/StaxEventItemReader.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/xml/StaxEventItemReader.java index c10cb1e75c..c225178dc6 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/xml/StaxEventItemReader.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/xml/StaxEventItemReader.java @@ -115,15 +115,14 @@ public void setUnmarshaller(Unmarshaller unmarshaller) { } /** - * @param fragmentRootElementName name of the root element of the fragment + * @param fragmentRootElementName the name of the fragment's root element */ public void setFragmentRootElementName(String fragmentRootElementName) { setFragmentRootElementNames(new String[] { fragmentRootElementName }); } /** - * @param fragmentRootElementNames list of the names of the root element of the - * fragment + * @param fragmentRootElementNames the names of the fragment's root element */ public void setFragmentRootElementNames(String[] fragmentRootElementNames) { this.fragmentRootElementNames = new ArrayList<>(); 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..5422c96c9d 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); } } @@ -813,8 +813,8 @@ private long getPosition() { try { eventWriter.flush(); position = channel.position(); - if (bufferedWriter instanceof TransactionAwareBufferedWriter) { - position += ((TransactionAwareBufferedWriter) bufferedWriter).getBufferSize(); + if (bufferedWriter instanceof TransactionAwareBufferedWriter transactionAwareBufferedWriter) { + position += transactionAwareBufferedWriter.getBufferSize(); } } catch (Exception e) { diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/xml/stax/NoStartEndDocumentStreamWriter.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/xml/stax/NoStartEndDocumentStreamWriter.java index cd44345f06..9879a26b43 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/xml/stax/NoStartEndDocumentStreamWriter.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/xml/stax/NoStartEndDocumentStreamWriter.java @@ -35,7 +35,7 @@ public NoStartEndDocumentStreamWriter(XMLEventWriter wrappedEventWriter) { @Override public void add(XMLEvent event) throws XMLStreamException { - if ((!event.isStartDocument()) && (!event.isEndDocument())) { + if (!event.isStartDocument() && !event.isEndDocument()) { wrappedEventWriter.add(event); } } diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/repeat/context/SynchronizedAttributeAccessor.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/repeat/context/SynchronizedAttributeAccessor.java index af6bbe8c3d..0413baa10e 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/repeat/context/SynchronizedAttributeAccessor.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/repeat/context/SynchronizedAttributeAccessor.java @@ -53,11 +53,11 @@ public boolean equals(Object other) { return true; } AttributeAccessorSupport that; - if (other instanceof SynchronizedAttributeAccessor) { - that = ((SynchronizedAttributeAccessor) other).support; + if (other instanceof SynchronizedAttributeAccessor synchronizedAttributeAccessor) { + that = synchronizedAttributeAccessor.support; } - else if (other instanceof AttributeAccessorSupport) { - that = (AttributeAccessorSupport) other; + else if (other instanceof AttributeAccessorSupport attributeAccessorSupport) { + that = attributeAccessorSupport; } else { return false; diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/repeat/interceptor/RepeatOperationsInterceptor.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/repeat/interceptor/RepeatOperationsInterceptor.java index b390e7b3f2..de3df9427a 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/repeat/interceptor/RepeatOperationsInterceptor.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/repeat/interceptor/RepeatOperationsInterceptor.java @@ -74,9 +74,9 @@ public Object invoke(final MethodInvocation invocation) throws Throwable { repeatOperations.iterate(context -> { try { - MethodInvocation clone = invocation; - if (invocation instanceof ProxyMethodInvocation) { - clone = ((ProxyMethodInvocation) invocation).invocableClone(); + MethodInvocation clone; + if (invocation instanceof ProxyMethodInvocation proxyMethodInvocation) { + clone = proxyMethodInvocation.invocableClone(); } else { throw new IllegalStateException( @@ -97,12 +97,12 @@ public Object invoke(final MethodInvocation invocation) throws Throwable { return RepeatStatus.FINISHED; } } - catch (Throwable e) { - if (e instanceof Exception) { - throw (Exception) e; + catch (Throwable t) { + if (t instanceof Exception e) { + throw e; } else { - throw new RepeatOperationsInterceptorException("Unexpected error in batch interceptor", e); + throw new RepeatOperationsInterceptorException("Unexpected error in batch interceptor", t); } } }); @@ -122,7 +122,7 @@ public Object invoke(final MethodInvocation invocation) throws Throwable { } private boolean isComplete(Object result) { - return result == null || (result instanceof Boolean) && !(Boolean) result; + return (result == null) || ((result instanceof Boolean b) && !b); } /** 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/policy/CompletionPolicySupport.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/repeat/policy/CompletionPolicySupport.java index a5acaf9358..9e375b0def 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/repeat/policy/CompletionPolicySupport.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/repeat/policy/CompletionPolicySupport.java @@ -73,8 +73,8 @@ public RepeatContext start(RepeatContext context) { */ @Override public void update(RepeatContext context) { - if (context instanceof RepeatContextSupport) { - ((RepeatContextSupport) context).increment(); + if (context instanceof RepeatContextSupport repeatContextSupport) { + repeatContextSupport.increment(); } } diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/repeat/support/RepeatTemplate.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/repeat/support/RepeatTemplate.java index 7b7af3fc68..1fc00e8bbb 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/repeat/support/RepeatTemplate.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/repeat/support/RepeatTemplate.java @@ -309,11 +309,11 @@ private void doHandle(Throwable throwable, RepeatContext context, Collection { 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..3ee8e22617 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 @@ -96,6 +77,7 @@ public void setTaskExecutor(TaskExecutor taskExecutor) { * need to synchronize access. * */ + @SuppressWarnings("removal") @Override protected RepeatStatus getNextResult(RepeatContext context, RepeatCallback callback, RepeatInternalState state) throws Throwable { @@ -153,6 +135,7 @@ protected RepeatStatus getNextResult(RepeatContext context, RepeatCallback callb * * @see org.springframework.batch.repeat.support.RepeatTemplate#waitForResults(org.springframework.batch.repeat.support.RepeatInternalState) */ + @SuppressWarnings("removal") @Override protected boolean waitForResults(RepeatInternalState state) { @@ -204,6 +187,7 @@ protected RepeatInternalState createInternalState(RepeatContext context) { * @author Dave Syer * */ + @SuppressWarnings("removal") private class ExecutingRunnable implements Runnable, ResultHolder { private final RepeatCallback callback; @@ -307,6 +291,7 @@ public RepeatContext getContext() { * @author Dave Syer * */ + @SuppressWarnings("removal") private static class ResultQueueInternalState extends RepeatInternalStateSupport { private final ResultQueue results; diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/repeat/support/ThrottleLimitResultQueue.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/repeat/support/ThrottleLimitResultQueue.java index 0fd0c215c9..6637f7951b 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/repeat/support/ThrottleLimitResultQueue.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/repeat/support/ThrottleLimitResultQueue.java @@ -29,6 +29,7 @@ * @author Mahmoud Ben Hassine * @deprecated since 5.0 with no replacement. Scheduled for removal in 6.0. */ +@SuppressWarnings("removal") @Deprecated(since = "5.0", forRemoval = true) public class ThrottleLimitResultQueue implements ResultQueue { diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/MethodInvokerUtils.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/MethodInvokerUtils.java index b824b36aea..10603064fa 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/MethodInvokerUtils.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/MethodInvokerUtils.java @@ -116,7 +116,7 @@ public static MethodInvoker getMethodInvokerForInterface(Class cls, String me public static MethodInvoker getMethodInvokerByAnnotation(final Class annotationType, final Object target, final Class... expectedParamTypes) { MethodInvoker mi = MethodInvokerUtils.getMethodInvokerByAnnotation(annotationType, target); - final Class targetClass = (target instanceof Advised) ? ((Advised) target).getTargetSource().getTargetClass() + final Class targetClass = (target instanceof Advised advised) ? advised.getTargetSource().getTargetClass() : target.getClass(); if (mi != null) { ReflectionUtils.doWithMethods(targetClass, method -> { @@ -156,7 +156,7 @@ public static MethodInvoker getMethodInvokerByAnnotation(final Class targetClass = (target instanceof Advised) ? ((Advised) target).getTargetSource().getTargetClass() + final Class targetClass = (target instanceof Advised advised) ? advised.getTargetSource().getTargetClass() : target.getClass(); if (targetClass == null) { // Proxy with no target cannot have annotations diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/PatternMatcher.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/PatternMatcher.java index f5f5434401..e4fdabe1a1 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/PatternMatcher.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/PatternMatcher.java @@ -46,8 +46,8 @@ public PatternMatcher(Map map) { } /** - * Lifted from AntPathMatcher in Spring Core. Tests whether or not a string matches - * against a pattern. The pattern may contain two special characters:
+ * Lifted from AntPathMatcher in Spring Core. Tests whether a string matches against a + * pattern. The pattern may contain two special characters:
* '*' means zero or more characters
* '?' means one and only one character * @param pattern pattern to match against. Must not be null. diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/PropertiesConverter.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/PropertiesConverter.java index 041026717b..e108d6c0f4 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/PropertiesConverter.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/PropertiesConverter.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. @@ -32,7 +32,9 @@ * @author Lucas Ward * @author Dave Syer * @author Mahmoud Ben Hassine + * @deprecated since 6.0 with no replacement. Scheduled for removal in 6.2 or later. */ +@Deprecated(since = "6.0", forRemoval = true) public final class PropertiesConverter { private static final String LINE_SEPARATOR = "\n"; diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/SimpleMethodInvoker.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/SimpleMethodInvoker.java index a7d0856239..926dca6284 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/SimpleMethodInvoker.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/SimpleMethodInvoker.java @@ -135,7 +135,7 @@ public boolean equals(Object obj) { if (obj == this) { return true; } - return (rhs.method.equals(this.method)) && (rhs.object.equals(this.object)); + return rhs.method.equals(this.method) && rhs.object.equals(this.object); } @Override 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/main/java/org/springframework/batch/support/transaction/TransactionAwareProxyFactory.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/transaction/TransactionAwareProxyFactory.java index c7ffbc5e3c..6877db4a84 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/transaction/TransactionAwareProxyFactory.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/transaction/TransactionAwareProxyFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2021 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. @@ -58,8 +58,9 @@ *

* * @author Dave Syer - * + * @deprecated since 6.0 with no replacement. Scheduled for removal in 6.2 or later. */ +@Deprecated(since = "6.0", forRemoval = true) public class TransactionAwareProxyFactory { private final T target; @@ -86,26 +87,26 @@ private TransactionAwareProxyFactory(T target, boolean appendOnly) { */ @SuppressWarnings({ "unchecked", "rawtypes" }) protected final T begin(T target) { - // Unfortunately in Java 5 this method has to synchronized + // Unfortunately in Java 5 this method has to be synchronized // (works OK without in Java 6). synchronized (target) { - if (target instanceof List) { + if (target instanceof List list) { if (appendOnly) { return (T) new ArrayList(); } - return (T) new ArrayList((List) target); + return (T) new ArrayList(list); } - else if (target instanceof Set) { + else if (target instanceof Set set) { if (appendOnly) { return (T) new HashSet(); } - return (T) new HashSet((Set) target); + return (T) new HashSet(set); } - else if (target instanceof Map) { + else if (target instanceof Map map) { if (appendOnly) { return (T) new HashMap(); } - return (T) new HashMap((Map) target); + return (T) new HashMap(map); } else { throw new UnsupportedOperationException("Cannot copy target for this type: " + target.getClass()); @@ -124,11 +125,11 @@ protected void commit(T copy, T target) { // Unfortunately in Java 5 this method has to be synchronized // (works OK without in Java 6). synchronized (target) { - if (target instanceof Collection) { + if (target instanceof Collection collection) { if (!appendOnly) { - ((Collection) target).clear(); + collection.clear(); } - ((Collection) target).addAll((Collection) copy); + collection.addAll((Collection) copy); } else { if (!appendOnly) { @@ -239,11 +240,11 @@ public Object invoke(MethodInvocation invocation) throws Throwable { if (appendOnly) { String methodName = invocation.getMethod().getName(); - if ((result == null && methodName.equals("get")) - || (Boolean.FALSE.equals(result) && (methodName.startsWith("contains")) + if (((result == null) && methodName.equals("get")) + || ((Boolean.FALSE.equals(result) && methodName.startsWith("contains")) || (Boolean.TRUE.equals(result) && methodName.startsWith("isEmpty")))) { - // In appendOnly mode the result of a get might not be - // in the cache... + // In appendOnly mode, the result of a get might not be in the + // cache... return invocation.proceed(); } if (result instanceof Collection) { 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 diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/container/jms/BatchMessageListenerContainer.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/container/jms/BatchMessageListenerContainer.java index df3c584f8c..5fab1c2686 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/container/jms/BatchMessageListenerContainer.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/container/jms/BatchMessageListenerContainer.java @@ -103,15 +103,15 @@ protected void handleListenerException(Throwable ex) { return; } logger.debug("Re-throwing exception in container."); - if (ex instanceof RuntimeException) { + if (ex instanceof RuntimeException runtimeException) { // We need to re-throw so that an enclosing non-JMS transaction can // rollback... - throw (RuntimeException) ex; + throw runtimeException; } - else if (ex instanceof Error) { - // Just re-throw Error instances because otherwise unit tests just - // swallow exceptions from EasyMock and JUnit. - throw (Error) ex; + else if (ex instanceof Error error) { + // Just re-throw Error instances because otherwise unit tests just swallow + // exceptions from EasyMock and JUnit. + throw error; } } diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/container/jms/BatchMessageListenerContainerTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/container/jms/BatchMessageListenerContainerTests.java index 8ad90b66b8..fdc8769288 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/container/jms/BatchMessageListenerContainerTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/container/jms/BatchMessageListenerContainerTests.java @@ -121,8 +121,7 @@ void testNonTransactionalReceiveAndExecuteWithCallbackThrowingError() throws Exc private BatchMessageListenerContainer getContainer(RepeatTemplate template) { ConnectionFactory connectionFactory = mock(); // Yuck: we need to turn these method in base class to no-ops because the invoker - // is a private class - // we can't create for test purposes... + // is a private class we can't create for test purposes... BatchMessageListenerContainer container = new BatchMessageListenerContainer() { @Override protected void messageReceived(Object invoker, Session session) { @@ -141,12 +140,12 @@ protected void noMessageReceived(Object invoker, Session session) { return container; } - private boolean doTestWithException(final Throwable t, boolean expectRollback, int expectGetTransactionCount) + private boolean doTestWithException(Throwable t, boolean expectRollback, int expectGetTransactionCount) throws JMSException, IllegalAccessException { container.setAcceptMessagesWhileStopping(true); container.setMessageListener((MessageListener) arg0 -> { - if (t instanceof RuntimeException) - throw (RuntimeException) t; + if (t instanceof RuntimeException runtimeException) + throw runtimeException; else throw (Error) t; }); @@ -159,16 +158,14 @@ private boolean doTestWithException(final Throwable t, boolean expectRollback, i when(session.getTransacted()).thenReturn(true); } - // Expect only one call to consumer (chunk size is 2, but first one - // rolls back terminating batch)... + // Expect only one call to consumer (chunk size is 2, but first one rolls back + // terminating batch)... when(consumer.receive(1000)).thenReturn(message); if (expectRollback) { session.rollback(); } - boolean received = doExecute(session, consumer); - - return received; + return doExecute(session, consumer); } private boolean doExecute(Session session, MessageConsumer consumer) throws IllegalAccessException { 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..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-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. @@ -15,22 +15,28 @@ */ 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 - * + * @author Seokmun Heo */ class ExecutionContextTests { @@ -88,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()); } @@ -161,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 */ @@ -196,4 +213,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)); + } + } diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/amqp/builder/AmqpItemReaderBuilderTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/amqp/builder/AmqpItemReaderBuilderTests.java index bba60288b7..347661b7d3 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/amqp/builder/AmqpItemReaderBuilderTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/amqp/builder/AmqpItemReaderBuilderTests.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. @@ -43,7 +43,8 @@ void testNoItemType() { when(this.amqpTemplate.receiveAndConvert()).thenReturn("foo"); final AmqpItemReader amqpItemReader = new AmqpItemReaderBuilder() - .amqpTemplate(this.amqpTemplate).build(); + .amqpTemplate(this.amqpTemplate) + .build(); assertEquals("foo", amqpItemReader.read()); } @@ -52,7 +53,9 @@ void testNonMessageItemType() { when(this.amqpTemplate.receiveAndConvert()).thenReturn("foo"); final AmqpItemReader amqpItemReader = new AmqpItemReaderBuilder() - .amqpTemplate(this.amqpTemplate).itemType(String.class).build(); + .amqpTemplate(this.amqpTemplate) + .itemType(String.class) + .build(); assertEquals("foo", amqpItemReader.read()); } diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/avro/example/AvroTestUtils.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/avro/example/AvroTestUtils.java index 63182ec246..bc7d6df8a1 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/avro/example/AvroTestUtils.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/avro/example/AvroTestUtils.java @@ -42,6 +42,7 @@ public static void main(String... args) { createTestData(); } catch (Exception e) { + // ignored } } diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/avro/example/User.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/avro/example/User.java index 0416ff9ec8..ddf84d5a1b 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/avro/example/User.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/avro/example/User.java @@ -119,15 +119,18 @@ public User(CharSequence name, Integer favorite_number, CharSequence favorite_co this.favorite_color = favorite_color; } + @Override public SpecificData getSpecificData() { return MODEL$; } + @Override public org.apache.avro.Schema getSchema() { return SCHEMA$; } // Used by DatumWriter. Applications should not call. + @Override public Object get(int field$) { return switch (field$) { case 0 -> name; @@ -138,7 +141,7 @@ public Object get(int field$) { } // Used by DatumReader. Applications should not call. - @SuppressWarnings(value = "unchecked") + @Override public void put(int field$, Object value$) { switch (field$) { case 0 -> name = (CharSequence) value$; @@ -476,7 +479,7 @@ public void customEncode(org.apache.avro.io.Encoder out) throws java.io.IOExcept public void customDecode(org.apache.avro.io.ResolvingDecoder in) throws java.io.IOException { org.apache.avro.Schema.Field[] fieldOrder = in.readFieldOrderIfDiff(); if (fieldOrder == null) { - this.name = in.readString(this.name instanceof Utf8 ? (Utf8) this.name : null); + this.name = in.readString(this.name instanceof Utf8 utf8 ? utf8 : null); if (in.readIndex() != 0) { in.readNull(); @@ -491,15 +494,14 @@ public void customDecode(org.apache.avro.io.ResolvingDecoder in) throws java.io. this.favorite_color = null; } else { - this.favorite_color = in - .readString(this.favorite_color instanceof Utf8 ? (Utf8) this.favorite_color : null); + this.favorite_color = in.readString(this.favorite_color instanceof Utf8 utf8 ? utf8 : null); } } else { for (int i = 0; i < 3; i++) { switch (fieldOrder[i].pos()) { - case 0 -> this.name = in.readString(this.name instanceof Utf8 ? (Utf8) this.name : null); + case 0 -> this.name = in.readString(this.name instanceof Utf8 utf8 ? utf8 : null); case 1 -> { if (in.readIndex() != 0) { in.readNull(); @@ -515,8 +517,7 @@ public void customDecode(org.apache.avro.io.ResolvingDecoder in) throws java.io. this.favorite_color = null; } else { - this.favorite_color = in - .readString(this.favorite_color instanceof Utf8 ? (Utf8) this.favorite_color : null); + this.favorite_color = in.readString(this.favorite_color instanceof Utf8 utf8 ? utf8 : null); } } default -> throw new java.io.IOException("Corrupt ResolvingDecoder."); diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/data/MongoCursorItemReaderTest.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/data/MongoCursorItemReaderTest.java index 8cb6bf84c7..f76f0fed13 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/data/MongoCursorItemReaderTest.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/data/MongoCursorItemReaderTest.java @@ -204,7 +204,7 @@ void testQueryWithLimit() throws Exception { @Test void testQueryWithMaxTime() throws Exception { - reader.setMaxTime(Duration.ofMillis(3000)); + reader.setMaxTime(Duration.ofSeconds(3)); ArgumentCaptor queryContainer = ArgumentCaptor.forClass(Query.class); when(template.stream(queryContainer.capture(), eq(String.class))).thenReturn(Stream.of()); 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/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); + } + } 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/MongoCursorItemReaderBuilderTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/data/builder/MongoCursorItemReaderBuilderTests.java index f8dcfa6e32..94937673e4 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/data/builder/MongoCursorItemReaderBuilderTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/data/builder/MongoCursorItemReaderBuilderTests.java @@ -45,7 +45,7 @@ void testBuild() { Map sorts = mock(); int batchSize = 100; int limit = 10000; - Duration maxTime = Duration.ofMillis(1000); + Duration maxTime = Duration.ofSeconds(1); // when MongoCursorItemReader reader = new MongoCursorItemReaderBuilder().name("reader") 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/AbstractDatabaseItemStreamItemReaderTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/AbstractDatabaseItemStreamItemReaderTests.java index 43b9ff289e..cd2b0de1a3 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/AbstractDatabaseItemStreamItemReaderTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/AbstractDatabaseItemStreamItemReaderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2009-2023 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. @@ -70,7 +70,7 @@ void testReadToExhaustion() throws Exception { } protected DataSource getDataSource() { - return (DataSource) ctx.getBean("dataSource"); + return ctx.getBean("dataSource", DataSource.class); } } diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/ExtendedConnectionDataSourceProxyTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/ExtendedConnectionDataSourceProxyTests.java index 3b83e488ce..df04ab7aff 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/ExtendedConnectionDataSourceProxyTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/ExtendedConnectionDataSourceProxyTests.java @@ -278,38 +278,38 @@ private static class DataSourceStub implements DataSource, Supported { private static final String UNWRAP_ERROR_MESSAGE = "supplied type is not implemented by this class"; @Override - public Connection getConnection() throws SQLException { + public Connection getConnection() { throw new UnsupportedOperationException(); } @Override - public Connection getConnection(String username, String password) throws SQLException { + public Connection getConnection(String username, String password) { throw new UnsupportedOperationException(); } @Override - public PrintWriter getLogWriter() throws SQLException { + public PrintWriter getLogWriter() { throw new UnsupportedOperationException(); } @Override - public int getLoginTimeout() throws SQLException { + public int getLoginTimeout() { throw new UnsupportedOperationException(); } @Override - public void setLogWriter(PrintWriter out) throws SQLException { + public void setLogWriter(PrintWriter out) { throw new UnsupportedOperationException(); } @Override - public void setLoginTimeout(int seconds) throws SQLException { + public void setLoginTimeout(int seconds) { throw new UnsupportedOperationException(); } @Override - public boolean isWrapperFor(Class iface) throws SQLException { - if (iface.equals(Supported.class) || (iface.equals(DataSource.class))) { + public boolean isWrapperFor(Class iface) { + if (iface.equals(Supported.class) || iface.equals(DataSource.class)) { return true; } return false; diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/JdbcBatchItemWriterNamedParameterTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/JdbcBatchItemWriterNamedParameterTests.java index 5cf270059c..b44ae947ae 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/JdbcBatchItemWriterNamedParameterTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/JdbcBatchItemWriterNamedParameterTests.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. @@ -119,7 +119,7 @@ void testWriteAndFlush() throws Exception { when(namedParameterJdbcOperations.batchUpdate(eq(sql), eqSqlParameterSourceArray( new SqlParameterSource[] { new BeanPropertySqlParameterSource(new Foo("bar")) }))) - .thenReturn(new int[] { 1 }); + .thenReturn(new int[] { 1 }); writer.write(Chunk.of(new Foo("bar"))); } @@ -166,7 +166,7 @@ void testWriteAndFlushWithEmptyUpdate() { when(namedParameterJdbcOperations.batchUpdate(eq(sql), eqSqlParameterSourceArray( new SqlParameterSource[] { new BeanPropertySqlParameterSource(new Foo("bar")) }))) - .thenReturn(new int[] { 0 }); + .thenReturn(new int[] { 0 }); Exception exception = assertThrows(EmptyResultDataAccessException.class, () -> writer.write(Chunk.of(new Foo("bar")))); String message = exception.getMessage(); diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/JpaNativeQueryProviderIntegrationTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/JpaNativeQueryProviderIntegrationTests.java index 959db5e22e..f8373e7de4 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/JpaNativeQueryProviderIntegrationTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/JpaNativeQueryProviderIntegrationTests.java @@ -71,7 +71,7 @@ void shouldRetrieveAndMapAllFoos() throws Exception { @SuppressWarnings("unchecked") List actualFoos = query.getResultList(); - assertEquals(actualFoos, expectedFoos); + assertEquals(expectedFoos, actualFoos); } @Test @@ -96,7 +96,7 @@ void shouldExecuteParameterizedQuery() throws Exception { @SuppressWarnings("unchecked") List actualFoos = query.getResultList(); - assertEquals(actualFoos, expectedFoos); + assertEquals(expectedFoos, actualFoos); } } diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/builder/JdbcBatchItemWriterBuilderTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/builder/JdbcBatchItemWriterBuilderTests.java index cdc7c5fc76..76ffb25b76 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/builder/JdbcBatchItemWriterBuilderTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/builder/JdbcBatchItemWriterBuilderTests.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. @@ -60,7 +60,7 @@ class JdbcBatchItemWriterBuilderTests { @BeforeEach void setUp() { this.context = new AnnotationConfigApplicationContext(TestDataSourceConfiguration.class); - this.dataSource = (DataSource) context.getBean("dataSource"); + this.dataSource = context.getBean("dataSource", DataSource.class); } @AfterEach diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/builder/JdbcCursorItemReaderBuilderTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/builder/JdbcCursorItemReaderBuilderTests.java index 16e33c6107..fa66612886 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/builder/JdbcCursorItemReaderBuilderTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/builder/JdbcCursorItemReaderBuilderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 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. @@ -58,7 +58,7 @@ class JdbcCursorItemReaderBuilderTests { @BeforeEach void setUp() { this.context = new AnnotationConfigApplicationContext(TestDataSourceConfiguration.class); - this.dataSource = (DataSource) context.getBean("dataSource"); + this.dataSource = context.getBean("dataSource", DataSource.class); } @AfterEach diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/builder/JdbcPagingItemReaderBuilderTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/builder/JdbcPagingItemReaderBuilderTests.java index a6220cbeb1..850067929d 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/builder/JdbcPagingItemReaderBuilderTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/builder/JdbcPagingItemReaderBuilderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2024 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. @@ -58,7 +58,7 @@ class JdbcPagingItemReaderBuilderTests { @BeforeEach void setUp() { this.context = new AnnotationConfigApplicationContext(TestDataSourceConfiguration.class); - this.dataSource = (DataSource) context.getBean("dataSource"); + this.dataSource = context.getBean("dataSource", DataSource.class); } @AfterEach diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/builder/JpaCursorItemReaderBuilderTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/builder/JpaCursorItemReaderBuilderTests.java index d24fca1a7a..1affc62d81 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/builder/JpaCursorItemReaderBuilderTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/builder/JpaCursorItemReaderBuilderTests.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. @@ -61,7 +61,7 @@ class JpaCursorItemReaderBuilderTests { void setUp() { this.context = new AnnotationConfigApplicationContext( JpaCursorItemReaderBuilderTests.TestDataSourceConfiguration.class); - this.entityManagerFactory = (EntityManagerFactory) context.getBean("entityManagerFactory"); + this.entityManagerFactory = context.getBean("entityManagerFactory", EntityManagerFactory.class); } @AfterEach diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/builder/JpaPagingItemReaderBuilderTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/builder/JpaPagingItemReaderBuilderTests.java index 8fce0376be..e0f1f67e04 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/builder/JpaPagingItemReaderBuilderTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/builder/JpaPagingItemReaderBuilderTests.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. @@ -64,7 +64,7 @@ class JpaPagingItemReaderBuilderTests { void setUp() { this.context = new AnnotationConfigApplicationContext( JpaPagingItemReaderBuilderTests.TestDataSourceConfiguration.class); - this.entityManagerFactory = (EntityManagerFactory) context.getBean("entityManagerFactory"); + this.entityManagerFactory = context.getBean("entityManagerFactory", EntityManagerFactory.class); } @AfterEach 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..1048e61738 --- /dev/null +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/Db2PagingQueryProviderIntegrationTests.java @@ -0,0 +1,72 @@ +/* + * 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. + * 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.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.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 + * @author Mahmoud Ben Hassine + */ +@Testcontainers(disabledWithoutDocker = true) +@SpringJUnitConfig +@Sql(scripts = "query-provider-fixture.sql", executionPhase = BEFORE_TEST_CLASS) +@Disabled("https://github.com/spring-projects/spring-batch/issues/4828") +class Db2PagingQueryProviderIntegrationTests extends AbstractPagingQueryProviderIntegrationTests { + + // TODO find the best way to externalize and manage image versions + private static final DockerImageName DB2_IMAGE = DockerImageName.parse("icr.io/db2_community/db2:12.1.0.0"); + + @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 new file mode 100644 index 0000000000..9a06de9369 --- /dev/null +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/DerbyPagingQueryProviderIntegrationTests.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 DerbyPagingQueryProviderIntegrationTests extends AbstractPagingQueryProviderIntegrationTests { + + DerbyPagingQueryProviderIntegrationTests(@Autowired DataSource dataSource) { + super(dataSource, new DerbyPagingQueryProvider()); + } + + @Configuration + static class TestConfiguration { + + @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(); + } + + } + +} 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/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..bbb45b1d3b --- /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:11.8.2"); + + @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..157df32a06 --- /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:9.2.0"); + + @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..a189e0e3b8 --- /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:17.5"); + + @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..004692bd99 --- /dev/null +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/SqlServerPagingQueryProviderIntegrationTests.java @@ -0,0 +1,69 @@ +/* + * 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. + * 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.junit.jupiter.api.Disabled; +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) +@Disabled("https://github.com/spring-projects/spring-batch/issues/4828") +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/SqlWindowingPagingQueryProviderTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/SqlWindowingPagingQueryProviderTests.java deleted file mode 100644 index bac04788f5..0000000000 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/SqlWindowingPagingQueryProviderTests.java +++ /dev/null @@ -1,76 +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.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 { - - 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/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; + } + + } + +} 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/SimpleBinaryBufferedReaderFactoryTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/SimpleBinaryBufferedReaderFactoryTests.java index 202fcc6476..49700060bc 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/SimpleBinaryBufferedReaderFactoryTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/SimpleBinaryBufferedReaderFactoryTests.java @@ -93,7 +93,7 @@ void testCreateWithFalseMixedCharacterLineEnding() throws Exception { SimpleBinaryBufferedReaderFactory factory = new SimpleBinaryBufferedReaderFactory(); factory.setLineEnding("#@"); @SuppressWarnings("resource") - BufferedReader reader = factory.create(new ByteArrayResource(("a##@").getBytes()), "UTF-8"); + BufferedReader reader = factory.create(new ByteArrayResource("a##@".getBytes()), "UTF-8"); assertEquals("a#", reader.readLine()); assertNull(reader.readLine()); } diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/builder/FlatFileItemReaderBuilderTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/builder/FlatFileItemReaderBuilderTests.java index e6c6f6c2de..3e824f4a96 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/builder/FlatFileItemReaderBuilderTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/builder/FlatFileItemReaderBuilderTests.java @@ -22,6 +22,7 @@ import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.function.Executable; import org.springframework.batch.item.ExecutionContext; import org.springframework.batch.item.file.FlatFileItemReader; import org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper; @@ -44,6 +45,7 @@ import org.springframework.test.util.ReflectionTestUtils; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -55,6 +57,8 @@ * @author Mahmoud Ben Hassine * @author Drummond Dawson * @author Glenn Renfro + * @author Patrick Baumgartner + * @author François Martin */ class FlatFileItemReaderBuilderTests { @@ -553,13 +557,23 @@ void testErrorMessageWhenNoFieldSetMapperIsProvided() { @Test void testErrorMessageWhenNoLineTokenizerWasProvided() { - try { - new FlatFileItemReaderBuilder().name("fooReader").resource(getResource("1;2;3")).build(); - } - catch (IllegalStateException exception) { - String exceptionMessage = exception.getMessage(); - assertEquals("No LineTokenizer implementation was provided.", exceptionMessage); - } + Executable builder = () -> new FlatFileItemReaderBuilder().name("fooReader") + .resource(getResource("1;2;3")) + .build(); + Exception exception = assertThrows(IllegalStateException.class, builder); + String message = exception.getMessage(); + assertEquals("No LineTokenizer implementation was provided.", message); + } + + @Test + void testErrorWhenTargetTypeAndFieldSetMapperIsProvided() { + var builder = new FlatFileItemReaderBuilder().name("fooReader") + .resource(getResource("1;2;3")) + .lineTokenizer(line -> new DefaultFieldSet(line.split(";"))) + .targetType(Foo.class) + .fieldSetMapper(fieldSet -> new Foo()); + var exception = assertThrows(IllegalStateException.class, builder::build); + assertEquals("Either a TargetType or FieldSetMapper can be set, can't be both.", exception.getMessage()); } @Test @@ -579,15 +593,16 @@ record Person(int id, String name) { // then Object lineMapper = ReflectionTestUtils.getField(reader, "lineMapper"); assertNotNull(lineMapper); - assertTrue(lineMapper instanceof DefaultLineMapper); + assertInstanceOf(DefaultLineMapper.class, lineMapper); Object fieldSetMapper = ReflectionTestUtils.getField(lineMapper, "fieldSetMapper"); assertNotNull(fieldSetMapper); - assertTrue(fieldSetMapper instanceof RecordFieldSetMapper); + assertInstanceOf(RecordFieldSetMapper.class, fieldSetMapper); } @Test void testSetupWithClassTargetType() { // given + @SuppressWarnings("unused") class Person { int id; @@ -607,10 +622,10 @@ class Person { // then Object lineMapper = ReflectionTestUtils.getField(reader, "lineMapper"); assertNotNull(lineMapper); - assertTrue(lineMapper instanceof DefaultLineMapper); + assertInstanceOf(DefaultLineMapper.class, lineMapper); Object fieldSetMapper = ReflectionTestUtils.getField(lineMapper, "fieldSetMapper"); assertNotNull(fieldSetMapper); - assertTrue(fieldSetMapper instanceof BeanWrapperFieldSetMapper); + assertInstanceOf(BeanWrapperFieldSetMapper.class, fieldSetMapper); } private Resource getResource(String contents) { diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/builder/FlatFileItemWriterBuilderTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/builder/FlatFileItemWriterBuilderTests.java index 0b37305559..ad8083c4d2 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/builder/FlatFileItemWriterBuilderTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/builder/FlatFileItemWriterBuilderTests.java @@ -38,6 +38,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -347,16 +348,18 @@ record Person(int id, String name) { // then Object lineAggregator = ReflectionTestUtils.getField(writer, "lineAggregator"); assertNotNull(lineAggregator); - assertTrue(lineAggregator instanceof DelimitedLineAggregator); + assertInstanceOf(DelimitedLineAggregator.class, lineAggregator); Object fieldExtractor = ReflectionTestUtils.getField(lineAggregator, "fieldExtractor"); assertNotNull(fieldExtractor); - assertTrue(fieldExtractor instanceof RecordFieldExtractor); + assertInstanceOf(RecordFieldExtractor.class, fieldExtractor); } @Test void testSetupDelimitedLineAggregatorWithClassItemType() throws IOException { // given WritableResource output = new FileSystemResource(File.createTempFile("foo", "txt")); + + @SuppressWarnings("unused") class Person { int id; @@ -376,10 +379,10 @@ class Person { // then Object lineAggregator = ReflectionTestUtils.getField(writer, "lineAggregator"); assertNotNull(lineAggregator); - assertTrue(lineAggregator instanceof DelimitedLineAggregator); + assertInstanceOf(DelimitedLineAggregator.class, lineAggregator); Object fieldExtractor = ReflectionTestUtils.getField(lineAggregator, "fieldExtractor"); assertNotNull(fieldExtractor); - assertTrue(fieldExtractor instanceof BeanWrapperFieldExtractor); + assertInstanceOf(BeanWrapperFieldExtractor.class, fieldExtractor); } @Test @@ -388,7 +391,7 @@ void testSetupDelimitedLineAggregatorWithNoItemType() throws IOException { WritableResource output = new FileSystemResource(File.createTempFile("foo", "txt")); // when - FlatFileItemWriter writer = new FlatFileItemWriterBuilder<>().name("personWriter") + FlatFileItemWriter writer = new FlatFileItemWriterBuilder<>().name("personWriter") .resource(output) .delimited() .names("id", "name") @@ -397,10 +400,10 @@ void testSetupDelimitedLineAggregatorWithNoItemType() throws IOException { // then Object lineAggregator = ReflectionTestUtils.getField(writer, "lineAggregator"); assertNotNull(lineAggregator); - assertTrue(lineAggregator instanceof DelimitedLineAggregator); + assertInstanceOf(DelimitedLineAggregator.class, lineAggregator); Object fieldExtractor = ReflectionTestUtils.getField(lineAggregator, "fieldExtractor"); assertNotNull(fieldExtractor); - assertTrue(fieldExtractor instanceof BeanWrapperFieldExtractor); + assertInstanceOf(BeanWrapperFieldExtractor.class, fieldExtractor); } @Test @@ -422,16 +425,18 @@ record Person(int id, String name) { // then Object lineAggregator = ReflectionTestUtils.getField(writer, "lineAggregator"); assertNotNull(lineAggregator); - assertTrue(lineAggregator instanceof FormatterLineAggregator); + assertInstanceOf(FormatterLineAggregator.class, lineAggregator); Object fieldExtractor = ReflectionTestUtils.getField(lineAggregator, "fieldExtractor"); assertNotNull(fieldExtractor); - assertTrue(fieldExtractor instanceof RecordFieldExtractor); + assertInstanceOf(RecordFieldExtractor.class, fieldExtractor); } @Test void testSetupFormatterLineAggregatorWithClassItemType() throws IOException { // given WritableResource output = new FileSystemResource(File.createTempFile("foo", "txt")); + + @SuppressWarnings("unused") class Person { int id; @@ -452,10 +457,10 @@ class Person { // then Object lineAggregator = ReflectionTestUtils.getField(writer, "lineAggregator"); assertNotNull(lineAggregator); - assertTrue(lineAggregator instanceof FormatterLineAggregator); + assertInstanceOf(FormatterLineAggregator.class, lineAggregator); Object fieldExtractor = ReflectionTestUtils.getField(lineAggregator, "fieldExtractor"); assertNotNull(fieldExtractor); - assertTrue(fieldExtractor instanceof BeanWrapperFieldExtractor); + assertInstanceOf(BeanWrapperFieldExtractor.class, fieldExtractor); } @Test @@ -474,10 +479,10 @@ void testSetupFormatterLineAggregatorWithNoItemType() throws IOException { // then Object lineAggregator = ReflectionTestUtils.getField(writer, "lineAggregator"); assertNotNull(lineAggregator); - assertTrue(lineAggregator instanceof FormatterLineAggregator); + assertInstanceOf(FormatterLineAggregator.class, lineAggregator); Object fieldExtractor = ReflectionTestUtils.getField(lineAggregator, "fieldExtractor"); assertNotNull(fieldExtractor); - assertTrue(fieldExtractor instanceof BeanWrapperFieldExtractor); + assertInstanceOf(BeanWrapperFieldExtractor.class, fieldExtractor); } private void validateBuilderFlags(FlatFileItemWriter writer, String encoding) { 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)); + } + } diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/mapping/RecordFieldSetMapperTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/mapping/RecordFieldSetMapperTests.java index a3fc68ff44..3a1dde5eef 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/mapping/RecordFieldSetMapperTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/mapping/RecordFieldSetMapperTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2024 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. @@ -16,7 +16,6 @@ package org.springframework.batch.item.file.mapping; import org.junit.jupiter.api.Test; - import org.springframework.batch.item.file.transform.DefaultFieldSet; import org.springframework.batch.item.file.transform.FieldSet; @@ -26,6 +25,7 @@ /** * @author Mahmoud Ben Hassine + * @author Seungyong Hong */ class RecordFieldSetMapperTests { @@ -68,7 +68,23 @@ void testMapFieldSetWhenFieldNamesAreNotSpecified() { assertEquals("Field names must be specified", exception.getMessage()); } + @Test + void testMapFieldSetWhenEmptyRecord() { + // given + RecordFieldSetMapper mapper = new RecordFieldSetMapper<>(EmptyRecord.class); + FieldSet fieldSet = new DefaultFieldSet(new String[0], new String[0]); + + // when + EmptyRecord empty = mapper.mapFieldSet(fieldSet); + + // then + assertNotNull(empty); + } + record Person(int id, String name) { } + record EmptyRecord() { + } + } diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/function/PredicateFilteringItemProcessorTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/function/PredicateFilteringItemProcessorTests.java index aa6b79d456..9b74b40f21 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/function/PredicateFilteringItemProcessorTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/function/PredicateFilteringItemProcessorTests.java @@ -15,8 +15,6 @@ */ package org.springframework.batch.item.function; -import java.util.function.Predicate; - import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -27,8 +25,6 @@ */ class PredicateFilteringItemProcessorTests { - private final Predicate foos = item -> item.startsWith("foo"); - @Test void testMandatoryPredicate() { Assertions.assertThrows(IllegalArgumentException.class, () -> new PredicateFilteringItemProcessor(null), @@ -38,7 +34,8 @@ void testMandatoryPredicate() { @Test void testProcess() throws Exception { // given - PredicateFilteringItemProcessor processor = new PredicateFilteringItemProcessor<>(this.foos); + PredicateFilteringItemProcessor processor = new PredicateFilteringItemProcessor<>( + item -> item.startsWith("foo")); // when & then Assertions.assertNull(processor.process("foo1")); diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/kafka/KafkaItemReaderIntegrationTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/kafka/KafkaItemReaderIntegrationTests.java index 11d36c89e1..6c8f43b2f9 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/kafka/KafkaItemReaderIntegrationTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/kafka/KafkaItemReaderIntegrationTests.java @@ -25,6 +25,8 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; +import org.apache.kafka.clients.admin.AdminClient; +import org.apache.kafka.clients.admin.AdminClientConfig; import org.apache.kafka.clients.admin.NewTopic; import org.apache.kafka.clients.consumer.ConsumerConfig; import org.apache.kafka.clients.consumer.OffsetAndMetadata; @@ -36,14 +38,15 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.batch.item.ExecutionContext; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.kafka.core.DefaultKafkaProducerFactory; import org.springframework.kafka.core.KafkaTemplate; import org.springframework.kafka.core.ProducerFactory; -import org.springframework.kafka.test.EmbeddedKafkaBroker; -import org.springframework.kafka.test.context.EmbeddedKafka; import org.springframework.kafka.test.utils.KafkaTestUtils; import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.kafka.KafkaContainer; +import org.testcontainers.utility.DockerImageName; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsInAnyOrder; @@ -54,13 +57,17 @@ /** * @author Mathieu Ouellet * @author Mahmoud Ben Hassine + * @author François Martin + * @author Patrick Baumgartner */ -@EmbeddedKafka +@Testcontainers(disabledWithoutDocker = true) @ExtendWith(SpringExtension.class) class KafkaItemReaderIntegrationTests { - @Autowired - private EmbeddedKafkaBroker embeddedKafka; + private static final DockerImageName KAFKA_IMAGE = DockerImageName.parse("apache/kafka:4.0.0"); + + @Container + public static KafkaContainer kafka = new KafkaContainer(KAFKA_IMAGE); private KafkaItemReader reader; @@ -69,21 +76,24 @@ class KafkaItemReaderIntegrationTests { private Properties consumerProperties; @BeforeAll - static void setUpTopics(@Autowired EmbeddedKafkaBroker embeddedKafka) { - embeddedKafka.addTopics(new NewTopic("topic1", 1, (short) 1), new NewTopic("topic2", 2, (short) 1), - new NewTopic("topic3", 1, (short) 1), new NewTopic("topic4", 2, (short) 1), - new NewTopic("topic5", 1, (short) 1), new NewTopic("topic6", 1, (short) 1)); + static void setUpTopics() { + Properties properties = new Properties(); + properties.put(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, kafka.getBootstrapServers()); + try (AdminClient adminClient = AdminClient.create(properties)) { + adminClient.createTopics(List.of(new NewTopic("topic1", 1, (short) 1), new NewTopic("topic2", 2, (short) 1), + new NewTopic("topic3", 1, (short) 1), new NewTopic("topic4", 2, (short) 1), + new NewTopic("topic5", 1, (short) 1), new NewTopic("topic6", 1, (short) 1))); + } } @BeforeEach void setUp() { - Map producerProperties = KafkaTestUtils.producerProps(embeddedKafka); + Map producerProperties = KafkaTestUtils.producerProps(kafka.getBootstrapServers()); ProducerFactory producerFactory = new DefaultKafkaProducerFactory<>(producerProperties); this.template = new KafkaTemplate<>(producerFactory); this.consumerProperties = new Properties(); - this.consumerProperties.setProperty(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, - embeddedKafka.getBrokersAsString()); + this.consumerProperties.setProperty(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, kafka.getBootstrapServers()); this.consumerProperties.setProperty(ConsumerConfig.GROUP_ID_CONFIG, "1"); this.consumerProperties.setProperty(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName()); @@ -186,8 +196,8 @@ void testReadFromSinglePartitionFromTheOffsetStoredInKafka() throws Exception { this.reader.close(); // The offset stored in Kafka should be equal to 2 at this point - OffsetAndMetadata currentOffset = KafkaTestUtils.getCurrentOffset(embeddedKafka.getBrokersAsString(), "1", - "topic6", 0); + OffsetAndMetadata currentOffset = KafkaTestUtils.getCurrentOffset(kafka.getBootstrapServers(), "1", "topic6", + 0); assertEquals(2, currentOffset.offset()); // second run (with same consumer group ID): new messages arrived since the last diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/queue/builder/BlockingQueueItemWriterBuilderTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/queue/builder/BlockingQueueItemWriterBuilderTests.java index 6a8eec4cd8..7c80c5efd6 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/queue/builder/BlockingQueueItemWriterBuilderTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/queue/builder/BlockingQueueItemWriterBuilderTests.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. @@ -20,7 +20,6 @@ import org.junit.jupiter.api.Test; -import org.springframework.batch.item.queue.BlockingQueueItemReader; import org.springframework.batch.item.queue.BlockingQueueItemWriter; import org.springframework.test.util.ReflectionTestUtils; diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/redis/RedisItemReaderIntegrationTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/redis/RedisItemReaderIntegrationTests.java new file mode 100644 index 0000000000..66e733fcfb --- /dev/null +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/redis/RedisItemReaderIntegrationTests.java @@ -0,0 +1,126 @@ +/* + * 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.redis; + +import com.redis.testcontainers.RedisContainer; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.springframework.batch.item.ExecutionContext; +import org.springframework.batch.item.redis.example.Person; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.connection.RedisStandaloneConfiguration; +import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; +import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.ScanOptions; +import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer; +import org.springframework.data.redis.serializer.StringRedisSerializer; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.DockerImageName; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsInAnyOrder; + +/** + * @author Hyunwoo Jung + */ +@Testcontainers(disabledWithoutDocker = true) +@ExtendWith(SpringExtension.class) +class RedisItemReaderIntegrationTests { + + private static final DockerImageName REDIS_IMAGE = DockerImageName.parse("redis:8.0.3"); + + @Container + public static RedisContainer redis = new RedisContainer(REDIS_IMAGE); + + private RedisItemReader reader; + + private RedisTemplate template; + + @BeforeEach + void setUp() { + this.template = setUpRedisTemplate(lettuceConnectionFactory()); + } + + @AfterEach + void tearDown() { + this.template.getConnectionFactory().getConnection().serverCommands().flushAll(); + } + + @ParameterizedTest + @MethodSource("connectionFactories") + void testRead(RedisConnectionFactory connectionFactory) throws Exception { + this.template.opsForValue().set("person:1", new Person(1, "foo")); + this.template.opsForValue().set("person:2", new Person(2, "bar")); + this.template.opsForValue().set("person:3", new Person(3, "baz")); + this.template.opsForValue().set("person:4", new Person(4, "qux")); + this.template.opsForValue().set("person:5", new Person(5, "quux")); + + RedisTemplate redisTemplate = setUpRedisTemplate(connectionFactory); + ScanOptions scanOptions = ScanOptions.scanOptions().match("person:*").count(10).build(); + this.reader = new RedisItemReader<>(redisTemplate, scanOptions); + + this.reader.open(new ExecutionContext()); + + List items = new ArrayList<>(); + for (int i = 0; i < 5; i++) { + items.add(this.reader.read()); + } + + assertThat(items, containsInAnyOrder(new Person(1, "foo"), new Person(2, "bar"), new Person(3, "baz"), + new Person(4, "qux"), new Person(5, "quux"))); + } + + private RedisTemplate setUpRedisTemplate(RedisConnectionFactory redisConnectionFactory) { + RedisTemplate redisTemplate = new RedisTemplate<>(); + redisTemplate.setConnectionFactory(redisConnectionFactory); + redisTemplate.setKeySerializer(new StringRedisSerializer()); + redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer()); + redisTemplate.afterPropertiesSet(); + + return redisTemplate; + } + + private static Stream connectionFactories() { + return Stream.of(Arguments.of(lettuceConnectionFactory()), Arguments.of(jedisConnectionFactory())); + } + + private static RedisConnectionFactory lettuceConnectionFactory() { + LettuceConnectionFactory lettuceConnectionFactory = new LettuceConnectionFactory( + new RedisStandaloneConfiguration(redis.getRedisHost(), redis.getRedisPort())); + lettuceConnectionFactory.afterPropertiesSet(); + return lettuceConnectionFactory; + } + + private static JedisConnectionFactory jedisConnectionFactory() { + JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory( + new RedisStandaloneConfiguration(redis.getRedisHost(), redis.getRedisPort())); + jedisConnectionFactory.afterPropertiesSet(); + return jedisConnectionFactory; + } + +} diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/redis/RedisItemWriterIntegrationTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/redis/RedisItemWriterIntegrationTests.java new file mode 100644 index 0000000000..73891407a1 --- /dev/null +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/redis/RedisItemWriterIntegrationTests.java @@ -0,0 +1,144 @@ +/* + * 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.redis; + +import com.redis.testcontainers.RedisContainer; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.springframework.batch.item.Chunk; +import org.springframework.batch.item.redis.example.Person; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.connection.RedisStandaloneConfiguration; +import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; +import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer; +import org.springframework.data.redis.serializer.StringRedisSerializer; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.DockerImageName; + +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * @author Hyunwoo Jung + */ +@Testcontainers(disabledWithoutDocker = true) +@ExtendWith(SpringExtension.class) +class RedisItemWriterIntegrationTests { + + private static final DockerImageName REDIS_IMAGE = DockerImageName.parse("redis:8.0.3"); + + @Container + public static RedisContainer redis = new RedisContainer(REDIS_IMAGE); + + private RedisItemWriter writer; + + private RedisTemplate template; + + @BeforeEach + void setUp() { + this.template = setUpRedisTemplate(lettuceConnectionFactory()); + } + + @AfterEach + void tearDown() { + this.template.getConnectionFactory().getConnection().serverCommands().flushAll(); + } + + @ParameterizedTest + @MethodSource("connectionFactories") + void testWriteWithLettuce(RedisConnectionFactory connectionFactory) throws Exception { + RedisTemplate redisTemplate = setUpRedisTemplate(connectionFactory); + this.writer = new RedisItemWriter<>(); + this.writer.setRedisTemplate(redisTemplate); + this.writer.setItemKeyMapper(p -> "person:" + p.getId()); + this.writer.setDelete(false); + + Chunk items = new Chunk<>(new Person(1, "foo"), new Person(2, "bar"), new Person(3, "baz"), + new Person(4, "qux"), new Person(5, "quux")); + this.writer.write(items); + + assertEquals(new Person(1, "foo"), this.template.opsForValue().get("person:1")); + assertEquals(new Person(2, "bar"), this.template.opsForValue().get("person:2")); + assertEquals(new Person(3, "baz"), this.template.opsForValue().get("person:3")); + assertEquals(new Person(4, "qux"), this.template.opsForValue().get("person:4")); + assertEquals(new Person(5, "quux"), this.template.opsForValue().get("person:5")); + } + + @ParameterizedTest + @MethodSource("connectionFactories") + void testDelete(RedisConnectionFactory connectionFactory) throws Exception { + this.template.opsForValue().set("person:1", new Person(1, "foo")); + this.template.opsForValue().set("person:2", new Person(2, "bar")); + this.template.opsForValue().set("person:3", new Person(3, "baz")); + this.template.opsForValue().set("person:4", new Person(4, "qux")); + this.template.opsForValue().set("person:5", new Person(5, "quux")); + + RedisTemplate redisTemplate = setUpRedisTemplate(connectionFactory); + this.writer = new RedisItemWriter<>(); + this.writer.setRedisTemplate(redisTemplate); + this.writer.setItemKeyMapper(p -> "person:" + p.getId()); + this.writer.setDelete(true); + + Chunk items = new Chunk<>(new Person(1, "foo"), new Person(2, "bar"), new Person(3, "baz"), + new Person(4, "qux"), new Person(5, "quux")); + this.writer.write(items); + + assertFalse(this.template.hasKey("person:1")); + assertFalse(this.template.hasKey("person:2")); + assertFalse(this.template.hasKey("person:3")); + assertFalse(this.template.hasKey("person:4")); + assertFalse(this.template.hasKey("person:5")); + } + + private RedisTemplate setUpRedisTemplate(RedisConnectionFactory redisConnectionFactory) { + RedisTemplate redisTemplate = new RedisTemplate<>(); + redisTemplate.setConnectionFactory(redisConnectionFactory); + redisTemplate.setKeySerializer(new StringRedisSerializer()); + redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer()); + redisTemplate.afterPropertiesSet(); + + return redisTemplate; + } + + private static Stream connectionFactories() { + return Stream.of(Arguments.of(lettuceConnectionFactory()), Arguments.of(jedisConnectionFactory())); + } + + private static RedisConnectionFactory lettuceConnectionFactory() { + LettuceConnectionFactory lettuceConnectionFactory = new LettuceConnectionFactory( + new RedisStandaloneConfiguration(redis.getRedisHost(), redis.getRedisPort())); + lettuceConnectionFactory.afterPropertiesSet(); + return lettuceConnectionFactory; + } + + private static JedisConnectionFactory jedisConnectionFactory() { + JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory( + new RedisStandaloneConfiguration(redis.getRedisHost(), redis.getRedisPort())); + jedisConnectionFactory.afterPropertiesSet(); + return jedisConnectionFactory; + } + +} diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/redis/example/Person.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/redis/example/Person.java new file mode 100644 index 0000000000..2cc819ab99 --- /dev/null +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/redis/example/Person.java @@ -0,0 +1,66 @@ +/* + * 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.redis.example; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Objects; + +/** + * @author Hyunwoo Jung + */ +public class Person implements Serializable { + + @Serial + private static final long serialVersionUID = 2396556853218591048L; + + private long id; + + private String name; + + public Person(long id, String name) { + this.id = id; + this.name = name; + } + + public long getId() { + return id; + } + + public String getName() { + return name; + } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) + return false; + Person person = (Person) o; + return id == person.id && Objects.equals(name, person.name); + } + + @Override + public int hashCode() { + return Objects.hash(id, name); + } + + @Override + public String toString() { + return "Person{id=" + id + ", name=" + name + "}"; + } + +} 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/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 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() { 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(); + } + } 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"); 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/StaxEventItemReaderTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/xml/StaxEventItemReaderTests.java index 19bfb27626..c1a5ecad43 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/xml/StaxEventItemReaderTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/xml/StaxEventItemReaderTests.java @@ -700,8 +700,7 @@ private List readRecordsInsideFragment(XMLEventReader eventReader, QNa List events = new ArrayList<>(); do { eventInsideFragment = eventReader.peek(); - if (eventInsideFragment instanceof EndElement - && fragmentName.equals(((EndElement) eventInsideFragment).getName())) { + if (eventInsideFragment instanceof EndElement endElement && fragmentName.equals(endElement.getName())) { break; } events.add(eventReader.nextEvent()); 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..9f2085f7f4 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 { @@ -383,7 +387,7 @@ void testWriteWithHeader() throws Exception { writer.open(executionContext); writer.write(items); String content = getOutputFileContent(); - assertTrue(content.contains(("
")), "Wrong content: " + content); + assertTrue(content.contains("
"), "Wrong content: " + content); assertTrue(content.contains(TEST_STRING), "Wrong content: " + content); } @@ -608,10 +612,10 @@ void testWriteRootTagWithNamespace() throws Exception { writer.write(items); writer.close(); String content = getOutputFileContent(); - assertTrue(content.contains(("")), + assertTrue(content.contains(""), "Wrong content: " + content); assertTrue(content.contains(TEST_STRING), "Wrong content: " + content); - assertTrue(content.contains(("")), "Wrong content: " + content); + assertTrue(content.contains(""), "Wrong content: " + content); } /** @@ -627,11 +631,11 @@ void testWriteRootTagWithNamespaceAndPrefix() throws Exception { writer.write(items); writer.close(); String content = getOutputFileContent(); - assertTrue(content.contains(("")), + assertTrue(content.contains(""), "Wrong content: " + content); assertTrue(content.contains(NS_TEST_STRING), "Wrong content: " + content); - assertTrue(content.contains(("")), "Wrong content: " + content); - assertTrue(content.contains((""), "Wrong content: " + content); + assertTrue(content.contains("")), + ""), "Wrong content: " + content); assertTrue(content.contains(FOO_TEST_STRING), "Wrong content: " + content); - assertTrue(content.contains(("")), "Wrong content: " + content); - assertTrue(content.contains((""), "Wrong content: " + content); + assertTrue(content.contains("", 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 -> { diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/xml/TransactionalStaxEventItemWriterTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/xml/TransactionalStaxEventItemWriterTests.java index 230c084604..1c2f73c3a5 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/xml/TransactionalStaxEventItemWriterTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/xml/TransactionalStaxEventItemWriterTests.java @@ -138,7 +138,7 @@ void testWriteWithHeaderAfterRollback() throws Exception { }); writer.close(); String content = outputFileContent(); - assertEquals(1, StringUtils.countOccurrencesOf(content, ("
")), "Wrong content: " + content); + assertEquals(1, StringUtils.countOccurrencesOf(content, "
"), "Wrong content: " + content); assertEquals(1, StringUtils.countOccurrencesOf(content, TEST_STRING), "Wrong content: " + content); } @@ -183,7 +183,7 @@ void testWriteWithHeaderAfterFlushAndRollback() throws Exception { })); writer.close(); String content = outputFileContent(); - assertEquals(1, StringUtils.countOccurrencesOf(content, ("
")), "Wrong content: " + content); + assertEquals(1, StringUtils.countOccurrencesOf(content, "
"), "Wrong content: " + content); assertEquals(1, StringUtils.countOccurrencesOf(content, TEST_STRING), "Wrong content: " + content); } diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/jms/ExternalRetryInBatchTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/jms/ExternalRetryInBatchTests.java index 2bad8747d6..2f5ec2bb0a 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/jms/ExternalRetryInBatchTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/jms/ExternalRetryInBatchTests.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. @@ -27,7 +27,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jms.core.JmsTemplate; -import org.springframework.lang.Nullable; import org.springframework.retry.RecoveryCallback; import org.springframework.retry.RetryCallback; import org.springframework.retry.policy.SimpleRetryPolicy; @@ -70,14 +69,10 @@ void onSetUp() { JdbcTestUtils.deleteFromTables(jdbcTemplate, "T_BARS"); jmsTemplate.convertAndSend("queue", "foo"); jmsTemplate.convertAndSend("queue", "bar"); - provider = new ItemReader<>() { - @Nullable - @Override - public String read() { - String text = (String) jmsTemplate.receiveAndConvert("queue"); - list.add(text); - return text; - } + provider = () -> { + String text = (String) jmsTemplate.receiveAndConvert("queue"); + list.add(text); + return text; }; retryTemplate = new RetryTemplate(); } diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/repeat/exception/SimpleLimitExceptionHandlerTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/repeat/exception/SimpleLimitExceptionHandlerTests.java index e064c604c5..d5cd02e00c 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/repeat/exception/SimpleLimitExceptionHandlerTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/repeat/exception/SimpleLimitExceptionHandlerTests.java @@ -24,6 +24,8 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -152,19 +154,14 @@ void testExceptionNotThrownBelowLimit() throws Throwable { handler.setLimit(EXCEPTION_LIMIT); handler.afterPropertiesSet(); - @SuppressWarnings("serial") - List throwables = new ArrayList<>() { - { - for (int i = 0; i < (EXCEPTION_LIMIT); i++) { - add(new RuntimeException("below exception limit")); - } - } - }; + List exceptions = IntStream.range(0, EXCEPTION_LIMIT) + .mapToObj(__ -> new RuntimeException("below exception limit")) + .toList(); RepeatContextSupport context = new RepeatContextSupport(null); - for (Throwable throwable : throwables) { - assertDoesNotThrow(() -> handler.handleException(context, throwable)); + for (RuntimeException exception : exceptions) { + assertDoesNotThrow(() -> handler.handleException(context, exception)); } } @@ -180,22 +177,17 @@ void testExceptionThrownAboveLimit() throws Throwable { handler.setLimit(EXCEPTION_LIMIT); handler.afterPropertiesSet(); - @SuppressWarnings("serial") - List throwables = new ArrayList<>() { - { - for (int i = 0; i < (EXCEPTION_LIMIT); i++) { - add(new RuntimeException("below exception limit")); - } - } - }; + List exceptions = IntStream.range(0, EXCEPTION_LIMIT) + .mapToObj(__ -> new RuntimeException("below exception limit")) + .collect(Collectors.toCollection(ArrayList::new)); - throwables.add(new RuntimeException("above exception limit")); + exceptions.add(new RuntimeException("above exception limit")); RepeatContextSupport context = new RepeatContextSupport(null); Exception expected = assertThrows(RuntimeException.class, () -> { - for (Throwable throwable : throwables) { - handler.handleException(context, throwable); + for (Throwable exception : exceptions) { + handler.handleException(context, exception); } }); assertEquals("above exception limit", expected.getMessage()); diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/repeat/jms/SynchronousTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/repeat/jms/SynchronousTests.java index bded5597a9..365ce8a5ed 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/repeat/jms/SynchronousTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/repeat/jms/SynchronousTests.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. @@ -155,7 +155,7 @@ void JpaNativeQueryProviderIntegrationTeststestPartialRollback() { // The JmsTemplate is used elsewhere outside a transaction, so // we need to use one here that is transaction aware. final JmsTemplate txJmsTemplate = new JmsTemplate( - (ConnectionFactory) applicationContext.getBean("txAwareConnectionFactory")); + applicationContext.getBean("txAwareConnectionFactory", ConnectionFactory.class)); txJmsTemplate.setReceiveTimeout(100L); txJmsTemplate.setSessionTransacted(true); diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/repeat/support/ResultHolderResultQueueTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/repeat/support/ResultHolderResultQueueTests.java index 0f793a3b23..9d516b3e8f 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/repeat/support/ResultHolderResultQueueTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/repeat/support/ResultHolderResultQueueTests.java @@ -22,6 +22,7 @@ import org.springframework.batch.repeat.RepeatContext; import org.springframework.batch.repeat.RepeatStatus; +@SuppressWarnings("removal") class ResultHolderResultQueueTests { private final ResultHolderResultQueue queue = new ResultHolderResultQueue(10); 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/repeat/support/ThrottleLimitResultQueueTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/repeat/support/ThrottleLimitResultQueueTests.java index a93896492e..68fe02f30e 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/repeat/support/ThrottleLimitResultQueueTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/repeat/support/ThrottleLimitResultQueueTests.java @@ -29,6 +29,7 @@ * @author Mahmoud Ben Hassine * */ +@SuppressWarnings("removal") class ThrottleLimitResultQueueTests { private final ThrottleLimitResultQueue queue = new ThrottleLimitResultQueue<>(1); diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/retry/jms/ExternalRetryTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/retry/jms/ExternalRetryTests.java index 4ec1aeb488..96faca6aaf 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/retry/jms/ExternalRetryTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/retry/jms/ExternalRetryTests.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. @@ -28,7 +28,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jms.core.JmsTemplate; -import org.springframework.lang.Nullable; import org.springframework.retry.RecoveryCallback; import org.springframework.retry.RetryCallback; import org.springframework.retry.support.DefaultRetryState; @@ -62,14 +61,10 @@ void onSetUp() { getMessages(); // drain queue JdbcTestUtils.deleteFromTables(jdbcTemplate, "T_BARS"); jmsTemplate.convertAndSend("queue", "foo"); - provider = new ItemReader<>() { - @Nullable - @Override - public String read() { - String text = (String) jmsTemplate.receiveAndConvert("queue"); - list.add(text); - return text; - } + provider = () -> { + String text = (String) jmsTemplate.receiveAndConvert("queue"); + list.add(text); + return text; }; retryTemplate = new RetryTemplate(); } diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/support/PropertiesConverterTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/support/PropertiesConverterTests.java index 43e537c79b..019146491a 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/support/PropertiesConverterTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/support/PropertiesConverterTests.java @@ -32,6 +32,7 @@ * @author Robert Kasanicky * @author Mahmoud Ben Hassine */ +@SuppressWarnings("removal") class PropertiesConverterTests { @Test 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-infrastructure/src/test/java/org/springframework/batch/support/transaction/ConcurrentTransactionAwareProxyTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/support/transaction/ConcurrentTransactionAwareProxyTests.java index baef448a31..7cd708dd10 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/support/transaction/ConcurrentTransactionAwareProxyTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/support/transaction/ConcurrentTransactionAwareProxyTests.java @@ -108,7 +108,7 @@ void testConcurrentTransactionalMap() { @Test void testTransactionalContains() { final Map> map = TransactionAwareProxyFactory.createAppendOnlyTransactionalMap(); - boolean result = new TransactionTemplate(transactionManager).execute(status -> map.containsKey("foo")); + boolean result = new TransactionTemplate(transactionManager).execute(status -> map.containsKey(0L)); assertFalse(result); } @@ -177,7 +177,7 @@ private void testMap(final Map> map) throws Exception for (int i = 0; i < outerMax; i++) { for (int j = 0; j < numberOfKeys; j++) { - final long id = j * 1000 + 123L + i; + final long id = j * 1000L + 123L + i; completionService.submit(() -> { List list = new ArrayList<>(); 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 + 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 diff --git a/spring-batch-integration/pom.xml b/spring-batch-integration/pom.xml index 121eaeb8fc..fa06f58bce 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 + 6.0.0-SNAPSHOT spring-batch-integration Spring Batch Integration @@ -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-integration/src/main/java/org/springframework/batch/integration/aot/IntegrationRuntimeHints.java b/spring-batch-integration/src/main/java/org/springframework/batch/integration/aot/IntegrationRuntimeHints.java index 5f42caf800..a043f21a74 100644 --- a/spring-batch-integration/src/main/java/org/springframework/batch/integration/aot/IntegrationRuntimeHints.java +++ b/spring-batch-integration/src/main/java/org/springframework/batch/integration/aot/IntegrationRuntimeHints.java @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 the original author or authors. + * Copyright 2023-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,7 +15,6 @@ */ package org.springframework.batch.integration.aot; -import org.springframework.aot.hint.MemberCategory; import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.RuntimeHintsRegistrar; import org.springframework.batch.integration.chunk.ChunkRequest; @@ -28,6 +27,7 @@ * AOT hints for Spring Batch integration module. * * @author Mahmoud Ben Hassine + * @author Andrey Litvitski * @since 5.0.1 */ public class IntegrationRuntimeHints implements RuntimeHintsRegistrar { @@ -35,11 +35,10 @@ public class IntegrationRuntimeHints implements RuntimeHintsRegistrar { @Override public void registerHints(RuntimeHints hints, ClassLoader classLoader) { // reflection hints - MemberCategory[] memberCategories = MemberCategory.values(); - hints.reflection().registerType(ChunkRequest.class, memberCategories); - hints.reflection().registerType(ChunkResponse.class, memberCategories); - hints.reflection().registerType(StepExecutionRequestHandler.class, memberCategories); - hints.reflection().registerType(MessageChannelPartitionHandler.class, memberCategories); + hints.reflection().registerType(ChunkRequest.class); + hints.reflection().registerType(ChunkResponse.class); + hints.reflection().registerType(StepExecutionRequestHandler.class); + hints.reflection().registerType(MessageChannelPartitionHandler.class); // serialization hints hints.serialization().registerType(ChunkRequest.class); diff --git a/spring-batch-integration/src/main/java/org/springframework/batch/integration/async/AsyncItemProcessor.java b/spring-batch-integration/src/main/java/org/springframework/batch/integration/async/AsyncItemProcessor.java index d6667974d7..286ede4ea8 100644 --- a/spring-batch-integration/src/main/java/org/springframework/batch/integration/async/AsyncItemProcessor.java +++ b/spring-batch-integration/src/main/java/org/springframework/batch/integration/async/AsyncItemProcessor.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,7 +18,8 @@ import java.util.concurrent.Future; import java.util.concurrent.FutureTask; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.step.StepExecution; +import org.springframework.batch.core.listener.ItemProcessListener; import org.springframework.batch.core.scope.context.StepContext; import org.springframework.batch.core.scope.context.StepSynchronizationManager; import org.springframework.batch.item.ItemProcessor; @@ -38,9 +39,7 @@ * lifecycle and stats limitations (since the framework doesn't know what the result of * the processor is). While not an exhaustive list, things like * {@link StepExecution#getFilterCount()} will not reflect the number of filtered items - * and - * {@link org.springframework.batch.core.ItemProcessListener#onProcessError(Object, Exception)} - * will not be called. + * and {@link ItemProcessListener#onProcessError(Object, Exception)} will not be called. * * @author Dave Syer * @author Mahmoud Ben Hassine diff --git a/spring-batch-integration/src/main/java/org/springframework/batch/integration/async/AsyncItemWriter.java b/spring-batch-integration/src/main/java/org/springframework/batch/integration/async/AsyncItemWriter.java index 58e9e9086a..b300409561 100644 --- a/spring-batch-integration/src/main/java/org/springframework/batch/integration/async/AsyncItemWriter.java +++ b/spring-batch-integration/src/main/java/org/springframework/batch/integration/async/AsyncItemWriter.java @@ -75,10 +75,10 @@ public void write(Chunk> items) throws Exception { catch (ExecutionException e) { Throwable cause = e.getCause(); - if (cause instanceof Exception) { + if (cause instanceof Exception exception) { logger.debug("An exception was thrown while processing an item", e); - throw (Exception) cause; + throw exception; } else { throw e; diff --git a/spring-batch-integration/src/main/java/org/springframework/batch/integration/async/StepExecutionInterceptor.java b/spring-batch-integration/src/main/java/org/springframework/batch/integration/async/StepExecutionInterceptor.java index c81f3bd1a9..3597a7182d 100644 --- a/spring-batch-integration/src/main/java/org/springframework/batch/integration/async/StepExecutionInterceptor.java +++ b/spring-batch-integration/src/main/java/org/springframework/batch/integration/async/StepExecutionInterceptor.java @@ -15,7 +15,7 @@ */ package org.springframework.batch.integration.async; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.scope.context.StepContext; import org.springframework.batch.core.scope.context.StepSynchronizationManager; import org.springframework.integration.support.MessageBuilder; diff --git a/spring-batch-integration/src/main/java/org/springframework/batch/integration/chunk/ChunkMessageChannelItemWriter.java b/spring-batch-integration/src/main/java/org/springframework/batch/integration/chunk/ChunkMessageChannelItemWriter.java index 4475e2d1a6..1e6c15a7d0 100644 --- a/spring-batch-integration/src/main/java/org/springframework/batch/integration/chunk/ChunkMessageChannelItemWriter.java +++ b/spring-batch-integration/src/main/java/org/springframework/batch/integration/chunk/ChunkMessageChannelItemWriter.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. @@ -27,9 +27,9 @@ import org.apache.commons.logging.LogFactory; import org.springframework.batch.core.BatchStatus; import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.StepContribution; -import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.StepExecutionListener; +import org.springframework.batch.core.step.StepContribution; +import org.springframework.batch.core.step.StepExecution; +import org.springframework.batch.core.listener.StepExecutionListener; import org.springframework.batch.item.Chunk; import org.springframework.batch.item.ExecutionContext; import org.springframework.batch.item.ItemStream; @@ -260,11 +260,11 @@ protected void getNextResult() throws AsynchronousFailureException { * {@link AsynchronousFailureException}. */ protected static AsynchronousFailureException wrapIfNecessary(Throwable throwable) { - if (throwable instanceof Error) { - throw (Error) throwable; + if (throwable instanceof Error error) { + throw error; } - else if (throwable instanceof AsynchronousFailureException) { - return (AsynchronousFailureException) throwable; + else if (throwable instanceof AsynchronousFailureException exception) { + return exception; } else { return new AsynchronousFailureException("Exception in remote process", throwable); diff --git a/spring-batch-integration/src/main/java/org/springframework/batch/integration/chunk/ChunkProcessorChunkHandler.java b/spring-batch-integration/src/main/java/org/springframework/batch/integration/chunk/ChunkProcessorChunkHandler.java index 73d45fcb42..5c9bc9a9c5 100644 --- a/spring-batch-integration/src/main/java/org/springframework/batch/integration/chunk/ChunkProcessorChunkHandler.java +++ b/spring-batch-integration/src/main/java/org/springframework/batch/integration/chunk/ChunkProcessorChunkHandler.java @@ -18,8 +18,8 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.batch.core.JobInterruptedException; -import org.springframework.batch.core.StepContribution; +import org.springframework.batch.core.job.JobInterruptedException; +import org.springframework.batch.core.step.StepContribution; import org.springframework.batch.item.Chunk; import org.springframework.batch.core.step.item.ChunkProcessor; import org.springframework.batch.core.step.item.FaultTolerantChunkProcessor; diff --git a/spring-batch-integration/src/main/java/org/springframework/batch/integration/chunk/ChunkRequest.java b/spring-batch-integration/src/main/java/org/springframework/batch/integration/chunk/ChunkRequest.java index 7abd58a359..3923787d99 100644 --- a/spring-batch-integration/src/main/java/org/springframework/batch/integration/chunk/ChunkRequest.java +++ b/spring-batch-integration/src/main/java/org/springframework/batch/integration/chunk/ChunkRequest.java @@ -18,7 +18,7 @@ import java.io.Serializable; -import org.springframework.batch.core.StepContribution; +import org.springframework.batch.core.step.StepContribution; import org.springframework.batch.item.Chunk; /** diff --git a/spring-batch-integration/src/main/java/org/springframework/batch/integration/chunk/ChunkResponse.java b/spring-batch-integration/src/main/java/org/springframework/batch/integration/chunk/ChunkResponse.java index 30965cb77e..088599d5d7 100644 --- a/spring-batch-integration/src/main/java/org/springframework/batch/integration/chunk/ChunkResponse.java +++ b/spring-batch-integration/src/main/java/org/springframework/batch/integration/chunk/ChunkResponse.java @@ -18,7 +18,7 @@ import java.io.Serializable; -import org.springframework.batch.core.StepContribution; +import org.springframework.batch.core.step.StepContribution; import org.springframework.lang.Nullable; /** diff --git a/spring-batch-integration/src/main/java/org/springframework/batch/integration/chunk/RemoteChunkHandlerFactoryBean.java b/spring-batch-integration/src/main/java/org/springframework/batch/integration/chunk/RemoteChunkHandlerFactoryBean.java index 9def1ae7c3..25e3c9fd9b 100644 --- a/spring-batch-integration/src/main/java/org/springframework/batch/integration/chunk/RemoteChunkHandlerFactoryBean.java +++ b/spring-batch-integration/src/main/java/org/springframework/batch/integration/chunk/RemoteChunkHandlerFactoryBean.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. @@ -20,8 +20,8 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.batch.core.StepContribution; -import org.springframework.batch.core.StepExecutionListener; +import org.springframework.batch.core.step.StepContribution; +import org.springframework.batch.core.listener.StepExecutionListener; import org.springframework.batch.item.Chunk; import org.springframework.batch.core.step.item.ChunkOrientedTasklet; import org.springframework.batch.core.step.item.ChunkProcessor; @@ -122,12 +122,12 @@ public ChunkHandler getObject() throws Exception { stepContributionSource = (StepContributionSource) chunkWriter; } - Assert.state(step instanceof TaskletStep, "Step [" + step.getName() + "] must be a TaskletStep"); + Assert.state(step != null, "A TaskletStep must be provided"); if (logger.isDebugEnabled()) { logger.debug("Converting TaskletStep with name=" + step.getName()); } - Tasklet tasklet = getTasklet(step); + Tasklet tasklet = step.getTasklet(); Assert.state(tasklet instanceof ChunkOrientedTasklet, "Tasklet must be ChunkOrientedTasklet in step=" + step.getName()); @@ -139,8 +139,8 @@ public ChunkHandler getObject() throws Exception { + "] because it already has a remote chunk writer. Use a local writer in the step."); replaceChunkProcessor((ChunkOrientedTasklet) tasklet, chunkWriter, stepContributionSource); - if (chunkWriter instanceof StepExecutionListener) { - step.registerStepExecutionListener((StepExecutionListener) chunkWriter); + if (chunkWriter instanceof StepExecutionListener stepExecutionListener) { + step.registerStepExecutionListener(stepExecutionListener); } ChunkProcessorChunkHandler handler = new ChunkProcessorChunkHandler<>(); @@ -227,15 +227,6 @@ private ChunkProcessor getChunkProcessor(ChunkOrientedTasklet tasklet) { return (ChunkProcessor) getField(tasklet, "chunkProcessor"); } - /** - * Pull a Tasklet out of a step. - * @param step a TaskletStep - * @return the Tasklet - */ - private Tasklet getTasklet(TaskletStep step) { - return (Tasklet) getField(step, "tasklet"); - } - private static Object getField(Object target, String name) { Assert.notNull(target, "Target object must not be null"); Field field = ReflectionUtils.findField(target.getClass(), name); 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..83fc6c1efa 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. @@ -15,11 +15,11 @@ */ package org.springframework.batch.integration.chunk; -import org.springframework.batch.core.ChunkListener; -import org.springframework.batch.core.ItemReadListener; -import org.springframework.batch.core.ItemWriteListener; -import org.springframework.batch.core.SkipListener; -import org.springframework.batch.core.StepExecutionListener; +import org.springframework.batch.core.listener.ChunkListener; +import org.springframework.batch.core.listener.ItemReadListener; +import org.springframework.batch.core.listener.ItemWriteListener; +import org.springframework.batch.core.listener.SkipListener; +import org.springframework.batch.core.listener.StepExecutionListener; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.step.builder.FaultTolerantStepBuilder; import org.springframework.batch.core.step.builder.StepBuilder; @@ -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/chunk/StepContributionSource.java b/spring-batch-integration/src/main/java/org/springframework/batch/integration/chunk/StepContributionSource.java index 50b599a936..227628c9ff 100644 --- a/spring-batch-integration/src/main/java/org/springframework/batch/integration/chunk/StepContributionSource.java +++ b/spring-batch-integration/src/main/java/org/springframework/batch/integration/chunk/StepContributionSource.java @@ -18,8 +18,8 @@ import java.util.Collection; -import org.springframework.batch.core.StepContribution; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.step.StepContribution; +import org.springframework.batch.core.step.StepExecution; /** * A source of {@link StepContribution} instances that can be aggregated and used to diff --git a/spring-batch-integration/src/main/java/org/springframework/batch/integration/config/annotation/BatchIntegrationConfiguration.java b/spring-batch-integration/src/main/java/org/springframework/batch/integration/config/annotation/BatchIntegrationConfiguration.java index eba18d0ed5..52ba5a4e61 100644 --- a/spring-batch-integration/src/main/java/org/springframework/batch/integration/config/annotation/BatchIntegrationConfiguration.java +++ b/spring-batch-integration/src/main/java/org/springframework/batch/integration/config/annotation/BatchIntegrationConfiguration.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. @@ -15,7 +15,6 @@ */ package org.springframework.batch.integration.config.annotation; -import org.springframework.batch.core.explore.JobExplorer; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.integration.chunk.RemoteChunkingManagerStepBuilderFactory; import org.springframework.batch.integration.chunk.RemoteChunkingWorkerBuilder; @@ -36,8 +35,6 @@ @Configuration(proxyBeanMethods = false) public class BatchIntegrationConfiguration implements InitializingBean { - private final JobExplorer jobExplorer; - private final JobRepository jobRepository; private final PlatformTransactionManager transactionManager; @@ -51,11 +48,8 @@ public class BatchIntegrationConfiguration implements InitializingBean { private RemotePartitioningWorkerStepBuilderFactory remotePartitioningWorkerStepBuilderFactory; @Autowired - public BatchIntegrationConfiguration(JobRepository jobRepository, JobExplorer jobExplorer, - PlatformTransactionManager transactionManager) { - + public BatchIntegrationConfiguration(JobRepository jobRepository, PlatformTransactionManager transactionManager) { this.jobRepository = jobRepository; - this.jobExplorer = jobExplorer; this.transactionManager = transactionManager; } @@ -85,9 +79,9 @@ public void afterPropertiesSet() throws Exception { this.transactionManager); this.remoteChunkingWorkerBuilder = new RemoteChunkingWorkerBuilder<>(); this.remotePartitioningManagerStepBuilderFactory = new RemotePartitioningManagerStepBuilderFactory( - this.jobRepository, this.jobExplorer); + this.jobRepository); this.remotePartitioningWorkerStepBuilderFactory = new RemotePartitioningWorkerStepBuilderFactory( - this.jobRepository, this.jobExplorer); + this.jobRepository); } } diff --git a/spring-batch-integration/src/main/java/org/springframework/batch/integration/config/xml/JobLaunchingGatewayParser.java b/spring-batch-integration/src/main/java/org/springframework/batch/integration/config/xml/JobLaunchingGatewayParser.java index eddb35d1e7..b8fd1a77ec 100644 --- a/spring-batch-integration/src/main/java/org/springframework/batch/integration/config/xml/JobLaunchingGatewayParser.java +++ b/spring-batch-integration/src/main/java/org/springframework/batch/integration/config/xml/JobLaunchingGatewayParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 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. @@ -17,7 +17,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.launch.JobOperator; import org.springframework.batch.integration.launch.JobLaunchingGateway; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.xml.ParserContext; @@ -28,10 +28,11 @@ /** * The parser for the Job-Launching Gateway, which will instantiate a - * {@link JobLaunchingGatewayParser}. If no {@link JobLauncher} reference has been - * provided, this parse will use the use the globally registered bean 'jobLauncher'. + * {@link JobLaunchingGatewayParser}. If no {@link JobOperator} reference has been + * provided, this parse will use the globally registered bean 'jobOperator'. * * @author Gunnar Hillert + * @author Mahmoud Ben Hassine * @since 1.3 * */ @@ -50,16 +51,16 @@ protected BeanDefinitionBuilder parseHandler(Element element, ParserContext pars final BeanDefinitionBuilder jobLaunchingGatewayBuilder = BeanDefinitionBuilder .genericBeanDefinition(JobLaunchingGateway.class); - final String jobLauncher = element.getAttribute("job-launcher"); + final String jobOperator = element.getAttribute("job-operator"); - if (StringUtils.hasText(jobLauncher)) { - jobLaunchingGatewayBuilder.addConstructorArgReference(jobLauncher); + if (StringUtils.hasText(jobOperator)) { + jobLaunchingGatewayBuilder.addConstructorArgReference(jobOperator); } else { if (logger.isDebugEnabled()) { - logger.debug("No jobLauncher specified, using default 'jobLauncher' reference instead."); + logger.debug("No jobOperator specified, using default 'jobOperator' reference instead."); } - jobLaunchingGatewayBuilder.addConstructorArgReference("jobLauncher"); + jobLaunchingGatewayBuilder.addConstructorArgReference("jobOperator"); } IntegrationNamespaceUtils.setValueIfAttributeDefined(jobLaunchingGatewayBuilder, element, "reply-timeout", diff --git a/spring-batch-integration/src/main/java/org/springframework/batch/integration/launch/JobLaunchRequest.java b/spring-batch-integration/src/main/java/org/springframework/batch/integration/launch/JobLaunchRequest.java index ceebf3428e..f2dd550cec 100644 --- a/spring-batch-integration/src/main/java/org/springframework/batch/integration/launch/JobLaunchRequest.java +++ b/spring-batch-integration/src/main/java/org/springframework/batch/integration/launch/JobLaunchRequest.java @@ -15,8 +15,8 @@ */ package org.springframework.batch.integration.launch; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.parameters.JobParameters; /** * Encapsulation of a {@link Job} and its {@link JobParameters} forming a request for a diff --git a/spring-batch-integration/src/main/java/org/springframework/batch/integration/launch/JobLaunchRequestHandler.java b/spring-batch-integration/src/main/java/org/springframework/batch/integration/launch/JobLaunchRequestHandler.java index 7de15d337b..97d472f805 100644 --- a/spring-batch-integration/src/main/java/org/springframework/batch/integration/launch/JobLaunchRequestHandler.java +++ b/spring-batch-integration/src/main/java/org/springframework/batch/integration/launch/JobLaunchRequestHandler.java @@ -16,8 +16,8 @@ package org.springframework.batch.integration.launch; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobExecutionException; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobExecutionException; /** * Interface for handling a {@link JobLaunchRequest} and returning a {@link JobExecution}. diff --git a/spring-batch-integration/src/main/java/org/springframework/batch/integration/launch/JobLaunchingGateway.java b/spring-batch-integration/src/main/java/org/springframework/batch/integration/launch/JobLaunchingGateway.java index a16365cc0a..380f1fdce1 100644 --- a/spring-batch-integration/src/main/java/org/springframework/batch/integration/launch/JobLaunchingGateway.java +++ b/spring-batch-integration/src/main/java/org/springframework/batch/integration/launch/JobLaunchingGateway.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 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. @@ -16,9 +16,9 @@ package org.springframework.batch.integration.launch; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobExecutionException; -import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobExecutionException; +import org.springframework.batch.core.launch.JobOperator; import org.springframework.integration.handler.AbstractReplyProducingMessageHandler; import org.springframework.messaging.Message; import org.springframework.messaging.MessageHandlingException; @@ -29,6 +29,7 @@ * to a {@link JobLaunchingMessageHandler}. * * @author Gunnar Hillert + * @author Mahmoud Ben Hassine * @since 1.3 */ public class JobLaunchingGateway extends AbstractReplyProducingMessageHandler { @@ -36,13 +37,13 @@ public class JobLaunchingGateway extends AbstractReplyProducingMessageHandler { private final JobLaunchingMessageHandler jobLaunchingMessageHandler; /** - * Constructor taking a {@link JobLauncher} as parameter. - * @param jobLauncher Must not be null. + * Constructor taking a {@link JobOperator} as parameter. + * @param jobOperator Must not be null. * */ - public JobLaunchingGateway(JobLauncher jobLauncher) { - Assert.notNull(jobLauncher, "jobLauncher must not be null."); - this.jobLaunchingMessageHandler = new JobLaunchingMessageHandler(jobLauncher); + public JobLaunchingGateway(JobOperator jobOperator) { + Assert.notNull(jobOperator, "jobLauncher must not be null."); + this.jobLaunchingMessageHandler = new JobLaunchingMessageHandler(jobOperator); } /** diff --git a/spring-batch-integration/src/main/java/org/springframework/batch/integration/launch/JobLaunchingMessageHandler.java b/spring-batch-integration/src/main/java/org/springframework/batch/integration/launch/JobLaunchingMessageHandler.java index a0bba11174..1ba1dd321e 100644 --- a/spring-batch-integration/src/main/java/org/springframework/batch/integration/launch/JobLaunchingMessageHandler.java +++ b/spring-batch-integration/src/main/java/org/springframework/batch/integration/launch/JobLaunchingMessageHandler.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,11 +16,11 @@ package org.springframework.batch.integration.launch; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobExecutionException; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobExecutionException; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.launch.JobOperator; import org.springframework.integration.annotation.ServiceActivator; /** @@ -30,19 +30,20 @@ * @author Jonas Partner * @author Dave Syer * @author Gunnar Hillert + * @author Mahmoud Ben Hassine * */ public class JobLaunchingMessageHandler implements JobLaunchRequestHandler { - private final JobLauncher jobLauncher; + private final JobOperator jobOperator; /** - * @param jobLauncher {@link org.springframework.batch.core.launch.JobLauncher} used + * @param jobOperator {@link org.springframework.batch.core.launch.JobOperator} used * to execute Spring Batch jobs */ - public JobLaunchingMessageHandler(JobLauncher jobLauncher) { + public JobLaunchingMessageHandler(JobOperator jobOperator) { super(); - this.jobLauncher = jobLauncher; + this.jobOperator = jobOperator; } @Override @@ -51,7 +52,7 @@ public JobExecution launch(JobLaunchRequest request) throws JobExecutionExceptio Job job = request.getJob(); JobParameters jobParameters = request.getJobParameters(); - return jobLauncher.run(job, jobParameters); + return jobOperator.start(job, jobParameters); } } diff --git a/spring-batch-integration/src/main/java/org/springframework/batch/integration/partition/BeanFactoryStepLocator.java b/spring-batch-integration/src/main/java/org/springframework/batch/integration/partition/BeanFactoryStepLocator.java index 4e0aee6ea3..3678d18e0d 100644 --- a/spring-batch-integration/src/main/java/org/springframework/batch/integration/partition/BeanFactoryStepLocator.java +++ b/spring-batch-integration/src/main/java/org/springframework/batch/integration/partition/BeanFactoryStepLocator.java @@ -18,7 +18,7 @@ import java.util.Arrays; import java.util.Collection; -import org.springframework.batch.core.Step; +import org.springframework.batch.core.step.Step; import org.springframework.batch.core.step.StepLocator; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; diff --git a/spring-batch-integration/src/main/java/org/springframework/batch/integration/partition/MessageChannelPartitionHandler.java b/spring-batch-integration/src/main/java/org/springframework/batch/integration/partition/MessageChannelPartitionHandler.java index f0c710c544..05ae32d719 100644 --- a/spring-batch-integration/src/main/java/org/springframework/batch/integration/partition/MessageChannelPartitionHandler.java +++ b/spring-batch-integration/src/main/java/org/springframework/batch/integration/partition/MessageChannelPartitionHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2009-2024 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. @@ -23,16 +23,12 @@ import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; -import javax.sql.DataSource; - import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.explore.JobExplorer; -import org.springframework.batch.core.explore.support.JobExplorerFactoryBean; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.partition.PartitionHandler; import org.springframework.batch.core.partition.StepExecutionSplitter; import org.springframework.batch.core.partition.support.AbstractPartitionHandler; @@ -96,14 +92,12 @@ public class MessageChannelPartitionHandler extends AbstractPartitionHandler imp private long pollInterval = 10000; - private JobExplorer jobExplorer; + private JobRepository jobRepository; - private boolean pollRepositoryForResults = false; + private boolean pollRepositoryForResults; private long timeout = -1; - private DataSource dataSource; - /** * pollable channel for the replies */ @@ -114,23 +108,19 @@ public void afterPropertiesSet() throws Exception { Assert.state(stepName != null, "A step name must be provided for the remote workers."); Assert.state(messagingGateway != null, "The MessagingOperations must be set"); - pollRepositoryForResults = !(dataSource == null && jobExplorer == null); + pollRepositoryForResults = jobRepository != null; if (pollRepositoryForResults) { logger.debug("MessageChannelPartitionHandler is configured to poll the job repository for worker results"); } - - if (dataSource != null && jobExplorer == null) { - JobExplorerFactoryBean jobExplorerFactoryBean = new JobExplorerFactoryBean(); - jobExplorerFactoryBean.setDataSource(dataSource); - jobExplorerFactoryBean.afterPropertiesSet(); - jobExplorer = jobExplorerFactoryBean.getObject(); + else { + logger.debug("MessageChannelPartitionHandler is configured to use a reply channel for worker results"); + if (replyChannel == null) { + logger.info("No reply channel configured, using a QueueChannel as the default reply channel."); + replyChannel = new QueueChannel(); + } } - if (!pollRepositoryForResults && replyChannel == null) { - replyChannel = new QueueChannel(); - } // end if - } /** @@ -142,14 +132,12 @@ public void setTimeout(long timeout) { } /** - * {@link org.springframework.batch.core.explore.JobExplorer} to use to query the job - * repository. Either this or a {@link javax.sql.DataSource} is required when using - * job repository polling. - * @param jobExplorer {@link org.springframework.batch.core.explore.JobExplorer} to - * use for lookups + * {@link JobRepository} to use to query the job repository. This is required when + * using job repository polling. + * @param jobRepository {@link JobRepository} to use for lookups */ - public void setJobExplorer(JobExplorer jobExplorer) { - this.jobExplorer = jobExplorer; + public void setJobRepository(JobRepository jobRepository) { + this.jobRepository = jobRepository; } /** @@ -160,15 +148,6 @@ public void setPollInterval(long pollInterval) { this.pollInterval = pollInterval; } - /** - * {@link javax.sql.DataSource} pointing to the job repository - * @param dataSource {@link javax.sql.DataSource} that points to the job repository's - * store - */ - public void setDataSource(DataSource dataSource) { - this.dataSource = dataSource; - } - /** * A pre-configured gateway for sending and receiving messages to the remote workers. * Using this property allows a large degree of control over the timeouts and other @@ -254,7 +233,7 @@ private Set pollReplies(final StepExecution managerStepExecution, Set partitionStepExecutionIds = split.stream().map(StepExecution::getId).collect(Collectors.toSet()); Callable> callback = () -> { - JobExecution jobExecution = jobExplorer.getJobExecution(managerStepExecution.getJobExecutionId()); + JobExecution jobExecution = jobRepository.getJobExecution(managerStepExecution.getJobExecutionId()); Set finishedStepExecutions = jobExecution.getStepExecutions() .stream() .filter(stepExecution -> partitionStepExecutionIds.contains(stepExecution.getId())) 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..f1faa7b908 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. @@ -16,13 +16,12 @@ package org.springframework.batch.integration.partition; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepExecutionListener; -import org.springframework.batch.core.explore.JobExplorer; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.listener.StepExecutionListener; import org.springframework.batch.core.partition.PartitionHandler; import org.springframework.batch.core.partition.StepExecutionSplitter; -import org.springframework.batch.core.partition.support.Partitioner; -import org.springframework.batch.core.partition.support.StepExecutionAggregator; +import org.springframework.batch.core.partition.Partitioner; +import org.springframework.batch.core.partition.StepExecutionAggregator; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.step.builder.PartitionStepBuilder; import org.springframework.batch.core.step.builder.StepBuilder; @@ -70,25 +69,12 @@ public class RemotePartitioningManagerStepBuilder extends PartitionStepBuilder { private MessageChannel outputChannel; - private JobExplorer jobExplorer; - private BeanFactory beanFactory; private long pollInterval = DEFAULT_POLL_INTERVAL; 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 @@ -148,17 +134,6 @@ public RemotePartitioningManagerStepBuilder messagingTemplate(MessagingTemplate return this; } - /** - * Set the job explorer. - * @param jobExplorer the job explorer to use. - * @return this builder instance for fluent chaining - */ - public RemotePartitioningManagerStepBuilder jobExplorer(JobExplorer jobExplorer) { - Assert.notNull(jobExplorer, "jobExplorer must not be null"); - this.jobExplorer = jobExplorer; - return this; - } - /** * How often to poll the job repository for the status of the workers. Defaults to 10 * seconds. @@ -213,19 +188,21 @@ public Step build() { partitionHandler.setMessagingOperations(this.messagingTemplate); if (isPolling()) { - partitionHandler.setJobExplorer(this.jobExplorer); + partitionHandler.setJobRepository(getJobRepository()); partitionHandler.setPollInterval(this.pollInterval); partitionHandler.setTimeout(this.timeout); } else { PollableChannel replies = new QueueChannel(); partitionHandler.setReplyChannel(replies); - StandardIntegrationFlow standardIntegrationFlow = IntegrationFlow.from(this.inputChannel) - .aggregate(aggregatorSpec -> aggregatorSpec.processor(partitionHandler)) - .channel(replies) - .get(); - IntegrationFlowContext integrationFlowContext = this.beanFactory.getBean(IntegrationFlowContext.class); - integrationFlowContext.registration(standardIntegrationFlow).autoStartup(false).register(); + if (this.beanFactory != null) { + StandardIntegrationFlow standardIntegrationFlow = IntegrationFlow.from(this.inputChannel) + .aggregate(aggregatorSpec -> aggregatorSpec.processor(partitionHandler)) + .channel(replies) + .get(); + IntegrationFlowContext integrationFlowContext = this.beanFactory.getBean(IntegrationFlowContext.class); + integrationFlowContext.registration(standardIntegrationFlow).autoStartup(false).register(); + } } try { @@ -243,21 +220,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/RemotePartitioningManagerStepBuilderFactory.java b/spring-batch-integration/src/main/java/org/springframework/batch/integration/partition/RemotePartitioningManagerStepBuilderFactory.java index 60a1f8d019..0dcd701c65 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. @@ -16,17 +16,14 @@ package org.springframework.batch.integration.partition; -import org.springframework.batch.core.explore.JobExplorer; import org.springframework.batch.core.repository.JobRepository; 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} and {@link BeanFactory} automatically. * * @since 4.2 * @author Mahmoud Ben Hassine @@ -35,19 +32,14 @@ public class RemotePartitioningManagerStepBuilderFactory implements BeanFactoryA private BeanFactory beanFactory; - final private JobExplorer jobExplorer; - final private JobRepository jobRepository; /** * Create a new {@link RemotePartitioningManagerStepBuilderFactory}. * @param jobRepository the job repository to use - * @param jobExplorer the job explorer to use */ - public RemotePartitioningManagerStepBuilderFactory(JobRepository jobRepository, JobExplorer jobExplorer) { - + public RemotePartitioningManagerStepBuilderFactory(JobRepository jobRepository) { this.jobRepository = jobRepository; - this.jobExplorer = jobExplorer; } @Override @@ -62,8 +54,7 @@ public void setBeanFactory(BeanFactory beanFactory) throws BeansException { * @return a {@link RemotePartitioningManagerStepBuilder} */ public RemotePartitioningManagerStepBuilder get(String name) { - return new RemotePartitioningManagerStepBuilder(name, this.jobRepository).jobExplorer(this.jobExplorer) - .beanFactory(this.beanFactory); + return new RemotePartitioningManagerStepBuilder(name, this.jobRepository).beanFactory(this.beanFactory); } } 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..c726661fcc 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. @@ -19,12 +19,11 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepExecutionListener; -import org.springframework.batch.core.explore.JobExplorer; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.listener.StepExecutionListener; import org.springframework.batch.core.job.flow.Flow; -import org.springframework.batch.core.partition.support.Partitioner; +import org.springframework.batch.core.partition.Partitioner; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.step.StepLocator; import org.springframework.batch.core.step.builder.FlowStepBuilder; @@ -74,23 +73,10 @@ public class RemotePartitioningWorkerStepBuilder extends StepBuilder { private MessageChannel outputChannel; - private JobExplorer jobExplorer; - private StepLocator stepLocator; 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 @@ -124,17 +110,6 @@ public RemotePartitioningWorkerStepBuilder outputChannel(MessageChannel outputCh return this; } - /** - * Set the job explorer. - * @param jobExplorer the job explorer to use - * @return this builder instance for fluent chaining - */ - public RemotePartitioningWorkerStepBuilder jobExplorer(JobExplorer jobExplorer) { - Assert.notNull(jobExplorer, "jobExplorer must not be null"); - this.jobExplorer = jobExplorer; - return this; - } - /** * Set the step locator used to locate the worker step to execute. * @param stepLocator the step locator to use @@ -157,21 +132,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 +156,19 @@ 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); - } - + @Deprecated(since = "6.0", forRemoval = true) @Override public SimpleStepBuilder chunk(CompletionPolicy completionPolicy, PlatformTransactionManager transactionManager) { @@ -267,7 +207,6 @@ public FlowStepBuilder flow(Flow flow) { */ private void configureWorkerIntegrationFlow() { Assert.notNull(this.inputChannel, "An InputChannel must be provided"); - Assert.notNull(this.jobExplorer, "A JobExplorer must be provided"); if (this.stepLocator == null) { BeanFactoryStepLocator beanFactoryStepLocator = new BeanFactoryStepLocator(); @@ -283,7 +222,7 @@ private void configureWorkerIntegrationFlow() { } StepExecutionRequestHandler stepExecutionRequestHandler = new StepExecutionRequestHandler(); - stepExecutionRequestHandler.setJobExplorer(this.jobExplorer); + stepExecutionRequestHandler.setJobRepository(getJobRepository()); stepExecutionRequestHandler.setStepLocator(this.stepLocator); StandardIntegrationFlow standardIntegrationFlow = IntegrationFlow.from(this.inputChannel) 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..f204cd2bb1 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. @@ -16,17 +16,14 @@ package org.springframework.batch.integration.partition; -import org.springframework.batch.core.explore.JobExplorer; import org.springframework.batch.core.repository.JobRepository; 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} and {@link BeanFactory} automatically. * * @since 4.1 * @author Mahmoud Ben Hassine @@ -35,18 +32,13 @@ public class RemotePartitioningWorkerStepBuilderFactory implements BeanFactoryAw private BeanFactory beanFactory; - final private JobExplorer jobExplorer; - final private JobRepository jobRepository; /** * Create a new {@link RemotePartitioningWorkerStepBuilderFactory}. * @param jobRepository the job repository to use - * @param jobExplorer the job explorer to use */ - public RemotePartitioningWorkerStepBuilderFactory(JobRepository jobRepository, JobExplorer jobExplorer) { - - this.jobExplorer = jobExplorer; + public RemotePartitioningWorkerStepBuilderFactory(JobRepository jobRepository) { this.jobRepository = jobRepository; } @@ -62,8 +54,7 @@ public void setBeanFactory(BeanFactory beanFactory) throws BeansException { * @return a {@link RemotePartitioningWorkerStepBuilder} */ public RemotePartitioningWorkerStepBuilder get(String name) { - return new RemotePartitioningWorkerStepBuilder(name, this.jobRepository).jobExplorer(this.jobExplorer) - .beanFactory(this.beanFactory); + return new RemotePartitioningWorkerStepBuilder(name, this.jobRepository).beanFactory(this.beanFactory); } } diff --git a/spring-batch-integration/src/main/java/org/springframework/batch/integration/partition/StepExecutionRequestHandler.java b/spring-batch-integration/src/main/java/org/springframework/batch/integration/partition/StepExecutionRequestHandler.java index bebf4f9d3f..b4fc1a322e 100644 --- a/spring-batch-integration/src/main/java/org/springframework/batch/integration/partition/StepExecutionRequestHandler.java +++ b/spring-batch-integration/src/main/java/org/springframework/batch/integration/partition/StepExecutionRequestHandler.java @@ -1,10 +1,10 @@ package org.springframework.batch.integration.partition; import org.springframework.batch.core.BatchStatus; -import org.springframework.batch.core.JobInterruptedException; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.explore.JobExplorer; +import org.springframework.batch.core.job.JobInterruptedException; +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.step.NoSuchStepException; import org.springframework.batch.core.step.StepLocator; import org.springframework.integration.annotation.MessageEndpoint; @@ -21,7 +21,7 @@ @MessageEndpoint public class StepExecutionRequestHandler { - private JobExplorer jobExplorer; + private JobRepository jobRepository; private StepLocator stepLocator; @@ -34,12 +34,12 @@ public void setStepLocator(StepLocator stepLocator) { } /** - * An explorer that should be used to check for {@link StepExecution} completion. - * @param jobExplorer a {@link JobExplorer} that is linked to the shared repository - * used by all remote workers. + * A job repository that should be used to check for {@link StepExecution} completion. + * @param jobRepository a {@link JobRepository} that is linked to the shared + * repository used by all remote workers. */ - public void setJobExplorer(JobExplorer jobExplorer) { - this.jobExplorer = jobExplorer; + public void setJobRepository(JobRepository jobRepository) { + this.jobRepository = jobRepository; } @ServiceActivator @@ -47,7 +47,7 @@ public StepExecution handle(StepExecutionRequest request) { Long jobExecutionId = request.getJobExecutionId(); Long stepExecutionId = request.getStepExecutionId(); - StepExecution stepExecution = jobExplorer.getStepExecution(jobExecutionId, stepExecutionId); + StepExecution stepExecution = jobRepository.getStepExecution(jobExecutionId, stepExecutionId); if (stepExecution == null) { throw new NoSuchStepException("No StepExecution could be located for this request: " + request); } diff --git a/spring-batch-integration/src/main/resources/org/springframework/batch/integration/config/xml/spring-batch-integration-3.1.xsd b/spring-batch-integration/src/main/resources/org/springframework/batch/integration/config/xml/spring-batch-integration-3.1.xsd index f1474b8a5e..e35e4b045a 100644 --- a/spring-batch-integration/src/main/resources/org/springframework/batch/integration/config/xml/spring-batch-integration-3.1.xsd +++ b/spring-batch-integration/src/main/resources/org/springframework/batch/integration/config/xml/spring-batch-integration-3.1.xsd @@ -162,7 +162,7 @@ ]]> - + diff --git a/spring-batch-integration/src/main/resources/org/springframework/batch/integration/config/xml/spring-batch-integration.xsd b/spring-batch-integration/src/main/resources/org/springframework/batch/integration/config/xml/spring-batch-integration.xsd index 4444eeb068..39dd611098 100644 --- a/spring-batch-integration/src/main/resources/org/springframework/batch/integration/config/xml/spring-batch-integration.xsd +++ b/spring-batch-integration/src/main/resources/org/springframework/batch/integration/config/xml/spring-batch-integration.xsd @@ -79,20 +79,20 @@ ]]> - + - + @@ -160,7 +160,7 @@ ]]> - + diff --git a/spring-batch-integration/src/test/java/org/springframework/batch/integration/JobRepositorySupport.java b/spring-batch-integration/src/test/java/org/springframework/batch/integration/JobRepositorySupport.java index 10c0688298..faea74454a 100644 --- a/spring-batch-integration/src/test/java/org/springframework/batch/integration/JobRepositorySupport.java +++ b/spring-batch-integration/src/test/java/org/springframework/batch/integration/JobRepositorySupport.java @@ -17,10 +17,10 @@ import java.util.Collection; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobInstance; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobInstance; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException; import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException; import org.springframework.batch.core.repository.JobRepository; @@ -76,6 +76,7 @@ public void add(StepExecution stepExecution) { public void update(StepExecution stepExecution) { } + @SuppressWarnings("removal") @Override public boolean isJobInstanceExists(String jobName, JobParameters jobParameters) { return false; diff --git a/spring-batch-integration/src/test/java/org/springframework/batch/integration/JobSupport.java b/spring-batch-integration/src/test/java/org/springframework/batch/integration/JobSupport.java index 3deacda468..c3105f3126 100644 --- a/spring-batch-integration/src/test/java/org/springframework/batch/integration/JobSupport.java +++ b/spring-batch-integration/src/test/java/org/springframework/batch/integration/JobSupport.java @@ -15,8 +15,8 @@ */ package org.springframework.batch.integration; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobExecution; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; public class JobSupport implements Job { diff --git a/spring-batch-integration/src/test/java/org/springframework/batch/integration/StepSupport.java b/spring-batch-integration/src/test/java/org/springframework/batch/integration/StepSupport.java index b196cde9e6..574ee7f565 100644 --- a/spring-batch-integration/src/test/java/org/springframework/batch/integration/StepSupport.java +++ b/spring-batch-integration/src/test/java/org/springframework/batch/integration/StepSupport.java @@ -15,9 +15,9 @@ */ package org.springframework.batch.integration; -import org.springframework.batch.core.JobInterruptedException; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobInterruptedException; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.step.StepExecution; /** * @author Dave Syer diff --git a/spring-batch-integration/src/test/java/org/springframework/batch/integration/async/AsyncItemProcessorMessagingGatewayTests.java b/spring-batch-integration/src/test/java/org/springframework/batch/integration/async/AsyncItemProcessorMessagingGatewayTests.java index 6e19aacfca..b103370933 100644 --- a/spring-batch-integration/src/test/java/org/springframework/batch/integration/async/AsyncItemProcessorMessagingGatewayTests.java +++ b/spring-batch-integration/src/test/java/org/springframework/batch/integration/async/AsyncItemProcessorMessagingGatewayTests.java @@ -23,8 +23,8 @@ import java.util.concurrent.Future; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.JobParametersBuilder; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.item.ItemProcessor; import org.springframework.batch.test.MetaDataInstanceFactory; import org.springframework.batch.test.StepScopeTestExecutionListener; diff --git a/spring-batch-integration/src/test/java/org/springframework/batch/integration/async/PollingAsyncItemProcessorMessagingGatewayTests.java b/spring-batch-integration/src/test/java/org/springframework/batch/integration/async/PollingAsyncItemProcessorMessagingGatewayTests.java index 9cc6e88f38..44da36d49c 100644 --- a/spring-batch-integration/src/test/java/org/springframework/batch/integration/async/PollingAsyncItemProcessorMessagingGatewayTests.java +++ b/spring-batch-integration/src/test/java/org/springframework/batch/integration/async/PollingAsyncItemProcessorMessagingGatewayTests.java @@ -21,8 +21,8 @@ import org.junit.jupiter.api.Test; -import org.springframework.batch.core.JobParametersBuilder; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.item.ItemProcessor; import org.springframework.batch.test.MetaDataInstanceFactory; import org.springframework.batch.test.StepScopeTestExecutionListener; diff --git a/spring-batch-integration/src/test/java/org/springframework/batch/integration/chunk/ChunkMessageItemWriterIntegrationTests.java b/spring-batch-integration/src/test/java/org/springframework/batch/integration/chunk/ChunkMessageItemWriterIntegrationTests.java index b086386a77..fc1083f828 100644 --- a/spring-batch-integration/src/test/java/org/springframework/batch/integration/chunk/ChunkMessageItemWriterIntegrationTests.java +++ b/spring-batch-integration/src/test/java/org/springframework/batch/integration/chunk/ChunkMessageItemWriterIntegrationTests.java @@ -23,19 +23,19 @@ import org.springframework.batch.core.BatchStatus; import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobInstance; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.JobParametersBuilder; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepContribution; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobInstance; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.step.StepContribution; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.job.SimpleJob; import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException; import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.repository.JobRestartException; -import org.springframework.batch.core.repository.support.JobRepositoryFactoryBean; +import org.springframework.batch.core.repository.support.JdbcJobRepositoryFactoryBean; import org.springframework.batch.core.step.factory.SimpleStepFactoryBean; import org.springframework.batch.item.Chunk; import org.springframework.batch.item.ExecutionContext; @@ -82,7 +82,7 @@ void setUp() throws Exception { .addScript("/org/springframework/batch/core/schema-hsqldb.sql") .build(); JdbcTransactionManager transactionManager = new JdbcTransactionManager(embeddedDatabase); - JobRepositoryFactoryBean repositoryFactoryBean = new JobRepositoryFactoryBean(); + JdbcJobRepositoryFactoryBean repositoryFactoryBean = new JdbcJobRepositoryFactoryBean(); repositoryFactoryBean.setDataSource(embeddedDatabase); repositoryFactoryBean.setTransactionManager(transactionManager); repositoryFactoryBean.afterPropertiesSet(); diff --git a/spring-batch-integration/src/test/java/org/springframework/batch/integration/chunk/ChunkProcessorChunkHandlerTests.java b/spring-batch-integration/src/test/java/org/springframework/batch/integration/chunk/ChunkProcessorChunkHandlerTests.java index 376894a490..3a9245ce4b 100644 --- a/spring-batch-integration/src/test/java/org/springframework/batch/integration/chunk/ChunkProcessorChunkHandlerTests.java +++ b/spring-batch-integration/src/test/java/org/springframework/batch/integration/chunk/ChunkProcessorChunkHandlerTests.java @@ -17,7 +17,7 @@ import org.junit.jupiter.api.Test; -import org.springframework.batch.core.StepContribution; +import org.springframework.batch.core.step.StepContribution; import org.springframework.batch.item.Chunk; import org.springframework.batch.test.MetaDataInstanceFactory; diff --git a/spring-batch-integration/src/test/java/org/springframework/batch/integration/chunk/RemoteChunkFaultTolerantStepIntegrationTests.java b/spring-batch-integration/src/test/java/org/springframework/batch/integration/chunk/RemoteChunkFaultTolerantStepIntegrationTests.java index 088e3964b2..d18d507db2 100644 --- a/spring-batch-integration/src/test/java/org/springframework/batch/integration/chunk/RemoteChunkFaultTolerantStepIntegrationTests.java +++ b/spring-batch-integration/src/test/java/org/springframework/batch/integration/chunk/RemoteChunkFaultTolerantStepIntegrationTests.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. @@ -22,13 +22,13 @@ 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.JobParameter; -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.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameter; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; +import org.springframework.batch.core.step.StepExecution; +import org.springframework.batch.core.launch.JobOperator; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.messaging.Message; import org.springframework.messaging.PollableChannel; @@ -38,7 +38,7 @@ class RemoteChunkFaultTolerantStepIntegrationTests { @Autowired - private JobLauncher jobLauncher; + private JobOperator jobOperator; @Autowired private Job job; @@ -56,7 +56,7 @@ void drain() { @Test void testFailedStep() throws Exception { - JobExecution jobExecution = jobLauncher.run(job, new JobParameters( + JobExecution jobExecution = jobOperator.start(job, new JobParameters( Collections.singletonMap("item.three", new JobParameter<>("unsupported", String.class)))); assertEquals(BatchStatus.FAILED, jobExecution.getStatus()); StepExecution stepExecution = jobExecution.getStepExecutions().iterator().next(); @@ -67,7 +67,7 @@ void testFailedStep() throws Exception { @Test void testFailedStepOnError() throws Exception { - JobExecution jobExecution = jobLauncher.run(job, + JobExecution jobExecution = jobOperator.start(job, new JobParameters(Collections.singletonMap("item.three", new JobParameter<>("error", String.class)))); assertEquals(BatchStatus.FAILED, jobExecution.getStatus()); StepExecution stepExecution = jobExecution.getStepExecutions().iterator().next(); @@ -78,7 +78,7 @@ void testFailedStepOnError() throws Exception { @Test void testSunnyDayFaultTolerant() throws Exception { - JobExecution jobExecution = jobLauncher.run(job, + JobExecution jobExecution = jobOperator.start(job, new JobParameters(Collections.singletonMap("item.three", new JobParameter("3", Integer.class)))); assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus()); StepExecution stepExecution = jobExecution.getStepExecutions().iterator().next(); @@ -88,7 +88,7 @@ void testSunnyDayFaultTolerant() throws Exception { @Test void testSkipsInWriter() throws Exception { - JobExecution jobExecution = jobLauncher.run(job, + JobExecution jobExecution = jobOperator.start(job, new JobParametersBuilder().addString("item.three", "fail").addLong("run.id", 1L).toJobParameters()); assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus()); StepExecution stepExecution = jobExecution.getStepExecutions().iterator().next(); diff --git a/spring-batch-integration/src/test/java/org/springframework/batch/integration/chunk/RemoteChunkFaultTolerantStepJdbcIntegrationTests.java b/spring-batch-integration/src/test/java/org/springframework/batch/integration/chunk/RemoteChunkFaultTolerantStepJdbcIntegrationTests.java index 6c30b38039..1488e40d12 100644 --- a/spring-batch-integration/src/test/java/org/springframework/batch/integration/chunk/RemoteChunkFaultTolerantStepJdbcIntegrationTests.java +++ b/spring-batch-integration/src/test/java/org/springframework/batch/integration/chunk/RemoteChunkFaultTolerantStepJdbcIntegrationTests.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. @@ -23,13 +23,13 @@ import org.junit.jupiter.api.Disabled; 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.JobParameter; -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.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameter; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; +import org.springframework.batch.core.step.StepExecution; +import org.springframework.batch.core.launch.JobOperator; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.messaging.Message; import org.springframework.messaging.PollableChannel; @@ -42,7 +42,7 @@ class RemoteChunkFaultTolerantStepJdbcIntegrationTests { @Autowired - private JobLauncher jobLauncher; + private JobOperator jobOperator; @Autowired private Job job; @@ -61,7 +61,7 @@ void drain() { @Test @DirtiesContext void testFailedStep() throws Exception { - JobExecution jobExecution = jobLauncher.run(job, new JobParameters( + JobExecution jobExecution = jobOperator.start(job, new JobParameters( Collections.singletonMap("item.three", new JobParameter<>("unsupported", String.class)))); assertEquals(BatchStatus.FAILED, jobExecution.getStatus()); StepExecution stepExecution = jobExecution.getStepExecutions().iterator().next(); @@ -73,7 +73,7 @@ void testFailedStep() throws Exception { @Test @DirtiesContext void testFailedStepOnError() throws Exception { - JobExecution jobExecution = jobLauncher.run(job, + JobExecution jobExecution = jobOperator.start(job, new JobParameters(Collections.singletonMap("item.three", new JobParameter<>("error", String.class)))); assertEquals(BatchStatus.FAILED, jobExecution.getStatus()); StepExecution stepExecution = jobExecution.getStepExecutions().iterator().next(); @@ -85,7 +85,7 @@ void testFailedStepOnError() throws Exception { @Test @DirtiesContext void testSunnyDayFaultTolerant() throws Exception { - JobExecution jobExecution = jobLauncher.run(job, + JobExecution jobExecution = jobOperator.start(job, new JobParameters(Collections.singletonMap("item.three", new JobParameter("3", Integer.class)))); assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus()); StepExecution stepExecution = jobExecution.getStepExecutions().iterator().next(); @@ -96,7 +96,7 @@ void testSunnyDayFaultTolerant() throws Exception { @Test @DirtiesContext void testSkipsInWriter() throws Exception { - JobExecution jobExecution = jobLauncher.run(job, + JobExecution jobExecution = jobOperator.start(job, new JobParametersBuilder().addString("item.three", "fail").addLong("run.id", 1L).toJobParameters()); assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus()); StepExecution stepExecution = jobExecution.getStepExecutions().iterator().next(); diff --git a/spring-batch-integration/src/test/java/org/springframework/batch/integration/chunk/RemoteChunkFaultTolerantStepJmsIntegrationTests.java b/spring-batch-integration/src/test/java/org/springframework/batch/integration/chunk/RemoteChunkFaultTolerantStepJmsIntegrationTests.java index 9595f1e27d..e3e51d4985 100644 --- a/spring-batch-integration/src/test/java/org/springframework/batch/integration/chunk/RemoteChunkFaultTolerantStepJmsIntegrationTests.java +++ b/spring-batch-integration/src/test/java/org/springframework/batch/integration/chunk/RemoteChunkFaultTolerantStepJmsIntegrationTests.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. @@ -23,13 +23,13 @@ import org.junit.jupiter.api.BeforeAll; 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.JobParameter; -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.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameter; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; +import org.springframework.batch.core.step.StepExecution; +import org.springframework.batch.core.launch.JobOperator; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; @@ -45,14 +45,14 @@ static void clear() { } @Autowired - private JobLauncher jobLauncher; + private JobOperator jobOperator; @Autowired private Job job; @Test void testFailedStep() throws Exception { - JobExecution jobExecution = jobLauncher.run(job, new JobParameters( + JobExecution jobExecution = jobOperator.start(job, new JobParameters( Collections.singletonMap("item.three", new JobParameter<>("unsupported", String.class)))); assertEquals(BatchStatus.FAILED, jobExecution.getStatus()); StepExecution stepExecution = jobExecution.getStepExecutions().iterator().next(); @@ -63,7 +63,7 @@ void testFailedStep() throws Exception { @Test void testFailedStepOnError() throws Exception { - JobExecution jobExecution = jobLauncher.run(job, + JobExecution jobExecution = jobOperator.start(job, new JobParameters(Collections.singletonMap("item.three", new JobParameter<>("error", String.class)))); assertEquals(BatchStatus.FAILED, jobExecution.getStatus()); StepExecution stepExecution = jobExecution.getStepExecutions().iterator().next(); @@ -74,7 +74,7 @@ void testFailedStepOnError() throws Exception { @Test void testSunnyDayFaultTolerant() throws Exception { - JobExecution jobExecution = jobLauncher.run(job, + JobExecution jobExecution = jobOperator.start(job, new JobParameters(Collections.singletonMap("item.three", new JobParameter("3", Integer.class)))); assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus()); StepExecution stepExecution = jobExecution.getStepExecutions().iterator().next(); @@ -84,7 +84,7 @@ void testSunnyDayFaultTolerant() throws Exception { @Test void testSkipsInWriter() throws Exception { - JobExecution jobExecution = jobLauncher.run(job, + JobExecution jobExecution = jobOperator.start(job, new JobParametersBuilder().addString("item.three", "fail").addLong("run.id", 1L).toJobParameters()); assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus()); StepExecution stepExecution = jobExecution.getStepExecutions().iterator().next(); diff --git a/spring-batch-integration/src/test/java/org/springframework/batch/integration/chunk/RemoteChunkStepIntegrationTests.java b/spring-batch-integration/src/test/java/org/springframework/batch/integration/chunk/RemoteChunkStepIntegrationTests.java index 7b6198e0e5..198384dc62 100644 --- a/spring-batch-integration/src/test/java/org/springframework/batch/integration/chunk/RemoteChunkStepIntegrationTests.java +++ b/spring-batch-integration/src/test/java/org/springframework/batch/integration/chunk/RemoteChunkStepIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2009-2023 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. @@ -21,12 +21,12 @@ 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.JobParameter; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameter; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.step.StepExecution; +import org.springframework.batch.core.launch.JobOperator; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; @@ -34,14 +34,14 @@ class RemoteChunkStepIntegrationTests { @Autowired - private JobLauncher jobLauncher; + private JobOperator jobOperator; @Autowired private Job job; @Test void testSunnyDaySimpleStep() throws Exception { - JobExecution jobExecution = jobLauncher.run(job, + JobExecution jobExecution = jobOperator.start(job, new JobParameters(Collections.singletonMap("item.three", new JobParameter("3", Integer.class)))); assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus()); StepExecution stepExecution = jobExecution.getStepExecutions().iterator().next(); @@ -51,7 +51,7 @@ void testSunnyDaySimpleStep() throws Exception { @Test void testFailedStep() throws Exception { - JobExecution jobExecution = jobLauncher.run(job, + JobExecution jobExecution = jobOperator.start(job, new JobParameters(Collections.singletonMap("item.three", new JobParameter<>("fail", String.class)))); assertEquals(BatchStatus.FAILED, jobExecution.getStatus()); StepExecution stepExecution = jobExecution.getStepExecutions().iterator().next(); diff --git a/spring-batch-integration/src/test/java/org/springframework/batch/integration/chunk/RemoteChunkingManagerStepBuilderTests.java b/spring-batch-integration/src/test/java/org/springframework/batch/integration/chunk/RemoteChunkingManagerStepBuilderTests.java index bb531b8f2c..704b262215 100644 --- a/spring-batch-integration/src/test/java/org/springframework/batch/integration/chunk/RemoteChunkingManagerStepBuilderTests.java +++ b/spring-batch-integration/src/test/java/org/springframework/batch/integration/chunk/RemoteChunkingManagerStepBuilderTests.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. @@ -23,14 +23,16 @@ import org.junit.jupiter.api.Test; -import org.springframework.batch.core.ChunkListener; -import org.springframework.batch.core.ItemReadListener; -import org.springframework.batch.core.ItemWriteListener; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.SkipListener; -import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.StepExecutionListener; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; +import org.springframework.batch.core.listener.ChunkListener; +import org.springframework.batch.core.listener.ItemReadListener; +import org.springframework.batch.core.listener.ItemWriteListener; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.listener.SkipListener; +import org.springframework.batch.core.scope.context.ChunkContext; +import org.springframework.batch.core.step.StepExecution; +import org.springframework.batch.core.listener.StepExecutionListener; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.step.item.ChunkOrientedTasklet; @@ -345,7 +347,7 @@ else if (count < items.size()) { verify(skipListener, atLeastOnce()).onSkipInProcess(any(), any()); verify(retryListener, atLeastOnce()).open(any(), any()); verify(stepExecutionListener, atLeastOnce()).beforeStep(any()); - verify(chunkListener, atLeastOnce()).beforeChunk(any()); + verify(chunkListener, atLeastOnce()).beforeChunk((ChunkContext) any()); verify(itemReadListener, atLeastOnce()).beforeRead(); verify(itemWriteListener, atLeastOnce()).beforeWrite(any()); @@ -355,6 +357,7 @@ else if (count < items.size()) { @Configuration @EnableBatchProcessing + @EnableJdbcJobRepository static class BatchConfiguration { @Bean diff --git a/spring-batch-integration/src/test/java/org/springframework/batch/integration/config/xml/JobLaunchingGatewayParserTests.java b/spring-batch-integration/src/test/java/org/springframework/batch/integration/config/xml/JobLaunchingGatewayParserTests.java index 82090608b1..02ca238f97 100644 --- a/spring-batch-integration/src/test/java/org/springframework/batch/integration/config/xml/JobLaunchingGatewayParserTests.java +++ b/spring-batch-integration/src/test/java/org/springframework/batch/integration/config/xml/JobLaunchingGatewayParserTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 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. You may obtain a copy of the License at @@ -13,9 +13,10 @@ package org.springframework.batch.integration.config.xml; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.launch.JobOperator; import org.springframework.batch.integration.launch.JobLaunchingMessageHandler; import org.springframework.beans.factory.BeanCreationException; import org.springframework.context.ConfigurableApplicationContext; @@ -80,10 +81,11 @@ void testJobLaunchingGatewayIsRunning() { void testJobLaunchingGatewayNoJobLauncher() { Exception exception = assertThrows(BeanCreationException.class, () -> setUp("JobLaunchingGatewayParserTestsNoJobLauncher-context.xml", getClass())); - assertEquals("No bean named 'jobLauncher' available", exception.getCause().getMessage()); + assertEquals("No bean named 'jobOperator' available", exception.getCause().getMessage()); } @Test + @Disabled("Seems like EnableBatchProcessing is not being picked up in this test") void testJobLaunchingGatewayWithEnableBatchProcessing() { setUp("JobLaunchingGatewayParserTestsWithEnableBatchProcessing-context.xml", getClass()); @@ -91,9 +93,9 @@ void testJobLaunchingGatewayWithEnableBatchProcessing() { "handler.jobLaunchingMessageHandler", JobLaunchingMessageHandler.class); assertNotNull(jobLaunchingMessageHandler); - final JobLauncher jobLauncher = TestUtils.getPropertyValue(jobLaunchingMessageHandler, "jobLauncher", - JobLauncher.class); - assertNotNull(jobLauncher); + final JobOperator jobOperator = TestUtils.getPropertyValue(jobLaunchingMessageHandler, "jobOperator", + JobOperator.class); + assertNotNull(jobOperator); } diff --git a/spring-batch-integration/src/test/java/org/springframework/batch/integration/file/FileToMessagesJobIntegrationTests.java b/spring-batch-integration/src/test/java/org/springframework/batch/integration/file/FileToMessagesJobIntegrationTests.java index 326c16ae7f..a5b92ea67b 100644 --- a/spring-batch-integration/src/test/java/org/springframework/batch/integration/file/FileToMessagesJobIntegrationTests.java +++ b/spring-batch-integration/src/test/java/org/springframework/batch/integration/file/FileToMessagesJobIntegrationTests.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. @@ -20,10 +20,10 @@ 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.JobParametersBuilder; -import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; +import org.springframework.batch.core.launch.JobOperator; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.messaging.Message; @@ -46,7 +46,7 @@ class FileToMessagesJobIntegrationTests implements MessageHandler { private Job job; @Autowired - private JobLauncher jobLauncher; + private JobOperator jobOperator; int count = 0; @@ -63,7 +63,7 @@ void setUp() { @Test void testFileSent() throws Exception { - JobExecution execution = jobLauncher.run(job, + JobExecution execution = jobOperator.start(job, new JobParametersBuilder().addLong("time.stamp", System.currentTimeMillis()).toJobParameters()); assertEquals(BatchStatus.COMPLETED, execution.getStatus()); // 2 chunks sent to channel (5 items and commit-interval=3) diff --git a/spring-batch-integration/src/test/java/org/springframework/batch/integration/item/MessagingGatewayIntegrationTests.java b/spring-batch-integration/src/test/java/org/springframework/batch/integration/item/MessagingGatewayIntegrationTests.java index e3bbd18ced..49a044523f 100644 --- a/spring-batch-integration/src/test/java/org/springframework/batch/integration/item/MessagingGatewayIntegrationTests.java +++ b/spring-batch-integration/src/test/java/org/springframework/batch/integration/item/MessagingGatewayIntegrationTests.java @@ -92,7 +92,7 @@ public String transform(String input) { if (input.equals("filter")) { return null; } - return input + ": " + (count++); + return input + ": " + count++; } } diff --git a/spring-batch-integration/src/test/java/org/springframework/batch/integration/launch/JobLaunchingGatewayIntegrationTests.java b/spring-batch-integration/src/test/java/org/springframework/batch/integration/launch/JobLaunchingGatewayIntegrationTests.java index d65fd5fa27..75f3a441fd 100644 --- a/spring-batch-integration/src/test/java/org/springframework/batch/integration/launch/JobLaunchingGatewayIntegrationTests.java +++ b/spring-batch-integration/src/test/java/org/springframework/batch/integration/launch/JobLaunchingGatewayIntegrationTests.java @@ -18,10 +18,10 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.batch.core.ExitStatus; -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.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; import org.springframework.batch.integration.JobSupport; import org.springframework.batch.integration.step.TestTasklet; import org.springframework.beans.factory.annotation.Autowired; diff --git a/spring-batch-integration/src/test/java/org/springframework/batch/integration/launch/JobLaunchingGatewayTests.java b/spring-batch-integration/src/test/java/org/springframework/batch/integration/launch/JobLaunchingGatewayTests.java index e9419e44c7..c627b92d5c 100644 --- a/spring-batch-integration/src/test/java/org/springframework/batch/integration/launch/JobLaunchingGatewayTests.java +++ b/spring-batch-integration/src/test/java/org/springframework/batch/integration/launch/JobLaunchingGatewayTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 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. @@ -16,10 +16,10 @@ package org.springframework.batch.integration.launch; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.JobParametersInvalidException; -import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersInvalidException; +import org.springframework.batch.core.launch.JobOperator; import org.springframework.batch.integration.JobSupport; import org.springframework.integration.support.MessageBuilder; import org.springframework.messaging.Message; @@ -33,6 +33,7 @@ /** * @author Gunnar Hillert + * @author Mahmoud Ben Hassine * @since 1.3 * */ @@ -45,11 +46,11 @@ void testExceptionRaised() throws Exception { .withPayload(new JobLaunchRequest(new JobSupport("testJob"), new JobParameters())) .build(); - final JobLauncher jobLauncher = mock(); - when(jobLauncher.run(any(Job.class), any(JobParameters.class))) + final JobOperator jobOperator = mock(); + when(jobOperator.start(any(Job.class), any(JobParameters.class))) .thenThrow(new JobParametersInvalidException("This is a JobExecutionException.")); - JobLaunchingGateway jobLaunchingGateway = new JobLaunchingGateway(jobLauncher); + JobLaunchingGateway jobLaunchingGateway = new JobLaunchingGateway(jobOperator); Exception exception = assertThrows(MessageHandlingException.class, () -> jobLaunchingGateway.handleMessage(message)); assertEquals("This is a JobExecutionException.", exception.getCause().getMessage()); diff --git a/spring-batch-integration/src/test/java/org/springframework/batch/integration/launch/JobLaunchingMessageHandlerIntegrationTests.java b/spring-batch-integration/src/test/java/org/springframework/batch/integration/launch/JobLaunchingMessageHandlerIntegrationTests.java index 052fb65b43..1fdf6633be 100644 --- a/spring-batch-integration/src/test/java/org/springframework/batch/integration/launch/JobLaunchingMessageHandlerIntegrationTests.java +++ b/spring-batch-integration/src/test/java/org/springframework/batch/integration/launch/JobLaunchingMessageHandlerIntegrationTests.java @@ -25,9 +25,9 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.JobParametersBuilder; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; import org.springframework.batch.integration.JobSupport; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; diff --git a/spring-batch-integration/src/test/java/org/springframework/batch/integration/launch/JobLaunchingMessageHandlerTests.java b/spring-batch-integration/src/test/java/org/springframework/batch/integration/launch/JobLaunchingMessageHandlerTests.java index 06ec4b5bd5..dc5224653b 100644 --- a/spring-batch-integration/src/test/java/org/springframework/batch/integration/launch/JobLaunchingMessageHandlerTests.java +++ b/spring-batch-integration/src/test/java/org/springframework/batch/integration/launch/JobLaunchingMessageHandlerTests.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. @@ -23,11 +23,11 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobInstance; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobInstance; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.launch.support.TaskExecutorJobOperator; import org.springframework.batch.integration.JobSupport; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; @@ -36,24 +36,24 @@ class JobLaunchingMessageHandlerTests { JobLaunchRequestHandler messageHandler; - StubJobLauncher jobLauncher; + StubJobOperator jobOperator; @BeforeEach void setUp() { - jobLauncher = new StubJobLauncher(); - messageHandler = new JobLaunchingMessageHandler(jobLauncher); + jobOperator = new StubJobOperator(); + messageHandler = new JobLaunchingMessageHandler(jobOperator); } @Test void testSimpleDelivery() throws Exception { messageHandler.launch(new JobLaunchRequest(new JobSupport("testjob"), null)); - assertEquals(1, jobLauncher.jobs.size(), "Wrong job count"); - assertEquals("testjob", jobLauncher.jobs.get(0).getName(), "Wrong job name"); + assertEquals(1, jobOperator.jobs.size(), "Wrong job count"); + assertEquals("testjob", jobOperator.jobs.get(0).getName(), "Wrong job name"); } - private static class StubJobLauncher implements JobLauncher { + private static class StubJobOperator extends TaskExecutorJobOperator { List jobs = new ArrayList<>(); @@ -62,7 +62,7 @@ private static class StubJobLauncher implements JobLauncher { AtomicLong jobId = new AtomicLong(); @Override - public JobExecution run(Job job, JobParameters jobParameters) { + public JobExecution start(Job job, JobParameters jobParameters) { jobs.add(job); parameters.add(jobParameters); return new JobExecution(new JobInstance(jobId.getAndIncrement(), job.getName()), jobParameters); diff --git a/spring-batch-integration/src/test/java/org/springframework/batch/integration/partition/BeanFactoryStepLocatorTests.java b/spring-batch-integration/src/test/java/org/springframework/batch/integration/partition/BeanFactoryStepLocatorTests.java index 2ca579be45..01cf6be584 100644 --- a/spring-batch-integration/src/test/java/org/springframework/batch/integration/partition/BeanFactoryStepLocatorTests.java +++ b/spring-batch-integration/src/test/java/org/springframework/batch/integration/partition/BeanFactoryStepLocatorTests.java @@ -19,9 +19,9 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.JobInterruptedException; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobInterruptedException; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.step.StepExecution; import org.springframework.beans.factory.support.DefaultListableBeanFactory; class BeanFactoryStepLocatorTests { diff --git a/spring-batch-integration/src/test/java/org/springframework/batch/integration/partition/JmsIntegrationTests.java b/spring-batch-integration/src/test/java/org/springframework/batch/integration/partition/JmsIntegrationTests.java index 14d72f8441..7b037fb0cc 100755 --- a/spring-batch-integration/src/test/java/org/springframework/batch/integration/partition/JmsIntegrationTests.java +++ b/spring-batch-integration/src/test/java/org/springframework/batch/integration/partition/JmsIntegrationTests.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. You may obtain a copy of the License at @@ -19,13 +19,13 @@ 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.JobInstance; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.explore.JobExplorer; -import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobInstance; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.core.step.StepExecution; +import org.springframework.batch.core.launch.JobOperator; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; @@ -45,27 +45,27 @@ class JmsIntegrationTests { private final Log logger = LogFactory.getLog(getClass()); @Autowired - private JobLauncher jobLauncher; + private JobOperator jobOperator; @Autowired private Job job; @Autowired - private JobExplorer jobExplorer; + private JobRepository jobRepository; @Test void testSimpleProperties() { - assertNotNull(jobLauncher); + assertNotNull(jobOperator); } @Test void testLaunchJob() throws Exception { - int before = jobExplorer.getJobInstances(job.getName(), 0, 100).size(); - assertNotNull(jobLauncher.run(job, new JobParameters())); - List jobInstances = jobExplorer.getJobInstances(job.getName(), 0, 100); + int before = jobRepository.getJobInstances(job.getName(), 0, 100).size(); + assertNotNull(jobOperator.start(job, new JobParameters())); + List jobInstances = jobRepository.getJobInstances(job.getName(), 0, 100); int after = jobInstances.size(); assertEquals(1, after - before); - JobExecution jobExecution = jobExplorer.getJobExecutions(jobInstances.get(jobInstances.size() - 1)).get(0); + JobExecution jobExecution = jobRepository.getJobExecutions(jobInstances.get(jobInstances.size() - 1)).get(0); assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus(), jobExecution.getExitStatus().getExitDescription()); assertEquals(3, jobExecution.getStepExecutions().size()); @@ -73,7 +73,7 @@ void testLaunchJob() throws Exception { // BATCH-1703: we are using a map dao so the step executions in the job // execution are old and we need to // pull them back out of the repository... - stepExecution = jobExplorer.getStepExecution(jobExecution.getId(), stepExecution.getId()); + stepExecution = jobRepository.getStepExecution(jobExecution.getId(), stepExecution.getId()); logger.debug(String.valueOf(stepExecution)); assertEquals(BatchStatus.COMPLETED, stepExecution.getStatus()); } diff --git a/spring-batch-integration/src/test/java/org/springframework/batch/integration/partition/MessageChannelPartitionHandlerTests.java b/spring-batch-integration/src/test/java/org/springframework/batch/integration/partition/MessageChannelPartitionHandlerTests.java index 4f7b677649..35b46f6ae9 100644 --- a/spring-batch-integration/src/test/java/org/springframework/batch/integration/partition/MessageChannelPartitionHandlerTests.java +++ b/spring-batch-integration/src/test/java/org/springframework/batch/integration/partition/MessageChannelPartitionHandlerTests.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. @@ -25,10 +25,10 @@ import org.junit.jupiter.api.Test; import org.springframework.batch.core.BatchStatus; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.explore.JobExplorer; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.partition.StepExecutionSplitter; import org.springframework.integration.MessageTimeoutException; import org.springframework.integration.core.MessagingTemplate; @@ -128,7 +128,6 @@ void testHandleWithReplyChannel() throws Exception { } - @SuppressWarnings("rawtypes") @Test void messageReceiveTimeout() throws Exception { // execute with no default set @@ -137,12 +136,10 @@ void messageReceiveTimeout() throws Exception { StepExecution managerStepExecution = mock(); StepExecutionSplitter stepExecutionSplitter = mock(); MessagingTemplate operations = mock(); - Message message = mock(); // when HashSet stepExecutions = new HashSet<>(); stepExecutions.add(new StepExecution("step1", new JobExecution(5L))); when(stepExecutionSplitter.split(any(StepExecution.class), eq(1))).thenReturn(stepExecutions); - when(message.getPayload()).thenReturn(Collections.emptyList()); // set messageChannelPartitionHandler.setMessagingOperations(operations); @@ -160,7 +157,7 @@ void testHandleWithJobRepositoryPolling() throws Exception { StepExecution managerStepExecution = new StepExecution("step1", jobExecution, 1L); StepExecutionSplitter stepExecutionSplitter = mock(); MessagingTemplate operations = mock(); - JobExplorer jobExplorer = mock(); + JobRepository jobRepository = mock(); // when HashSet stepExecutions = new HashSet<>(); StepExecution partition1 = new StepExecution("step1:partition1", jobExecution, 2L); @@ -179,12 +176,12 @@ void testHandleWithJobRepositoryPolling() throws Exception { runningJobExecution.addStepExecutions(Arrays.asList(partition2, partition1, partition3)); JobExecution completedJobExecution = new JobExecution(5L, new JobParameters()); completedJobExecution.addStepExecutions(Arrays.asList(partition2, partition1, partition4)); - when(jobExplorer.getJobExecution(5L)).thenReturn(runningJobExecution, runningJobExecution, runningJobExecution, - completedJobExecution); + when(jobRepository.getJobExecution(5L)).thenReturn(runningJobExecution, runningJobExecution, + runningJobExecution, completedJobExecution); // set messageChannelPartitionHandler.setMessagingOperations(operations); - messageChannelPartitionHandler.setJobExplorer(jobExplorer); + messageChannelPartitionHandler.setJobRepository(jobRepository); messageChannelPartitionHandler.setStepName("step1"); messageChannelPartitionHandler.setPollInterval(500L); messageChannelPartitionHandler.afterPropertiesSet(); @@ -212,7 +209,7 @@ void testHandleWithJobRepositoryPollingTimeout() throws Exception { StepExecution managerStepExecution = new StepExecution("step1", jobExecution, 1L); StepExecutionSplitter stepExecutionSplitter = mock(); MessagingTemplate operations = mock(); - JobExplorer jobExplorer = mock(); + JobRepository jobRepository = mock(); // when HashSet stepExecutions = new HashSet<>(); StepExecution partition1 = new StepExecution("step1:partition1", jobExecution, 2L); @@ -227,11 +224,11 @@ void testHandleWithJobRepositoryPollingTimeout() throws Exception { when(stepExecutionSplitter.split(any(StepExecution.class), eq(1))).thenReturn(stepExecutions); JobExecution runningJobExecution = new JobExecution(5L, new JobParameters()); runningJobExecution.addStepExecutions(Arrays.asList(partition2, partition1, partition3)); - when(jobExplorer.getJobExecution(5L)).thenReturn(runningJobExecution); + when(jobRepository.getJobExecution(5L)).thenReturn(runningJobExecution); // set messageChannelPartitionHandler.setMessagingOperations(operations); - messageChannelPartitionHandler.setJobExplorer(jobExplorer); + messageChannelPartitionHandler.setJobRepository(jobRepository); messageChannelPartitionHandler.setStepName("step1"); messageChannelPartitionHandler.setTimeout(1000L); messageChannelPartitionHandler.afterPropertiesSet(); diff --git a/spring-batch-integration/src/test/java/org/springframework/batch/integration/partition/PollingIntegrationTests.java b/spring-batch-integration/src/test/java/org/springframework/batch/integration/partition/PollingIntegrationTests.java index 87c17934b8..a24960b4ca 100644 --- a/spring-batch-integration/src/test/java/org/springframework/batch/integration/partition/PollingIntegrationTests.java +++ b/spring-batch-integration/src/test/java/org/springframework/batch/integration/partition/PollingIntegrationTests.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. @@ -23,12 +23,12 @@ 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.JobInstance; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.explore.JobExplorer; -import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobInstance; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.core.launch.JobOperator; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; @@ -40,27 +40,27 @@ class PollingIntegrationTests { @Autowired - private JobLauncher jobLauncher; + private JobOperator jobOperator; @Autowired private Job job; @Autowired - private JobExplorer jobExplorer; + private JobRepository jobRepository; @Test void testSimpleProperties() { - assertNotNull(jobLauncher); + assertNotNull(jobOperator); } @Test void testLaunchJob() throws Exception { - int before = jobExplorer.getJobInstances(job.getName(), 0, 100).size(); - assertNotNull(jobLauncher.run(job, new JobParameters())); - List jobInstances = jobExplorer.getJobInstances(job.getName(), 0, 100); + int before = jobRepository.getJobInstances(job.getName(), 0, 100).size(); + assertNotNull(jobOperator.start(job, new JobParameters())); + List jobInstances = jobRepository.getJobInstances(job.getName(), 0, 100); int after = jobInstances.size(); assertEquals(1, after - before); - JobExecution jobExecution = jobExplorer.getJobExecutions(jobInstances.get(jobInstances.size() - 1)).get(0); + JobExecution jobExecution = jobRepository.getJobExecutions(jobInstances.get(jobInstances.size() - 1)).get(0); assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus()); assertEquals(3, jobExecution.getStepExecutions().size()); } diff --git a/spring-batch-integration/src/test/java/org/springframework/batch/integration/partition/RemotePartitioningManagerStepBuilderTests.java b/spring-batch-integration/src/test/java/org/springframework/batch/integration/partition/RemotePartitioningManagerStepBuilderTests.java index 035bfedaad..3d7e682fb1 100644 --- a/spring-batch-integration/src/test/java/org/springframework/batch/integration/partition/RemotePartitioningManagerStepBuilderTests.java +++ b/spring-batch-integration/src/test/java/org/springframework/batch/integration/partition/RemotePartitioningManagerStepBuilderTests.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,11 +21,11 @@ import org.junit.jupiter.api.Test; import org.mockito.Mockito; -import org.springframework.batch.core.Step; +import org.springframework.batch.core.step.Step; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; import org.springframework.batch.core.partition.PartitionHandler; -import org.springframework.batch.core.partition.support.Partitioner; -import org.springframework.batch.core.partition.support.StepExecutionAggregator; +import org.springframework.batch.core.partition.Partitioner; +import org.springframework.batch.core.partition.StepExecutionAggregator; import org.springframework.batch.core.repository.JobRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; @@ -38,10 +38,7 @@ import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; import static org.assertj.core.api.Assertions.assertThat; -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.*; import static org.springframework.test.util.ReflectionTestUtils.getField; /** @@ -95,20 +92,6 @@ void messagingTemplateMustNotBeNull() { assertThat(expectedException).hasMessage("messagingTemplate must not be null"); } - @Test - void jobExplorerMustNotBeNull() { - // given - final RemotePartitioningManagerStepBuilder builder = new RemotePartitioningManagerStepBuilder("step", - this.jobRepository); - - // when - final Exception expectedException = assertThrows(IllegalArgumentException.class, - () -> builder.jobExplorer(null)); - - // then - assertThat(expectedException).hasMessage("jobExplorer must not be null"); - } - @Test void pollIntervalMustBeGreaterThanZero() { // given @@ -207,6 +190,7 @@ void testManagerStepCreationWhenAggregatingReplies() { // given int gridSize = 5; int startLimit = 3; + DirectChannel inputChannel = new DirectChannel(); DirectChannel outputChannel = new DirectChannel(); Partitioner partitioner = Mockito.mock(); StepExecutionAggregator stepExecutionAggregator = (result, executions) -> { @@ -214,6 +198,7 @@ void testManagerStepCreationWhenAggregatingReplies() { // when Step step = new RemotePartitioningManagerStepBuilder("managerStep", this.jobRepository) + .inputChannel(inputChannel) .outputChannel(outputChannel) .partitioner("workerStep", partitioner) .gridSize(gridSize) diff --git a/spring-batch-integration/src/test/java/org/springframework/batch/integration/partition/RemotePartitioningWorkerStepBuilderTests.java b/spring-batch-integration/src/test/java/org/springframework/batch/integration/partition/RemotePartitioningWorkerStepBuilderTests.java index 87c4fbef1b..556b9aaf2f 100644 --- a/spring-batch-integration/src/test/java/org/springframework/batch/integration/partition/RemotePartitioningWorkerStepBuilderTests.java +++ b/spring-batch-integration/src/test/java/org/springframework/batch/integration/partition/RemotePartitioningWorkerStepBuilderTests.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,7 +21,6 @@ import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.step.tasklet.Tasklet; -import org.springframework.integration.channel.DirectChannel; import org.springframework.transaction.PlatformTransactionManager; import static org.assertj.core.api.Assertions.assertThat; @@ -69,20 +68,6 @@ void outputChannelMustNotBeNull() { assertThat(expectedException).hasMessage("outputChannel must not be null"); } - @Test - void jobExplorerMustNotBeNull() { - // given - final RemotePartitioningWorkerStepBuilder builder = new RemotePartitioningWorkerStepBuilder("step", - this.jobRepository); - - // when - final Exception expectedException = assertThrows(IllegalArgumentException.class, - () -> builder.jobExplorer(null)); - - // then - assertThat(expectedException).hasMessage("jobExplorer must not be null"); - } - @Test void stepLocatorMustNotBeNull() { // given @@ -125,20 +110,4 @@ void testMandatoryInputChannel() { assertThat(expectedException).hasMessage("An InputChannel must be provided"); } - @Test - void testMandatoryJobExplorer() { - // given - DirectChannel inputChannel = new DirectChannel(); - final RemotePartitioningWorkerStepBuilder builder = new RemotePartitioningWorkerStepBuilder("step", - this.jobRepository) - .inputChannel(inputChannel); - - // when - final Exception expectedException = assertThrows(IllegalArgumentException.class, - () -> builder.tasklet(this.tasklet, this.transactionManager)); - - // then - assertThat(expectedException).hasMessage("A JobExplorer must be provided"); - } - } diff --git a/spring-batch-integration/src/test/java/org/springframework/batch/integration/partition/VanillaIntegrationTests.java b/spring-batch-integration/src/test/java/org/springframework/batch/integration/partition/VanillaIntegrationTests.java index 8a3601e2b4..4ddd61e4aa 100644 --- a/spring-batch-integration/src/test/java/org/springframework/batch/integration/partition/VanillaIntegrationTests.java +++ b/spring-batch-integration/src/test/java/org/springframework/batch/integration/partition/VanillaIntegrationTests.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,12 +22,12 @@ 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.JobInstance; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.explore.JobExplorer; -import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobInstance; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.launch.JobOperator; +import org.springframework.batch.core.repository.JobRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; @@ -39,27 +39,27 @@ class VanillaIntegrationTests { @Autowired - private JobLauncher jobLauncher; + private JobOperator jobOperator; @Autowired private Job job; @Autowired - private JobExplorer jobExplorer; + private JobRepository jobRepository; @Test void testSimpleProperties() { - assertNotNull(jobLauncher); + assertNotNull(jobOperator); } @Test void testLaunchJob() throws Exception { - int before = jobExplorer.getJobInstances(job.getName(), 0, 100).size(); - assertNotNull(jobLauncher.run(job, new JobParameters())); - List jobInstances = jobExplorer.getJobInstances(job.getName(), 0, 100); + int before = jobRepository.getJobInstances(job.getName(), 0, 100).size(); + assertNotNull(jobOperator.start(job, new JobParameters())); + List jobInstances = jobRepository.getJobInstances(job.getName(), 0, 100); int after = jobInstances.size(); assertEquals(1, after - before); - JobExecution jobExecution = jobExplorer.getJobExecutions(jobInstances.get(jobInstances.size() - 1)).get(0); + JobExecution jobExecution = jobRepository.getJobExecutions(jobInstances.get(jobInstances.size() - 1)).get(0); assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus()); assertEquals(3, jobExecution.getStepExecutions().size()); } diff --git a/spring-batch-integration/src/test/java/org/springframework/batch/integration/step/DelegateStep.java b/spring-batch-integration/src/test/java/org/springframework/batch/integration/step/DelegateStep.java index 4338a9ba70..a96571cc67 100644 --- a/spring-batch-integration/src/test/java/org/springframework/batch/integration/step/DelegateStep.java +++ b/spring-batch-integration/src/test/java/org/springframework/batch/integration/step/DelegateStep.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,8 +15,9 @@ */ package org.springframework.batch.integration.step; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.step.AbstractStep; import org.springframework.util.Assert; @@ -32,6 +33,15 @@ public class DelegateStep extends AbstractStep { private Step delegate; + /** + * Create a new instance of a {@link DelegateStep} with the given job repository. + * @param jobRepository the job repository to use. Must not be null. + * @since 6.0 + */ + public DelegateStep(JobRepository jobRepository) { + super(jobRepository); + } + /** * @param delegate the delegate to set */ diff --git a/spring-batch-integration/src/test/java/org/springframework/batch/integration/step/StepGatewayIntegrationTests.java b/spring-batch-integration/src/test/java/org/springframework/batch/integration/step/StepGatewayIntegrationTests.java index 2ae737c4fd..2f9b8e76c1 100644 --- a/spring-batch-integration/src/test/java/org/springframework/batch/integration/step/StepGatewayIntegrationTests.java +++ b/spring-batch-integration/src/test/java/org/springframework/batch/integration/step/StepGatewayIntegrationTests.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. @@ -21,11 +21,11 @@ import org.junit.jupiter.api.Test; import org.springframework.batch.core.BatchStatus; import org.springframework.batch.core.ExitStatus; -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.launch.JobLauncher; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; +import org.springframework.batch.core.launch.JobOperator; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; @@ -38,7 +38,7 @@ class StepGatewayIntegrationTests { @Autowired - private JobLauncher jobLauncher; + private JobOperator jobOperator; @Autowired @Qualifier("job") @@ -54,7 +54,7 @@ void clear() { @Test void testLaunchJob() throws Exception { - JobExecution jobExecution = jobLauncher.run(job, new JobParameters()); + JobExecution jobExecution = jobOperator.start(job, new JobParameters()); assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus()); assertEquals(ExitStatus.COMPLETED, jobExecution.getExitStatus()); } @@ -62,7 +62,7 @@ void testLaunchJob() throws Exception { @Test void testLaunchFailedJob() throws Exception { tasklet.setFail(true); - JobExecution jobExecution = jobLauncher.run(job, + JobExecution jobExecution = jobOperator.start(job, new JobParametersBuilder().addLong("run.id", 2L).toJobParameters()); assertEquals(BatchStatus.FAILED, jobExecution.getStatus()); assertEquals(ExitStatus.FAILED, jobExecution.getExitStatus()); diff --git a/spring-batch-integration/src/test/java/org/springframework/batch/integration/step/TestTasklet.java b/spring-batch-integration/src/test/java/org/springframework/batch/integration/step/TestTasklet.java index 59c42baa73..d2205ba65a 100644 --- a/spring-batch-integration/src/test/java/org/springframework/batch/integration/step/TestTasklet.java +++ b/spring-batch-integration/src/test/java/org/springframework/batch/integration/step/TestTasklet.java @@ -15,7 +15,7 @@ */ package org.springframework.batch.integration.step; -import org.springframework.batch.core.StepContribution; +import org.springframework.batch.core.step.StepContribution; import org.springframework.batch.core.scope.context.ChunkContext; import org.springframework.batch.core.step.tasklet.Tasklet; import org.springframework.batch.repeat.RepeatStatus; diff --git a/spring-batch-integration/src/test/resources/org/springframework/batch/integration/chunk/RemoteChunkFaultTolerantStepIntegrationTests-context.xml b/spring-batch-integration/src/test/resources/org/springframework/batch/integration/chunk/RemoteChunkFaultTolerantStepIntegrationTests-context.xml index 9ff0d00b6d..90ac290c6d 100644 --- a/spring-batch-integration/src/test/resources/org/springframework/batch/integration/chunk/RemoteChunkFaultTolerantStepIntegrationTests-context.xml +++ b/spring-batch-integration/src/test/resources/org/springframework/batch/integration/chunk/RemoteChunkFaultTolerantStepIntegrationTests-context.xml @@ -75,7 +75,8 @@ - + diff --git a/spring-batch-integration/src/test/resources/org/springframework/batch/integration/chunk/RemoteChunkFaultTolerantStepJmsIntegrationTests-context.xml b/spring-batch-integration/src/test/resources/org/springframework/batch/integration/chunk/RemoteChunkFaultTolerantStepJmsIntegrationTests-context.xml index 0895b1369c..702dec1c86 100644 --- a/spring-batch-integration/src/test/resources/org/springframework/batch/integration/chunk/RemoteChunkFaultTolerantStepJmsIntegrationTests-context.xml +++ b/spring-batch-integration/src/test/resources/org/springframework/batch/integration/chunk/RemoteChunkFaultTolerantStepJmsIntegrationTests-context.xml @@ -109,7 +109,8 @@ - + diff --git a/spring-batch-integration/src/test/resources/org/springframework/batch/integration/chunk/RemoteChunkStepIntegrationTests-context.xml b/spring-batch-integration/src/test/resources/org/springframework/batch/integration/chunk/RemoteChunkStepIntegrationTests-context.xml index 9b6fb3d610..0ce037dd99 100644 --- a/spring-batch-integration/src/test/resources/org/springframework/batch/integration/chunk/RemoteChunkStepIntegrationTests-context.xml +++ b/spring-batch-integration/src/test/resources/org/springframework/batch/integration/chunk/RemoteChunkStepIntegrationTests-context.xml @@ -62,12 +62,8 @@ - - - - - - + diff --git a/spring-batch-integration/src/test/resources/org/springframework/batch/integration/config/xml/JobLaunchingGatewayParserTests-context.xml b/spring-batch-integration/src/test/resources/org/springframework/batch/integration/config/xml/JobLaunchingGatewayParserTests-context.xml index c389d4ce56..1d3772de55 100644 --- a/spring-batch-integration/src/test/resources/org/springframework/batch/integration/config/xml/JobLaunchingGatewayParserTests-context.xml +++ b/spring-batch-integration/src/test/resources/org/springframework/batch/integration/config/xml/JobLaunchingGatewayParserTests-context.xml @@ -1,9 +1,10 @@ @@ -14,14 +15,15 @@ - - + + + job-operator="jobOperator"/> diff --git a/spring-batch-integration/src/test/resources/org/springframework/batch/integration/config/xml/JobLaunchingGatewayParserTestsRunning-context.xml b/spring-batch-integration/src/test/resources/org/springframework/batch/integration/config/xml/JobLaunchingGatewayParserTestsRunning-context.xml index 44d4170f94..8d9f90ba19 100644 --- a/spring-batch-integration/src/test/resources/org/springframework/batch/integration/config/xml/JobLaunchingGatewayParserTestsRunning-context.xml +++ b/spring-batch-integration/src/test/resources/org/springframework/batch/integration/config/xml/JobLaunchingGatewayParserTestsRunning-context.xml @@ -14,14 +14,15 @@ - - + + + job-operator="jobOperator"/> diff --git a/spring-batch-integration/src/test/resources/org/springframework/batch/integration/launch/JobLaunchingGatewayIntegrationTests-context.xml b/spring-batch-integration/src/test/resources/org/springframework/batch/integration/launch/JobLaunchingGatewayIntegrationTests-context.xml index 5c33b1b4bf..c855b0c318 100644 --- a/spring-batch-integration/src/test/resources/org/springframework/batch/integration/launch/JobLaunchingGatewayIntegrationTests-context.xml +++ b/spring-batch-integration/src/test/resources/org/springframework/batch/integration/launch/JobLaunchingGatewayIntegrationTests-context.xml @@ -14,7 +14,7 @@ - + diff --git a/spring-batch-integration/src/test/resources/org/springframework/batch/integration/launch/JobLaunchingMessageHandlerIntegrationTests-context.xml b/spring-batch-integration/src/test/resources/org/springframework/batch/integration/launch/JobLaunchingMessageHandlerIntegrationTests-context.xml index 71ddfda8b4..cdb860f503 100644 --- a/spring-batch-integration/src/test/resources/org/springframework/batch/integration/launch/JobLaunchingMessageHandlerIntegrationTests-context.xml +++ b/spring-batch-integration/src/test/resources/org/springframework/batch/integration/launch/JobLaunchingMessageHandlerIntegrationTests-context.xml @@ -19,7 +19,7 @@ - + diff --git a/spring-batch-integration/src/test/resources/org/springframework/batch/integration/partition/JmsIntegrationTests-context.xml b/spring-batch-integration/src/test/resources/org/springframework/batch/integration/partition/JmsIntegrationTests-context.xml index e885b61b0a..af1323f7d6 100755 --- a/spring-batch-integration/src/test/resources/org/springframework/batch/integration/partition/JmsIntegrationTests-context.xml +++ b/spring-batch-integration/src/test/resources/org/springframework/batch/integration/partition/JmsIntegrationTests-context.xml @@ -38,7 +38,7 @@ + p:jobRepository-ref="jobRepository" p:stepLocator-ref="stepLocator" /> diff --git a/spring-batch-integration/src/test/resources/org/springframework/batch/integration/partition/PollingIntegrationTests-context.xml b/spring-batch-integration/src/test/resources/org/springframework/batch/integration/partition/PollingIntegrationTests-context.xml index e4ac226664..8e4bdf4ca1 100644 --- a/spring-batch-integration/src/test/resources/org/springframework/batch/integration/partition/PollingIntegrationTests-context.xml +++ b/spring-batch-integration/src/test/resources/org/springframework/batch/integration/partition/PollingIntegrationTests-context.xml @@ -19,7 +19,7 @@ + p:jobRepository-ref="jobRepository" p:stepLocator-ref="stepLocator" /> @@ -29,7 +29,7 @@ - + diff --git a/spring-batch-integration/src/test/resources/org/springframework/batch/integration/partition/VanillaIntegrationTests-context.xml b/spring-batch-integration/src/test/resources/org/springframework/batch/integration/partition/VanillaIntegrationTests-context.xml index dddc94ed1b..813802ac06 100644 --- a/spring-batch-integration/src/test/resources/org/springframework/batch/integration/partition/VanillaIntegrationTests-context.xml +++ b/spring-batch-integration/src/test/resources/org/springframework/batch/integration/partition/VanillaIntegrationTests-context.xml @@ -27,7 +27,7 @@ + p:jobRepository-ref="jobRepository" p:stepLocator-ref="stepLocator" /> diff --git a/spring-batch-integration/src/test/resources/org/springframework/batch/integration/step/StepGatewayIntegrationTests-context.xml b/spring-batch-integration/src/test/resources/org/springframework/batch/integration/step/StepGatewayIntegrationTests-context.xml index c8a15e5c4f..17d3d20f55 100644 --- a/spring-batch-integration/src/test/resources/org/springframework/batch/integration/step/StepGatewayIntegrationTests-context.xml +++ b/spring-batch-integration/src/test/resources/org/springframework/batch/integration/step/StepGatewayIntegrationTests-context.xml @@ -19,12 +19,12 @@ + - - + diff --git a/spring-batch-integration/src/test/resources/simple-job-launcher-context.xml b/spring-batch-integration/src/test/resources/simple-job-launcher-context.xml index 00b702423c..d1bdaf96c2 100644 --- a/spring-batch-integration/src/test/resources/simple-job-launcher-context.xml +++ b/spring-batch-integration/src/test/resources/simple-job-launcher-context.xml @@ -5,17 +5,12 @@ - + - - - - - diff --git a/spring-batch-samples/README.md b/spring-batch-samples/README.md index eb26857fc5..3756eebfc1 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 @@ -593,7 +594,7 @@ This should start the required monitoring stack: * Prometheus push gateway on port `9091` * Grafana on port `3000` -Once started, you need to [configure Prometheus as data source in Grafana](https://grafana.com/docs/features/datasources/prometheus/) +Once started, you need to [configure Prometheus as data source in Grafana](https://grafana.com/docs/grafana/latest/datasources/prometheus/configure/) and import the ready-to-use dashboard in `spring-batch-samples/src/main/resources/org/springframework/batch/samples/metrics/spring-batch-dashboard.json`. Finally, run the `org.springframework.batch.samples.metrics.BatchMetricsApplication` @@ -602,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, @@ -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/pom.xml b/spring-batch-samples/pom.xml index 250a969e0c..68d56d2143 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 + 6.0.0-SNAPSHOT spring-batch-samples jar @@ -52,11 +52,9 @@ spring-context-support ${spring-framework.version} - io.micrometer - micrometer-registry-prometheus-simpleclient + micrometer-registry-prometheus ${micrometer.version} @@ -204,10 +202,10 @@ mongodb-driver-sync ${mongodb-driver.version} - - io.prometheus - simpleclient_pushgateway - ${prometheus_pushgateway.version} + + io.prometheus + prometheus-metrics-exporter-pushgateway + ${prometheus-metrics-exporter-pushgateway} com.fasterxml.jackson.core diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/amqp/AmqpJobConfiguration.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/amqp/AmqpJobConfiguration.java index 3ed5730536..9fcb89077e 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/amqp/AmqpJobConfiguration.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/amqp/AmqpJobConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2023 the original author or authors. + * Copyright 2023-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,9 +17,10 @@ package org.springframework.batch.samples.amqp; import org.springframework.amqp.rabbit.core.RabbitTemplate; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.Step; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.step.Step; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; import org.springframework.batch.core.job.builder.JobBuilder; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.step.builder.StepBuilder; @@ -41,6 +42,7 @@ */ @Configuration @EnableBatchProcessing +@EnableJdbcJobRepository @Import(DataSourceConfiguration.class) public class AmqpJobConfiguration { @@ -52,7 +54,8 @@ public Job job(JobRepository jobRepository, Step step) { @Bean public Step step(JobRepository jobRepository, JdbcTransactionManager transactionManager, RabbitTemplate rabbitInputTemplate, RabbitTemplate rabbitOutputTemplate) { - return new StepBuilder("step", jobRepository).chunk(1, transactionManager) + return new StepBuilder("step", jobRepository).chunk(1) + .transactionManager(transactionManager) .reader(amqpItemReader(rabbitInputTemplate)) .processor(new MessageProcessor()) .writer(amqpItemWriter(rabbitOutputTemplate)) diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/chunking/ManagerConfiguration.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/chunking/ManagerConfiguration.java index d4e53aa5df..9ba06ca753 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/chunking/ManagerConfiguration.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/chunking/ManagerConfiguration.java @@ -20,7 +20,8 @@ import jakarta.jms.JMSException; import org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory; -import org.springframework.batch.core.Job; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; +import org.springframework.batch.core.job.Job; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; import org.springframework.batch.core.job.builder.JobBuilder; import org.springframework.batch.core.repository.JobRepository; @@ -50,6 +51,7 @@ */ @Configuration @EnableBatchProcessing +@EnableJdbcJobRepository @EnableBatchIntegration @EnableIntegration @PropertySource("classpath:org/springframework/batch/samples/chunking/remote-chunking.properties") @@ -110,7 +112,7 @@ public ListItemReader itemReader() { @Bean public TaskletStep managerStep() { return this.managerStepBuilderFactory.get("managerStep") - .chunk(3) + .chunk(3) .reader(itemReader()) .outputChannel(requests()) .inputChannel(replies()) diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/common/ColumnRangePartitioner.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/common/ColumnRangePartitioner.java index 0707873df0..d6556e7986 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/common/ColumnRangePartitioner.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/common/ColumnRangePartitioner.java @@ -1,5 +1,5 @@ /* - * Copyright 2009-2014 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. @@ -20,7 +20,7 @@ import javax.sql.DataSource; -import org.springframework.batch.core.partition.support.Partitioner; +import org.springframework.batch.core.partition.Partitioner; import org.springframework.batch.item.ExecutionContext; import org.springframework.jdbc.core.JdbcOperations; import org.springframework.jdbc.core.JdbcTemplate; diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/common/ErrorLogTasklet.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/common/ErrorLogTasklet.java index ad36068a65..fc30224baa 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/common/ErrorLogTasklet.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/common/ErrorLogTasklet.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. @@ -18,9 +18,9 @@ import javax.sql.DataSource; import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.StepContribution; -import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.StepExecutionListener; +import org.springframework.batch.core.step.StepContribution; +import org.springframework.batch.core.step.StepExecution; +import org.springframework.batch.core.listener.StepExecutionListener; import org.springframework.batch.core.scope.context.ChunkContext; import org.springframework.batch.core.step.tasklet.Tasklet; import org.springframework.batch.repeat.RepeatStatus; diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/common/OutputFileListener.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/common/OutputFileListener.java index 426434c923..ecd5ef260f 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/common/OutputFileListener.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/common/OutputFileListener.java @@ -16,7 +16,7 @@ package org.springframework.batch.samples.common; import org.apache.commons.io.FilenameUtils; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.annotation.BeforeStep; import org.springframework.batch.item.ExecutionContext; diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/common/SkipCheckingDecider.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/common/SkipCheckingDecider.java index f3d6c4b5d8..94ae806a19 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/common/SkipCheckingDecider.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/common/SkipCheckingDecider.java @@ -16,8 +16,8 @@ package org.springframework.batch.samples.common; import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.job.flow.FlowExecutionStatus; import org.springframework.batch.core.job.flow.JobExecutionDecider; import org.springframework.lang.Nullable; diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/common/SkipCheckingListener.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/common/SkipCheckingListener.java index e13570d277..221f231758 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/common/SkipCheckingListener.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/common/SkipCheckingListener.java @@ -18,7 +18,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.annotation.AfterStep; import org.springframework.batch.core.annotation.BeforeStep; import org.springframework.batch.core.annotation.OnSkipInProcess; diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/common/StagingItemReader.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/common/StagingItemReader.java index 0967635495..9b64f77d71 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/common/StagingItemReader.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/common/StagingItemReader.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. @@ -29,8 +29,8 @@ import org.apache.commons.logging.LogFactory; import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.StepExecutionListener; +import org.springframework.batch.core.step.StepExecution; +import org.springframework.batch.core.listener.StepExecutionListener; import org.springframework.batch.item.ItemReader; import org.springframework.batch.item.ReaderNotOpenException; import org.springframework.beans.factory.DisposableBean; diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/common/StagingItemWriter.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/common/StagingItemWriter.java index b56454502f..6d9974dfe5 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/common/StagingItemWriter.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/common/StagingItemWriter.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. @@ -21,8 +21,8 @@ import java.util.ListIterator; import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.StepExecutionListener; +import org.springframework.batch.core.step.StepExecution; +import org.springframework.batch.core.listener.StepExecutionListener; import org.springframework.batch.item.Chunk; import org.springframework.batch.item.ItemWriter; import org.springframework.jdbc.core.BatchPreparedStatementSetter; diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/domain/trade/CompositeCustomerUpdateLineTokenizer.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/domain/trade/CompositeCustomerUpdateLineTokenizer.java index 1c081de5fd..525cab87f4 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/domain/trade/CompositeCustomerUpdateLineTokenizer.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/domain/trade/CompositeCustomerUpdateLineTokenizer.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,8 +16,8 @@ package org.springframework.batch.samples.domain.trade; -import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.StepExecutionListener; +import org.springframework.batch.core.step.StepExecution; +import org.springframework.batch.core.listener.StepExecutionListener; import org.springframework.batch.item.file.transform.FieldSet; import org.springframework.batch.item.file.transform.LineTokenizer; import org.springframework.lang.Nullable; diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/domain/trade/CustomerCredit.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/domain/trade/CustomerCredit.java index 2812aced71..55a22d5785 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/domain/trade/CustomerCredit.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/domain/trade/CustomerCredit.java @@ -81,7 +81,7 @@ public CustomerCredit increaseCreditBy(BigDecimal sum) { @Override public boolean equals(Object o) { - return (o instanceof CustomerCredit) && ((CustomerCredit) o).id == id; + return (o instanceof CustomerCredit customerCredit) && customerCredit.id == id; } @Override diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/delimited/DelimitedJobConfiguration.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/delimited/DelimitedJobConfiguration.java index 08bfef4387..d3b059fd36 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/delimited/DelimitedJobConfiguration.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/delimited/DelimitedJobConfiguration.java @@ -1,6 +1,22 @@ +/* + * Copyright 2023-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.file.delimited; -import org.springframework.batch.core.Job; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; +import org.springframework.batch.core.job.Job; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; import org.springframework.batch.core.configuration.annotation.StepScope; import org.springframework.batch.core.job.builder.JobBuilder; @@ -25,6 +41,7 @@ @Configuration @EnableBatchProcessing +@EnableJdbcJobRepository @Import(DataSourceConfiguration.class) public class DelimitedJobConfiguration { @@ -54,7 +71,8 @@ public FlatFileItemWriter itemWriter( public Job job(JobRepository jobRepository, JdbcTransactionManager transactionManager, ItemReader itemReader, ItemWriter itemWriter) { return new JobBuilder("ioSampleJob", jobRepository) - .start(new StepBuilder("step1", jobRepository).chunk(2, transactionManager) + .start(new StepBuilder("step1", jobRepository).chunk(2) + .transactionManager(transactionManager) .reader(itemReader) .processor(new CustomerCreditIncreaseProcessor()) .writer(itemWriter) diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/fixed/FixedLengthJobConfiguration.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/fixed/FixedLengthJobConfiguration.java index 7ab07d32f3..d9db0e8eea 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/fixed/FixedLengthJobConfiguration.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/fixed/FixedLengthJobConfiguration.java @@ -1,6 +1,22 @@ +/* + * Copyright 2023-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.file.fixed; -import org.springframework.batch.core.Job; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; +import org.springframework.batch.core.job.Job; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; import org.springframework.batch.core.configuration.annotation.StepScope; import org.springframework.batch.core.job.builder.JobBuilder; @@ -26,6 +42,7 @@ @Configuration @EnableBatchProcessing +@EnableJdbcJobRepository @Import(DataSourceConfiguration.class) public class FixedLengthJobConfiguration { @@ -57,7 +74,8 @@ public FlatFileItemWriter itemWriter( public Job job(JobRepository jobRepository, JdbcTransactionManager transactionManager, ItemReader itemReader, ItemWriter itemWriter) { return new JobBuilder("ioSampleJob", jobRepository) - .start(new StepBuilder("step1", jobRepository).chunk(2, transactionManager) + .start(new StepBuilder("step1", jobRepository).chunk(2) + .transactionManager(transactionManager) .reader(itemReader) .processor(new CustomerCreditIncreaseProcessor()) .writer(itemWriter) diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/json/JsonJobConfiguration.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/json/JsonJobConfiguration.java index 1ead490d55..a00c4b9c96 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/json/JsonJobConfiguration.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/json/JsonJobConfiguration.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. @@ -15,8 +15,9 @@ */ package org.springframework.batch.samples.file.json; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.Step; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.step.Step; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; import org.springframework.batch.core.configuration.annotation.StepScope; import org.springframework.batch.core.job.builder.JobBuilder; @@ -43,6 +44,7 @@ */ @Configuration @EnableBatchProcessing +@EnableJdbcJobRepository @Import(DataSourceConfiguration.class) public class JsonJobConfiguration { @@ -69,7 +71,8 @@ public JsonFileItemWriter itemWriter(@Value("#{jobParameters[outputFile]} @Bean public Step step(JobRepository jobRepository, JdbcTransactionManager transactionManager, JsonItemReader itemReader, JsonFileItemWriter itemWriter) { - return new StepBuilder("step", jobRepository).chunk(2, transactionManager) + return new StepBuilder("step", jobRepository).chunk(2) + .transactionManager(transactionManager) .reader(itemReader) .writer(itemWriter) .build(); diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/multiline/MultiLineJobConfiguration.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/multiline/MultiLineJobConfiguration.java index 78d40c8631..981785c62b 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/multiline/MultiLineJobConfiguration.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/multiline/MultiLineJobConfiguration.java @@ -1,6 +1,22 @@ +/* + * Copyright 2023-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.file.multiline; -import org.springframework.batch.core.Job; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; +import org.springframework.batch.core.job.Job; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; import org.springframework.batch.core.configuration.annotation.StepScope; import org.springframework.batch.core.job.builder.JobBuilder; @@ -19,18 +35,19 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; -import org.springframework.core.io.Resource; +import org.springframework.core.io.FileSystemResource; import org.springframework.core.io.WritableResource; import org.springframework.jdbc.support.JdbcTransactionManager; @Configuration @EnableBatchProcessing +@EnableJdbcJobRepository @Import(DataSourceConfiguration.class) public class MultiLineJobConfiguration { @Bean @StepScope - public MultiLineTradeItemReader itemReader(@Value("#{jobParameters[inputFile]}") Resource resource) { + public MultiLineTradeItemReader itemReader(@Value("#{jobParameters[inputFile]}") FileSystemResource resource) { FlatFileItemReader
delegate = new FlatFileItemReaderBuilder
().name("delegateItemReader") .resource(resource) .lineTokenizer(new DelimitedLineTokenizer()) @@ -57,7 +74,8 @@ public MultiLineTradeItemWriter itemWriter(@Value("#{jobParameters[outputFile]}" public Job job(JobRepository jobRepository, JdbcTransactionManager transactionManager, MultiLineTradeItemReader itemReader, MultiLineTradeItemWriter itemWriter) { return new JobBuilder("ioSampleJob", jobRepository) - .start(new StepBuilder("step1", jobRepository).chunk(2, transactionManager) + .start(new StepBuilder("step1", jobRepository).chunk(2) + .transactionManager(transactionManager) .reader(itemReader) .writer(itemWriter) .build()) diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/multirecordtype/DelegatingTradeLineAggregator.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/multirecordtype/DelegatingTradeLineAggregator.java index 7aaa8b68af..53aa2418e0 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/multirecordtype/DelegatingTradeLineAggregator.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/multirecordtype/DelegatingTradeLineAggregator.java @@ -32,11 +32,11 @@ public class DelegatingTradeLineAggregator implements LineAggregator { @Override public String aggregate(Object item) { - if (item instanceof Trade) { - return this.tradeLineAggregator.aggregate((Trade) item); + if (item instanceof Trade trade) { + return this.tradeLineAggregator.aggregate(trade); } - else if (item instanceof CustomerCredit) { - return this.customerLineAggregator.aggregate((CustomerCredit) item); + else if (item instanceof CustomerCredit customerCredit) { + return this.customerLineAggregator.aggregate(customerCredit); } else { throw new RuntimeException(); diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/multirecordtype/MultiRecordTypeJobConfiguration.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/multirecordtype/MultiRecordTypeJobConfiguration.java index 29a29c7a42..f5aaf0e463 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/multirecordtype/MultiRecordTypeJobConfiguration.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/multirecordtype/MultiRecordTypeJobConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2023 the original author or authors. + * Copyright 2023-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,7 +17,8 @@ import java.util.Map; -import org.springframework.batch.core.Job; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; +import org.springframework.batch.core.job.Job; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; import org.springframework.batch.core.configuration.annotation.StepScope; import org.springframework.batch.core.job.builder.JobBuilder; @@ -41,7 +42,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; -import org.springframework.core.io.Resource; +import org.springframework.core.io.FileSystemResource; import org.springframework.core.io.WritableResource; import org.springframework.jdbc.support.JdbcTransactionManager; @@ -50,13 +51,14 @@ */ @Configuration @EnableBatchProcessing +@EnableJdbcJobRepository @Import(DataSourceConfiguration.class) public class MultiRecordTypeJobConfiguration { @Bean @StepScope public FlatFileItemReader itemReader(PatternMatchingCompositeLineMapper lineMapper, - @Value("#{jobParameters[inputFile]}") Resource resource) { + @Value("#{jobParameters[inputFile]}") FileSystemResource resource) { return new FlatFileItemReaderBuilder().name("itemReader").resource(resource).lineMapper(lineMapper).build(); } @@ -129,7 +131,8 @@ public FormatterLineAggregator customerLineAggregator() { public Job job(JobRepository jobRepository, JdbcTransactionManager transactionManager, FlatFileItemReader itemReader, FlatFileItemWriter itemWriter) { return new JobBuilder("ioSampleJob", jobRepository) - .start(new StepBuilder("step1", jobRepository).chunk(2, transactionManager) + .start(new StepBuilder("step1", jobRepository).chunk(2) + .transactionManager(transactionManager) .reader(itemReader) .writer(itemWriter) .build()) diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/multiresource/MultiResourceJobConfiguration.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/multiresource/MultiResourceJobConfiguration.java index 2e21465916..0ea241c961 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/multiresource/MultiResourceJobConfiguration.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/multiresource/MultiResourceJobConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2023 the original author or authors. + * Copyright 2023-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,7 +15,8 @@ */ package org.springframework.batch.samples.file.multiresource; -import org.springframework.batch.core.Job; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; +import org.springframework.batch.core.job.Job; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; import org.springframework.batch.core.configuration.annotation.StepScope; import org.springframework.batch.core.job.builder.JobBuilder; @@ -47,6 +48,7 @@ */ @Configuration @EnableBatchProcessing +@EnableJdbcJobRepository @Import(DataSourceConfiguration.class) public class MultiResourceJobConfiguration { @@ -92,7 +94,8 @@ public FlatFileItemWriter delegateWriter() { public Job job(JobRepository jobRepository, JdbcTransactionManager transactionManager, ItemReader itemReader, ItemWriter itemWriter) { return new JobBuilder("ioSampleJob", jobRepository) - .start(new StepBuilder("step1", jobRepository).chunk(2, transactionManager) + .start(new StepBuilder("step1", jobRepository).chunk(2) + .transactionManager(transactionManager) .reader(itemReader) .processor(new CustomerCreditIncreaseProcessor()) .writer(itemWriter) diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/patternmatching/internal/validator/OrderValidator.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/patternmatching/internal/validator/OrderValidator.java index d3a2841896..c25cfee129 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/patternmatching/internal/validator/OrderValidator.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/patternmatching/internal/validator/OrderValidator.java @@ -256,8 +256,8 @@ protected void validateAddress(Address address, Errors errors, String prefix) { errors.rejectValue(prefix + ".zipCode", "error.baddress.zipcode.format"); } - if ((!StringUtils.hasText(address.getState()) && ("United States".equals(address.getCountry())) - || StringUtils.hasText(address.getState()) && address.getState().length() != 2)) { + if ((!StringUtils.hasText(address.getState()) && "United States".equals(address.getCountry())) + || (StringUtils.hasText(address.getState()) && (address.getState().length() != 2))) { errors.rejectValue(prefix + ".state", "error.baddress.state.length"); } diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/xml/XmlJobConfiguration.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/xml/XmlJobConfiguration.java index 72452914e2..47dbe6397a 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/xml/XmlJobConfiguration.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/xml/XmlJobConfiguration.java @@ -1,3 +1,18 @@ +/* + * Copyright 2023-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.file.xml; import java.math.BigDecimal; @@ -5,7 +20,8 @@ import com.thoughtworks.xstream.security.ExplicitTypePermission; -import org.springframework.batch.core.Job; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; +import org.springframework.batch.core.job.Job; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; import org.springframework.batch.core.configuration.annotation.StepScope; import org.springframework.batch.core.job.builder.JobBuilder; @@ -31,6 +47,7 @@ @Configuration @EnableBatchProcessing +@EnableJdbcJobRepository @Import(DataSourceConfiguration.class) public class XmlJobConfiguration { @@ -69,7 +86,8 @@ public StaxEventItemWriter itemWriter( public Job job(JobRepository jobRepository, JdbcTransactionManager transactionManager, ItemReader itemReader, ItemWriter itemWriter) { return new JobBuilder("ioSampleJob", jobRepository) - .start(new StepBuilder("step1", jobRepository).chunk(2, transactionManager) + .start(new StepBuilder("step1", jobRepository).chunk(2) + .transactionManager(transactionManager) .reader(itemReader) .processor(new CustomerCreditIncreaseProcessor()) .writer(itemWriter) diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/football/FootballJobConfiguration.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/football/FootballJobConfiguration.java index c19e74406c..d6f5e9a051 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/football/FootballJobConfiguration.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/football/FootballJobConfiguration.java @@ -1,9 +1,25 @@ +/* + * Copyright 2023-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.football; import javax.sql.DataSource; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.Step; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.step.Step; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; import org.springframework.batch.core.job.builder.JobBuilder; import org.springframework.batch.core.repository.JobRepository; @@ -28,6 +44,7 @@ @Configuration @EnableBatchProcessing +@EnableJdbcJobRepository @Import(DataSourceConfiguration.class) public class FootballJobConfiguration { @@ -55,7 +72,8 @@ public PlayerItemWriter playerWriter(DataSource dataSource) { @Bean public Step playerLoad(JobRepository jobRepository, JdbcTransactionManager transactionManager, FlatFileItemReader playerFileItemReader, PlayerItemWriter playerWriter) { - return new StepBuilder("playerLoad", jobRepository).chunk(2, transactionManager) + return new StepBuilder("playerLoad", jobRepository).chunk(2) + .transactionManager(transactionManager) .reader(playerFileItemReader) .writer(playerWriter) .build(); @@ -84,7 +102,8 @@ public JdbcGameDao gameWriter(DataSource dataSource) { @Bean public Step gameLoad(JobRepository jobRepository, JdbcTransactionManager transactionManager, FlatFileItemReader gameFileItemReader, JdbcGameDao gameWriter) { - return new StepBuilder("gameLoad", jobRepository).chunk(2, transactionManager) + return new StepBuilder("gameLoad", jobRepository).chunk(2) + .transactionManager(transactionManager) .reader(gameFileItemReader) .writer(gameWriter) .build(); @@ -120,8 +139,8 @@ public JdbcPlayerSummaryDao summaryWriter(DataSource dataSource) { @Bean public Step summarizationStep(JobRepository jobRepository, JdbcTransactionManager transactionManager, JdbcCursorItemReader playerSummarizationSource, JdbcPlayerSummaryDao summaryWriter) { - return new StepBuilder("summarizationStep", jobRepository) - .chunk(2, transactionManager) + return new StepBuilder("summarizationStep", jobRepository).chunk(2) + .transactionManager(transactionManager) .reader(playerSummarizationSource) .writer(summaryWriter) .build(); diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/helloworld/HelloWorldJobConfiguration.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/helloworld/HelloWorldJobConfiguration.java index 4811f6c965..1be6378d09 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/helloworld/HelloWorldJobConfiguration.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/helloworld/HelloWorldJobConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2023 the original author or authors. + * Copyright 2023-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,30 +15,26 @@ */ package org.springframework.batch.samples.helloworld; -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.Job; import org.springframework.batch.core.job.builder.JobBuilder; import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.core.step.Step; import org.springframework.batch.core.step.builder.StepBuilder; import org.springframework.batch.repeat.RepeatStatus; -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.jdbc.support.JdbcTransactionManager; @Configuration @EnableBatchProcessing -@Import(DataSourceConfiguration.class) public class HelloWorldJobConfiguration { @Bean - public Step step(JobRepository jobRepository, JdbcTransactionManager transactionManager) { + public Step step(JobRepository jobRepository) { return new StepBuilder("step", jobRepository).tasklet((contribution, chunkContext) -> { System.out.println("Hello world!"); return RepeatStatus.FINISHED; - }, transactionManager).build(); + }).build(); } @Bean diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/jdbc/JdbcReaderBatchWriterSampleJob.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/jdbc/JdbcReaderBatchWriterSampleJob.java index b8727b82f5..34e6522ec8 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/jdbc/JdbcReaderBatchWriterSampleJob.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/jdbc/JdbcReaderBatchWriterSampleJob.java @@ -1,5 +1,5 @@ /* - * Copyright 2023 the original author or authors. + * Copyright 2023-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,7 +17,8 @@ import javax.sql.DataSource; -import org.springframework.batch.core.Job; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; +import org.springframework.batch.core.job.Job; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; import org.springframework.batch.core.job.builder.JobBuilder; import org.springframework.batch.core.repository.JobRepository; @@ -39,6 +40,7 @@ */ @Configuration @EnableBatchProcessing +@EnableJdbcJobRepository @Import(DataSourceConfiguration.class) public class JdbcReaderBatchWriterSampleJob { @@ -56,7 +58,8 @@ public JdbcBatchItemWriter itemWriter(DataSource dataSource) { public Job job(JobRepository jobRepository, JdbcTransactionManager transactionManager, ItemReader itemReader, JdbcBatchItemWriter itemWriter) { return new JobBuilder("ioSampleJob", jobRepository) - .start(new StepBuilder("step1", jobRepository).chunk(2, transactionManager) + .start(new StepBuilder("step1", jobRepository).chunk(2) + .transactionManager(transactionManager) .reader(itemReader) .processor(new CustomerCreditIncreaseProcessor()) .writer(itemWriter) diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/jpa/JpaJobConfiguration.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/jpa/JpaJobConfiguration.java index 2d10df4f1b..5a12279056 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/jpa/JpaJobConfiguration.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/jpa/JpaJobConfiguration.java @@ -18,8 +18,9 @@ import javax.sql.DataSource; import jakarta.persistence.EntityManagerFactory; -import org.springframework.batch.core.Job; +import org.springframework.batch.core.job.Job; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; import org.springframework.batch.core.job.builder.JobBuilder; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.step.builder.StepBuilder; @@ -38,6 +39,7 @@ import org.springframework.orm.jpa.persistenceunit.DefaultPersistenceUnitManager; import org.springframework.orm.jpa.persistenceunit.PersistenceUnitManager; import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; +import org.springframework.transaction.annotation.Isolation; /** * Hibernate JPA dialect does not support custom tx isolation levels => overwrite with @@ -47,7 +49,8 @@ */ @Configuration @Import(DataSourceConfiguration.class) -@EnableBatchProcessing(isolationLevelForCreate = "ISOLATION_DEFAULT", transactionManagerRef = "jpaTransactionManager") +@EnableBatchProcessing +@EnableJdbcJobRepository(isolationLevelForCreate = Isolation.DEFAULT, transactionManagerRef = "jpaTransactionManager") public class JpaJobConfiguration { @Bean diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/jpa/JpaRepositoryJobConfiguration.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/jpa/JpaRepositoryJobConfiguration.java index 8820bd9567..40ec61482e 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/jpa/JpaRepositoryJobConfiguration.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/jpa/JpaRepositoryJobConfiguration.java @@ -21,8 +21,9 @@ import javax.sql.DataSource; import jakarta.persistence.EntityManagerFactory; -import org.springframework.batch.core.Job; +import org.springframework.batch.core.job.Job; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; import org.springframework.batch.core.configuration.annotation.StepScope; import org.springframework.batch.core.job.builder.JobBuilder; import org.springframework.batch.core.repository.JobRepository; @@ -45,6 +46,7 @@ import org.springframework.orm.jpa.persistenceunit.DefaultPersistenceUnitManager; import org.springframework.orm.jpa.persistenceunit.PersistenceUnitManager; import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; +import org.springframework.transaction.annotation.Isolation; /** * Hibernate JPA dialect does not support custom tx isolation levels => overwrite with @@ -54,7 +56,8 @@ */ @Configuration @Import(DataSourceConfiguration.class) -@EnableBatchProcessing(isolationLevelForCreate = "ISOLATION_DEFAULT", transactionManagerRef = "jpaTransactionManager") +@EnableBatchProcessing +@EnableJdbcJobRepository(isolationLevelForCreate = Isolation.DEFAULT, transactionManagerRef = "jpaTransactionManager") @EnableJpaRepositories(basePackages = "org.springframework.batch.samples.jpa") public class JpaRepositoryJobConfiguration { diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/launch/DefaultJobLoader.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/launch/DefaultJobLoader.java index d7bc149d0b..07ee3a6f2a 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/launch/DefaultJobLoader.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/launch/DefaultJobLoader.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. @@ -19,8 +19,8 @@ import java.util.HashMap; import java.util.Map; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.configuration.ListableJobLocator; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.configuration.JobRegistry; import org.springframework.batch.core.launch.NoSuchJobException; import org.springframework.beans.BeanWrapperImpl; import org.springframework.beans.BeansException; @@ -32,7 +32,7 @@ public class DefaultJobLoader implements JobLoader, ApplicationContextAware { - private ListableJobLocator registry; + private JobRegistry registry; private ApplicationContext applicationContext; @@ -43,7 +43,7 @@ public void setApplicationContext(ApplicationContext applicationContext) throws this.applicationContext = applicationContext; } - public void setRegistry(ListableJobLocator registry) { + public void setRegistry(JobRegistry registry) { this.registry = registry; } diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/loom/JobConfigurationForAsynchronousItemProcessingWithVirtualThreads.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/loom/JobConfigurationForAsynchronousItemProcessingWithVirtualThreads.java index 13690a8c3d..d51f3efcd7 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/loom/JobConfigurationForAsynchronousItemProcessingWithVirtualThreads.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/loom/JobConfigurationForAsynchronousItemProcessingWithVirtualThreads.java @@ -1,5 +1,5 @@ /* - * Copyright 2023 the original author or authors. + * Copyright 2023-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,8 +18,9 @@ import java.util.Arrays; import java.util.concurrent.Future; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.Step; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.step.Step; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; import org.springframework.batch.core.job.builder.JobBuilder; import org.springframework.batch.core.repository.JobRepository; @@ -43,6 +44,7 @@ */ @Configuration @EnableBatchProcessing +@EnableJdbcJobRepository @Import(DataSourceConfiguration.class) public class JobConfigurationForAsynchronousItemProcessingWithVirtualThreads { @@ -77,7 +79,8 @@ public AsyncItemWriter itemWriter() { public Job job(JobRepository jobRepository, JdbcTransactionManager transactionManager, ItemReader itemReader, AsyncItemProcessor itemProcessor, AsyncItemWriter itemWriter) { - Step step = new StepBuilder("step", jobRepository).>chunk(2, transactionManager) + Step step = new StepBuilder("step", jobRepository).>chunk(2) + .transactionManager(transactionManager) .reader(itemReader) .processor(itemProcessor) .writer(itemWriter) diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/loom/JobConfigurationForLaunchingJobsWithVirtualThreads.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/loom/JobConfigurationForLaunchingJobsWithVirtualThreads.java index 10adaf525d..8006c3fb48 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/loom/JobConfigurationForLaunchingJobsWithVirtualThreads.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/loom/JobConfigurationForLaunchingJobsWithVirtualThreads.java @@ -1,5 +1,5 @@ /* - * Copyright 2023 the original author or authors. + * Copyright 2023-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,11 +15,12 @@ */ package org.springframework.batch.samples.loom; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.Step; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.launch.JobOperator; +import org.springframework.batch.core.step.Step; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; 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.repeat.RepeatStatus; @@ -32,13 +33,14 @@ import org.springframework.jdbc.support.JdbcTransactionManager; /** - * Configuration class that defines a {@link JobLauncher} based on a + * Configuration class that defines a {@link JobOperator} based on a * {@link VirtualThreadTaskExecutor}. * * @author Mahmoud Ben Hassine */ @Configuration @EnableBatchProcessing +@EnableJdbcJobRepository @Import(DataSourceConfiguration.class) public class JobConfigurationForLaunchingJobsWithVirtualThreads { diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/loom/JobConfigurationForRunningConcurrentStepsWithVirtualThreads.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/loom/JobConfigurationForRunningConcurrentStepsWithVirtualThreads.java index aa360a8df6..39a4a7f161 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/loom/JobConfigurationForRunningConcurrentStepsWithVirtualThreads.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/loom/JobConfigurationForRunningConcurrentStepsWithVirtualThreads.java @@ -1,5 +1,5 @@ /* - * Copyright 2023 the original author or authors. + * Copyright 2023-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,8 +19,9 @@ import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.Step; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.step.Step; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; import org.springframework.batch.core.job.builder.JobBuilder; import org.springframework.batch.core.repository.JobRepository; @@ -43,6 +44,7 @@ */ @Configuration @EnableBatchProcessing +@EnableJdbcJobRepository @Import(DataSourceConfiguration.class) public class JobConfigurationForRunningConcurrentStepsWithVirtualThreads { @@ -78,7 +80,8 @@ public ItemWriter itemWriter() { @Bean public Job job(JobRepository jobRepository, JdbcTransactionManager transactionManager, ItemReader itemReader, ItemWriter itemWriter) { - Step step = new StepBuilder("step", jobRepository).chunk(2, transactionManager) + Step step = new StepBuilder("step", jobRepository).chunk(2) + .transactionManager(transactionManager) .reader(itemReader) .writer(itemWriter) .taskExecutor(new VirtualThreadTaskExecutor("spring-batch-")) diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/loom/JobConfigurationForRunningParallelStepsWithVirtualThreads.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/loom/JobConfigurationForRunningParallelStepsWithVirtualThreads.java index 3c345dc9d4..f19c757bae 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/loom/JobConfigurationForRunningParallelStepsWithVirtualThreads.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/loom/JobConfigurationForRunningParallelStepsWithVirtualThreads.java @@ -15,8 +15,9 @@ */ package org.springframework.batch.samples.loom; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.Step; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.step.Step; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; import org.springframework.batch.core.job.builder.FlowBuilder; import org.springframework.batch.core.job.builder.JobBuilder; @@ -39,6 +40,7 @@ */ @Configuration @EnableBatchProcessing +@EnableJdbcJobRepository @Import(DataSourceConfiguration.class) public class JobConfigurationForRunningParallelStepsWithVirtualThreads { diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/loom/JobConfigurationForRunningPartitionedStepsWithVirtualThreads.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/loom/JobConfigurationForRunningPartitionedStepsWithVirtualThreads.java index be20323453..742494c8e8 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/loom/JobConfigurationForRunningPartitionedStepsWithVirtualThreads.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/loom/JobConfigurationForRunningPartitionedStepsWithVirtualThreads.java @@ -1,5 +1,5 @@ /* - * Copyright 2023 the original author or authors. + * Copyright 2023-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,13 @@ import java.util.HashMap; import java.util.Map; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.Step; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.step.Step; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; import org.springframework.batch.core.configuration.annotation.StepScope; import org.springframework.batch.core.job.builder.JobBuilder; -import org.springframework.batch.core.partition.support.Partitioner; +import org.springframework.batch.core.partition.Partitioner; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.step.builder.StepBuilder; import org.springframework.batch.core.step.tasklet.Tasklet; @@ -45,6 +46,7 @@ */ @Configuration @EnableBatchProcessing +@EnableJdbcJobRepository @Import(DataSourceConfiguration.class) public class JobConfigurationForRunningPartitionedStepsWithVirtualThreads { diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/loom/JobConfigurationForRunningSystemCommandTaskletsWithVirtualThreads.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/loom/JobConfigurationForRunningSystemCommandTaskletsWithVirtualThreads.java index 6cd881053e..d64985f0df 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/loom/JobConfigurationForRunningSystemCommandTaskletsWithVirtualThreads.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/loom/JobConfigurationForRunningSystemCommandTaskletsWithVirtualThreads.java @@ -19,8 +19,9 @@ import java.io.IOException; import java.util.Arrays; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.Step; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.step.Step; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; import org.springframework.batch.core.job.builder.JobBuilder; import org.springframework.batch.core.repository.JobRepository; @@ -42,6 +43,7 @@ */ @Configuration @EnableBatchProcessing +@EnableJdbcJobRepository @Import(DataSourceConfiguration.class) public class JobConfigurationForRunningSystemCommandTaskletsWithVirtualThreads { diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/loop/GeneratingTradeResettingListener.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/loop/GeneratingTradeResettingListener.java index 686450009c..e26ab46e69 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/loop/GeneratingTradeResettingListener.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/loop/GeneratingTradeResettingListener.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,8 +16,8 @@ package org.springframework.batch.samples.loop; import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.StepExecutionListener; +import org.springframework.batch.core.step.StepExecution; +import org.springframework.batch.core.listener.StepExecutionListener; import org.springframework.batch.samples.domain.trade.internal.GeneratingTradeItemReader; import org.springframework.beans.factory.InitializingBean; import org.springframework.lang.Nullable; diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/loop/LimitDecider.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/loop/LimitDecider.java index ca0a6f09d1..51cd2e7533 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/loop/LimitDecider.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/loop/LimitDecider.java @@ -15,8 +15,8 @@ */ package org.springframework.batch.samples.loop; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.job.flow.FlowExecutionStatus; import org.springframework.batch.core.job.flow.JobExecutionDecider; import org.springframework.lang.Nullable; diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/metrics/BatchMetricsApplication.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/metrics/BatchMetricsApplication.java index 80ea2f8211..d670adbd9f 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/metrics/BatchMetricsApplication.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/metrics/BatchMetricsApplication.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. @@ -16,6 +16,7 @@ package org.springframework.batch.samples.metrics; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; import org.springframework.batch.samples.common.DataSourceConfiguration; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.AnnotationConfigApplicationContext; @@ -27,9 +28,10 @@ @EnableScheduling @EnableBatchProcessing +@EnableJdbcJobRepository @Import({ Job1Configuration.class, Job2Configuration.class, JobScheduler.class, PrometheusConfiguration.class, DataSourceConfiguration.class }) -@PropertySource("metrics-sample.properties") +@PropertySource("classpath:org/springframework/batch/samples/metrics/metrics-sample.properties") public class BatchMetricsApplication { public static void main(String[] args) { diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/metrics/Job1Configuration.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/metrics/Job1Configuration.java index e7fd372209..f3b6309456 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/metrics/Job1Configuration.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/metrics/Job1Configuration.java @@ -17,8 +17,8 @@ import java.util.Random; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.Step; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.step.Step; import org.springframework.batch.core.job.builder.JobBuilder; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.step.builder.StepBuilder; diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/metrics/Job2Configuration.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/metrics/Job2Configuration.java index 99a81e22d4..e0f59048ff 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/metrics/Job2Configuration.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/metrics/Job2Configuration.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. @@ -19,8 +19,8 @@ import java.util.List; import java.util.Random; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.Step; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.step.Step; import org.springframework.batch.core.configuration.annotation.StepScope; import org.springframework.batch.core.job.builder.JobBuilder; import org.springframework.batch.core.repository.JobRepository; @@ -47,7 +47,8 @@ public Job job2(JobRepository jobRepository, PlatformTransactionManager transact @Bean public Step step(JobRepository jobRepository, PlatformTransactionManager transactionManager) { - return new StepBuilder("step1", jobRepository).chunk(3, transactionManager) + return new StepBuilder("step1", jobRepository).chunk(3) + .transactionManager(transactionManager) .reader(itemReader()) .writer(itemWriter()) .build(); diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/metrics/JobScheduler.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/metrics/JobScheduler.java index d765266977..ad4ac6f003 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/metrics/JobScheduler.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/metrics/JobScheduler.java @@ -1,9 +1,28 @@ +/* + * 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. + * 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.metrics; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.JobParametersBuilder; -import org.springframework.batch.core.launch.JobLauncher; +import io.prometheus.metrics.exporter.pushgateway.PushGateway; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; +import org.springframework.batch.core.launch.JobOperator; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; @@ -11,17 +30,22 @@ @Component public class JobScheduler { + private static final Log LOGGER = LogFactory.getLog(JobScheduler.class); + private final Job job1; private final Job job2; - private final JobLauncher jobLauncher; + private final JobOperator jobOperator; + + private final PushGateway pushGateway; @Autowired - public JobScheduler(Job job1, Job job2, JobLauncher jobLauncher) { + public JobScheduler(Job job1, Job job2, JobOperator jobOperator, PushGateway pushGateway) { this.job1 = job1; this.job2 = job2; - this.jobLauncher = jobLauncher; + this.jobOperator = jobOperator; + this.pushGateway = pushGateway; } @Scheduled(cron = "*/10 * * * * *") @@ -29,7 +53,7 @@ public void launchJob1() throws Exception { JobParameters jobParameters = new JobParametersBuilder().addLong("time", System.currentTimeMillis()) .toJobParameters(); - jobLauncher.run(job1, jobParameters); + jobOperator.start(job1, jobParameters); } @Scheduled(cron = "*/15 * * * * *") @@ -37,7 +61,17 @@ public void launchJob2() throws Exception { JobParameters jobParameters = new JobParametersBuilder().addLong("time", System.currentTimeMillis()) .toJobParameters(); - jobLauncher.run(job2, jobParameters); + jobOperator.start(job2, jobParameters); + } + + @Scheduled(fixedRateString = "${prometheus.push.rate}") + public void pushMetrics() { + try { + pushGateway.pushAdd(); + } + catch (Throwable ex) { + LOGGER.error("Unable to push metrics to Prometheus Push Gateway", ex); + } } } diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/metrics/PrometheusConfiguration.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/metrics/PrometheusConfiguration.java index b83014b94e..40d39280ab 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/metrics/PrometheusConfiguration.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/metrics/PrometheusConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2021-2023 the original author or authors. + * Copyright 2021-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,27 +15,21 @@ */ package org.springframework.batch.samples.metrics; -import java.util.HashMap; -import java.util.Map; -import jakarta.annotation.PostConstruct; - -import io.micrometer.core.instrument.Metrics; -import io.micrometer.prometheus.PrometheusConfig; -import io.micrometer.prometheus.PrometheusMeterRegistry; -import io.prometheus.client.CollectorRegistry; -import io.prometheus.client.exporter.PushGateway; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import io.micrometer.core.instrument.Clock; +import io.micrometer.core.instrument.observation.DefaultMeterObservationHandler; +import io.micrometer.observation.ObservationRegistry; +import io.micrometer.prometheusmetrics.PrometheusConfig; +import io.micrometer.prometheusmetrics.PrometheusMeterRegistry; +import io.prometheus.metrics.exporter.pushgateway.PushGateway; +import io.prometheus.metrics.model.registry.PrometheusRegistry; import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.scheduling.annotation.Scheduled; @Configuration public class PrometheusConfiguration { - private static final Logger LOGGER = LoggerFactory.getLogger(PrometheusConfiguration.class); - @Value("${prometheus.job.name}") private String prometheusJobName; @@ -45,29 +39,30 @@ public class PrometheusConfiguration { @Value("${prometheus.pushgateway.url}") private String prometheusPushGatewayUrl; - private final Map groupingKey = new HashMap<>(); - - private PushGateway pushGateway; + @Bean + public PrometheusRegistry prometheusRegistry() { + return new PrometheusRegistry(); + } - private CollectorRegistry collectorRegistry; + @Bean + public PrometheusMeterRegistry meterRegistry(PrometheusRegistry prometheusRegistry) { + return new PrometheusMeterRegistry(PrometheusConfig.DEFAULT, prometheusRegistry, Clock.SYSTEM); + } - @PostConstruct - public void init() { - pushGateway = new PushGateway(prometheusPushGatewayUrl); - groupingKey.put(prometheusGroupingKey, prometheusJobName); - PrometheusMeterRegistry prometheusMeterRegistry = new PrometheusMeterRegistry(PrometheusConfig.DEFAULT); - collectorRegistry = prometheusMeterRegistry.getPrometheusRegistry(); - Metrics.globalRegistry.add(prometheusMeterRegistry); + @Bean + public PushGateway pushGateway(PrometheusRegistry prometheusRegistry) { + return PushGateway.builder() + .address(prometheusPushGatewayUrl) + .groupingKey(prometheusGroupingKey, prometheusJobName) + .registry(prometheusRegistry) + .build(); } - @Scheduled(fixedRateString = "${prometheus.push.rate}") - public void pushMetrics() { - try { - pushGateway.pushAdd(collectorRegistry, prometheusJobName, groupingKey); - } - catch (Throwable ex) { - LOGGER.error("Unable to push metrics to Prometheus Push Gateway", ex); - } + @Bean + public ObservationRegistry observationRegistry(PrometheusMeterRegistry meterRegistry) { + ObservationRegistry observationRegistry = ObservationRegistry.create(); + observationRegistry.observationConfig().observationHandler(new DefaultMeterObservationHandler(meterRegistry)); + return observationRegistry; } } diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/misc/jmx/InfiniteLoopWriter.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/misc/jmx/InfiniteLoopWriter.java index c1052b5367..3b5b4152ea 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/misc/jmx/InfiniteLoopWriter.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/misc/jmx/InfiniteLoopWriter.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. @@ -19,8 +19,8 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.StepExecutionListener; +import org.springframework.batch.core.step.StepExecution; +import org.springframework.batch.core.listener.StepExecutionListener; import org.springframework.batch.item.Chunk; import org.springframework.batch.item.ItemWriter; @@ -41,7 +41,7 @@ public class InfiniteLoopWriter implements StepExecutionListener, ItemWriter jobDataMap) for (Entry entry : jobDataMap.entrySet()) { String key = entry.getKey(); Object value = entry.getValue(); - if (value instanceof String && !key.equals(JOB_NAME)) { - builder.addString(key, (String) value); + if (value instanceof String s && !key.equals(JOB_NAME)) { + builder.addString(key, s); } else if (value instanceof Float || value instanceof Double) { builder.addDouble(key, ((Number) value).doubleValue()); @@ -101,8 +94,8 @@ else if (value instanceof Float || value instanceof Double) { else if (value instanceof Integer || value instanceof Long) { builder.addLong(key, ((Number) value).longValue()); } - else if (value instanceof Date) { - builder.addDate(key, (Date) value); + else if (value instanceof Date date) { + builder.addDate(key, date); } else { log.debug("JobDataMap contains values which are not job parameters (ignoring)."); 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..483b500181 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-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,16 +18,15 @@ import java.util.HashMap; import java.util.Map; -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.Job; +import org.springframework.batch.core.step.Step; 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,15 +59,16 @@ 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) { - return new StepBuilder("step", jobRepository).chunk(2, transactionManager) + MongoPagingItemReader mongoPersonReader, MongoItemWriter mongoPersonRemover) { + return new StepBuilder("step", jobRepository).chunk(2) + .transactionManager(transactionManager) .reader(mongoPersonReader) .writer(mongoPersonRemover) .build(); 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..b245b02084 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-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,16 +18,15 @@ import java.util.HashMap; import java.util.Map; -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.Job; +import org.springframework.batch.core.step.Step; 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,8 +60,9 @@ public MongoItemWriter mongoItemWriter(MongoTemplate mongoTemplate) { @Bean public Step step(JobRepository jobRepository, PlatformTransactionManager transactionManager, - MongoItemReader mongoItemReader, MongoItemWriter mongoItemWriter) { - return new StepBuilder("step", jobRepository).chunk(2, transactionManager) + MongoPagingItemReader mongoItemReader, MongoItemWriter mongoItemWriter) { + return new StepBuilder("step", jobRepository).chunk(2) + .transactionManager(transactionManager) .reader(mongoItemReader) .writer(mongoItemWriter) .build(); 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..a795e66789 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-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,10 @@ import com.mongodb.client.MongoClient; import com.mongodb.client.MongoClients; +import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; +import org.springframework.batch.core.configuration.annotation.EnableMongoJobRepository; +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 +30,12 @@ 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 +@EnableMongoJobRepository 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,14 @@ 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(); + } + } 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..f6ed798011 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-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,13 +17,14 @@ import java.util.Arrays; import java.util.List; +import java.util.Map; import com.mongodb.client.MongoCollection; import org.bson.Document; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.launch.JobOperator; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.data.mongodb.core.MongoTemplate; @@ -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"); @@ -54,9 +67,9 @@ public static void main(String[] args) throws Exception { new Document("name", "foo3"), new Document("name", "foo4"))); // run the insertion job - JobLauncher jobLauncher = context.getBean(JobLauncher.class); + JobOperator jobOperator = context.getBean(JobOperator.class); Job insertionJob = context.getBean("insertionJob", Job.class); - jobLauncher.run(insertionJob, new JobParameters()); + jobOperator.start(insertionJob, new JobParameters()); // check results List persons = mongoTemplate.findAll(Person.class, "person_out"); @@ -67,7 +80,7 @@ public static void main(String[] args) throws Exception { // run the deletion job Job deletionJob = context.getBean("deletionJob", Job.class); - jobLauncher.run(deletionJob, new JobParameters()); + jobOperator.start(deletionJob, new JobParameters()); // check results (foo3 should have been removed) persons = mongoTemplate.findAll(Person.class, "person_out"); diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/partitioning/remote/BasicPartitioner.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/partitioning/remote/BasicPartitioner.java index 99a5b50b60..b0e6dd86ce 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/partitioning/remote/BasicPartitioner.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/partitioning/remote/BasicPartitioner.java @@ -35,7 +35,7 @@ public Map partition(int gridSize) { Map partitions = super.partition(gridSize); int i = 0; for (ExecutionContext context : partitions.values()) { - context.put(PARTITION_KEY, PARTITION_KEY + (i++)); + context.put(PARTITION_KEY, PARTITION_KEY + i++); } return partitions; } diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/partitioning/remote/aggregating/ManagerConfiguration.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/partitioning/remote/aggregating/ManagerConfiguration.java index 34b14195c2..7703ef327c 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/partitioning/remote/aggregating/ManagerConfiguration.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/partitioning/remote/aggregating/ManagerConfiguration.java @@ -17,9 +17,10 @@ import org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.Step; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.step.Step; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; import org.springframework.batch.core.job.builder.JobBuilder; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.integration.config.annotation.EnableBatchIntegration; @@ -42,6 +43,7 @@ */ @Configuration @EnableBatchProcessing +@EnableJdbcJobRepository @EnableBatchIntegration @Import(value = { DataSourceConfiguration.class, BrokerConfiguration.class }) public class ManagerConfiguration { diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/partitioning/remote/aggregating/WorkerConfiguration.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/partitioning/remote/aggregating/WorkerConfiguration.java index 76985b89b0..791d4028f7 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/partitioning/remote/aggregating/WorkerConfiguration.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/partitioning/remote/aggregating/WorkerConfiguration.java @@ -17,8 +17,9 @@ import org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory; -import org.springframework.batch.core.Step; +import org.springframework.batch.core.step.Step; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; import org.springframework.batch.core.configuration.annotation.StepScope; import org.springframework.batch.core.step.tasklet.Tasklet; import org.springframework.batch.integration.config.annotation.EnableBatchIntegration; @@ -43,6 +44,7 @@ */ @Configuration @EnableBatchProcessing +@EnableJdbcJobRepository @EnableBatchIntegration @Import(value = { DataSourceConfiguration.class, BrokerConfiguration.class }) public class WorkerConfiguration { diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/partitioning/remote/polling/ManagerConfiguration.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/partitioning/remote/polling/ManagerConfiguration.java index af3102493a..c22afd4597 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/partitioning/remote/polling/ManagerConfiguration.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/partitioning/remote/polling/ManagerConfiguration.java @@ -17,9 +17,10 @@ import org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.Step; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.step.Step; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; import org.springframework.batch.core.job.builder.JobBuilder; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.integration.config.annotation.EnableBatchIntegration; @@ -42,6 +43,7 @@ */ @Configuration @EnableBatchProcessing +@EnableJdbcJobRepository @EnableBatchIntegration @Import(value = { DataSourceConfiguration.class, BrokerConfiguration.class }) public class ManagerConfiguration { diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/partitioning/remote/polling/WorkerConfiguration.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/partitioning/remote/polling/WorkerConfiguration.java index 61d84081b5..52dc7d132f 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/partitioning/remote/polling/WorkerConfiguration.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/partitioning/remote/polling/WorkerConfiguration.java @@ -17,8 +17,9 @@ import org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory; -import org.springframework.batch.core.Step; +import org.springframework.batch.core.step.Step; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; import org.springframework.batch.core.configuration.annotation.StepScope; import org.springframework.batch.core.step.tasklet.Tasklet; import org.springframework.batch.integration.config.annotation.EnableBatchIntegration; @@ -43,6 +44,7 @@ */ @Configuration @EnableBatchProcessing +@EnableJdbcJobRepository @EnableBatchIntegration @Import(value = { DataSourceConfiguration.class, BrokerConfiguration.class }) public class WorkerConfiguration { diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/common/OutputFileNameListener.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/petclinic/Owner.java similarity index 72% rename from spring-batch-samples/src/main/java/org/springframework/batch/samples/common/OutputFileNameListener.java rename to spring-batch-samples/src/main/java/org/springframework/batch/samples/petclinic/Owner.java index c0fcce77dc..7a66d7d296 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/common/OutputFileNameListener.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/petclinic/Owner.java @@ -1,5 +1,5 @@ /* - * Copyright 2009 the original author or authors. + * 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. @@ -13,8 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.batch.samples.common; - -public class OutputFileNameListener { +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..c87122f7e3 --- /dev/null +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/petclinic/OwnersExportJobConfiguration.java @@ -0,0 +1,74 @@ +/* + * 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.configuration.annotation.EnableJdbcJobRepository; +import org.springframework.batch.core.job.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 +@EnableJdbcJobRepository +@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(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/java/org/springframework/batch/samples/retry/RetrySampleConfiguration.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/retry/RetrySampleConfiguration.java index 5685cfa888..3984bc96fa 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/retry/RetrySampleConfiguration.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/retry/RetrySampleConfiguration.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. @@ -15,8 +15,9 @@ */ package org.springframework.batch.samples.retry; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.Step; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.step.Step; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; import org.springframework.batch.core.job.builder.JobBuilder; import org.springframework.batch.core.repository.JobRepository; @@ -37,6 +38,7 @@ */ @Configuration @EnableBatchProcessing +@EnableJdbcJobRepository @Import(DataSourceConfiguration.class) public class RetrySampleConfiguration { @@ -47,7 +49,8 @@ public Job retrySample(JobRepository jobRepository, Step step) { @Bean protected Step step(JobRepository jobRepository, JdbcTransactionManager transactionManager) { - return new StepBuilder("step", jobRepository).chunk(1, transactionManager) + return new StepBuilder("step", jobRepository).chunk(1) + .transactionManager(transactionManager) .reader(reader()) .writer(writer()) .faultTolerant() diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/skip/SkippableExceptionDuringProcessSample.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/skip/SkippableExceptionDuringProcessSample.java index 526d07518a..f1ec9c18e4 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/skip/SkippableExceptionDuringProcessSample.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/skip/SkippableExceptionDuringProcessSample.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. @@ -18,8 +18,9 @@ import java.util.Arrays; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.Step; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.step.Step; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; import org.springframework.batch.core.job.builder.JobBuilder; import org.springframework.batch.core.repository.JobRepository; @@ -39,6 +40,7 @@ */ @Configuration @EnableBatchProcessing +@EnableJdbcJobRepository @Import(DataSourceConfiguration.class) public class SkippableExceptionDuringProcessSample { @@ -84,7 +86,8 @@ public ItemWriter itemWriter() { @Bean public Step step(JobRepository jobRepository) { - return new StepBuilder("step", jobRepository).chunk(3, this.transactionManager) + return new StepBuilder("step", jobRepository).chunk(3) + .transactionManager(this.transactionManager) .reader(itemReader()) .processor(itemProcessor()) .writer(itemWriter()) diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/skip/SkippableExceptionDuringReadSample.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/skip/SkippableExceptionDuringReadSample.java index c55411aa96..2d09447828 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/skip/SkippableExceptionDuringReadSample.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/skip/SkippableExceptionDuringReadSample.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. @@ -18,8 +18,9 @@ import java.util.Arrays; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.Step; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.step.Step; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; import org.springframework.batch.core.job.builder.JobBuilder; import org.springframework.batch.core.repository.JobRepository; @@ -39,6 +40,7 @@ */ @Configuration @EnableBatchProcessing +@EnableJdbcJobRepository @Import(DataSourceConfiguration.class) public class SkippableExceptionDuringReadSample { @@ -84,7 +86,8 @@ public ItemWriter itemWriter() { @Bean public Step step(JobRepository jobRepository) { - return new StepBuilder("step", jobRepository).chunk(3, this.transactionManager) + return new StepBuilder("step", jobRepository).chunk(3) + .transactionManager(this.transactionManager) .reader(itemReader()) .processor(itemProcessor()) .writer(itemWriter()) diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/skip/SkippableExceptionDuringWriteSample.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/skip/SkippableExceptionDuringWriteSample.java index fbc610b16c..45b15c7d20 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/skip/SkippableExceptionDuringWriteSample.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/skip/SkippableExceptionDuringWriteSample.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. @@ -18,8 +18,9 @@ import java.util.Arrays; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.Step; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.step.Step; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; import org.springframework.batch.core.job.builder.JobBuilder; import org.springframework.batch.core.repository.JobRepository; @@ -39,6 +40,7 @@ */ @Configuration @EnableBatchProcessing +@EnableJdbcJobRepository @Import(DataSourceConfiguration.class) public class SkippableExceptionDuringWriteSample { @@ -84,7 +86,8 @@ public ItemWriter itemWriter() { @Bean public Step step(JobRepository jobRepository) { - return new StepBuilder("step", jobRepository).chunk(3, this.transactionManager) + return new StepBuilder("step", jobRepository).chunk(3) + .transactionManager(this.transactionManager) .reader(itemReader()) .processor(itemProcessor()) .writer(itemWriter()) diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/support/ExceptionThrowingItemReaderProxy.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/support/ExceptionThrowingItemReaderProxy.java index dbe0cf8fcd..8c16e3cb21 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/support/ExceptionThrowingItemReaderProxy.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/support/ExceptionThrowingItemReaderProxy.java @@ -16,7 +16,7 @@ package org.springframework.batch.samples.support; -import org.springframework.batch.core.UnexpectedJobExecutionException; +import org.springframework.batch.core.job.UnexpectedJobExecutionException; import org.springframework.batch.item.ItemReader; import org.springframework.lang.Nullable; diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/support/SummaryFooterCallback.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/support/SummaryFooterCallback.java index 985f8d5c79..c3d67441ec 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/support/SummaryFooterCallback.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/support/SummaryFooterCallback.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2021 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. @@ -19,8 +19,8 @@ import java.io.IOException; import java.io.Writer; -import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.StepExecutionListener; +import org.springframework.batch.core.step.StepExecution; +import org.springframework.batch.core.listener.StepExecutionListener; import org.springframework.batch.item.file.FlatFileFooterCallback; /** diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/validation/ValidationSampleConfiguration.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/validation/ValidationSampleConfiguration.java index a75a12653d..37f15d3c17 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/validation/ValidationSampleConfiguration.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/validation/ValidationSampleConfiguration.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,8 +18,9 @@ import java.util.Arrays; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.Step; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.step.Step; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; import org.springframework.batch.core.job.builder.JobBuilder; import org.springframework.batch.core.repository.JobRepository; @@ -39,6 +40,7 @@ */ @Configuration @EnableBatchProcessing +@EnableJdbcJobRepository @Import(DataSourceConfiguration.class) public class ValidationSampleConfiguration { @@ -65,7 +67,8 @@ public BeanValidatingItemProcessor itemValidator() throws Exception { @Bean public Step step(JobRepository jobRepository, JdbcTransactionManager transactionManager) throws Exception { - return new StepBuilder("step", jobRepository).chunk(1, transactionManager) + return new StepBuilder("step", jobRepository).chunk(1) + .transactionManager(transactionManager) .reader(itemReader()) .processor(itemValidator()) .writer(itemWriter()) 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/adapter/readerwriter/delegatingJob.xml b/spring-batch-samples/src/main/resources/org/springframework/batch/samples/adapter/readerwriter/delegatingJob.xml index cdb0c5177e..c2b9af257c 100644 --- a/spring-batch-samples/src/main/resources/org/springframework/batch/samples/adapter/readerwriter/delegatingJob.xml +++ b/spring-batch-samples/src/main/resources/org/springframework/batch/samples/adapter/readerwriter/delegatingJob.xml @@ -6,7 +6,7 @@ http://www.springframework.org/schema/batch https://www.springframework.org/schema/batch/spring-batch.xsd"> - The intent is to to give an example of how existing bean + The intent is to give an example of how existing bean definitions (e.g. from custom application's domain layer) can be integrated into a batch job. 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..f86890ec58 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 @@ -25,26 +26,26 @@ CREATE TABLE TRADE_SEQ ( ); INSERT INTO TRADE_SEQ (ID) values (0); -CREATE TABLE BATCH_STAGING ( - ID BIGINT IDENTITY NOT NULL PRIMARY KEY , +CREATE TABLE BATCH_STAGING ( + ID BIGINT IDENTITY NOT NULL PRIMARY KEY, JOB_ID BIGINT NOT NULL, VALUE LONGVARBINARY NOT NULL, PROCESSED CHAR(1) NOT NULL ) ; -CREATE TABLE TRADE ( - ID BIGINT IDENTITY NOT NULL PRIMARY KEY , - VERSION BIGINT , +CREATE TABLE TRADE ( + ID BIGINT IDENTITY NOT NULL PRIMARY KEY, + VERSION BIGINT, ISIN VARCHAR(45) NOT NULL, - QUANTITY BIGINT , - PRICE DECIMAL(8,2) , + QUANTITY BIGINT, + PRICE DECIMAL(8,2), CUSTOMER VARCHAR(45) ) ; CREATE TABLE CUSTOMER ( - ID BIGINT IDENTITY NOT NULL PRIMARY KEY , - VERSION BIGINT , - NAME VARCHAR(45) , + ID BIGINT IDENTITY NOT NULL PRIMARY KEY, + VERSION BIGINT, + NAME VARCHAR(45), CREDIT DECIMAL(10,2) ) ; @@ -57,7 +58,7 @@ CREATE TABLE PLAYERS ( PLAYER_ID CHAR(8) NOT NULL PRIMARY KEY, LAST_NAME VARCHAR(35) NOT NULL, FIRST_NAME VARCHAR(25) NOT NULL, - POS VARCHAR(10) , + POS VARCHAR(10), YEAR_OF_BIRTH BIGINT NOT NULL, YEAR_DRAFTED BIGINT NOT NULL ) ; @@ -67,36 +68,59 @@ CREATE TABLE GAMES ( YEAR_NO BIGINT NOT NULL, TEAM CHAR(3) NOT NULL, WEEK BIGINT NOT NULL, - OPPONENT CHAR(3) , - COMPLETES BIGINT , - ATTEMPTS BIGINT , - PASSING_YARDS BIGINT , - PASSING_TD BIGINT , - INTERCEPTIONS BIGINT , - RUSHES BIGINT , - RUSH_YARDS BIGINT , - RECEPTIONS BIGINT , - RECEPTIONS_YARDS BIGINT , + OPPONENT CHAR(3), + COMPLETES BIGINT, + ATTEMPTS BIGINT, + PASSING_YARDS BIGINT, + PASSING_TD BIGINT, + INTERCEPTIONS BIGINT, + RUSHES BIGINT, + RUSH_YARDS BIGINT, + RECEPTIONS BIGINT, + RECEPTIONS_YARDS BIGINT, TOTAL_TD BIGINT ) ; -CREATE TABLE PLAYER_SUMMARY ( +CREATE TABLE PLAYER_SUMMARY ( ID CHAR(8) NOT NULL, YEAR_NO BIGINT NOT NULL, - COMPLETES BIGINT NOT NULL , - ATTEMPTS BIGINT NOT NULL , - PASSING_YARDS BIGINT NOT NULL , - PASSING_TD BIGINT NOT NULL , - INTERCEPTIONS BIGINT NOT NULL , - RUSHES BIGINT NOT NULL , - RUSH_YARDS BIGINT NOT NULL , - RECEPTIONS BIGINT NOT NULL , - RECEPTIONS_YARDS BIGINT NOT NULL , + COMPLETES BIGINT NOT NULL, + ATTEMPTS BIGINT NOT NULL, + PASSING_YARDS BIGINT NOT NULL, + PASSING_TD BIGINT NOT NULL, + INTERCEPTIONS BIGINT NOT NULL, + RUSHES BIGINT NOT NULL, + RUSH_YARDS BIGINT NOT NULL, + RECEPTIONS BIGINT NOT NULL, + RECEPTIONS_YARDS BIGINT NOT NULL, TOTAL_TD BIGINT NOT NULL ) ; -CREATE TABLE ERROR_LOG ( - JOB_NAME CHAR(20) , - STEP_NAME CHAR(20) , +CREATE TABLE ERROR_LOG ( + JOB_NAME CHAR(20), + 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/file/multiline/job/multiLine.xml b/spring-batch-samples/src/main/resources/org/springframework/batch/samples/file/multiline/job/multiLine.xml index a21502bd22..879db2bd43 100644 --- a/spring-batch-samples/src/main/resources/org/springframework/batch/samples/file/multiline/job/multiLine.xml +++ b/spring-batch-samples/src/main/resources/org/springframework/batch/samples/file/multiline/job/multiLine.xml @@ -14,10 +14,14 @@ + + + + - + diff --git a/spring-batch-samples/src/main/resources/org/springframework/batch/samples/file/multirecordtype/job/multiRecordType.xml b/spring-batch-samples/src/main/resources/org/springframework/batch/samples/file/multirecordtype/job/multiRecordType.xml index d28c4382b4..dd1ca5b5fd 100644 --- a/spring-batch-samples/src/main/resources/org/springframework/batch/samples/file/multirecordtype/job/multiRecordType.xml +++ b/spring-batch-samples/src/main/resources/org/springframework/batch/samples/file/multirecordtype/job/multiRecordType.xml @@ -14,8 +14,12 @@ - - + + + + + + diff --git a/spring-batch-samples/src/main/resources/org/springframework/batch/samples/headerfooter/job/headerFooterSample.xml b/spring-batch-samples/src/main/resources/org/springframework/batch/samples/headerfooter/job/headerFooterSample.xml index 05658f6e05..b734b6a773 100644 --- a/spring-batch-samples/src/main/resources/org/springframework/batch/samples/headerfooter/job/headerFooterSample.xml +++ b/spring-batch-samples/src/main/resources/org/springframework/batch/samples/headerfooter/job/headerFooterSample.xml @@ -55,9 +55,9 @@ - + + value="src/main/resources/org/springframework/batch/samples/headerfooter/data/input.txt" /> + class="org.springframework.batch.core.repository.support.JdbcJobRepositoryFactoryBean"> - + + diff --git a/spring-batch-samples/src/main/resources/org/springframework/batch/samples/jpa/job/repository.xml b/spring-batch-samples/src/main/resources/org/springframework/batch/samples/jpa/job/repository.xml index d3e3698abc..13ac9a73f3 100644 --- a/spring-batch-samples/src/main/resources/org/springframework/batch/samples/jpa/job/repository.xml +++ b/spring-batch-samples/src/main/resources/org/springframework/batch/samples/jpa/job/repository.xml @@ -69,14 +69,14 @@ overwrite with ISOLATION_DEFAULT --> + class="org.springframework.batch.core.repository.support.JdbcJobRepositoryFactoryBean"> - + diff --git a/spring-batch-samples/src/main/resources/org/springframework/batch/samples/metrics/docker-compose.yml b/spring-batch-samples/src/main/resources/org/springframework/batch/samples/metrics/docker-compose.yml index a04917f915..aa5d46a382 100644 --- a/spring-batch-samples/src/main/resources/org/springframework/batch/samples/metrics/docker-compose.yml +++ b/spring-batch-samples/src/main/resources/org/springframework/batch/samples/metrics/docker-compose.yml @@ -2,7 +2,7 @@ version: '3.3' services: prometheus: - image: prom/prometheus:v2.7.2 + image: prom/prometheus:v3.6.0 container_name: 'prometheus' ports: - '9090:9090' @@ -10,13 +10,13 @@ services: - ./prometheus.yml:/etc/prometheus/prometheus.yml pushgateway: - image: prom/pushgateway:v0.6.0 + image: prom/pushgateway:v1.11.1 container_name: 'pushgateway' ports: - '9091:9091' grafana: - image: grafana/grafana:6.0.2 + image: grafana/grafana:12.3.0-17900959199-ubuntu container_name: 'grafana' ports: - '3000:3000' 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..25046703c5 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 @@ -6,21 +6,8 @@ - - - - - - - - - - - - + - - - - - - + + 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/main/resources/org/springframework/batch/samples/restart/stop/stopRestartSample.xml b/spring-batch-samples/src/main/resources/org/springframework/batch/samples/restart/stop/stopRestartSample.xml index 715a773e63..5ec9d360bf 100644 --- a/spring-batch-samples/src/main/resources/org/springframework/batch/samples/restart/stop/stopRestartSample.xml +++ b/spring-batch-samples/src/main/resources/org/springframework/batch/samples/restart/stop/stopRestartSample.xml @@ -1,29 +1,39 @@ + xmlns:p="http://www.springframework.org/schema/p" + xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd"> - + - - + + + + + + + + - + - + @@ -31,7 +41,7 @@ - + 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..58a60aaaf8 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 @@ -6,17 +6,13 @@ - - - - - - + - 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 7e66008208..8872e36eca 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 @@ -6,32 +6,15 @@ - - - - - - - - - - - - - + - + diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/samples/adapter/readerwriter/DelegatingJobFunctionalTests.java b/spring-batch-samples/src/test/java/org/springframework/batch/samples/adapter/readerwriter/DelegatingJobFunctionalTests.java index 592a3dcbeb..606c779aac 100644 --- a/spring-batch-samples/src/test/java/org/springframework/batch/samples/adapter/readerwriter/DelegatingJobFunctionalTests.java +++ b/spring-batch-samples/src/test/java/org/springframework/batch/samples/adapter/readerwriter/DelegatingJobFunctionalTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2007-2023 the original author or authors. + * Copyright 2007-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,7 +18,7 @@ import org.junit.jupiter.api.Test; import org.springframework.batch.samples.domain.person.PersonService; -import org.springframework.batch.test.JobLauncherTestUtils; +import org.springframework.batch.test.JobOperatorTestUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; @@ -30,14 +30,14 @@ class DelegatingJobFunctionalTests { @Autowired - private JobLauncherTestUtils jobLauncherTestUtils; + private JobOperatorTestUtils jobOperatorTestUtils; @Autowired private PersonService personService; @Test void testLaunchJob() throws Exception { - jobLauncherTestUtils.launchJob(); + jobOperatorTestUtils.startJob(); assertTrue(personService.getReturnedCount() > 0); assertEquals(personService.getReturnedCount(), personService.getReceivedCount()); diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/samples/adapter/tasklet/TaskletAdapterJobFunctionalTests.java b/spring-batch-samples/src/test/java/org/springframework/batch/samples/adapter/tasklet/TaskletAdapterJobFunctionalTests.java index c7916b0644..4f0a500477 100644 --- a/spring-batch-samples/src/test/java/org/springframework/batch/samples/adapter/tasklet/TaskletAdapterJobFunctionalTests.java +++ b/spring-batch-samples/src/test/java/org/springframework/batch/samples/adapter/tasklet/TaskletAdapterJobFunctionalTests.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. @@ -19,9 +19,9 @@ import org.junit.jupiter.api.Test; import org.springframework.batch.core.BatchStatus; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobParametersBuilder; -import org.springframework.batch.test.JobLauncherTestUtils; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; +import org.springframework.batch.test.JobOperatorTestUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; @@ -32,12 +32,12 @@ class TaskletAdapterJobFunctionalTests { @Autowired - private JobLauncherTestUtils jobLauncherTestUtils; + private JobOperatorTestUtils jobOperatorTestUtils; @Test void testLaunchJob() throws Exception { - JobExecution jobExecution = jobLauncherTestUtils - .launchJob(new JobParametersBuilder().addString("value", "foo").toJobParameters()); + JobExecution jobExecution = jobOperatorTestUtils + .startJob(new JobParametersBuilder().addString("value", "foo").toJobParameters()); assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus()); assertEquals("yes", jobExecution.getExecutionContext().getString("done")); } diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/samples/amqp/AmqpJobFunctionalTests.java b/spring-batch-samples/src/test/java/org/springframework/batch/samples/amqp/AmqpJobFunctionalTests.java index c13603e20c..7b9e83ab3e 100644 --- a/spring-batch-samples/src/test/java/org/springframework/batch/samples/amqp/AmqpJobFunctionalTests.java +++ b/spring-batch-samples/src/test/java/org/springframework/batch/samples/amqp/AmqpJobFunctionalTests.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. @@ -30,13 +30,13 @@ import org.springframework.amqp.rabbit.core.RabbitAdmin; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.explore.JobExplorer; -import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.core.launch.JobOperator; import org.springframework.batch.item.ItemReader; import org.springframework.batch.item.ItemWriter; -import org.springframework.batch.test.JobLauncherTestUtils; +import org.springframework.batch.test.JobOperatorTestUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -64,24 +64,24 @@ @Testcontainers(disabledWithoutDocker = true) class AmqpJobFunctionalTests { - private static final DockerImageName RABBITMQ_IMAGE = DockerImageName.parse("rabbitmq:3"); + private static final DockerImageName RABBITMQ_IMAGE = DockerImageName.parse("rabbitmq:4.1.2"); @Container public static RabbitMQContainer rabbitmq = new RabbitMQContainer(RABBITMQ_IMAGE); @Autowired - private JobLauncherTestUtils jobLauncherTestUtils; + private JobOperatorTestUtils jobOperatorTestUtils; @Autowired - private JobExplorer jobExplorer; + private JobRepository jobRepository; @Test void testLaunchJobWithXmlConfig() throws Exception { // given - this.jobLauncherTestUtils.launchJob(); + this.jobOperatorTestUtils.startJob(); // when - int count = jobExplorer.getJobInstances("amqp-example-job", 0, 1).size(); + int count = jobRepository.getJobInstances("amqp-example-job", 0, 1).size(); // then assertTrue(count > 0); @@ -92,15 +92,15 @@ public void testLaunchJobWithJavaConfig() throws Exception { // given ApplicationContext context = new AnnotationConfigApplicationContext(AmqpJobConfiguration.class, AmqpConfiguration.class); - JobLauncher jobLauncher = context.getBean(JobLauncher.class); + JobOperator jobOperator = context.getBean(JobOperator.class); Job job = context.getBean(Job.class); // when - jobLauncher.run(job, new JobParameters()); + jobOperator.start(job, new JobParameters()); // then - JobExplorer localJobExplorer = context.getBean(JobExplorer.class); - int count = localJobExplorer.getJobInstances("amqp-config-job", 0, 1).size(); + JobRepository localJobRepository = context.getBean(JobRepository.class); + int count = localJobRepository.getJobInstances("amqp-config-job", 0, 1).size(); assertTrue(count > 0); } diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/samples/beanwrapper/BeanWrapperMapperSampleJobFunctionalTests.java b/spring-batch-samples/src/test/java/org/springframework/batch/samples/beanwrapper/BeanWrapperMapperSampleJobFunctionalTests.java index a2d0c4cdc6..dd205c6a15 100644 --- a/spring-batch-samples/src/test/java/org/springframework/batch/samples/beanwrapper/BeanWrapperMapperSampleJobFunctionalTests.java +++ b/spring-batch-samples/src/test/java/org/springframework/batch/samples/beanwrapper/BeanWrapperMapperSampleJobFunctionalTests.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. @@ -19,8 +19,8 @@ import org.junit.jupiter.api.Test; import org.springframework.batch.core.BatchStatus; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.test.JobLauncherTestUtils; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.test.JobOperatorTestUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; @@ -31,12 +31,12 @@ class BeanWrapperMapperSampleJobFunctionalTests { @Autowired - private JobLauncherTestUtils jobLauncherTestUtils; + private JobOperatorTestUtils jobOperatorTestUtils; @Test void testJobLaunch() throws Exception { // when - JobExecution jobExecution = this.jobLauncherTestUtils.launchJob(); + JobExecution jobExecution = this.jobOperatorTestUtils.startJob(); // then assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus()); diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/samples/chunking/RemoteChunkingJobFunctionalTests.java b/spring-batch-samples/src/test/java/org/springframework/batch/samples/chunking/RemoteChunkingJobFunctionalTests.java index 07d5f93da5..d024317990 100644 --- a/spring-batch-samples/src/test/java/org/springframework/batch/samples/chunking/RemoteChunkingJobFunctionalTests.java +++ b/spring-batch-samples/src/test/java/org/springframework/batch/samples/chunking/RemoteChunkingJobFunctionalTests.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. @@ -23,10 +23,10 @@ import org.junit.jupiter.api.Test; import org.springframework.batch.core.ExitStatus; -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.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.launch.JobOperator; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.PropertySource; @@ -48,7 +48,7 @@ class RemoteChunkingJobFunctionalTests { @Autowired - private JobLauncher jobLauncher; + private JobOperator jobOperator; private EmbeddedActiveMQ brokerService; @@ -74,13 +74,12 @@ void tearDown() throws Exception { @Test void testRemoteChunkingJob(@Autowired Job job) throws Exception { // when - JobExecution jobExecution = this.jobLauncher.run(job, new JobParameters()); + JobExecution jobExecution = this.jobOperator.start(job, new JobParameters()); // then + // the manager sent 2 chunks ({1, 2, 3} and {4, 5, 6}) to workers assertEquals(ExitStatus.COMPLETED.getExitCode(), jobExecution.getExitStatus().getExitCode()); - assertEquals("Waited for 2 results.", // the manager sent 2 chunks ({1, 2, - // 3} and {4, 5, 6}) to workers - jobExecution.getExitStatus().getExitDescription()); + assertEquals("Waited for 2 results.", jobExecution.getExitStatus().getExitDescription()); } } 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 88% 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..e537b7a3ce 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 @@ -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,12 +23,13 @@ import org.junit.jupiter.api.Test; import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; import org.springframework.batch.core.job.builder.JobBuilder; -import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.launch.JobOperator; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.step.builder.StepBuilder; import org.springframework.batch.item.database.JdbcBatchItemWriter; @@ -50,7 +51,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) { } @@ -59,11 +60,11 @@ record Person(int id, String name) { void testJobLaunch() throws Exception { // given ApplicationContext context = new AnnotationConfigApplicationContext(JobConfiguration.class); - JobLauncher jobLauncher = context.getBean(JobLauncher.class); + JobOperator jobOperator = context.getBean(JobOperator.class); Job job = context.getBean(Job.class); // when - JobExecution jobExecution = jobLauncher.run(job, new JobParameters()); + JobExecution jobExecution = jobOperator.start(job, new JobParameters()); // then Assertions.assertEquals(ExitStatus.COMPLETED, jobExecution.getExitStatus()); @@ -74,6 +75,7 @@ void testJobLaunch() throws Exception { @Configuration @EnableBatchProcessing + @EnableJdbcJobRepository static class JobConfiguration { @Bean @@ -120,7 +122,8 @@ public JdbcBatchItemWriter itemWriter() { @Bean public Job job(JobRepository jobRepository, JdbcTransactionManager transactionManager) { return new JobBuilder("job", jobRepository) - .start(new StepBuilder("step", jobRepository).chunk(5, transactionManager) + .start(new StepBuilder("step", jobRepository).chunk(5) + .transactionManager(transactionManager) .reader(itemReader()) .writer(itemWriter()) .build()) diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/samples/compositewriter/CompositeItemWriterSampleFunctionalTests.java b/spring-batch-samples/src/test/java/org/springframework/batch/samples/compositewriter/CompositeItemWriterSampleFunctionalTests.java index 5b909167da..f15d09e4d1 100644 --- a/spring-batch-samples/src/test/java/org/springframework/batch/samples/compositewriter/CompositeItemWriterSampleFunctionalTests.java +++ b/spring-batch-samples/src/test/java/org/springframework/batch/samples/compositewriter/CompositeItemWriterSampleFunctionalTests.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. @@ -20,7 +20,6 @@ import java.math.BigDecimal; import java.sql.ResultSet; import java.sql.SQLException; -import java.util.ArrayList; import java.util.List; import javax.sql.DataSource; @@ -29,7 +28,7 @@ import org.junit.jupiter.api.Test; import org.springframework.batch.samples.domain.trade.Trade; -import org.springframework.batch.test.JobLauncherTestUtils; +import org.springframework.batch.test.JobOperatorTestUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowCallbackHandler; @@ -54,7 +53,7 @@ class CompositeItemWriterSampleFunctionalTests { private JdbcTemplate jdbcTemplate; @Autowired - private JobLauncherTestUtils jobLauncherTestUtils; + private JobOperatorTestUtils jobOperatorTestUtils; @Autowired public void setDataSource(DataSource dataSource) { @@ -66,7 +65,7 @@ void testJobLaunch() throws Exception { JdbcTestUtils.deleteFromTables(jdbcTemplate, "TRADE"); int before = JdbcTestUtils.countRowsInTable(jdbcTemplate, "TRADE"); - jobLauncherTestUtils.launchJob(); + jobOperatorTestUtils.startJob(); checkOutputFile("target/test-outputs/CustomerReport1.txt"); checkOutputFile("target/test-outputs/CustomerReport2.txt"); @@ -74,15 +73,12 @@ void testJobLaunch() throws Exception { } private void checkOutputTable(int before) { - final List trades = new ArrayList<>() { - { - add(new Trade("UK21341EAH41", 211, new BigDecimal("31.11"), "customer1")); - add(new Trade("UK21341EAH42", 212, new BigDecimal("32.11"), "customer2")); - add(new Trade("UK21341EAH43", 213, new BigDecimal("33.11"), "customer3")); - add(new Trade("UK21341EAH44", 214, new BigDecimal("34.11"), "customer4")); - add(new Trade("UK21341EAH45", 215, new BigDecimal("35.11"), "customer5")); - } - }; + final List trades = List.of( // + new Trade("UK21341EAH41", 211, new BigDecimal("31.11"), "customer1"), + new Trade("UK21341EAH42", 212, new BigDecimal("32.11"), "customer2"), + new Trade("UK21341EAH43", 213, new BigDecimal("33.11"), "customer3"), + new Trade("UK21341EAH44", 214, new BigDecimal("34.11"), "customer4"), + new Trade("UK21341EAH45", 215, new BigDecimal("35.11"), "customer5")); int after = JdbcTestUtils.countRowsInTable(jdbcTemplate, "TRADE"); diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/samples/file/delimited/DelimitedFunctionalTests.java b/spring-batch-samples/src/test/java/org/springframework/batch/samples/file/delimited/DelimitedFunctionalTests.java index e33a0eabe2..4961be1e00 100644 --- a/spring-batch-samples/src/test/java/org/springframework/batch/samples/file/delimited/DelimitedFunctionalTests.java +++ b/spring-batch-samples/src/test/java/org/springframework/batch/samples/file/delimited/DelimitedFunctionalTests.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. @@ -19,12 +19,12 @@ 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.JobParametersBuilder; -import org.springframework.batch.core.launch.JobLauncher; -import org.springframework.batch.test.JobLauncherTestUtils; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; +import org.springframework.batch.core.launch.JobOperator; +import org.springframework.batch.test.JobOperatorTestUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; @@ -44,7 +44,7 @@ class DelimitedFunctionalTests { @Autowired - private JobLauncherTestUtils jobLauncherTestUtils; + private JobOperatorTestUtils jobOperatorTestUtils; @Test void testLaunchJobWithXmlConfig() throws Exception { @@ -55,7 +55,7 @@ void testLaunchJobWithXmlConfig() throws Exception { .toJobParameters(); // when - JobExecution jobExecution = this.jobLauncherTestUtils.launchJob(jobParameters); + JobExecution jobExecution = this.jobOperatorTestUtils.startJob(jobParameters); // then assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus()); @@ -65,7 +65,7 @@ void testLaunchJobWithXmlConfig() throws Exception { public void testLaunchJobWithJavaConfig() throws Exception { // given ApplicationContext context = new AnnotationConfigApplicationContext(DelimitedJobConfiguration.class); - JobLauncher jobLauncher = context.getBean(JobLauncher.class); + JobOperator jobOperator = context.getBean(JobOperator.class); Job job = context.getBean(Job.class); JobParameters jobParameters = new JobParametersBuilder() .addString("inputFile", "org/springframework/batch/samples/file/delimited/data/delimited.csv") @@ -73,7 +73,7 @@ public void testLaunchJobWithJavaConfig() throws Exception { .toJobParameters(); // when - JobExecution jobExecution = jobLauncher.run(job, jobParameters); + JobExecution jobExecution = jobOperator.start(job, jobParameters); // then assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus()); diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/samples/file/fixed/FixedLengthFunctionalTests.java b/spring-batch-samples/src/test/java/org/springframework/batch/samples/file/fixed/FixedLengthFunctionalTests.java index 3ce89133d7..561155a1e7 100644 --- a/spring-batch-samples/src/test/java/org/springframework/batch/samples/file/fixed/FixedLengthFunctionalTests.java +++ b/spring-batch-samples/src/test/java/org/springframework/batch/samples/file/fixed/FixedLengthFunctionalTests.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. @@ -19,12 +19,12 @@ 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.JobParametersBuilder; -import org.springframework.batch.core.launch.JobLauncher; -import org.springframework.batch.test.JobLauncherTestUtils; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; +import org.springframework.batch.core.launch.JobOperator; +import org.springframework.batch.test.JobOperatorTestUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; @@ -37,7 +37,7 @@ class FixedLengthFunctionalTests { @Autowired - private JobLauncherTestUtils jobLauncherTestUtils; + private JobOperatorTestUtils jobOperatorTestUtils; @Test void testLaunchJobWithXmlConfig() throws Exception { @@ -48,7 +48,7 @@ void testLaunchJobWithXmlConfig() throws Exception { .toJobParameters(); // when - JobExecution jobExecution = this.jobLauncherTestUtils.launchJob(jobParameters); + JobExecution jobExecution = this.jobOperatorTestUtils.startJob(jobParameters); // then assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus()); @@ -58,7 +58,7 @@ void testLaunchJobWithXmlConfig() throws Exception { public void testLaunchJobWithJavaConfig() throws Exception { // given ApplicationContext context = new AnnotationConfigApplicationContext(FixedLengthJobConfiguration.class); - JobLauncher jobLauncher = context.getBean(JobLauncher.class); + JobOperator jobOperator = context.getBean(JobOperator.class); Job job = context.getBean(Job.class); JobParameters jobParameters = new JobParametersBuilder() .addString("inputFile", "org/springframework/batch/samples/file/fixed/data/fixedLength.txt") @@ -66,7 +66,7 @@ public void testLaunchJobWithJavaConfig() throws Exception { .toJobParameters(); // when - JobExecution jobExecution = jobLauncher.run(job, jobParameters); + JobExecution jobExecution = jobOperator.start(job, jobParameters); // then assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus()); diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/samples/file/json/JsonFunctionalTests.java b/spring-batch-samples/src/test/java/org/springframework/batch/samples/file/json/JsonFunctionalTests.java index a0fc6a8448..96da9c593a 100644 --- a/spring-batch-samples/src/test/java/org/springframework/batch/samples/file/json/JsonFunctionalTests.java +++ b/spring-batch-samples/src/test/java/org/springframework/batch/samples/file/json/JsonFunctionalTests.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. @@ -22,11 +22,11 @@ import org.junit.jupiter.api.Test; import org.springframework.batch.core.ExitStatus; -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.launch.JobLauncher; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; +import org.springframework.batch.core.launch.JobOperator; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.util.DigestUtils; @@ -46,12 +46,12 @@ class JsonFunctionalTests { @Test void testJsonReadingAndWriting() throws Exception { ApplicationContext context = new AnnotationConfigApplicationContext(JsonJobConfiguration.class); - JobLauncher jobLauncher = context.getBean(JobLauncher.class); + JobOperator jobOperator = context.getBean(JobOperator.class); Job job = context.getBean(Job.class); JobParameters jobParameters = new JobParametersBuilder().addString("inputFile", INPUT_FILE) .addString("outputFile", "file:./" + OUTPUT_FILE) .toJobParameters(); - JobExecution jobExecution = jobLauncher.run(job, jobParameters); + JobExecution jobExecution = jobOperator.start(job, jobParameters); assertEquals(ExitStatus.COMPLETED.getExitCode(), jobExecution.getExitStatus().getExitCode()); assertFileEquals(new File("src/main/resources/" + INPUT_FILE), new File(OUTPUT_FILE)); diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/samples/file/multiline/MultiLineFunctionalTests.java b/spring-batch-samples/src/test/java/org/springframework/batch/samples/file/multiline/MultiLineFunctionalTests.java index cb3dcaf027..188ade2aac 100644 --- a/spring-batch-samples/src/test/java/org/springframework/batch/samples/file/multiline/MultiLineFunctionalTests.java +++ b/spring-batch-samples/src/test/java/org/springframework/batch/samples/file/multiline/MultiLineFunctionalTests.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. @@ -23,16 +23,15 @@ 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.JobParametersBuilder; -import org.springframework.batch.core.launch.JobLauncher; -import org.springframework.batch.test.JobLauncherTestUtils; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; +import org.springframework.batch.core.launch.JobOperator; +import org.springframework.batch.test.JobOperatorTestUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; -import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.FileSystemResource; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; @@ -48,12 +47,12 @@ "/simple-job-launcher-context.xml" }) class MultiLineFunctionalTests { - private static final String INPUT_FILE = "org/springframework/batch/samples/file/multiline/data/multiLine.txt"; + private static final String INPUT_FILE = "src/main/resources/org/springframework/batch/samples/file/multiline/data/multiLine.txt"; private static final String OUTPUT_FILE = "target/test-outputs/multiLineOutput.txt"; @Autowired - private JobLauncherTestUtils jobLauncherTestUtils; + private JobOperatorTestUtils jobOperatorTestUtils; @Test void testLaunchJobWithXmlConfig() throws Exception { @@ -62,11 +61,11 @@ void testLaunchJobWithXmlConfig() throws Exception { .toJobParameters(); // when - JobExecution jobExecution = this.jobLauncherTestUtils.launchJob(jobParameters); + JobExecution jobExecution = this.jobOperatorTestUtils.startJob(jobParameters); // then assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus()); - Path inputFile = new ClassPathResource(INPUT_FILE).getFile().toPath(); + Path inputFile = new FileSystemResource(INPUT_FILE).getFile().toPath(); Path outputFile = new FileSystemResource(OUTPUT_FILE).getFile().toPath(); Assertions.assertLinesMatch(Files.lines(inputFile), Files.lines(outputFile)); } @@ -75,18 +74,18 @@ void testLaunchJobWithXmlConfig() throws Exception { public void testLaunchJobWithJavaConfig() throws Exception { // given ApplicationContext context = new AnnotationConfigApplicationContext(MultiLineJobConfiguration.class); - JobLauncher jobLauncher = context.getBean(JobLauncher.class); + JobOperator jobOperator = context.getBean(JobOperator.class); Job job = context.getBean(Job.class); JobParameters jobParameters = new JobParametersBuilder().addString("inputFile", INPUT_FILE) .addString("outputFile", "file:./" + OUTPUT_FILE) .toJobParameters(); // when - JobExecution jobExecution = jobLauncher.run(job, jobParameters); + JobExecution jobExecution = jobOperator.start(job, jobParameters); // then assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus()); - Path inputFile = new ClassPathResource(INPUT_FILE).getFile().toPath(); + Path inputFile = new FileSystemResource(INPUT_FILE).getFile().toPath(); Path outputFile = new FileSystemResource(OUTPUT_FILE).getFile().toPath(); Assertions.assertLinesMatch(Files.lines(inputFile), Files.lines(outputFile)); } diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/samples/file/multilineaggregate/MultilineAggregateJobFunctionalTests.java b/spring-batch-samples/src/test/java/org/springframework/batch/samples/file/multilineaggregate/MultilineAggregateJobFunctionalTests.java index 1f85659a6b..3be5e8dc46 100644 --- a/spring-batch-samples/src/test/java/org/springframework/batch/samples/file/multilineaggregate/MultilineAggregateJobFunctionalTests.java +++ b/spring-batch-samples/src/test/java/org/springframework/batch/samples/file/multilineaggregate/MultilineAggregateJobFunctionalTests.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. @@ -21,7 +21,7 @@ import org.apache.commons.io.IOUtils; import org.junit.jupiter.api.Test; -import org.springframework.batch.test.JobLauncherTestUtils; +import org.springframework.batch.test.JobOperatorTestUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.FileSystemResource; import org.springframework.core.io.Resource; @@ -35,7 +35,7 @@ class MultilineAggregateJobFunctionalTests { @Autowired - private JobLauncherTestUtils jobLauncherTestUtils; + private JobOperatorTestUtils jobOperatorTestUtils; // The output is grouped together in two lines, instead of all the // trades coming out on a single line. @@ -46,7 +46,7 @@ class MultilineAggregateJobFunctionalTests { @Test void testJobLaunch() throws Exception { - this.jobLauncherTestUtils.launchJob(); + this.jobOperatorTestUtils.startJob(); assertEquals(EXPECTED_RESULT, StringUtils.replace(IOUtils.toString(output.getInputStream(), StandardCharsets.UTF_8), System.getProperty("line.separator"), "")); diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/samples/file/multirecordtype/MultiRecordTypeFunctionalTests.java b/spring-batch-samples/src/test/java/org/springframework/batch/samples/file/multirecordtype/MultiRecordTypeFunctionalTests.java index 8a1985d7e5..b3afa611b3 100644 --- a/spring-batch-samples/src/test/java/org/springframework/batch/samples/file/multirecordtype/MultiRecordTypeFunctionalTests.java +++ b/spring-batch-samples/src/test/java/org/springframework/batch/samples/file/multirecordtype/MultiRecordTypeFunctionalTests.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. @@ -23,16 +23,15 @@ 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.JobParametersBuilder; -import org.springframework.batch.core.launch.JobLauncher; -import org.springframework.batch.test.JobLauncherTestUtils; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; +import org.springframework.batch.core.launch.JobOperator; +import org.springframework.batch.test.JobOperatorTestUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; -import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.FileSystemResource; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; @@ -50,10 +49,10 @@ class MultiRecordTypeFunctionalTests { private static final String OUTPUT_FILE = "target/test-outputs/multiRecordTypeOutput.txt"; - private static final String INPUT_FILE = "org/springframework/batch/samples/file/multirecordtype/data/multiRecordType.txt"; + private static final String INPUT_FILE = "src/main/resources/org/springframework/batch/samples/file/multirecordtype/data/multiRecordType.txt"; @Autowired - private JobLauncherTestUtils jobLauncherTestUtils; + private JobOperatorTestUtils jobOperatorTestUtils; @Test void testLaunchJobWithXmlConfig() throws Exception { @@ -63,11 +62,11 @@ void testLaunchJobWithXmlConfig() throws Exception { .toJobParameters(); // when - JobExecution jobExecution = this.jobLauncherTestUtils.launchJob(jobParameters); + JobExecution jobExecution = this.jobOperatorTestUtils.startJob(jobParameters); // then assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus()); - Path inputFile = new ClassPathResource(INPUT_FILE).getFile().toPath(); + Path inputFile = new FileSystemResource(INPUT_FILE).getFile().toPath(); Path outputFile = new FileSystemResource(OUTPUT_FILE).getFile().toPath(); Assertions.assertLinesMatch(Files.lines(inputFile), Files.lines(outputFile)); } @@ -76,18 +75,18 @@ void testLaunchJobWithXmlConfig() throws Exception { public void testLaunchJobWithJavaConfig() throws Exception { // given ApplicationContext context = new AnnotationConfigApplicationContext(MultiRecordTypeJobConfiguration.class); - JobLauncher jobLauncher = context.getBean(JobLauncher.class); + JobOperator jobOperator = context.getBean(JobOperator.class); Job job = context.getBean(Job.class); JobParameters jobParameters = new JobParametersBuilder().addString("inputFile", INPUT_FILE) .addString("outputFile", "file:./" + OUTPUT_FILE) .toJobParameters(); // when - JobExecution jobExecution = jobLauncher.run(job, jobParameters); + JobExecution jobExecution = jobOperator.start(job, jobParameters); // then assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus()); - Path inputFile = new ClassPathResource(INPUT_FILE).getFile().toPath(); + Path inputFile = new FileSystemResource(INPUT_FILE).getFile().toPath(); Path outputFile = new FileSystemResource(OUTPUT_FILE).getFile().toPath(); Assertions.assertLinesMatch(Files.lines(inputFile), Files.lines(outputFile)); } 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..a0efe95281 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,15 +16,16 @@ 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; -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.launch.JobLauncher; -import org.springframework.batch.test.JobLauncherTestUtils; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; +import org.springframework.batch.core.launch.JobOperator; +import org.springframework.batch.test.JobOperatorTestUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; @@ -38,12 +39,13 @@ * @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 { @Autowired - private JobLauncherTestUtils jobLauncherTestUtils; + private JobOperatorTestUtils jobOperatorTestUtils; @Test void testLaunchJobWithXmlConfig() throws Exception { @@ -54,7 +56,7 @@ void testLaunchJobWithXmlConfig() throws Exception { .toJobParameters(); // when - JobExecution jobExecution = this.jobLauncherTestUtils.launchJob(jobParameters); + JobExecution jobExecution = this.jobOperatorTestUtils.startJob(jobParameters); // then assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus()); @@ -64,7 +66,7 @@ void testLaunchJobWithXmlConfig() throws Exception { public void testLaunchJobWithJavaConfig() throws Exception { // given ApplicationContext context = new AnnotationConfigApplicationContext(MultiResourceJobConfiguration.class); - JobLauncher jobLauncher = context.getBean(JobLauncher.class); + JobOperator jobOperator = context.getBean(JobOperator.class); Job job = context.getBean(Job.class); JobParameters jobParameters = new JobParametersBuilder() .addString("inputFiles", "org/springframework/batch/samples/file/multiresource/data/delimited*.csv") @@ -72,7 +74,7 @@ public void testLaunchJobWithJavaConfig() throws Exception { .toJobParameters(); // when - JobExecution jobExecution = jobLauncher.run(job, jobParameters); + JobExecution jobExecution = jobOperator.start(job, jobParameters); // then assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus()); diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/samples/file/patternmatching/PatternMatchingJobFunctionalTests.java b/spring-batch-samples/src/test/java/org/springframework/batch/samples/file/patternmatching/PatternMatchingJobFunctionalTests.java index 6d6df834e4..093b8eca6a 100644 --- a/spring-batch-samples/src/test/java/org/springframework/batch/samples/file/patternmatching/PatternMatchingJobFunctionalTests.java +++ b/spring-batch-samples/src/test/java/org/springframework/batch/samples/file/patternmatching/PatternMatchingJobFunctionalTests.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. @@ -19,32 +19,32 @@ import java.nio.file.Files; import java.nio.file.Path; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import org.springframework.batch.test.JobLauncherTestUtils; +import org.springframework.batch.test.JobOperatorTestUtils; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.FileSystemResource; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; +import static org.junit.jupiter.api.Assertions.assertLinesMatch; + @SpringJUnitConfig(locations = { "/org/springframework/batch/samples/file/patternmatching/job/multilineOrderJob.xml", "/simple-job-launcher-context.xml" }) class PatternMatchingJobFunctionalTests { private static final String ACTUAL = "target/test-outputs/multilineOrderOutput.txt"; - private static final String EXPECTED = "org/springframework/batch/samples/file/patternmatching/data/multilineOrderOutput.txt"; + private static final String EXPECTED = "src/main/resources/org/springframework/batch/samples/file/patternmatching/data/multilineOrderOutput.txt"; @Autowired - private JobLauncherTestUtils jobLauncherTestUtils; + private JobOperatorTestUtils jobOperatorTestUtils; @Test void testJobLaunch() throws Exception { - this.jobLauncherTestUtils.launchJob(); - Path expectedFile = new ClassPathResource(EXPECTED).getFile().toPath(); + this.jobOperatorTestUtils.startJob(); + Path expectedFile = new FileSystemResource(EXPECTED).getFile().toPath(); Path actualFile = new FileSystemResource(ACTUAL).getFile().toPath(); - Assertions.assertLinesMatch(Files.lines(expectedFile), Files.lines(actualFile)); + assertLinesMatch(Files.lines(expectedFile), Files.lines(actualFile)); } } diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/samples/file/xml/XmlFunctionalTests.java b/spring-batch-samples/src/test/java/org/springframework/batch/samples/file/xml/XmlFunctionalTests.java index ea6d40ba5b..f852ad285c 100644 --- a/spring-batch-samples/src/test/java/org/springframework/batch/samples/file/xml/XmlFunctionalTests.java +++ b/spring-batch-samples/src/test/java/org/springframework/batch/samples/file/xml/XmlFunctionalTests.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. @@ -19,12 +19,12 @@ 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.JobParametersBuilder; -import org.springframework.batch.core.launch.JobLauncher; -import org.springframework.batch.test.JobLauncherTestUtils; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; +import org.springframework.batch.core.launch.JobOperator; +import org.springframework.batch.test.JobOperatorTestUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; @@ -43,7 +43,7 @@ class XmlFunctionalTests { @Autowired - private JobLauncherTestUtils jobLauncherTestUtils; + private JobOperatorTestUtils jobOperatorTestUtils; @Test void testLaunchJobWithXmlConfig() throws Exception { @@ -54,7 +54,7 @@ void testLaunchJobWithXmlConfig() throws Exception { .toJobParameters(); // when - JobExecution jobExecution = this.jobLauncherTestUtils.launchJob(jobParameters); + JobExecution jobExecution = this.jobOperatorTestUtils.startJob(jobParameters); // then assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus()); @@ -64,7 +64,7 @@ void testLaunchJobWithXmlConfig() throws Exception { public void testLaunchJobWithJavaConfig() throws Exception { // given ApplicationContext context = new AnnotationConfigApplicationContext(XmlJobConfiguration.class); - JobLauncher jobLauncher = context.getBean(JobLauncher.class); + JobOperator jobOperator = context.getBean(JobOperator.class); Job job = context.getBean(Job.class); JobParameters jobParameters = new JobParametersBuilder() .addString("inputFile", "org/springframework/batch/samples/file/xml/data/input.xml") @@ -72,7 +72,7 @@ public void testLaunchJobWithJavaConfig() throws Exception { .toJobParameters(); // when - JobExecution jobExecution = jobLauncher.run(job, jobParameters); + JobExecution jobExecution = jobOperator.start(job, jobParameters); // then assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus()); diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/samples/filter/CustomerFilterJobFunctionalTests.java b/spring-batch-samples/src/test/java/org/springframework/batch/samples/filter/CustomerFilterJobFunctionalTests.java index 26aa4c3acf..f87dcd83c3 100644 --- a/spring-batch-samples/src/test/java/org/springframework/batch/samples/filter/CustomerFilterJobFunctionalTests.java +++ b/spring-batch-samples/src/test/java/org/springframework/batch/samples/filter/CustomerFilterJobFunctionalTests.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. @@ -27,8 +27,8 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.test.JobLauncherTestUtils; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.test.JobOperatorTestUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; @@ -51,7 +51,7 @@ class CustomerFilterJobFunctionalTests { private final Map credits = new HashMap<>(); @Autowired - private JobLauncherTestUtils jobLauncherTestUtils; + private JobOperatorTestUtils jobOperatorTestUtils; @Autowired public void setDataSource(DataSource dataSource) { @@ -79,10 +79,10 @@ void tearDown() { @Test void testFilterJob() throws Exception { - JobExecution jobExecution = jobLauncherTestUtils.launchJob(); + JobExecution jobExecution = jobOperatorTestUtils.startJob(); - customers = Arrays.asList(new Customer("customer1", (credits.get("customer1"))), - new Customer("customer2", (credits.get("customer2"))), new Customer("customer3", 100500), + customers = Arrays.asList(new Customer("customer1", credits.get("customer1")), + new Customer("customer2", credits.get("customer2")), new Customer("customer3", 100500), new Customer("customer4", credits.get("customer4")), new Customer("customer5", 32345), new Customer("customer6", 123456)); diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/samples/football/FootballJobFunctionalTests.java b/spring-batch-samples/src/test/java/org/springframework/batch/samples/football/FootballJobFunctionalTests.java index c8aa20778d..2b936cdbe8 100644 --- a/spring-batch-samples/src/test/java/org/springframework/batch/samples/football/FootballJobFunctionalTests.java +++ b/spring-batch-samples/src/test/java/org/springframework/batch/samples/football/FootballJobFunctionalTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2007-2023 the original author or authors. + * Copyright 2007-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,10 +19,10 @@ import org.junit.jupiter.api.Test; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.launch.JobLauncher; -import org.springframework.batch.test.JobLauncherTestUtils; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.launch.JobOperator; +import org.springframework.batch.test.JobOperatorTestUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; @@ -37,7 +37,7 @@ class FootballJobFunctionalTests { @Autowired - private JobLauncherTestUtils jobLauncherTestUtils; + private JobOperatorTestUtils jobOperatorTestUtils; private JdbcTemplate jdbcTemplate; @@ -50,7 +50,7 @@ public void setDataSource(DataSource dataSource) { void testLaunchJobWithXmlConfiguration() throws Exception { JdbcTestUtils.deleteFromTables(jdbcTemplate, "PLAYERS", "GAMES", "PLAYER_SUMMARY"); - jobLauncherTestUtils.launchJob(); + jobOperatorTestUtils.startJob(); int count = JdbcTestUtils.countRowsInTable(jdbcTemplate, "PLAYER_SUMMARY"); assertTrue(count > 0); @@ -60,11 +60,11 @@ void testLaunchJobWithXmlConfiguration() throws Exception { void testLaunchJobWithJavaConfiguration() throws Exception { // given ApplicationContext context = new AnnotationConfigApplicationContext(FootballJobConfiguration.class); - JobLauncher jobLauncher = context.getBean(JobLauncher.class); + JobOperator jobOperator = context.getBean(JobOperator.class); Job job = context.getBean(Job.class); // when - jobLauncher.run(job, new JobParameters()); + jobOperator.start(job, new JobParameters()); // then int count = JdbcTestUtils.countRowsInTable(new JdbcTemplate(context.getBean(DataSource.class)), diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/samples/headerfooter/HeaderFooterSampleFunctionalTests.java b/spring-batch-samples/src/test/java/org/springframework/batch/samples/headerfooter/HeaderFooterSampleFunctionalTests.java index 0ebf63b7f2..a276a47495 100644 --- a/spring-batch-samples/src/test/java/org/springframework/batch/samples/headerfooter/HeaderFooterSampleFunctionalTests.java +++ b/spring-batch-samples/src/test/java/org/springframework/batch/samples/headerfooter/HeaderFooterSampleFunctionalTests.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. @@ -20,7 +20,7 @@ import org.junit.jupiter.api.Test; -import org.springframework.batch.test.JobLauncherTestUtils; +import org.springframework.batch.test.JobOperatorTestUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.core.io.Resource; @@ -41,11 +41,11 @@ class HeaderFooterSampleFunctionalTests { private Resource output; @Autowired - private JobLauncherTestUtils jobLauncherTestUtils; + private JobOperatorTestUtils jobOperatorTestUtils; @Test void testJob() throws Exception { - this.jobLauncherTestUtils.launchJob(); + this.jobOperatorTestUtils.startJob(); BufferedReader inputReader = new BufferedReader(new FileReader(input.getFile())); BufferedReader outputReader = new BufferedReader(new FileReader(output.getFile())); diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/samples/helloworld/HelloWorldJobFunctionalTests.java b/spring-batch-samples/src/test/java/org/springframework/batch/samples/helloworld/HelloWorldJobFunctionalTests.java index 9dfcc75f44..6516443138 100644 --- a/spring-batch-samples/src/test/java/org/springframework/batch/samples/helloworld/HelloWorldJobFunctionalTests.java +++ b/spring-batch-samples/src/test/java/org/springframework/batch/samples/helloworld/HelloWorldJobFunctionalTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2023 the original author or authors. + * Copyright 2023-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,10 +19,10 @@ 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.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.launch.JobOperator; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; @@ -34,14 +34,16 @@ class HelloWorldJobFunctionalTests { public void testLaunchJob() throws Exception { // given ApplicationContext context = new AnnotationConfigApplicationContext(HelloWorldJobConfiguration.class); - JobLauncher jobLauncher = context.getBean(JobLauncher.class); + JobOperator jobOperator = context.getBean(JobOperator.class); Job job = context.getBean(Job.class); // when - JobExecution jobExecution = jobLauncher.run(job, new JobParameters()); + JobExecution jobExecution = jobOperator.start(job, new JobParameters()); // then assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus()); + assertEquals(1, jobExecution.getStepExecutions().size()); + assertEquals(BatchStatus.COMPLETED, jobExecution.getStepExecutions().iterator().next().getStatus()); } } diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/samples/jdbc/JdbcCursorFunctionalTests.java b/spring-batch-samples/src/test/java/org/springframework/batch/samples/jdbc/JdbcCursorFunctionalTests.java index cf8f0463d5..74c16c21a8 100644 --- a/spring-batch-samples/src/test/java/org/springframework/batch/samples/jdbc/JdbcCursorFunctionalTests.java +++ b/spring-batch-samples/src/test/java/org/springframework/batch/samples/jdbc/JdbcCursorFunctionalTests.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. @@ -19,12 +19,12 @@ 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.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.launch.JobOperator; import org.springframework.batch.samples.jdbc.cursor.JdbcCursorReaderBatchWriterSampleJob; -import org.springframework.batch.test.JobLauncherTestUtils; +import org.springframework.batch.test.JobOperatorTestUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; @@ -43,12 +43,12 @@ class JdbcCursorFunctionalTests { @Autowired - private JobLauncherTestUtils jobLauncherTestUtils; + private JobOperatorTestUtils jobOperatorTestUtils; @Test void testLaunchJobWithXmlConfig() throws Exception { // when - JobExecution jobExecution = this.jobLauncherTestUtils.launchJob(); + JobExecution jobExecution = this.jobOperatorTestUtils.startJob(); // then assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus()); @@ -58,11 +58,11 @@ void testLaunchJobWithXmlConfig() throws Exception { public void testLaunchJobWithJavaConfig() throws Exception { // given ApplicationContext context = new AnnotationConfigApplicationContext(JdbcCursorReaderBatchWriterSampleJob.class); - JobLauncher jobLauncher = context.getBean(JobLauncher.class); + JobOperator jobOperator = context.getBean(JobOperator.class); Job job = context.getBean(Job.class); // when - JobExecution jobExecution = jobLauncher.run(job, new JobParameters()); + JobExecution jobExecution = jobOperator.start(job, new JobParameters()); // then assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus()); diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/samples/jdbc/JdbcPagingFunctionalTests.java b/spring-batch-samples/src/test/java/org/springframework/batch/samples/jdbc/JdbcPagingFunctionalTests.java index cda54a4454..0cbc45fd05 100644 --- a/spring-batch-samples/src/test/java/org/springframework/batch/samples/jdbc/JdbcPagingFunctionalTests.java +++ b/spring-batch-samples/src/test/java/org/springframework/batch/samples/jdbc/JdbcPagingFunctionalTests.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. @@ -19,13 +19,13 @@ 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.JobParametersBuilder; -import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; +import org.springframework.batch.core.launch.JobOperator; import org.springframework.batch.samples.jdbc.paging.JdbcPagingReaderBatchWriterSampleJob; -import org.springframework.batch.test.JobLauncherTestUtils; +import org.springframework.batch.test.JobOperatorTestUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; @@ -44,17 +44,17 @@ class JdbcPagingFunctionalTests { @Autowired - private JobLauncherTestUtils jobLauncherTestUtils; + private JobOperatorTestUtils jobOperatorTestUtils; @Test void testLaunchJobWithXmlConfig() throws Exception { // given - JobParameters jobParameters = jobLauncherTestUtils.getUniqueJobParametersBuilder() + JobParameters jobParameters = this.jobOperatorTestUtils.getUniqueJobParametersBuilder() .addDouble("credit", 0.) .toJobParameters(); // when - JobExecution jobExecution = this.jobLauncherTestUtils.launchJob(jobParameters); + JobExecution jobExecution = this.jobOperatorTestUtils.startJob(jobParameters); // then assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus()); @@ -64,12 +64,12 @@ void testLaunchJobWithXmlConfig() throws Exception { public void testLaunchJobWithJavaConfig() throws Exception { // given ApplicationContext context = new AnnotationConfigApplicationContext(JdbcPagingReaderBatchWriterSampleJob.class); - JobLauncher jobLauncher = context.getBean(JobLauncher.class); + JobOperator jobOperator = context.getBean(JobOperator.class); Job job = context.getBean(Job.class); // when JobParameters jobParameters = new JobParametersBuilder().addDouble("credit", 0.).toJobParameters(); - JobExecution jobExecution = jobLauncher.run(job, jobParameters); + JobExecution jobExecution = jobOperator.start(job, jobParameters); // then assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus()); diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/samples/jobstep/JobStepFunctionalTests.java b/spring-batch-samples/src/test/java/org/springframework/batch/samples/jobstep/JobStepFunctionalTests.java index 925b793162..49cbdca664 100644 --- a/spring-batch-samples/src/test/java/org/springframework/batch/samples/jobstep/JobStepFunctionalTests.java +++ b/spring-batch-samples/src/test/java/org/springframework/batch/samples/jobstep/JobStepFunctionalTests.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. @@ -20,10 +20,10 @@ import org.junit.jupiter.api.Test; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.JobParametersBuilder; -import org.springframework.batch.test.JobLauncherTestUtils; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; +import org.springframework.batch.test.JobOperatorTestUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; @@ -47,7 +47,7 @@ class JobStepFunctionalTests { private Job jobStepJob; @Autowired - private JobLauncherTestUtils jobLauncherTestUtils; + private JobOperatorTestUtils jobOperatorTestUtils; private JdbcTemplate jdbcTemplate; @@ -58,13 +58,13 @@ public void setDataSource(DataSource dataSource) { @Test void testJobLaunch() throws Exception { - jobLauncherTestUtils.setJob(jobStepJob); + jobOperatorTestUtils.setJob(jobStepJob); JdbcTestUtils.deleteFromTables(jdbcTemplate, "TRADE"); JobParameters jobParameters = new JobParametersBuilder() .addString("input.file", "org/springframework/batch/samples/jobstep/data/ImportTradeDataStep.txt") .toJobParameters(); - jobLauncherTestUtils.launchJob(jobParameters); + jobOperatorTestUtils.startJob(jobParameters); int after = JdbcTestUtils.countRowsInTable(jdbcTemplate, "TRADE"); assertEquals(5, after); diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/samples/jpa/JpaFunctionalTests.java b/spring-batch-samples/src/test/java/org/springframework/batch/samples/jpa/JpaFunctionalTests.java index e1d9a8373e..a43860a9b9 100644 --- a/spring-batch-samples/src/test/java/org/springframework/batch/samples/jpa/JpaFunctionalTests.java +++ b/spring-batch-samples/src/test/java/org/springframework/batch/samples/jpa/JpaFunctionalTests.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. @@ -18,10 +18,10 @@ 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.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.launch.JobOperator; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; @@ -33,9 +33,9 @@ class JpaFunctionalTests { @Test - void testLaunchJobWithXmlConfig(@Autowired JobLauncher jobLauncher, @Autowired Job job) throws Exception { + void testLaunchJobWithXmlConfig(@Autowired JobOperator jobOperator, @Autowired Job job) throws Exception { // when - JobExecution jobExecution = jobLauncher.run(job, new JobParameters()); + JobExecution jobExecution = jobOperator.start(job, new JobParameters()); // then assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus()); @@ -45,11 +45,11 @@ void testLaunchJobWithXmlConfig(@Autowired JobLauncher jobLauncher, @Autowired J public void testLaunchJobWithJavaConfig() throws Exception { // given ApplicationContext context = new AnnotationConfigApplicationContext(JpaJobConfiguration.class); - JobLauncher jobLauncher = context.getBean(JobLauncher.class); + JobOperator jobOperator = context.getBean(JobOperator.class); Job job = context.getBean(Job.class); // when - JobExecution jobExecution = jobLauncher.run(job, new JobParameters()); + JobExecution jobExecution = jobOperator.start(job, new JobParameters()); // then assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus()); diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/samples/jpa/RepositoryFunctionalTests.java b/spring-batch-samples/src/test/java/org/springframework/batch/samples/jpa/RepositoryFunctionalTests.java index 56cb54eb1f..61de737812 100644 --- a/spring-batch-samples/src/test/java/org/springframework/batch/samples/jpa/RepositoryFunctionalTests.java +++ b/spring-batch-samples/src/test/java/org/springframework/batch/samples/jpa/RepositoryFunctionalTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 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. @@ -18,11 +18,11 @@ 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.JobParametersBuilder; -import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; +import org.springframework.batch.core.launch.JobOperator; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; @@ -34,12 +34,12 @@ class RepositoryFunctionalTests { @Test - void testLaunchJobWithXmlConfig(@Autowired JobLauncher jobLauncher, @Autowired Job job) throws Exception { + void testLaunchJobWithXmlConfig(@Autowired JobOperator jobOperator, @Autowired Job job) throws Exception { // given JobParameters jobParameters = new JobParametersBuilder().addDouble("credit", 10000D).toJobParameters(); // when - JobExecution jobExecution = jobLauncher.run(job, jobParameters); + JobExecution jobExecution = jobOperator.start(job, jobParameters); // then assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus()); @@ -49,12 +49,12 @@ void testLaunchJobWithXmlConfig(@Autowired JobLauncher jobLauncher, @Autowired J public void testLaunchJobWithJavaConfig() throws Exception { // given ApplicationContext context = new AnnotationConfigApplicationContext(JpaRepositoryJobConfiguration.class); - JobLauncher jobLauncher = context.getBean(JobLauncher.class); + JobOperator jobOperator = context.getBean(JobOperator.class); Job job = context.getBean(Job.class); JobParameters jobParameters = new JobParametersBuilder().addDouble("credit", 10000D).toJobParameters(); // when - JobExecution jobExecution = jobLauncher.run(job, jobParameters); + JobExecution jobExecution = jobOperator.start(job, jobParameters); // then assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus()); diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/samples/loom/VirtualThreadsSupportTests.java b/spring-batch-samples/src/test/java/org/springframework/batch/samples/loom/VirtualThreadsSupportFunctionalTests.java similarity index 80% rename from spring-batch-samples/src/test/java/org/springframework/batch/samples/loom/VirtualThreadsSupportTests.java rename to spring-batch-samples/src/test/java/org/springframework/batch/samples/loom/VirtualThreadsSupportFunctionalTests.java index f20794c5dd..ae7e247a47 100644 --- a/spring-batch-samples/src/test/java/org/springframework/batch/samples/loom/VirtualThreadsSupportTests.java +++ b/spring-batch-samples/src/test/java/org/springframework/batch/samples/loom/VirtualThreadsSupportFunctionalTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2023 the original author or authors. + * Copyright 2023-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,14 +21,14 @@ import org.junit.jupiter.api.condition.JRE; import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.explore.JobExplorer; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.job.builder.FlowBuilder; -import org.springframework.batch.core.launch.JobLauncher; -import org.springframework.batch.core.launch.support.TaskExecutorJobLauncher; +import org.springframework.batch.core.launch.JobOperator; +import org.springframework.batch.core.launch.support.TaskExecutorJobOperator; import org.springframework.batch.core.partition.support.TaskExecutorPartitionHandler; import org.springframework.batch.core.step.builder.TaskletStepBuilder; import org.springframework.batch.core.step.tasklet.SystemCommandTasklet; @@ -49,7 +49,7 @@ *

* Here are the places where a {@link TaskExecutor} is used in production code: *

    - *
  • {@link TaskExecutorJobLauncher#setTaskExecutor}: to launch jobs in background + *
  • {@link TaskExecutorJobOperator#setTaskExecutor}: to launch jobs in background * threads
  • *
  • {@link TaskletStepBuilder#taskExecutor(TaskExecutor)}: to execute steps * concurrently
  • @@ -65,7 +65,7 @@ * @author Mahmoud Ben Hassine */ @EnabledForJreRange(min = JRE.JAVA_21) -public class VirtualThreadsSupportTests { +public class VirtualThreadsSupportFunctionalTests { @Test public void testJobLaunchingWithVirtualThreads() throws Exception { @@ -73,17 +73,17 @@ public void testJobLaunchingWithVirtualThreads() throws Exception { ApplicationContext context = new AnnotationConfigApplicationContext( JobConfigurationForLaunchingJobsWithVirtualThreads.class); Job job = context.getBean(Job.class); - JobLauncher jobLauncher = context.getBean(JobLauncher.class); - JobExplorer jobExplorer = context.getBean(JobExplorer.class); + JobOperator jobOperator = context.getBean(JobOperator.class); + JobRepository jobRepository = context.getBean(JobRepository.class); // when - JobExecution jobExecution = jobLauncher.run(job, new JobParameters()); + JobExecution jobExecution = jobOperator.start(job, new JobParameters()); // then // should wait for virtual threads to finish, otherwise the following assertion // might be executed before the virtual thread running the job is finished // and therefore will fail. - while (jobExplorer.getJobExecution(jobExecution.getId()).isRunning()) { + while (jobRepository.getJobExecution(jobExecution.getId()).isRunning()) { Thread.sleep(100); } Assertions.assertEquals(ExitStatus.COMPLETED, jobExecution.getExitStatus()); @@ -100,10 +100,10 @@ public void testConcurrentStepsWithVirtualThreads() throws Exception { ApplicationContext context = new AnnotationConfigApplicationContext( JobConfigurationForRunningConcurrentStepsWithVirtualThreads.class); Job job = context.getBean(Job.class); - JobLauncher jobLauncher = context.getBean(JobLauncher.class); + JobOperator jobOperator = context.getBean(JobOperator.class); // when - JobExecution jobExecution = jobLauncher.run(job, new JobParameters()); + JobExecution jobExecution = jobOperator.start(job, new JobParameters()); // then Assertions.assertEquals(ExitStatus.COMPLETED, jobExecution.getExitStatus()); @@ -119,10 +119,10 @@ public void testParallelStepsWithVirtualThreads() throws Exception { ApplicationContext context = new AnnotationConfigApplicationContext( JobConfigurationForRunningParallelStepsWithVirtualThreads.class); Job job = context.getBean(Job.class); - JobLauncher jobLauncher = context.getBean(JobLauncher.class); + JobOperator jobOperator = context.getBean(JobOperator.class); // when - JobExecution jobExecution = jobLauncher.run(job, new JobParameters()); + JobExecution jobExecution = jobOperator.start(job, new JobParameters()); // then Assertions.assertEquals(ExitStatus.COMPLETED, jobExecution.getExitStatus()); @@ -134,10 +134,10 @@ public void testAsyncItemProcessingWithVirtualThreads() throws Exception { ApplicationContext context = new AnnotationConfigApplicationContext( JobConfigurationForAsynchronousItemProcessingWithVirtualThreads.class); Job job = context.getBean(Job.class); - JobLauncher jobLauncher = context.getBean(JobLauncher.class); + JobOperator jobOperator = context.getBean(JobOperator.class); // when - JobExecution jobExecution = jobLauncher.run(job, new JobParameters()); + JobExecution jobExecution = jobOperator.start(job, new JobParameters()); // then Assertions.assertEquals(ExitStatus.COMPLETED, jobExecution.getExitStatus()); @@ -152,10 +152,10 @@ public void testLocalPartitioningWithVirtualThreads() throws Exception { ApplicationContext context = new AnnotationConfigApplicationContext( JobConfigurationForRunningPartitionedStepsWithVirtualThreads.class); Job job = context.getBean(Job.class); - JobLauncher jobLauncher = context.getBean(JobLauncher.class); + JobOperator jobOperator = context.getBean(JobOperator.class); // when - JobExecution jobExecution = jobLauncher.run(job, new JobParameters()); + JobExecution jobExecution = jobOperator.start(job, new JobParameters()); // then Assertions.assertEquals(ExitStatus.COMPLETED, jobExecution.getExitStatus()); @@ -168,10 +168,10 @@ public void testSystemCommandTaskletWithVirtualThreads() throws Exception { ApplicationContext context = new AnnotationConfigApplicationContext( JobConfigurationForRunningSystemCommandTaskletsWithVirtualThreads.class); Job job = context.getBean(Job.class); - JobLauncher jobLauncher = context.getBean(JobLauncher.class); + JobOperator jobOperator = context.getBean(JobOperator.class); // when - JobExecution jobExecution = jobLauncher.run(job, new JobParameters()); + JobExecution jobExecution = jobOperator.start(job, new JobParameters()); // then Assertions.assertEquals(ExitStatus.COMPLETED, jobExecution.getExitStatus()); diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/samples/loop/LoopFlowSampleFunctionalTests.java b/spring-batch-samples/src/test/java/org/springframework/batch/samples/loop/LoopFlowSampleFunctionalTests.java index 910ebabe91..0f0b70cb81 100644 --- a/spring-batch-samples/src/test/java/org/springframework/batch/samples/loop/LoopFlowSampleFunctionalTests.java +++ b/spring-batch-samples/src/test/java/org/springframework/batch/samples/loop/LoopFlowSampleFunctionalTests.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,7 +18,7 @@ import org.junit.jupiter.api.Test; import org.springframework.batch.samples.domain.trade.internal.ItemTrackingTradeItemWriter; -import org.springframework.batch.test.JobLauncherTestUtils; +import org.springframework.batch.test.JobOperatorTestUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; @@ -41,11 +41,11 @@ class LoopFlowSampleFunctionalTests { private ItemTrackingTradeItemWriter itemWriter; @Autowired - private JobLauncherTestUtils jobLauncherTestUtils; + private JobOperatorTestUtils jobOperatorTestUtils; @Test void testJobLaunch() throws Exception { - this.jobLauncherTestUtils.launchJob(); + this.jobOperatorTestUtils.startJob(); // items processed = items read + 2 exceptions assertEquals(10, itemWriter.getItems().size()); } diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/samples/mail/MailJobFunctionalTests.java b/spring-batch-samples/src/test/java/org/springframework/batch/samples/mail/MailJobFunctionalTests.java index a9d57ce1dd..05e610e5fb 100644 --- a/spring-batch-samples/src/test/java/org/springframework/batch/samples/mail/MailJobFunctionalTests.java +++ b/spring-batch-samples/src/test/java/org/springframework/batch/samples/mail/MailJobFunctionalTests.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,8 +25,8 @@ import org.junit.jupiter.api.Test; import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.test.JobLauncherTestUtils; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.test.JobOperatorTestUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.mail.MailMessage; @@ -68,7 +68,7 @@ class MailJobFunctionalTests { private JdbcTemplate jdbcTemplate; @Autowired - private JobLauncherTestUtils jobLauncherTestUtils; + private JobOperatorTestUtils jobOperatorTestUtils; @Autowired private TestMailErrorHandler errorHandler; @@ -97,7 +97,7 @@ void after() { void testLaunchJob() throws Exception { this.createUsers(new Object[][] { USER1, USER2_SKIP, USER3, USER4_SKIP, USER5, USER6, USER7, USER8 }); - JobExecution jobExecution = jobLauncherTestUtils.launchJob(); + JobExecution jobExecution = jobOperatorTestUtils.startJob(); assertEquals(ExitStatus.COMPLETED, jobExecution.getExitStatus()); List receivedMessages = mailSender.getReceivedMessages(); diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/samples/misc/groovy/GroovyJobFunctionalTests.java b/spring-batch-samples/src/test/java/org/springframework/batch/samples/misc/groovy/GroovyJobFunctionalTests.java index 82f75cc4f8..d9b59058ef 100644 --- a/spring-batch-samples/src/test/java/org/springframework/batch/samples/misc/groovy/GroovyJobFunctionalTests.java +++ b/spring-batch-samples/src/test/java/org/springframework/batch/samples/misc/groovy/GroovyJobFunctionalTests.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. @@ -23,7 +23,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.batch.test.JobLauncherTestUtils; +import org.springframework.batch.test.JobOperatorTestUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; @@ -35,7 +35,7 @@ public class GroovyJobFunctionalTests { @Autowired - private JobLauncherTestUtils jobLauncherTestUtils; + private JobOperatorTestUtils jobOperatorTestUtils; @BeforeEach void removeOldData() throws IOException { @@ -45,7 +45,7 @@ void removeOldData() throws IOException { @Test void testLaunchJob() throws Exception { assertFalse(new File("target/groovyJob/output/files.zip").exists()); - jobLauncherTestUtils.launchJob(); + jobOperatorTestUtils.startJob(); assertTrue(new File("target/groovyJob/output/files.zip").exists()); } diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/samples/misc/jmx/RemoteLauncherTests.java b/spring-batch-samples/src/test/java/org/springframework/batch/samples/misc/jmx/RemoteLauncherFunctionalTests.java similarity index 92% rename from spring-batch-samples/src/test/java/org/springframework/batch/samples/misc/jmx/RemoteLauncherTests.java rename to spring-batch-samples/src/test/java/org/springframework/batch/samples/misc/jmx/RemoteLauncherFunctionalTests.java index b3b91d0257..0e656fc01f 100644 --- a/spring-batch-samples/src/test/java/org/springframework/batch/samples/misc/jmx/RemoteLauncherTests.java +++ b/spring-batch-samples/src/test/java/org/springframework/batch/samples/misc/jmx/RemoteLauncherFunctionalTests.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. @@ -33,7 +33,11 @@ import java.util.List; import java.util.Properties; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; /** * @author Dave Syer @@ -41,9 +45,10 @@ * @author Mahmoud Ben Hassine * */ -class RemoteLauncherTests { +@SuppressWarnings("removal") +class RemoteLauncherFunctionalTests { - private static final Log logger = LogFactory.getLog(RemoteLauncherTests.class); + private static final Log logger = LogFactory.getLog(RemoteLauncherFunctionalTests.class); private static final List errors = new ArrayList<>(); diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/samples/misc/quartz/JobLauncherDetailsTests.java b/spring-batch-samples/src/test/java/org/springframework/batch/samples/misc/quartz/JobLauncherDetailsTests.java deleted file mode 100644 index c9eb56edbc..0000000000 --- a/spring-batch-samples/src/test/java/org/springframework/batch/samples/misc/quartz/JobLauncherDetailsTests.java +++ /dev/null @@ -1,174 +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.samples.misc.quartz; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.quartz.JobDetail; -import org.quartz.JobExecutionContext; -import org.quartz.impl.JobDetailImpl; -import org.quartz.impl.JobExecutionContextImpl; -import org.quartz.impl.triggers.SimpleTriggerImpl; -import org.quartz.spi.TriggerFiredBundle; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.JobParametersIncrementer; -import org.springframework.batch.core.JobParametersValidator; -import org.springframework.lang.Nullable; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.Mockito.mock; - -/** - * @author Dave Syer - * @author Glenn Renfro - * @author Mahmoud Ben Hassine - * - */ -class JobLauncherDetailsTests { - - private final JobLauncherDetails details = new JobLauncherDetails(); - - private TriggerFiredBundle firedBundle; - - private final List list = new ArrayList<>(); - - @BeforeEach - public void setUp() throws Exception { - details.setJobLauncher((job, jobParameters) -> { - list.add(jobParameters); - return null; - }); - - details.setJobLocator(name -> { - list.add(name); - return new StubJob("foo"); - }); - } - - private JobExecutionContext createContext(JobDetail jobDetail) { - firedBundle = new TriggerFiredBundle(jobDetail, new SimpleTriggerImpl(), null, false, new Date(), new Date(), - new Date(), new Date()); - return new StubJobExecutionContext(); - } - - @Test - void testExecuteWithNoJobParameters() { - JobDetail jobDetail = new JobDetailImpl(); - JobExecutionContext context = createContext(jobDetail); - details.executeInternal(context); - assertEquals(2, list.size()); - JobParameters parameters = (JobParameters) list.get(1); - assertEquals(0, parameters.getParameters().size()); - } - - @Test - void testExecuteWithJobName() { - JobDetail jobDetail = new JobDetailImpl(); - jobDetail.getJobDataMap().put(JobLauncherDetails.JOB_NAME, "FOO"); - JobExecutionContext context = createContext(jobDetail); - details.executeInternal(context); - assertEquals(2, list.size()); - assertEquals("FOO", list.get(0)); - } - - @Test - void testExecuteWithSomeJobParameters() { - JobDetail jobDetail = new JobDetailImpl(); - jobDetail.getJobDataMap().put("foo", "bar"); - JobExecutionContext context = createContext(jobDetail); - details.executeInternal(context); - assertEquals(2, list.size()); - JobParameters parameters = (JobParameters) list.get(1); - assertEquals(1, parameters.getParameters().size()); - } - - @Test - void testExecuteWithJobNameAndParameters() { - JobDetail jobDetail = new JobDetailImpl(); - jobDetail.getJobDataMap().put(JobLauncherDetails.JOB_NAME, "FOO"); - jobDetail.getJobDataMap().put("foo", "bar"); - JobExecutionContext context = createContext(jobDetail); - details.executeInternal(context); - assertEquals(2, list.size()); - assertEquals("FOO", list.get(0)); - JobParameters parameters = (JobParameters) list.get(1); - assertEquals(1, parameters.getParameters().size()); - } - - @Test - void testExecuteWithJobNameAndComplexParameters() { - JobDetail jobDetail = new JobDetailImpl(); - jobDetail.getJobDataMap().put(JobLauncherDetails.JOB_NAME, "FOO"); - jobDetail.getJobDataMap().put("foo", this); - JobExecutionContext context = createContext(jobDetail); - details.executeInternal(context); - assertEquals(2, list.size()); - assertEquals("FOO", list.get(0)); - JobParameters parameters = (JobParameters) list.get(1); - // Silently ignore parameters that are not simple types - assertEquals(0, parameters.getParameters().size()); - } - - private final class StubJobExecutionContext extends JobExecutionContextImpl { - - private StubJobExecutionContext() { - super(mock(), firedBundle, mock()); - } - - } - - private static class StubJob implements org.springframework.batch.core.Job { - - private final String name; - - public StubJob(String name) { - this.name = name; - } - - @Override - public void execute(JobExecution execution) { - } - - @Nullable - @Override - public JobParametersIncrementer getJobParametersIncrementer() { - return null; - } - - @Override - public JobParametersValidator getJobParametersValidator() { - return null; - } - - @Override - public String getName() { - return name; - } - - @Override - public boolean isRestartable() { - return false; - } - - } - -} diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/samples/partition/file/PartitionFileJobFunctionalTests.java b/spring-batch-samples/src/test/java/org/springframework/batch/samples/partition/file/PartitionFileJobFunctionalTests.java index 7b02c19379..7a9e3959ea 100644 --- a/spring-batch-samples/src/test/java/org/springframework/batch/samples/partition/file/PartitionFileJobFunctionalTests.java +++ b/spring-batch-samples/src/test/java/org/springframework/batch/samples/partition/file/PartitionFileJobFunctionalTests.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. @@ -24,13 +24,13 @@ import org.junit.jupiter.api.Test; import org.springframework.batch.core.BatchStatus; -import org.springframework.batch.core.JobExecution; +import org.springframework.batch.core.job.JobExecution; import org.springframework.batch.item.ExecutionContext; import org.springframework.batch.item.ItemReader; import org.springframework.batch.item.ItemStream; import org.springframework.batch.samples.domain.trade.CustomerCredit; import org.springframework.batch.samples.domain.trade.internal.CustomerCreditIncreaseProcessor; -import org.springframework.batch.test.JobLauncherTestUtils; +import org.springframework.batch.test.JobOperatorTestUtils; import org.springframework.beans.BeansException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; @@ -50,7 +50,7 @@ class PartitionFileJobFunctionalTests implements ApplicationContextAware { private ItemReader inputReader; @Autowired - private JobLauncherTestUtils jobLauncherTestUtils; + private JobOperatorTestUtils jobOperatorTestUtils; private ApplicationContext applicationContext; @@ -71,7 +71,7 @@ void testUpdateCredit() throws Exception { List inputs = new ArrayList<>(getCredits(inputReader)); close(inputReader); - JobExecution jobExecution = jobLauncherTestUtils.launchJob(); + JobExecution jobExecution = jobOperatorTestUtils.startJob(); assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus()); @SuppressWarnings("unchecked") @@ -85,7 +85,6 @@ void testUpdateCredit() throws Exception { int itemCount = inputs.size(); assertTrue(itemCount > 0, "No entries were available in the input"); - inputs.iterator(); for (int i = 0; i < itemCount; i++) { assertEquals(inputs.get(i).getCredit().add(CustomerCreditIncreaseProcessor.FIXED_AMOUNT).intValue(), outputs.get(i).getCredit().intValue()); @@ -110,8 +109,8 @@ private Set getCredits(ItemReader reader) throws * Open the reader if applicable. */ private void open(ItemReader reader) { - if (reader instanceof ItemStream) { - ((ItemStream) reader).open(new ExecutionContext()); + if (reader instanceof ItemStream itemStream) { + itemStream.open(new ExecutionContext()); } } @@ -119,8 +118,8 @@ private void open(ItemReader reader) { * Close the reader if applicable. */ private void close(ItemReader reader) { - if (reader instanceof ItemStream) { - ((ItemStream) reader).close(); + if (reader instanceof ItemStream itemStream) { + itemStream.close(); } } diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/samples/partition/jdbc/PartitionJdbcJobFunctionalTests.java b/spring-batch-samples/src/test/java/org/springframework/batch/samples/partition/jdbc/PartitionJdbcJobFunctionalTests.java index 97719a96d7..c0c414e90e 100644 --- a/spring-batch-samples/src/test/java/org/springframework/batch/samples/partition/jdbc/PartitionJdbcJobFunctionalTests.java +++ b/spring-batch-samples/src/test/java/org/springframework/batch/samples/partition/jdbc/PartitionJdbcJobFunctionalTests.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. @@ -24,13 +24,13 @@ import org.junit.jupiter.api.Test; import org.springframework.batch.core.BatchStatus; -import org.springframework.batch.core.JobExecution; +import org.springframework.batch.core.job.JobExecution; import org.springframework.batch.item.ExecutionContext; import org.springframework.batch.item.ItemReader; import org.springframework.batch.item.ItemStream; import org.springframework.batch.samples.domain.trade.CustomerCredit; import org.springframework.batch.samples.domain.trade.internal.CustomerCreditIncreaseProcessor; -import org.springframework.batch.test.JobLauncherTestUtils; +import org.springframework.batch.test.JobOperatorTestUtils; import org.springframework.beans.BeansException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; @@ -50,7 +50,7 @@ class PartitionJdbcJobFunctionalTests implements ApplicationContextAware { private ItemReader inputReader; @Autowired - private JobLauncherTestUtils jobLauncherTestUtils; + private JobOperatorTestUtils jobOperatorTestUtils; private ApplicationContext applicationContext; @@ -71,7 +71,7 @@ void testUpdateCredit() throws Exception { List inputs = new ArrayList<>(getCredits(inputReader)); close(inputReader); - JobExecution jobExecution = jobLauncherTestUtils.launchJob(); + JobExecution jobExecution = jobOperatorTestUtils.startJob(); assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus()); @SuppressWarnings("unchecked") @@ -85,7 +85,6 @@ void testUpdateCredit() throws Exception { int itemCount = inputs.size(); assertTrue(itemCount > 0, "Input from reader has no entries."); - inputs.iterator(); for (int i = 0; i < itemCount; i++) { assertEquals(inputs.get(i).getCredit().add(CustomerCreditIncreaseProcessor.FIXED_AMOUNT).intValue(), outputs.get(i).getCredit().intValue()); @@ -109,8 +108,8 @@ private Set getCredits(ItemReader reader) throws * Open the reader if applicable. */ private void open(ItemReader reader) { - if (reader instanceof ItemStream) { - ((ItemStream) reader).open(new ExecutionContext()); + if (reader instanceof ItemStream itemStream) { + itemStream.open(new ExecutionContext()); } } @@ -118,8 +117,8 @@ private void open(ItemReader reader) { * Close the reader if applicable. */ private void close(ItemReader reader) { - if (reader instanceof ItemStream) { - ((ItemStream) reader).close(); + if (reader instanceof ItemStream itemStream) { + itemStream.close(); } } diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/samples/partition/remote/RemotePartitioningJobFunctionalTests.java b/spring-batch-samples/src/test/java/org/springframework/batch/samples/partition/remote/RemotePartitioningJobFunctionalTests.java index 6025175727..2dea8b7462 100644 --- a/spring-batch-samples/src/test/java/org/springframework/batch/samples/partition/remote/RemotePartitioningJobFunctionalTests.java +++ b/spring-batch-samples/src/test/java/org/springframework/batch/samples/partition/remote/RemotePartitioningJobFunctionalTests.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. @@ -25,10 +25,10 @@ import org.junit.jupiter.api.Test; import org.springframework.batch.core.ExitStatus; -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.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.launch.JobOperator; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.AnnotationConfigApplicationContext; @@ -54,7 +54,7 @@ public abstract class RemotePartitioningJobFunctionalTests { private String brokerUrl; @Autowired - protected JobLauncher jobLauncher; + protected JobOperator jobOperator; @Autowired private DataSource dataSource; @@ -85,12 +85,12 @@ void setUp() throws Exception { @Test void testRemotePartitioningJob(@Autowired Job job) throws Exception { // when - JobExecution jobExecution = this.jobLauncher.run(job, new JobParameters()); + JobExecution jobExecution = this.jobOperator.start(job, new JobParameters()); // then assertEquals(ExitStatus.COMPLETED.getExitCode(), jobExecution.getExitStatus().getExitCode()); - assertEquals(4, jobExecution.getStepExecutions().size()); // manager + 3 - // workers + // Expecting 4 StepExecution instances: manager + 3 workers + assertEquals(4, jobExecution.getStepExecutions().size()); } @AfterEach 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..9df598db32 --- /dev/null +++ b/spring-batch-samples/src/test/java/org/springframework/batch/samples/petclinic/PetClinicJobFunctionalTests.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.samples.petclinic; + +import java.io.IOException; +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; + +import org.springframework.batch.core.BatchStatus; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.launch.JobOperator; +import org.springframework.batch.test.JobOperatorTestUtils; +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 JobOperatorTestUtils jobOperatorTestUtils; + + @BeforeEach + @AfterEach + public void deleteOwnersFile() throws IOException { + Files.deleteIfExists(Paths.get("owners.csv")); + } + + @Test + void testLaunchJobWithXmlConfiguration() throws Exception { + // when + JobExecution jobExecution = jobOperatorTestUtils.startJob(); + + // then + assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus()); + } + + @Test + void testLaunchJobWithJavaConfiguration() throws Exception { + // given + ApplicationContext context = new AnnotationConfigApplicationContext(OwnersExportJobConfiguration.class); + JobOperator jobOperator = context.getBean(JobOperator.class); + Job job = context.getBean(Job.class); + + // when + JobExecution jobExecution = jobOperator.start(job, new JobParameters()); + + // then + assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus()); + } + +} diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/samples/processindicator/ProcessIndicatorJobFunctionalTests.java b/spring-batch-samples/src/test/java/org/springframework/batch/samples/processindicator/ProcessIndicatorJobFunctionalTests.java index 221d658d2f..85e29f70d9 100644 --- a/spring-batch-samples/src/test/java/org/springframework/batch/samples/processindicator/ProcessIndicatorJobFunctionalTests.java +++ b/spring-batch-samples/src/test/java/org/springframework/batch/samples/processindicator/ProcessIndicatorJobFunctionalTests.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. @@ -21,8 +21,8 @@ import org.junit.jupiter.api.Test; import org.springframework.batch.core.BatchStatus; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.test.JobLauncherTestUtils; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.test.JobOperatorTestUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; @@ -35,7 +35,7 @@ class ProcessIndicatorJobFunctionalTests { @Autowired - private JobLauncherTestUtils jobLauncherTestUtils; + private JobOperatorTestUtils jobOperatorTestUtils; private JdbcTemplate jdbcTemplate; @@ -47,7 +47,7 @@ public void setDataSource(DataSource dataSource) { @Test void testLaunchJob() throws Exception { int before = JdbcTestUtils.countRowsInTable(jdbcTemplate, "BATCH_STAGING"); - JobExecution execution = jobLauncherTestUtils.launchJob(); + JobExecution execution = jobOperatorTestUtils.startJob(); int after = JdbcTestUtils.countRowsInTable(jdbcTemplate, "BATCH_STAGING"); assertEquals(BatchStatus.COMPLETED, execution.getStatus()); assertEquals(after - before, execution.getStepExecutions().iterator().next().getReadCount()); diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/samples/restart/fail/RestartFunctionalTests.java b/spring-batch-samples/src/test/java/org/springframework/batch/samples/restart/fail/RestartFunctionalTests.java index 2efbe557b0..a87608b349 100644 --- a/spring-batch-samples/src/test/java/org/springframework/batch/samples/restart/fail/RestartFunctionalTests.java +++ b/spring-batch-samples/src/test/java/org/springframework/batch/samples/restart/fail/RestartFunctionalTests.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. @@ -21,10 +21,10 @@ import org.junit.jupiter.api.Test; import org.springframework.batch.core.BatchStatus; -import org.springframework.batch.core.JobExecution; +import org.springframework.batch.core.job.JobExecution; import org.springframework.batch.core.converter.DefaultJobParametersConverter; import org.springframework.batch.support.PropertiesConverter; -import org.springframework.batch.test.JobLauncherTestUtils; +import org.springframework.batch.test.JobOperatorTestUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; @@ -49,7 +49,7 @@ class RestartFunctionalTests { private JdbcTemplate jdbcTemplate; @Autowired - private JobLauncherTestUtils jobLauncherTestUtils; + private JobOperatorTestUtils jobOperatorTestUtils; @Autowired public void setDataSource(DataSource dataSource) { @@ -91,8 +91,8 @@ void testLaunchJob() throws Exception { // load the application context and launch the job private JobExecution runJobForRestartTest() throws Exception { - return jobLauncherTestUtils - .launchJob(new DefaultJobParametersConverter().getJobParameters(PropertiesConverter.stringToProperties( + return jobOperatorTestUtils + .startJob(new DefaultJobParametersConverter().getJobParameters(PropertiesConverter.stringToProperties( "input.file=classpath:org/springframework/batch/samples/restart/fail/data/ImportTradeDataStep.txt"))); } diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/samples/restart/stop/GracefulShutdownFunctionalTests.java b/spring-batch-samples/src/test/java/org/springframework/batch/samples/restart/stop/GracefulShutdownFunctionalTests.java index 20c8a11e03..37ae48506c 100644 --- a/spring-batch-samples/src/test/java/org/springframework/batch/samples/restart/stop/GracefulShutdownFunctionalTests.java +++ b/spring-batch-samples/src/test/java/org/springframework/batch/samples/restart/stop/GracefulShutdownFunctionalTests.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. @@ -21,11 +21,11 @@ import org.junit.jupiter.api.Test; import org.springframework.batch.core.BatchStatus; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.JobParametersBuilder; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; import org.springframework.batch.core.launch.JobOperator; -import org.springframework.batch.test.JobLauncherTestUtils; +import org.springframework.batch.test.JobOperatorTestUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; @@ -35,7 +35,7 @@ /** * Functional test for graceful shutdown. A batch container is started in a new thread, - * then it's stopped using {@link JobOperator#stop(long)}. + * then it's stopped using {@link JobOperator#stop}. * * @author Lucas Ward * @author Parikshit Dutta @@ -43,15 +43,14 @@ * @author Mahmoud Ben Hassine * */ -@SpringJUnitConfig(locations = { "/simple-job-launcher-context.xml", - "/org/springframework/batch/samples/restart/stop/stopRestartSample.xml" }) +@SpringJUnitConfig(locations = { "/org/springframework/batch/samples/restart/stop/stopRestartSample.xml" }) class GracefulShutdownFunctionalTests { /** Logger */ private final Log logger = LogFactory.getLog(getClass()); @Autowired - private JobLauncherTestUtils jobLauncherTestUtils; + private JobOperatorTestUtils jobOperatorTestUtils; @Autowired private JobOperator jobOperator; @@ -61,14 +60,14 @@ void testLaunchJob() throws Exception { final JobParameters jobParameters = new JobParametersBuilder().addLong("timestamp", System.currentTimeMillis()) .toJobParameters(); - JobExecution jobExecution = jobLauncherTestUtils.launchJob(jobParameters); + JobExecution jobExecution = jobOperatorTestUtils.startJob(jobParameters); Thread.sleep(1000); assertEquals(BatchStatus.STARTED, jobExecution.getStatus()); assertTrue(jobExecution.isRunning()); - jobOperator.stop(jobExecution.getId()); + jobOperator.stop(jobExecution); int count = 0; while (jobExecution.isRunning() && count <= 10) { diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/samples/restart/stop/JobOperatorFunctionalTests.java b/spring-batch-samples/src/test/java/org/springframework/batch/samples/restart/stop/JobOperatorFunctionalTests.java deleted file mode 100644 index 88c5b0e94a..0000000000 --- a/spring-batch-samples/src/test/java/org/springframework/batch/samples/restart/stop/JobOperatorFunctionalTests.java +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright 2008-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.samples.restart.stop; - -import java.util.List; -import java.util.Map; -import java.util.Properties; -import java.util.Set; - -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.springframework.batch.core.BatchStatus; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.configuration.JobRegistry; -import org.springframework.batch.core.configuration.support.ReferenceJobFactory; -import org.springframework.batch.core.launch.JobOperator; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -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.assertTrue; - -@SpringJUnitConfig(locations = { "/simple-job-launcher-context.xml", - "/org/springframework/batch/samples/restart/stop/stopRestartSample.xml" }) -class JobOperatorFunctionalTests { - - private static final Log LOG = LogFactory.getLog(JobOperatorFunctionalTests.class); - - @Autowired - private JobOperator operator; - - @Autowired - private Job job; - - @Autowired - private JobRegistry jobRegistry; - - @BeforeEach - void setUp() throws Exception { - if (!jobRegistry.getJobNames().contains(job.getName())) { - jobRegistry.register(new ReferenceJobFactory(job)); - } - } - - @Test - void testStartStopResumeJob() throws Exception { - String params = "jobOperatorTestParam=7,java.lang.Long,true"; - Properties properties = new Properties(); - properties.setProperty("jobOperatorTestParam", "7,java.lang.Long,true"); - - long executionId = operator.start(job.getName(), properties); - assertEquals(params, operator.getParameters(executionId)); - stopAndCheckStatus(executionId); - - long resumedExecutionId = operator.restart(executionId); - assertEquals(params, operator.getParameters(resumedExecutionId)); - stopAndCheckStatus(resumedExecutionId); - - List instances = operator.getJobInstances(job.getName(), 0, 1); - assertEquals(1, instances.size()); - long instanceId = instances.get(0); - - List executions = operator.getExecutions(instanceId); - assertEquals(2, executions.size()); - // latest execution is the first in the returned list - assertEquals(resumedExecutionId, executions.get(0).longValue()); - assertEquals(executionId, executions.get(1).longValue()); - } - - /** - * @param executionId id of running job execution - */ - private void stopAndCheckStatus(long executionId) throws Exception { - // wait to the job to get up and running - Thread.sleep(1000); - - Set runningExecutions = operator.getRunningExecutions(job.getName()); - assertTrue(runningExecutions.contains(executionId), - "Wrong executions: " + runningExecutions + " expected: " + executionId); - assertTrue(operator.getSummary(executionId).contains(BatchStatus.STARTED.toString()), - "Wrong summary: " + operator.getSummary(executionId)); - - operator.stop(executionId); - - int count = 0; - while (operator.getRunningExecutions(job.getName()).contains(executionId) && count <= 10) { - LOG.info("Checking for running JobExecution: count=" + count); - Thread.sleep(100); - count++; - } - - runningExecutions = operator.getRunningExecutions(job.getName()); - assertFalse(runningExecutions.contains(executionId), - "Wrong executions: " + runningExecutions + " expected: " + executionId); - assertTrue(operator.getSummary(executionId).contains(BatchStatus.STOPPED.toString()), - "Wrong summary: " + operator.getSummary(executionId)); - - // there is just a single step in the test job - Map summaries = operator.getStepExecutionSummaries(executionId); - LOG.info(summaries); - assertTrue(summaries.values().toString().contains(BatchStatus.STOPPED.toString())); - } - - @Test - void testMultipleSimultaneousInstances() throws Exception { - String jobName = job.getName(); - - Set names = operator.getJobNames(); - assertEquals(1, names.size()); - assertTrue(names.contains(jobName)); - - long exec1 = operator.startNextInstance(jobName); - long exec2 = operator.startNextInstance(jobName); - - assertTrue(exec1 != exec2); - assertNotEquals(operator.getParameters(exec1), operator.getParameters(exec2)); - - // Give the asynchronous task executor a chance to start executions - Thread.sleep(1000); - - Set executions = operator.getRunningExecutions(jobName); - assertTrue(executions.contains(exec1)); - assertTrue(executions.contains(exec2)); - - int count = 0; - boolean running = operator.getSummary(exec1).contains("STARTED") - && operator.getSummary(exec2).contains("STARTED"); - - while (count++ < 10 && !running) { - Thread.sleep(100L); - running = operator.getSummary(exec1).contains("STARTED") && operator.getSummary(exec2).contains("STARTED"); - } - - assertTrue(running, String.format("Jobs not started: [%s] and [%s]", operator.getSummary(exec1), - operator.getSummary(exec1))); - - operator.stop(exec1); - operator.stop(exec2); - } - -} diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/samples/retry/RetrySampleFunctionalTests.java b/spring-batch-samples/src/test/java/org/springframework/batch/samples/retry/RetrySampleFunctionalTests.java index 48fdb57b91..8f0c558962 100644 --- a/spring-batch-samples/src/test/java/org/springframework/batch/samples/retry/RetrySampleFunctionalTests.java +++ b/spring-batch-samples/src/test/java/org/springframework/batch/samples/retry/RetrySampleFunctionalTests.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. @@ -18,13 +18,13 @@ 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.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.launch.JobOperator; import org.springframework.batch.samples.domain.trade.internal.GeneratingTradeItemReader; import org.springframework.batch.samples.support.RetrySampleItemWriter; -import org.springframework.batch.test.JobLauncherTestUtils; +import org.springframework.batch.test.JobOperatorTestUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; @@ -51,11 +51,11 @@ class RetrySampleFunctionalTests { private RetrySampleItemWriter itemProcessor; @Autowired - private JobLauncherTestUtils jobLauncherTestUtils; + private JobOperatorTestUtils jobOperatorTestUtils; @Test void testLaunchJobWithXmlConfig() throws Exception { - this.jobLauncherTestUtils.launchJob(); + this.jobOperatorTestUtils.startJob(); // items processed = items read + 2 exceptions assertEquals(itemGenerator.getLimit() + 2, itemProcessor.getCounter()); } @@ -64,13 +64,13 @@ void testLaunchJobWithXmlConfig() throws Exception { public void testLaunchJobWithJavaConfig() throws Exception { // given ApplicationContext context = new AnnotationConfigApplicationContext(RetrySampleConfiguration.class); - JobLauncher jobLauncher = context.getBean(JobLauncher.class); + JobOperator jobOperator = context.getBean(JobOperator.class); Job job = context.getBean(Job.class); GeneratingTradeItemReader itemGenerator = context.getBean(GeneratingTradeItemReader.class); RetrySampleItemWriter itemProcessor = context.getBean(RetrySampleItemWriter.class); // when - JobExecution jobExecution = jobLauncher.run(job, new JobParameters()); + JobExecution jobExecution = jobOperator.start(job, new JobParameters()); // then assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus()); diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/samples/skip/SkipSampleFunctionalTests.java b/spring-batch-samples/src/test/java/org/springframework/batch/samples/skip/SkipSampleFunctionalTests.java index 1edfdab192..acaceb7146 100644 --- a/spring-batch-samples/src/test/java/org/springframework/batch/samples/skip/SkipSampleFunctionalTests.java +++ b/spring-batch-samples/src/test/java/org/springframework/batch/samples/skip/SkipSampleFunctionalTests.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. @@ -26,14 +26,13 @@ import org.springframework.batch.core.BatchStatus; import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.JobParametersInvalidException; -import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.UnexpectedJobExecutionException; -import org.springframework.batch.core.explore.JobExplorer; -import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersInvalidException; +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.core.step.StepExecution; +import org.springframework.batch.core.job.UnexpectedJobExecutionException; import org.springframework.batch.core.launch.JobOperator; import org.springframework.batch.core.launch.JobParametersNotFoundException; import org.springframework.batch.core.launch.NoSuchJobException; @@ -69,7 +68,7 @@ class SkipSampleFunctionalTests { private JdbcTemplate jdbcTemplate; @Autowired - private JobExplorer jobExplorer; + private JobRepository jobRepository; @Autowired private JobOperator jobOperator; @@ -157,7 +156,7 @@ void testJobIncrementing() { // Launch 1 // long id1 = launchJobWithIncrementer(); - JobExecution execution1 = jobExplorer.getJobExecution(id1); + JobExecution execution1 = jobRepository.getJobExecution(id1); assertEquals(BatchStatus.COMPLETED, execution1.getStatus()); validateLaunchWithSkips(execution1); @@ -171,7 +170,7 @@ void testJobIncrementing() { // Launch 2 // long id2 = launchJobWithIncrementer(); - JobExecution execution2 = jobExplorer.getJobExecution(id2); + JobExecution execution2 = jobRepository.getJobExecution(id2); assertEquals(BatchStatus.COMPLETED, execution2.getStatus()); validateLaunchWithoutSkips(execution2); @@ -193,11 +192,11 @@ void testJobIncrementing() { void testSkippableExceptionDuringRead() throws Exception { // given ApplicationContext context = new AnnotationConfigApplicationContext(SkippableExceptionDuringReadSample.class); - JobLauncher jobLauncher = context.getBean(JobLauncher.class); + JobOperator jobOperator = context.getBean(JobOperator.class); Job job = context.getBean(Job.class); // when - JobExecution jobExecution = jobLauncher.run(job, new JobParameters()); + JobExecution jobExecution = jobOperator.start(job, new JobParameters()); // then assertEquals(ExitStatus.COMPLETED.getExitCode(), jobExecution.getExitStatus().getExitCode()); @@ -217,11 +216,11 @@ void testSkippableExceptionDuringProcess() throws Exception { // given ApplicationContext context = new AnnotationConfigApplicationContext( SkippableExceptionDuringProcessSample.class); - JobLauncher jobLauncher = context.getBean(JobLauncher.class); + JobOperator jobOperator = context.getBean(JobOperator.class); Job job = context.getBean(Job.class); // when - JobExecution jobExecution = jobLauncher.run(job, new JobParameters()); + JobExecution jobExecution = jobOperator.start(job, new JobParameters()); // then assertEquals(ExitStatus.COMPLETED.getExitCode(), jobExecution.getExitStatus().getExitCode()); @@ -242,11 +241,11 @@ void testSkippableExceptionDuringProcess() throws Exception { void testSkippableExceptionDuringWrite() throws Exception { // given ApplicationContext context = new AnnotationConfigApplicationContext(SkippableExceptionDuringWriteSample.class); - JobLauncher jobLauncher = context.getBean(JobLauncher.class); + JobOperator jobOperator = context.getBean(JobOperator.class); Job job = context.getBean(Job.class); // when - JobExecution jobExecution = jobLauncher.run(job, new JobParameters()); + JobExecution jobExecution = jobOperator.start(job, new JobParameters()); // then assertEquals(ExitStatus.COMPLETED.getExitCode(), jobExecution.getExitStatus().getExitCode()); @@ -312,6 +311,7 @@ private Map getStepExecutionAsMap(JobExecution jobExecution, Str * Launch the entire job, including all steps, in order. * @return JobExecution, so that the test may validate the exit status */ + @SuppressWarnings("removal") public long launchJobWithIncrementer() { SkipCheckingListener.resetProcessSkips(); try { diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/samples/trade/TradeJobFunctionalTests.java b/spring-batch-samples/src/test/java/org/springframework/batch/samples/trade/TradeJobFunctionalTests.java index 88297d3034..9f78a9693b 100644 --- a/spring-batch-samples/src/test/java/org/springframework/batch/samples/trade/TradeJobFunctionalTests.java +++ b/spring-batch-samples/src/test/java/org/springframework/batch/samples/trade/TradeJobFunctionalTests.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. @@ -29,7 +29,7 @@ import org.junit.jupiter.api.Test; import org.springframework.batch.samples.domain.trade.Trade; -import org.springframework.batch.test.JobLauncherTestUtils; +import org.springframework.batch.test.JobOperatorTestUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; @@ -56,7 +56,7 @@ class TradeJobFunctionalTests { private final Map credits = new HashMap<>(); @Autowired - private JobLauncherTestUtils jobLauncherTestUtils; + private JobOperatorTestUtils jobOperatorTestUtils; @Autowired public void setDataSource(DataSource dataSource) { @@ -80,7 +80,7 @@ void tearDown() { @Test void testLaunchJob() throws Exception { - this.jobLauncherTestUtils.launchJob(); + this.jobOperatorTestUtils.startJob(); customers = Arrays.asList(new Customer("customer1", (credits.get("customer1") - 98.34)), new Customer("customer2", (credits.get("customer2") - 18.12 - 12.78)), diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/samples/validation/ValidationSampleFunctionalTests.java b/spring-batch-samples/src/test/java/org/springframework/batch/samples/validation/ValidationSampleFunctionalTests.java index 760799c3d3..f8b0a23b7c 100644 --- a/spring-batch-samples/src/test/java/org/springframework/batch/samples/validation/ValidationSampleFunctionalTests.java +++ b/spring-batch-samples/src/test/java/org/springframework/batch/samples/validation/ValidationSampleFunctionalTests.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,10 +21,10 @@ import org.junit.jupiter.api.Test; import org.springframework.batch.core.ExitStatus; -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.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.launch.JobOperator; import org.springframework.batch.item.support.ListItemWriter; import org.springframework.batch.samples.validation.domain.Person; import org.springframework.beans.factory.annotation.Autowired; @@ -43,7 +43,7 @@ class ValidationSampleFunctionalTests { private Job job; @Autowired - private JobLauncher jobLauncher; + private JobOperator jobOperator; @Autowired private ListItemWriter listItemWriter; @@ -54,7 +54,7 @@ void testItemValidation() throws Exception { JobParameters jobParameters = new JobParameters(); // when - JobExecution jobExecution = this.jobLauncher.run(this.job, jobParameters); + JobExecution jobExecution = this.jobOperator.start(this.job, jobParameters); // then assertEquals(ExitStatus.COMPLETED.getExitCode(), jobExecution.getExitStatus().getExitCode()); diff --git a/spring-batch-test/pom.xml b/spring-batch-test/pom.xml index 9aefd49260..30ca92fe41 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 + 6.0.0-SNAPSHOT spring-batch-test Spring Batch Test @@ -79,6 +79,12 @@ ${junit-vintage-engine.version} test + + org.junit.platform + junit-platform-launcher + ${junit-platform-launcher.version} + test + org.mockito mockito-core diff --git a/spring-batch-test/src/main/java/org/springframework/batch/test/ExecutionContextTestUtils.java b/spring-batch-test/src/main/java/org/springframework/batch/test/ExecutionContextTestUtils.java index 9d143d80b6..f3bbdbbf53 100644 --- a/spring-batch-test/src/main/java/org/springframework/batch/test/ExecutionContextTestUtils.java +++ b/spring-batch-test/src/main/java/org/springframework/batch/test/ExecutionContextTestUtils.java @@ -19,8 +19,8 @@ import java.util.ArrayList; import java.util.List; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.item.ExecutionContext; import org.springframework.lang.Nullable; diff --git a/spring-batch-test/src/main/java/org/springframework/batch/test/JobLauncherTestUtils.java b/spring-batch-test/src/main/java/org/springframework/batch/test/JobLauncherTestUtils.java index b1a4da0f27..1acb5ad071 100644 --- a/spring-batch-test/src/main/java/org/springframework/batch/test/JobLauncherTestUtils.java +++ b/spring-batch-test/src/main/java/org/springframework/batch/test/JobLauncherTestUtils.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. @@ -23,12 +23,12 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobParameter; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.JobParametersBuilder; -import org.springframework.batch.core.Step; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameter; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; +import org.springframework.batch.core.step.Step; import org.springframework.batch.core.job.AbstractJob; import org.springframework.batch.core.job.SimpleJob; import org.springframework.batch.core.job.flow.FlowJob; @@ -66,21 +66,25 @@ * @author Dave Syer * @author Mahmoud Ben Hassine * @since 2.1 + * @deprecated Since 6.0 in favor of {@link JobOperatorTestUtils}. Scheduled for removal + * in 6.2 or later. */ +@SuppressWarnings("removal") +@Deprecated(since = "6.0", forRemoval = true) public class JobLauncherTestUtils { - private final SecureRandom secureRandom = new SecureRandom(); + protected final SecureRandom secureRandom = new SecureRandom(); /** Logger */ protected final Log logger = LogFactory.getLog(getClass()); - private JobLauncher jobLauncher; + protected JobLauncher jobLauncher; - private Job job; + protected Job job; - private JobRepository jobRepository; + protected JobRepository jobRepository; - private StepRunner stepRunner; + protected StepRunner stepRunner; /** * The Job instance that can be manipulated (e.g. launched) in this utility. @@ -131,7 +135,10 @@ public JobLauncher getJobLauncher() { * Launch the entire job, including all steps. * @return JobExecution, so that the test can validate the exit status * @throws Exception thrown if error occurs launching the job. + * @deprecated Since 6.0 in favor of {@link JobOperatorTestUtils#startJob()}. + * Scheduled for removal in 6.2 or later. */ + @Deprecated(since = "6.0", forRemoval = true) public JobExecution launchJob() throws Exception { return this.launchJob(this.getUniqueJobParameters()); } @@ -141,7 +148,11 @@ public JobExecution launchJob() throws Exception { * @param jobParameters instance of {@link JobParameters}. * @return JobExecution, so that the test can validate the exit status * @throws Exception thrown if error occurs launching the job. + * @deprecated Since 6.0 in favor of + * {@link JobOperatorTestUtils#startJob(JobParameters)}. Scheduled for removal in 6.2 + * or later. */ + @Deprecated(since = "6.0", forRemoval = true) public JobExecution launchJob(JobParameters jobParameters) throws Exception { return getJobLauncher().run(this.job, jobParameters); } @@ -183,7 +194,10 @@ protected StepRunner getStepRunner() { * Step with the given name. * @param stepName The name of the step to launch * @return JobExecution + * @deprecated Since 6.0 in favor of {@link JobOperatorTestUtils#startStep(String)}. + * Scheduled for removal in 6.2 or later. */ + @Deprecated(since = "6.0", forRemoval = true) public JobExecution launchStep(String stepName) { return this.launchStep(stepName, this.getUniqueJobParameters(), null); } @@ -223,7 +237,11 @@ public JobExecution launchStep(String stepName, JobParameters jobParameters) { * @param jobExecutionContext An ExecutionContext whose values will be loaded into the * Job ExecutionContext prior to launching the step. * @return JobExecution + * @deprecated Since 6.0 in favor of + * {@link JobOperatorTestUtils#startStep(String, JobParameters, ExecutionContext)}. + * Scheduled for removal in 6.2 or later. */ + @Deprecated(since = "6.0", forRemoval = true) public JobExecution launchStep(String stepName, JobParameters jobParameters, @Nullable ExecutionContext jobExecutionContext) { if (!(job instanceof StepLocator)) { diff --git a/spring-batch-test/src/main/java/org/springframework/batch/test/JobOperatorTestUtils.java b/spring-batch-test/src/main/java/org/springframework/batch/test/JobOperatorTestUtils.java new file mode 100644 index 0000000000..ca6795b34a --- /dev/null +++ b/spring-batch-test/src/main/java/org/springframework/batch/test/JobOperatorTestUtils.java @@ -0,0 +1,250 @@ +/* + * 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; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.springframework.batch.core.job.AbstractJob; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.SimpleJob; +import org.springframework.batch.core.job.UnexpectedJobExecutionException; +import org.springframework.batch.core.job.flow.FlowJob; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; +import org.springframework.batch.core.job.parameters.JobParametersInvalidException; +import org.springframework.batch.core.launch.JobOperator; +import org.springframework.batch.core.launch.NoSuchJobException; +import org.springframework.batch.core.listener.JobExecutionListener; +import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException; +import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException; +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.core.repository.JobRestartException; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.step.StepLocator; +import org.springframework.batch.item.ExecutionContext; +import org.springframework.context.ApplicationContext; +import org.springframework.util.Assert; + +/** + *

    + * Utility class for testing batch jobs. It provides methods for starting an entire + * {@link AbstractJob}, allowing for end-to-end testing of individual steps, without + * having to run every step in the job. Any test classes using this utility can set up an + * instance in the {@link ApplicationContext} as part of the Spring test framework. The + * test context must contain batch infrastructure beans (ie a {@link JobRepository} and a + * {@link JobOperator}) as well as the job under test. The job under test will be + * autowired into this utility. + *

    + * + *

    + * This class also provides the ability to run {@link Step}s individually from a + * {@link SimpleJob} {@link FlowJob}. By starting {@link Step}s within a {@link Job} on + * their own, end-to-end testing of individual steps can be performed without having to + * run every step in the job. + *

    + * + *

    + * It should be noted that using any of the methods that don't contain + * {@link JobParameters} in their signature, will result in one being created with a + * random number of type {@code long} as a parameter. This will ensure restartability when + * no parameters are provided. + *

    + * + * @author Mahmoud Ben Hassine + * @since 6.0 + * + */ +@SuppressWarnings("removal") +public class JobOperatorTestUtils extends JobLauncherTestUtils { + + /** + * Name of the single-step job surrounding steps when tested individually + */ + public static final String JOB_NAME = "TestJob"; + + protected JobOperator jobOperator; + + /** + * Create a new instance of {@link JobOperatorTestUtils} with the provided job + * repository and job operator. + * @param jobOperator to use to start jobs and steps + * @param jobRepository to use to access job metadata + */ + public JobOperatorTestUtils(JobOperator jobOperator, JobRepository jobRepository) { + Assert.notNull(jobOperator, "JobRepository must not be null"); + Assert.notNull(jobRepository, "JobRepository must not be null"); + this.jobOperator = jobOperator; + this.jobRepository = jobRepository; + } + + /** + * Set the job that can be operated by this utility. + * @param job the job to test + */ + public void setJob(Job job) { + this.job = job; + } + + /** + * Set the job operator to be used by this utility. + * @param jobOperator the job operator to use to start jobs and steps + */ + public void setJobOperator(JobOperator jobOperator) { + this.jobOperator = jobOperator; + } + + /** + * Set the job repository to be used by this utility. + * @param jobRepository the job repository to use to access job metadata + */ + public void setJobRepository(JobRepository jobRepository) { + this.jobRepository = jobRepository; + } + + /** + * Start the entire job, including all steps, with a set of unique random job + * parameters. + * @return JobExecution, so that the test can validate the exit status + * @throws Exception thrown if error occurs launching the job. + */ + public JobExecution startJob() throws Exception { + return this.startJob(super.getUniqueJobParameters()); + } + + /** + * Start the entire job, including all steps, with the provided set of job parameters. + * @param jobParameters instance of {@link JobParameters}. + * @return JobExecution, so that the test can validate the exit status + * @throws Exception thrown if error occurs launching the job. + */ + public JobExecution startJob(JobParameters jobParameters) throws Exception { + return this.jobOperator.start(this.job, jobParameters); + } + + /** + * Start just the specified step in a surrounding single-step job of type + * {@link SimpleJob} named {@link #JOB_NAME}. A unique set of JobParameters will + * automatically be generated. An IllegalStateException is thrown if there is no Step + * with the given name. + * @param stepName The name of the step to launch + * @return JobExecution + */ + public JobExecution startStep(String stepName) { + return this.startStep(stepName, this.getUniqueJobParameters(), new ExecutionContext()); + } + + /** + * Extract the step from the injected job and start it in a surrounding single-step + * job of type {@link SimpleJob} named {@link #JOB_NAME}. An IllegalStateException is + * thrown if there is no Step with the given name. + * @param stepName The name of the step to start + * @param jobParameters The JobParameters to use during the start + * @param jobExecutionContext An ExecutionContext whose values will be loaded into the + * Job ExecutionContext before starting the step. + * @return JobExecution + */ + public JobExecution startStep(String stepName, JobParameters jobParameters, ExecutionContext jobExecutionContext) { + if (!(job instanceof StepLocator)) { + throw new UnsupportedOperationException("Cannot locate step from a Job that is not a StepLocator: job=" + + job.getName() + " does not implement StepLocator"); + } + StepLocator locator = (StepLocator) this.job; + Step step = locator.getStep(stepName); + if (step == null) { + step = locator.getStep(this.job.getName() + "." + stepName); + } + if (step == null) { + throw new IllegalStateException("No Step found with name: [" + stepName + "]"); + } + + return startStep(step, jobParameters, jobExecutionContext); + } + + /** + * Start just the specified step with a unique set of job parameters in a surrounding + * single-step job of type {@link SimpleJob} named {@link StepRunner#JOB_NAME}. An + * IllegalStateException is thrown if there is no Step with the given name. + * @param step The step to start + * @return JobExecution + */ + public JobExecution startStep(Step step) { + return startStep(step, getUniqueJobParameters(), new ExecutionContext()); + } + + /** + * Start just the specified step in a surrounding single-step job of type + * {@link SimpleJob} named {@link StepRunner#JOB_NAME}. An IllegalStateException is + * thrown if there is no Step with the given name. + * @param step The step to start + * @param jobParameters The JobParameters to use during the start + * @param jobExecutionContext An ExecutionContext whose values will be loaded into the + * Job ExecutionContext before starting the step. + * @return JobExecution + */ + public JobExecution startStep(Step step, JobParameters jobParameters, ExecutionContext jobExecutionContext) { + // Create a fake job + SimpleJob job = new SimpleJob(); + job.setName(JOB_NAME); + job.setJobRepository(this.jobRepository); + + List stepsToExecute = new ArrayList<>(); + stepsToExecute.add(step); + job.setSteps(stepsToExecute); + + // Dump the given Job ExecutionContext using a listener + if (jobExecutionContext != null && !jobExecutionContext.isEmpty()) { + job.setJobExecutionListeners(new JobExecutionListener[] { new JobExecutionListener() { + @Override + public void beforeJob(JobExecution jobExecution) { + ExecutionContext jobContext = jobExecution.getExecutionContext(); + for (Map.Entry entry : jobExecutionContext.entrySet()) { + jobContext.put(entry.getKey(), entry.getValue()); + } + } + } }); + } + + // Launch the job + try { + return this.jobOperator.start(job, jobParameters); + } + catch (NoSuchJobException | JobExecutionAlreadyRunningException | JobRestartException + | JobInstanceAlreadyCompleteException | JobParametersInvalidException e) { + throw new UnexpectedJobExecutionException("Step runner encountered exception.", e); + } + } + + /** + * @return a new {@link JobParameters} object containing only a parameter with a + * random number of type {@code long}, to ensure that the job instance will be unique. + */ + public JobParameters getUniqueJobParameters() { + return super.getUniqueJobParameters(); + } + + /** + * @return a new {@link JobParametersBuilder} object containing only a parameter with + * a random number of type {@code long}, to ensure that the job instance will be + * unique. + */ + public JobParametersBuilder getUniqueJobParametersBuilder() { + return super.getUniqueJobParametersBuilder(); + } + +} diff --git a/spring-batch-test/src/main/java/org/springframework/batch/test/JobRepositoryTestUtils.java b/spring-batch-test/src/main/java/org/springframework/batch/test/JobRepositoryTestUtils.java index d6a7b07327..d4cee99af9 100644 --- a/spring-batch-test/src/main/java/org/springframework/batch/test/JobRepositoryTestUtils.java +++ b/spring-batch-test/src/main/java/org/springframework/batch/test/JobRepositoryTestUtils.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. @@ -20,16 +20,17 @@ import java.util.Collections; import java.util.List; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobInstance; -import org.springframework.batch.core.JobParameter; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.JobParametersIncrementer; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobInstance; +import org.springframework.batch.core.job.parameters.JobParameter; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersIncrementer; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException; import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.repository.JobRestartException; +import org.springframework.dao.OptimisticLockingFailureException; import org.springframework.lang.Nullable; /** @@ -39,6 +40,7 @@ * * @author Dave Syer * @author Mahmoud Ben Hassine + * @author Yanming Zhou */ public class JobRepositoryTestUtils { @@ -136,7 +138,12 @@ public void removeJobExecutions(Collection jobExecutions) { removeJobExecution(jobExecution); } for (JobExecution jobExecution : jobExecutions) { - this.jobRepository.deleteJobInstance(jobExecution.getJobInstance()); + try { + this.jobRepository.deleteJobInstance(jobExecution.getJobInstance()); + } + catch (OptimisticLockingFailureException ignore) { + // same job instance may be already deleted + } } } @@ -159,16 +166,16 @@ public void removeJobExecutions() { for (String jobName : jobNames) { int start = 0; int count = 100; - List jobInstances = this.jobRepository.findJobInstancesByName(jobName, start, count); + List jobInstances = this.jobRepository.getJobInstances(jobName, start, count); while (!jobInstances.isEmpty()) { for (JobInstance jobInstance : jobInstances) { - List jobExecutions = this.jobRepository.findJobExecutions(jobInstance); + List jobExecutions = this.jobRepository.getJobExecutions(jobInstance); if (jobExecutions != null && !jobExecutions.isEmpty()) { removeJobExecutions(jobExecutions); } } start += count; - jobInstances = this.jobRepository.findJobInstancesByName(jobName, start, count); + jobInstances = this.jobRepository.getJobInstances(jobName, start, count); } } } diff --git a/spring-batch-test/src/main/java/org/springframework/batch/test/JobScopeTestExecutionListener.java b/spring-batch-test/src/main/java/org/springframework/batch/test/JobScopeTestExecutionListener.java index 5376d5a87a..d51ad56079 100644 --- a/spring-batch-test/src/main/java/org/springframework/batch/test/JobScopeTestExecutionListener.java +++ b/spring-batch-test/src/main/java/org/springframework/batch/test/JobScopeTestExecutionListener.java @@ -17,7 +17,7 @@ import java.lang.reflect.Method; -import org.springframework.batch.core.JobExecution; +import org.springframework.batch.core.job.JobExecution; import org.springframework.batch.core.scope.context.JobContext; import org.springframework.batch.core.scope.context.JobSynchronizationManager; import org.springframework.batch.item.adapter.HippyMethodInvoker; diff --git a/spring-batch-test/src/main/java/org/springframework/batch/test/JobScopeTestUtils.java b/spring-batch-test/src/main/java/org/springframework/batch/test/JobScopeTestUtils.java index 8c0d4391a0..c9ffc58dd7 100644 --- a/spring-batch-test/src/main/java/org/springframework/batch/test/JobScopeTestUtils.java +++ b/spring-batch-test/src/main/java/org/springframework/batch/test/JobScopeTestUtils.java @@ -17,7 +17,7 @@ import java.util.concurrent.Callable; -import org.springframework.batch.core.JobExecution; +import org.springframework.batch.core.job.JobExecution; import org.springframework.batch.core.scope.JobScope; import org.springframework.batch.core.scope.context.JobSynchronizationManager; diff --git a/spring-batch-test/src/main/java/org/springframework/batch/test/MetaDataInstanceFactory.java b/spring-batch-test/src/main/java/org/springframework/batch/test/MetaDataInstanceFactory.java index 9ade608be6..f506b594cf 100644 --- a/spring-batch-test/src/main/java/org/springframework/batch/test/MetaDataInstanceFactory.java +++ b/spring-batch-test/src/main/java/org/springframework/batch/test/MetaDataInstanceFactory.java @@ -17,10 +17,10 @@ import java.util.Collection; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobInstance; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.JobInstance; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.item.ExecutionContext; /** diff --git a/spring-batch-test/src/main/java/org/springframework/batch/test/StepRunner.java b/spring-batch-test/src/main/java/org/springframework/batch/test/StepRunner.java index 2f1bc86801..673f30d684 100755 --- a/spring-batch-test/src/main/java/org/springframework/batch/test/StepRunner.java +++ b/spring-batch-test/src/main/java/org/springframework/batch/test/StepRunner.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,14 +24,14 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobExecutionListener; -import org.springframework.batch.core.JobParameter; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.JobParametersInvalidException; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.UnexpectedJobExecutionException; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.listener.JobExecutionListener; +import org.springframework.batch.core.job.parameters.JobParameter; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersInvalidException; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.job.UnexpectedJobExecutionException; import org.springframework.batch.core.job.SimpleJob; import org.springframework.batch.core.launch.JobLauncher; import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException; @@ -63,7 +63,12 @@ * @author Mahmoud Ben Hassine * @since 2.0 * @see SimpleJob + * @deprecated since 6.0 in favor of + * {@link JobOperatorTestUtils#startStep(String, JobParameters, ExecutionContext)}. + * Scheduled for removal in 6.2 or later */ +@SuppressWarnings("removal") +@Deprecated(since = "6.0", forRemoval = true) public class StepRunner { /** diff --git a/spring-batch-test/src/main/java/org/springframework/batch/test/StepScopeTestExecutionListener.java b/spring-batch-test/src/main/java/org/springframework/batch/test/StepScopeTestExecutionListener.java index 1deea865bd..8d6a6eea44 100644 --- a/spring-batch-test/src/main/java/org/springframework/batch/test/StepScopeTestExecutionListener.java +++ b/spring-batch-test/src/main/java/org/springframework/batch/test/StepScopeTestExecutionListener.java @@ -17,7 +17,7 @@ import java.lang.reflect.Method; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.scope.context.StepContext; import org.springframework.batch.core.scope.context.StepSynchronizationManager; import org.springframework.batch.item.adapter.HippyMethodInvoker; diff --git a/spring-batch-test/src/main/java/org/springframework/batch/test/StepScopeTestUtils.java b/spring-batch-test/src/main/java/org/springframework/batch/test/StepScopeTestUtils.java index 93e26151f2..839aacb8c7 100644 --- a/spring-batch-test/src/main/java/org/springframework/batch/test/StepScopeTestUtils.java +++ b/spring-batch-test/src/main/java/org/springframework/batch/test/StepScopeTestUtils.java @@ -17,7 +17,7 @@ import java.util.concurrent.Callable; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.scope.StepScope; import org.springframework.batch.core.scope.context.StepSynchronizationManager; diff --git a/spring-batch-test/src/main/java/org/springframework/batch/test/context/BatchTestContextBeanPostProcessor.java b/spring-batch-test/src/main/java/org/springframework/batch/test/context/BatchTestContextBeanPostProcessor.java index ca3046f4df..5eea9814c7 100644 --- a/spring-batch-test/src/main/java/org/springframework/batch/test/context/BatchTestContextBeanPostProcessor.java +++ b/spring-batch-test/src/main/java/org/springframework/batch/test/context/BatchTestContextBeanPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2022 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. @@ -15,10 +15,10 @@ */ package org.springframework.batch.test.context; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.launch.JobOperator; import org.springframework.batch.core.repository.JobRepository; -import org.springframework.batch.test.JobLauncherTestUtils; +import org.springframework.batch.test.JobOperatorTestUtils; import org.springframework.batch.test.JobRepositoryTestUtils; import org.springframework.beans.BeansException; import org.springframework.beans.factory.ObjectProvider; @@ -27,19 +27,20 @@ /** * {@link BeanPostProcessor} implementation that injects a job bean into - * {@link JobLauncherTestUtils} if there is a unique job bean. + * {@link JobOperatorTestUtils} if there is a unique job bean. * * @author Henning Pöttker * @author Mahmoud Ben Hassine * @since 5.0 */ +@SuppressWarnings("removal") public class BatchTestContextBeanPostProcessor implements BeanPostProcessor { private ObjectProvider jobProvider; private ObjectProvider jobRepositoryProvider; - private ObjectProvider jobLauncherProvider; + private ObjectProvider jobOperatorProvider; @Autowired public void setJobProvider(ObjectProvider jobProvider) { @@ -52,16 +53,16 @@ public void setJobRepositoryProvider(ObjectProvider jobRepository } @Autowired - public void setJobLauncherProvider(ObjectProvider jobLauncherProvider) { - this.jobLauncherProvider = jobLauncherProvider; + public void setJobOperatorProvider(ObjectProvider jobOperatorProvider) { + this.jobOperatorProvider = jobOperatorProvider; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { - if (bean instanceof JobLauncherTestUtils jobLauncherTestUtils) { - this.jobProvider.ifUnique(jobLauncherTestUtils::setJob); - this.jobRepositoryProvider.ifUnique(jobLauncherTestUtils::setJobRepository); - this.jobLauncherProvider.ifUnique(jobLauncherTestUtils::setJobLauncher); + if (bean instanceof JobOperatorTestUtils jobOperatorTestUtils) { + this.jobProvider.ifUnique(jobOperatorTestUtils::setJob); + this.jobRepositoryProvider.ifUnique(jobOperatorTestUtils::setJobRepository); + this.jobOperatorProvider.ifUnique(jobOperatorTestUtils::setJobOperator); } if (bean instanceof JobRepositoryTestUtils jobRepositoryTestUtils) { this.jobRepositoryProvider.ifUnique(jobRepositoryTestUtils::setJobRepository); diff --git a/spring-batch-test/src/main/java/org/springframework/batch/test/context/BatchTestContextCustomizer.java b/spring-batch-test/src/main/java/org/springframework/batch/test/context/BatchTestContextCustomizer.java index baeca9ad6a..fec8ec2f72 100644 --- a/spring-batch-test/src/main/java/org/springframework/batch/test/context/BatchTestContextCustomizer.java +++ b/spring-batch-test/src/main/java/org/springframework/batch/test/context/BatchTestContextCustomizer.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. @@ -16,7 +16,7 @@ package org.springframework.batch.test.context; import org.springframework.aot.AotDetector; -import org.springframework.batch.test.JobLauncherTestUtils; +import org.springframework.batch.test.JobOperatorTestUtils; import org.springframework.batch.test.JobRepositoryTestUtils; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.BeanDefinitionRegistry; @@ -28,7 +28,7 @@ /** * {@link ContextCustomizer} implementation that adds batch test utility classes - * ({@link JobLauncherTestUtils} and {@link JobRepositoryTestUtils}) as beans in the test + * ({@link JobOperatorTestUtils} and {@link JobRepositoryTestUtils}) as beans in the test * context. * * @author Mahmoud Ben Hassine, Alexander Arshavskiy @@ -36,7 +36,7 @@ */ public class BatchTestContextCustomizer implements ContextCustomizer { - private static final String JOB_LAUNCHER_TEST_UTILS_BEAN_NAME = "jobLauncherTestUtils"; + private static final String JOB_OPERATOR_TEST_UTILS_BEAN_NAME = "jobOperatorTestUtils"; private static final String JOB_REPOSITORY_TEST_UTILS_BEAN_NAME = "jobRepositoryTestUtils"; @@ -53,8 +53,8 @@ public void customizeContext(ConfigurableApplicationContext context, MergedConte "The bean factory must be an instance of BeanDefinitionRegistry"); BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory; - registry.registerBeanDefinition(JOB_LAUNCHER_TEST_UTILS_BEAN_NAME, - new RootBeanDefinition(JobLauncherTestUtils.class)); + registry.registerBeanDefinition(JOB_OPERATOR_TEST_UTILS_BEAN_NAME, + new RootBeanDefinition(JobOperatorTestUtils.class)); registry.registerBeanDefinition(JOB_REPOSITORY_TEST_UTILS_BEAN_NAME, new RootBeanDefinition(JobRepositoryTestUtils.class)); registry.registerBeanDefinition(BATCH_TEST_CONTEXT_BEAN_POST_PROCESSOR_BEAN_NAME, 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/main/java/org/springframework/batch/test/context/SpringBatchTest.java b/spring-batch-test/src/main/java/org/springframework/batch/test/context/SpringBatchTest.java index a597f95b9b..d12cbf9fc9 100644 --- a/spring-batch-test/src/main/java/org/springframework/batch/test/context/SpringBatchTest.java +++ b/spring-batch-test/src/main/java/org/springframework/batch/test/context/SpringBatchTest.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. @@ -24,7 +24,7 @@ import org.junit.jupiter.api.extension.ExtendWith; -import org.springframework.batch.test.JobLauncherTestUtils; +import org.springframework.batch.test.JobOperatorTestUtils; import org.springframework.batch.test.JobRepositoryTestUtils; import org.springframework.batch.test.JobScopeTestExecutionListener; import org.springframework.batch.test.StepScopeTestExecutionListener; @@ -35,8 +35,8 @@ * Annotation that can be specified on a test class that runs Spring Batch based tests. * Provides the following features over the regular Spring TestContext Framework: *
      - *
    • Registers a {@link JobLauncherTestUtils} bean named "jobLauncherTestUtils" which - * can be used in tests for launching jobs and steps.
    • + *
    • Registers a {@link JobOperatorTestUtils} bean named "jobOperatorTestUtils" which + * can be used in tests for starting jobs and steps.
    • *
    • Registers a {@link JobRepositoryTestUtils} bean named "jobRepositoryTestUtils" * which can be used in tests setup to create or remove job executions.
    • *
    • Registers the {@link StepScopeTestExecutionListener} and @@ -53,7 +53,7 @@ * public class MyBatchJobTests { * * @Autowired - * private JobLauncherTestUtils jobLauncherTestUtils; + * private JobOperatorTestUtils jobOperatorTestUtils; * * @Autowired * private JobRepositoryTestUtils jobRepositoryTestUtils; @@ -64,16 +64,16 @@ * @Before * public void setup() { * this.jobRepositoryTestUtils.removeJobExecutions(); - * this.jobLauncherTestUtils.setJob(this.jobUnderTest); // this is optional if the job is unique + * this.jobOperatorTestUtils.setJob(this.jobUnderTest); // this is optional if the job is unique * } * * @Test * public void testMyJob() throws Exception { * // given - * JobParameters jobParameters = this.jobLauncherTestUtils.getUniqueJobParameters(); + * JobParameters jobParameters = this.jobOperatorTestUtils.getUniqueJobParameters(); * * // when - * JobExecution jobExecution = this.jobLauncherTestUtils.launchJob(jobParameters); + * JobExecution jobExecution = this.jobOperatorTestUtils.startJob(jobParameters); * * // then * Assert.assertEquals(ExitStatus.COMPLETED, jobExecution.getExitStatus()); @@ -92,24 +92,24 @@ * public class MyBatchJobTests { * * @Autowired - * private JobLauncherTestUtils jobLauncherTestUtils; + * private JobOperatorTestUtils jobOperatorTestUtils; * * @Autowired * private JobRepositoryTestUtils jobRepositoryTestUtils; * * @BeforeEach * public void setup(@Autowired Job jobUnderTest) { - * this.jobLauncherTestUtils.setJob(jobUnderTest); // this is optional if the job is unique + * this.jobOperatorTestUtils.setJob(jobUnderTest); // this is optional if the job is unique * this.jobRepositoryTestUtils.removeJobExecutions(); * } * * @Test * public void testMyJob() throws Exception { * // given - * JobParameters jobParameters = this.jobLauncherTestUtils.getUniqueJobParameters(); + * JobParameters jobParameters = this.jobOperatorTestUtils.getUniqueJobParameters(); * * // when - * JobExecution jobExecution = this.jobLauncherTestUtils.launchJob(jobParameters); + * JobExecution jobExecution = this.jobOperatorTestUtils.startJob(jobParameters); * * // then * Assertions.assertEquals(ExitStatus.COMPLETED, jobExecution.getExitStatus()); @@ -120,7 +120,7 @@ * * It should be noted that if the test context contains a single job bean definition, that * is the job under test, then this annotation will set that job in the - * {@link JobLauncherTestUtils} automatically. + * {@link JobOperatorTestUtils} automatically. * * The test context must contain a JobRepository and a * JobLauncher beans for this annotation to properly set up test utilities. @@ -131,7 +131,7 @@ * @author Mahmoud Ben Hassine * @author Taeik Lim * @since 4.1 - * @see JobLauncherTestUtils + * @see JobOperatorTestUtils * @see JobRepositoryTestUtils * @see StepScopeTestExecutionListener * @see JobScopeTestExecutionListener diff --git a/spring-batch-test/src/test/java/org/springframework/batch/test/AbstractSampleJobTests.java b/spring-batch-test/src/test/java/org/springframework/batch/test/AbstractSampleJobTests.java index 0cbc7c2342..715f0ed0ef 100644 --- a/spring-batch-test/src/test/java/org/springframework/batch/test/AbstractSampleJobTests.java +++ b/spring-batch-test/src/test/java/org/springframework/batch/test/AbstractSampleJobTests.java @@ -24,7 +24,7 @@ import org.junit.jupiter.api.RepeatedTest; import org.junit.jupiter.api.Test; import org.springframework.batch.core.BatchStatus; -import org.springframework.batch.core.Job; +import org.springframework.batch.core.job.Job; import org.springframework.batch.item.ExecutionContext; import org.springframework.batch.test.sample.SampleTasklet; import org.springframework.beans.factory.annotation.Autowired; diff --git a/spring-batch-test/src/test/java/org/springframework/batch/test/ExecutionContextTestUtilsTests.java b/spring-batch-test/src/test/java/org/springframework/batch/test/ExecutionContextTestUtilsTests.java index a823b01c77..906fdbd477 100644 --- a/spring-batch-test/src/test/java/org/springframework/batch/test/ExecutionContextTestUtilsTests.java +++ b/spring-batch-test/src/test/java/org/springframework/batch/test/ExecutionContextTestUtilsTests.java @@ -23,8 +23,8 @@ import java.util.Date; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.step.StepExecution; class ExecutionContextTestUtilsTests { diff --git a/spring-batch-test/src/test/java/org/springframework/batch/test/JobLauncherTestUtilsTests.java b/spring-batch-test/src/test/java/org/springframework/batch/test/JobLauncherTestUtilsTests.java index e378cfd92f..60ed6feeb3 100644 --- a/spring-batch-test/src/test/java/org/springframework/batch/test/JobLauncherTestUtilsTests.java +++ b/spring-batch-test/src/test/java/org/springframework/batch/test/JobLauncherTestUtilsTests.java @@ -18,11 +18,12 @@ import org.junit.jupiter.api.Test; import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepContribution; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.step.StepContribution; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; import org.springframework.batch.core.job.builder.JobBuilder; import org.springframework.batch.core.launch.JobLauncher; @@ -78,6 +79,7 @@ void getUniqueJobParameters_doesNotRepeatJobParameters() { @Configuration @EnableBatchProcessing + @EnableJdbcJobRepository static class TestJobConfiguration { @Bean diff --git a/spring-batch-test/src/test/java/org/springframework/batch/test/JobRepositoryTestUtilsTests.java b/spring-batch-test/src/test/java/org/springframework/batch/test/JobRepositoryTestUtilsTests.java index 3887f71ec1..aabad7826b 100644 --- a/spring-batch-test/src/test/java/org/springframework/batch/test/JobRepositoryTestUtilsTests.java +++ b/spring-batch-test/src/test/java/org/springframework/batch/test/JobRepositoryTestUtilsTests.java @@ -25,9 +25,9 @@ import org.junit.jupiter.api.Test; import org.springframework.batch.core.BatchStatus; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.JobParametersBuilder; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; import org.springframework.batch.core.repository.JobRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; diff --git a/spring-batch-test/src/test/java/org/springframework/batch/test/JobScopeTestExecutionListenerIntegrationTests.java b/spring-batch-test/src/test/java/org/springframework/batch/test/JobScopeTestExecutionListenerIntegrationTests.java index d295007f00..6e527486bd 100644 --- a/spring-batch-test/src/test/java/org/springframework/batch/test/JobScopeTestExecutionListenerIntegrationTests.java +++ b/spring-batch-test/src/test/java/org/springframework/batch/test/JobScopeTestExecutionListenerIntegrationTests.java @@ -20,7 +20,7 @@ import static org.junit.jupiter.api.Assertions.assertNull; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.JobExecution; +import org.springframework.batch.core.job.JobExecution; import org.springframework.batch.item.ExecutionContext; import org.springframework.batch.item.ItemReader; import org.springframework.batch.item.ItemStream; diff --git a/spring-batch-test/src/test/java/org/springframework/batch/test/JobScopeTestExecutionListenerTests.java b/spring-batch-test/src/test/java/org/springframework/batch/test/JobScopeTestExecutionListenerTests.java index 61efbf62a8..fa39c9fa94 100644 --- a/spring-batch-test/src/test/java/org/springframework/batch/test/JobScopeTestExecutionListenerTests.java +++ b/spring-batch-test/src/test/java/org/springframework/batch/test/JobScopeTestExecutionListenerTests.java @@ -20,8 +20,8 @@ import static org.junit.jupiter.api.Assertions.assertNull; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobParametersBuilder; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; import org.springframework.batch.core.scope.context.JobContext; import org.springframework.batch.core.scope.context.JobSynchronizationManager; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; diff --git a/spring-batch-test/src/test/java/org/springframework/batch/test/MetaDataInstanceFactoryTests.java b/spring-batch-test/src/test/java/org/springframework/batch/test/MetaDataInstanceFactoryTests.java index 2b6412962e..4f466549bd 100644 --- a/spring-batch-test/src/test/java/org/springframework/batch/test/MetaDataInstanceFactoryTests.java +++ b/spring-batch-test/src/test/java/org/springframework/batch/test/MetaDataInstanceFactoryTests.java @@ -20,7 +20,7 @@ import java.util.List; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.job.parameters.JobParameters; import org.springframework.batch.core.converter.DefaultJobParametersConverter; import org.springframework.batch.support.PropertiesConverter; diff --git a/spring-batch-test/src/test/java/org/springframework/batch/test/SampleStepTests.java b/spring-batch-test/src/test/java/org/springframework/batch/test/SampleStepTests.java index e16e1410a7..0b41737841 100755 --- a/spring-batch-test/src/test/java/org/springframework/batch/test/SampleStepTests.java +++ b/spring-batch-test/src/test/java/org/springframework/batch/test/SampleStepTests.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. @@ -15,14 +15,14 @@ */ package org.springframework.batch.test; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.batch.core.BatchStatus; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.launch.JobOperator; +import org.springframework.batch.core.step.Step; import org.springframework.batch.core.repository.JobRepository; import org.springframework.beans.BeansException; import org.springframework.beans.factory.annotation.Autowired; @@ -38,12 +38,12 @@ class SampleStepTests implements ApplicationContextAware { @Autowired private JdbcTemplate jdbcTemplate; - private StepRunner stepRunner; + private JobOperatorTestUtils jobOperatorTestUtils; private ApplicationContext context; @Autowired - private JobLauncher jobLauncher; + private JobOperator jobOperator; @Autowired private JobRepository jobRepository; @@ -51,7 +51,7 @@ class SampleStepTests implements ApplicationContextAware { @BeforeEach void setUp() { jdbcTemplate.update("create table TESTS (ID integer, NAME varchar(40))"); - stepRunner = new StepRunner(jobLauncher, jobRepository); + jobOperatorTestUtils = new JobOperatorTestUtils(jobOperator, jobRepository); } @AfterEach @@ -61,8 +61,8 @@ void tearDown() { @Test void testTasklet() { - Step step = (Step) context.getBean("s2"); - assertEquals(BatchStatus.COMPLETED, stepRunner.launchStep(step).getStatus()); + Step step = context.getBean("s2", Step.class); + assertEquals(BatchStatus.COMPLETED, jobOperatorTestUtils.startStep(step).getStatus()); assertEquals(2, jdbcTemplate.queryForObject("SELECT ID from TESTS where NAME = 'SampleTasklet2'", Integer.class) .intValue()); } diff --git a/spring-batch-test/src/test/java/org/springframework/batch/test/SpringBatchTestJUnit4Tests.java b/spring-batch-test/src/test/java/org/springframework/batch/test/SpringBatchTestJUnit4Tests.java index dbe3f22da7..1ab1c7d1df 100644 --- a/spring-batch-test/src/test/java/org/springframework/batch/test/SpringBatchTestJUnit4Tests.java +++ b/spring-batch-test/src/test/java/org/springframework/batch/test/SpringBatchTestJUnit4Tests.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. @@ -24,9 +24,10 @@ import org.junit.runner.RunWith; import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; import org.springframework.batch.core.configuration.annotation.JobScope; import org.springframework.batch.core.configuration.annotation.StepScope; @@ -58,7 +59,7 @@ public class SpringBatchTestJUnit4Tests { @Autowired - private JobLauncherTestUtils jobLauncherTestUtils; + private JobOperatorTestUtils jobOperatorTestUtils; @Autowired private JobRepositoryTestUtils jobRepositoryTestUtils; @@ -99,7 +100,7 @@ public void testJobScopedItemReader() throws Exception { public void testJob() throws Exception { // when this.jobRepositoryTestUtils.removeJobExecutions(); - JobExecution jobExecution = this.jobLauncherTestUtils.launchJob(); + JobExecution jobExecution = this.jobOperatorTestUtils.startJob(); // then Assert.assertEquals(ExitStatus.COMPLETED, jobExecution.getExitStatus()); @@ -107,6 +108,7 @@ public void testJob() throws Exception { @Configuration @EnableBatchProcessing + @EnableJdbcJobRepository public static class JobConfiguration { @Bean diff --git a/spring-batch-test/src/test/java/org/springframework/batch/test/SpringBatchTestJUnit5Tests.java b/spring-batch-test/src/test/java/org/springframework/batch/test/SpringBatchTestJUnit5Tests.java index 924779cc11..bd18a3d750 100644 --- a/spring-batch-test/src/test/java/org/springframework/batch/test/SpringBatchTestJUnit5Tests.java +++ b/spring-batch-test/src/test/java/org/springframework/batch/test/SpringBatchTestJUnit5Tests.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2022 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. @@ -22,10 +22,11 @@ import org.junit.jupiter.api.Test; import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; import org.springframework.batch.core.configuration.annotation.JobScope; import org.springframework.batch.core.configuration.annotation.StepScope; @@ -58,7 +59,7 @@ public class SpringBatchTestJUnit5Tests { @Autowired - private JobLauncherTestUtils jobLauncherTestUtils; + private JobOperatorTestUtils jobOperatorTestUtils; @Autowired private JobRepositoryTestUtils jobRepositoryTestUtils; @@ -87,10 +88,10 @@ void testJobScopedItemReader() throws Exception { void testJob() throws Exception { // given this.jobRepositoryTestUtils.removeJobExecutions(); - JobParameters jobParameters = this.jobLauncherTestUtils.getUniqueJobParameters(); + JobParameters jobParameters = this.jobOperatorTestUtils.getUniqueJobParameters(); // when - JobExecution jobExecution = this.jobLauncherTestUtils.launchJob(jobParameters); + JobExecution jobExecution = this.jobOperatorTestUtils.startJob(jobParameters); // then assertEquals(ExitStatus.COMPLETED, jobExecution.getExitStatus()); @@ -110,6 +111,7 @@ JobExecution getJobExecution() { @Configuration @EnableBatchProcessing + @EnableJdbcJobRepository public static class JobConfiguration { @Bean diff --git a/spring-batch-test/src/test/java/org/springframework/batch/test/StepScopeAnnotatedListenerIntegrationTests.java b/spring-batch-test/src/test/java/org/springframework/batch/test/StepScopeAnnotatedListenerIntegrationTests.java index b865a7035f..57b2315fb9 100644 --- a/spring-batch-test/src/test/java/org/springframework/batch/test/StepScopeAnnotatedListenerIntegrationTests.java +++ b/spring-batch-test/src/test/java/org/springframework/batch/test/StepScopeAnnotatedListenerIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2023 the original author or authors. + * Copyright 2014-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,16 +23,17 @@ import org.junit.jupiter.api.Test; import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.launch.JobOperator; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.annotation.AfterStep; import org.springframework.batch.core.annotation.BeforeStep; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; import org.springframework.batch.core.configuration.annotation.StepScope; 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.item.ItemProcessor; @@ -54,15 +55,15 @@ class StepScopeAnnotatedListenerIntegrationTests { @Autowired - JobLauncherTestUtils jobLauncherTestUtils; + JobOperatorTestUtils jobOperatorTestUtils; @Test void test(@Autowired Job job) { // given - this.jobLauncherTestUtils.setJob(job); + this.jobOperatorTestUtils.setJob(job); // when - JobExecution jobExecution = jobLauncherTestUtils.launchStep("step-under-test"); + JobExecution jobExecution = jobOperatorTestUtils.startStep("step-under-test"); // then assertEquals(ExitStatus.COMPLETED, jobExecution.getExitStatus()); @@ -96,17 +97,15 @@ public String read() throws Exception { @Configuration @EnableBatchProcessing + @EnableJdbcJobRepository static class TestConfig { @Autowired private PlatformTransactionManager transactionManager; @Bean - JobLauncherTestUtils jobLauncherTestUtils(JobRepository jobRepository, JobLauncher jobLauncher) { - JobLauncherTestUtils jobLauncherTestUtils = new JobLauncherTestUtils(); - jobLauncherTestUtils.setJobRepository(jobRepository); - jobLauncherTestUtils.setJobLauncher(jobLauncher); - return jobLauncherTestUtils; + JobOperatorTestUtils jobOperatorTestUtils(JobRepository jobRepository, JobOperator jobOperator) { + return new JobOperatorTestUtils(jobOperator, jobRepository); } @Bean diff --git a/spring-batch-test/src/test/java/org/springframework/batch/test/StepScopeTestExecutionListenerIntegrationTests.java b/spring-batch-test/src/test/java/org/springframework/batch/test/StepScopeTestExecutionListenerIntegrationTests.java index 4f3ce40652..d37e58a9bf 100644 --- a/spring-batch-test/src/test/java/org/springframework/batch/test/StepScopeTestExecutionListenerIntegrationTests.java +++ b/spring-batch-test/src/test/java/org/springframework/batch/test/StepScopeTestExecutionListenerIntegrationTests.java @@ -19,7 +19,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.item.ExecutionContext; import org.springframework.batch.item.ItemReader; import org.springframework.batch.item.ItemStream; diff --git a/spring-batch-test/src/test/java/org/springframework/batch/test/StepScopeTestExecutionListenerTests.java b/spring-batch-test/src/test/java/org/springframework/batch/test/StepScopeTestExecutionListenerTests.java index 8e176c5fb6..4afcf653e4 100644 --- a/spring-batch-test/src/test/java/org/springframework/batch/test/StepScopeTestExecutionListenerTests.java +++ b/spring-batch-test/src/test/java/org/springframework/batch/test/StepScopeTestExecutionListenerTests.java @@ -20,9 +20,9 @@ import static org.junit.jupiter.api.Assertions.assertNull; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobParametersBuilder; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.parameters.JobParametersBuilder; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.scope.context.StepContext; import org.springframework.batch.core.scope.context.StepSynchronizationManager; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; diff --git a/spring-batch-test/src/test/java/org/springframework/batch/test/context/BatchTestContextBeanPostProcessorTests.java b/spring-batch-test/src/test/java/org/springframework/batch/test/context/BatchTestContextBeanPostProcessorTests.java index 06e225b2e9..0cf7be2c92 100644 --- a/spring-batch-test/src/test/java/org/springframework/batch/test/context/BatchTestContextBeanPostProcessorTests.java +++ b/spring-batch-test/src/test/java/org/springframework/batch/test/context/BatchTestContextBeanPostProcessorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2022 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. @@ -20,10 +20,10 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobExecution; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; -import org.springframework.batch.test.JobLauncherTestUtils; +import org.springframework.batch.test.JobOperatorTestUtils; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -46,7 +46,7 @@ class BatchTestContextBeanPostProcessorTests { @BeforeEach void setUp() { this.applicationContext = new AnnotationConfigApplicationContext(BatchConfiguration.class); - this.applicationContext.registerBean(JobLauncherTestUtils.class); + this.applicationContext.registerBean(JobOperatorTestUtils.class); } @AfterEach @@ -58,25 +58,25 @@ void tearDown() { @Test void testContextWithoutJobBean() { - var jobLauncherTestUtils = this.applicationContext.getBean(JobLauncherTestUtils.class); - assertNotNull(jobLauncherTestUtils); - assertNull(jobLauncherTestUtils.getJob()); + var jobOperatorTestUtils = this.applicationContext.getBean(JobOperatorTestUtils.class); + assertNotNull(jobOperatorTestUtils); + assertNull(jobOperatorTestUtils.getJob()); } @Test void testContextWithUniqueJobBean() { applicationContext.registerBean(StubJob.class); - var jobLauncherTestUtils = this.applicationContext.getBean(JobLauncherTestUtils.class); - assertNotNull(jobLauncherTestUtils.getJob()); + var jobOperatorTestUtils = this.applicationContext.getBean(JobOperatorTestUtils.class); + assertNotNull(jobOperatorTestUtils.getJob()); } @Test void testContextWithTwoJobBeans() { this.applicationContext.registerBean("jobA", StubJob.class); this.applicationContext.registerBean("jobB", StubJob.class); - var jobLauncherTestUtils = applicationContext.getBean(JobLauncherTestUtils.class); - assertNotNull(jobLauncherTestUtils); - assertNull(jobLauncherTestUtils.getJob()); + var jobOperatorTestUtils = applicationContext.getBean(JobOperatorTestUtils.class); + assertNotNull(jobOperatorTestUtils); + assertNull(jobOperatorTestUtils.getJob()); } static class StubJob implements Job { 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/BatchTestContextCustomizerTests.java b/spring-batch-test/src/test/java/org/springframework/batch/test/context/BatchTestContextCustomizerTests.java index efb96008f6..19111e0216 100644 --- a/spring-batch-test/src/test/java/org/springframework/batch/test/context/BatchTestContextCustomizerTests.java +++ b/spring-batch-test/src/test/java/org/springframework/batch/test/context/BatchTestContextCustomizerTests.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. @@ -52,7 +52,7 @@ void testCustomizeContext() { this.contextCustomizer.customizeContext(context, mergedConfig); // then - assertTrue(context.containsBean("jobLauncherTestUtils")); + assertTrue(context.containsBean("jobOperatorTestUtils")); assertTrue(context.containsBean("jobRepositoryTestUtils")); assertTrue(context.containsBean("batchTestContextBeanPostProcessor")); } @@ -83,7 +83,7 @@ void testCustomizeContext_whenUsingAotGeneratedArtifactsBatchTestContextIsNotReg this.contextCustomizer.customizeContext(context, mergedConfig); // then - assertFalse(context.containsBean("jobLauncherTestUtils")); + assertFalse(context.containsBean("jobOperatorTestUtils")); assertFalse(context.containsBean("jobRepositoryTestUtils")); assertFalse(context.containsBean("batchTestContextBeanPostProcessor")); } 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..adbcbeee8d --- /dev/null +++ b/spring-batch-test/src/test/java/org/springframework/batch/test/context/SpringBatchTestIntegrationTests.java @@ -0,0 +1,94 @@ +/* + * 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.core.launch.support.JobOperatorFactoryBean; +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.core.repository.support.ResourcelessJobRepository; +import org.springframework.batch.test.JobOperatorTestUtils; +import org.springframework.batch.test.JobRepositoryTestUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +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 + * @author Mahmoud Ben Hassine + */ +@SpringJUnitConfig +@SpringBatchTest +class SpringBatchTestIntegrationTests { + + @Autowired + ApplicationContext context; + + @Nested + class InnerWithoutSpringBatchTest extends BatchConfiguration { + + @Autowired + ApplicationContext context; + + @Test + void test() { + assertSame(SpringBatchTestIntegrationTests.this.context, context); + assertNotNull(context.getBean(JobOperatorTestUtils.class)); + assertNotNull(context.getBean(JobRepositoryTestUtils.class)); + } + + } + + @Nested + @SpringBatchTest + class InnerWithSpringBatchTest extends BatchConfiguration { + + @Autowired + ApplicationContext context; + + @Test + void test() { + assertSame(SpringBatchTestIntegrationTests.this.context, context); + assertNotNull(context.getBean(JobOperatorTestUtils.class)); + assertNotNull(context.getBean(JobRepositoryTestUtils.class)); + } + + } + + @Configuration + static class BatchConfiguration { + + @Bean + public JobRepository jobRepository() { + return new ResourcelessJobRepository(); + } + + @Bean + public JobOperatorFactoryBean jobOperator(JobRepository jobRepository) throws Exception { + JobOperatorFactoryBean jobOperatorFactoryBean = new JobOperatorFactoryBean(); + jobOperatorFactoryBean.setJobRepository(jobRepository); + return jobOperatorFactoryBean; + } + + } + +} diff --git a/spring-batch-test/src/test/java/org/springframework/batch/test/jmx/StepExecutionApplicationEventAdvice.java b/spring-batch-test/src/test/java/org/springframework/batch/test/jmx/StepExecutionApplicationEventAdvice.java index 3e71868131..98f487f666 100755 --- a/spring-batch-test/src/test/java/org/springframework/batch/test/jmx/StepExecutionApplicationEventAdvice.java +++ b/spring-batch-test/src/test/java/org/springframework/batch/test/jmx/StepExecutionApplicationEventAdvice.java @@ -17,7 +17,7 @@ package org.springframework.batch.test.jmx; import org.aspectj.lang.JoinPoint; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.step.StepExecution; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisherAware; diff --git a/spring-batch-test/src/test/java/org/springframework/batch/test/sample/LoggingTasklet.java b/spring-batch-test/src/test/java/org/springframework/batch/test/sample/LoggingTasklet.java index a3baab9a1b..dbe8e117b3 100644 --- a/spring-batch-test/src/test/java/org/springframework/batch/test/sample/LoggingTasklet.java +++ b/spring-batch-test/src/test/java/org/springframework/batch/test/sample/LoggingTasklet.java @@ -17,7 +17,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.batch.core.StepContribution; +import org.springframework.batch.core.step.StepContribution; import org.springframework.batch.core.scope.context.ChunkContext; import org.springframework.batch.core.step.tasklet.Tasklet; import org.springframework.batch.repeat.RepeatStatus; diff --git a/spring-batch-test/src/test/java/org/springframework/batch/test/sample/SampleTasklet.java b/spring-batch-test/src/test/java/org/springframework/batch/test/sample/SampleTasklet.java index 702c6ac587..595212b7de 100755 --- a/spring-batch-test/src/test/java/org/springframework/batch/test/sample/SampleTasklet.java +++ b/spring-batch-test/src/test/java/org/springframework/batch/test/sample/SampleTasklet.java @@ -15,9 +15,9 @@ */ package org.springframework.batch.test.sample; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.StepContribution; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.step.StepContribution; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.annotation.BeforeStep; import org.springframework.batch.core.scope.context.ChunkContext; import org.springframework.batch.core.step.tasklet.Tasklet; diff --git a/spring-batch-test/src/test/resources/simple-job-launcher-context.xml b/spring-batch-test/src/test/resources/simple-job-launcher-context.xml index 237e3aadd9..499aa0501d 100755 --- a/spring-batch-test/src/test/resources/simple-job-launcher-context.xml +++ b/spring-batch-test/src/test/resources/simple-job-launcher-context.xml @@ -14,8 +14,13 @@ + + + + + class="org.springframework.batch.core.repository.support.JdbcJobRepositoryFactoryBean"> diff --git a/spring-eclipse-code-conventions.xml b/spring-eclipse-code-conventions.xml deleted file mode 100644 index 0ed42acd4b..0000000000 --- a/spring-eclipse-code-conventions.xml +++ /dev/null @@ -1,251 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -