From e84fc1c4c3894cc8fd4c18d88ac955b885ac4222 Mon Sep 17 00:00:00 2001 From: igorcampos-dev Date: Sat, 4 Oct 2025 11:21:23 -0300 Subject: [PATCH 01/16] feat(spring-batch-examples): add base config spring batch examples --- pom.xml | 1 + spring-batch-examples/.gitattributes | 2 + spring-batch-examples/.gitignore | 32 ++ .../.mvn/wrapper/maven-wrapper.properties | 3 + spring-batch-examples/mvnw | 295 ++++++++++++++++++ spring-batch-examples/mvnw.cmd | 189 +++++++++++ spring-batch-examples/pom.xml | 119 +++++++ .../SpringBatchExampleApplication.java | 13 + .../com/io/example/dbReader/async/README.md | 37 +++ .../async/config/AsyncBatchConfig.java | 119 +++++++ .../async/controller/TestController.java | 35 +++ .../async/exception/BusinessException.java | 13 + .../exception/GlobalHandlerException.java | 19 ++ .../io/example/dbReader/async/init/Init.java | 44 +++ .../dbReader/async/job/AsyncBatchJob.java | 22 ++ .../async/listener/LoggingStepListener.java | 27 ++ .../async/mapper/TestEntityToDtoMapper.java | 21 ++ .../dbReader/async/model/dto/TestDto.java | 21 ++ .../async/model/entity/TestEntity.java | 27 ++ .../repository/TestEntityRepository.java | 9 + .../repository/query/TestEntityQuery.java | 27 ++ .../async/service/AsyncBatchService.java | 8 + .../dbReader/async/service/TestService.java | 7 + .../service/impl/AsyncBatchServiceImpl.java | 51 +++ .../async/service/impl/TestServiceImpl.java | 17 + .../example/dbReader/asyncParallel/README.md | 38 +++ .../com/io/example/fileReader/async/README.md | 40 +++ .../fileReader/asyncParallel/README.md | 40 +++ .../resources/application-asyncDbReader.yml | 24 ++ .../SpringBatchExampleApplicationTests.java | 13 + 30 files changed, 1313 insertions(+) create mode 100644 spring-batch-examples/.gitattributes create mode 100644 spring-batch-examples/.gitignore create mode 100644 spring-batch-examples/.mvn/wrapper/maven-wrapper.properties create mode 100755 spring-batch-examples/mvnw create mode 100644 spring-batch-examples/mvnw.cmd create mode 100644 spring-batch-examples/pom.xml create mode 100644 spring-batch-examples/src/main/java/com/io/example/SpringBatchExampleApplication.java create mode 100644 spring-batch-examples/src/main/java/com/io/example/dbReader/async/README.md create mode 100644 spring-batch-examples/src/main/java/com/io/example/dbReader/async/config/AsyncBatchConfig.java create mode 100644 spring-batch-examples/src/main/java/com/io/example/dbReader/async/controller/TestController.java create mode 100644 spring-batch-examples/src/main/java/com/io/example/dbReader/async/exception/BusinessException.java create mode 100644 spring-batch-examples/src/main/java/com/io/example/dbReader/async/exception/GlobalHandlerException.java create mode 100644 spring-batch-examples/src/main/java/com/io/example/dbReader/async/init/Init.java create mode 100644 spring-batch-examples/src/main/java/com/io/example/dbReader/async/job/AsyncBatchJob.java create mode 100644 spring-batch-examples/src/main/java/com/io/example/dbReader/async/listener/LoggingStepListener.java create mode 100644 spring-batch-examples/src/main/java/com/io/example/dbReader/async/mapper/TestEntityToDtoMapper.java create mode 100644 spring-batch-examples/src/main/java/com/io/example/dbReader/async/model/dto/TestDto.java create mode 100644 spring-batch-examples/src/main/java/com/io/example/dbReader/async/model/entity/TestEntity.java create mode 100644 spring-batch-examples/src/main/java/com/io/example/dbReader/async/repository/TestEntityRepository.java create mode 100644 spring-batch-examples/src/main/java/com/io/example/dbReader/async/repository/query/TestEntityQuery.java create mode 100644 spring-batch-examples/src/main/java/com/io/example/dbReader/async/service/AsyncBatchService.java create mode 100644 spring-batch-examples/src/main/java/com/io/example/dbReader/async/service/TestService.java create mode 100644 spring-batch-examples/src/main/java/com/io/example/dbReader/async/service/impl/AsyncBatchServiceImpl.java create mode 100644 spring-batch-examples/src/main/java/com/io/example/dbReader/async/service/impl/TestServiceImpl.java create mode 100644 spring-batch-examples/src/main/java/com/io/example/dbReader/asyncParallel/README.md create mode 100644 spring-batch-examples/src/main/java/com/io/example/fileReader/async/README.md create mode 100644 spring-batch-examples/src/main/java/com/io/example/fileReader/asyncParallel/README.md create mode 100644 spring-batch-examples/src/main/resources/application-asyncDbReader.yml create mode 100644 spring-batch-examples/src/test/java/com/io/example/SpringBatchExampleApplicationTests.java diff --git a/pom.xml b/pom.xml index 9cc5fed..cdbe9d0 100644 --- a/pom.xml +++ b/pom.xml @@ -17,6 +17,7 @@ spring-keycloak-example spring-jasper-example spring-oracle-example + spring-batch-examples \ No newline at end of file diff --git a/spring-batch-examples/.gitattributes b/spring-batch-examples/.gitattributes new file mode 100644 index 0000000..3b41682 --- /dev/null +++ b/spring-batch-examples/.gitattributes @@ -0,0 +1,2 @@ +/mvnw text eol=lf +*.cmd text eol=crlf diff --git a/spring-batch-examples/.gitignore b/spring-batch-examples/.gitignore new file mode 100644 index 0000000..a71cd2b --- /dev/null +++ b/spring-batch-examples/.gitignore @@ -0,0 +1,32 @@ +target/ +.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/spring-batch-examples/.mvn/wrapper/maven-wrapper.properties b/spring-batch-examples/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..c0bcafe --- /dev/null +++ b/spring-batch-examples/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,3 @@ +wrapperVersion=3.3.4 +distributionType=only-script +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.11/apache-maven-3.9.11-bin.zip diff --git a/spring-batch-examples/mvnw b/spring-batch-examples/mvnw new file mode 100755 index 0000000..bd8896b --- /dev/null +++ b/spring-batch-examples/mvnw @@ -0,0 +1,295 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.3.4 +# +# Optional ENV vars +# ----------------- +# JAVA_HOME - location of a JDK home dir, required when download maven via java source +# MVNW_REPOURL - repo url base for downloading maven distribution +# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output +# ---------------------------------------------------------------------------- + +set -euf +[ "${MVNW_VERBOSE-}" != debug ] || set -x + +# OS specific support. +native_path() { printf %s\\n "$1"; } +case "$(uname)" in +CYGWIN* | MINGW*) + [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" + native_path() { cygpath --path --windows "$1"; } + ;; +esac + +# set JAVACMD and JAVACCMD +set_java_home() { + # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched + if [ -n "${JAVA_HOME-}" ]; then + if [ -x "$JAVA_HOME/jre/sh/java" ]; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACCMD="$JAVA_HOME/jre/sh/javac" + else + JAVACMD="$JAVA_HOME/bin/java" + JAVACCMD="$JAVA_HOME/bin/javac" + + if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then + echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 + echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 + return 1 + fi + fi + else + JAVACMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v java + )" || : + JAVACCMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v javac + )" || : + + if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then + echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 + return 1 + fi + fi +} + +# hash string like Java String::hashCode +hash_string() { + str="${1:-}" h=0 + while [ -n "$str" ]; do + char="${str%"${str#?}"}" + h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) + str="${str#?}" + done + printf %x\\n $h +} + +verbose() { :; } +[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } + +die() { + printf %s\\n "$1" >&2 + exit 1 +} + +trim() { + # MWRAPPER-139: + # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. + # Needed for removing poorly interpreted newline sequences when running in more + # exotic environments such as mingw bash on Windows. + printf "%s" "${1}" | tr -d '[:space:]' +} + +scriptDir="$(dirname "$0")" +scriptName="$(basename "$0")" + +# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties +while IFS="=" read -r key value; do + case "${key-}" in + distributionUrl) distributionUrl=$(trim "${value-}") ;; + distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; + esac +done <"$scriptDir/.mvn/wrapper/maven-wrapper.properties" +[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" + +case "${distributionUrl##*/}" in +maven-mvnd-*bin.*) + MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ + case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in + *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; + :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; + :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; + :Linux*x86_64*) distributionPlatform=linux-amd64 ;; + *) + echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 + distributionPlatform=linux-amd64 + ;; + esac + distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" + ;; +maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; +*) MVN_CMD="mvn${scriptName#mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; +esac + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" +distributionUrlName="${distributionUrl##*/}" +distributionUrlNameMain="${distributionUrlName%.*}" +distributionUrlNameMain="${distributionUrlNameMain%-bin}" +MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" +MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" + +exec_maven() { + unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : + exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" +} + +if [ -d "$MAVEN_HOME" ]; then + verbose "found existing MAVEN_HOME at $MAVEN_HOME" + exec_maven "$@" +fi + +case "${distributionUrl-}" in +*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; +*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; +esac + +# prepare tmp dir +if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then + clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } + trap clean HUP INT TERM EXIT +else + die "cannot create temp dir" +fi + +mkdir -p -- "${MAVEN_HOME%/*}" + +# Download and Install Apache Maven +verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +verbose "Downloading from: $distributionUrl" +verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +# select .zip or .tar.gz +if ! command -v unzip >/dev/null; then + distributionUrl="${distributionUrl%.zip}.tar.gz" + distributionUrlName="${distributionUrl##*/}" +fi + +# verbose opt +__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' +[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v + +# normalize http auth +case "${MVNW_PASSWORD:+has-password}" in +'') MVNW_USERNAME='' MVNW_PASSWORD='' ;; +has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; +esac + +if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then + verbose "Found wget ... using wget" + wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" +elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then + verbose "Found curl ... using curl" + curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" +elif set_java_home; then + verbose "Falling back to use Java to download" + javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" + targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" + cat >"$javaSource" <<-END + public class Downloader extends java.net.Authenticator + { + protected java.net.PasswordAuthentication getPasswordAuthentication() + { + return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); + } + public static void main( String[] args ) throws Exception + { + setDefault( new Downloader() ); + java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); + } + } + END + # For Cygwin/MinGW, switch paths to Windows format before running javac and java + verbose " - Compiling Downloader.java ..." + "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" + verbose " - Running Downloader.java ..." + "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" +fi + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +if [ -n "${distributionSha256Sum-}" ]; then + distributionSha256Result=false + if [ "$MVN_CMD" = mvnd.sh ]; then + echo "Checksum validation is not supported for maven-mvnd." >&2 + echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + elif command -v sha256sum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c - >/dev/null 2>&1; then + distributionSha256Result=true + fi + elif command -v shasum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 + echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + fi + if [ $distributionSha256Result = false ]; then + echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 + echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 + exit 1 + fi +fi + +# unzip and move +if command -v unzip >/dev/null; then + unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" +else + tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" +fi + +# Find the actual extracted directory name (handles snapshots where filename != directory name) +actualDistributionDir="" + +# First try the expected directory name (for regular distributions) +if [ -d "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" ]; then + if [ -f "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/bin/$MVN_CMD" ]; then + actualDistributionDir="$distributionUrlNameMain" + fi +fi + +# If not found, search for any directory with the Maven executable (for snapshots) +if [ -z "$actualDistributionDir" ]; then + # enable globbing to iterate over items + set +f + for dir in "$TMP_DOWNLOAD_DIR"/*; do + if [ -d "$dir" ]; then + if [ -f "$dir/bin/$MVN_CMD" ]; then + actualDistributionDir="$(basename "$dir")" + break + fi + fi + done + set -f +fi + +if [ -z "$actualDistributionDir" ]; then + verbose "Contents of $TMP_DOWNLOAD_DIR:" + verbose "$(ls -la "$TMP_DOWNLOAD_DIR")" + die "Could not find Maven distribution directory in extracted archive" +fi + +verbose "Found extracted Maven distribution directory: $actualDistributionDir" +printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$actualDistributionDir/mvnw.url" +mv -- "$TMP_DOWNLOAD_DIR/$actualDistributionDir" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" + +clean || : +exec_maven "$@" diff --git a/spring-batch-examples/mvnw.cmd b/spring-batch-examples/mvnw.cmd new file mode 100644 index 0000000..92450f9 --- /dev/null +++ b/spring-batch-examples/mvnw.cmd @@ -0,0 +1,189 @@ +<# : batch portion +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.3.4 +@REM +@REM Optional ENV vars +@REM MVNW_REPOURL - repo url base for downloading maven distribution +@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output +@REM ---------------------------------------------------------------------------- + +@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) +@SET __MVNW_CMD__= +@SET __MVNW_ERROR__= +@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% +@SET PSModulePath= +@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( + IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) +) +@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% +@SET __MVNW_PSMODULEP_SAVE= +@SET __MVNW_ARG0_NAME__= +@SET MVNW_USERNAME= +@SET MVNW_PASSWORD= +@IF NOT "%__MVNW_CMD__%"=="" ("%__MVNW_CMD__%" %*) +@echo Cannot start maven from wrapper >&2 && exit /b 1 +@GOTO :EOF +: end batch / begin powershell #> + +$ErrorActionPreference = "Stop" +if ($env:MVNW_VERBOSE -eq "true") { + $VerbosePreference = "Continue" +} + +# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties +$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl +if (!$distributionUrl) { + Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" +} + +switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { + "maven-mvnd-*" { + $USE_MVND = $true + $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" + $MVN_CMD = "mvnd.cmd" + break + } + default { + $USE_MVND = $false + $MVN_CMD = $script -replace '^mvnw','mvn' + break + } +} + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +if ($env:MVNW_REPOURL) { + $MVNW_REPO_PATTERN = if ($USE_MVND -eq $False) { "/org/apache/maven/" } else { "/maven/mvnd/" } + $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace "^.*$MVNW_REPO_PATTERN",'')" +} +$distributionUrlName = $distributionUrl -replace '^.*/','' +$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' + +$MAVEN_M2_PATH = "$HOME/.m2" +if ($env:MAVEN_USER_HOME) { + $MAVEN_M2_PATH = "$env:MAVEN_USER_HOME" +} + +if (-not (Test-Path -Path $MAVEN_M2_PATH)) { + New-Item -Path $MAVEN_M2_PATH -ItemType Directory | Out-Null +} + +$MAVEN_WRAPPER_DISTS = $null +if ((Get-Item $MAVEN_M2_PATH).Target[0] -eq $null) { + $MAVEN_WRAPPER_DISTS = "$MAVEN_M2_PATH/wrapper/dists" +} else { + $MAVEN_WRAPPER_DISTS = (Get-Item $MAVEN_M2_PATH).Target[0] + "/wrapper/dists" +} + +$MAVEN_HOME_PARENT = "$MAVEN_WRAPPER_DISTS/$distributionUrlNameMain" +$MAVEN_HOME_NAME = ([System.Security.Cryptography.SHA256]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' +$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" + +if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { + Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" + Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" + exit $? +} + +if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { + Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" +} + +# prepare tmp dir +$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile +$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" +$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null +trap { + if ($TMP_DOWNLOAD_DIR.Exists) { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } + } +} + +New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null + +# Download and Install Apache Maven +Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +Write-Verbose "Downloading from: $distributionUrl" +Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +$webclient = New-Object System.Net.WebClient +if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { + $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) +} +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum +if ($distributionSha256Sum) { + if ($USE_MVND) { + Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." + } + Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash + if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { + Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." + } +} + +# unzip and move +Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null + +# Find the actual extracted directory name (handles snapshots where filename != directory name) +$actualDistributionDir = "" + +# First try the expected directory name (for regular distributions) +$expectedPath = Join-Path "$TMP_DOWNLOAD_DIR" "$distributionUrlNameMain" +$expectedMvnPath = Join-Path "$expectedPath" "bin/$MVN_CMD" +if ((Test-Path -Path $expectedPath -PathType Container) -and (Test-Path -Path $expectedMvnPath -PathType Leaf)) { + $actualDistributionDir = $distributionUrlNameMain +} + +# If not found, search for any directory with the Maven executable (for snapshots) +if (!$actualDistributionDir) { + Get-ChildItem -Path "$TMP_DOWNLOAD_DIR" -Directory | ForEach-Object { + $testPath = Join-Path $_.FullName "bin/$MVN_CMD" + if (Test-Path -Path $testPath -PathType Leaf) { + $actualDistributionDir = $_.Name + } + } +} + +if (!$actualDistributionDir) { + Write-Error "Could not find Maven distribution directory in extracted archive" +} + +Write-Verbose "Found extracted Maven distribution directory: $actualDistributionDir" +Rename-Item -Path "$TMP_DOWNLOAD_DIR/$actualDistributionDir" -NewName $MAVEN_HOME_NAME | Out-Null +try { + Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null +} catch { + if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { + Write-Error "fail to move MAVEN_HOME" + } +} finally { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } +} + +Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" diff --git a/spring-batch-examples/pom.xml b/spring-batch-examples/pom.xml new file mode 100644 index 0000000..ffbe344 --- /dev/null +++ b/spring-batch-examples/pom.xml @@ -0,0 +1,119 @@ + + + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 3.5.6 + + + + com.io + spring-batch-examples + 0.0.1-SNAPSHOT + spring-batch-examples + Demo project for Spring Boot + + + 21 + 1.18.40 + 2.3.232 + 3.5.6 + 5.2.3 + 3.14.1 + + + + + + org.springframework.boot + spring-boot-starter-web + ${spring.boot.version} + + + + org.springframework.boot + spring-boot-starter-data-jpa + ${spring.boot.version} + + + + org.springframework.boot + spring-boot-starter-batch + ${spring.boot.version} + + + + org.springframework.batch + spring-batch-integration + ${spring.batch.version} + + + + com.h2database + h2 + runtime + ${h2.version} + + + + org.projectlombok + lombok + true + ${lombok.version} + + + + org.springframework.boot + spring-boot-starter-test + ${spring.boot.version} + test + + + + org.springframework.batch + spring-batch-test + test + ${spring.batch.version} + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven.compiler.plugin.version} + + + + org.projectlombok + lombok + + + + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring.boot.version} + + + + org.projectlombok + lombok + + + + + + + + + diff --git a/spring-batch-examples/src/main/java/com/io/example/SpringBatchExampleApplication.java b/spring-batch-examples/src/main/java/com/io/example/SpringBatchExampleApplication.java new file mode 100644 index 0000000..989dd9a --- /dev/null +++ b/spring-batch-examples/src/main/java/com/io/example/SpringBatchExampleApplication.java @@ -0,0 +1,13 @@ +package com.io.example; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class SpringBatchExampleApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBatchExampleApplication.class, args); + } + +} diff --git a/spring-batch-examples/src/main/java/com/io/example/dbReader/async/README.md b/spring-batch-examples/src/main/java/com/io/example/dbReader/async/README.md new file mode 100644 index 0000000..f165640 --- /dev/null +++ b/spring-batch-examples/src/main/java/com/io/example/dbReader/async/README.md @@ -0,0 +1,37 @@ +# Spring Batch Examples | DB And Async + +This project is a **Spring Boot** application demonstrating a **fully asynchronous Spring Batch job**, designed with a focus on **performance** and **scalability**. + +--- + +## 🚀 Overview + +The example showcases how to configure and run an **asynchronous Spring Batch job** that processes a large dataset efficiently. +The job reads **10,000 records** from a database table, simulating item processing by printing +`"item processed"` for each entry. + +--- + +## ⚙️ How It Works + +- The job leverages Spring Batch’s asynchronous capabilities to read and process data concurrently. +- An **H2 in-memory database** is used to store the sample data. +- The asynchronous behavior is enabled through a specific Spring profile. + +--- + +## 🧩 Technologies Used + +- **Java 21** +- **Spring Batch** +- **Spring Boot** +- **H2 Database** + +--- + +## ▶️ Running the Project + +To run the asynchronous batch job, simply activate the `asyncDbReader` profile: + +```bash +./mvnw spring-boot:run -Dspring-boot.run.profiles=asyncDbReader diff --git a/spring-batch-examples/src/main/java/com/io/example/dbReader/async/config/AsyncBatchConfig.java b/spring-batch-examples/src/main/java/com/io/example/dbReader/async/config/AsyncBatchConfig.java new file mode 100644 index 0000000..794556c --- /dev/null +++ b/spring-batch-examples/src/main/java/com/io/example/dbReader/async/config/AsyncBatchConfig.java @@ -0,0 +1,119 @@ +package com.io.example.dbReader.async.config; + +import com.io.example.dbReader.async.model.dto.TestDto; +import com.io.example.dbReader.async.model.entity.TestEntity; +import com.io.example.dbReader.async.listener.LoggingStepListener; +import com.io.example.dbReader.async.mapper.TestEntityToDtoMapper; +import com.io.example.dbReader.async.service.TestService; +import lombok.RequiredArgsConstructor; +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.repository.JobRepository; +import org.springframework.batch.core.step.builder.StepBuilder; +import org.springframework.batch.integration.async.AsyncItemProcessor; +import org.springframework.batch.integration.async.AsyncItemWriter; +import org.springframework.batch.item.ItemWriter; +import org.springframework.batch.item.database.JdbcPagingItemReader; +import org.springframework.batch.item.database.Order; +import org.springframework.batch.item.database.builder.JdbcPagingItemReaderBuilder; +import org.springframework.batch.item.database.support.H2PagingQueryProvider; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.task.AsyncTaskExecutor; +import org.springframework.core.task.SimpleAsyncTaskExecutor; +import org.springframework.jdbc.core.BeanPropertyRowMapper; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import org.springframework.transaction.PlatformTransactionManager; + +import javax.sql.DataSource; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.Future; + +import static com.io.example.dbReader.async.repository.query.TestEntityQuery.*; + +@Configuration +@RequiredArgsConstructor +public class AsyncBatchConfig { + + private final TestService testService; + + @Bean + public JobLauncher asyncJobLauncher(JobRepository jobRepository) throws Exception { + TaskExecutorJobLauncher jobLauncher = new TaskExecutorJobLauncher(); + jobLauncher.setJobRepository(jobRepository); + jobLauncher.setTaskExecutor(new SimpleAsyncTaskExecutor()); + jobLauncher.afterPropertiesSet(); + return jobLauncher; + } + + @Bean + public AsyncItemProcessor asyncProcessor(AsyncTaskExecutor asyncTaskExecutor) { + AsyncItemProcessor processor = new AsyncItemProcessor<>(); + processor.setDelegate(new TestEntityToDtoMapper()); + processor.setTaskExecutor(asyncTaskExecutor); + return processor; + } + + @Bean + public AsyncItemWriter asyncWriter(ItemWriter itemWriter) { + AsyncItemWriter writer = new AsyncItemWriter<>(); + writer.setDelegate(itemWriter); + return writer; + } + + @Bean + public ItemWriter writerDelegate() { + return items -> items.forEach(testService::print); + } + + @Bean + public AsyncTaskExecutor asyncTaskExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(10); + executor.setMaxPoolSize(50); + executor.setQueueCapacity(500); + executor.setThreadNamePrefix("batch-async-"); + executor.initialize(); + return executor; + } + + @Bean + public Step asyncStep(JobRepository jobRepository, + PlatformTransactionManager transactionManager, + JdbcPagingItemReader asyncReader, + AsyncItemProcessor asyncProcessor, + AsyncItemWriter asyncWriter) { + + return new StepBuilder("asyncStep", jobRepository) + .>chunk(500, transactionManager) + .reader(asyncReader) + .processor(asyncProcessor) + .writer(asyncWriter) + .listener(new LoggingStepListener()) + .build(); + } + + @Bean + public JdbcPagingItemReader asyncReader(DataSource dataSource) { + return new JdbcPagingItemReaderBuilder() + .name("asyncReader") + .dataSource(dataSource) + .queryProvider(createQueryProvider()) + .pageSize(1000) + .rowMapper(new BeanPropertyRowMapper<>(TestEntity.class)) + .build(); + } + + private H2PagingQueryProvider createQueryProvider() { + H2PagingQueryProvider queryProvider = new H2PagingQueryProvider(); + queryProvider.setSelectClause(SELECT_CLAUSE); + queryProvider.setFromClause(FROM_CLAUSE); + Map sortKeys = new HashMap<>(); + sortKeys.put(ORDER_ID, Order.ASCENDING); + queryProvider.setSortKeys(sortKeys); + return queryProvider; + } + +} diff --git a/spring-batch-examples/src/main/java/com/io/example/dbReader/async/controller/TestController.java b/spring-batch-examples/src/main/java/com/io/example/dbReader/async/controller/TestController.java new file mode 100644 index 0000000..2692f31 --- /dev/null +++ b/spring-batch-examples/src/main/java/com/io/example/dbReader/async/controller/TestController.java @@ -0,0 +1,35 @@ +package com.io.example.dbReader.async.controller; + +import com.io.example.dbReader.async.service.AsyncBatchService; +import lombok.RequiredArgsConstructor; +import org.springframework.batch.core.BatchStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/job") +@RequiredArgsConstructor +public class TestController { + + private final AsyncBatchService asyncBatchService; + + @GetMapping("/process") + public ResponseEntity processJob(){ + var response = asyncBatchService.runJob(); + return ResponseEntity.ok(response); + } + + @GetMapping("/{jobId}/status") + public ResponseEntity getJobStatus(@PathVariable Long jobId) { + try { + BatchStatus status = asyncBatchService.getJobStatus(jobId); + return ResponseEntity.ok(status.name()); + } catch (IllegalArgumentException e) { + return ResponseEntity.notFound().build(); + } + } + +} diff --git a/spring-batch-examples/src/main/java/com/io/example/dbReader/async/exception/BusinessException.java b/spring-batch-examples/src/main/java/com/io/example/dbReader/async/exception/BusinessException.java new file mode 100644 index 0000000..c6fcdfa --- /dev/null +++ b/spring-batch-examples/src/main/java/com/io/example/dbReader/async/exception/BusinessException.java @@ -0,0 +1,13 @@ +package com.io.example.dbReader.async.exception; + +public class BusinessException extends RuntimeException { + + public BusinessException(String message, Throwable cause) { + super(message, cause); + } + + public BusinessException(String message) { + super(message); + } + +} diff --git a/spring-batch-examples/src/main/java/com/io/example/dbReader/async/exception/GlobalHandlerException.java b/spring-batch-examples/src/main/java/com/io/example/dbReader/async/exception/GlobalHandlerException.java new file mode 100644 index 0000000..1e4cb93 --- /dev/null +++ b/spring-batch-examples/src/main/java/com/io/example/dbReader/async/exception/GlobalHandlerException.java @@ -0,0 +1,19 @@ +package com.io.example.dbReader.async.exception; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; + +@Slf4j +@ControllerAdvice +public class GlobalHandlerException { + + @ExceptionHandler(BusinessException.class) + public ResponseEntity handleBusinessException(BusinessException ex) { + log.error("BusinessException caught: {}", ex.getMessage()); + return new ResponseEntity<>(ex.getMessage(), HttpStatus.BAD_REQUEST); + } + +} \ No newline at end of file diff --git a/spring-batch-examples/src/main/java/com/io/example/dbReader/async/init/Init.java b/spring-batch-examples/src/main/java/com/io/example/dbReader/async/init/Init.java new file mode 100644 index 0000000..a473ef0 --- /dev/null +++ b/spring-batch-examples/src/main/java/com/io/example/dbReader/async/init/Init.java @@ -0,0 +1,44 @@ +package com.io.example.dbReader.async.init; + +import com.io.example.dbReader.async.model.entity.TestEntity; +import com.io.example.dbReader.async.repository.TestEntityRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.CommandLineRunner; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.Random; +import java.util.stream.IntStream; + +@Slf4j +@Component +@RequiredArgsConstructor +public class Init implements CommandLineRunner { + + private final TestEntityRepository repository; + private final Random random = new Random(); + + @Override + public void run(String... args){ + log.info("Generating 10k random entities..."); + log.info("Saving entities in parallel..."); + this.getRandomEntities() + .parallelStream() + .forEach(repository::save); + log.info("Done!"); + } + + private List getRandomEntities(){ + return IntStream.range(0, 10_000) + .mapToObj(i -> TestEntity.builder() + .fieldA("A" + random.nextInt(10000)) + .fieldB("B" + random.nextInt(10000)) + .fieldC(random.nextInt(1000)) + .fieldD(random.nextDouble() * 1000) + .fieldE(random.nextBoolean()) + .build()) + .toList(); + } + +} diff --git a/spring-batch-examples/src/main/java/com/io/example/dbReader/async/job/AsyncBatchJob.java b/spring-batch-examples/src/main/java/com/io/example/dbReader/async/job/AsyncBatchJob.java new file mode 100644 index 0000000..ff58448 --- /dev/null +++ b/spring-batch-examples/src/main/java/com/io/example/dbReader/async/job/AsyncBatchJob.java @@ -0,0 +1,22 @@ +package com.io.example.dbReader.async.job; + +import lombok.RequiredArgsConstructor; +import org.springframework.batch.core.Job; +import org.springframework.batch.core.Step; +import org.springframework.batch.core.job.builder.JobBuilder; +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.context.annotation.Bean; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class AsyncBatchJob { + + @Bean + public Job job(JobRepository jobRepository, Step asyncStep) { + return new JobBuilder("asyncBatchJob", jobRepository) + .start(asyncStep) + .build(); + } + +} diff --git a/spring-batch-examples/src/main/java/com/io/example/dbReader/async/listener/LoggingStepListener.java b/spring-batch-examples/src/main/java/com/io/example/dbReader/async/listener/LoggingStepListener.java new file mode 100644 index 0000000..ee895e0 --- /dev/null +++ b/spring-batch-examples/src/main/java/com/io/example/dbReader/async/listener/LoggingStepListener.java @@ -0,0 +1,27 @@ +package com.io.example.dbReader.async.listener; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.batch.core.ExitStatus; +import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.StepExecutionListener; + +@Slf4j +public class LoggingStepListener implements StepExecutionListener { + + @Override + public void beforeStep(StepExecution stepExecution) { + log.info("______________ STEP STARTING | StepName: {} | Read count: {} ______________", + stepExecution.getStepName(), + stepExecution.getReadCount()); + } + + @Override + public ExitStatus afterStep(StepExecution stepExecution) { + log.info("______________ STEP FINISHED | StepName: {} | Read count: {} | Write count: {} ______________", + stepExecution.getStepName(), + stepExecution.getReadCount(), + stepExecution.getWriteCount()); + return stepExecution.getExitStatus(); + } + +} diff --git a/spring-batch-examples/src/main/java/com/io/example/dbReader/async/mapper/TestEntityToDtoMapper.java b/spring-batch-examples/src/main/java/com/io/example/dbReader/async/mapper/TestEntityToDtoMapper.java new file mode 100644 index 0000000..13abb14 --- /dev/null +++ b/spring-batch-examples/src/main/java/com/io/example/dbReader/async/mapper/TestEntityToDtoMapper.java @@ -0,0 +1,21 @@ +package com.io.example.dbReader.async.mapper; + +import com.io.example.dbReader.async.model.dto.TestDto; +import com.io.example.dbReader.async.model.entity.TestEntity; +import org.springframework.batch.item.ItemProcessor; + +public class TestEntityToDtoMapper implements ItemProcessor { + + @Override + public TestDto process(TestEntity entity) { + return TestDto.builder() + .id(entity.getId()) + .fieldA(entity.getFieldA()) + .fieldB(entity.getFieldB()) + .fieldC(entity.getFieldC()) + .fieldD(entity.getFieldD()) + .fieldE(entity.isFieldE()) + .build(); + } + +} diff --git a/spring-batch-examples/src/main/java/com/io/example/dbReader/async/model/dto/TestDto.java b/spring-batch-examples/src/main/java/com/io/example/dbReader/async/model/dto/TestDto.java new file mode 100644 index 0000000..da15726 --- /dev/null +++ b/spring-batch-examples/src/main/java/com/io/example/dbReader/async/model/dto/TestDto.java @@ -0,0 +1,21 @@ +package com.io.example.dbReader.async.model.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class TestDto { + + private Long id; + private String fieldA; + private String fieldB; + private int fieldC; + private double fieldD; + private boolean fieldE; + +} \ No newline at end of file diff --git a/spring-batch-examples/src/main/java/com/io/example/dbReader/async/model/entity/TestEntity.java b/spring-batch-examples/src/main/java/com/io/example/dbReader/async/model/entity/TestEntity.java new file mode 100644 index 0000000..9aa3298 --- /dev/null +++ b/spring-batch-examples/src/main/java/com/io/example/dbReader/async/model/entity/TestEntity.java @@ -0,0 +1,27 @@ +package com.io.example.dbReader.async.model.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Table(name = "test_entity") +@Entity +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class TestEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String fieldA; + private String fieldB; + private int fieldC; + private double fieldD; + private boolean fieldE; + +} \ No newline at end of file diff --git a/spring-batch-examples/src/main/java/com/io/example/dbReader/async/repository/TestEntityRepository.java b/spring-batch-examples/src/main/java/com/io/example/dbReader/async/repository/TestEntityRepository.java new file mode 100644 index 0000000..8fe873c --- /dev/null +++ b/spring-batch-examples/src/main/java/com/io/example/dbReader/async/repository/TestEntityRepository.java @@ -0,0 +1,9 @@ +package com.io.example.dbReader.async.repository; + +import com.io.example.dbReader.async.model.entity.TestEntity; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface TestEntityRepository extends JpaRepository { +} diff --git a/spring-batch-examples/src/main/java/com/io/example/dbReader/async/repository/query/TestEntityQuery.java b/spring-batch-examples/src/main/java/com/io/example/dbReader/async/repository/query/TestEntityQuery.java new file mode 100644 index 0000000..3a65cba --- /dev/null +++ b/spring-batch-examples/src/main/java/com/io/example/dbReader/async/repository/query/TestEntityQuery.java @@ -0,0 +1,27 @@ +package com.io.example.dbReader.async.repository.query; + +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +public class TestEntityQuery { + + public static final String SELECT_CLAUSE = """ + entity.id, + entity.fieldA, + entity.fieldB, + entity.fieldC, + entity.fieldD, + entity.fieldE + """; + + public static final String FROM_CLAUSE = """ + test_entity entity + """; + + public static final String WHERE_CLAUSE = """ + """; + + public static final String ORDER_ID = "id"; + +} diff --git a/spring-batch-examples/src/main/java/com/io/example/dbReader/async/service/AsyncBatchService.java b/spring-batch-examples/src/main/java/com/io/example/dbReader/async/service/AsyncBatchService.java new file mode 100644 index 0000000..c757a0b --- /dev/null +++ b/spring-batch-examples/src/main/java/com/io/example/dbReader/async/service/AsyncBatchService.java @@ -0,0 +1,8 @@ +package com.io.example.dbReader.async.service; + +import org.springframework.batch.core.BatchStatus; + +public interface AsyncBatchService { + Long runJob(); + BatchStatus getJobStatus(Long jobId); +} diff --git a/spring-batch-examples/src/main/java/com/io/example/dbReader/async/service/TestService.java b/spring-batch-examples/src/main/java/com/io/example/dbReader/async/service/TestService.java new file mode 100644 index 0000000..0e6d2f8 --- /dev/null +++ b/spring-batch-examples/src/main/java/com/io/example/dbReader/async/service/TestService.java @@ -0,0 +1,7 @@ +package com.io.example.dbReader.async.service; + +import com.io.example.dbReader.async.model.dto.TestDto; + +public interface TestService { + void print(TestDto testDto); +} diff --git a/spring-batch-examples/src/main/java/com/io/example/dbReader/async/service/impl/AsyncBatchServiceImpl.java b/spring-batch-examples/src/main/java/com/io/example/dbReader/async/service/impl/AsyncBatchServiceImpl.java new file mode 100644 index 0000000..05fa61f --- /dev/null +++ b/spring-batch-examples/src/main/java/com/io/example/dbReader/async/service/impl/AsyncBatchServiceImpl.java @@ -0,0 +1,51 @@ +package com.io.example.dbReader.async.service.impl; + +import com.io.example.dbReader.async.exception.BusinessException; +import com.io.example.dbReader.async.service.AsyncBatchService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.batch.core.*; +import org.springframework.batch.core.explore.JobExplorer; +import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.stereotype.Service; + +@Service +@Slf4j +@RequiredArgsConstructor +public class AsyncBatchServiceImpl implements AsyncBatchService { + + private final JobLauncher asyncJobLauncher; + private final Job asyncBatchJob; + private final JobExplorer jobExplorer; + + @Override + public Long runJob() { + String jobName = asyncBatchJob.getName(); + var parameters = getParameters(); + try { + log.info("Starting async execution of job: {}", jobName); + JobExecution jobExecution = asyncJobLauncher.run(asyncBatchJob, parameters); + log.info("Job {} started with status: {}", jobName, jobExecution.getStatus()); + return jobExecution.getId(); + } catch (Exception e) { + log.error("Error executing job {} asynchronously", jobName, e); + throw new BusinessException(e.getMessage(), e); + } + } + + @Override + public BatchStatus getJobStatus(Long jobId) { + JobExecution jobExecution = jobExplorer.getJobExecution(jobId); + if (jobExecution == null) { + throw new BusinessException("JobExecution não encontrado para id: " + jobId); + } + return jobExecution.getStatus(); + } + + private JobParameters getParameters(){ + return new JobParametersBuilder() + .addLong("time", System.currentTimeMillis()) + .toJobParameters(); + } + +} diff --git a/spring-batch-examples/src/main/java/com/io/example/dbReader/async/service/impl/TestServiceImpl.java b/spring-batch-examples/src/main/java/com/io/example/dbReader/async/service/impl/TestServiceImpl.java new file mode 100644 index 0000000..d61fb9c --- /dev/null +++ b/spring-batch-examples/src/main/java/com/io/example/dbReader/async/service/impl/TestServiceImpl.java @@ -0,0 +1,17 @@ +package com.io.example.dbReader.async.service.impl; + +import com.io.example.dbReader.async.model.dto.TestDto; +import com.io.example.dbReader.async.service.TestService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +@Slf4j +@Service +public class TestServiceImpl implements TestService { + + @Override + public void print(TestDto dto) { + log.info("Processed: {}", dto); + } + +} diff --git a/spring-batch-examples/src/main/java/com/io/example/dbReader/asyncParallel/README.md b/spring-batch-examples/src/main/java/com/io/example/dbReader/asyncParallel/README.md new file mode 100644 index 0000000..74b5d33 --- /dev/null +++ b/spring-batch-examples/src/main/java/com/io/example/dbReader/asyncParallel/README.md @@ -0,0 +1,38 @@ +# Spring Batch Examples | DB And Async Parallel + +This project is a **Spring Boot** application demonstrating a **fully asynchronous and parallel Spring Batch job**, designed with a focus on **performance** and **scalability**. + +--- + +## 🚀 Overview + +The example showcases how to configure and run an **asynchronous Spring Batch job** that executes **multiple steps in parallel**, processing a large dataset efficiently. +The job reads **10,000 records** from a database table, simulating item processing by printing +`"item processed"` for each entry. + +--- + +## ⚙️ How It Works + +* The job leverages Spring Batch’s **asynchronous and parallel capabilities** to execute multiple steps concurrently and process data efficiently. +* An **H2 in-memory database** is used to store the sample data. +* The parallel execution is enabled through a specific Spring profile. + +--- + +## 🧩 Technologies Used + +* **Java 21** +* **Spring Batch** +* **Spring Boot** +* **H2 Database** + +--- + +## ▶️ Running the Project + +To run the asynchronous and parallel batch job, simply activate the `asyncDbReader` profile: + +```bash +./mvnw spring-boot:run -Dspring-boot.run.profiles=asyncParallelDbReader +``` diff --git a/spring-batch-examples/src/main/java/com/io/example/fileReader/async/README.md b/spring-batch-examples/src/main/java/com/io/example/fileReader/async/README.md new file mode 100644 index 0000000..b47c67e --- /dev/null +++ b/spring-batch-examples/src/main/java/com/io/example/fileReader/async/README.md @@ -0,0 +1,40 @@ +# PENDING + +# Spring Batch Examples | Async CSV Processing + +This project is a **Spring Boot** application demonstrating a **fully asynchronous Spring Batch job**, designed with a focus on **performance** and **scalability**. + +--- + +## 🚀 Overview + +The example showcases how to configure and run an **asynchronous Spring Batch job** that processes a single CSV file efficiently. +The job reads **one CSV file**, simulating item processing by printing +`"item processed"` for each entry. + +--- + +## ⚙️ How It Works + +* The job leverages Spring Batch’s **asynchronous capabilities** to process data efficiently. +* A **CSV file** is used as the input data source. +* The asynchronous execution is enabled through a specific Spring profile. + +--- + +## 🧩 Technologies Used + +* **Java 21** +* **Spring Batch** +* **Spring Boot** +* **CSV file** + +--- + +## ▶️ Running the Project + +To run the asynchronous batch job processing a CSV file, simply activate the `asyncCsvReader` profile: + +```bash +./mvnw spring-boot:run -Dspring-boot.run.profiles=asyncFileReader +``` diff --git a/spring-batch-examples/src/main/java/com/io/example/fileReader/asyncParallel/README.md b/spring-batch-examples/src/main/java/com/io/example/fileReader/asyncParallel/README.md new file mode 100644 index 0000000..b7f1519 --- /dev/null +++ b/spring-batch-examples/src/main/java/com/io/example/fileReader/asyncParallel/README.md @@ -0,0 +1,40 @@ +# PENDING + +# Spring Batch Examples | Async CSV Processing + +This project is a **Spring Boot** application demonstrating a **fully asynchronous and parallel Spring Batch job**, designed with a focus on **performance** and **scalability**. + +--- + +## 🚀 Overview + +The example showcases how to configure and run an **asynchronous Spring Batch job** that executes **multiple steps in parallel**, each processing a different CSV file efficiently. +The job reads **two separate CSV files**, simulating item processing by printing +`"item processed"` for each entry in both files. + +--- + +## ⚙️ How It Works + +* The job leverages Spring Batch’s **asynchronous and parallel capabilities** to execute multiple steps concurrently, processing each CSV file independently and efficiently. +* Two **CSV files** are used as input data sources. +* The parallel execution is enabled through a specific Spring profile. + +--- + +## 🧩 Technologies Used + +* **Java 21** +* **Spring Batch** +* **Spring Boot** +* **CSV files** + +--- + +## ▶️ Running the Project + +To run the asynchronous and parallel batch job processing multiple CSV files, simply activate the `asyncCsvReader` profile: + +```bash +./mvnw spring-boot:run -Dspring-boot.run.profiles=asyncParallelFileReader +``` diff --git a/spring-batch-examples/src/main/resources/application-asyncDbReader.yml b/spring-batch-examples/src/main/resources/application-asyncDbReader.yml new file mode 100644 index 0000000..0150ddd --- /dev/null +++ b/spring-batch-examples/src/main/resources/application-asyncDbReader.yml @@ -0,0 +1,24 @@ +server: + port: 8081 + +spring: + + datasource: + url: jdbc:h2:mem:db + driver-class-name: org.h2.Driver + username: sa + password: + + jpa: + hibernate: + ddl-auto: create-drop + + h2: + console: + enabled: true + path: /h2-console + + batch: + initialize-schema: always + job: + enabled: false diff --git a/spring-batch-examples/src/test/java/com/io/example/SpringBatchExampleApplicationTests.java b/spring-batch-examples/src/test/java/com/io/example/SpringBatchExampleApplicationTests.java new file mode 100644 index 0000000..4ff4059 --- /dev/null +++ b/spring-batch-examples/src/test/java/com/io/example/SpringBatchExampleApplicationTests.java @@ -0,0 +1,13 @@ +package com.io.example; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class SpringBatchExampleApplicationTests { + + @Test + void contextLoads() { + } + +} From 1acac3d99ee669b0c0e57cffc52d3ee5b0156a22 Mon Sep 17 00:00:00 2001 From: igorcampos-dev Date: Sat, 4 Oct 2025 11:23:47 -0300 Subject: [PATCH 02/16] feat(docs): add link to Spring Batch examples in README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index b252b95..bd7549c 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ Below you will find a summary table of each subproject. For more details, please | Name | Description | |---------------------------------------------------------------------------|-----------------------------------------------------------------------------------| +| [Spring Boot + Spring Batch](./spring-batch-examples) | Spring batch examples. | | [Spring Boot + Jasper](./spring-jasper-example) | Demonstrates a basic integration between Spring Boot and Jasper. | | [Spring Boot + Kafka](./spring-kafka-example) | Demonstrates a basic integration between Spring Boot and Apache Kafka. | | [Spring Boot + Keycloak](./spring-keycloak-example) | Demonstrates a basic integration between Spring Boot and Keycloak. | From d574230cdd61cd912cc91668dde4de3b30269803 Mon Sep 17 00:00:00 2001 From: igorcampos-dev Date: Sat, 4 Oct 2025 19:13:26 -0300 Subject: [PATCH 03/16] feat(spring-batch-examples): add unit tests for AsyncBatchService and update error messages to English --- spring-batch-examples/pom.xml | 7 ++ .../service/impl/AsyncBatchServiceImpl.java | 4 +- .../async/controller/TestControllerTest.java | 70 +++++++++++++++++++ .../mapper/TestEntityToDtoMapperTest.java | 32 +++++++++ 4 files changed, 111 insertions(+), 2 deletions(-) create mode 100644 spring-batch-examples/src/test/java/com/io/example/dbReader/async/controller/TestControllerTest.java create mode 100644 spring-batch-examples/src/test/java/com/io/example/dbReader/async/mapper/TestEntityToDtoMapperTest.java diff --git a/spring-batch-examples/pom.xml b/spring-batch-examples/pom.xml index ffbe344..369d48f 100644 --- a/spring-batch-examples/pom.xml +++ b/spring-batch-examples/pom.xml @@ -24,6 +24,7 @@ 3.5.6 5.2.3 3.14.1 + 5.5.1 @@ -66,6 +67,12 @@ ${lombok.version} + + org.instancio + instancio-core + ${instancio.version} + + org.springframework.boot spring-boot-starter-test diff --git a/spring-batch-examples/src/main/java/com/io/example/dbReader/async/service/impl/AsyncBatchServiceImpl.java b/spring-batch-examples/src/main/java/com/io/example/dbReader/async/service/impl/AsyncBatchServiceImpl.java index 05fa61f..4ad4a56 100644 --- a/spring-batch-examples/src/main/java/com/io/example/dbReader/async/service/impl/AsyncBatchServiceImpl.java +++ b/spring-batch-examples/src/main/java/com/io/example/dbReader/async/service/impl/AsyncBatchServiceImpl.java @@ -9,8 +9,8 @@ import org.springframework.batch.core.launch.JobLauncher; import org.springframework.stereotype.Service; -@Service @Slf4j +@Service @RequiredArgsConstructor public class AsyncBatchServiceImpl implements AsyncBatchService { @@ -37,7 +37,7 @@ public Long runJob() { public BatchStatus getJobStatus(Long jobId) { JobExecution jobExecution = jobExplorer.getJobExecution(jobId); if (jobExecution == null) { - throw new BusinessException("JobExecution não encontrado para id: " + jobId); + throw new BusinessException("JobExecution not found for this id: " + jobId); } return jobExecution.getStatus(); } diff --git a/spring-batch-examples/src/test/java/com/io/example/dbReader/async/controller/TestControllerTest.java b/spring-batch-examples/src/test/java/com/io/example/dbReader/async/controller/TestControllerTest.java new file mode 100644 index 0000000..e8062ef --- /dev/null +++ b/spring-batch-examples/src/test/java/com/io/example/dbReader/async/controller/TestControllerTest.java @@ -0,0 +1,70 @@ +package com.io.example.dbReader.async.controller; + +import com.io.example.dbReader.async.exception.BusinessException; +import com.io.example.dbReader.async.service.AsyncBatchService; +import org.instancio.Instancio; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.springframework.batch.core.BatchStatus; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.beans.factory.annotation.Autowired; + +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +@DisplayName("TestController - Unit tests with MockMvc") +@WebMvcTest(TestController.class) +class TestControllerTest { + + private static final Long jobId = Instancio.create(Long.class); + + @Autowired + private MockMvc mockMvc; + + @MockitoBean + private AsyncBatchService asyncBatchService; + + @Test + @DisplayName("GET /job/process → should return job ID when service runs successfully") + void shouldReturnJobIdWhenProcessJobIsCalled() throws Exception { + + when(asyncBatchService.runJob()).thenReturn(jobId); + + mockMvc.perform(get("/job/process") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(content().string(String.valueOf(jobId))); + } + + @ParameterizedTest + @EnumSource(BatchStatus.class) + @DisplayName("GET /job/{jobId}/status → should return correct status for each BatchStatus") + void shouldReturnJobStatusForAllBatchStatuses(BatchStatus status) throws Exception { + + when(asyncBatchService.getJobStatus(jobId)).thenReturn(status); + + mockMvc.perform(get("/job/{jobId}/status", jobId) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(content().string(status.name())); + } + + @Test + @DisplayName("GET /job/{jobId}/status → should return 404 when job ID is invalid") + void shouldReturn404WhenJobIdIsInvalid() throws Exception { + + when(asyncBatchService.getJobStatus(jobId)) + .thenThrow(new BusinessException("JobExecution not found for this id: " + jobId)); + + mockMvc.perform(get("/job/{jobId}/status", jobId) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()); + } + +} diff --git a/spring-batch-examples/src/test/java/com/io/example/dbReader/async/mapper/TestEntityToDtoMapperTest.java b/spring-batch-examples/src/test/java/com/io/example/dbReader/async/mapper/TestEntityToDtoMapperTest.java new file mode 100644 index 0000000..f663956 --- /dev/null +++ b/spring-batch-examples/src/test/java/com/io/example/dbReader/async/mapper/TestEntityToDtoMapperTest.java @@ -0,0 +1,32 @@ +package com.io.example.dbReader.async.mapper; + +import com.io.example.dbReader.async.model.dto.TestDto; +import com.io.example.dbReader.async.model.entity.TestEntity; +import org.instancio.Instancio; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +@ExtendWith(MockitoExtension.class) +public class TestEntityToDtoMapperTest{ + + @InjectMocks + private TestEntityToDtoMapper mapper; + + @Test + @DisplayName("should correctly map TestEntity to TestDto") + void shouldMapEntityToDtoCorrectly() { + + TestEntity entity = Instancio.create(TestEntity.class); + TestDto dto = mapper.process(entity); + + assertThat(dto).usingRecursiveComparison() + .isEqualTo(entity); + + } + +} \ No newline at end of file From 3be0e6d054139cca8ffeee2018e6202757e0141a Mon Sep 17 00:00:00 2001 From: igorcampos-dev Date: Sat, 4 Oct 2025 19:34:09 -0300 Subject: [PATCH 04/16] feat(spring-batch-examples): add batch size configuration and integration tests for async job --- .../async/config/AsyncBatchConfig.java | 8 +- .../resources/application-asyncDbReader.yml | 1 + .../job/AsyncBatchJobIntegrationTest.java | 86 +++++++++++++++++++ .../dbReader/async/util/DataUtils.java | 35 ++++++++ .../resources/application-asyncDbReader.yml | 7 ++ 5 files changed, 135 insertions(+), 2 deletions(-) create mode 100644 spring-batch-examples/src/test/java/com/io/example/dbReader/async/job/AsyncBatchJobIntegrationTest.java create mode 100644 spring-batch-examples/src/test/java/com/io/example/dbReader/async/util/DataUtils.java create mode 100644 spring-batch-examples/src/test/resources/application-asyncDbReader.yml diff --git a/spring-batch-examples/src/main/java/com/io/example/dbReader/async/config/AsyncBatchConfig.java b/spring-batch-examples/src/main/java/com/io/example/dbReader/async/config/AsyncBatchConfig.java index 794556c..d44a5da 100644 --- a/spring-batch-examples/src/main/java/com/io/example/dbReader/async/config/AsyncBatchConfig.java +++ b/spring-batch-examples/src/main/java/com/io/example/dbReader/async/config/AsyncBatchConfig.java @@ -18,6 +18,7 @@ import org.springframework.batch.item.database.Order; import org.springframework.batch.item.database.builder.JdbcPagingItemReaderBuilder; import org.springframework.batch.item.database.support.H2PagingQueryProvider; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.task.AsyncTaskExecutor; @@ -96,12 +97,15 @@ public Step asyncStep(JobRepository jobRepository, } @Bean - public JdbcPagingItemReader asyncReader(DataSource dataSource) { + public JdbcPagingItemReader asyncReader( + DataSource dataSource, + @Value("${spring.batch.batch-size}") int batchSize + ) { return new JdbcPagingItemReaderBuilder() .name("asyncReader") .dataSource(dataSource) .queryProvider(createQueryProvider()) - .pageSize(1000) + .pageSize(batchSize) .rowMapper(new BeanPropertyRowMapper<>(TestEntity.class)) .build(); } diff --git a/spring-batch-examples/src/main/resources/application-asyncDbReader.yml b/spring-batch-examples/src/main/resources/application-asyncDbReader.yml index 0150ddd..13d8554 100644 --- a/spring-batch-examples/src/main/resources/application-asyncDbReader.yml +++ b/spring-batch-examples/src/main/resources/application-asyncDbReader.yml @@ -22,3 +22,4 @@ spring: initialize-schema: always job: enabled: false + batch-size: 1000 diff --git a/spring-batch-examples/src/test/java/com/io/example/dbReader/async/job/AsyncBatchJobIntegrationTest.java b/spring-batch-examples/src/test/java/com/io/example/dbReader/async/job/AsyncBatchJobIntegrationTest.java new file mode 100644 index 0000000..b067070 --- /dev/null +++ b/spring-batch-examples/src/test/java/com/io/example/dbReader/async/job/AsyncBatchJobIntegrationTest.java @@ -0,0 +1,86 @@ +package com.io.example.dbReader.async.job; + +import com.io.example.dbReader.async.model.dto.TestDto; +import com.io.example.dbReader.async.model.entity.TestEntity; +import com.io.example.dbReader.async.service.TestService; +import org.instancio.Instancio; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.batch.core.*; +import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.item.database.JdbcPagingItemReader; +import org.springframework.batch.test.context.SpringBatchTest; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.bean.override.mockito.MockitoBean; + +import static com.io.example.dbReader.async.util.DataUtils.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +@SpringBootTest +@SpringBatchTest +@ActiveProfiles("test") +@DisplayName("AsyncBatchJob - Integration test with real Job execution") +class AsyncBatchJobIntegrationTest { + + @Autowired + private JobLauncher jobLauncher; + + @Autowired + private Job asyncBatchJob; + + @MockitoBean("asyncReader") + private JdbcPagingItemReader asyncReader; + + @MockitoBean + private TestService testService; + + @Test + @DisplayName("Should execute asyncBatchJob and complete successfully with mocked reader") + void shouldExecuteJobSuccessfully() throws Exception { + + configJdbcPagingItemReaderMock(asyncReader); + + JobExecution execution = jobLauncher.run(asyncBatchJob, getParameters()); + + assertThat(execution.getStatus()).isEqualTo(BatchStatus.COMPLETED); + verify(testService, times(5)).print(any(TestDto.class)); + + } + + @Test + @DisplayName("Should fail executing asyncBatchJob when reader throws exception") + void shouldFailJobWhenReaderThrowsException() throws Exception { + + when(asyncReader.read()) + .thenThrow(new RuntimeException("Simulated reader failure")); + + JobExecution execution = jobLauncher.run(asyncBatchJob, getParameters()); + + assertThat(execution.getStatus()).isNotEqualTo(BatchStatus.COMPLETED); + assertThat(execution.getStatus()).isEqualTo(BatchStatus.FAILED); + + verify(testService, never()).print(any(TestDto.class)); + + } + + @Test + @DisplayName("Should execute asyncBatchJob and complete successfully when reader has no data") + void shouldCompleteJobWithNoData() throws Exception { + + when(asyncReader.read()).thenReturn(null); + + JobExecution execution = jobLauncher.run(asyncBatchJob, getParameters()); + + assertThat(execution.getStatus()).isEqualTo(BatchStatus.COMPLETED); + + verify(testService, never()).print(any(TestDto.class)); + + } + + + +} diff --git a/spring-batch-examples/src/test/java/com/io/example/dbReader/async/util/DataUtils.java b/spring-batch-examples/src/test/java/com/io/example/dbReader/async/util/DataUtils.java new file mode 100644 index 0000000..9195dfa --- /dev/null +++ b/spring-batch-examples/src/test/java/com/io/example/dbReader/async/util/DataUtils.java @@ -0,0 +1,35 @@ +package com.io.example.dbReader.async.util; + +import com.io.example.dbReader.async.model.entity.TestEntity; +import lombok.SneakyThrows; +import org.instancio.Instancio; +import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.JobParametersBuilder; +import org.springframework.batch.item.database.JdbcPagingItemReader; + +import static org.mockito.BDDMockito.given; + +public class DataUtils { + + public static JobParameters getParameters(){ + return new JobParametersBuilder() + .addLong("time", System.currentTimeMillis()) + .toJobParameters(); + } + + public static TestEntity createRandomTestEntity(){ + return Instancio.create(TestEntity.class); + } + + @SneakyThrows(Exception.class) + public static void configJdbcPagingItemReaderMock(JdbcPagingItemReader reader){ + given(reader.read()) + .willReturn(createRandomTestEntity()) + .willReturn(createRandomTestEntity()) + .willReturn(createRandomTestEntity()) + .willReturn(createRandomTestEntity()) + .willReturn(createRandomTestEntity()) + .willReturn(null); + } + +} diff --git a/spring-batch-examples/src/test/resources/application-asyncDbReader.yml b/spring-batch-examples/src/test/resources/application-asyncDbReader.yml new file mode 100644 index 0000000..6ec2115 --- /dev/null +++ b/spring-batch-examples/src/test/resources/application-asyncDbReader.yml @@ -0,0 +1,7 @@ +spring: + + batch: + initialize-schema: always + job: + enabled: false + batch-size: 1 From 59acb529ef66e77908eedefe6aa6ef4f4a874aed Mon Sep 17 00:00:00 2001 From: igorcampos-dev Date: Sat, 4 Oct 2025 20:22:50 -0300 Subject: [PATCH 05/16] feat(spring-batch-examples): add unit tests for AsyncBatchServiceImpl and improve test coverage --- .../mapper/TestEntityToDtoMapperTest.java | 1 + .../AsyncBatchServiceImplUnitTest.java | 93 +++++++++++++++++++ 2 files changed, 94 insertions(+) create mode 100644 spring-batch-examples/src/test/java/com/io/example/dbReader/async/service/AsyncBatchServiceImplUnitTest.java diff --git a/spring-batch-examples/src/test/java/com/io/example/dbReader/async/mapper/TestEntityToDtoMapperTest.java b/spring-batch-examples/src/test/java/com/io/example/dbReader/async/mapper/TestEntityToDtoMapperTest.java index f663956..937fad5 100644 --- a/spring-batch-examples/src/test/java/com/io/example/dbReader/async/mapper/TestEntityToDtoMapperTest.java +++ b/spring-batch-examples/src/test/java/com/io/example/dbReader/async/mapper/TestEntityToDtoMapperTest.java @@ -12,6 +12,7 @@ import static org.assertj.core.api.AssertionsForClassTypes.assertThat; @ExtendWith(MockitoExtension.class) +@DisplayName("Unit tests for TestEntityToDtoMapper") public class TestEntityToDtoMapperTest{ @InjectMocks diff --git a/spring-batch-examples/src/test/java/com/io/example/dbReader/async/service/AsyncBatchServiceImplUnitTest.java b/spring-batch-examples/src/test/java/com/io/example/dbReader/async/service/AsyncBatchServiceImplUnitTest.java new file mode 100644 index 0000000..dc3b83b --- /dev/null +++ b/spring-batch-examples/src/test/java/com/io/example/dbReader/async/service/AsyncBatchServiceImplUnitTest.java @@ -0,0 +1,93 @@ +package com.io.example.dbReader.async.service; + +import com.io.example.dbReader.async.exception.BusinessException; +import com.io.example.dbReader.async.service.impl.AsyncBatchServiceImpl; +import org.instancio.Instancio; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +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.explore.JobExplorer; +import org.springframework.batch.core.launch.JobLauncher; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +@DisplayName("Unit tests for AsyncBatchServiceImpl") +class AsyncBatchServiceImplUnitTest { + + private static final Long jobId = Instancio.create(Long.class); + + @Mock + private JobLauncher asyncJobLauncher; + + @Mock + private Job asyncBatchJob; + + @Mock + private JobExplorer jobExplorer; + + @InjectMocks + private AsyncBatchServiceImpl asyncBatchService; + + @Test + @DisplayName("Should run job successfully and return job execution id") + void shouldRunJobSuccessfully() throws Exception { + + JobExecution jobExecutionMock = mock(JobExecution.class); + when(jobExecutionMock.getId()).thenReturn(123L); + when(asyncJobLauncher.run(eq(asyncBatchJob), any(JobParameters.class))) + .thenReturn(jobExecutionMock); + + Long jobId = asyncBatchService.runJob(); + + assertThat(jobId).isEqualTo(123L); + verify(asyncJobLauncher).run(eq(asyncBatchJob), any(JobParameters.class)); + } + + @Test + @DisplayName("Should throw BusinessException when job execution fails") + void shouldThrowBusinessExceptionWhenJobFails() throws Exception { + + when(asyncJobLauncher.run(any(Job.class), any(JobParameters.class))) + .thenThrow(new RuntimeException("Simulated error")); + + BusinessException exception = assertThrows(BusinessException.class, + () -> asyncBatchService.runJob()); + + assertThat(exception.getMessage()).isEqualTo("Simulated error"); + } + + @Test + @DisplayName("Should return job status when job execution exists") + void shouldReturnJobStatus() { + JobExecution jobExecutionMock = mock(JobExecution.class); + when(jobExecutionMock.getStatus()).thenReturn(BatchStatus.COMPLETED); + when(jobExplorer.getJobExecution(jobId)).thenReturn(jobExecutionMock); + + BatchStatus status = asyncBatchService.getJobStatus(jobId); + + assertThat(status).isEqualTo(BatchStatus.COMPLETED); + verify(jobExplorer).getJobExecution(jobId); + } + + @Test + @DisplayName("Should throw BusinessException when job execution not found") + void shouldThrowBusinessExceptionWhenJobNotFound() { + when(jobExplorer.getJobExecution(jobId)).thenReturn(null); + + BusinessException exception = assertThrows(BusinessException.class, + () -> asyncBatchService.getJobStatus(jobId)); + + assertThat(exception.getMessage()) + .isEqualTo("JobExecution not found for this id: " + jobId); + } +} From 28060987aab9af0d0966a75ac8a9acad61fb9d2d Mon Sep 17 00:00:00 2001 From: igorcampos-dev Date: Sun, 12 Oct 2025 13:45:21 -0300 Subject: [PATCH 06/16] feat(spring-batch-examples): implement async file reading with Excel support and refactor batch service --- spring-batch-examples/pom.xml | 13 +++ .../DbReaderAsyncExampleApplication.java} | 6 +- .../async/controller/TestController.java | 8 +- ...hService.java => AsyncDBBatchService.java} | 2 +- .../service/impl/AsyncDBBatchServiceImpl.java | 51 +++++++++++ .../FileReaderAsyncExampleApplication.java | 13 +++ .../async/config/ExcelBatchConfig.java | 85 ++++++++++++++++++ .../async/controller/TestController.java | 35 ++++++++ .../fileReader/async/dto/StudentDto.java | 17 ++++ .../async/exception/BusinessException.java | 13 +++ .../fileReader/async/job/AsyncFileob.java | 22 +++++ .../fileReader/async/mapper/AlunoMapper.java | 20 +++++ .../async/service/AsyncFileBatchService.java | 8 ++ .../fileReader/async/service/TestService.java | 7 ++ .../service/impl/AsyncBatchServiceImpl.java | 14 +-- .../async/service/impl/TestServiceImpl.java | 17 ++++ .../application-asyncExcelFileReader.yml | 25 ++++++ .../src/main/resources/students.xlsx | Bin 0 -> 4955 bytes .../async/controller/TestControllerTest.java | 10 +-- ...a => AsyncDBBatchServiceImplUnitTest.java} | 6 +- 20 files changed, 349 insertions(+), 23 deletions(-) rename spring-batch-examples/src/main/java/com/io/example/{SpringBatchExampleApplication.java => dbReader/async/DbReaderAsyncExampleApplication.java} (55%) rename spring-batch-examples/src/main/java/com/io/example/dbReader/async/service/{AsyncBatchService.java => AsyncDBBatchService.java} (80%) create mode 100644 spring-batch-examples/src/main/java/com/io/example/dbReader/async/service/impl/AsyncDBBatchServiceImpl.java create mode 100644 spring-batch-examples/src/main/java/com/io/example/fileReader/async/FileReaderAsyncExampleApplication.java create mode 100644 spring-batch-examples/src/main/java/com/io/example/fileReader/async/config/ExcelBatchConfig.java create mode 100644 spring-batch-examples/src/main/java/com/io/example/fileReader/async/controller/TestController.java create mode 100644 spring-batch-examples/src/main/java/com/io/example/fileReader/async/dto/StudentDto.java create mode 100644 spring-batch-examples/src/main/java/com/io/example/fileReader/async/exception/BusinessException.java create mode 100644 spring-batch-examples/src/main/java/com/io/example/fileReader/async/job/AsyncFileob.java create mode 100644 spring-batch-examples/src/main/java/com/io/example/fileReader/async/mapper/AlunoMapper.java create mode 100644 spring-batch-examples/src/main/java/com/io/example/fileReader/async/service/AsyncFileBatchService.java create mode 100644 spring-batch-examples/src/main/java/com/io/example/fileReader/async/service/TestService.java rename spring-batch-examples/src/main/java/com/io/example/{dbReader => fileReader}/async/service/impl/AsyncBatchServiceImpl.java (76%) create mode 100644 spring-batch-examples/src/main/java/com/io/example/fileReader/async/service/impl/TestServiceImpl.java create mode 100644 spring-batch-examples/src/main/resources/application-asyncExcelFileReader.yml create mode 100644 spring-batch-examples/src/main/resources/students.xlsx rename spring-batch-examples/src/test/java/com/io/example/dbReader/async/service/{AsyncBatchServiceImplUnitTest.java => AsyncDBBatchServiceImplUnitTest.java} (95%) diff --git a/spring-batch-examples/pom.xml b/spring-batch-examples/pom.xml index 369d48f..db30cee 100644 --- a/spring-batch-examples/pom.xml +++ b/spring-batch-examples/pom.xml @@ -25,6 +25,7 @@ 5.2.3 3.14.1 5.5.1 + 5.4.1 @@ -87,6 +88,18 @@ ${spring.batch.version} + + org.springframework.batch.extensions + spring-batch-excel + 0.1.1 + + + + org.apache.poi + poi-ooxml + ${poi.ooxml.version} + + diff --git a/spring-batch-examples/src/main/java/com/io/example/SpringBatchExampleApplication.java b/spring-batch-examples/src/main/java/com/io/example/dbReader/async/DbReaderAsyncExampleApplication.java similarity index 55% rename from spring-batch-examples/src/main/java/com/io/example/SpringBatchExampleApplication.java rename to spring-batch-examples/src/main/java/com/io/example/dbReader/async/DbReaderAsyncExampleApplication.java index 989dd9a..31b237d 100644 --- a/spring-batch-examples/src/main/java/com/io/example/SpringBatchExampleApplication.java +++ b/spring-batch-examples/src/main/java/com/io/example/dbReader/async/DbReaderAsyncExampleApplication.java @@ -1,13 +1,13 @@ -package com.io.example; +package com.io.example.dbReader.async; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication -public class SpringBatchExampleApplication { +public class DbReaderAsyncExampleApplication { public static void main(String[] args) { - SpringApplication.run(SpringBatchExampleApplication.class, args); + SpringApplication.run(DbReaderAsyncExampleApplication.class, args); } } diff --git a/spring-batch-examples/src/main/java/com/io/example/dbReader/async/controller/TestController.java b/spring-batch-examples/src/main/java/com/io/example/dbReader/async/controller/TestController.java index 2692f31..db5e2a7 100644 --- a/spring-batch-examples/src/main/java/com/io/example/dbReader/async/controller/TestController.java +++ b/spring-batch-examples/src/main/java/com/io/example/dbReader/async/controller/TestController.java @@ -1,6 +1,6 @@ package com.io.example.dbReader.async.controller; -import com.io.example.dbReader.async.service.AsyncBatchService; +import com.io.example.dbReader.async.service.AsyncDBBatchService; import lombok.RequiredArgsConstructor; import org.springframework.batch.core.BatchStatus; import org.springframework.http.ResponseEntity; @@ -14,18 +14,18 @@ @RequiredArgsConstructor public class TestController { - private final AsyncBatchService asyncBatchService; + private final AsyncDBBatchService asyncDBBatchService; @GetMapping("/process") public ResponseEntity processJob(){ - var response = asyncBatchService.runJob(); + var response = asyncDBBatchService.runJob(); return ResponseEntity.ok(response); } @GetMapping("/{jobId}/status") public ResponseEntity getJobStatus(@PathVariable Long jobId) { try { - BatchStatus status = asyncBatchService.getJobStatus(jobId); + BatchStatus status = asyncDBBatchService.getJobStatus(jobId); return ResponseEntity.ok(status.name()); } catch (IllegalArgumentException e) { return ResponseEntity.notFound().build(); diff --git a/spring-batch-examples/src/main/java/com/io/example/dbReader/async/service/AsyncBatchService.java b/spring-batch-examples/src/main/java/com/io/example/dbReader/async/service/AsyncDBBatchService.java similarity index 80% rename from spring-batch-examples/src/main/java/com/io/example/dbReader/async/service/AsyncBatchService.java rename to spring-batch-examples/src/main/java/com/io/example/dbReader/async/service/AsyncDBBatchService.java index c757a0b..ea0c572 100644 --- a/spring-batch-examples/src/main/java/com/io/example/dbReader/async/service/AsyncBatchService.java +++ b/spring-batch-examples/src/main/java/com/io/example/dbReader/async/service/AsyncDBBatchService.java @@ -2,7 +2,7 @@ import org.springframework.batch.core.BatchStatus; -public interface AsyncBatchService { +public interface AsyncDBBatchService { Long runJob(); BatchStatus getJobStatus(Long jobId); } diff --git a/spring-batch-examples/src/main/java/com/io/example/dbReader/async/service/impl/AsyncDBBatchServiceImpl.java b/spring-batch-examples/src/main/java/com/io/example/dbReader/async/service/impl/AsyncDBBatchServiceImpl.java new file mode 100644 index 0000000..5a252e8 --- /dev/null +++ b/spring-batch-examples/src/main/java/com/io/example/dbReader/async/service/impl/AsyncDBBatchServiceImpl.java @@ -0,0 +1,51 @@ +package com.io.example.dbReader.async.service.impl; + +import com.io.example.dbReader.async.exception.BusinessException; +import com.io.example.dbReader.async.service.AsyncDBBatchService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.batch.core.*; +import org.springframework.batch.core.explore.JobExplorer; +import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.stereotype.Service; + +@Slf4j +@Service +@RequiredArgsConstructor +public class AsyncDBBatchServiceImpl implements AsyncDBBatchService { + + private final JobLauncher asyncJobLauncher; + private final Job asyncBatchJob; + private final JobExplorer jobExplorer; + + @Override + public Long runJob() { + String jobName = asyncBatchJob.getName(); + var parameters = getParameters(); + try { + log.info("Starting async execution of job: {}", jobName); + JobExecution jobExecution = asyncJobLauncher.run(asyncBatchJob, parameters); + log.info("Job {} started with status: {}", jobName, jobExecution.getStatus()); + return jobExecution.getId(); + } catch (Exception e) { + log.error("Error executing job {} asynchronously", jobName, e); + throw new BusinessException(e.getMessage(), e); + } + } + + @Override + public BatchStatus getJobStatus(Long jobId) { + JobExecution jobExecution = jobExplorer.getJobExecution(jobId); + if (jobExecution == null) { + throw new BusinessException("JobExecution not found for this id: " + jobId); + } + return jobExecution.getStatus(); + } + + private JobParameters getParameters(){ + return new JobParametersBuilder() + .addLong("time", System.currentTimeMillis()) + .toJobParameters(); + } + +} diff --git a/spring-batch-examples/src/main/java/com/io/example/fileReader/async/FileReaderAsyncExampleApplication.java b/spring-batch-examples/src/main/java/com/io/example/fileReader/async/FileReaderAsyncExampleApplication.java new file mode 100644 index 0000000..c0a87ac --- /dev/null +++ b/spring-batch-examples/src/main/java/com/io/example/fileReader/async/FileReaderAsyncExampleApplication.java @@ -0,0 +1,13 @@ +package com.io.example.fileReader.async; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class FileReaderAsyncExampleApplication { + + public static void main(String[] args) { + SpringApplication.run(FileReaderAsyncExampleApplication.class, args); + } + +} diff --git a/spring-batch-examples/src/main/java/com/io/example/fileReader/async/config/ExcelBatchConfig.java b/spring-batch-examples/src/main/java/com/io/example/fileReader/async/config/ExcelBatchConfig.java new file mode 100644 index 0000000..c2e3603 --- /dev/null +++ b/spring-batch-examples/src/main/java/com/io/example/fileReader/async/config/ExcelBatchConfig.java @@ -0,0 +1,85 @@ +package com.io.example.fileReader.async.config; + +import com.io.example.fileReader.async.service.TestService; +import com.io.example.fileReader.async.dto.StudentDto; +import com.io.example.fileReader.async.mapper.AlunoMapper; +import lombok.RequiredArgsConstructor; +import org.springframework.batch.core.Step; +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.core.step.builder.StepBuilder; +import org.springframework.batch.extensions.excel.poi.PoiItemReader; +import org.springframework.batch.integration.async.AsyncItemProcessor; +import org.springframework.batch.integration.async.AsyncItemWriter; +import org.springframework.batch.item.ItemProcessor; +import org.springframework.batch.item.ItemWriter; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; +import org.springframework.core.io.ClassPathResource; +import org.springframework.batch.core.configuration.annotation.StepScope; +import org.springframework.core.task.SimpleAsyncTaskExecutor; +import org.springframework.transaction.PlatformTransactionManager; +import java.util.concurrent.Future; + +@Configuration +@RequiredArgsConstructor +@Profile("asyncExcelFileReader") +public class ExcelBatchConfig { + + private final TestService testService; + + @Bean + @StepScope + public PoiItemReader excelReader() { + PoiItemReader reader = new PoiItemReader<>(); + reader.setResource(new ClassPathResource("students.xlsx")); + reader.setRowMapper(new AlunoMapper()); + reader.setLinesToSkip(1); + return reader; + } + + @Bean + public AsyncItemProcessor asyncProcessor( + ItemProcessor processor + ) { + AsyncItemProcessor asyncProcessor = new AsyncItemProcessor<>(); + asyncProcessor.setDelegate(processor); + asyncProcessor.setTaskExecutor(new SimpleAsyncTaskExecutor()); + return asyncProcessor; + } + + @Bean + public AsyncItemWriter asyncWriter( + ItemWriter writer + ) { + AsyncItemWriter asyncWriter = new AsyncItemWriter<>(); + asyncWriter.setDelegate(writer); + return asyncWriter; + } + + @Bean + public ItemWriter writer() { + return studentsDto -> studentsDto.forEach(testService::print); + } + + @Bean + public ItemProcessor processor() { + return studentDto -> studentDto; + } + + @Bean + public Step step(JobRepository jobRepository, + PlatformTransactionManager transactionManager, + AsyncItemProcessor processor, + AsyncItemWriter writer, + PoiItemReader reader ) { + + return new StepBuilder("step", jobRepository) + .>chunk(1, transactionManager) + .processor(processor) + .writer(writer) + .reader(reader) + .build(); + } + +} diff --git a/spring-batch-examples/src/main/java/com/io/example/fileReader/async/controller/TestController.java b/spring-batch-examples/src/main/java/com/io/example/fileReader/async/controller/TestController.java new file mode 100644 index 0000000..05a458d --- /dev/null +++ b/spring-batch-examples/src/main/java/com/io/example/fileReader/async/controller/TestController.java @@ -0,0 +1,35 @@ +package com.io.example.fileReader.async.controller; + +import com.io.example.fileReader.async.service.AsyncFileBatchService; +import lombok.RequiredArgsConstructor; +import org.springframework.batch.core.BatchStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/job") +@RequiredArgsConstructor +public class TestController { + + private final AsyncFileBatchService asyncFileBatchService; + + @GetMapping("/process") + public ResponseEntity processJob(){ + var response = asyncFileBatchService.runJob(); + return ResponseEntity.ok(response); + } + + @GetMapping("/{jobId}/status") + public ResponseEntity getJobStatus(@PathVariable Long jobId) { + try { + BatchStatus status = asyncFileBatchService.getJobStatus(jobId); + return ResponseEntity.ok(status.name()); + } catch (IllegalArgumentException e) { + return ResponseEntity.notFound().build(); + } + } + +} diff --git a/spring-batch-examples/src/main/java/com/io/example/fileReader/async/dto/StudentDto.java b/spring-batch-examples/src/main/java/com/io/example/fileReader/async/dto/StudentDto.java new file mode 100644 index 0000000..3426cd4 --- /dev/null +++ b/spring-batch-examples/src/main/java/com/io/example/fileReader/async/dto/StudentDto.java @@ -0,0 +1,17 @@ +package com.io.example.fileReader.async.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import java.time.LocalDate; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class StudentDto { + private String nome; + private String turma; + private LocalDate data; +} diff --git a/spring-batch-examples/src/main/java/com/io/example/fileReader/async/exception/BusinessException.java b/spring-batch-examples/src/main/java/com/io/example/fileReader/async/exception/BusinessException.java new file mode 100644 index 0000000..f62fcf4 --- /dev/null +++ b/spring-batch-examples/src/main/java/com/io/example/fileReader/async/exception/BusinessException.java @@ -0,0 +1,13 @@ +package com.io.example.fileReader.async.exception; + +public class BusinessException extends RuntimeException { + + public BusinessException(String message, Throwable cause) { + super(message, cause); + } + + public BusinessException(String message) { + super(message); + } + +} diff --git a/spring-batch-examples/src/main/java/com/io/example/fileReader/async/job/AsyncFileob.java b/spring-batch-examples/src/main/java/com/io/example/fileReader/async/job/AsyncFileob.java new file mode 100644 index 0000000..d45b695 --- /dev/null +++ b/spring-batch-examples/src/main/java/com/io/example/fileReader/async/job/AsyncFileob.java @@ -0,0 +1,22 @@ +package com.io.example.fileReader.async.job; + +import lombok.RequiredArgsConstructor; +import org.springframework.batch.core.Job; +import org.springframework.batch.core.Step; +import org.springframework.batch.core.job.builder.JobBuilder; +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.context.annotation.Bean; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class AsyncFileob { + + @Bean + public Job excelJob(JobRepository jobRepository, Step step) { + return new JobBuilder("excelJob", jobRepository) + .start(step) + .build(); + } + +} diff --git a/spring-batch-examples/src/main/java/com/io/example/fileReader/async/mapper/AlunoMapper.java b/spring-batch-examples/src/main/java/com/io/example/fileReader/async/mapper/AlunoMapper.java new file mode 100644 index 0000000..92d07aa --- /dev/null +++ b/spring-batch-examples/src/main/java/com/io/example/fileReader/async/mapper/AlunoMapper.java @@ -0,0 +1,20 @@ +package com.io.example.fileReader.async.mapper; + +import com.io.example.fileReader.async.dto.StudentDto; +import org.springframework.batch.extensions.excel.RowMapper; +import org.springframework.batch.extensions.excel.support.rowset.RowSet; +import java.time.LocalDate; + +public class AlunoMapper implements RowMapper { + + @Override + public StudentDto mapRow(RowSet rowSet){ + String[] cells = rowSet.getCurrentRow(); + return StudentDto.builder() + .nome(cells[0]) + .turma(cells[1]) + .data(LocalDate.parse(cells[2])) + .build(); + } + +} diff --git a/spring-batch-examples/src/main/java/com/io/example/fileReader/async/service/AsyncFileBatchService.java b/spring-batch-examples/src/main/java/com/io/example/fileReader/async/service/AsyncFileBatchService.java new file mode 100644 index 0000000..ad26bf4 --- /dev/null +++ b/spring-batch-examples/src/main/java/com/io/example/fileReader/async/service/AsyncFileBatchService.java @@ -0,0 +1,8 @@ +package com.io.example.fileReader.async.service; + +import org.springframework.batch.core.BatchStatus; + +public interface AsyncFileBatchService { + Long runJob(); + BatchStatus getJobStatus(Long jobId); +} diff --git a/spring-batch-examples/src/main/java/com/io/example/fileReader/async/service/TestService.java b/spring-batch-examples/src/main/java/com/io/example/fileReader/async/service/TestService.java new file mode 100644 index 0000000..19100f8 --- /dev/null +++ b/spring-batch-examples/src/main/java/com/io/example/fileReader/async/service/TestService.java @@ -0,0 +1,7 @@ +package com.io.example.fileReader.async.service; + +import com.io.example.fileReader.async.dto.StudentDto; + +public interface TestService { + void print(StudentDto studentDto); +} diff --git a/spring-batch-examples/src/main/java/com/io/example/dbReader/async/service/impl/AsyncBatchServiceImpl.java b/spring-batch-examples/src/main/java/com/io/example/fileReader/async/service/impl/AsyncBatchServiceImpl.java similarity index 76% rename from spring-batch-examples/src/main/java/com/io/example/dbReader/async/service/impl/AsyncBatchServiceImpl.java rename to spring-batch-examples/src/main/java/com/io/example/fileReader/async/service/impl/AsyncBatchServiceImpl.java index 4ad4a56..f50a407 100644 --- a/spring-batch-examples/src/main/java/com/io/example/dbReader/async/service/impl/AsyncBatchServiceImpl.java +++ b/spring-batch-examples/src/main/java/com/io/example/fileReader/async/service/impl/AsyncBatchServiceImpl.java @@ -1,7 +1,7 @@ -package com.io.example.dbReader.async.service.impl; +package com.io.example.fileReader.async.service.impl; -import com.io.example.dbReader.async.exception.BusinessException; -import com.io.example.dbReader.async.service.AsyncBatchService; +import com.io.example.fileReader.async.exception.BusinessException; +import com.io.example.fileReader.async.service.AsyncFileBatchService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.batch.core.*; @@ -12,19 +12,19 @@ @Slf4j @Service @RequiredArgsConstructor -public class AsyncBatchServiceImpl implements AsyncBatchService { +public class AsyncBatchServiceImpl implements AsyncFileBatchService { private final JobLauncher asyncJobLauncher; - private final Job asyncBatchJob; + private final Job excelJob; private final JobExplorer jobExplorer; @Override public Long runJob() { - String jobName = asyncBatchJob.getName(); + String jobName = excelJob.getName(); var parameters = getParameters(); try { log.info("Starting async execution of job: {}", jobName); - JobExecution jobExecution = asyncJobLauncher.run(asyncBatchJob, parameters); + JobExecution jobExecution = asyncJobLauncher.run(excelJob, parameters); log.info("Job {} started with status: {}", jobName, jobExecution.getStatus()); return jobExecution.getId(); } catch (Exception e) { diff --git a/spring-batch-examples/src/main/java/com/io/example/fileReader/async/service/impl/TestServiceImpl.java b/spring-batch-examples/src/main/java/com/io/example/fileReader/async/service/impl/TestServiceImpl.java new file mode 100644 index 0000000..dfed6db --- /dev/null +++ b/spring-batch-examples/src/main/java/com/io/example/fileReader/async/service/impl/TestServiceImpl.java @@ -0,0 +1,17 @@ +package com.io.example.fileReader.async.service.impl; + +import com.io.example.fileReader.async.dto.StudentDto; +import com.io.example.fileReader.async.service.TestService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +@Slf4j +@Service +public class TestServiceImpl implements TestService { + + @Override + public void print(StudentDto studentDto) { + log.info("Processed: {}", studentDto); + } + +} \ No newline at end of file diff --git a/spring-batch-examples/src/main/resources/application-asyncExcelFileReader.yml b/spring-batch-examples/src/main/resources/application-asyncExcelFileReader.yml new file mode 100644 index 0000000..d5dc04b --- /dev/null +++ b/spring-batch-examples/src/main/resources/application-asyncExcelFileReader.yml @@ -0,0 +1,25 @@ +server: + port: 8082 + +spring: + + datasource: + url: jdbc:h2:mem:db + driver-class-name: org.h2.Driver + username: sa + password: + + jpa: + hibernate: + ddl-auto: create-drop + + h2: + console: + enabled: true + path: /h2-console + + batch: + initialize-schema: always + job: + enabled: false + batch-size: 1000 diff --git a/spring-batch-examples/src/main/resources/students.xlsx b/spring-batch-examples/src/main/resources/students.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..530e9744bb5fcfcc0e8fd9ce6b28d02866a6b9e5 GIT binary patch literal 4955 zcmZ`-1yodB*B)9LhHeSTA%`vrNnrp1r9qeh25E*A1e6l#ZlpzG0O^uO=@O9;q!Ez@ z>2K8af4=g+-@WUcv+i2=*>|7k?mAohE(Rt!006iFs6^K=#*x>w3`9Luq6P_SK%K0# zU7eiW_#Zku^LaVgYevc7qYIMUx$1LisgrM4u#_9QS5Za$n2tXNs#vA)q*bCl+L6(T zT6rGNhI_G?h&MzpdWj{D7(hW4UJEfTa->hTEwJ(iNUg;VMGUK|S(HpLzQQd?UDdoK zic-#YC1Nb|f~%b z=|9!Aa&m?J)ORp$SiMVtB-C8V)rkZCL2-RG*XtgMiFR`^)I)6QoS6oGyydXzNHvPH zlagxQdV*aDR!d9SmFNvhrB5muJ@U%c3!8I$$<_Fz{G)+IL+> zq4R{-i_Jj_$4Hh<87_y z@eEEO?dMs&rw?*Jx$@C5&ukulIt-mxno`;H_|8O^jirzC_Pc@~+TY})nq;t&U;_Xd z8~^|riX1O{euOQ|0rqLxgHXhu;*p*iGOCYsgD2i z!UG{TQh5QIBIHgRUV(;LjS(EsDem{44A?pQ^u>+4UgZ6gCJV;2{H&8-t7qo!2f^)g zs8ygAer6zBxe+}bBPa8l)hkTk9~Aw1k+3{8vIp|)4Pn)LC-&Xv)1PHw!yA$eLV=C~ zbOty5hn$L*Ax2?e^g6oQ>}+n|f7ClVrW)|{ySiLMh2igCP&!qpq#& zh`worumkPF(yo_yZU5K{XL`&9ou;gg%me$k`SQ5cdGHbF)X{W#Y~kxjU9ItBzxmB{ z+YG!&W3BN=o^#_-&wI*7ya-!&wL&|~pZAoEAU5Ib92ez}@;4}L6_l4YeSj1-D{|vE z0S3+jWEx08#ynTYV36Zv8x}Q{GIy}6b z72(R-GUrVUBB1U`uf(?9vJ7bkGT>kWKDK;FG8;RvSmKGMD%mAZ!SrRuxZF+xKijE#Y&850 zYY~Eda2-tBu8gE}549bW_PNA5$q>P#kELTkx1oEZ%p3%7+F?YpMWvN>kn$+VTGEKp zy4Fwz*18l2Sr`F(M!g_o)uiW!r6TdN7Vo|d#<~wp#uAannTes^9eNQ%`j#<7Ts>_o z*Tn$yBFn5(t$qDzzx74^laLQ!dr;J|$^@1AGBJD6?F}JeoV+wc+?JsGcG<5(g=Jqc z3gKbxHZaPr_;}%oCG|9Gmp^@sb~QRJ)=~1~X`6&UpWkr7cEf>8&RCrotDjB#_zUCD z%8tfeXXS#^ABFiitsb}zzU$RP#&bn`04md*?`w66Mq~jQBJzp}2oE@2ZEVC8}=q4-H(<-!LI5cL?N{VNt z#5UlK--~hTJlW{>qFt2ugxTPi^1xwjYIO8$tzD-gZ(BNvp*L(Vm-%~6NXl@hVyO3H z^AwxJ9&KTA2m4YskaC?x{l{Gp`2y5}_a)9Vqz?MtJ$(y7;~os2m|S0t2&HxBwPX|X zrO~*hnL=2NBa+LL#)0LYL*pJ7PNtdyGJI)R_AZ-%6hes) zRJBT=Dpb$ir_pU)<5BC<7k{e+t_3DjH!rRBx%&@lM!64*$7!->yT#sx^LrKI6{$7z15?HFCr5Kjr;hPbsu~&I-K1y; zC3MzAdTdH6p=IswaWU9rXoCsw@9dyIg%;8Wq@3cn>=86@Q4E#IO86^*o`pcn#%=-0 z!z&1|n5kTswzuQ$JR?4yf&v|a^n{=vsQMJ2x66QC6{qxW?Uzrp;|I?y>J2v0wz6B? z?euZXOmWy&5yl6?=F#Mq zWrdYxJ=1~&-T?;t1Uc5?^Px&OAXVpY-~ zf>yhU0DNb8`L*t{;$pG_*2EZ`j@!Km4d7Q-GaB$8xM+V9MVlpfVc(NQNme|e(ZXGI={1G^xj!-z;_x1=` z#d27tjl|<#z0ubD&3V#E*T<9PwW>n8`g+)!zYJ@=oB?)+(8C>y`Btv}Gh*sajaxE3 zB)W@LoFBKtv1G*LlF<;U6(>EBVGM(!i4~FZg&1emHF(pBo{gHR*@-$#~qJ?e| zCG^uJY|1r2wuS`LsrN5zr7NUsZB@&Qq|_ zTP4SVJ%^)i@q3n?7&7yVh_m>RsdMyDPFg62h^~N{+@?%!6K1Pm@X_o!#@|(h?|$B_ z4ywF5!UX^be;a`t!rLC^_OpJNyFc^u&tFh{A$}E~BI2U9r>$s_!;9ZU?5A%jLAt)! zw-8tYon8{2?96a1sT z527UvaX1{EhUEo~%KLbPDkY^>jQ2=YvF~2-bo9S^n3TuF8|iL4U=_5p-d?O{r*4s` z)af|xvKS4}q$(to-Z4T4P^XeJ0ghT%dAxQuRyDQRi#; zA~6b%!Qut=2S)lzI!Sseo06&Wnulee-Q*+Xl@J1MBIVO2v+?mlvE?P1m)57FA=-&! z>8kqb$F#AC`4}Dj4Y;mLjJ#&xG($vo)HA8XRdOsyVqc`sGkE@MPE1e1mOPjmqNqzs zhUqIWSNLmn*02IFCmvpVU99pe0s$O6uJ9cO-+h@rr}<*v%c8n7!oO`VIlPI!4P|p= zDEq@lt>&&UdpCZ*pL1qhgJY)vft-K$NR|N32y+-1wo8e5f_6WbcnXLWu9W!}IbGlq zgHPivBMqEogv)J-=hwa{roJG#2RHgqkEU9CD;ho&t57}xViXA-le;rxrC4DEl9l75 zv94=@QWm-8KL|7Vm{}k*!>=Xp6o8b}a8ZD(-USow437B=9q$u}D6OcoCK}UvjjvpK zEU1m>UFNNefj^ysIXMe2;Icgm5RCpt4(jOAk8)g^ghM8bRj^ogeE z;8@d4MIF4xe4CxwV5hsNDb;k^@^RQh7`O+CmjiD85Ap(!Kr`=3GGpXm*XH#IyW2XC&%hpN77Qyrd z^XrFYKGK1F#IGoP6gb*=cyDC!3fg6j4%=GM_Ir!{8cDgYglNLKlbnriF229%0Ke~7p#`a4^QICzRw^G{ z!Y!XCaP~r6Udz|881Fb5)CPw!ejoUZ6`dRrFh_*Bp0_j1&Ge@wmBnj9I#H<_34|Hn z;sY7HETpr*H+4p{(xNT7jJGiwn*l?n)n5WBf;CIMJ~t0%C6|7VCaqY{Je*2k(D5Zl z*T;caGO&1+S2Ihfou}2ZsMo|BlN|T2JkxF`^LowsIAQYxxZ2gJ*lWX^PIE^ywawB{ zI>6CyM2=>;p?4O4Q?YAHY|Jw+TkTZCMOKeUmr`!sVgg1#y&SIuu5Dn zlan-R^O;`kqfwlp#gcs0vXn$?ghE9(0zZ)SaoB4GGp!og&ZISEf>wSseSO6dZ4f#*>|8*&@!>^y* z|G)=OpZ}M0{B;4>E4Tj&2qe7mOTfR@ao43>PyPRuf(FF Date: Sun, 12 Oct 2025 13:48:32 -0300 Subject: [PATCH 07/16] feat(spring-batch-examples): update spring-batch-excel dependency to use property version --- spring-batch-examples/pom.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spring-batch-examples/pom.xml b/spring-batch-examples/pom.xml index db30cee..1d8729d 100644 --- a/spring-batch-examples/pom.xml +++ b/spring-batch-examples/pom.xml @@ -26,6 +26,7 @@ 3.14.1 5.5.1 5.4.1 + 0.1.1 @@ -91,7 +92,7 @@ org.springframework.batch.extensions spring-batch-excel - 0.1.1 + ${spring.batch.excel.version} From 1224604ea916519800ba03251388fc87b901310b Mon Sep 17 00:00:00 2001 From: igorcampos-dev Date: Fri, 17 Oct 2025 23:03:22 -0300 Subject: [PATCH 08/16] feat(spring-batch-examples): refactor package structure, update batch configuration, and add new test files --- spring-batch-examples/pom.xml | 8 ++- .../DbReaderAsyncExampleApplication.java | 2 +- .../io/example/dbReader/{async => }/README.md | 0 .../dbReader/async/service/TestService.java | 7 -- .../example/dbReader/asyncParallel/README.md | 38 ---------- .../BatchConfig.java} | 21 +++--- .../controller/TestController.java | 13 ++-- .../exception/BusinessException.java | 2 +- .../exception/GlobalHandlerException.java | 2 +- .../dbReader/{async => }/init/Init.java | 6 +- .../AsyncBatchJob.java => job/BatchJob.java} | 4 +- .../listener/LoggingStepListener.java | 2 +- .../mapper/TestEntityToDtoMapper.java | 6 +- .../{async => }/model/dto/TestDto.java | 2 +- .../{async => }/model/entity/TestEntity.java | 2 +- .../repository/TestEntityRepository.java | 4 +- .../repository/query/TestEntityQuery.java | 2 +- .../DBBatchService.java} | 4 +- .../example/dbReader/service/TestService.java | 7 ++ .../impl/DBBatchServiceImpl.java} | 8 +-- .../service/impl/TestServiceImpl.java | 6 +- ...java => FileReaderExampleApplication.java} | 6 +- .../example/fileReader/{async => }/README.md | 0 .../fileReader/async/service/TestService.java | 7 -- .../fileReader/asyncParallel/README.md | 40 ----------- .../{async => }/config/ExcelBatchConfig.java | 13 ++-- .../controller/TestController.java | 13 ++-- .../{async => }/dto/StudentDto.java | 2 +- .../exception/BusinessException.java | 2 +- .../exception/GlobalHandlerException.java | 20 ++++++ .../job/AsyncFileob.java => job/FileJob.java} | 4 +- .../StudentMapper.java} | 6 +- .../FileBatchService.java} | 4 +- .../fileReader/service/TestService.java | 7 ++ .../impl/FileBatchServiceImpl.java} | 8 +-- .../service/impl/TestServiceImpl.java | 6 +- ...cDbReader.yml => application-dbReader.yml} | 3 +- ...er.yml => application-excelFileReader.yml} | 0 .../SpringBatchExampleApplicationTests.java | 13 ---- .../controller/TestControllerTest.java | 16 ++--- .../BatchJobIntegrationTest.java} | 19 +++-- .../mapper/TestEntityToDtoMapperTest.java | 6 +- .../DBBatchServiceImplUnitTest.java} | 10 +-- .../dbReader/{async => }/util/DataUtils.java | 4 +- .../controller/TestControllerTest.java | 71 +++++++++++++++++++ .../fileReader/mapper/StudentMapperTest.java | 29 ++++++++ .../resources/application-asyncDbReader.yml | 7 -- .../resources/application-dbReader-test.yml | 26 +++++++ 48 files changed, 272 insertions(+), 216 deletions(-) rename spring-batch-examples/src/main/java/com/io/example/dbReader/{async => }/DbReaderAsyncExampleApplication.java (88%) rename spring-batch-examples/src/main/java/com/io/example/dbReader/{async => }/README.md (100%) delete mode 100644 spring-batch-examples/src/main/java/com/io/example/dbReader/async/service/TestService.java delete mode 100644 spring-batch-examples/src/main/java/com/io/example/dbReader/asyncParallel/README.md rename spring-batch-examples/src/main/java/com/io/example/dbReader/{async/config/AsyncBatchConfig.java => config/BatchConfig.java} (87%) rename spring-batch-examples/src/main/java/com/io/example/dbReader/{async => }/controller/TestController.java (70%) rename spring-batch-examples/src/main/java/com/io/example/dbReader/{async => }/exception/BusinessException.java (83%) rename spring-batch-examples/src/main/java/com/io/example/dbReader/{async => }/exception/GlobalHandlerException.java (92%) rename spring-batch-examples/src/main/java/com/io/example/dbReader/{async => }/init/Init.java (87%) rename spring-batch-examples/src/main/java/com/io/example/dbReader/{async/job/AsyncBatchJob.java => job/BatchJob.java} (89%) rename spring-batch-examples/src/main/java/com/io/example/dbReader/{async => }/listener/LoggingStepListener.java (95%) rename spring-batch-examples/src/main/java/com/io/example/dbReader/{async => }/mapper/TestEntityToDtoMapper.java (76%) rename spring-batch-examples/src/main/java/com/io/example/dbReader/{async => }/model/dto/TestDto.java (87%) rename spring-batch-examples/src/main/java/com/io/example/dbReader/{async => }/model/entity/TestEntity.java (90%) rename spring-batch-examples/src/main/java/com/io/example/dbReader/{async => }/repository/TestEntityRepository.java (65%) rename spring-batch-examples/src/main/java/com/io/example/dbReader/{async => }/repository/query/TestEntityQuery.java (91%) rename spring-batch-examples/src/main/java/com/io/example/dbReader/{async/service/AsyncDBBatchService.java => service/DBBatchService.java} (57%) create mode 100644 spring-batch-examples/src/main/java/com/io/example/dbReader/service/TestService.java rename spring-batch-examples/src/main/java/com/io/example/dbReader/{async/service/impl/AsyncDBBatchServiceImpl.java => service/impl/DBBatchServiceImpl.java} (86%) rename spring-batch-examples/src/main/java/com/io/example/dbReader/{async => }/service/impl/TestServiceImpl.java (60%) rename spring-batch-examples/src/main/java/com/io/example/fileReader/{async/FileReaderAsyncExampleApplication.java => FileReaderExampleApplication.java} (54%) rename spring-batch-examples/src/main/java/com/io/example/fileReader/{async => }/README.md (100%) delete mode 100644 spring-batch-examples/src/main/java/com/io/example/fileReader/async/service/TestService.java delete mode 100644 spring-batch-examples/src/main/java/com/io/example/fileReader/asyncParallel/README.md rename spring-batch-examples/src/main/java/com/io/example/fileReader/{async => }/config/ExcelBatchConfig.java (87%) rename spring-batch-examples/src/main/java/com/io/example/fileReader/{async => }/controller/TestController.java (69%) rename spring-batch-examples/src/main/java/com/io/example/fileReader/{async => }/dto/StudentDto.java (86%) rename spring-batch-examples/src/main/java/com/io/example/fileReader/{async => }/exception/BusinessException.java (82%) create mode 100644 spring-batch-examples/src/main/java/com/io/example/fileReader/exception/GlobalHandlerException.java rename spring-batch-examples/src/main/java/com/io/example/fileReader/{async/job/AsyncFileob.java => job/FileJob.java} (89%) rename spring-batch-examples/src/main/java/com/io/example/fileReader/{async/mapper/AlunoMapper.java => mapper/StudentMapper.java} (73%) rename spring-batch-examples/src/main/java/com/io/example/fileReader/{async/service/AsyncFileBatchService.java => service/FileBatchService.java} (56%) create mode 100644 spring-batch-examples/src/main/java/com/io/example/fileReader/service/TestService.java rename spring-batch-examples/src/main/java/com/io/example/fileReader/{async/service/impl/AsyncBatchServiceImpl.java => service/impl/FileBatchServiceImpl.java} (85%) rename spring-batch-examples/src/main/java/com/io/example/fileReader/{async => }/service/impl/TestServiceImpl.java (61%) rename spring-batch-examples/src/main/resources/{application-asyncDbReader.yml => application-dbReader.yml} (88%) rename spring-batch-examples/src/main/resources/{application-asyncExcelFileReader.yml => application-excelFileReader.yml} (100%) delete mode 100644 spring-batch-examples/src/test/java/com/io/example/SpringBatchExampleApplicationTests.java rename spring-batch-examples/src/test/java/com/io/example/dbReader/{async => }/controller/TestControllerTest.java (82%) rename spring-batch-examples/src/test/java/com/io/example/dbReader/{async/job/AsyncBatchJobIntegrationTest.java => job/BatchJobIntegrationTest.java} (84%) rename spring-batch-examples/src/test/java/com/io/example/dbReader/{async => }/mapper/TestEntityToDtoMapperTest.java (83%) rename spring-batch-examples/src/test/java/com/io/example/dbReader/{async/service/AsyncDBBatchServiceImplUnitTest.java => service/DBBatchServiceImplUnitTest.java} (91%) rename spring-batch-examples/src/test/java/com/io/example/dbReader/{async => }/util/DataUtils.java (91%) create mode 100644 spring-batch-examples/src/test/java/com/io/example/fileReader/controller/TestControllerTest.java create mode 100644 spring-batch-examples/src/test/java/com/io/example/fileReader/mapper/StudentMapperTest.java delete mode 100644 spring-batch-examples/src/test/resources/application-asyncDbReader.yml create mode 100644 spring-batch-examples/src/test/resources/application-dbReader-test.yml diff --git a/spring-batch-examples/pom.xml b/spring-batch-examples/pom.xml index 1d8729d..e72f3eb 100644 --- a/spring-batch-examples/pom.xml +++ b/spring-batch-examples/pom.xml @@ -101,7 +101,13 @@ ${poi.ooxml.version} - + + com.amazonaws + aws-java-sdk-s3 + 1.12.777 + + + diff --git a/spring-batch-examples/src/main/java/com/io/example/dbReader/async/DbReaderAsyncExampleApplication.java b/spring-batch-examples/src/main/java/com/io/example/dbReader/DbReaderAsyncExampleApplication.java similarity index 88% rename from spring-batch-examples/src/main/java/com/io/example/dbReader/async/DbReaderAsyncExampleApplication.java rename to spring-batch-examples/src/main/java/com/io/example/dbReader/DbReaderAsyncExampleApplication.java index 31b237d..fdfa55d 100644 --- a/spring-batch-examples/src/main/java/com/io/example/dbReader/async/DbReaderAsyncExampleApplication.java +++ b/spring-batch-examples/src/main/java/com/io/example/dbReader/DbReaderAsyncExampleApplication.java @@ -1,4 +1,4 @@ -package com.io.example.dbReader.async; +package com.io.example.dbReader; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; diff --git a/spring-batch-examples/src/main/java/com/io/example/dbReader/async/README.md b/spring-batch-examples/src/main/java/com/io/example/dbReader/README.md similarity index 100% rename from spring-batch-examples/src/main/java/com/io/example/dbReader/async/README.md rename to spring-batch-examples/src/main/java/com/io/example/dbReader/README.md diff --git a/spring-batch-examples/src/main/java/com/io/example/dbReader/async/service/TestService.java b/spring-batch-examples/src/main/java/com/io/example/dbReader/async/service/TestService.java deleted file mode 100644 index 0e6d2f8..0000000 --- a/spring-batch-examples/src/main/java/com/io/example/dbReader/async/service/TestService.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.io.example.dbReader.async.service; - -import com.io.example.dbReader.async.model.dto.TestDto; - -public interface TestService { - void print(TestDto testDto); -} diff --git a/spring-batch-examples/src/main/java/com/io/example/dbReader/asyncParallel/README.md b/spring-batch-examples/src/main/java/com/io/example/dbReader/asyncParallel/README.md deleted file mode 100644 index 74b5d33..0000000 --- a/spring-batch-examples/src/main/java/com/io/example/dbReader/asyncParallel/README.md +++ /dev/null @@ -1,38 +0,0 @@ -# Spring Batch Examples | DB And Async Parallel - -This project is a **Spring Boot** application demonstrating a **fully asynchronous and parallel Spring Batch job**, designed with a focus on **performance** and **scalability**. - ---- - -## 🚀 Overview - -The example showcases how to configure and run an **asynchronous Spring Batch job** that executes **multiple steps in parallel**, processing a large dataset efficiently. -The job reads **10,000 records** from a database table, simulating item processing by printing -`"item processed"` for each entry. - ---- - -## ⚙️ How It Works - -* The job leverages Spring Batch’s **asynchronous and parallel capabilities** to execute multiple steps concurrently and process data efficiently. -* An **H2 in-memory database** is used to store the sample data. -* The parallel execution is enabled through a specific Spring profile. - ---- - -## 🧩 Technologies Used - -* **Java 21** -* **Spring Batch** -* **Spring Boot** -* **H2 Database** - ---- - -## ▶️ Running the Project - -To run the asynchronous and parallel batch job, simply activate the `asyncDbReader` profile: - -```bash -./mvnw spring-boot:run -Dspring-boot.run.profiles=asyncParallelDbReader -``` diff --git a/spring-batch-examples/src/main/java/com/io/example/dbReader/async/config/AsyncBatchConfig.java b/spring-batch-examples/src/main/java/com/io/example/dbReader/config/BatchConfig.java similarity index 87% rename from spring-batch-examples/src/main/java/com/io/example/dbReader/async/config/AsyncBatchConfig.java rename to spring-batch-examples/src/main/java/com/io/example/dbReader/config/BatchConfig.java index d44a5da..ab0a085 100644 --- a/spring-batch-examples/src/main/java/com/io/example/dbReader/async/config/AsyncBatchConfig.java +++ b/spring-batch-examples/src/main/java/com/io/example/dbReader/config/BatchConfig.java @@ -1,10 +1,10 @@ -package com.io.example.dbReader.async.config; +package com.io.example.dbReader.config; -import com.io.example.dbReader.async.model.dto.TestDto; -import com.io.example.dbReader.async.model.entity.TestEntity; -import com.io.example.dbReader.async.listener.LoggingStepListener; -import com.io.example.dbReader.async.mapper.TestEntityToDtoMapper; -import com.io.example.dbReader.async.service.TestService; +import com.io.example.dbReader.model.dto.TestDto; +import com.io.example.dbReader.model.entity.TestEntity; +import com.io.example.dbReader.listener.LoggingStepListener; +import com.io.example.dbReader.mapper.TestEntityToDtoMapper; +import com.io.example.dbReader.service.TestService; import lombok.RequiredArgsConstructor; import org.springframework.batch.core.Step; import org.springframework.batch.core.launch.JobLauncher; @@ -32,11 +32,11 @@ import java.util.Map; import java.util.concurrent.Future; -import static com.io.example.dbReader.async.repository.query.TestEntityQuery.*; +import static com.io.example.dbReader.repository.query.TestEntityQuery.*; @Configuration @RequiredArgsConstructor -public class AsyncBatchConfig { +public class BatchConfig { private final TestService testService; @@ -85,10 +85,11 @@ public Step asyncStep(JobRepository jobRepository, PlatformTransactionManager transactionManager, JdbcPagingItemReader asyncReader, AsyncItemProcessor asyncProcessor, - AsyncItemWriter asyncWriter) { + AsyncItemWriter asyncWriter, + @Value("${spring.batch.chunk-size}") int chunkSize) { return new StepBuilder("asyncStep", jobRepository) - .>chunk(500, transactionManager) + .>chunk(chunkSize, transactionManager) .reader(asyncReader) .processor(asyncProcessor) .writer(asyncWriter) diff --git a/spring-batch-examples/src/main/java/com/io/example/dbReader/async/controller/TestController.java b/spring-batch-examples/src/main/java/com/io/example/dbReader/controller/TestController.java similarity index 70% rename from spring-batch-examples/src/main/java/com/io/example/dbReader/async/controller/TestController.java rename to spring-batch-examples/src/main/java/com/io/example/dbReader/controller/TestController.java index db5e2a7..30d5fc3 100644 --- a/spring-batch-examples/src/main/java/com/io/example/dbReader/async/controller/TestController.java +++ b/spring-batch-examples/src/main/java/com/io/example/dbReader/controller/TestController.java @@ -1,6 +1,7 @@ -package com.io.example.dbReader.async.controller; +package com.io.example.dbReader.controller; -import com.io.example.dbReader.async.service.AsyncDBBatchService; +import com.io.example.dbReader.exception.BusinessException; +import com.io.example.dbReader.service.DBBatchService; import lombok.RequiredArgsConstructor; import org.springframework.batch.core.BatchStatus; import org.springframework.http.ResponseEntity; @@ -14,20 +15,20 @@ @RequiredArgsConstructor public class TestController { - private final AsyncDBBatchService asyncDBBatchService; + private final DBBatchService DBBatchService; @GetMapping("/process") public ResponseEntity processJob(){ - var response = asyncDBBatchService.runJob(); + var response = DBBatchService.runJob(); return ResponseEntity.ok(response); } @GetMapping("/{jobId}/status") public ResponseEntity getJobStatus(@PathVariable Long jobId) { try { - BatchStatus status = asyncDBBatchService.getJobStatus(jobId); + BatchStatus status = DBBatchService.getJobStatus(jobId); return ResponseEntity.ok(status.name()); - } catch (IllegalArgumentException e) { + } catch (BusinessException e) { return ResponseEntity.notFound().build(); } } diff --git a/spring-batch-examples/src/main/java/com/io/example/dbReader/async/exception/BusinessException.java b/spring-batch-examples/src/main/java/com/io/example/dbReader/exception/BusinessException.java similarity index 83% rename from spring-batch-examples/src/main/java/com/io/example/dbReader/async/exception/BusinessException.java rename to spring-batch-examples/src/main/java/com/io/example/dbReader/exception/BusinessException.java index c6fcdfa..cee5770 100644 --- a/spring-batch-examples/src/main/java/com/io/example/dbReader/async/exception/BusinessException.java +++ b/spring-batch-examples/src/main/java/com/io/example/dbReader/exception/BusinessException.java @@ -1,4 +1,4 @@ -package com.io.example.dbReader.async.exception; +package com.io.example.dbReader.exception; public class BusinessException extends RuntimeException { diff --git a/spring-batch-examples/src/main/java/com/io/example/dbReader/async/exception/GlobalHandlerException.java b/spring-batch-examples/src/main/java/com/io/example/dbReader/exception/GlobalHandlerException.java similarity index 92% rename from spring-batch-examples/src/main/java/com/io/example/dbReader/async/exception/GlobalHandlerException.java rename to spring-batch-examples/src/main/java/com/io/example/dbReader/exception/GlobalHandlerException.java index 1e4cb93..66ccdae 100644 --- a/spring-batch-examples/src/main/java/com/io/example/dbReader/async/exception/GlobalHandlerException.java +++ b/spring-batch-examples/src/main/java/com/io/example/dbReader/exception/GlobalHandlerException.java @@ -1,4 +1,4 @@ -package com.io.example.dbReader.async.exception; +package com.io.example.dbReader.exception; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; diff --git a/spring-batch-examples/src/main/java/com/io/example/dbReader/async/init/Init.java b/spring-batch-examples/src/main/java/com/io/example/dbReader/init/Init.java similarity index 87% rename from spring-batch-examples/src/main/java/com/io/example/dbReader/async/init/Init.java rename to spring-batch-examples/src/main/java/com/io/example/dbReader/init/Init.java index a473ef0..07cf33e 100644 --- a/spring-batch-examples/src/main/java/com/io/example/dbReader/async/init/Init.java +++ b/spring-batch-examples/src/main/java/com/io/example/dbReader/init/Init.java @@ -1,7 +1,7 @@ -package com.io.example.dbReader.async.init; +package com.io.example.dbReader.init; -import com.io.example.dbReader.async.model.entity.TestEntity; -import com.io.example.dbReader.async.repository.TestEntityRepository; +import com.io.example.dbReader.model.entity.TestEntity; +import com.io.example.dbReader.repository.TestEntityRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.CommandLineRunner; diff --git a/spring-batch-examples/src/main/java/com/io/example/dbReader/async/job/AsyncBatchJob.java b/spring-batch-examples/src/main/java/com/io/example/dbReader/job/BatchJob.java similarity index 89% rename from spring-batch-examples/src/main/java/com/io/example/dbReader/async/job/AsyncBatchJob.java rename to spring-batch-examples/src/main/java/com/io/example/dbReader/job/BatchJob.java index ff58448..aa2c319 100644 --- a/spring-batch-examples/src/main/java/com/io/example/dbReader/async/job/AsyncBatchJob.java +++ b/spring-batch-examples/src/main/java/com/io/example/dbReader/job/BatchJob.java @@ -1,4 +1,4 @@ -package com.io.example.dbReader.async.job; +package com.io.example.dbReader.job; import lombok.RequiredArgsConstructor; import org.springframework.batch.core.Job; @@ -10,7 +10,7 @@ @Component @RequiredArgsConstructor -public class AsyncBatchJob { +public class BatchJob { @Bean public Job job(JobRepository jobRepository, Step asyncStep) { diff --git a/spring-batch-examples/src/main/java/com/io/example/dbReader/async/listener/LoggingStepListener.java b/spring-batch-examples/src/main/java/com/io/example/dbReader/listener/LoggingStepListener.java similarity index 95% rename from spring-batch-examples/src/main/java/com/io/example/dbReader/async/listener/LoggingStepListener.java rename to spring-batch-examples/src/main/java/com/io/example/dbReader/listener/LoggingStepListener.java index ee895e0..49a9689 100644 --- a/spring-batch-examples/src/main/java/com/io/example/dbReader/async/listener/LoggingStepListener.java +++ b/spring-batch-examples/src/main/java/com/io/example/dbReader/listener/LoggingStepListener.java @@ -1,4 +1,4 @@ -package com.io.example.dbReader.async.listener; +package com.io.example.dbReader.listener; import lombok.extern.slf4j.Slf4j; import org.springframework.batch.core.ExitStatus; diff --git a/spring-batch-examples/src/main/java/com/io/example/dbReader/async/mapper/TestEntityToDtoMapper.java b/spring-batch-examples/src/main/java/com/io/example/dbReader/mapper/TestEntityToDtoMapper.java similarity index 76% rename from spring-batch-examples/src/main/java/com/io/example/dbReader/async/mapper/TestEntityToDtoMapper.java rename to spring-batch-examples/src/main/java/com/io/example/dbReader/mapper/TestEntityToDtoMapper.java index 13abb14..fc78398 100644 --- a/spring-batch-examples/src/main/java/com/io/example/dbReader/async/mapper/TestEntityToDtoMapper.java +++ b/spring-batch-examples/src/main/java/com/io/example/dbReader/mapper/TestEntityToDtoMapper.java @@ -1,7 +1,7 @@ -package com.io.example.dbReader.async.mapper; +package com.io.example.dbReader.mapper; -import com.io.example.dbReader.async.model.dto.TestDto; -import com.io.example.dbReader.async.model.entity.TestEntity; +import com.io.example.dbReader.model.dto.TestDto; +import com.io.example.dbReader.model.entity.TestEntity; import org.springframework.batch.item.ItemProcessor; public class TestEntityToDtoMapper implements ItemProcessor { diff --git a/spring-batch-examples/src/main/java/com/io/example/dbReader/async/model/dto/TestDto.java b/spring-batch-examples/src/main/java/com/io/example/dbReader/model/dto/TestDto.java similarity index 87% rename from spring-batch-examples/src/main/java/com/io/example/dbReader/async/model/dto/TestDto.java rename to spring-batch-examples/src/main/java/com/io/example/dbReader/model/dto/TestDto.java index da15726..5285b93 100644 --- a/spring-batch-examples/src/main/java/com/io/example/dbReader/async/model/dto/TestDto.java +++ b/spring-batch-examples/src/main/java/com/io/example/dbReader/model/dto/TestDto.java @@ -1,4 +1,4 @@ -package com.io.example.dbReader.async.model.dto; +package com.io.example.dbReader.model.dto; import lombok.AllArgsConstructor; import lombok.Builder; diff --git a/spring-batch-examples/src/main/java/com/io/example/dbReader/async/model/entity/TestEntity.java b/spring-batch-examples/src/main/java/com/io/example/dbReader/model/entity/TestEntity.java similarity index 90% rename from spring-batch-examples/src/main/java/com/io/example/dbReader/async/model/entity/TestEntity.java rename to spring-batch-examples/src/main/java/com/io/example/dbReader/model/entity/TestEntity.java index 9aa3298..2dcd37f 100644 --- a/spring-batch-examples/src/main/java/com/io/example/dbReader/async/model/entity/TestEntity.java +++ b/spring-batch-examples/src/main/java/com/io/example/dbReader/model/entity/TestEntity.java @@ -1,4 +1,4 @@ -package com.io.example.dbReader.async.model.entity; +package com.io.example.dbReader.model.entity; import jakarta.persistence.*; import lombok.AllArgsConstructor; diff --git a/spring-batch-examples/src/main/java/com/io/example/dbReader/async/repository/TestEntityRepository.java b/spring-batch-examples/src/main/java/com/io/example/dbReader/repository/TestEntityRepository.java similarity index 65% rename from spring-batch-examples/src/main/java/com/io/example/dbReader/async/repository/TestEntityRepository.java rename to spring-batch-examples/src/main/java/com/io/example/dbReader/repository/TestEntityRepository.java index 8fe873c..8e32879 100644 --- a/spring-batch-examples/src/main/java/com/io/example/dbReader/async/repository/TestEntityRepository.java +++ b/spring-batch-examples/src/main/java/com/io/example/dbReader/repository/TestEntityRepository.java @@ -1,6 +1,6 @@ -package com.io.example.dbReader.async.repository; +package com.io.example.dbReader.repository; -import com.io.example.dbReader.async.model.entity.TestEntity; +import com.io.example.dbReader.model.entity.TestEntity; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; diff --git a/spring-batch-examples/src/main/java/com/io/example/dbReader/async/repository/query/TestEntityQuery.java b/spring-batch-examples/src/main/java/com/io/example/dbReader/repository/query/TestEntityQuery.java similarity index 91% rename from spring-batch-examples/src/main/java/com/io/example/dbReader/async/repository/query/TestEntityQuery.java rename to spring-batch-examples/src/main/java/com/io/example/dbReader/repository/query/TestEntityQuery.java index 3a65cba..f78dee7 100644 --- a/spring-batch-examples/src/main/java/com/io/example/dbReader/async/repository/query/TestEntityQuery.java +++ b/spring-batch-examples/src/main/java/com/io/example/dbReader/repository/query/TestEntityQuery.java @@ -1,4 +1,4 @@ -package com.io.example.dbReader.async.repository.query; +package com.io.example.dbReader.repository.query; import lombok.AccessLevel; import lombok.RequiredArgsConstructor; diff --git a/spring-batch-examples/src/main/java/com/io/example/dbReader/async/service/AsyncDBBatchService.java b/spring-batch-examples/src/main/java/com/io/example/dbReader/service/DBBatchService.java similarity index 57% rename from spring-batch-examples/src/main/java/com/io/example/dbReader/async/service/AsyncDBBatchService.java rename to spring-batch-examples/src/main/java/com/io/example/dbReader/service/DBBatchService.java index ea0c572..3eb8d43 100644 --- a/spring-batch-examples/src/main/java/com/io/example/dbReader/async/service/AsyncDBBatchService.java +++ b/spring-batch-examples/src/main/java/com/io/example/dbReader/service/DBBatchService.java @@ -1,8 +1,8 @@ -package com.io.example.dbReader.async.service; +package com.io.example.dbReader.service; import org.springframework.batch.core.BatchStatus; -public interface AsyncDBBatchService { +public interface DBBatchService { Long runJob(); BatchStatus getJobStatus(Long jobId); } diff --git a/spring-batch-examples/src/main/java/com/io/example/dbReader/service/TestService.java b/spring-batch-examples/src/main/java/com/io/example/dbReader/service/TestService.java new file mode 100644 index 0000000..693f085 --- /dev/null +++ b/spring-batch-examples/src/main/java/com/io/example/dbReader/service/TestService.java @@ -0,0 +1,7 @@ +package com.io.example.dbReader.service; + +import com.io.example.dbReader.model.dto.TestDto; + +public interface TestService { + void print(TestDto testDto); +} diff --git a/spring-batch-examples/src/main/java/com/io/example/dbReader/async/service/impl/AsyncDBBatchServiceImpl.java b/spring-batch-examples/src/main/java/com/io/example/dbReader/service/impl/DBBatchServiceImpl.java similarity index 86% rename from spring-batch-examples/src/main/java/com/io/example/dbReader/async/service/impl/AsyncDBBatchServiceImpl.java rename to spring-batch-examples/src/main/java/com/io/example/dbReader/service/impl/DBBatchServiceImpl.java index 5a252e8..b696d37 100644 --- a/spring-batch-examples/src/main/java/com/io/example/dbReader/async/service/impl/AsyncDBBatchServiceImpl.java +++ b/spring-batch-examples/src/main/java/com/io/example/dbReader/service/impl/DBBatchServiceImpl.java @@ -1,7 +1,7 @@ -package com.io.example.dbReader.async.service.impl; +package com.io.example.dbReader.service.impl; -import com.io.example.dbReader.async.exception.BusinessException; -import com.io.example.dbReader.async.service.AsyncDBBatchService; +import com.io.example.dbReader.exception.BusinessException; +import com.io.example.dbReader.service.DBBatchService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.batch.core.*; @@ -12,7 +12,7 @@ @Slf4j @Service @RequiredArgsConstructor -public class AsyncDBBatchServiceImpl implements AsyncDBBatchService { +public class DBBatchServiceImpl implements DBBatchService { private final JobLauncher asyncJobLauncher; private final Job asyncBatchJob; diff --git a/spring-batch-examples/src/main/java/com/io/example/dbReader/async/service/impl/TestServiceImpl.java b/spring-batch-examples/src/main/java/com/io/example/dbReader/service/impl/TestServiceImpl.java similarity index 60% rename from spring-batch-examples/src/main/java/com/io/example/dbReader/async/service/impl/TestServiceImpl.java rename to spring-batch-examples/src/main/java/com/io/example/dbReader/service/impl/TestServiceImpl.java index d61fb9c..7e82a51 100644 --- a/spring-batch-examples/src/main/java/com/io/example/dbReader/async/service/impl/TestServiceImpl.java +++ b/spring-batch-examples/src/main/java/com/io/example/dbReader/service/impl/TestServiceImpl.java @@ -1,7 +1,7 @@ -package com.io.example.dbReader.async.service.impl; +package com.io.example.dbReader.service.impl; -import com.io.example.dbReader.async.model.dto.TestDto; -import com.io.example.dbReader.async.service.TestService; +import com.io.example.dbReader.model.dto.TestDto; +import com.io.example.dbReader.service.TestService; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; diff --git a/spring-batch-examples/src/main/java/com/io/example/fileReader/async/FileReaderAsyncExampleApplication.java b/spring-batch-examples/src/main/java/com/io/example/fileReader/FileReaderExampleApplication.java similarity index 54% rename from spring-batch-examples/src/main/java/com/io/example/fileReader/async/FileReaderAsyncExampleApplication.java rename to spring-batch-examples/src/main/java/com/io/example/fileReader/FileReaderExampleApplication.java index c0a87ac..badb540 100644 --- a/spring-batch-examples/src/main/java/com/io/example/fileReader/async/FileReaderAsyncExampleApplication.java +++ b/spring-batch-examples/src/main/java/com/io/example/fileReader/FileReaderExampleApplication.java @@ -1,13 +1,13 @@ -package com.io.example.fileReader.async; +package com.io.example.fileReader; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication -public class FileReaderAsyncExampleApplication { +public class FileReaderExampleApplication { public static void main(String[] args) { - SpringApplication.run(FileReaderAsyncExampleApplication.class, args); + SpringApplication.run(FileReaderExampleApplication.class, args); } } diff --git a/spring-batch-examples/src/main/java/com/io/example/fileReader/async/README.md b/spring-batch-examples/src/main/java/com/io/example/fileReader/README.md similarity index 100% rename from spring-batch-examples/src/main/java/com/io/example/fileReader/async/README.md rename to spring-batch-examples/src/main/java/com/io/example/fileReader/README.md diff --git a/spring-batch-examples/src/main/java/com/io/example/fileReader/async/service/TestService.java b/spring-batch-examples/src/main/java/com/io/example/fileReader/async/service/TestService.java deleted file mode 100644 index 19100f8..0000000 --- a/spring-batch-examples/src/main/java/com/io/example/fileReader/async/service/TestService.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.io.example.fileReader.async.service; - -import com.io.example.fileReader.async.dto.StudentDto; - -public interface TestService { - void print(StudentDto studentDto); -} diff --git a/spring-batch-examples/src/main/java/com/io/example/fileReader/asyncParallel/README.md b/spring-batch-examples/src/main/java/com/io/example/fileReader/asyncParallel/README.md deleted file mode 100644 index b7f1519..0000000 --- a/spring-batch-examples/src/main/java/com/io/example/fileReader/asyncParallel/README.md +++ /dev/null @@ -1,40 +0,0 @@ -# PENDING - -# Spring Batch Examples | Async CSV Processing - -This project is a **Spring Boot** application demonstrating a **fully asynchronous and parallel Spring Batch job**, designed with a focus on **performance** and **scalability**. - ---- - -## 🚀 Overview - -The example showcases how to configure and run an **asynchronous Spring Batch job** that executes **multiple steps in parallel**, each processing a different CSV file efficiently. -The job reads **two separate CSV files**, simulating item processing by printing -`"item processed"` for each entry in both files. - ---- - -## ⚙️ How It Works - -* The job leverages Spring Batch’s **asynchronous and parallel capabilities** to execute multiple steps concurrently, processing each CSV file independently and efficiently. -* Two **CSV files** are used as input data sources. -* The parallel execution is enabled through a specific Spring profile. - ---- - -## 🧩 Technologies Used - -* **Java 21** -* **Spring Batch** -* **Spring Boot** -* **CSV files** - ---- - -## ▶️ Running the Project - -To run the asynchronous and parallel batch job processing multiple CSV files, simply activate the `asyncCsvReader` profile: - -```bash -./mvnw spring-boot:run -Dspring-boot.run.profiles=asyncParallelFileReader -``` diff --git a/spring-batch-examples/src/main/java/com/io/example/fileReader/async/config/ExcelBatchConfig.java b/spring-batch-examples/src/main/java/com/io/example/fileReader/config/ExcelBatchConfig.java similarity index 87% rename from spring-batch-examples/src/main/java/com/io/example/fileReader/async/config/ExcelBatchConfig.java rename to spring-batch-examples/src/main/java/com/io/example/fileReader/config/ExcelBatchConfig.java index c2e3603..c46e2da 100644 --- a/spring-batch-examples/src/main/java/com/io/example/fileReader/async/config/ExcelBatchConfig.java +++ b/spring-batch-examples/src/main/java/com/io/example/fileReader/config/ExcelBatchConfig.java @@ -1,8 +1,8 @@ -package com.io.example.fileReader.async.config; +package com.io.example.fileReader.config; -import com.io.example.fileReader.async.service.TestService; -import com.io.example.fileReader.async.dto.StudentDto; -import com.io.example.fileReader.async.mapper.AlunoMapper; +import com.io.example.fileReader.service.TestService; +import com.io.example.fileReader.dto.StudentDto; +import com.io.example.fileReader.mapper.StudentMapper; import lombok.RequiredArgsConstructor; import org.springframework.batch.core.Step; import org.springframework.batch.core.repository.JobRepository; @@ -17,6 +17,7 @@ import org.springframework.context.annotation.Profile; import org.springframework.core.io.ClassPathResource; import org.springframework.batch.core.configuration.annotation.StepScope; +import org.springframework.core.io.InputStreamResource; import org.springframework.core.task.SimpleAsyncTaskExecutor; import org.springframework.transaction.PlatformTransactionManager; import java.util.concurrent.Future; @@ -32,9 +33,9 @@ public class ExcelBatchConfig { @StepScope public PoiItemReader excelReader() { PoiItemReader reader = new PoiItemReader<>(); - reader.setResource(new ClassPathResource("students.xlsx")); - reader.setRowMapper(new AlunoMapper()); + reader.setResource(new InputStreamResource(new ClassPathResource("students.xlsx"))); reader.setLinesToSkip(1); + reader.setRowMapper(new StudentMapper()); return reader; } diff --git a/spring-batch-examples/src/main/java/com/io/example/fileReader/async/controller/TestController.java b/spring-batch-examples/src/main/java/com/io/example/fileReader/controller/TestController.java similarity index 69% rename from spring-batch-examples/src/main/java/com/io/example/fileReader/async/controller/TestController.java rename to spring-batch-examples/src/main/java/com/io/example/fileReader/controller/TestController.java index 05a458d..17e384b 100644 --- a/spring-batch-examples/src/main/java/com/io/example/fileReader/async/controller/TestController.java +++ b/spring-batch-examples/src/main/java/com/io/example/fileReader/controller/TestController.java @@ -1,6 +1,7 @@ -package com.io.example.fileReader.async.controller; +package com.io.example.fileReader.controller; -import com.io.example.fileReader.async.service.AsyncFileBatchService; +import com.io.example.fileReader.exception.BusinessException; +import com.io.example.fileReader.service.FileBatchService; import lombok.RequiredArgsConstructor; import org.springframework.batch.core.BatchStatus; import org.springframework.http.ResponseEntity; @@ -14,20 +15,20 @@ @RequiredArgsConstructor public class TestController { - private final AsyncFileBatchService asyncFileBatchService; + private final FileBatchService fileBatchService; @GetMapping("/process") public ResponseEntity processJob(){ - var response = asyncFileBatchService.runJob(); + var response = fileBatchService.runJob(); return ResponseEntity.ok(response); } @GetMapping("/{jobId}/status") public ResponseEntity getJobStatus(@PathVariable Long jobId) { try { - BatchStatus status = asyncFileBatchService.getJobStatus(jobId); + BatchStatus status = fileBatchService.getJobStatus(jobId); return ResponseEntity.ok(status.name()); - } catch (IllegalArgumentException e) { + } catch (BusinessException e) { return ResponseEntity.notFound().build(); } } diff --git a/spring-batch-examples/src/main/java/com/io/example/fileReader/async/dto/StudentDto.java b/spring-batch-examples/src/main/java/com/io/example/fileReader/dto/StudentDto.java similarity index 86% rename from spring-batch-examples/src/main/java/com/io/example/fileReader/async/dto/StudentDto.java rename to spring-batch-examples/src/main/java/com/io/example/fileReader/dto/StudentDto.java index 3426cd4..5a4428f 100644 --- a/spring-batch-examples/src/main/java/com/io/example/fileReader/async/dto/StudentDto.java +++ b/spring-batch-examples/src/main/java/com/io/example/fileReader/dto/StudentDto.java @@ -1,4 +1,4 @@ -package com.io.example.fileReader.async.dto; +package com.io.example.fileReader.dto; import lombok.AllArgsConstructor; import lombok.Builder; diff --git a/spring-batch-examples/src/main/java/com/io/example/fileReader/async/exception/BusinessException.java b/spring-batch-examples/src/main/java/com/io/example/fileReader/exception/BusinessException.java similarity index 82% rename from spring-batch-examples/src/main/java/com/io/example/fileReader/async/exception/BusinessException.java rename to spring-batch-examples/src/main/java/com/io/example/fileReader/exception/BusinessException.java index f62fcf4..a137884 100644 --- a/spring-batch-examples/src/main/java/com/io/example/fileReader/async/exception/BusinessException.java +++ b/spring-batch-examples/src/main/java/com/io/example/fileReader/exception/BusinessException.java @@ -1,4 +1,4 @@ -package com.io.example.fileReader.async.exception; +package com.io.example.fileReader.exception; public class BusinessException extends RuntimeException { diff --git a/spring-batch-examples/src/main/java/com/io/example/fileReader/exception/GlobalHandlerException.java b/spring-batch-examples/src/main/java/com/io/example/fileReader/exception/GlobalHandlerException.java new file mode 100644 index 0000000..40739db --- /dev/null +++ b/spring-batch-examples/src/main/java/com/io/example/fileReader/exception/GlobalHandlerException.java @@ -0,0 +1,20 @@ +package com.io.example.fileReader.exception; + +import com.io.example.fileReader.exception.BusinessException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; + +@Slf4j +@ControllerAdvice +public class GlobalHandlerException { + + @ExceptionHandler(BusinessException.class) + public ResponseEntity handleBusinessException(BusinessException ex) { + log.error("BusinessException caught: {}", ex.getMessage()); + return new ResponseEntity<>(ex.getMessage(), HttpStatus.BAD_REQUEST); + } + +} \ No newline at end of file diff --git a/spring-batch-examples/src/main/java/com/io/example/fileReader/async/job/AsyncFileob.java b/spring-batch-examples/src/main/java/com/io/example/fileReader/job/FileJob.java similarity index 89% rename from spring-batch-examples/src/main/java/com/io/example/fileReader/async/job/AsyncFileob.java rename to spring-batch-examples/src/main/java/com/io/example/fileReader/job/FileJob.java index d45b695..581c32c 100644 --- a/spring-batch-examples/src/main/java/com/io/example/fileReader/async/job/AsyncFileob.java +++ b/spring-batch-examples/src/main/java/com/io/example/fileReader/job/FileJob.java @@ -1,4 +1,4 @@ -package com.io.example.fileReader.async.job; +package com.io.example.fileReader.job; import lombok.RequiredArgsConstructor; import org.springframework.batch.core.Job; @@ -10,7 +10,7 @@ @Component @RequiredArgsConstructor -public class AsyncFileob { +public class FileJob { @Bean public Job excelJob(JobRepository jobRepository, Step step) { diff --git a/spring-batch-examples/src/main/java/com/io/example/fileReader/async/mapper/AlunoMapper.java b/spring-batch-examples/src/main/java/com/io/example/fileReader/mapper/StudentMapper.java similarity index 73% rename from spring-batch-examples/src/main/java/com/io/example/fileReader/async/mapper/AlunoMapper.java rename to spring-batch-examples/src/main/java/com/io/example/fileReader/mapper/StudentMapper.java index 92d07aa..0ff9c53 100644 --- a/spring-batch-examples/src/main/java/com/io/example/fileReader/async/mapper/AlunoMapper.java +++ b/spring-batch-examples/src/main/java/com/io/example/fileReader/mapper/StudentMapper.java @@ -1,11 +1,11 @@ -package com.io.example.fileReader.async.mapper; +package com.io.example.fileReader.mapper; -import com.io.example.fileReader.async.dto.StudentDto; +import com.io.example.fileReader.dto.StudentDto; import org.springframework.batch.extensions.excel.RowMapper; import org.springframework.batch.extensions.excel.support.rowset.RowSet; import java.time.LocalDate; -public class AlunoMapper implements RowMapper { +public class StudentMapper implements RowMapper { @Override public StudentDto mapRow(RowSet rowSet){ diff --git a/spring-batch-examples/src/main/java/com/io/example/fileReader/async/service/AsyncFileBatchService.java b/spring-batch-examples/src/main/java/com/io/example/fileReader/service/FileBatchService.java similarity index 56% rename from spring-batch-examples/src/main/java/com/io/example/fileReader/async/service/AsyncFileBatchService.java rename to spring-batch-examples/src/main/java/com/io/example/fileReader/service/FileBatchService.java index ad26bf4..b2ff4e9 100644 --- a/spring-batch-examples/src/main/java/com/io/example/fileReader/async/service/AsyncFileBatchService.java +++ b/spring-batch-examples/src/main/java/com/io/example/fileReader/service/FileBatchService.java @@ -1,8 +1,8 @@ -package com.io.example.fileReader.async.service; +package com.io.example.fileReader.service; import org.springframework.batch.core.BatchStatus; -public interface AsyncFileBatchService { +public interface FileBatchService { Long runJob(); BatchStatus getJobStatus(Long jobId); } diff --git a/spring-batch-examples/src/main/java/com/io/example/fileReader/service/TestService.java b/spring-batch-examples/src/main/java/com/io/example/fileReader/service/TestService.java new file mode 100644 index 0000000..d5881a8 --- /dev/null +++ b/spring-batch-examples/src/main/java/com/io/example/fileReader/service/TestService.java @@ -0,0 +1,7 @@ +package com.io.example.fileReader.service; + +import com.io.example.fileReader.dto.StudentDto; + +public interface TestService { + void print(StudentDto studentDto); +} diff --git a/spring-batch-examples/src/main/java/com/io/example/fileReader/async/service/impl/AsyncBatchServiceImpl.java b/spring-batch-examples/src/main/java/com/io/example/fileReader/service/impl/FileBatchServiceImpl.java similarity index 85% rename from spring-batch-examples/src/main/java/com/io/example/fileReader/async/service/impl/AsyncBatchServiceImpl.java rename to spring-batch-examples/src/main/java/com/io/example/fileReader/service/impl/FileBatchServiceImpl.java index f50a407..1657410 100644 --- a/spring-batch-examples/src/main/java/com/io/example/fileReader/async/service/impl/AsyncBatchServiceImpl.java +++ b/spring-batch-examples/src/main/java/com/io/example/fileReader/service/impl/FileBatchServiceImpl.java @@ -1,7 +1,7 @@ -package com.io.example.fileReader.async.service.impl; +package com.io.example.fileReader.service.impl; -import com.io.example.fileReader.async.exception.BusinessException; -import com.io.example.fileReader.async.service.AsyncFileBatchService; +import com.io.example.fileReader.exception.BusinessException; +import com.io.example.fileReader.service.FileBatchService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.batch.core.*; @@ -12,7 +12,7 @@ @Slf4j @Service @RequiredArgsConstructor -public class AsyncBatchServiceImpl implements AsyncFileBatchService { +public class FileBatchServiceImpl implements FileBatchService { private final JobLauncher asyncJobLauncher; private final Job excelJob; diff --git a/spring-batch-examples/src/main/java/com/io/example/fileReader/async/service/impl/TestServiceImpl.java b/spring-batch-examples/src/main/java/com/io/example/fileReader/service/impl/TestServiceImpl.java similarity index 61% rename from spring-batch-examples/src/main/java/com/io/example/fileReader/async/service/impl/TestServiceImpl.java rename to spring-batch-examples/src/main/java/com/io/example/fileReader/service/impl/TestServiceImpl.java index dfed6db..2e7b8cd 100644 --- a/spring-batch-examples/src/main/java/com/io/example/fileReader/async/service/impl/TestServiceImpl.java +++ b/spring-batch-examples/src/main/java/com/io/example/fileReader/service/impl/TestServiceImpl.java @@ -1,7 +1,7 @@ -package com.io.example.fileReader.async.service.impl; +package com.io.example.fileReader.service.impl; -import com.io.example.fileReader.async.dto.StudentDto; -import com.io.example.fileReader.async.service.TestService; +import com.io.example.fileReader.dto.StudentDto; +import com.io.example.fileReader.service.TestService; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; diff --git a/spring-batch-examples/src/main/resources/application-asyncDbReader.yml b/spring-batch-examples/src/main/resources/application-dbReader.yml similarity index 88% rename from spring-batch-examples/src/main/resources/application-asyncDbReader.yml rename to spring-batch-examples/src/main/resources/application-dbReader.yml index 13d8554..f1b14b7 100644 --- a/spring-batch-examples/src/main/resources/application-asyncDbReader.yml +++ b/spring-batch-examples/src/main/resources/application-dbReader.yml @@ -22,4 +22,5 @@ spring: initialize-schema: always job: enabled: false - batch-size: 1000 + batch-size: 500 + chunk-size: 200 diff --git a/spring-batch-examples/src/main/resources/application-asyncExcelFileReader.yml b/spring-batch-examples/src/main/resources/application-excelFileReader.yml similarity index 100% rename from spring-batch-examples/src/main/resources/application-asyncExcelFileReader.yml rename to spring-batch-examples/src/main/resources/application-excelFileReader.yml diff --git a/spring-batch-examples/src/test/java/com/io/example/SpringBatchExampleApplicationTests.java b/spring-batch-examples/src/test/java/com/io/example/SpringBatchExampleApplicationTests.java deleted file mode 100644 index 4ff4059..0000000 --- a/spring-batch-examples/src/test/java/com/io/example/SpringBatchExampleApplicationTests.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.io.example; - -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; - -@SpringBootTest -class SpringBatchExampleApplicationTests { - - @Test - void contextLoads() { - } - -} diff --git a/spring-batch-examples/src/test/java/com/io/example/dbReader/async/controller/TestControllerTest.java b/spring-batch-examples/src/test/java/com/io/example/dbReader/controller/TestControllerTest.java similarity index 82% rename from spring-batch-examples/src/test/java/com/io/example/dbReader/async/controller/TestControllerTest.java rename to spring-batch-examples/src/test/java/com/io/example/dbReader/controller/TestControllerTest.java index 907b026..38aa463 100644 --- a/spring-batch-examples/src/test/java/com/io/example/dbReader/async/controller/TestControllerTest.java +++ b/spring-batch-examples/src/test/java/com/io/example/dbReader/controller/TestControllerTest.java @@ -1,7 +1,7 @@ -package com.io.example.dbReader.async.controller; +package com.io.example.dbReader.controller; -import com.io.example.dbReader.async.exception.BusinessException; -import com.io.example.dbReader.async.service.AsyncDBBatchService; +import com.io.example.dbReader.exception.BusinessException; +import com.io.example.dbReader.service.DBBatchService; import org.instancio.Instancio; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -28,13 +28,13 @@ class TestControllerTest { private MockMvc mockMvc; @MockitoBean - private AsyncDBBatchService asyncDBBatchService; + private DBBatchService DBBatchService; @Test @DisplayName("GET /job/process → should return job ID when service runs successfully") void shouldReturnJobIdWhenProcessJobIsCalled() throws Exception { - when(asyncDBBatchService.runJob()).thenReturn(jobId); + when(DBBatchService.runJob()).thenReturn(jobId); mockMvc.perform(get("/job/process") .accept(MediaType.APPLICATION_JSON)) @@ -47,7 +47,7 @@ void shouldReturnJobIdWhenProcessJobIsCalled() throws Exception { @DisplayName("GET /job/{jobId}/status → should return correct status for each BatchStatus") void shouldReturnJobStatusForAllBatchStatuses(BatchStatus status) throws Exception { - when(asyncDBBatchService.getJobStatus(jobId)).thenReturn(status); + when(DBBatchService.getJobStatus(jobId)).thenReturn(status); mockMvc.perform(get("/job/{jobId}/status", jobId) .accept(MediaType.APPLICATION_JSON)) @@ -59,12 +59,12 @@ void shouldReturnJobStatusForAllBatchStatuses(BatchStatus status) throws Excepti @DisplayName("GET /job/{jobId}/status → should return 404 when job ID is invalid") void shouldReturn404WhenJobIdIsInvalid() throws Exception { - when(asyncDBBatchService.getJobStatus(jobId)) + when(DBBatchService.getJobStatus(jobId)) .thenThrow(new BusinessException("JobExecution not found for this id: " + jobId)); mockMvc.perform(get("/job/{jobId}/status", jobId) .accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isBadRequest()); + .andExpect(status().isNotFound()); } } diff --git a/spring-batch-examples/src/test/java/com/io/example/dbReader/async/job/AsyncBatchJobIntegrationTest.java b/spring-batch-examples/src/test/java/com/io/example/dbReader/job/BatchJobIntegrationTest.java similarity index 84% rename from spring-batch-examples/src/test/java/com/io/example/dbReader/async/job/AsyncBatchJobIntegrationTest.java rename to spring-batch-examples/src/test/java/com/io/example/dbReader/job/BatchJobIntegrationTest.java index b067070..906e90e 100644 --- a/spring-batch-examples/src/test/java/com/io/example/dbReader/async/job/AsyncBatchJobIntegrationTest.java +++ b/spring-batch-examples/src/test/java/com/io/example/dbReader/job/BatchJobIntegrationTest.java @@ -1,9 +1,8 @@ -package com.io.example.dbReader.async.job; +package com.io.example.dbReader.job; -import com.io.example.dbReader.async.model.dto.TestDto; -import com.io.example.dbReader.async.model.entity.TestEntity; -import com.io.example.dbReader.async.service.TestService; -import org.instancio.Instancio; +import com.io.example.dbReader.model.dto.TestDto; +import com.io.example.dbReader.model.entity.TestEntity; +import com.io.example.dbReader.service.TestService; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.batch.core.*; @@ -15,16 +14,16 @@ import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.bean.override.mockito.MockitoBean; -import static com.io.example.dbReader.async.util.DataUtils.*; +import static com.io.example.dbReader.util.DataUtils.*; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; @SpringBootTest @SpringBatchTest -@ActiveProfiles("test") -@DisplayName("AsyncBatchJob - Integration test with real Job execution") -class AsyncBatchJobIntegrationTest { +@ActiveProfiles("dbReader-test") +@DisplayName("BatchJob - Integration test with real Job execution") +class BatchJobIntegrationTest { @Autowired private JobLauncher jobLauncher; @@ -81,6 +80,4 @@ void shouldCompleteJobWithNoData() throws Exception { } - - } diff --git a/spring-batch-examples/src/test/java/com/io/example/dbReader/async/mapper/TestEntityToDtoMapperTest.java b/spring-batch-examples/src/test/java/com/io/example/dbReader/mapper/TestEntityToDtoMapperTest.java similarity index 83% rename from spring-batch-examples/src/test/java/com/io/example/dbReader/async/mapper/TestEntityToDtoMapperTest.java rename to spring-batch-examples/src/test/java/com/io/example/dbReader/mapper/TestEntityToDtoMapperTest.java index 937fad5..2298d00 100644 --- a/spring-batch-examples/src/test/java/com/io/example/dbReader/async/mapper/TestEntityToDtoMapperTest.java +++ b/spring-batch-examples/src/test/java/com/io/example/dbReader/mapper/TestEntityToDtoMapperTest.java @@ -1,7 +1,7 @@ -package com.io.example.dbReader.async.mapper; +package com.io.example.dbReader.mapper; -import com.io.example.dbReader.async.model.dto.TestDto; -import com.io.example.dbReader.async.model.entity.TestEntity; +import com.io.example.dbReader.model.dto.TestDto; +import com.io.example.dbReader.model.entity.TestEntity; import org.instancio.Instancio; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/spring-batch-examples/src/test/java/com/io/example/dbReader/async/service/AsyncDBBatchServiceImplUnitTest.java b/spring-batch-examples/src/test/java/com/io/example/dbReader/service/DBBatchServiceImplUnitTest.java similarity index 91% rename from spring-batch-examples/src/test/java/com/io/example/dbReader/async/service/AsyncDBBatchServiceImplUnitTest.java rename to spring-batch-examples/src/test/java/com/io/example/dbReader/service/DBBatchServiceImplUnitTest.java index fc83298..cfe1af2 100644 --- a/spring-batch-examples/src/test/java/com/io/example/dbReader/async/service/AsyncDBBatchServiceImplUnitTest.java +++ b/spring-batch-examples/src/test/java/com/io/example/dbReader/service/DBBatchServiceImplUnitTest.java @@ -1,7 +1,7 @@ -package com.io.example.dbReader.async.service; +package com.io.example.dbReader.service; -import com.io.example.dbReader.async.exception.BusinessException; -import com.io.example.dbReader.async.service.impl.AsyncDBBatchServiceImpl; +import com.io.example.dbReader.exception.BusinessException; +import com.io.example.dbReader.service.impl.DBBatchServiceImpl; import org.instancio.Instancio; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -22,7 +22,7 @@ @ExtendWith(MockitoExtension.class) @DisplayName("Unit tests for AsyncBatchServiceImpl") -class AsyncDBBatchServiceImplUnitTest { +class DBBatchServiceImplUnitTest { private static final Long jobId = Instancio.create(Long.class); @@ -36,7 +36,7 @@ class AsyncDBBatchServiceImplUnitTest { private JobExplorer jobExplorer; @InjectMocks - private AsyncDBBatchServiceImpl asyncBatchService; + private DBBatchServiceImpl asyncBatchService; @Test @DisplayName("Should run job successfully and return job execution id") diff --git a/spring-batch-examples/src/test/java/com/io/example/dbReader/async/util/DataUtils.java b/spring-batch-examples/src/test/java/com/io/example/dbReader/util/DataUtils.java similarity index 91% rename from spring-batch-examples/src/test/java/com/io/example/dbReader/async/util/DataUtils.java rename to spring-batch-examples/src/test/java/com/io/example/dbReader/util/DataUtils.java index 9195dfa..33f771e 100644 --- a/spring-batch-examples/src/test/java/com/io/example/dbReader/async/util/DataUtils.java +++ b/spring-batch-examples/src/test/java/com/io/example/dbReader/util/DataUtils.java @@ -1,6 +1,6 @@ -package com.io.example.dbReader.async.util; +package com.io.example.dbReader.util; -import com.io.example.dbReader.async.model.entity.TestEntity; +import com.io.example.dbReader.model.entity.TestEntity; import lombok.SneakyThrows; import org.instancio.Instancio; import org.springframework.batch.core.JobParameters; diff --git a/spring-batch-examples/src/test/java/com/io/example/fileReader/controller/TestControllerTest.java b/spring-batch-examples/src/test/java/com/io/example/fileReader/controller/TestControllerTest.java new file mode 100644 index 0000000..3ccfa65 --- /dev/null +++ b/spring-batch-examples/src/test/java/com/io/example/fileReader/controller/TestControllerTest.java @@ -0,0 +1,71 @@ +package com.io.example.fileReader.controller; + +import com.io.example.fileReader.exception.BusinessException; +import com.io.example.fileReader.service.FileBatchService; +import org.instancio.Instancio; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.springframework.batch.core.BatchStatus; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.web.servlet.MockMvc; + +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@DisplayName("TestController - Unit tests with MockMvc") +@WebMvcTest(TestController.class) +class TestControllerTest { + + private static final Long jobId = Instancio.create(Long.class); + + @Autowired + private MockMvc mockMvc; + + @MockitoBean + private FileBatchService fileBatchService; + + @Test + @DisplayName("GET /job/process → should return job ID when service runs successfully") + void shouldReturnJobIdWhenProcessJobIsCalled() throws Exception { + + when(fileBatchService.runJob()).thenReturn(jobId); + + mockMvc.perform(get("/job/process") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(content().string(String.valueOf(jobId))); + } + + @ParameterizedTest + @EnumSource(BatchStatus.class) + @DisplayName("GET /job/{jobId}/status → should return correct status for each BatchStatus") + void shouldReturnJobStatusForAllBatchStatuses(BatchStatus status) throws Exception { + + when(fileBatchService.getJobStatus(jobId)).thenReturn(status); + + mockMvc.perform(get("/job/{jobId}/status", jobId) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(content().string(status.name())); + } + + @Test + @DisplayName("GET /job/{jobId}/status → should return 404 when job ID is invalid") + void shouldReturn404WhenJobIdIsInvalid() throws Exception { + + when(fileBatchService.getJobStatus(jobId)) + .thenThrow(new BusinessException("JobExecution not found for this id: " + jobId)); + + mockMvc.perform(get("/job/{jobId}/status", jobId) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isNotFound()); + } + +} diff --git a/spring-batch-examples/src/test/java/com/io/example/fileReader/mapper/StudentMapperTest.java b/spring-batch-examples/src/test/java/com/io/example/fileReader/mapper/StudentMapperTest.java new file mode 100644 index 0000000..07e82d5 --- /dev/null +++ b/spring-batch-examples/src/test/java/com/io/example/fileReader/mapper/StudentMapperTest.java @@ -0,0 +1,29 @@ +package com.io.example.fileReader.mapper; + +import com.io.example.fileReader.dto.StudentDto; +import org.junit.jupiter.api.Test; +import org.instancio.Instancio; +import org.springframework.batch.extensions.excel.support.rowset.RowSet; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.*; + +class StudentMapperTest { + + @Test + void shouldMapRowSetUsingInstancio() { + StudentDto sample = Instancio.create(StudentDto.class); + + RowSet rowSet = mock(RowSet.class); + when(rowSet.getCurrentRow()).thenReturn(new String[]{ + sample.getNome(), + sample.getTurma(), + sample.getData().toString() + }); + + StudentMapper mapper = new StudentMapper(); + StudentDto dto = mapper.mapRow(rowSet); + + assertEquals(sample, dto); + } +} diff --git a/spring-batch-examples/src/test/resources/application-asyncDbReader.yml b/spring-batch-examples/src/test/resources/application-asyncDbReader.yml deleted file mode 100644 index 6ec2115..0000000 --- a/spring-batch-examples/src/test/resources/application-asyncDbReader.yml +++ /dev/null @@ -1,7 +0,0 @@ -spring: - - batch: - initialize-schema: always - job: - enabled: false - batch-size: 1 diff --git a/spring-batch-examples/src/test/resources/application-dbReader-test.yml b/spring-batch-examples/src/test/resources/application-dbReader-test.yml new file mode 100644 index 0000000..f1b14b7 --- /dev/null +++ b/spring-batch-examples/src/test/resources/application-dbReader-test.yml @@ -0,0 +1,26 @@ +server: + port: 8081 + +spring: + + datasource: + url: jdbc:h2:mem:db + driver-class-name: org.h2.Driver + username: sa + password: + + jpa: + hibernate: + ddl-auto: create-drop + + h2: + console: + enabled: true + path: /h2-console + + batch: + initialize-schema: always + job: + enabled: false + batch-size: 500 + chunk-size: 200 From c63d689beec0cece9bd2c502b00783c285e5b6a9 Mon Sep 17 00:00:00 2001 From: igorcampos-dev Date: Sat, 18 Oct 2025 15:33:28 -0300 Subject: [PATCH 09/16] feat(spring-batch-examples): separate spring-batch examples in two modules, add application configuration, integration tests, and utility methods for batch processing --- pom.xml | 5 +- .../.gitattributes | 0 .../.github/workflows/maven.yml | 30 ++ .../.gitignore | 0 .../.mvn/wrapper/maven-wrapper.properties | 0 .../README.md | 7 - .../mvnw | 0 .../mvnw.cmd | 0 spring-batch-db-examples/pom.xml | 172 ++++++++++ .../example/DbReaderExampleApplication.java | 6 +- .../com/io/example}/config/BatchConfig.java | 16 +- .../example}/controller/TestController.java | 11 +- .../example}/exception/BusinessException.java | 2 +- .../exception/GlobalHandlerException.java | 2 +- .../main/java/com/io/example}/init/Init.java | 6 +- .../java/com/io/example}/job/BatchJob.java | 4 +- .../listener/LoggingStepListener.java | 2 +- .../io/example/mapper/TestEntityMapper.java | 8 +- .../com/io/example}/model/dto/TestDto.java | 2 +- .../io/example}/model/entity/TestEntity.java | 2 +- .../repository/TestEntityRepository.java | 4 +- .../repository/query/TestEntityQuery.java | 2 +- .../io/example}/service/DBBatchService.java | 2 +- .../com/io/example/service/TestService.java | 7 + .../service/impl/DBBatchServiceImpl.java | 12 +- .../service/impl/TestServiceImpl.java | 6 +- .../src/main/resources/application.yml | 0 .../controller/TestControllerTest.java | 8 +- .../io/example/init/InitIntegrationTest.java | 29 ++ .../example}/job/BatchJobIntegrationTest.java | 26 +- .../example/mapper/TestEntityMapperTest.java | 10 +- .../service/DBBatchServiceImplTest.java | 93 ++++++ .../example/service/TestServiceImplTest.java | 18 ++ .../java/com/io/example}/util/DataUtils.java | 4 +- .../src/test/resources/application-test.yml | 4 +- .../example/dbReader/service/TestService.java | 7 - .../java/com/io/example/fileReader/README.md | 40 --- .../fileReader/service/TestService.java | 7 - spring-batch-file-examples/.gitattributes | 2 + .../.github/workflows/maven.yml | 30 ++ spring-batch-file-examples/.gitignore | 32 ++ .../.mvn/wrapper/maven-wrapper.properties | 3 + spring-batch-file-examples/README.md | 30 ++ spring-batch-file-examples/mvnw | 295 ++++++++++++++++++ spring-batch-file-examples/mvnw.cmd | 189 +++++++++++ .../pom.xml | 76 ++++- .../FileReaderExampleApplication.java | 2 +- .../src/main/java/com/io/example/README.md | 38 +++ .../io/example}/config/ExcelBatchConfig.java | 34 +- .../example}/controller/TestController.java | 14 +- .../java/com/io/example}/dto/StudentDto.java | 8 +- .../example}/exception/BusinessException.java | 2 +- .../exception/GlobalHandlerException.java | 3 +- .../java/com/io/example}/job/FileJob.java | 2 +- .../com/io/example}/mapper/StudentMapper.java | 10 +- .../io/example}/service/FileBatchService.java | 2 +- .../com/io/example/service/TestService.java | 7 + .../service/impl/FileBatchServiceImpl.java | 7 +- .../service/impl/TestServiceImpl.java | 6 +- .../src/main/resources/application.yml | 19 +- .../src/main/resources/files}/students.xlsx | Bin .../FileReaderExampleApplicationTest.java | 25 ++ .../controller/TestControllerTest.java | 13 +- .../example/job/BatchJobIntegrationTest.java | 76 +++++ .../io/example}/mapper/StudentMapperTest.java | 10 +- .../service/FileBatchServiceImplTest.java | 18 +- .../example/service/TestServiceImplTest.java | 18 ++ .../java/com/io/example/util/DataUtils.java | 15 + .../src/test/resources/application-test.yml | 25 ++ .../test/resources/files/students-empty.xlsx | Bin 0 -> 8678 bytes .../test/resources/files/students-error.xlsx | 3 + .../src/test/resources/files/students.xlsx | Bin 0 -> 4955 bytes 72 files changed, 1348 insertions(+), 220 deletions(-) rename {spring-batch-examples => spring-batch-db-examples}/.gitattributes (100%) create mode 100644 spring-batch-db-examples/.github/workflows/maven.yml rename {spring-batch-examples => spring-batch-db-examples}/.gitignore (100%) rename {spring-batch-examples => spring-batch-db-examples}/.mvn/wrapper/maven-wrapper.properties (100%) rename {spring-batch-examples/src/main/java/com/io/example/dbReader => spring-batch-db-examples}/README.md (82%) rename {spring-batch-examples => spring-batch-db-examples}/mvnw (100%) rename {spring-batch-examples => spring-batch-db-examples}/mvnw.cmd (100%) create mode 100644 spring-batch-db-examples/pom.xml rename spring-batch-examples/src/main/java/com/io/example/dbReader/DbReaderAsyncExampleApplication.java => spring-batch-db-examples/src/main/java/com/io/example/DbReaderExampleApplication.java (56%) rename {spring-batch-examples/src/main/java/com/io/example/dbReader => spring-batch-db-examples/src/main/java/com/io/example}/config/BatchConfig.java (91%) rename {spring-batch-examples/src/main/java/com/io/example/dbReader => spring-batch-db-examples/src/main/java/com/io/example}/controller/TestController.java (77%) rename {spring-batch-examples/src/main/java/com/io/example/dbReader => spring-batch-db-examples/src/main/java/com/io/example}/exception/BusinessException.java (84%) rename {spring-batch-examples/src/main/java/com/io/example/dbReader => spring-batch-db-examples/src/main/java/com/io/example}/exception/GlobalHandlerException.java (93%) rename {spring-batch-examples/src/main/java/com/io/example/dbReader => spring-batch-db-examples/src/main/java/com/io/example}/init/Init.java (88%) rename {spring-batch-examples/src/main/java/com/io/example/dbReader => spring-batch-db-examples/src/main/java/com/io/example}/job/BatchJob.java (85%) rename {spring-batch-examples/src/main/java/com/io/example/dbReader => spring-batch-db-examples/src/main/java/com/io/example}/listener/LoggingStepListener.java (95%) rename spring-batch-examples/src/main/java/com/io/example/dbReader/mapper/TestEntityToDtoMapper.java => spring-batch-db-examples/src/main/java/com/io/example/mapper/TestEntityMapper.java (65%) rename {spring-batch-examples/src/main/java/com/io/example/dbReader => spring-batch-db-examples/src/main/java/com/io/example}/model/dto/TestDto.java (88%) rename {spring-batch-examples/src/main/java/com/io/example/dbReader => spring-batch-db-examples/src/main/java/com/io/example}/model/entity/TestEntity.java (91%) rename {spring-batch-examples/src/main/java/com/io/example/dbReader => spring-batch-db-examples/src/main/java/com/io/example}/repository/TestEntityRepository.java (67%) rename {spring-batch-examples/src/main/java/com/io/example/dbReader => spring-batch-db-examples/src/main/java/com/io/example}/repository/query/TestEntityQuery.java (92%) rename {spring-batch-examples/src/main/java/com/io/example/dbReader => spring-batch-db-examples/src/main/java/com/io/example}/service/DBBatchService.java (78%) create mode 100644 spring-batch-db-examples/src/main/java/com/io/example/service/TestService.java rename {spring-batch-examples/src/main/java/com/io/example/dbReader => spring-batch-db-examples/src/main/java/com/io/example}/service/impl/DBBatchServiceImpl.java (81%) rename {spring-batch-examples/src/main/java/com/io/example/dbReader => spring-batch-db-examples/src/main/java/com/io/example}/service/impl/TestServiceImpl.java (63%) rename spring-batch-examples/src/main/resources/application-excelFileReader.yml => spring-batch-db-examples/src/main/resources/application.yml (100%) rename {spring-batch-examples/src/test/java/com/io/example/dbReader => spring-batch-db-examples/src/test/java/com/io/example}/controller/TestControllerTest.java (92%) create mode 100644 spring-batch-db-examples/src/test/java/com/io/example/init/InitIntegrationTest.java rename {spring-batch-examples/src/test/java/com/io/example/dbReader => spring-batch-db-examples/src/test/java/com/io/example}/job/BatchJobIntegrationTest.java (70%) rename spring-batch-examples/src/test/java/com/io/example/dbReader/mapper/TestEntityToDtoMapperTest.java => spring-batch-db-examples/src/test/java/com/io/example/mapper/TestEntityMapperTest.java (76%) create mode 100644 spring-batch-db-examples/src/test/java/com/io/example/service/DBBatchServiceImplTest.java create mode 100644 spring-batch-db-examples/src/test/java/com/io/example/service/TestServiceImplTest.java rename {spring-batch-examples/src/test/java/com/io/example/dbReader => spring-batch-db-examples/src/test/java/com/io/example}/util/DataUtils.java (92%) rename spring-batch-examples/src/test/resources/application-dbReader-test.yml => spring-batch-db-examples/src/test/resources/application-test.yml (88%) delete mode 100644 spring-batch-examples/src/main/java/com/io/example/dbReader/service/TestService.java delete mode 100644 spring-batch-examples/src/main/java/com/io/example/fileReader/README.md delete mode 100644 spring-batch-examples/src/main/java/com/io/example/fileReader/service/TestService.java create mode 100644 spring-batch-file-examples/.gitattributes create mode 100644 spring-batch-file-examples/.github/workflows/maven.yml create mode 100644 spring-batch-file-examples/.gitignore create mode 100644 spring-batch-file-examples/.mvn/wrapper/maven-wrapper.properties create mode 100644 spring-batch-file-examples/README.md create mode 100755 spring-batch-file-examples/mvnw create mode 100644 spring-batch-file-examples/mvnw.cmd rename {spring-batch-examples => spring-batch-file-examples}/pom.xml (65%) rename {spring-batch-examples/src/main/java/com/io/example/fileReader => spring-batch-file-examples/src/main/java/com/io/example}/FileReaderExampleApplication.java (89%) create mode 100644 spring-batch-file-examples/src/main/java/com/io/example/README.md rename {spring-batch-examples/src/main/java/com/io/example/fileReader => spring-batch-file-examples/src/main/java/com/io/example}/config/ExcelBatchConfig.java (71%) rename {spring-batch-examples/src/main/java/com/io/example/fileReader => spring-batch-file-examples/src/main/java/com/io/example}/controller/TestController.java (66%) rename {spring-batch-examples/src/main/java/com/io/example/fileReader => spring-batch-file-examples/src/main/java/com/io/example}/dto/StudentDto.java (65%) rename {spring-batch-examples/src/main/java/com/io/example/fileReader => spring-batch-file-examples/src/main/java/com/io/example}/exception/BusinessException.java (84%) rename {spring-batch-examples/src/main/java/com/io/example/fileReader => spring-batch-file-examples/src/main/java/com/io/example}/exception/GlobalHandlerException.java (84%) rename {spring-batch-examples/src/main/java/com/io/example/fileReader => spring-batch-file-examples/src/main/java/com/io/example}/job/FileJob.java (94%) rename {spring-batch-examples/src/main/java/com/io/example/fileReader => spring-batch-file-examples/src/main/java/com/io/example}/mapper/StudentMapper.java (66%) rename {spring-batch-examples/src/main/java/com/io/example/fileReader => spring-batch-file-examples/src/main/java/com/io/example}/service/FileBatchService.java (77%) create mode 100644 spring-batch-file-examples/src/main/java/com/io/example/service/TestService.java rename {spring-batch-examples/src/main/java/com/io/example/fileReader => spring-batch-file-examples/src/main/java/com/io/example}/service/impl/FileBatchServiceImpl.java (89%) rename {spring-batch-examples/src/main/java/com/io/example/fileReader => spring-batch-file-examples/src/main/java/com/io/example}/service/impl/TestServiceImpl.java (64%) rename spring-batch-examples/src/main/resources/application-dbReader.yml => spring-batch-file-examples/src/main/resources/application.yml (54%) rename {spring-batch-examples/src/main/resources => spring-batch-file-examples/src/main/resources/files}/students.xlsx (100%) create mode 100644 spring-batch-file-examples/src/test/java/com/io/example/FileReaderExampleApplicationTest.java rename {spring-batch-examples/src/test/java/com/io/example/fileReader => spring-batch-file-examples/src/test/java/com/io/example}/controller/TestControllerTest.java (88%) create mode 100644 spring-batch-file-examples/src/test/java/com/io/example/job/BatchJobIntegrationTest.java rename {spring-batch-examples/src/test/java/com/io/example/fileReader => spring-batch-file-examples/src/test/java/com/io/example}/mapper/StudentMapperTest.java (76%) rename spring-batch-examples/src/test/java/com/io/example/dbReader/service/DBBatchServiceImplUnitTest.java => spring-batch-file-examples/src/test/java/com/io/example/service/FileBatchServiceImplTest.java (85%) create mode 100644 spring-batch-file-examples/src/test/java/com/io/example/service/TestServiceImplTest.java create mode 100644 spring-batch-file-examples/src/test/java/com/io/example/util/DataUtils.java create mode 100644 spring-batch-file-examples/src/test/resources/application-test.yml create mode 100644 spring-batch-file-examples/src/test/resources/files/students-empty.xlsx create mode 100644 spring-batch-file-examples/src/test/resources/files/students-error.xlsx create mode 100644 spring-batch-file-examples/src/test/resources/files/students.xlsx diff --git a/pom.xml b/pom.xml index cdbe9d0..7ed636f 100644 --- a/pom.xml +++ b/pom.xml @@ -17,7 +17,8 @@ spring-keycloak-example spring-jasper-example spring-oracle-example - spring-batch-examples + spring-batch-file-examples + spring-batch-db-examples - \ No newline at end of file + diff --git a/spring-batch-examples/.gitattributes b/spring-batch-db-examples/.gitattributes similarity index 100% rename from spring-batch-examples/.gitattributes rename to spring-batch-db-examples/.gitattributes diff --git a/spring-batch-db-examples/.github/workflows/maven.yml b/spring-batch-db-examples/.github/workflows/maven.yml new file mode 100644 index 0000000..16d1a66 --- /dev/null +++ b/spring-batch-db-examples/.github/workflows/maven.yml @@ -0,0 +1,30 @@ +name: CI Build + +on: + push: + branches: + - "**" + +jobs: + build: + name: Build + runs-on: ubuntu-latest + strategy: + matrix: + distribution: [ 'temurin' ] + java: [ '21' ] + steps: + - uses: actions/checkout@v5 + + - name: Setup Java 21 + uses: actions/setup-java@v5 + with: + java-version: ${{ matrix.java }} + distribution: ${{ matrix.distribution }} + cache: 'maven' + + - name: Grant execute permission for mvnw + run: chmod +x mvnw + + - name: Build with Maven + run: ./mvnw clean verify \ No newline at end of file diff --git a/spring-batch-examples/.gitignore b/spring-batch-db-examples/.gitignore similarity index 100% rename from spring-batch-examples/.gitignore rename to spring-batch-db-examples/.gitignore diff --git a/spring-batch-examples/.mvn/wrapper/maven-wrapper.properties b/spring-batch-db-examples/.mvn/wrapper/maven-wrapper.properties similarity index 100% rename from spring-batch-examples/.mvn/wrapper/maven-wrapper.properties rename to spring-batch-db-examples/.mvn/wrapper/maven-wrapper.properties diff --git a/spring-batch-examples/src/main/java/com/io/example/dbReader/README.md b/spring-batch-db-examples/README.md similarity index 82% rename from spring-batch-examples/src/main/java/com/io/example/dbReader/README.md rename to spring-batch-db-examples/README.md index f165640..3f52ae7 100644 --- a/spring-batch-examples/src/main/java/com/io/example/dbReader/README.md +++ b/spring-batch-db-examples/README.md @@ -28,10 +28,3 @@ The job reads **10,000 records** from a database table, simulating item processi - **H2 Database** --- - -## ▶️ Running the Project - -To run the asynchronous batch job, simply activate the `asyncDbReader` profile: - -```bash -./mvnw spring-boot:run -Dspring-boot.run.profiles=asyncDbReader diff --git a/spring-batch-examples/mvnw b/spring-batch-db-examples/mvnw similarity index 100% rename from spring-batch-examples/mvnw rename to spring-batch-db-examples/mvnw diff --git a/spring-batch-examples/mvnw.cmd b/spring-batch-db-examples/mvnw.cmd similarity index 100% rename from spring-batch-examples/mvnw.cmd rename to spring-batch-db-examples/mvnw.cmd diff --git a/spring-batch-db-examples/pom.xml b/spring-batch-db-examples/pom.xml new file mode 100644 index 0000000..63b78b9 --- /dev/null +++ b/spring-batch-db-examples/pom.xml @@ -0,0 +1,172 @@ + + + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 3.5.6 + + + + com.io + spring-batch-db-examples + 0.0.1-SNAPSHOT + spring-batch-db-examples + Demo project for Spring Boot + + + 21 + 1.18.40 + 2.3.232 + 3.5.6 + 5.2.3 + 3.14.1 + 5.5.1 + 0.8.14 + + + + + + org.springframework.boot + spring-boot-starter-web + ${spring.boot.version} + + + + org.springframework.boot + spring-boot-starter-data-jpa + ${spring.boot.version} + + + + org.springframework.boot + spring-boot-starter-batch + ${spring.boot.version} + + + + org.springframework.batch + spring-batch-integration + ${spring.batch.version} + + + + com.h2database + h2 + runtime + ${h2.version} + + + + org.projectlombok + lombok + true + ${lombok.version} + + + + org.instancio + instancio-core + ${instancio.version} + + + + org.springframework.boot + spring-boot-starter-test + ${spring.boot.version} + test + + + + org.springframework.batch + spring-batch-test + test + ${spring.batch.version} + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven.compiler.plugin.version} + + + + org.projectlombok + lombok + + + + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring.boot.version} + + + + org.projectlombok + lombok + + + + + + + org.jacoco + jacoco-maven-plugin + ${jacoco.version} + + + + prepare-agent + + + + report + test + + report + + + + check + + check + + + + + BUNDLE + + + INSTRUCTION + COVEREDRATIO + 0.80 + + + BRANCH + COVEREDRATIO + 0.70 + + + + + + + + + + + + + diff --git a/spring-batch-examples/src/main/java/com/io/example/dbReader/DbReaderAsyncExampleApplication.java b/spring-batch-db-examples/src/main/java/com/io/example/DbReaderExampleApplication.java similarity index 56% rename from spring-batch-examples/src/main/java/com/io/example/dbReader/DbReaderAsyncExampleApplication.java rename to spring-batch-db-examples/src/main/java/com/io/example/DbReaderExampleApplication.java index fdfa55d..954f79e 100644 --- a/spring-batch-examples/src/main/java/com/io/example/dbReader/DbReaderAsyncExampleApplication.java +++ b/spring-batch-db-examples/src/main/java/com/io/example/DbReaderExampleApplication.java @@ -1,13 +1,13 @@ -package com.io.example.dbReader; +package com.io.example; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication -public class DbReaderAsyncExampleApplication { +public class DbReaderExampleApplication { public static void main(String[] args) { - SpringApplication.run(DbReaderAsyncExampleApplication.class, args); + SpringApplication.run(DbReaderExampleApplication.class, args); } } diff --git a/spring-batch-examples/src/main/java/com/io/example/dbReader/config/BatchConfig.java b/spring-batch-db-examples/src/main/java/com/io/example/config/BatchConfig.java similarity index 91% rename from spring-batch-examples/src/main/java/com/io/example/dbReader/config/BatchConfig.java rename to spring-batch-db-examples/src/main/java/com/io/example/config/BatchConfig.java index ab0a085..cf3b3f9 100644 --- a/spring-batch-examples/src/main/java/com/io/example/dbReader/config/BatchConfig.java +++ b/spring-batch-db-examples/src/main/java/com/io/example/config/BatchConfig.java @@ -1,10 +1,10 @@ -package com.io.example.dbReader.config; +package com.io.example.config; -import com.io.example.dbReader.model.dto.TestDto; -import com.io.example.dbReader.model.entity.TestEntity; -import com.io.example.dbReader.listener.LoggingStepListener; -import com.io.example.dbReader.mapper.TestEntityToDtoMapper; -import com.io.example.dbReader.service.TestService; +import com.io.example.model.dto.TestDto; +import com.io.example.model.entity.TestEntity; +import com.io.example.listener.LoggingStepListener; +import com.io.example.mapper.TestEntityMapper; +import com.io.example.service.TestService; import lombok.RequiredArgsConstructor; import org.springframework.batch.core.Step; import org.springframework.batch.core.launch.JobLauncher; @@ -32,7 +32,7 @@ import java.util.Map; import java.util.concurrent.Future; -import static com.io.example.dbReader.repository.query.TestEntityQuery.*; +import static com.io.example.repository.query.TestEntityQuery.*; @Configuration @RequiredArgsConstructor @@ -52,7 +52,7 @@ public JobLauncher asyncJobLauncher(JobRepository jobRepository) throws Exceptio @Bean public AsyncItemProcessor asyncProcessor(AsyncTaskExecutor asyncTaskExecutor) { AsyncItemProcessor processor = new AsyncItemProcessor<>(); - processor.setDelegate(new TestEntityToDtoMapper()); + processor.setDelegate(new TestEntityMapper()); processor.setTaskExecutor(asyncTaskExecutor); return processor; } diff --git a/spring-batch-examples/src/main/java/com/io/example/dbReader/controller/TestController.java b/spring-batch-db-examples/src/main/java/com/io/example/controller/TestController.java similarity index 77% rename from spring-batch-examples/src/main/java/com/io/example/dbReader/controller/TestController.java rename to spring-batch-db-examples/src/main/java/com/io/example/controller/TestController.java index 30d5fc3..203a487 100644 --- a/spring-batch-examples/src/main/java/com/io/example/dbReader/controller/TestController.java +++ b/spring-batch-db-examples/src/main/java/com/io/example/controller/TestController.java @@ -1,9 +1,11 @@ -package com.io.example.dbReader.controller; +package com.io.example.controller; -import com.io.example.dbReader.exception.BusinessException; -import com.io.example.dbReader.service.DBBatchService; +import com.io.example.exception.BusinessException; +import com.io.example.exception.GlobalHandlerException; +import com.io.example.service.DBBatchService; import lombok.RequiredArgsConstructor; import org.springframework.batch.core.BatchStatus; +import org.springframework.context.annotation.Import; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -13,6 +15,7 @@ @RestController @RequestMapping("/job") @RequiredArgsConstructor +@Import(GlobalHandlerException.class) public class TestController { private final DBBatchService DBBatchService; @@ -29,7 +32,7 @@ public ResponseEntity getJobStatus(@PathVariable Long jobId) { BatchStatus status = DBBatchService.getJobStatus(jobId); return ResponseEntity.ok(status.name()); } catch (BusinessException e) { - return ResponseEntity.notFound().build(); + throw e; } } diff --git a/spring-batch-examples/src/main/java/com/io/example/dbReader/exception/BusinessException.java b/spring-batch-db-examples/src/main/java/com/io/example/exception/BusinessException.java similarity index 84% rename from spring-batch-examples/src/main/java/com/io/example/dbReader/exception/BusinessException.java rename to spring-batch-db-examples/src/main/java/com/io/example/exception/BusinessException.java index cee5770..bef631f 100644 --- a/spring-batch-examples/src/main/java/com/io/example/dbReader/exception/BusinessException.java +++ b/spring-batch-db-examples/src/main/java/com/io/example/exception/BusinessException.java @@ -1,4 +1,4 @@ -package com.io.example.dbReader.exception; +package com.io.example.exception; public class BusinessException extends RuntimeException { diff --git a/spring-batch-examples/src/main/java/com/io/example/dbReader/exception/GlobalHandlerException.java b/spring-batch-db-examples/src/main/java/com/io/example/exception/GlobalHandlerException.java similarity index 93% rename from spring-batch-examples/src/main/java/com/io/example/dbReader/exception/GlobalHandlerException.java rename to spring-batch-db-examples/src/main/java/com/io/example/exception/GlobalHandlerException.java index 66ccdae..d345db5 100644 --- a/spring-batch-examples/src/main/java/com/io/example/dbReader/exception/GlobalHandlerException.java +++ b/spring-batch-db-examples/src/main/java/com/io/example/exception/GlobalHandlerException.java @@ -1,4 +1,4 @@ -package com.io.example.dbReader.exception; +package com.io.example.exception; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; diff --git a/spring-batch-examples/src/main/java/com/io/example/dbReader/init/Init.java b/spring-batch-db-examples/src/main/java/com/io/example/init/Init.java similarity index 88% rename from spring-batch-examples/src/main/java/com/io/example/dbReader/init/Init.java rename to spring-batch-db-examples/src/main/java/com/io/example/init/Init.java index 07cf33e..1ee1a6b 100644 --- a/spring-batch-examples/src/main/java/com/io/example/dbReader/init/Init.java +++ b/spring-batch-db-examples/src/main/java/com/io/example/init/Init.java @@ -1,7 +1,7 @@ -package com.io.example.dbReader.init; +package com.io.example.init; -import com.io.example.dbReader.model.entity.TestEntity; -import com.io.example.dbReader.repository.TestEntityRepository; +import com.io.example.model.entity.TestEntity; +import com.io.example.repository.TestEntityRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.CommandLineRunner; diff --git a/spring-batch-examples/src/main/java/com/io/example/dbReader/job/BatchJob.java b/spring-batch-db-examples/src/main/java/com/io/example/job/BatchJob.java similarity index 85% rename from spring-batch-examples/src/main/java/com/io/example/dbReader/job/BatchJob.java rename to spring-batch-db-examples/src/main/java/com/io/example/job/BatchJob.java index aa2c319..7e297f3 100644 --- a/spring-batch-examples/src/main/java/com/io/example/dbReader/job/BatchJob.java +++ b/spring-batch-db-examples/src/main/java/com/io/example/job/BatchJob.java @@ -1,4 +1,4 @@ -package com.io.example.dbReader.job; +package com.io.example.job; import lombok.RequiredArgsConstructor; import org.springframework.batch.core.Job; @@ -14,7 +14,7 @@ public class BatchJob { @Bean public Job job(JobRepository jobRepository, Step asyncStep) { - return new JobBuilder("asyncBatchJob", jobRepository) + return new JobBuilder("batchJob", jobRepository) .start(asyncStep) .build(); } diff --git a/spring-batch-examples/src/main/java/com/io/example/dbReader/listener/LoggingStepListener.java b/spring-batch-db-examples/src/main/java/com/io/example/listener/LoggingStepListener.java similarity index 95% rename from spring-batch-examples/src/main/java/com/io/example/dbReader/listener/LoggingStepListener.java rename to spring-batch-db-examples/src/main/java/com/io/example/listener/LoggingStepListener.java index 49a9689..d6364f5 100644 --- a/spring-batch-examples/src/main/java/com/io/example/dbReader/listener/LoggingStepListener.java +++ b/spring-batch-db-examples/src/main/java/com/io/example/listener/LoggingStepListener.java @@ -1,4 +1,4 @@ -package com.io.example.dbReader.listener; +package com.io.example.listener; import lombok.extern.slf4j.Slf4j; import org.springframework.batch.core.ExitStatus; diff --git a/spring-batch-examples/src/main/java/com/io/example/dbReader/mapper/TestEntityToDtoMapper.java b/spring-batch-db-examples/src/main/java/com/io/example/mapper/TestEntityMapper.java similarity index 65% rename from spring-batch-examples/src/main/java/com/io/example/dbReader/mapper/TestEntityToDtoMapper.java rename to spring-batch-db-examples/src/main/java/com/io/example/mapper/TestEntityMapper.java index fc78398..5c1b051 100644 --- a/spring-batch-examples/src/main/java/com/io/example/dbReader/mapper/TestEntityToDtoMapper.java +++ b/spring-batch-db-examples/src/main/java/com/io/example/mapper/TestEntityMapper.java @@ -1,10 +1,10 @@ -package com.io.example.dbReader.mapper; +package com.io.example.mapper; -import com.io.example.dbReader.model.dto.TestDto; -import com.io.example.dbReader.model.entity.TestEntity; +import com.io.example.model.dto.TestDto; +import com.io.example.model.entity.TestEntity; import org.springframework.batch.item.ItemProcessor; -public class TestEntityToDtoMapper implements ItemProcessor { +public class TestEntityMapper implements ItemProcessor { @Override public TestDto process(TestEntity entity) { diff --git a/spring-batch-examples/src/main/java/com/io/example/dbReader/model/dto/TestDto.java b/spring-batch-db-examples/src/main/java/com/io/example/model/dto/TestDto.java similarity index 88% rename from spring-batch-examples/src/main/java/com/io/example/dbReader/model/dto/TestDto.java rename to spring-batch-db-examples/src/main/java/com/io/example/model/dto/TestDto.java index 5285b93..d2aa953 100644 --- a/spring-batch-examples/src/main/java/com/io/example/dbReader/model/dto/TestDto.java +++ b/spring-batch-db-examples/src/main/java/com/io/example/model/dto/TestDto.java @@ -1,4 +1,4 @@ -package com.io.example.dbReader.model.dto; +package com.io.example.model.dto; import lombok.AllArgsConstructor; import lombok.Builder; diff --git a/spring-batch-examples/src/main/java/com/io/example/dbReader/model/entity/TestEntity.java b/spring-batch-db-examples/src/main/java/com/io/example/model/entity/TestEntity.java similarity index 91% rename from spring-batch-examples/src/main/java/com/io/example/dbReader/model/entity/TestEntity.java rename to spring-batch-db-examples/src/main/java/com/io/example/model/entity/TestEntity.java index 2dcd37f..acce612 100644 --- a/spring-batch-examples/src/main/java/com/io/example/dbReader/model/entity/TestEntity.java +++ b/spring-batch-db-examples/src/main/java/com/io/example/model/entity/TestEntity.java @@ -1,4 +1,4 @@ -package com.io.example.dbReader.model.entity; +package com.io.example.model.entity; import jakarta.persistence.*; import lombok.AllArgsConstructor; diff --git a/spring-batch-examples/src/main/java/com/io/example/dbReader/repository/TestEntityRepository.java b/spring-batch-db-examples/src/main/java/com/io/example/repository/TestEntityRepository.java similarity index 67% rename from spring-batch-examples/src/main/java/com/io/example/dbReader/repository/TestEntityRepository.java rename to spring-batch-db-examples/src/main/java/com/io/example/repository/TestEntityRepository.java index 8e32879..de20b2f 100644 --- a/spring-batch-examples/src/main/java/com/io/example/dbReader/repository/TestEntityRepository.java +++ b/spring-batch-db-examples/src/main/java/com/io/example/repository/TestEntityRepository.java @@ -1,6 +1,6 @@ -package com.io.example.dbReader.repository; +package com.io.example.repository; -import com.io.example.dbReader.model.entity.TestEntity; +import com.io.example.model.entity.TestEntity; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; diff --git a/spring-batch-examples/src/main/java/com/io/example/dbReader/repository/query/TestEntityQuery.java b/spring-batch-db-examples/src/main/java/com/io/example/repository/query/TestEntityQuery.java similarity index 92% rename from spring-batch-examples/src/main/java/com/io/example/dbReader/repository/query/TestEntityQuery.java rename to spring-batch-db-examples/src/main/java/com/io/example/repository/query/TestEntityQuery.java index f78dee7..6f084b7 100644 --- a/spring-batch-examples/src/main/java/com/io/example/dbReader/repository/query/TestEntityQuery.java +++ b/spring-batch-db-examples/src/main/java/com/io/example/repository/query/TestEntityQuery.java @@ -1,4 +1,4 @@ -package com.io.example.dbReader.repository.query; +package com.io.example.repository.query; import lombok.AccessLevel; import lombok.RequiredArgsConstructor; diff --git a/spring-batch-examples/src/main/java/com/io/example/dbReader/service/DBBatchService.java b/spring-batch-db-examples/src/main/java/com/io/example/service/DBBatchService.java similarity index 78% rename from spring-batch-examples/src/main/java/com/io/example/dbReader/service/DBBatchService.java rename to spring-batch-db-examples/src/main/java/com/io/example/service/DBBatchService.java index 3eb8d43..aff7176 100644 --- a/spring-batch-examples/src/main/java/com/io/example/dbReader/service/DBBatchService.java +++ b/spring-batch-db-examples/src/main/java/com/io/example/service/DBBatchService.java @@ -1,4 +1,4 @@ -package com.io.example.dbReader.service; +package com.io.example.service; import org.springframework.batch.core.BatchStatus; diff --git a/spring-batch-db-examples/src/main/java/com/io/example/service/TestService.java b/spring-batch-db-examples/src/main/java/com/io/example/service/TestService.java new file mode 100644 index 0000000..53e3ebf --- /dev/null +++ b/spring-batch-db-examples/src/main/java/com/io/example/service/TestService.java @@ -0,0 +1,7 @@ +package com.io.example.service; + +import com.io.example.model.dto.TestDto; + +public interface TestService { + void print(TestDto testDto); +} diff --git a/spring-batch-examples/src/main/java/com/io/example/dbReader/service/impl/DBBatchServiceImpl.java b/spring-batch-db-examples/src/main/java/com/io/example/service/impl/DBBatchServiceImpl.java similarity index 81% rename from spring-batch-examples/src/main/java/com/io/example/dbReader/service/impl/DBBatchServiceImpl.java rename to spring-batch-db-examples/src/main/java/com/io/example/service/impl/DBBatchServiceImpl.java index b696d37..50e9b5d 100644 --- a/spring-batch-examples/src/main/java/com/io/example/dbReader/service/impl/DBBatchServiceImpl.java +++ b/spring-batch-db-examples/src/main/java/com/io/example/service/impl/DBBatchServiceImpl.java @@ -1,7 +1,7 @@ -package com.io.example.dbReader.service.impl; +package com.io.example.service.impl; -import com.io.example.dbReader.exception.BusinessException; -import com.io.example.dbReader.service.DBBatchService; +import com.io.example.exception.BusinessException; +import com.io.example.service.DBBatchService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.batch.core.*; @@ -15,16 +15,16 @@ public class DBBatchServiceImpl implements DBBatchService { private final JobLauncher asyncJobLauncher; - private final Job asyncBatchJob; + private final Job batchJob; private final JobExplorer jobExplorer; @Override public Long runJob() { - String jobName = asyncBatchJob.getName(); + String jobName = batchJob.getName(); var parameters = getParameters(); try { log.info("Starting async execution of job: {}", jobName); - JobExecution jobExecution = asyncJobLauncher.run(asyncBatchJob, parameters); + JobExecution jobExecution = asyncJobLauncher.run(batchJob, parameters); log.info("Job {} started with status: {}", jobName, jobExecution.getStatus()); return jobExecution.getId(); } catch (Exception e) { diff --git a/spring-batch-examples/src/main/java/com/io/example/dbReader/service/impl/TestServiceImpl.java b/spring-batch-db-examples/src/main/java/com/io/example/service/impl/TestServiceImpl.java similarity index 63% rename from spring-batch-examples/src/main/java/com/io/example/dbReader/service/impl/TestServiceImpl.java rename to spring-batch-db-examples/src/main/java/com/io/example/service/impl/TestServiceImpl.java index 7e82a51..4d5cf17 100644 --- a/spring-batch-examples/src/main/java/com/io/example/dbReader/service/impl/TestServiceImpl.java +++ b/spring-batch-db-examples/src/main/java/com/io/example/service/impl/TestServiceImpl.java @@ -1,7 +1,7 @@ -package com.io.example.dbReader.service.impl; +package com.io.example.service.impl; -import com.io.example.dbReader.model.dto.TestDto; -import com.io.example.dbReader.service.TestService; +import com.io.example.model.dto.TestDto; +import com.io.example.service.TestService; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; diff --git a/spring-batch-examples/src/main/resources/application-excelFileReader.yml b/spring-batch-db-examples/src/main/resources/application.yml similarity index 100% rename from spring-batch-examples/src/main/resources/application-excelFileReader.yml rename to spring-batch-db-examples/src/main/resources/application.yml diff --git a/spring-batch-examples/src/test/java/com/io/example/dbReader/controller/TestControllerTest.java b/spring-batch-db-examples/src/test/java/com/io/example/controller/TestControllerTest.java similarity index 92% rename from spring-batch-examples/src/test/java/com/io/example/dbReader/controller/TestControllerTest.java rename to spring-batch-db-examples/src/test/java/com/io/example/controller/TestControllerTest.java index 38aa463..dc3c73a 100644 --- a/spring-batch-examples/src/test/java/com/io/example/dbReader/controller/TestControllerTest.java +++ b/spring-batch-db-examples/src/test/java/com/io/example/controller/TestControllerTest.java @@ -1,7 +1,7 @@ -package com.io.example.dbReader.controller; +package com.io.example.controller; -import com.io.example.dbReader.exception.BusinessException; -import com.io.example.dbReader.service.DBBatchService; +import com.io.example.exception.BusinessException; +import com.io.example.service.DBBatchService; import org.instancio.Instancio; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -64,7 +64,7 @@ void shouldReturn404WhenJobIdIsInvalid() throws Exception { mockMvc.perform(get("/job/{jobId}/status", jobId) .accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isNotFound()); + .andExpect(status().isBadRequest() ); } } diff --git a/spring-batch-db-examples/src/test/java/com/io/example/init/InitIntegrationTest.java b/spring-batch-db-examples/src/test/java/com/io/example/init/InitIntegrationTest.java new file mode 100644 index 0000000..9ca707b --- /dev/null +++ b/spring-batch-db-examples/src/test/java/com/io/example/init/InitIntegrationTest.java @@ -0,0 +1,29 @@ +package com.io.example.init; + +import com.io.example.repository.TestEntityRepository; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +@SpringBootTest +@ActiveProfiles("test") +class InitIntegrationTest { + + @Autowired + private Init init; + + @Autowired + private TestEntityRepository repository; + + @Test + void run_shouldSaveAllRandomEntities() { + repository.deleteAll(); + init.run(); + int size = repository.findAll().size(); + assertEquals(10000, size); + } + +} diff --git a/spring-batch-examples/src/test/java/com/io/example/dbReader/job/BatchJobIntegrationTest.java b/spring-batch-db-examples/src/test/java/com/io/example/job/BatchJobIntegrationTest.java similarity index 70% rename from spring-batch-examples/src/test/java/com/io/example/dbReader/job/BatchJobIntegrationTest.java rename to spring-batch-db-examples/src/test/java/com/io/example/job/BatchJobIntegrationTest.java index 906e90e..359e8a0 100644 --- a/spring-batch-examples/src/test/java/com/io/example/dbReader/job/BatchJobIntegrationTest.java +++ b/spring-batch-db-examples/src/test/java/com/io/example/job/BatchJobIntegrationTest.java @@ -1,8 +1,8 @@ -package com.io.example.dbReader.job; +package com.io.example.job; -import com.io.example.dbReader.model.dto.TestDto; -import com.io.example.dbReader.model.entity.TestEntity; -import com.io.example.dbReader.service.TestService; +import com.io.example.model.dto.TestDto; +import com.io.example.model.entity.TestEntity; +import com.io.example.service.TestService; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.batch.core.*; @@ -14,14 +14,14 @@ import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.bean.override.mockito.MockitoBean; -import static com.io.example.dbReader.util.DataUtils.*; +import static com.io.example.util.DataUtils.*; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; @SpringBootTest @SpringBatchTest -@ActiveProfiles("dbReader-test") +@ActiveProfiles("test") @DisplayName("BatchJob - Integration test with real Job execution") class BatchJobIntegrationTest { @@ -29,7 +29,7 @@ class BatchJobIntegrationTest { private JobLauncher jobLauncher; @Autowired - private Job asyncBatchJob; + private Job batchJob; @MockitoBean("asyncReader") private JdbcPagingItemReader asyncReader; @@ -38,12 +38,12 @@ class BatchJobIntegrationTest { private TestService testService; @Test - @DisplayName("Should execute asyncBatchJob and complete successfully with mocked reader") + @DisplayName("Should execute batchJob and complete successfully with mocked reader") void shouldExecuteJobSuccessfully() throws Exception { configJdbcPagingItemReaderMock(asyncReader); - JobExecution execution = jobLauncher.run(asyncBatchJob, getParameters()); + JobExecution execution = jobLauncher.run(batchJob, getParameters()); assertThat(execution.getStatus()).isEqualTo(BatchStatus.COMPLETED); verify(testService, times(5)).print(any(TestDto.class)); @@ -51,13 +51,13 @@ void shouldExecuteJobSuccessfully() throws Exception { } @Test - @DisplayName("Should fail executing asyncBatchJob when reader throws exception") + @DisplayName("Should fail executing batchJob when reader throws exception") void shouldFailJobWhenReaderThrowsException() throws Exception { when(asyncReader.read()) .thenThrow(new RuntimeException("Simulated reader failure")); - JobExecution execution = jobLauncher.run(asyncBatchJob, getParameters()); + JobExecution execution = jobLauncher.run(batchJob, getParameters()); assertThat(execution.getStatus()).isNotEqualTo(BatchStatus.COMPLETED); assertThat(execution.getStatus()).isEqualTo(BatchStatus.FAILED); @@ -67,12 +67,12 @@ void shouldFailJobWhenReaderThrowsException() throws Exception { } @Test - @DisplayName("Should execute asyncBatchJob and complete successfully when reader has no data") + @DisplayName("Should execute batchJob and complete successfully when reader has no data") void shouldCompleteJobWithNoData() throws Exception { when(asyncReader.read()).thenReturn(null); - JobExecution execution = jobLauncher.run(asyncBatchJob, getParameters()); + JobExecution execution = jobLauncher.run(batchJob, getParameters()); assertThat(execution.getStatus()).isEqualTo(BatchStatus.COMPLETED); diff --git a/spring-batch-examples/src/test/java/com/io/example/dbReader/mapper/TestEntityToDtoMapperTest.java b/spring-batch-db-examples/src/test/java/com/io/example/mapper/TestEntityMapperTest.java similarity index 76% rename from spring-batch-examples/src/test/java/com/io/example/dbReader/mapper/TestEntityToDtoMapperTest.java rename to spring-batch-db-examples/src/test/java/com/io/example/mapper/TestEntityMapperTest.java index 2298d00..c44d000 100644 --- a/spring-batch-examples/src/test/java/com/io/example/dbReader/mapper/TestEntityToDtoMapperTest.java +++ b/spring-batch-db-examples/src/test/java/com/io/example/mapper/TestEntityMapperTest.java @@ -1,7 +1,7 @@ -package com.io.example.dbReader.mapper; +package com.io.example.mapper; -import com.io.example.dbReader.model.dto.TestDto; -import com.io.example.dbReader.model.entity.TestEntity; +import com.io.example.model.dto.TestDto; +import com.io.example.model.entity.TestEntity; import org.instancio.Instancio; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -13,10 +13,10 @@ @ExtendWith(MockitoExtension.class) @DisplayName("Unit tests for TestEntityToDtoMapper") -public class TestEntityToDtoMapperTest{ +public class TestEntityMapperTest { @InjectMocks - private TestEntityToDtoMapper mapper; + private TestEntityMapper mapper; @Test @DisplayName("should correctly map TestEntity to TestDto") diff --git a/spring-batch-db-examples/src/test/java/com/io/example/service/DBBatchServiceImplTest.java b/spring-batch-db-examples/src/test/java/com/io/example/service/DBBatchServiceImplTest.java new file mode 100644 index 0000000..ee5c396 --- /dev/null +++ b/spring-batch-db-examples/src/test/java/com/io/example/service/DBBatchServiceImplTest.java @@ -0,0 +1,93 @@ +package com.io.example.service; + +import com.io.example.exception.BusinessException; +import com.io.example.service.impl.DBBatchServiceImpl; +import org.instancio.Instancio; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +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.explore.JobExplorer; +import org.springframework.batch.core.launch.JobLauncher; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +@DisplayName("Unit tests for AsyncBatchServiceImpl") +class DBBatchServiceImplTest { + + private static final Long jobId = Instancio.create(Long.class); + + @Mock + private JobLauncher asyncJobLauncher; + + @Mock + private Job batchJob; + + @Mock + private JobExplorer jobExplorer; + + @InjectMocks + private DBBatchServiceImpl batchService; + + @Test + @DisplayName("Should run job successfully and return job execution id") + void shouldRunJobSuccessfully() throws Exception { + + JobExecution jobExecutionMock = mock(JobExecution.class); + when(jobExecutionMock.getId()).thenReturn(123L); + when(asyncJobLauncher.run(eq(batchJob), any(JobParameters.class))) + .thenReturn(jobExecutionMock); + + Long jobId = batchService.runJob(); + + assertThat(jobId).isEqualTo(123L); + verify(asyncJobLauncher).run(eq(batchJob), any(JobParameters.class)); + } + + @Test + @DisplayName("Should throw BusinessException when job execution fails") + void shouldThrowBusinessExceptionWhenJobFails() throws Exception { + + when(asyncJobLauncher.run(any(Job.class), any(JobParameters.class))) + .thenThrow(new RuntimeException("Simulated error")); + + BusinessException exception = assertThrows(BusinessException.class, + () -> batchService.runJob()); + + assertThat(exception.getMessage()).isEqualTo("Simulated error"); + } + + @Test + @DisplayName("Should return job status when job execution exists") + void shouldReturnJobStatus() { + JobExecution jobExecutionMock = mock(JobExecution.class); + when(jobExecutionMock.getStatus()).thenReturn(BatchStatus.COMPLETED); + when(jobExplorer.getJobExecution(jobId)).thenReturn(jobExecutionMock); + + BatchStatus status = batchService.getJobStatus(jobId); + + assertThat(status).isEqualTo(BatchStatus.COMPLETED); + verify(jobExplorer).getJobExecution(jobId); + } + + @Test + @DisplayName("Should throw BusinessException when job execution not found") + void shouldThrowBusinessExceptionWhenJobNotFound() { + when(jobExplorer.getJobExecution(jobId)).thenReturn(null); + + BusinessException exception = assertThrows(BusinessException.class, + () -> batchService.getJobStatus(jobId)); + + assertThat(exception.getMessage()) + .isEqualTo("JobExecution not found for this id: " + jobId); + } +} diff --git a/spring-batch-db-examples/src/test/java/com/io/example/service/TestServiceImplTest.java b/spring-batch-db-examples/src/test/java/com/io/example/service/TestServiceImplTest.java new file mode 100644 index 0000000..180d4d8 --- /dev/null +++ b/spring-batch-db-examples/src/test/java/com/io/example/service/TestServiceImplTest.java @@ -0,0 +1,18 @@ +package com.io.example.service; + +import com.io.example.model.dto.TestDto; +import com.io.example.service.impl.TestServiceImpl; +import org.instancio.Instancio; +import org.junit.jupiter.api.Test; + +class TestServiceImplTest { + + private final TestServiceImpl service = new TestServiceImpl(); + + @Test + void print_shouldLogStudent() { + TestDto testDto = Instancio.create(TestDto.class); + service.print(testDto); + } + +} diff --git a/spring-batch-examples/src/test/java/com/io/example/dbReader/util/DataUtils.java b/spring-batch-db-examples/src/test/java/com/io/example/util/DataUtils.java similarity index 92% rename from spring-batch-examples/src/test/java/com/io/example/dbReader/util/DataUtils.java rename to spring-batch-db-examples/src/test/java/com/io/example/util/DataUtils.java index 33f771e..ef02bac 100644 --- a/spring-batch-examples/src/test/java/com/io/example/dbReader/util/DataUtils.java +++ b/spring-batch-db-examples/src/test/java/com/io/example/util/DataUtils.java @@ -1,6 +1,6 @@ -package com.io.example.dbReader.util; +package com.io.example.util; -import com.io.example.dbReader.model.entity.TestEntity; +import com.io.example.model.entity.TestEntity; import lombok.SneakyThrows; import org.instancio.Instancio; import org.springframework.batch.core.JobParameters; diff --git a/spring-batch-examples/src/test/resources/application-dbReader-test.yml b/spring-batch-db-examples/src/test/resources/application-test.yml similarity index 88% rename from spring-batch-examples/src/test/resources/application-dbReader-test.yml rename to spring-batch-db-examples/src/test/resources/application-test.yml index f1b14b7..5909ecc 100644 --- a/spring-batch-examples/src/test/resources/application-dbReader-test.yml +++ b/spring-batch-db-examples/src/test/resources/application-test.yml @@ -22,5 +22,5 @@ spring: initialize-schema: always job: enabled: false - batch-size: 500 - chunk-size: 200 + batch-size: 2 + chunk-size: 1 diff --git a/spring-batch-examples/src/main/java/com/io/example/dbReader/service/TestService.java b/spring-batch-examples/src/main/java/com/io/example/dbReader/service/TestService.java deleted file mode 100644 index 693f085..0000000 --- a/spring-batch-examples/src/main/java/com/io/example/dbReader/service/TestService.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.io.example.dbReader.service; - -import com.io.example.dbReader.model.dto.TestDto; - -public interface TestService { - void print(TestDto testDto); -} diff --git a/spring-batch-examples/src/main/java/com/io/example/fileReader/README.md b/spring-batch-examples/src/main/java/com/io/example/fileReader/README.md deleted file mode 100644 index b47c67e..0000000 --- a/spring-batch-examples/src/main/java/com/io/example/fileReader/README.md +++ /dev/null @@ -1,40 +0,0 @@ -# PENDING - -# Spring Batch Examples | Async CSV Processing - -This project is a **Spring Boot** application demonstrating a **fully asynchronous Spring Batch job**, designed with a focus on **performance** and **scalability**. - ---- - -## 🚀 Overview - -The example showcases how to configure and run an **asynchronous Spring Batch job** that processes a single CSV file efficiently. -The job reads **one CSV file**, simulating item processing by printing -`"item processed"` for each entry. - ---- - -## ⚙️ How It Works - -* The job leverages Spring Batch’s **asynchronous capabilities** to process data efficiently. -* A **CSV file** is used as the input data source. -* The asynchronous execution is enabled through a specific Spring profile. - ---- - -## 🧩 Technologies Used - -* **Java 21** -* **Spring Batch** -* **Spring Boot** -* **CSV file** - ---- - -## ▶️ Running the Project - -To run the asynchronous batch job processing a CSV file, simply activate the `asyncCsvReader` profile: - -```bash -./mvnw spring-boot:run -Dspring-boot.run.profiles=asyncFileReader -``` diff --git a/spring-batch-examples/src/main/java/com/io/example/fileReader/service/TestService.java b/spring-batch-examples/src/main/java/com/io/example/fileReader/service/TestService.java deleted file mode 100644 index d5881a8..0000000 --- a/spring-batch-examples/src/main/java/com/io/example/fileReader/service/TestService.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.io.example.fileReader.service; - -import com.io.example.fileReader.dto.StudentDto; - -public interface TestService { - void print(StudentDto studentDto); -} diff --git a/spring-batch-file-examples/.gitattributes b/spring-batch-file-examples/.gitattributes new file mode 100644 index 0000000..3b41682 --- /dev/null +++ b/spring-batch-file-examples/.gitattributes @@ -0,0 +1,2 @@ +/mvnw text eol=lf +*.cmd text eol=crlf diff --git a/spring-batch-file-examples/.github/workflows/maven.yml b/spring-batch-file-examples/.github/workflows/maven.yml new file mode 100644 index 0000000..16d1a66 --- /dev/null +++ b/spring-batch-file-examples/.github/workflows/maven.yml @@ -0,0 +1,30 @@ +name: CI Build + +on: + push: + branches: + - "**" + +jobs: + build: + name: Build + runs-on: ubuntu-latest + strategy: + matrix: + distribution: [ 'temurin' ] + java: [ '21' ] + steps: + - uses: actions/checkout@v5 + + - name: Setup Java 21 + uses: actions/setup-java@v5 + with: + java-version: ${{ matrix.java }} + distribution: ${{ matrix.distribution }} + cache: 'maven' + + - name: Grant execute permission for mvnw + run: chmod +x mvnw + + - name: Build with Maven + run: ./mvnw clean verify \ No newline at end of file diff --git a/spring-batch-file-examples/.gitignore b/spring-batch-file-examples/.gitignore new file mode 100644 index 0000000..a71cd2b --- /dev/null +++ b/spring-batch-file-examples/.gitignore @@ -0,0 +1,32 @@ +target/ +.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/spring-batch-file-examples/.mvn/wrapper/maven-wrapper.properties b/spring-batch-file-examples/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..c0bcafe --- /dev/null +++ b/spring-batch-file-examples/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,3 @@ +wrapperVersion=3.3.4 +distributionType=only-script +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.11/apache-maven-3.9.11-bin.zip diff --git a/spring-batch-file-examples/README.md b/spring-batch-file-examples/README.md new file mode 100644 index 0000000..444578a --- /dev/null +++ b/spring-batch-file-examples/README.md @@ -0,0 +1,30 @@ +# Spring Batch Examples | DB And Async + +This project is a **Spring Boot** application demonstrating a **fully asynchronous Spring Batch job**, designed with a focus on **performance** and **scalability**. + +--- + +## 🚀 Overview + +The example showcases how to configure and run an **asynchronous Spring Batch job** that processes a large dataset efficiently. +The job reads **10,000 records** from a database table, simulating item processing by printing +`"item processed"` for each entry. + +--- + +## ⚙️ How It Works + +- The job leverages Spring Batch’s asynchronous capabilities to read and process data concurrently. +- An **H2 in-memory database** is used to store the sample data. +- The asynchronous behavior is enabled through a specific Spring profile. + +--- + +## 🧩 Technologies Used + +- **Java 21** +- **Spring Batch** +- **Spring Boot** +- **H2 Database** + +--- \ No newline at end of file diff --git a/spring-batch-file-examples/mvnw b/spring-batch-file-examples/mvnw new file mode 100755 index 0000000..bd8896b --- /dev/null +++ b/spring-batch-file-examples/mvnw @@ -0,0 +1,295 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.3.4 +# +# Optional ENV vars +# ----------------- +# JAVA_HOME - location of a JDK home dir, required when download maven via java source +# MVNW_REPOURL - repo url base for downloading maven distribution +# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output +# ---------------------------------------------------------------------------- + +set -euf +[ "${MVNW_VERBOSE-}" != debug ] || set -x + +# OS specific support. +native_path() { printf %s\\n "$1"; } +case "$(uname)" in +CYGWIN* | MINGW*) + [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" + native_path() { cygpath --path --windows "$1"; } + ;; +esac + +# set JAVACMD and JAVACCMD +set_java_home() { + # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched + if [ -n "${JAVA_HOME-}" ]; then + if [ -x "$JAVA_HOME/jre/sh/java" ]; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACCMD="$JAVA_HOME/jre/sh/javac" + else + JAVACMD="$JAVA_HOME/bin/java" + JAVACCMD="$JAVA_HOME/bin/javac" + + if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then + echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 + echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 + return 1 + fi + fi + else + JAVACMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v java + )" || : + JAVACCMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v javac + )" || : + + if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then + echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 + return 1 + fi + fi +} + +# hash string like Java String::hashCode +hash_string() { + str="${1:-}" h=0 + while [ -n "$str" ]; do + char="${str%"${str#?}"}" + h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) + str="${str#?}" + done + printf %x\\n $h +} + +verbose() { :; } +[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } + +die() { + printf %s\\n "$1" >&2 + exit 1 +} + +trim() { + # MWRAPPER-139: + # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. + # Needed for removing poorly interpreted newline sequences when running in more + # exotic environments such as mingw bash on Windows. + printf "%s" "${1}" | tr -d '[:space:]' +} + +scriptDir="$(dirname "$0")" +scriptName="$(basename "$0")" + +# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties +while IFS="=" read -r key value; do + case "${key-}" in + distributionUrl) distributionUrl=$(trim "${value-}") ;; + distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; + esac +done <"$scriptDir/.mvn/wrapper/maven-wrapper.properties" +[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" + +case "${distributionUrl##*/}" in +maven-mvnd-*bin.*) + MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ + case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in + *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; + :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; + :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; + :Linux*x86_64*) distributionPlatform=linux-amd64 ;; + *) + echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 + distributionPlatform=linux-amd64 + ;; + esac + distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" + ;; +maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; +*) MVN_CMD="mvn${scriptName#mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; +esac + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" +distributionUrlName="${distributionUrl##*/}" +distributionUrlNameMain="${distributionUrlName%.*}" +distributionUrlNameMain="${distributionUrlNameMain%-bin}" +MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" +MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" + +exec_maven() { + unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : + exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" +} + +if [ -d "$MAVEN_HOME" ]; then + verbose "found existing MAVEN_HOME at $MAVEN_HOME" + exec_maven "$@" +fi + +case "${distributionUrl-}" in +*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; +*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; +esac + +# prepare tmp dir +if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then + clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } + trap clean HUP INT TERM EXIT +else + die "cannot create temp dir" +fi + +mkdir -p -- "${MAVEN_HOME%/*}" + +# Download and Install Apache Maven +verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +verbose "Downloading from: $distributionUrl" +verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +# select .zip or .tar.gz +if ! command -v unzip >/dev/null; then + distributionUrl="${distributionUrl%.zip}.tar.gz" + distributionUrlName="${distributionUrl##*/}" +fi + +# verbose opt +__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' +[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v + +# normalize http auth +case "${MVNW_PASSWORD:+has-password}" in +'') MVNW_USERNAME='' MVNW_PASSWORD='' ;; +has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; +esac + +if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then + verbose "Found wget ... using wget" + wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" +elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then + verbose "Found curl ... using curl" + curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" +elif set_java_home; then + verbose "Falling back to use Java to download" + javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" + targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" + cat >"$javaSource" <<-END + public class Downloader extends java.net.Authenticator + { + protected java.net.PasswordAuthentication getPasswordAuthentication() + { + return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); + } + public static void main( String[] args ) throws Exception + { + setDefault( new Downloader() ); + java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); + } + } + END + # For Cygwin/MinGW, switch paths to Windows format before running javac and java + verbose " - Compiling Downloader.java ..." + "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" + verbose " - Running Downloader.java ..." + "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" +fi + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +if [ -n "${distributionSha256Sum-}" ]; then + distributionSha256Result=false + if [ "$MVN_CMD" = mvnd.sh ]; then + echo "Checksum validation is not supported for maven-mvnd." >&2 + echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + elif command -v sha256sum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c - >/dev/null 2>&1; then + distributionSha256Result=true + fi + elif command -v shasum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 + echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + fi + if [ $distributionSha256Result = false ]; then + echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 + echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 + exit 1 + fi +fi + +# unzip and move +if command -v unzip >/dev/null; then + unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" +else + tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" +fi + +# Find the actual extracted directory name (handles snapshots where filename != directory name) +actualDistributionDir="" + +# First try the expected directory name (for regular distributions) +if [ -d "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" ]; then + if [ -f "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/bin/$MVN_CMD" ]; then + actualDistributionDir="$distributionUrlNameMain" + fi +fi + +# If not found, search for any directory with the Maven executable (for snapshots) +if [ -z "$actualDistributionDir" ]; then + # enable globbing to iterate over items + set +f + for dir in "$TMP_DOWNLOAD_DIR"/*; do + if [ -d "$dir" ]; then + if [ -f "$dir/bin/$MVN_CMD" ]; then + actualDistributionDir="$(basename "$dir")" + break + fi + fi + done + set -f +fi + +if [ -z "$actualDistributionDir" ]; then + verbose "Contents of $TMP_DOWNLOAD_DIR:" + verbose "$(ls -la "$TMP_DOWNLOAD_DIR")" + die "Could not find Maven distribution directory in extracted archive" +fi + +verbose "Found extracted Maven distribution directory: $actualDistributionDir" +printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$actualDistributionDir/mvnw.url" +mv -- "$TMP_DOWNLOAD_DIR/$actualDistributionDir" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" + +clean || : +exec_maven "$@" diff --git a/spring-batch-file-examples/mvnw.cmd b/spring-batch-file-examples/mvnw.cmd new file mode 100644 index 0000000..92450f9 --- /dev/null +++ b/spring-batch-file-examples/mvnw.cmd @@ -0,0 +1,189 @@ +<# : batch portion +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.3.4 +@REM +@REM Optional ENV vars +@REM MVNW_REPOURL - repo url base for downloading maven distribution +@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output +@REM ---------------------------------------------------------------------------- + +@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) +@SET __MVNW_CMD__= +@SET __MVNW_ERROR__= +@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% +@SET PSModulePath= +@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( + IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) +) +@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% +@SET __MVNW_PSMODULEP_SAVE= +@SET __MVNW_ARG0_NAME__= +@SET MVNW_USERNAME= +@SET MVNW_PASSWORD= +@IF NOT "%__MVNW_CMD__%"=="" ("%__MVNW_CMD__%" %*) +@echo Cannot start maven from wrapper >&2 && exit /b 1 +@GOTO :EOF +: end batch / begin powershell #> + +$ErrorActionPreference = "Stop" +if ($env:MVNW_VERBOSE -eq "true") { + $VerbosePreference = "Continue" +} + +# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties +$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl +if (!$distributionUrl) { + Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" +} + +switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { + "maven-mvnd-*" { + $USE_MVND = $true + $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" + $MVN_CMD = "mvnd.cmd" + break + } + default { + $USE_MVND = $false + $MVN_CMD = $script -replace '^mvnw','mvn' + break + } +} + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +if ($env:MVNW_REPOURL) { + $MVNW_REPO_PATTERN = if ($USE_MVND -eq $False) { "/org/apache/maven/" } else { "/maven/mvnd/" } + $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace "^.*$MVNW_REPO_PATTERN",'')" +} +$distributionUrlName = $distributionUrl -replace '^.*/','' +$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' + +$MAVEN_M2_PATH = "$HOME/.m2" +if ($env:MAVEN_USER_HOME) { + $MAVEN_M2_PATH = "$env:MAVEN_USER_HOME" +} + +if (-not (Test-Path -Path $MAVEN_M2_PATH)) { + New-Item -Path $MAVEN_M2_PATH -ItemType Directory | Out-Null +} + +$MAVEN_WRAPPER_DISTS = $null +if ((Get-Item $MAVEN_M2_PATH).Target[0] -eq $null) { + $MAVEN_WRAPPER_DISTS = "$MAVEN_M2_PATH/wrapper/dists" +} else { + $MAVEN_WRAPPER_DISTS = (Get-Item $MAVEN_M2_PATH).Target[0] + "/wrapper/dists" +} + +$MAVEN_HOME_PARENT = "$MAVEN_WRAPPER_DISTS/$distributionUrlNameMain" +$MAVEN_HOME_NAME = ([System.Security.Cryptography.SHA256]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' +$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" + +if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { + Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" + Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" + exit $? +} + +if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { + Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" +} + +# prepare tmp dir +$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile +$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" +$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null +trap { + if ($TMP_DOWNLOAD_DIR.Exists) { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } + } +} + +New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null + +# Download and Install Apache Maven +Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +Write-Verbose "Downloading from: $distributionUrl" +Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +$webclient = New-Object System.Net.WebClient +if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { + $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) +} +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum +if ($distributionSha256Sum) { + if ($USE_MVND) { + Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." + } + Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash + if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { + Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." + } +} + +# unzip and move +Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null + +# Find the actual extracted directory name (handles snapshots where filename != directory name) +$actualDistributionDir = "" + +# First try the expected directory name (for regular distributions) +$expectedPath = Join-Path "$TMP_DOWNLOAD_DIR" "$distributionUrlNameMain" +$expectedMvnPath = Join-Path "$expectedPath" "bin/$MVN_CMD" +if ((Test-Path -Path $expectedPath -PathType Container) -and (Test-Path -Path $expectedMvnPath -PathType Leaf)) { + $actualDistributionDir = $distributionUrlNameMain +} + +# If not found, search for any directory with the Maven executable (for snapshots) +if (!$actualDistributionDir) { + Get-ChildItem -Path "$TMP_DOWNLOAD_DIR" -Directory | ForEach-Object { + $testPath = Join-Path $_.FullName "bin/$MVN_CMD" + if (Test-Path -Path $testPath -PathType Leaf) { + $actualDistributionDir = $_.Name + } + } +} + +if (!$actualDistributionDir) { + Write-Error "Could not find Maven distribution directory in extracted archive" +} + +Write-Verbose "Found extracted Maven distribution directory: $actualDistributionDir" +Rename-Item -Path "$TMP_DOWNLOAD_DIR/$actualDistributionDir" -NewName $MAVEN_HOME_NAME | Out-Null +try { + Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null +} catch { + if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { + Write-Error "fail to move MAVEN_HOME" + } +} finally { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } +} + +Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" diff --git a/spring-batch-examples/pom.xml b/spring-batch-file-examples/pom.xml similarity index 65% rename from spring-batch-examples/pom.xml rename to spring-batch-file-examples/pom.xml index e72f3eb..6750d81 100644 --- a/spring-batch-examples/pom.xml +++ b/spring-batch-file-examples/pom.xml @@ -12,21 +12,22 @@ com.io - spring-batch-examples + spring-batch-file-examples 0.0.1-SNAPSHOT - spring-batch-examples + spring-batch-file-examples Demo project for Spring Boot 21 1.18.40 - 2.3.232 3.5.6 5.2.3 3.14.1 5.5.1 5.4.1 0.1.1 + 2.3.232 + 0.8.14 @@ -55,13 +56,6 @@ ${spring.batch.version} - - com.h2database - h2 - runtime - ${h2.version} - - org.projectlombok lombok @@ -96,15 +90,16 @@ - org.apache.poi - poi-ooxml - ${poi.ooxml.version} + com.h2database + h2 + runtime + ${h2.version} - com.amazonaws - aws-java-sdk-s3 - 1.12.777 + org.apache.poi + poi-ooxml + ${poi.ooxml.version} @@ -140,6 +135,55 @@ + + org.jacoco + jacoco-maven-plugin + ${jacoco.version} + + + + + prepare-agent + + + + + report + test + + report + + + + + check + + check + + + + + BUNDLE + + + INSTRUCTION + COVEREDRATIO + 0.80 + + + BRANCH + COVEREDRATIO + 0.70 + + + + + + + + + + diff --git a/spring-batch-examples/src/main/java/com/io/example/fileReader/FileReaderExampleApplication.java b/spring-batch-file-examples/src/main/java/com/io/example/FileReaderExampleApplication.java similarity index 89% rename from spring-batch-examples/src/main/java/com/io/example/fileReader/FileReaderExampleApplication.java rename to spring-batch-file-examples/src/main/java/com/io/example/FileReaderExampleApplication.java index badb540..ad6cbd4 100644 --- a/spring-batch-examples/src/main/java/com/io/example/fileReader/FileReaderExampleApplication.java +++ b/spring-batch-file-examples/src/main/java/com/io/example/FileReaderExampleApplication.java @@ -1,4 +1,4 @@ -package com.io.example.fileReader; +package com.io.example; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; diff --git a/spring-batch-file-examples/src/main/java/com/io/example/README.md b/spring-batch-file-examples/src/main/java/com/io/example/README.md new file mode 100644 index 0000000..b73f712 --- /dev/null +++ b/spring-batch-file-examples/src/main/java/com/io/example/README.md @@ -0,0 +1,38 @@ +# File Reading Examples \| Multi-format Processing + +This project is a Spring Boot module that demonstrates basic, configurable setups for reading and processing different file formats using Spring batch. The examples are intentionally generic and meant to illustrate common configuration patterns, extension points, and how to plug different file readers into a batch processing pipeline. + +--- + +## Overview + +- Purpose: provide foundational configurations and examples for reading files of various formats. +- Goal: show how to configure readers, processors, and batch steps in a reusable way that can be adapted to different file types and processing requirements. + +--- + +## Scope + +This module focuses on demonstrating basic reader configurations and integration points. Use this section to list which file types are supported by this module: + +- Excel \(.xlsx\) — reading implemented +- CSV \(.csv\) — next/planned + +--- + +## How It Works (Generic) + +- Uses Spring Batch for job and step orchestration. +- Provides example reader beans and mapping strategies for different file formats. +- Demonstrates how to enable/disable features via Spring profiles and configuration properties. +- Designed for extensibility so additional file readers can be added with minimal changes. + +--- + +## Technologies Used + +- Java 21 +- Spring Boot +- Spring Batch + +--- \ No newline at end of file diff --git a/spring-batch-examples/src/main/java/com/io/example/fileReader/config/ExcelBatchConfig.java b/spring-batch-file-examples/src/main/java/com/io/example/config/ExcelBatchConfig.java similarity index 71% rename from spring-batch-examples/src/main/java/com/io/example/fileReader/config/ExcelBatchConfig.java rename to spring-batch-file-examples/src/main/java/com/io/example/config/ExcelBatchConfig.java index c46e2da..0629d51 100644 --- a/spring-batch-examples/src/main/java/com/io/example/fileReader/config/ExcelBatchConfig.java +++ b/spring-batch-file-examples/src/main/java/com/io/example/config/ExcelBatchConfig.java @@ -1,9 +1,10 @@ -package com.io.example.fileReader.config; +package com.io.example.config; -import com.io.example.fileReader.service.TestService; -import com.io.example.fileReader.dto.StudentDto; -import com.io.example.fileReader.mapper.StudentMapper; +import com.io.example.service.TestService; +import com.io.example.dto.StudentDto; +import com.io.example.mapper.StudentMapper; import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; import org.springframework.batch.core.Step; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.step.builder.StepBuilder; @@ -12,28 +13,33 @@ import org.springframework.batch.integration.async.AsyncItemWriter; import org.springframework.batch.item.ItemProcessor; import org.springframework.batch.item.ItemWriter; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Profile; import org.springframework.core.io.ClassPathResource; import org.springframework.batch.core.configuration.annotation.StepScope; import org.springframework.core.io.InputStreamResource; import org.springframework.core.task.SimpleAsyncTaskExecutor; import org.springframework.transaction.PlatformTransactionManager; + +import java.io.IOException; +import java.io.InputStream; import java.util.concurrent.Future; @Configuration @RequiredArgsConstructor -@Profile("asyncExcelFileReader") public class ExcelBatchConfig { private final TestService testService; @Bean @StepScope - public PoiItemReader excelReader() { + public PoiItemReader excelReader( + @Value("#{jobParameters['filePath']}") String filePath + ) throws IOException { + InputStream inputStream = new ClassPathResource(filePath).getInputStream(); PoiItemReader reader = new PoiItemReader<>(); - reader.setResource(new InputStreamResource(new ClassPathResource("students.xlsx"))); + reader.setResource(new InputStreamResource(inputStream)); reader.setLinesToSkip(1); reader.setRowMapper(new StudentMapper()); return reader; @@ -70,13 +76,13 @@ public ItemProcessor processor() { @Bean public Step step(JobRepository jobRepository, - PlatformTransactionManager transactionManager, - AsyncItemProcessor processor, - AsyncItemWriter writer, - PoiItemReader reader ) { - + PlatformTransactionManager transactionManager, + AsyncItemProcessor processor, + AsyncItemWriter writer, + PoiItemReader reader, + @Value("${spring.batch.chunk-size}") int chunkSize) { return new StepBuilder("step", jobRepository) - .>chunk(1, transactionManager) + .>chunk(chunkSize, transactionManager) .processor(processor) .writer(writer) .reader(reader) diff --git a/spring-batch-examples/src/main/java/com/io/example/fileReader/controller/TestController.java b/spring-batch-file-examples/src/main/java/com/io/example/controller/TestController.java similarity index 66% rename from spring-batch-examples/src/main/java/com/io/example/fileReader/controller/TestController.java rename to spring-batch-file-examples/src/main/java/com/io/example/controller/TestController.java index 17e384b..10493ca 100644 --- a/spring-batch-examples/src/main/java/com/io/example/fileReader/controller/TestController.java +++ b/spring-batch-file-examples/src/main/java/com/io/example/controller/TestController.java @@ -1,7 +1,7 @@ -package com.io.example.fileReader.controller; +package com.io.example.controller; -import com.io.example.fileReader.exception.BusinessException; -import com.io.example.fileReader.service.FileBatchService; +import com.io.example.exception.BusinessException; +import com.io.example.service.FileBatchService; import lombok.RequiredArgsConstructor; import org.springframework.batch.core.BatchStatus; import org.springframework.http.ResponseEntity; @@ -25,12 +25,8 @@ public ResponseEntity processJob(){ @GetMapping("/{jobId}/status") public ResponseEntity getJobStatus(@PathVariable Long jobId) { - try { - BatchStatus status = fileBatchService.getJobStatus(jobId); - return ResponseEntity.ok(status.name()); - } catch (BusinessException e) { - return ResponseEntity.notFound().build(); - } + BatchStatus status = fileBatchService.getJobStatus(jobId); + return ResponseEntity.ok(status.name()); } } diff --git a/spring-batch-examples/src/main/java/com/io/example/fileReader/dto/StudentDto.java b/spring-batch-file-examples/src/main/java/com/io/example/dto/StudentDto.java similarity index 65% rename from spring-batch-examples/src/main/java/com/io/example/fileReader/dto/StudentDto.java rename to spring-batch-file-examples/src/main/java/com/io/example/dto/StudentDto.java index 5a4428f..1dac686 100644 --- a/spring-batch-examples/src/main/java/com/io/example/fileReader/dto/StudentDto.java +++ b/spring-batch-file-examples/src/main/java/com/io/example/dto/StudentDto.java @@ -1,4 +1,4 @@ -package com.io.example.fileReader.dto; +package com.io.example.dto; import lombok.AllArgsConstructor; import lombok.Builder; @@ -11,7 +11,7 @@ @NoArgsConstructor @AllArgsConstructor public class StudentDto { - private String nome; - private String turma; - private LocalDate data; + private String name; + private String clasS; + private LocalDate date; } diff --git a/spring-batch-examples/src/main/java/com/io/example/fileReader/exception/BusinessException.java b/spring-batch-file-examples/src/main/java/com/io/example/exception/BusinessException.java similarity index 84% rename from spring-batch-examples/src/main/java/com/io/example/fileReader/exception/BusinessException.java rename to spring-batch-file-examples/src/main/java/com/io/example/exception/BusinessException.java index a137884..bef631f 100644 --- a/spring-batch-examples/src/main/java/com/io/example/fileReader/exception/BusinessException.java +++ b/spring-batch-file-examples/src/main/java/com/io/example/exception/BusinessException.java @@ -1,4 +1,4 @@ -package com.io.example.fileReader.exception; +package com.io.example.exception; public class BusinessException extends RuntimeException { diff --git a/spring-batch-examples/src/main/java/com/io/example/fileReader/exception/GlobalHandlerException.java b/spring-batch-file-examples/src/main/java/com/io/example/exception/GlobalHandlerException.java similarity index 84% rename from spring-batch-examples/src/main/java/com/io/example/fileReader/exception/GlobalHandlerException.java rename to spring-batch-file-examples/src/main/java/com/io/example/exception/GlobalHandlerException.java index 40739db..d345db5 100644 --- a/spring-batch-examples/src/main/java/com/io/example/fileReader/exception/GlobalHandlerException.java +++ b/spring-batch-file-examples/src/main/java/com/io/example/exception/GlobalHandlerException.java @@ -1,6 +1,5 @@ -package com.io.example.fileReader.exception; +package com.io.example.exception; -import com.io.example.fileReader.exception.BusinessException; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; diff --git a/spring-batch-examples/src/main/java/com/io/example/fileReader/job/FileJob.java b/spring-batch-file-examples/src/main/java/com/io/example/job/FileJob.java similarity index 94% rename from spring-batch-examples/src/main/java/com/io/example/fileReader/job/FileJob.java rename to spring-batch-file-examples/src/main/java/com/io/example/job/FileJob.java index 581c32c..bdf6e93 100644 --- a/spring-batch-examples/src/main/java/com/io/example/fileReader/job/FileJob.java +++ b/spring-batch-file-examples/src/main/java/com/io/example/job/FileJob.java @@ -1,4 +1,4 @@ -package com.io.example.fileReader.job; +package com.io.example.job; import lombok.RequiredArgsConstructor; import org.springframework.batch.core.Job; diff --git a/spring-batch-examples/src/main/java/com/io/example/fileReader/mapper/StudentMapper.java b/spring-batch-file-examples/src/main/java/com/io/example/mapper/StudentMapper.java similarity index 66% rename from spring-batch-examples/src/main/java/com/io/example/fileReader/mapper/StudentMapper.java rename to spring-batch-file-examples/src/main/java/com/io/example/mapper/StudentMapper.java index 0ff9c53..bb34187 100644 --- a/spring-batch-examples/src/main/java/com/io/example/fileReader/mapper/StudentMapper.java +++ b/spring-batch-file-examples/src/main/java/com/io/example/mapper/StudentMapper.java @@ -1,6 +1,6 @@ -package com.io.example.fileReader.mapper; +package com.io.example.mapper; -import com.io.example.fileReader.dto.StudentDto; +import com.io.example.dto.StudentDto; import org.springframework.batch.extensions.excel.RowMapper; import org.springframework.batch.extensions.excel.support.rowset.RowSet; import java.time.LocalDate; @@ -11,9 +11,9 @@ public class StudentMapper implements RowMapper { public StudentDto mapRow(RowSet rowSet){ String[] cells = rowSet.getCurrentRow(); return StudentDto.builder() - .nome(cells[0]) - .turma(cells[1]) - .data(LocalDate.parse(cells[2])) + .name(cells[0]) + .clasS(cells[1]) + .date(LocalDate.parse(cells[2])) .build(); } diff --git a/spring-batch-examples/src/main/java/com/io/example/fileReader/service/FileBatchService.java b/spring-batch-file-examples/src/main/java/com/io/example/service/FileBatchService.java similarity index 77% rename from spring-batch-examples/src/main/java/com/io/example/fileReader/service/FileBatchService.java rename to spring-batch-file-examples/src/main/java/com/io/example/service/FileBatchService.java index b2ff4e9..c8118be 100644 --- a/spring-batch-examples/src/main/java/com/io/example/fileReader/service/FileBatchService.java +++ b/spring-batch-file-examples/src/main/java/com/io/example/service/FileBatchService.java @@ -1,4 +1,4 @@ -package com.io.example.fileReader.service; +package com.io.example.service; import org.springframework.batch.core.BatchStatus; diff --git a/spring-batch-file-examples/src/main/java/com/io/example/service/TestService.java b/spring-batch-file-examples/src/main/java/com/io/example/service/TestService.java new file mode 100644 index 0000000..7033c45 --- /dev/null +++ b/spring-batch-file-examples/src/main/java/com/io/example/service/TestService.java @@ -0,0 +1,7 @@ +package com.io.example.service; + +import com.io.example.dto.StudentDto; + +public interface TestService { + void print(StudentDto studentDto); +} diff --git a/spring-batch-examples/src/main/java/com/io/example/fileReader/service/impl/FileBatchServiceImpl.java b/spring-batch-file-examples/src/main/java/com/io/example/service/impl/FileBatchServiceImpl.java similarity index 89% rename from spring-batch-examples/src/main/java/com/io/example/fileReader/service/impl/FileBatchServiceImpl.java rename to spring-batch-file-examples/src/main/java/com/io/example/service/impl/FileBatchServiceImpl.java index 1657410..ddd1d13 100644 --- a/spring-batch-examples/src/main/java/com/io/example/fileReader/service/impl/FileBatchServiceImpl.java +++ b/spring-batch-file-examples/src/main/java/com/io/example/service/impl/FileBatchServiceImpl.java @@ -1,7 +1,7 @@ -package com.io.example.fileReader.service.impl; +package com.io.example.service.impl; -import com.io.example.fileReader.exception.BusinessException; -import com.io.example.fileReader.service.FileBatchService; +import com.io.example.exception.BusinessException; +import com.io.example.service.FileBatchService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.batch.core.*; @@ -45,6 +45,7 @@ public BatchStatus getJobStatus(Long jobId) { private JobParameters getParameters(){ return new JobParametersBuilder() .addLong("time", System.currentTimeMillis()) + .addString("filePath", "files/students.xlsx") .toJobParameters(); } diff --git a/spring-batch-examples/src/main/java/com/io/example/fileReader/service/impl/TestServiceImpl.java b/spring-batch-file-examples/src/main/java/com/io/example/service/impl/TestServiceImpl.java similarity index 64% rename from spring-batch-examples/src/main/java/com/io/example/fileReader/service/impl/TestServiceImpl.java rename to spring-batch-file-examples/src/main/java/com/io/example/service/impl/TestServiceImpl.java index 2e7b8cd..7e5b676 100644 --- a/spring-batch-examples/src/main/java/com/io/example/fileReader/service/impl/TestServiceImpl.java +++ b/spring-batch-file-examples/src/main/java/com/io/example/service/impl/TestServiceImpl.java @@ -1,7 +1,7 @@ -package com.io.example.fileReader.service.impl; +package com.io.example.service.impl; -import com.io.example.fileReader.dto.StudentDto; -import com.io.example.fileReader.service.TestService; +import com.io.example.dto.StudentDto; +import com.io.example.service.TestService; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; diff --git a/spring-batch-examples/src/main/resources/application-dbReader.yml b/spring-batch-file-examples/src/main/resources/application.yml similarity index 54% rename from spring-batch-examples/src/main/resources/application-dbReader.yml rename to spring-batch-file-examples/src/main/resources/application.yml index f1b14b7..a41cd55 100644 --- a/spring-batch-examples/src/main/resources/application-dbReader.yml +++ b/spring-batch-file-examples/src/main/resources/application.yml @@ -1,5 +1,5 @@ server: - port: 8081 + port: 8082 spring: @@ -9,18 +9,17 @@ spring: username: sa password: - jpa: - hibernate: - ddl-auto: create-drop + jpa: + hibernate: + ddl-auto: create-drop - h2: - console: - enabled: true - path: /h2-console + h2: + console: + enabled: true + path: /h2-console batch: initialize-schema: always job: enabled: false - batch-size: 500 - chunk-size: 200 + chunk-size: 1000 diff --git a/spring-batch-examples/src/main/resources/students.xlsx b/spring-batch-file-examples/src/main/resources/files/students.xlsx similarity index 100% rename from spring-batch-examples/src/main/resources/students.xlsx rename to spring-batch-file-examples/src/main/resources/files/students.xlsx diff --git a/spring-batch-file-examples/src/test/java/com/io/example/FileReaderExampleApplicationTest.java b/spring-batch-file-examples/src/test/java/com/io/example/FileReaderExampleApplicationTest.java new file mode 100644 index 0000000..7ef1b2a --- /dev/null +++ b/spring-batch-file-examples/src/test/java/com/io/example/FileReaderExampleApplicationTest.java @@ -0,0 +1,25 @@ +package com.io.example; + +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class FileReaderExampleApplicationTest { + + @Test + void contextLoads() { + } + + @Test + void main_shouldCallSpringApplicationRun() { + try (MockedStatic mocked = Mockito.mockStatic(SpringApplication.class)) { + String[] args = {}; + FileReaderExampleApplication.main(args); + mocked.verify(() -> SpringApplication.run(FileReaderExampleApplication.class, args)); + } + } + +} diff --git a/spring-batch-examples/src/test/java/com/io/example/fileReader/controller/TestControllerTest.java b/spring-batch-file-examples/src/test/java/com/io/example/controller/TestControllerTest.java similarity index 88% rename from spring-batch-examples/src/test/java/com/io/example/fileReader/controller/TestControllerTest.java rename to spring-batch-file-examples/src/test/java/com/io/example/controller/TestControllerTest.java index 3ccfa65..bbf6902 100644 --- a/spring-batch-examples/src/test/java/com/io/example/fileReader/controller/TestControllerTest.java +++ b/spring-batch-file-examples/src/test/java/com/io/example/controller/TestControllerTest.java @@ -1,7 +1,8 @@ -package com.io.example.fileReader.controller; +package com.io.example.controller; -import com.io.example.fileReader.exception.BusinessException; -import com.io.example.fileReader.service.FileBatchService; +import com.io.example.exception.BusinessException; +import com.io.example.exception.GlobalHandlerException; +import com.io.example.service.FileBatchService; import org.instancio.Instancio; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -10,6 +11,7 @@ import org.springframework.batch.core.BatchStatus; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.context.annotation.Import; import org.springframework.http.MediaType; import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.test.web.servlet.MockMvc; @@ -19,8 +21,9 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -@DisplayName("TestController - Unit tests with MockMvc") @WebMvcTest(TestController.class) +@DisplayName("TestController - Unit tests with MockMvc") +@Import(GlobalHandlerException.class) class TestControllerTest { private static final Long jobId = Instancio.create(Long.class); @@ -65,7 +68,7 @@ void shouldReturn404WhenJobIdIsInvalid() throws Exception { mockMvc.perform(get("/job/{jobId}/status", jobId) .accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isNotFound()); + .andExpect(status().isBadRequest()); } } diff --git a/spring-batch-file-examples/src/test/java/com/io/example/job/BatchJobIntegrationTest.java b/spring-batch-file-examples/src/test/java/com/io/example/job/BatchJobIntegrationTest.java new file mode 100644 index 0000000..10857b1 --- /dev/null +++ b/spring-batch-file-examples/src/test/java/com/io/example/job/BatchJobIntegrationTest.java @@ -0,0 +1,76 @@ +package com.io.example.job; + +import com.io.example.dto.StudentDto; +import com.io.example.service.TestService; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.batch.core.*; +import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.extensions.excel.poi.PoiItemReader; +import org.springframework.batch.test.JobLauncherTestUtils; +import org.springframework.batch.test.context.SpringBatchTest; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.bean.override.mockito.MockitoBean; + +import static com.io.example.util.DataUtils.getParameters; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +@SpringBootTest +@SpringBatchTest +@ActiveProfiles("test") +@DisplayName("ExcelJob - Integration test with real Excel reading and job execution") +class BatchJobIntegrationTest { + + @Autowired + private JobLauncherTestUtils jobLauncherTestUtils; + + @Autowired + private PoiItemReader excelReader; + + @MockitoBean + private TestService testService; + + @Test + @DisplayName("Should execute ExcelJob and complete successfully reading real file") + void shouldExecuteJobSuccessfully() throws Exception { + JobExecution execution = jobLauncherTestUtils.launchJob(getParameters("files/students.xlsx")); + + assertThat(execution.getStatus()) + .as("Job should complete successfully") + .isEqualTo(BatchStatus.COMPLETED); + + verify(testService, atLeastOnce()).print(any(StudentDto.class)); + + } + + @Test + @DisplayName("Should fail executing ExcelJob when reader throws exception") + void shouldFailJobWhenReaderThrowsException() throws Exception { + + JobExecution execution = jobLauncherTestUtils.launchJob(getParameters("files/students-error.xlsx")); + + assertThat(execution.getStatus()).isEqualTo(BatchStatus.FAILED); + + verify(testService, never()).print(any(StudentDto.class)); + + } + + @Test + @DisplayName("Should execute ExcelJob and complete successfully when reader has no data") + void shouldCompleteJobWithNoData() throws Exception { + + JobExecution execution = jobLauncherTestUtils.launchJob(getParameters("files/students-empty.xlsx")); + + assertThat(execution.getStatus()) + .as("Job should complete successfully") + .isEqualTo(BatchStatus.COMPLETED); + + verify(testService, never()).print(any(StudentDto.class)); + + } + +} diff --git a/spring-batch-examples/src/test/java/com/io/example/fileReader/mapper/StudentMapperTest.java b/spring-batch-file-examples/src/test/java/com/io/example/mapper/StudentMapperTest.java similarity index 76% rename from spring-batch-examples/src/test/java/com/io/example/fileReader/mapper/StudentMapperTest.java rename to spring-batch-file-examples/src/test/java/com/io/example/mapper/StudentMapperTest.java index 07e82d5..277dcef 100644 --- a/spring-batch-examples/src/test/java/com/io/example/fileReader/mapper/StudentMapperTest.java +++ b/spring-batch-file-examples/src/test/java/com/io/example/mapper/StudentMapperTest.java @@ -1,6 +1,6 @@ -package com.io.example.fileReader.mapper; +package com.io.example.mapper; -import com.io.example.fileReader.dto.StudentDto; +import com.io.example.dto.StudentDto; import org.junit.jupiter.api.Test; import org.instancio.Instancio; import org.springframework.batch.extensions.excel.support.rowset.RowSet; @@ -16,9 +16,9 @@ void shouldMapRowSetUsingInstancio() { RowSet rowSet = mock(RowSet.class); when(rowSet.getCurrentRow()).thenReturn(new String[]{ - sample.getNome(), - sample.getTurma(), - sample.getData().toString() + sample.getName(), + sample.getClasS(), + sample.getDate().toString() }); StudentMapper mapper = new StudentMapper(); diff --git a/spring-batch-examples/src/test/java/com/io/example/dbReader/service/DBBatchServiceImplUnitTest.java b/spring-batch-file-examples/src/test/java/com/io/example/service/FileBatchServiceImplTest.java similarity index 85% rename from spring-batch-examples/src/test/java/com/io/example/dbReader/service/DBBatchServiceImplUnitTest.java rename to spring-batch-file-examples/src/test/java/com/io/example/service/FileBatchServiceImplTest.java index cfe1af2..6fd56b3 100644 --- a/spring-batch-examples/src/test/java/com/io/example/dbReader/service/DBBatchServiceImplUnitTest.java +++ b/spring-batch-file-examples/src/test/java/com/io/example/service/FileBatchServiceImplTest.java @@ -1,7 +1,7 @@ -package com.io.example.dbReader.service; +package com.io.example.service; -import com.io.example.dbReader.exception.BusinessException; -import com.io.example.dbReader.service.impl.DBBatchServiceImpl; +import com.io.example.exception.BusinessException; +import com.io.example.service.impl.FileBatchServiceImpl; import org.instancio.Instancio; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -22,7 +22,7 @@ @ExtendWith(MockitoExtension.class) @DisplayName("Unit tests for AsyncBatchServiceImpl") -class DBBatchServiceImplUnitTest { +class FileBatchServiceImplTest { private static final Long jobId = Instancio.create(Long.class); @@ -36,7 +36,7 @@ class DBBatchServiceImplUnitTest { private JobExplorer jobExplorer; @InjectMocks - private DBBatchServiceImpl asyncBatchService; + private FileBatchServiceImpl fileBatchService; @Test @DisplayName("Should run job successfully and return job execution id") @@ -47,7 +47,7 @@ void shouldRunJobSuccessfully() throws Exception { when(asyncJobLauncher.run(eq(asyncBatchJob), any(JobParameters.class))) .thenReturn(jobExecutionMock); - Long jobId = asyncBatchService.runJob(); + Long jobId = fileBatchService.runJob(); assertThat(jobId).isEqualTo(123L); verify(asyncJobLauncher).run(eq(asyncBatchJob), any(JobParameters.class)); @@ -61,7 +61,7 @@ void shouldThrowBusinessExceptionWhenJobFails() throws Exception { .thenThrow(new RuntimeException("Simulated error")); BusinessException exception = assertThrows(BusinessException.class, - () -> asyncBatchService.runJob()); + () -> fileBatchService.runJob()); assertThat(exception.getMessage()).isEqualTo("Simulated error"); } @@ -73,7 +73,7 @@ void shouldReturnJobStatus() { when(jobExecutionMock.getStatus()).thenReturn(BatchStatus.COMPLETED); when(jobExplorer.getJobExecution(jobId)).thenReturn(jobExecutionMock); - BatchStatus status = asyncBatchService.getJobStatus(jobId); + BatchStatus status = fileBatchService.getJobStatus(jobId); assertThat(status).isEqualTo(BatchStatus.COMPLETED); verify(jobExplorer).getJobExecution(jobId); @@ -85,7 +85,7 @@ void shouldThrowBusinessExceptionWhenJobNotFound() { when(jobExplorer.getJobExecution(jobId)).thenReturn(null); BusinessException exception = assertThrows(BusinessException.class, - () -> asyncBatchService.getJobStatus(jobId)); + () -> fileBatchService.getJobStatus(jobId)); assertThat(exception.getMessage()) .isEqualTo("JobExecution not found for this id: " + jobId); diff --git a/spring-batch-file-examples/src/test/java/com/io/example/service/TestServiceImplTest.java b/spring-batch-file-examples/src/test/java/com/io/example/service/TestServiceImplTest.java new file mode 100644 index 0000000..bb5a715 --- /dev/null +++ b/spring-batch-file-examples/src/test/java/com/io/example/service/TestServiceImplTest.java @@ -0,0 +1,18 @@ +package com.io.example.service; + +import com.io.example.dto.StudentDto; +import com.io.example.service.impl.TestServiceImpl; +import org.instancio.Instancio; +import org.junit.jupiter.api.Test; + +class TestServiceImplTest { + + private final TestServiceImpl service = new TestServiceImpl(); + + @Test + void print_shouldLogStudent() { + StudentDto student = Instancio.create(StudentDto.class); + service.print(student); + } + +} diff --git a/spring-batch-file-examples/src/test/java/com/io/example/util/DataUtils.java b/spring-batch-file-examples/src/test/java/com/io/example/util/DataUtils.java new file mode 100644 index 0000000..306cde4 --- /dev/null +++ b/spring-batch-file-examples/src/test/java/com/io/example/util/DataUtils.java @@ -0,0 +1,15 @@ +package com.io.example.util; + +import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.JobParametersBuilder; + +public class DataUtils { + + public static JobParameters getParameters(String filePath) { + return new JobParametersBuilder() + .addLong("time", System.currentTimeMillis()) + .addString("filePath", filePath) + .toJobParameters(); + } + +} \ No newline at end of file diff --git a/spring-batch-file-examples/src/test/resources/application-test.yml b/spring-batch-file-examples/src/test/resources/application-test.yml new file mode 100644 index 0000000..8c68bc3 --- /dev/null +++ b/spring-batch-file-examples/src/test/resources/application-test.yml @@ -0,0 +1,25 @@ +server: + port: 8082 + +spring: + + datasource: + url: jdbc:h2:mem:db + driver-class-name: org.h2.Driver + username: sa + password: + + jpa: + hibernate: + ddl-auto: create-drop + + h2: + console: + enabled: true + path: /h2-console + + batch: + initialize-schema: always + job: + enabled: false + chunk-size: 5 diff --git a/spring-batch-file-examples/src/test/resources/files/students-empty.xlsx b/spring-batch-file-examples/src/test/resources/files/students-empty.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..605672befcb4f84534218001ff093a8d03e06d0d GIT binary patch literal 8678 zcmb_h1z1#D*QUEWg&7c}Q%XuY6p)m5fT4$&K^j4nZj>%52`TCB4rx$OI-~@Hu78kw zugd5B@;}dW&O9?`-c|eEd#|;&nlds93BtvNZKbY{KkdmFi;947aTCLT)0eila{}8r z8Ed*ZfMJH5t`J*M3HXEF84QJ4+1rT$c{sU&^k6$vdowFL3o)RUrW}V5kRIj)vNHqO z+S`G}fNo$IP(mC_^t%XpIM@y*2DEf?auDI-f|*)^As`s1y#v?|{>I!M3IRF6zd|jz z96+WvAPX=TFE_UUm+7S$98MQz0L4XRz~-Qbwodf)vaWFRE-iuMf%Jdad!b7V2y$?+ zwK4@c!42iQc+2&BpbNzO3eXC2q1lz=;?3WHOswp#fPU8IY-e`GCk}gab1PG@nZ4;l z2t1&i_O@VE6Kk-k)88q8f^A{{nT9_i#tBD$_Zenstm%@z#1AkYSP7}YeawR?qAAH?V;fRT>GCCxE$p9UUoh>Jbc z#>C#<=0ErR%JjU#vkSg)LO@n_|BeCQ*$IOO1rGY(3HPfd7Y=?G{HGyY-s2`F_F2w2B{3ABDpW~5b{%VTjYT(XQqrQ zL7;E7r~Scs#M(70br%C6^U@MKJv9X{yeKs6xUW_f=p73z2Q0*Tu0ZKkoU+_R#!Bws ze)Pf`IvzK>FQJ-Tll6>Tq;a>u*1y0P1u7+edkJw7IaGLYWT3!qdz&=4%D0Pa^6|$| zpKBxqR97cB!cg&{Hg5 zp1{n6-9aOiGvL#}Xz2Z1JgLjw+U=H)%^J2MJOc}$`?T%cSw!AhKqxxN*_FLNE`&qN zR+C}(fVBuv%CCJ7|D~Z{QE4(+(b9W1%FcVw{e!W>ZneHOYUS?Be&A{SR?czjR^W29 zyTVLN22||sYh{Pvsoj@Y@%KpS$)vEF?9(Y&1X7F3UpoQPKPKYbgMhMkqA)6}b@COV zOxY-9oe9}=2uBQSUysVTkH-)RYQkkQA|nTLzuFneDZNP1AMS4@j4|p_!Y}xS!7%JS zs^HY#p)=x-t`{27c4FJ6(dPOlu?NXw(UsV9HT-j@%x>L~-@D}lCclEc23dcKt*PU> z8=eV^LcRXzv-q6a*@KSG)NKq{RQhL1nKK4dV+^S+v6Y-ioP0hNHkerR2+OHw2Z@$Y|mg~hd@Lp$@ z4#C$oI%Zy-CWaJre@?UFccW>+D%DL!QH*A4se1R(BeIjH=jAh9=&io9N6RJBFJH`T zK{!F@_MgrXf2D(%Z$XC}5(0ue8Uh0DAME~XcPZ2|gp+~flr3r&K!>cY7OnJLE`Llb zW=hH9W)SkkNz^+PQh}|e?I}FQ=C*u@=!4nC4cMFA3P8h_5cW*ah-NTO$0!D>%=6L= zleqM%NfYQ3BuGgv_cLF!kCObg0~!##(E3eG9z+6+l=KFk0al!cHOw>{dA5N*F?y`w zxKFLg*X0Netm=ZwxPvleyLm{v`3hVQ35mkTt11(oM+?3k;sB4)&`d_X46)D=O^#Kv z)=gsB#0DFVMjK4)`uEKcaOvRR0+7z|-k_v#q;&c$b)A9G6SBX^TTK)1v$q|;;6`zM z#xZq5or3mxDJk02TjZe}x|Un!K9Uw6(EZ%>l$f+0NCv+(kVIK&XPyDcRR!w}C9Hu| z=7rcP+>Hb+)zU*#%!rpBi~7w_WXK@i9vU>p$IZp8mPzegJ_UH>>;P0&GI8w?J``$_ z8caF1T3t8)_n(FlJkpg`oanNGsnc@_su!cnXnJWa1ko%H#6E3ZFdSO}hj)F%EOfcy9nH#0}c(UK#UE zx9w!uD907#75PTgda?6`7QeK__GDUI(lIdMz05{Z&n9t#YV6ltbKbNMg@Q>Yl-Gwp z6rUeRqZNi&uno{F?{@617^AUEXEsk)tvb~Z)8AyI6;T=Ei+0N-Wov1hOL`&uAnSO#9knpPG+!Ruwx~ z5=&Fp?kJsbtqdSrRS55=95l;7cChj@P#({qOdrX$fX?@JaD4B3ysDeK*W!bcw7X7w zYryTP0kxu;KQn5$aHE1^C#X0NgV3unPkR04YCIKXh44Dsat3>vSuMEvIZOKltAf7% z5n8U_wRGKREW{tLP*sdQGl4YExemsUN5Ky8lS{@Zkp6pRu_tGTu{_3fzUq<>JPM z$L2CH--GdA78j!GKc~F^WsUJm+6=ETewIm3uV^L)gescx0O>Dli+^3fTxJw9e}*&f^&uAk!ryOI;#N~oRLg|n?0o=6bDKd;6&WvE4| zotpq{PKTw&o>*2^c}jLHgT69=)s<#+&F;lrDRcHZ@dlRGd=J9d7l%!rX!R;C+20^_<_{JlM^1EJMf7~ z_;ZN<9PzN|9=kSr0!ifaq3oL^l^41DrRrvX+;|O-gNnR zL3^?r6X+k9Ja%sDY5)@npZi2rYQJO|makCMxq*2vUax#YGANv2D3N@G$4ac;ZKGKp z6|g48#J#3e9x}`m`sDp%GQbArD0GPy6U*CAf)GbT7ucvYgwop77 zx9>QnnE=utBH*&_$wDVATx~n~ZyX6M9{S1(=vfQX*^sOgKTk^h6i(*8c8&>gXIHA3|le88cx0X}=$K+#h( zCxOjxS%H^B#L%+!-U8c6MUUcYPE5J7<&S~*1iWC+v zJ`%a9}Wm~7-N9Em`dD)~Go5gD0+D5u=5fcFIUsLH$ zC#b-~uRcOd!WCY}jbH$eN&o|Uy61yy?te97T%=!5S5y7EoF&m4`&`qmq>od;HN^1Y zYfuQdeU=HZxM^13h9}VyF5VI@(V}t|08Uvb)X=IfTeCB{uKWDcvrau&&X=?$swN)E zZ+%?zE5S%NN!q8jy)xeyy?bX)G{m&u&yB)wMWNWzfzXQGUXrpYdSBn%4^>dB*ep#$ zkaMKi%@$4M1tCpQCsWi9Ne=6O@9YTAuY4i2v&fIJ88&QSC$(}BS9B8X9k&U1NB zUj*82wrW#1B|jZj(zRv?JJ;t(6mEg})-EhgKYdDUPZT3B?L3`Vg~1V457H5i=f1r$ zlcM$YpUSQXQd@5voMTgi5*zyH;}IXLkc`VbKJ~-k5zlQ=h1{?x?LrZ0FEG8`%_Bh- z9ndRuoo##P)q#$f&)nCgsx$lLAw%!0>r+DX(&Raw zemg!jYKjeszFo2?39*(fS$Sc&kr=g7iB`^SQf#f0)Hmdp8wUI+S2bNB82chjv|JYj zqN6`hctlj55HRIe<6Pz5FhMl-Mp|EXsAV}!8%8;$lzuI<lohjXn&4`As>> zQrzy!cW(Z#8~fco`SwV1wK%pH(G75#Z(w$mWFV@SMcyevUJ=`jK4Hv8zbbWA&;vbgclURQ`kapU31AYCd6LnX?!gh0Se_@ip!Z@I?$VAG^zl0=>jvgo)dqY9VFt}eA%DvOup{a%RIf(SEE}SU zJvJmF@cTv~L7zq8mNsJ0;F8Nwy+rKyNn@;|yg)%(M7^V$dMtn?DTdq6zgx>@_1ZdA zo5(DQW+e;+0BJA8ml!~b7xP*lQF?6hjUJF`8)43$COANe0i@-=$`taaJW-`6&hq`x zF?$n&>e9xyAU+9ub`r50JdJ+Bf?-vMw+GuE-S138OSL+S67_l1X&Lv5S3tC>=-bUZ zgm~J!ZDLAedE@h(4;vPkMi?|eqQE;b5#Tg5`zSBD+xxQ#n@<`WkM`EkSr7DgB-wT4 zLwK87o$kH&FbG`>$ljWErP?IA4_F;}#JL530IiJEF>(l%8tWg3>xL!rY8qwXsDI2XcCHML5a z%gRiF~5i(a}T7Z}+ zlcMriFRb|H8-DyPjk`};z?rX|Ide8KpWT6xQXbRZl&vUXiH;+6+X^y7#kOuoaza_! zs)Yd1188lKjx`R}0_F^@2|N37MPf7C>rPXJYSJ%w0Q_4 zCn)ZG{1UHj=P5n2z_E8GRJ#2!-ctdy97KzA`LRh~SKk_0z3-+GMIh&CU3!HoL6}MtzUi+h|M+ z_oAckoSh0pBfO(X>T%6qDG^6e+t?^JwoddsIVsHg=*Mj|Hr@-B5F!I=sprVX6c%`2 zTbu=AJ3f9zGFl5{+X!>m_?i=OpuLpEh%rRk)WOC^o>5Z)Np0p`pHEnZm?CTXb8e~E2z_ow{Pm*~{lgR7t+&Hu z=n^&OiB3+GOVhh1lwa+;C@U(znf7~H8X$w#BGcWl_H5edfO=NV--z2#cg|g}Nj%!V zRf+_L=o_=#O((f&S5`mnz{*rkL1l7CZ8bFXt>+ol{mKokYR8iLnn{f7Ls_XnTItH^ zE%S|VfIF88QDD$5UsT7XB2{k6?pZCfuEd<6;$QF>4OoWq3xu5WjU7uS!HX zMMf;)MdS?2iOR2kG7{RfW+sBT7|!dDLR7Qa*9@Wl;pl>X_&U?A^oircV*=Vi`Vk zLVq$nZW>{5UdKRXh?fg$9%-sYgsEHW5g3%x*ITQnKevA%8o3`reE>Rs<7WEyK7bf>`Ke>U1 z;N3F%O@ONLaJYCG7X=NL=J-og^2q0(5aqhL zhfoKsKidPo1kea(Z3}lH2Z#s83kXl#ACi-Qx<^ zwet27mlu?y0NRofq{0kK_UWgMFGhDm0qLXf*GADx1ycprYZx-yg&Um6-W_T@x*op!=&wS7)_m1C#X zoZ|_SOhO={Nnk- zSY5G0==QwFb^?~Hmx*Gl_$j)YG9tW4`}-4d`10}7^_%nY9|C@V7IT4y*Vyo@3x3G{ zyOWu#60RP9TwpIH#KIHiD}9iw;H%B@AK+|^OZ9)X&aXnR_Aq}y72$0=xZc0%X#SA% z`+nbroXZQ0^Go;d2k-vG8fVY)@#ed(()Ra--q_}9T QVk0!enM?q$rw|bS4}(>BH~;_u literal 0 HcmV?d00001 diff --git a/spring-batch-file-examples/src/test/resources/files/students-error.xlsx b/spring-batch-file-examples/src/test/resources/files/students-error.xlsx new file mode 100644 index 0000000..d36536a --- /dev/null +++ b/spring-batch-file-examples/src/test/resources/files/students-error.xlsx @@ -0,0 +1,3 @@ +this is not a real excel file +line2 +line3 \ No newline at end of file diff --git a/spring-batch-file-examples/src/test/resources/files/students.xlsx b/spring-batch-file-examples/src/test/resources/files/students.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..530e9744bb5fcfcc0e8fd9ce6b28d02866a6b9e5 GIT binary patch literal 4955 zcmZ`-1yodB*B)9LhHeSTA%`vrNnrp1r9qeh25E*A1e6l#ZlpzG0O^uO=@O9;q!Ez@ z>2K8af4=g+-@WUcv+i2=*>|7k?mAohE(Rt!006iFs6^K=#*x>w3`9Luq6P_SK%K0# zU7eiW_#Zku^LaVgYevc7qYIMUx$1LisgrM4u#_9QS5Za$n2tXNs#vA)q*bCl+L6(T zT6rGNhI_G?h&MzpdWj{D7(hW4UJEfTa->hTEwJ(iNUg;VMGUK|S(HpLzQQd?UDdoK zic-#YC1Nb|f~%b z=|9!Aa&m?J)ORp$SiMVtB-C8V)rkZCL2-RG*XtgMiFR`^)I)6QoS6oGyydXzNHvPH zlagxQdV*aDR!d9SmFNvhrB5muJ@U%c3!8I$$<_Fz{G)+IL+> zq4R{-i_Jj_$4Hh<87_y z@eEEO?dMs&rw?*Jx$@C5&ukulIt-mxno`;H_|8O^jirzC_Pc@~+TY})nq;t&U;_Xd z8~^|riX1O{euOQ|0rqLxgHXhu;*p*iGOCYsgD2i z!UG{TQh5QIBIHgRUV(;LjS(EsDem{44A?pQ^u>+4UgZ6gCJV;2{H&8-t7qo!2f^)g zs8ygAer6zBxe+}bBPa8l)hkTk9~Aw1k+3{8vIp|)4Pn)LC-&Xv)1PHw!yA$eLV=C~ zbOty5hn$L*Ax2?e^g6oQ>}+n|f7ClVrW)|{ySiLMh2igCP&!qpq#& zh`worumkPF(yo_yZU5K{XL`&9ou;gg%me$k`SQ5cdGHbF)X{W#Y~kxjU9ItBzxmB{ z+YG!&W3BN=o^#_-&wI*7ya-!&wL&|~pZAoEAU5Ib92ez}@;4}L6_l4YeSj1-D{|vE z0S3+jWEx08#ynTYV36Zv8x}Q{GIy}6b z72(R-GUrVUBB1U`uf(?9vJ7bkGT>kWKDK;FG8;RvSmKGMD%mAZ!SrRuxZF+xKijE#Y&850 zYY~Eda2-tBu8gE}549bW_PNA5$q>P#kELTkx1oEZ%p3%7+F?YpMWvN>kn$+VTGEKp zy4Fwz*18l2Sr`F(M!g_o)uiW!r6TdN7Vo|d#<~wp#uAannTes^9eNQ%`j#<7Ts>_o z*Tn$yBFn5(t$qDzzx74^laLQ!dr;J|$^@1AGBJD6?F}JeoV+wc+?JsGcG<5(g=Jqc z3gKbxHZaPr_;}%oCG|9Gmp^@sb~QRJ)=~1~X`6&UpWkr7cEf>8&RCrotDjB#_zUCD z%8tfeXXS#^ABFiitsb}zzU$RP#&bn`04md*?`w66Mq~jQBJzp}2oE@2ZEVC8}=q4-H(<-!LI5cL?N{VNt z#5UlK--~hTJlW{>qFt2ugxTPi^1xwjYIO8$tzD-gZ(BNvp*L(Vm-%~6NXl@hVyO3H z^AwxJ9&KTA2m4YskaC?x{l{Gp`2y5}_a)9Vqz?MtJ$(y7;~os2m|S0t2&HxBwPX|X zrO~*hnL=2NBa+LL#)0LYL*pJ7PNtdyGJI)R_AZ-%6hes) zRJBT=Dpb$ir_pU)<5BC<7k{e+t_3DjH!rRBx%&@lM!64*$7!->yT#sx^LrKI6{$7z15?HFCr5Kjr;hPbsu~&I-K1y; zC3MzAdTdH6p=IswaWU9rXoCsw@9dyIg%;8Wq@3cn>=86@Q4E#IO86^*o`pcn#%=-0 z!z&1|n5kTswzuQ$JR?4yf&v|a^n{=vsQMJ2x66QC6{qxW?Uzrp;|I?y>J2v0wz6B? z?euZXOmWy&5yl6?=F#Mq zWrdYxJ=1~&-T?;t1Uc5?^Px&OAXVpY-~ zf>yhU0DNb8`L*t{;$pG_*2EZ`j@!Km4d7Q-GaB$8xM+V9MVlpfVc(NQNme|e(ZXGI={1G^xj!-z;_x1=` z#d27tjl|<#z0ubD&3V#E*T<9PwW>n8`g+)!zYJ@=oB?)+(8C>y`Btv}Gh*sajaxE3 zB)W@LoFBKtv1G*LlF<;U6(>EBVGM(!i4~FZg&1emHF(pBo{gHR*@-$#~qJ?e| zCG^uJY|1r2wuS`LsrN5zr7NUsZB@&Qq|_ zTP4SVJ%^)i@q3n?7&7yVh_m>RsdMyDPFg62h^~N{+@?%!6K1Pm@X_o!#@|(h?|$B_ z4ywF5!UX^be;a`t!rLC^_OpJNyFc^u&tFh{A$}E~BI2U9r>$s_!;9ZU?5A%jLAt)! zw-8tYon8{2?96a1sT z527UvaX1{EhUEo~%KLbPDkY^>jQ2=YvF~2-bo9S^n3TuF8|iL4U=_5p-d?O{r*4s` z)af|xvKS4}q$(to-Z4T4P^XeJ0ghT%dAxQuRyDQRi#; zA~6b%!Qut=2S)lzI!Sseo06&Wnulee-Q*+Xl@J1MBIVO2v+?mlvE?P1m)57FA=-&! z>8kqb$F#AC`4}Dj4Y;mLjJ#&xG($vo)HA8XRdOsyVqc`sGkE@MPE1e1mOPjmqNqzs zhUqIWSNLmn*02IFCmvpVU99pe0s$O6uJ9cO-+h@rr}<*v%c8n7!oO`VIlPI!4P|p= zDEq@lt>&&UdpCZ*pL1qhgJY)vft-K$NR|N32y+-1wo8e5f_6WbcnXLWu9W!}IbGlq zgHPivBMqEogv)J-=hwa{roJG#2RHgqkEU9CD;ho&t57}xViXA-le;rxrC4DEl9l75 zv94=@QWm-8KL|7Vm{}k*!>=Xp6o8b}a8ZD(-USow437B=9q$u}D6OcoCK}UvjjvpK zEU1m>UFNNefj^ysIXMe2;Icgm5RCpt4(jOAk8)g^ghM8bRj^ogeE z;8@d4MIF4xe4CxwV5hsNDb;k^@^RQh7`O+CmjiD85Ap(!Kr`=3GGpXm*XH#IyW2XC&%hpN77Qyrd z^XrFYKGK1F#IGoP6gb*=cyDC!3fg6j4%=GM_Ir!{8cDgYglNLKlbnriF229%0Ke~7p#`a4^QICzRw^G{ z!Y!XCaP~r6Udz|881Fb5)CPw!ejoUZ6`dRrFh_*Bp0_j1&Ge@wmBnj9I#H<_34|Hn z;sY7HETpr*H+4p{(xNT7jJGiwn*l?n)n5WBf;CIMJ~t0%C6|7VCaqY{Je*2k(D5Zl z*T;caGO&1+S2Ihfou}2ZsMo|BlN|T2JkxF`^LowsIAQYxxZ2gJ*lWX^PIE^ywawB{ zI>6CyM2=>;p?4O4Q?YAHY|Jw+TkTZCMOKeUmr`!sVgg1#y&SIuu5Dn zlan-R^O;`kqfwlp#gcs0vXn$?ghE9(0zZ)SaoB4GGp!og&ZISEf>wSseSO6dZ4f#*>|8*&@!>^y* z|G)=OpZ}M0{B;4>E4Tj&2qe7mOTfR@ao43>PyPRuf(FF Date: Sat, 18 Oct 2025 15:35:09 -0300 Subject: [PATCH 10/16] feat(spring-batch-examples): reorganize imports and enhance test coverage in integration and unit tests --- .../src/main/java/com/io/example/config/BatchConfig.java | 4 ++-- .../java/com/io/example/controller/TestControllerTest.java | 5 +++-- .../java/com/io/example/job/BatchJobIntegrationTest.java | 7 +++++-- .../main/java/com/io/example/config/ExcelBatchConfig.java | 5 ++--- .../java/com/io/example/controller/TestController.java | 1 - .../src/main/java/com/io/example/dto/StudentDto.java | 1 + .../src/main/java/com/io/example/mapper/StudentMapper.java | 1 + .../java/com/io/example/job/BatchJobIntegrationTest.java | 4 ++-- .../test/java/com/io/example/mapper/StudentMapperTest.java | 5 +++-- 9 files changed, 19 insertions(+), 14 deletions(-) diff --git a/spring-batch-db-examples/src/main/java/com/io/example/config/BatchConfig.java b/spring-batch-db-examples/src/main/java/com/io/example/config/BatchConfig.java index cf3b3f9..fce5ef9 100644 --- a/spring-batch-db-examples/src/main/java/com/io/example/config/BatchConfig.java +++ b/spring-batch-db-examples/src/main/java/com/io/example/config/BatchConfig.java @@ -1,9 +1,9 @@ package com.io.example.config; -import com.io.example.model.dto.TestDto; -import com.io.example.model.entity.TestEntity; import com.io.example.listener.LoggingStepListener; import com.io.example.mapper.TestEntityMapper; +import com.io.example.model.dto.TestDto; +import com.io.example.model.entity.TestEntity; import com.io.example.service.TestService; import lombok.RequiredArgsConstructor; import org.springframework.batch.core.Step; diff --git a/spring-batch-db-examples/src/test/java/com/io/example/controller/TestControllerTest.java b/spring-batch-db-examples/src/test/java/com/io/example/controller/TestControllerTest.java index dc3c73a..845f107 100644 --- a/spring-batch-db-examples/src/test/java/com/io/example/controller/TestControllerTest.java +++ b/spring-batch-db-examples/src/test/java/com/io/example/controller/TestControllerTest.java @@ -8,15 +8,16 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; import org.springframework.batch.core.BatchStatus; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.http.MediaType; import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.test.web.servlet.MockMvc; -import org.springframework.beans.factory.annotation.Autowired; import static org.mockito.Mockito.when; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @DisplayName("TestController - Unit tests with MockMvc") @WebMvcTest(TestController.class) diff --git a/spring-batch-db-examples/src/test/java/com/io/example/job/BatchJobIntegrationTest.java b/spring-batch-db-examples/src/test/java/com/io/example/job/BatchJobIntegrationTest.java index 359e8a0..cab80bd 100644 --- a/spring-batch-db-examples/src/test/java/com/io/example/job/BatchJobIntegrationTest.java +++ b/spring-batch-db-examples/src/test/java/com/io/example/job/BatchJobIntegrationTest.java @@ -5,7 +5,9 @@ import com.io.example.service.TestService; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.*; +import org.springframework.batch.core.BatchStatus; +import org.springframework.batch.core.Job; +import org.springframework.batch.core.JobExecution; import org.springframework.batch.core.launch.JobLauncher; import org.springframework.batch.item.database.JdbcPagingItemReader; import org.springframework.batch.test.context.SpringBatchTest; @@ -14,7 +16,8 @@ import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.bean.override.mockito.MockitoBean; -import static com.io.example.util.DataUtils.*; +import static com.io.example.util.DataUtils.configJdbcPagingItemReaderMock; +import static com.io.example.util.DataUtils.getParameters; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; diff --git a/spring-batch-file-examples/src/main/java/com/io/example/config/ExcelBatchConfig.java b/spring-batch-file-examples/src/main/java/com/io/example/config/ExcelBatchConfig.java index 0629d51..42e582e 100644 --- a/spring-batch-file-examples/src/main/java/com/io/example/config/ExcelBatchConfig.java +++ b/spring-batch-file-examples/src/main/java/com/io/example/config/ExcelBatchConfig.java @@ -1,11 +1,11 @@ package com.io.example.config; -import com.io.example.service.TestService; import com.io.example.dto.StudentDto; import com.io.example.mapper.StudentMapper; +import com.io.example.service.TestService; import lombok.RequiredArgsConstructor; -import lombok.SneakyThrows; import org.springframework.batch.core.Step; +import org.springframework.batch.core.configuration.annotation.StepScope; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.step.builder.StepBuilder; import org.springframework.batch.extensions.excel.poi.PoiItemReader; @@ -17,7 +17,6 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.ClassPathResource; -import org.springframework.batch.core.configuration.annotation.StepScope; import org.springframework.core.io.InputStreamResource; import org.springframework.core.task.SimpleAsyncTaskExecutor; import org.springframework.transaction.PlatformTransactionManager; diff --git a/spring-batch-file-examples/src/main/java/com/io/example/controller/TestController.java b/spring-batch-file-examples/src/main/java/com/io/example/controller/TestController.java index 10493ca..bdd8db8 100644 --- a/spring-batch-file-examples/src/main/java/com/io/example/controller/TestController.java +++ b/spring-batch-file-examples/src/main/java/com/io/example/controller/TestController.java @@ -1,6 +1,5 @@ package com.io.example.controller; -import com.io.example.exception.BusinessException; import com.io.example.service.FileBatchService; import lombok.RequiredArgsConstructor; import org.springframework.batch.core.BatchStatus; diff --git a/spring-batch-file-examples/src/main/java/com/io/example/dto/StudentDto.java b/spring-batch-file-examples/src/main/java/com/io/example/dto/StudentDto.java index 1dac686..9b767cf 100644 --- a/spring-batch-file-examples/src/main/java/com/io/example/dto/StudentDto.java +++ b/spring-batch-file-examples/src/main/java/com/io/example/dto/StudentDto.java @@ -4,6 +4,7 @@ import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; + import java.time.LocalDate; @Data diff --git a/spring-batch-file-examples/src/main/java/com/io/example/mapper/StudentMapper.java b/spring-batch-file-examples/src/main/java/com/io/example/mapper/StudentMapper.java index bb34187..f1e254e 100644 --- a/spring-batch-file-examples/src/main/java/com/io/example/mapper/StudentMapper.java +++ b/spring-batch-file-examples/src/main/java/com/io/example/mapper/StudentMapper.java @@ -3,6 +3,7 @@ import com.io.example.dto.StudentDto; import org.springframework.batch.extensions.excel.RowMapper; import org.springframework.batch.extensions.excel.support.rowset.RowSet; + import java.time.LocalDate; public class StudentMapper implements RowMapper { diff --git a/spring-batch-file-examples/src/test/java/com/io/example/job/BatchJobIntegrationTest.java b/spring-batch-file-examples/src/test/java/com/io/example/job/BatchJobIntegrationTest.java index 10857b1..2289946 100644 --- a/spring-batch-file-examples/src/test/java/com/io/example/job/BatchJobIntegrationTest.java +++ b/spring-batch-file-examples/src/test/java/com/io/example/job/BatchJobIntegrationTest.java @@ -4,8 +4,8 @@ import com.io.example.service.TestService; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.*; -import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.BatchStatus; +import org.springframework.batch.core.JobExecution; import org.springframework.batch.extensions.excel.poi.PoiItemReader; import org.springframework.batch.test.JobLauncherTestUtils; import org.springframework.batch.test.context.SpringBatchTest; diff --git a/spring-batch-file-examples/src/test/java/com/io/example/mapper/StudentMapperTest.java b/spring-batch-file-examples/src/test/java/com/io/example/mapper/StudentMapperTest.java index 277dcef..93aca7f 100644 --- a/spring-batch-file-examples/src/test/java/com/io/example/mapper/StudentMapperTest.java +++ b/spring-batch-file-examples/src/test/java/com/io/example/mapper/StudentMapperTest.java @@ -1,12 +1,13 @@ package com.io.example.mapper; import com.io.example.dto.StudentDto; -import org.junit.jupiter.api.Test; import org.instancio.Instancio; +import org.junit.jupiter.api.Test; import org.springframework.batch.extensions.excel.support.rowset.RowSet; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; class StudentMapperTest { From 64eeb5bf885460019bd06f6c586dd2971889a17a Mon Sep 17 00:00:00 2001 From: igorcampos-dev Date: Sat, 18 Oct 2025 16:17:29 -0300 Subject: [PATCH 11/16] feat(spring-batch-examples): add banner files and update version to 1.0.0 for db and file examples --- spring-batch-db-examples/pom.xml | 2 +- spring-batch-db-examples/src/main/resources/banner.txt | 6 ++++++ spring-batch-file-examples/pom.xml | 2 +- spring-batch-file-examples/src/main/resources/banner.txt | 6 ++++++ 4 files changed, 14 insertions(+), 2 deletions(-) create mode 100644 spring-batch-db-examples/src/main/resources/banner.txt create mode 100644 spring-batch-file-examples/src/main/resources/banner.txt diff --git a/spring-batch-db-examples/pom.xml b/spring-batch-db-examples/pom.xml index 63b78b9..0e7e6a6 100644 --- a/spring-batch-db-examples/pom.xml +++ b/spring-batch-db-examples/pom.xml @@ -13,7 +13,7 @@ com.io spring-batch-db-examples - 0.0.1-SNAPSHOT + 1.0.0 spring-batch-db-examples Demo project for Spring Boot diff --git a/spring-batch-db-examples/src/main/resources/banner.txt b/spring-batch-db-examples/src/main/resources/banner.txt new file mode 100644 index 0000000..112316b --- /dev/null +++ b/spring-batch-db-examples/src/main/resources/banner.txt @@ -0,0 +1,6 @@ +,---. ,------. ,------. ,--.,--. ,--. ,----. ,-----. ,---. ,--------.,-----.,--. ,--. ,------. ,-----. +' .-'| .--. '| .--. '| || ,'.| |' .-./ ,-----.| |) /_ / O \'--. .--' .--./| '--' |,-----.| .-. \ | |) /_ +`. `-.| '--' || '--'.'| || |' ' || | .---.'-----'| .-. \ .-. | | | | | | .--. |'-----'| | \ :| .-. \ +.-' | | --' | |\ \ | || | ` |' '--' | | '--' / | | | | | ' '--'\| | | | | '--' /| '--' / +`-----'`--' `--' '--'`--'`--' `--' `------' `------'`--' `--' `--' `-----'`--' `--' `-------' `------' + SPRING-BATCH-DB-EXAMPLES \ No newline at end of file diff --git a/spring-batch-file-examples/pom.xml b/spring-batch-file-examples/pom.xml index 6750d81..0d35d1f 100644 --- a/spring-batch-file-examples/pom.xml +++ b/spring-batch-file-examples/pom.xml @@ -13,7 +13,7 @@ com.io spring-batch-file-examples - 0.0.1-SNAPSHOT + 1.0.0 spring-batch-file-examples Demo project for Spring Boot diff --git a/spring-batch-file-examples/src/main/resources/banner.txt b/spring-batch-file-examples/src/main/resources/banner.txt new file mode 100644 index 0000000..52c0568 --- /dev/null +++ b/spring-batch-file-examples/src/main/resources/banner.txt @@ -0,0 +1,6 @@ + ,---. ,------. ,------. ,--.,--. ,--. ,----. ,-----. ,---. ,--------.,-----.,--. ,--. ,------.,--.,--. ,------. +' .-'| .--. '| .--. '| || ,'.| |' .-./ ,-----.| |) /_ / O \'--. .--' .--./| '--' |,-----.| .---'| || | | .---' +`. `-.| '--' || '--'.'| || |' ' || | .---.'-----'| .-. \ .-. | | | | | | .--. |'-----'| `--, | || | | `--, +.-' | | --' | |\ \ | || | ` |' '--' | | '--' / | | | | | ' '--'\| | | | | |` | || '--.| `---. +`-----'`--' `--' '--'`--'`--' `--' `------' `------'`--' `--' `--' `-----'`--' `--' `--' `--'`-----'`------' + SPRING-BATCH-FILE-EXAMPLES \ No newline at end of file From 936cd497e79de4cb79c970978468531d5f3f02fe Mon Sep 17 00:00:00 2001 From: igorcampos-dev Date: Sat, 18 Oct 2025 16:21:16 -0300 Subject: [PATCH 12/16] feat(spring-batch-examples): update README to clarify examples for database and file integrations --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index bd7549c..0d75c48 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,8 @@ Below you will find a summary table of each subproject. For more details, please | Name | Description | |---------------------------------------------------------------------------|-----------------------------------------------------------------------------------| -| [Spring Boot + Spring Batch](./spring-batch-examples) | Spring batch examples. | +| [Spring Boot + Spring Batch + Database](./spring-batch-db-examples) | Demonstrates a basic integration between Spring Batch and Databases | +| [Spring Boot + Spring Batch + File](./spring-batch-file-examples) | Demonstrates a basic integration between Spring Batch and Files | | [Spring Boot + Jasper](./spring-jasper-example) | Demonstrates a basic integration between Spring Boot and Jasper. | | [Spring Boot + Kafka](./spring-kafka-example) | Demonstrates a basic integration between Spring Boot and Apache Kafka. | | [Spring Boot + Keycloak](./spring-keycloak-example) | Demonstrates a basic integration between Spring Boot and Keycloak. | From 2aa5f20dd0dd419f6ae198a34ed7c597478fba8a Mon Sep 17 00:00:00 2001 From: igorcampos-dev Date: Mon, 17 Nov 2025 08:34:05 -0300 Subject: [PATCH 13/16] feat(spring-batch-examples): add Docker configuration files and CI workflows for batch examples --- .github/workflows/spring-batch-db-example.yml | 68 +++++++++++++++++++ .../workflows/spring-batch-file-example.yml | 68 +++++++++++++++++++ spring-batch-db-examples/.dockerignore | 62 +++++++++++++++++ spring-batch-db-examples/Dockerfile | 47 +++++++++++++ spring-batch-db-examples/compose.yaml | 21 ++++++ .../src/main/resources/application-prd.yml | 25 +++++++ spring-batch-file-examples/.dockerignore | 62 +++++++++++++++++ spring-batch-file-examples/Dockerfile | 47 +++++++++++++ spring-batch-file-examples/compose.yaml | 21 ++++++ .../src/main/resources/application-prd.yml | 25 +++++++ 10 files changed, 446 insertions(+) create mode 100644 .github/workflows/spring-batch-db-example.yml create mode 100644 .github/workflows/spring-batch-file-example.yml create mode 100644 spring-batch-db-examples/.dockerignore create mode 100644 spring-batch-db-examples/Dockerfile create mode 100644 spring-batch-db-examples/compose.yaml create mode 100644 spring-batch-db-examples/src/main/resources/application-prd.yml create mode 100644 spring-batch-file-examples/.dockerignore create mode 100644 spring-batch-file-examples/Dockerfile create mode 100644 spring-batch-file-examples/compose.yaml create mode 100644 spring-batch-file-examples/src/main/resources/application-prd.yml diff --git a/.github/workflows/spring-batch-db-example.yml b/.github/workflows/spring-batch-db-example.yml new file mode 100644 index 0000000..b2a702d --- /dev/null +++ b/.github/workflows/spring-batch-db-example.yml @@ -0,0 +1,68 @@ +name: spring-batch-db-example CI Build + +on: + pull_request: + branches: [master] + paths: + - "spring-batch-db-example/**" + types: + - opened + - synchronize + - reopened + +jobs: + + integration-tests: + name: Run Unit & Integration Tests + runs-on: ubuntu-latest + defaults: + run: + working-directory: spring-batch-db-example + strategy: + matrix: + distribution: [ 'temurin' ] + java: [ '21' ] + steps: + - uses: actions/checkout@v5 + with: + fetch-depth: 0 + + - name: Set up JDK ${{ matrix.java }} + uses: actions/setup-java@v5.0.0 + with: + java-version: ${{ matrix.java }} + distribution: ${{ matrix.distribution }} + cache: 'maven' + - name: Build and analyze + run: ./mvnw clean verify + + health-check: + name: Health Check on Services + runs-on: ubuntu-latest + steps: + - name: Checkout repository and submodules + uses: actions/checkout@v5 + with: + submodules: true + + - name: Extract service names from docker compose + id: services + run: | + echo "services<> $GITHUB_OUTPUT + docker compose -f ./spring-batch-db-example/compose.yaml config --services >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + - name: Start containers with Compose Action + uses: hoverkraft-tech/compose-action@v2.4.0 + with: + compose-file: './spring-batch-db-example/compose.yaml' + services: ${{ steps.services.outputs.services }} + up-flags: '--build' + down-flags: '--volumes' + + - name: Wait for containers to initialize + run: sleep 10 + + - name: Check container health + run: | + ./.github/scripts/check-container-health.sh "${{ steps.services.outputs.services }}" \ No newline at end of file diff --git a/.github/workflows/spring-batch-file-example.yml b/.github/workflows/spring-batch-file-example.yml new file mode 100644 index 0000000..68ee205 --- /dev/null +++ b/.github/workflows/spring-batch-file-example.yml @@ -0,0 +1,68 @@ +name: spring-batch-file-example CI Build + +on: + pull_request: + branches: [master] + paths: + - "spring-batch-file-example/**" + types: + - opened + - synchronize + - reopened + +jobs: + + integration-tests: + name: Run Unit & Integration Tests + runs-on: ubuntu-latest + defaults: + run: + working-directory: spring-batch-file-example + strategy: + matrix: + distribution: [ 'temurin' ] + java: [ '21' ] + steps: + - uses: actions/checkout@v5 + with: + fetch-depth: 0 + + - name: Set up JDK ${{ matrix.java }} + uses: actions/setup-java@v5.0.0 + with: + java-version: ${{ matrix.java }} + distribution: ${{ matrix.distribution }} + cache: 'maven' + - name: Build and analyze + run: ./mvnw clean verify + + health-check: + name: Health Check on Services + runs-on: ubuntu-latest + steps: + - name: Checkout repository and submodules + uses: actions/checkout@v5 + with: + submodules: true + + - name: Extract service names from docker compose + id: services + run: | + echo "services<> $GITHUB_OUTPUT + docker compose -f ./spring-batch-file-example/compose.yaml config --services >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + - name: Start containers with Compose Action + uses: hoverkraft-tech/compose-action@v2.4.0 + with: + compose-file: './spring-batch-file-example/compose.yaml' + services: ${{ steps.services.outputs.services }} + up-flags: '--build' + down-flags: '--volumes' + + - name: Wait for containers to initialize + run: sleep 10 + + - name: Check container health + run: | + ./.github/scripts/check-container-health.sh "${{ steps.services.outputs.services }}" \ No newline at end of file diff --git a/spring-batch-db-examples/.dockerignore b/spring-batch-db-examples/.dockerignore new file mode 100644 index 0000000..8893889 --- /dev/null +++ b/spring-batch-db-examples/.dockerignore @@ -0,0 +1,62 @@ +# Include any files or directories that you don't want to be copied to your +# container here (e.g., local build artifacts, temporary files, etc.). +# +# For more help, visit the .dockerignore file reference guide at +# https://docs.docker.com/go/build-context-dockerignore/ + +**/.DS_Store +**/.classpath +**/.dockerignore +**/.env +**/.factorypath +**/.git +**/.gitignore +**/.idea +**/.project +**/.sts4-cache +**/.settings +**/.toolstarget +**/.vs +**/.vscode +**/.next +**/.cache +**/*.dbmdl +**/*.jfm +**/charts +**/docker-compose* +**/compose.y*ml +**/Dockerfile* +**/secrets.dev.yaml +**/values.dev.yaml +**/vendor +LICENSE +README.md +**/*.class +**/*.iml +**/*.ipr +**/*.iws +**/*.log +**/.apt_generated +**/.gradle +**/.gradletasknamecache +**/.nb-gradle +**/.springBeans +**/build +**/dist +**/gradle-app.setting +**/nbbuild +**/nbdist +**/nbproject/private +**/target +*.ctxt +.mtj.tmp +.mvn/timing.properties +buildNumber.properties +dependency-reduced-pom.xml +hs_err_pid* +pom.xml.next +pom.xml.releaseBackup +pom.xml.tag +pom.xml.versionsBackup +release.properties +replay_pid* \ No newline at end of file diff --git a/spring-batch-db-examples/Dockerfile b/spring-batch-db-examples/Dockerfile new file mode 100644 index 0000000..ed30a9a --- /dev/null +++ b/spring-batch-db-examples/Dockerfile @@ -0,0 +1,47 @@ +FROM eclipse-temurin:21-jdk-jammy as deps + +WORKDIR /build + +COPY --chmod=0755 mvnw mvnw +COPY .mvn/ .mvn/ + +RUN --mount=type=bind,source=pom.xml,target=pom.xml \ + --mount=type=cache,target=/root/.m2 ./mvnw dependency:go-offline -DskipTests + +FROM deps as package + +WORKDIR /build + +COPY ./src src/ +RUN --mount=type=bind,source=pom.xml,target=pom.xml \ + --mount=type=cache,target=/root/.m2 \ + ./mvnw package -DskipTests && \ + mv target/$(./mvnw help:evaluate -Dexpression=project.artifactId -q -DforceStdout)-$(./mvnw help:evaluate -Dexpression=project.version -q -DforceStdout).jar target/app.jar + +FROM package as extract + +WORKDIR /build + +RUN java -Djarmode=layertools -jar target/app.jar extract --destination target/extracted + +FROM eclipse-temurin:21-jre-jammy AS final + +ARG UID=10001 +RUN adduser \ + --disabled-password \ + --gecos "" \ + --home "/nonexistent" \ + --shell "/sbin/nologin" \ + --no-create-home \ + --uid "${UID}" \ + appuser +USER appuser + +COPY --from=extract build/target/extracted/dependencies/ ./ +COPY --from=extract build/target/extracted/spring-boot-loader/ ./ +COPY --from=extract build/target/extracted/snapshot-dependencies/ ./ +COPY --from=extract build/target/extracted/application/ ./ + +EXPOSE 8082 + +ENTRYPOINT [ "java", "org.springframework.boot.loader.launch.JarLauncher" ] diff --git a/spring-batch-db-examples/compose.yaml b/spring-batch-db-examples/compose.yaml new file mode 100644 index 0000000..959a73b --- /dev/null +++ b/spring-batch-db-examples/compose.yaml @@ -0,0 +1,21 @@ +services: + + app: + container_name: app + build: + context: . + ports: + - "8082:8082" + environment: + SERVER_PORT: "8082" + SPRING_DATASOURCE_URL: "jdbc:h2:mem:db" + SPRING_DATASOURCE_DRIVER_CLASS_NAME: "org.h2.Driver" + SPRING_DATASOURCE_USERNAME: "sa" + SPRING_DATASOURCE_PASSWORD: "" + SPRING_JPA_HIBERNATE_DDL_AUTO: "create-drop" + SPRING_H2_CONSOLE_ENABLED: "true" + SPRING_H2_CONSOLE_PATH: "/h2-console" + SPRING_BATCH_INITIALIZE_SCHEMA: "always" + SPRING_BATCH_JOB_ENABLED: "false" + SPRING_BATCH_BATCH_SIZE: "1000" + SPRING_PROFILES_ACTIVE: "prd" \ No newline at end of file diff --git a/spring-batch-db-examples/src/main/resources/application-prd.yml b/spring-batch-db-examples/src/main/resources/application-prd.yml new file mode 100644 index 0000000..2a676ca --- /dev/null +++ b/spring-batch-db-examples/src/main/resources/application-prd.yml @@ -0,0 +1,25 @@ +server: + port: ${SERVER_PORT} + +spring: + + datasource: + url: ${SPRING_DATASOURCE_URL} + driver-class-name: ${SPRING_DATASOURCE_DRIVER_CLASS_NAME} + username: ${SPRING_DATASOURCE_USERNAME} + password: ${SPRING_DATASOURCE_PASSWORD} + + jpa: + hibernate: + ddl-auto: ${SPRING_JPA_HIBERNATE_DDL_AUTO} + + h2: + console: + enabled: ${SPRING_H2_CONSOLE_ENABLED} + path: ${SPRING_H2_CONSOLE_PATH} + + batch: + initialize-schema: ${SPRING_BATCH_INITIALIZE_SCHEMA} + job: + enabled: ${SPRING_BATCH_JOB_ENABLED} + batch-size: ${SPRING_BATCH_BATCH_SIZE} diff --git a/spring-batch-file-examples/.dockerignore b/spring-batch-file-examples/.dockerignore new file mode 100644 index 0000000..8893889 --- /dev/null +++ b/spring-batch-file-examples/.dockerignore @@ -0,0 +1,62 @@ +# Include any files or directories that you don't want to be copied to your +# container here (e.g., local build artifacts, temporary files, etc.). +# +# For more help, visit the .dockerignore file reference guide at +# https://docs.docker.com/go/build-context-dockerignore/ + +**/.DS_Store +**/.classpath +**/.dockerignore +**/.env +**/.factorypath +**/.git +**/.gitignore +**/.idea +**/.project +**/.sts4-cache +**/.settings +**/.toolstarget +**/.vs +**/.vscode +**/.next +**/.cache +**/*.dbmdl +**/*.jfm +**/charts +**/docker-compose* +**/compose.y*ml +**/Dockerfile* +**/secrets.dev.yaml +**/values.dev.yaml +**/vendor +LICENSE +README.md +**/*.class +**/*.iml +**/*.ipr +**/*.iws +**/*.log +**/.apt_generated +**/.gradle +**/.gradletasknamecache +**/.nb-gradle +**/.springBeans +**/build +**/dist +**/gradle-app.setting +**/nbbuild +**/nbdist +**/nbproject/private +**/target +*.ctxt +.mtj.tmp +.mvn/timing.properties +buildNumber.properties +dependency-reduced-pom.xml +hs_err_pid* +pom.xml.next +pom.xml.releaseBackup +pom.xml.tag +pom.xml.versionsBackup +release.properties +replay_pid* \ No newline at end of file diff --git a/spring-batch-file-examples/Dockerfile b/spring-batch-file-examples/Dockerfile new file mode 100644 index 0000000..ed30a9a --- /dev/null +++ b/spring-batch-file-examples/Dockerfile @@ -0,0 +1,47 @@ +FROM eclipse-temurin:21-jdk-jammy as deps + +WORKDIR /build + +COPY --chmod=0755 mvnw mvnw +COPY .mvn/ .mvn/ + +RUN --mount=type=bind,source=pom.xml,target=pom.xml \ + --mount=type=cache,target=/root/.m2 ./mvnw dependency:go-offline -DskipTests + +FROM deps as package + +WORKDIR /build + +COPY ./src src/ +RUN --mount=type=bind,source=pom.xml,target=pom.xml \ + --mount=type=cache,target=/root/.m2 \ + ./mvnw package -DskipTests && \ + mv target/$(./mvnw help:evaluate -Dexpression=project.artifactId -q -DforceStdout)-$(./mvnw help:evaluate -Dexpression=project.version -q -DforceStdout).jar target/app.jar + +FROM package as extract + +WORKDIR /build + +RUN java -Djarmode=layertools -jar target/app.jar extract --destination target/extracted + +FROM eclipse-temurin:21-jre-jammy AS final + +ARG UID=10001 +RUN adduser \ + --disabled-password \ + --gecos "" \ + --home "/nonexistent" \ + --shell "/sbin/nologin" \ + --no-create-home \ + --uid "${UID}" \ + appuser +USER appuser + +COPY --from=extract build/target/extracted/dependencies/ ./ +COPY --from=extract build/target/extracted/spring-boot-loader/ ./ +COPY --from=extract build/target/extracted/snapshot-dependencies/ ./ +COPY --from=extract build/target/extracted/application/ ./ + +EXPOSE 8082 + +ENTRYPOINT [ "java", "org.springframework.boot.loader.launch.JarLauncher" ] diff --git a/spring-batch-file-examples/compose.yaml b/spring-batch-file-examples/compose.yaml new file mode 100644 index 0000000..959a73b --- /dev/null +++ b/spring-batch-file-examples/compose.yaml @@ -0,0 +1,21 @@ +services: + + app: + container_name: app + build: + context: . + ports: + - "8082:8082" + environment: + SERVER_PORT: "8082" + SPRING_DATASOURCE_URL: "jdbc:h2:mem:db" + SPRING_DATASOURCE_DRIVER_CLASS_NAME: "org.h2.Driver" + SPRING_DATASOURCE_USERNAME: "sa" + SPRING_DATASOURCE_PASSWORD: "" + SPRING_JPA_HIBERNATE_DDL_AUTO: "create-drop" + SPRING_H2_CONSOLE_ENABLED: "true" + SPRING_H2_CONSOLE_PATH: "/h2-console" + SPRING_BATCH_INITIALIZE_SCHEMA: "always" + SPRING_BATCH_JOB_ENABLED: "false" + SPRING_BATCH_BATCH_SIZE: "1000" + SPRING_PROFILES_ACTIVE: "prd" \ No newline at end of file diff --git a/spring-batch-file-examples/src/main/resources/application-prd.yml b/spring-batch-file-examples/src/main/resources/application-prd.yml new file mode 100644 index 0000000..2a676ca --- /dev/null +++ b/spring-batch-file-examples/src/main/resources/application-prd.yml @@ -0,0 +1,25 @@ +server: + port: ${SERVER_PORT} + +spring: + + datasource: + url: ${SPRING_DATASOURCE_URL} + driver-class-name: ${SPRING_DATASOURCE_DRIVER_CLASS_NAME} + username: ${SPRING_DATASOURCE_USERNAME} + password: ${SPRING_DATASOURCE_PASSWORD} + + jpa: + hibernate: + ddl-auto: ${SPRING_JPA_HIBERNATE_DDL_AUTO} + + h2: + console: + enabled: ${SPRING_H2_CONSOLE_ENABLED} + path: ${SPRING_H2_CONSOLE_PATH} + + batch: + initialize-schema: ${SPRING_BATCH_INITIALIZE_SCHEMA} + job: + enabled: ${SPRING_BATCH_JOB_ENABLED} + batch-size: ${SPRING_BATCH_BATCH_SIZE} From cddcce6cbab4a90b1c58efcd58eca1894454aecb Mon Sep 17 00:00:00 2001 From: igorcampos-dev Date: Mon, 17 Nov 2025 21:35:08 -0300 Subject: [PATCH 14/16] feat(spring-batch-examples): update CI configuration to reflect new module structure for database and file examples --- .github/workflows/spring-batch-db-example.yml | 10 +++++----- .github/workflows/spring-batch-file-example.yml | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/spring-batch-db-example.yml b/.github/workflows/spring-batch-db-example.yml index b2a702d..8348063 100644 --- a/.github/workflows/spring-batch-db-example.yml +++ b/.github/workflows/spring-batch-db-example.yml @@ -1,10 +1,10 @@ -name: spring-batch-db-example CI Build +name: spring-batch-db-examples CI Build on: pull_request: branches: [master] paths: - - "spring-batch-db-example/**" + - "spring-batch-db-examples/**" types: - opened - synchronize @@ -17,7 +17,7 @@ jobs: runs-on: ubuntu-latest defaults: run: - working-directory: spring-batch-db-example + working-directory: spring-batch-db-examples strategy: matrix: distribution: [ 'temurin' ] @@ -49,13 +49,13 @@ jobs: id: services run: | echo "services<> $GITHUB_OUTPUT - docker compose -f ./spring-batch-db-example/compose.yaml config --services >> $GITHUB_OUTPUT + docker compose -f ./spring-batch-db-examples/compose.yaml config --services >> $GITHUB_OUTPUT echo "EOF" >> $GITHUB_OUTPUT - name: Start containers with Compose Action uses: hoverkraft-tech/compose-action@v2.4.0 with: - compose-file: './spring-batch-db-example/compose.yaml' + compose-file: './spring-batch-db-examples/compose.yaml' services: ${{ steps.services.outputs.services }} up-flags: '--build' down-flags: '--volumes' diff --git a/.github/workflows/spring-batch-file-example.yml b/.github/workflows/spring-batch-file-example.yml index 68ee205..152bc53 100644 --- a/.github/workflows/spring-batch-file-example.yml +++ b/.github/workflows/spring-batch-file-example.yml @@ -1,10 +1,10 @@ -name: spring-batch-file-example CI Build +name: spring-batch-file-examples CI Build on: pull_request: branches: [master] paths: - - "spring-batch-file-example/**" + - "spring-batch-file-examples/**" types: - opened - synchronize @@ -17,7 +17,7 @@ jobs: runs-on: ubuntu-latest defaults: run: - working-directory: spring-batch-file-example + working-directory: spring-batch-file-examples strategy: matrix: distribution: [ 'temurin' ] @@ -49,13 +49,13 @@ jobs: id: services run: | echo "services<> $GITHUB_OUTPUT - docker compose -f ./spring-batch-file-example/compose.yaml config --services >> $GITHUB_OUTPUT + docker compose -f ./spring-batch-file-examples/compose.yaml config --services >> $GITHUB_OUTPUT echo "EOF" >> $GITHUB_OUTPUT - name: Start containers with Compose Action uses: hoverkraft-tech/compose-action@v2.4.0 with: - compose-file: './spring-batch-file-example/compose.yaml' + compose-file: './spring-batch-file-examples/compose.yaml' services: ${{ steps.services.outputs.services }} up-flags: '--build' down-flags: '--volumes' From 8fb53941041b45464250620a335c23a62ab64556 Mon Sep 17 00:00:00 2001 From: igorcampos-dev Date: Mon, 17 Nov 2025 21:49:07 -0300 Subject: [PATCH 15/16] feat(spring-batch-examples): add chunk size configuration to application properties --- spring-batch-db-examples/compose.yaml | 1 + spring-batch-db-examples/src/main/resources/application-prd.yml | 1 + spring-batch-db-examples/src/main/resources/application.yml | 1 + spring-batch-file-examples/compose.yaml | 1 + .../src/main/resources/application-prd.yml | 1 + spring-batch-file-examples/src/main/resources/application.yml | 1 + .../src/test/resources/application-test.yml | 1 + 7 files changed, 7 insertions(+) diff --git a/spring-batch-db-examples/compose.yaml b/spring-batch-db-examples/compose.yaml index 959a73b..3dc65db 100644 --- a/spring-batch-db-examples/compose.yaml +++ b/spring-batch-db-examples/compose.yaml @@ -17,5 +17,6 @@ services: SPRING_H2_CONSOLE_PATH: "/h2-console" SPRING_BATCH_INITIALIZE_SCHEMA: "always" SPRING_BATCH_JOB_ENABLED: "false" + SPRING_BATCH_CHUNK_SIZE: "1000" SPRING_BATCH_BATCH_SIZE: "1000" SPRING_PROFILES_ACTIVE: "prd" \ No newline at end of file diff --git a/spring-batch-db-examples/src/main/resources/application-prd.yml b/spring-batch-db-examples/src/main/resources/application-prd.yml index 2a676ca..ace581b 100644 --- a/spring-batch-db-examples/src/main/resources/application-prd.yml +++ b/spring-batch-db-examples/src/main/resources/application-prd.yml @@ -22,4 +22,5 @@ spring: initialize-schema: ${SPRING_BATCH_INITIALIZE_SCHEMA} job: enabled: ${SPRING_BATCH_JOB_ENABLED} + chunk-size: ${SPRING_BATCH_CHUNK_SIZE} batch-size: ${SPRING_BATCH_BATCH_SIZE} diff --git a/spring-batch-db-examples/src/main/resources/application.yml b/spring-batch-db-examples/src/main/resources/application.yml index d5dc04b..e5e1ec7 100644 --- a/spring-batch-db-examples/src/main/resources/application.yml +++ b/spring-batch-db-examples/src/main/resources/application.yml @@ -22,4 +22,5 @@ spring: initialize-schema: always job: enabled: false + chunk-size: 1000 batch-size: 1000 diff --git a/spring-batch-file-examples/compose.yaml b/spring-batch-file-examples/compose.yaml index 959a73b..7910379 100644 --- a/spring-batch-file-examples/compose.yaml +++ b/spring-batch-file-examples/compose.yaml @@ -18,4 +18,5 @@ services: SPRING_BATCH_INITIALIZE_SCHEMA: "always" SPRING_BATCH_JOB_ENABLED: "false" SPRING_BATCH_BATCH_SIZE: "1000" + SPRING_BATCH_CHUNK_SIZE: "1000" SPRING_PROFILES_ACTIVE: "prd" \ No newline at end of file diff --git a/spring-batch-file-examples/src/main/resources/application-prd.yml b/spring-batch-file-examples/src/main/resources/application-prd.yml index 2a676ca..93cbcb6 100644 --- a/spring-batch-file-examples/src/main/resources/application-prd.yml +++ b/spring-batch-file-examples/src/main/resources/application-prd.yml @@ -22,4 +22,5 @@ spring: initialize-schema: ${SPRING_BATCH_INITIALIZE_SCHEMA} job: enabled: ${SPRING_BATCH_JOB_ENABLED} + SPRING_BATCH_CHUNK_SIZE: ${SPRING_BATCH_CHUNK_SIZE} batch-size: ${SPRING_BATCH_BATCH_SIZE} diff --git a/spring-batch-file-examples/src/main/resources/application.yml b/spring-batch-file-examples/src/main/resources/application.yml index a41cd55..e270c61 100644 --- a/spring-batch-file-examples/src/main/resources/application.yml +++ b/spring-batch-file-examples/src/main/resources/application.yml @@ -23,3 +23,4 @@ spring: job: enabled: false chunk-size: 1000 + batch-size: 1000 diff --git a/spring-batch-file-examples/src/test/resources/application-test.yml b/spring-batch-file-examples/src/test/resources/application-test.yml index 8c68bc3..5a5a88f 100644 --- a/spring-batch-file-examples/src/test/resources/application-test.yml +++ b/spring-batch-file-examples/src/test/resources/application-test.yml @@ -23,3 +23,4 @@ spring: job: enabled: false chunk-size: 5 + batch-size: 5 From a3b55e42d62766fd565fd938acc621fcf7ea9e29 Mon Sep 17 00:00:00 2001 From: igorcampos-dev Date: Mon, 17 Nov 2025 21:57:49 -0300 Subject: [PATCH 16/16] fix(spring-batch-examples): correct chunk size property name and update test expectations for invalid job ID --- .../java/com/io/example/controller/TestControllerTest.java | 4 ++-- .../src/main/resources/application-prd.yml | 2 +- .../java/com/io/example/controller/TestControllerTest.java | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/spring-batch-db-examples/src/test/java/com/io/example/controller/TestControllerTest.java b/spring-batch-db-examples/src/test/java/com/io/example/controller/TestControllerTest.java index 845f107..4fd90c8 100644 --- a/spring-batch-db-examples/src/test/java/com/io/example/controller/TestControllerTest.java +++ b/spring-batch-db-examples/src/test/java/com/io/example/controller/TestControllerTest.java @@ -57,8 +57,8 @@ void shouldReturnJobStatusForAllBatchStatuses(BatchStatus status) throws Excepti } @Test - @DisplayName("GET /job/{jobId}/status → should return 404 when job ID is invalid") - void shouldReturn404WhenJobIdIsInvalid() throws Exception { + @DisplayName("GET /job/{jobId}/status → should return 400 when job ID is invalid") + void shouldReturn400WhenJobIdIsInvalid() throws Exception { when(DBBatchService.getJobStatus(jobId)) .thenThrow(new BusinessException("JobExecution not found for this id: " + jobId)); diff --git a/spring-batch-file-examples/src/main/resources/application-prd.yml b/spring-batch-file-examples/src/main/resources/application-prd.yml index 93cbcb6..ace581b 100644 --- a/spring-batch-file-examples/src/main/resources/application-prd.yml +++ b/spring-batch-file-examples/src/main/resources/application-prd.yml @@ -22,5 +22,5 @@ spring: initialize-schema: ${SPRING_BATCH_INITIALIZE_SCHEMA} job: enabled: ${SPRING_BATCH_JOB_ENABLED} - SPRING_BATCH_CHUNK_SIZE: ${SPRING_BATCH_CHUNK_SIZE} + chunk-size: ${SPRING_BATCH_CHUNK_SIZE} batch-size: ${SPRING_BATCH_BATCH_SIZE} diff --git a/spring-batch-file-examples/src/test/java/com/io/example/controller/TestControllerTest.java b/spring-batch-file-examples/src/test/java/com/io/example/controller/TestControllerTest.java index bbf6902..5e37e47 100644 --- a/spring-batch-file-examples/src/test/java/com/io/example/controller/TestControllerTest.java +++ b/spring-batch-file-examples/src/test/java/com/io/example/controller/TestControllerTest.java @@ -60,8 +60,8 @@ void shouldReturnJobStatusForAllBatchStatuses(BatchStatus status) throws Excepti } @Test - @DisplayName("GET /job/{jobId}/status → should return 404 when job ID is invalid") - void shouldReturn404WhenJobIdIsInvalid() throws Exception { + @DisplayName("GET /job/{jobId}/status → should return 400 when job ID is invalid") + void shouldReturn400WhenJobIdIsInvalid() throws Exception { when(fileBatchService.getJobStatus(jobId)) .thenThrow(new BusinessException("JobExecution not found for this id: " + jobId));