From 2534541a881389b4fd692d512e82310a1d9fc222 Mon Sep 17 00:00:00 2001 From: z3rotig4r Date: Thu, 30 Jul 2026 08:32:13 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20=EB=B0=B0=EC=B9=98=20-next=20=EC=98=B5?= =?UTF-8?q?=EC=85=98=EC=97=90=EC=84=9C=20=EB=B3=91=ED=95=A9=ED=95=9C=20Job?= =?UTF-8?q?Parameters=EA=B0=80=20=EB=B2=84=EB=A0=A4=EC=A7=80=EB=8A=94=20?= =?UTF-8?q?=EB=AC=B8=EC=A0=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit EgovCommandLineRunner.start()의 -next 처리는 JobParametersIncrementer가 만든 파라미터와 명령행 파라미터를 map으로 병합한 뒤, 그 map을 쓰지 않고 new JobParameters()로 빈 파라미터를 만들어 JobLauncher에 넘겼다. 그 결과 -next로 실행하면 잡이 파라미터 0개로 기동해 증분 파라미터와 명령행 파라미터가 모두 사라진다. 증분 파라미터가 유실되므로 JobInstanceAlreadyCompleteException이 발생하거나, 명령행으로 지정한 기준일자 등이 빠진 채 잘못된 데이터를 처리할 수 있다. 병합한 map을 new JobParameters(map)으로 전달하도록 고쳤다. 병합 순서는 기존과 같아 이름이 같으면 명령행 파라미터가 증분 파라미터를 덮어쓴다. map의 raw type도 함께 제거했는데, 이는 생성자 시그니처 JobParameters(Map>)와의 정합에 필요하다. raw Map를 그대로 두면 Map를 Map>로 변환할 수 없어 컴파일에 실패한다. 검증: JobLauncher 스텁으로 실제 전달된 JobParameters를 캡처하는 테스트를 추가했다. 수정 전에는 파라미터가 비어 있어 실패하고, 수정 후에는 증분 파라미터(run.id)와 명령행 파라미터(targetDate)가 함께 전달되며 이름이 겹칠 때 명령행 값이 우선하고 각 파라미터의 타입과 identifying 여부도 보존된다. --- .../launch/support/EgovCommandLineRunner.java | 4 +- ...ommandLineRunnerNextJobParametersTest.java | 125 ++++++++++++++++++ .../core/launch/support/next-job-context.xml | 8 ++ 3 files changed, 135 insertions(+), 2 deletions(-) create mode 100644 Batch/org.egovframe.rte.bat.core/src/test/java/org/egovframe/rte/bat/core/launch/support/EgovCommandLineRunnerNextJobParametersTest.java create mode 100644 Batch/org.egovframe.rte.bat.core/src/test/resources/org/egovframe/rte/bat/core/launch/support/next-job-context.xml diff --git a/Batch/org.egovframe.rte.bat.core/src/main/java/org/egovframe/rte/bat/core/launch/support/EgovCommandLineRunner.java b/Batch/org.egovframe.rte.bat.core/src/main/java/org/egovframe/rte/bat/core/launch/support/EgovCommandLineRunner.java index 0b6665ad..1c757daf 100755 --- a/Batch/org.egovframe.rte.bat.core/src/main/java/org/egovframe/rte/bat/core/launch/support/EgovCommandLineRunner.java +++ b/Batch/org.egovframe.rte.bat.core/src/main/java/org/egovframe/rte/bat/core/launch/support/EgovCommandLineRunner.java @@ -272,9 +272,9 @@ public int start(String jobPath, String jobIdentifier, String[] parameters, Set< // 다음 Batch Job을 실행하기 위한 Job Parameters를 생성한다. if (opts.contains("-next")) { JobParameters nextParameters = getNextJobParameters(job); - Map map = new HashMap<>(nextParameters.getParameters()); + Map> map = new HashMap<>(nextParameters.getParameters()); map.putAll(jobParameters.getParameters()); - jobParameters = new JobParameters(); + jobParameters = new JobParameters(map); } // Batch Job을 실행한다. diff --git a/Batch/org.egovframe.rte.bat.core/src/test/java/org/egovframe/rte/bat/core/launch/support/EgovCommandLineRunnerNextJobParametersTest.java b/Batch/org.egovframe.rte.bat.core/src/test/java/org/egovframe/rte/bat/core/launch/support/EgovCommandLineRunnerNextJobParametersTest.java new file mode 100644 index 00000000..0d28000c --- /dev/null +++ b/Batch/org.egovframe.rte.bat.core/src/test/java/org/egovframe/rte/bat/core/launch/support/EgovCommandLineRunnerNextJobParametersTest.java @@ -0,0 +1,125 @@ +package org.egovframe.rte.bat.core.launch.support; + +import org.junit.jupiter.api.Test; +import org.springframework.batch.core.ExitStatus; +import org.springframework.batch.core.Job; +import org.springframework.batch.core.JobExecution; +import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.JobParametersIncrementer; +import org.springframework.batch.core.JobParametersValidator; +import org.springframework.batch.core.explore.JobExplorer; +import org.springframework.batch.core.job.DefaultJobParametersValidator; +import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.launch.support.RunIdIncrementer; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Proxy; +import java.util.List; +import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * {@link EgovCommandLineRunner}의 -next 옵션이 증가 파라미터와 명령행 파라미터를 함께 전달하는지 검증한다. + */ +class EgovCommandLineRunnerNextJobParametersTest { + + private static final String JOB_NAME = "nextSampleJob"; + + @Test + void nextOption_keepsIncrementedAndCommandLineParameters() throws Exception { + JobParameters actual = runNext("targetDate=20260101,java.lang.String,false"); + + assertFalse(actual.isEmpty(), "-next 실행 시 JobParameters가 비어 있으면 안 된다"); + assertEquals(1L, actual.getLong("run.id"), "증가 파라미터가 유지되어야 한다"); + assertEquals("20260101", actual.getString("targetDate"), "명령행 파라미터가 유지되어야 한다"); + assertEquals(String.class, actual.getParameter("targetDate").getType(), "명령행 파라미터의 타입이 보존되어야 한다"); + assertFalse(actual.getParameter("targetDate").isIdentifying(), "명령행에서 지정한 identifying 여부가 보존되어야 한다"); + } + + @Test + void nextOption_commandLineParameterOverridesIncrementedParameter() throws Exception { + JobParameters actual = runNext("run.id=99,java.lang.Long,true"); + + assertEquals(99L, actual.getLong("run.id"), "이름이 같으면 명령행 파라미터가 증가 파라미터를 덮어써야 한다"); + assertEquals(Long.class, actual.getParameter("run.id").getType(), "덮어쓴 파라미터의 타입이 보존되어야 한다"); + assertTrue(actual.getParameter("run.id").isIdentifying(), "덮어쓴 파라미터의 identifying 여부가 보존되어야 한다"); + } + + /** -next 옵션으로 Job을 실행하고 JobLauncher에 실제로 전달된 JobParameters를 반환한다. */ + private static JobParameters runNext(String... parameters) throws Exception { + AtomicReference captured = new AtomicReference<>(); + EgovCommandLineRunner runner = new EgovCommandLineRunner(); + inject(runner, "launcher", stubJobLauncher(captured)); + inject(runner, "jobExplorer", stubJobExplorer()); + + int exit = runner.start("org/egovframe/rte/bat/core/launch/support/next-job-context.xml", JOB_NAME, parameters, + Set.of("-next")); + + JobParameters actual = captured.get(); + assertNotNull(actual, "JobParameters가 전달되어야 한다: " + EgovCommandLineRunner.getErrorMessage()); + assertEquals(0, exit, "완료 ExitStatus는 0으로 매핑되어야 한다"); + return actual; + } + + private static void inject(EgovCommandLineRunner runner, String fieldName, Object value) throws Exception { + Field field = EgovCommandLineRunner.class.getDeclaredField(fieldName); + field.setAccessible(true); + field.set(runner, value); + } + + private static JobLauncher stubJobLauncher(AtomicReference captured) { + return (job, jobParameters) -> { + captured.set(jobParameters); + JobExecution jobExecution = new JobExecution(1L); + jobExecution.setExitStatus(ExitStatus.COMPLETED); + return jobExecution; + }; + } + + private static JobExplorer stubJobExplorer() { + InvocationHandler handler = (proxy, method, args) -> { + if ("getJobInstances".equals(method.getName())) { + return List.of(); + } + return null; + }; + return (JobExplorer) Proxy.newProxyInstance(JobExplorer.class.getClassLoader(), + new Class[] { JobExplorer.class }, handler); + } + + public static class NextSampleJob implements Job { + + private final JobParametersIncrementer incrementer = new RunIdIncrementer(); + private final JobParametersValidator validator = new DefaultJobParametersValidator(); + + @Override + public String getName() { + return JOB_NAME; + } + + @Override + public boolean isRestartable() { + return true; + } + + @Override + public JobParametersIncrementer getJobParametersIncrementer() { + return incrementer; + } + + @Override + public JobParametersValidator getJobParametersValidator() { + return validator; + } + + @Override + public void execute(JobExecution execution) { + } + } +} diff --git a/Batch/org.egovframe.rte.bat.core/src/test/resources/org/egovframe/rte/bat/core/launch/support/next-job-context.xml b/Batch/org.egovframe.rte.bat.core/src/test/resources/org/egovframe/rte/bat/core/launch/support/next-job-context.xml new file mode 100644 index 00000000..f219c8ba --- /dev/null +++ b/Batch/org.egovframe.rte.bat.core/src/test/resources/org/egovframe/rte/bat/core/launch/support/next-job-context.xml @@ -0,0 +1,8 @@ + + + + + +