인프런 커뮤니티 질문&답변
Batch6: jobOperator.startNextInstance() throws UnexpectedJobExecutionException
작성
·
30
·
수정됨
1
KILL-9형 도와줘,,!!!
spring boot 4.0.0, spring batch6, java24 사용중이야
아래 코드를 스케줄러를 통해 "deleteSuspendedJob"을 1분마다 동작하게 하고 싶었어.
그리고 실제로 동작하긴 해. 딱 1번만....
@Configuration
class DeleteSuspendedScheduler(
private val jdbcTemplate: JdbcTemplate,
private val jobOperator: JobOperator,
private val jobRepository: JobRepository,
private val transactionManager: PlatformTransactionManager,
) {
@Scheduled(cron = "0 */1 * * * *") //1분마다 실행되길 기대함
fun runDeleteSuspendedJob() {
jobOperator.startNextInstance(deleteSuspendedJob())
}
@Bean
fun deleteSuspendedJob(): Job =
JobBuilder("deleteSuspendedJob", jobRepository)
.incrementer(RunIdIncrementer())
.start( deleteSuspendedStep())
.build()
@Bean
fun deleteSuspendedStep(): Step =
StepBuilder("deleteSuspendedStep", jobRepository)
.tasklet(DeleteSuspendedTasklet(jdbcTemplate), transactionManager)
.build()
}아래는 에러 로그야.
2025-12-11T19:37:31.332+09:00 INFO 39640 --- [ main] com.clip.BatchApplicationKt : Started BatchApplicationKt in 6.378 seconds (process running for 6.894)
2025-12-11T19:38:00.017+09:00 INFO 39640 --- [ scheduling-1] o.s.b.c.l.s.TaskExecutorJobOperator : Launching next instance of job: [deleteSuspendedJob] with parameters: [{JobParameter{name='run.id', value=1, type=class java.lang.Long, identifying=true}}]
2025-12-11T19:38:00.019+09:00 INFO 39640 --- [ scheduling-1] o.s.b.c.l.s.TaskExecutorJobLauncher : Job: [SimpleJob: [name=deleteSuspendedJob]] launched with the following parameters: [{JobParameter{name='run.id', value=1, type=class java.lang.Long, identifying=true}}]
2025-12-11T19:38:00.052+09:00 INFO 39640 --- [ scheduling-1] o.s.batch.core.job.SimpleStepHandler : Executing step: [deleteSuspendedStep]
2025-12-11T19:38:00.065+09:00 INFO 39640 --- [ scheduling-1] c.c.b.b.t.DeleteExpiredBlacklistTasklet : 0개의 기간 만료된 탈퇴 이력(재가입 방지용) 레코드가 삭제되었습니다.
2025-12-11T19:38:00.067+09:00 INFO 39640 --- [ scheduling-1] o.s.batch.core.step.AbstractStep : Step: [deleteSuspendedStep] executed in 14ms
2025-12-11T19:38:00.067+09:00 INFO 39640 --- [ scheduling-1] o.s.b.c.l.s.TaskExecutorJobLauncher : Job: [SimpleJob: [name=deleteSuspendedJob]] completed with the following parameters: [{JobParameter{name='run.id', value=1, type=class java.lang.Long, identifying=true}}] and the following status: [COMPLETED] in 15ms
2025-12-11T19:39:00.007+09:00 INFO 39640 --- [ scheduling-1] o.s.b.c.l.s.TaskExecutorJobOperator : Launching next instance of job: [deleteSuspendedJob] with parameters: [{JobParameter{name='run.id', value=2, type=class java.lang.Long, identifying=true}}]
2025-12-11T19:39:00.009+09:00 ERROR 39640 --- [ scheduling-1] o.s.s.s.TaskUtils$LoggingErrorHandler : Unexpected error occurred in scheduled task
org.springframework.batch.core.job.UnexpectedJobExecutionException: Illegal state (only happens on a race condition): job instance already complete with name=deleteSuspendedJob and parameters={JobParameter{name='run.id', value=2, type=class java.lang.Long, identifying=true}}
at org.springframework.batch.core.launch.support.SimpleJobOperator.startNextInstance(SimpleJobOperator.java:314) ~[spring-batch-core-6.0.0.jar:6.0.0]
at org.springframework.batch.core.launch.support.TaskExecutorJobOperator.startNextInstance(TaskExecutorJobOperator.java:133) ~[spring-batch-core-6.0.0.jar:6.0.0]
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:565) ~[na:na]
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359) ~[spring-aop-7.0.1.jar:7.0.1]
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190) ~[spring-aop-7.0.1.jar:7.0.1]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:158) ~[spring-aop-7.0.1.jar:7.0.1]
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:370) ~[spring-tx-7.0.1.jar:7.0.1]
Caused by: org.springframework.batch.core.launch.JobInstanceAlreadyCompleteException: A job instance already exists and is complete for identifying parameters={JobParameter{name='run.id', value=1, type=class java.lang.Long, identifying=true}}. If you want to run this job again, change the parameters.
at org.springframework.batch.core.launch.support.TaskExecutorJobLauncher.createJobExecution(TaskExecutorJobLauncher.java:149) ~[spring-batch-core-6.0.0.jar:6.0.0]
at org.springframework.batch.core.launch.support.TaskExecutorJobLauncher.run(TaskExecutorJobLauncher.java:108) ~[spring-batch-core-6.0.0.jar:6.0.0]
at org.springframework.batch.core.launch.support.SimpleJobOperator.startNextInstance(SimpleJobOperator.java:294) ~[spring-batch-core-6.0.0.jar:6.0.0]원래 Kill9형 강의 보고 잡 런쳐로 정상동작 하도록 만들었던 걸, 이번에 배치6로 올리면서 JobLauncher가 JobOperator로 옮겨졌다는 문서를 보고 바꾼뒤로 퇴근을 못하고 있어,,
역시 공식 문서보단 kill9 형 문서를 보고 했어야 했던걸까??
오퍼레이터와 스케줄러를 통해 잡을 특정 주기마다 동작하는 방법(위 내 코드)이 뭐가 잘못된건지 알려주면 고맙겠어!!
형 제발 도와줘!!!
cf.https://github.com/spring-projects/spring-batch/issues/5115
답변 5
1
@EnableJdbcJobRepository를 메인 어플리케이션 클래스에 추가하고 모든 것이 해결되었다..
인프런의 대표 배치 마스터 kill9형도 예상하지 못할 정도의 기초적인 실수를 해서 형의 시간을 빼앗은 점,, 깊이 사죄한다.
덕분에 공식 마이그레이션 문서를 다시 읽다가 발견했다.
곧 세상으로 나올 배치6 강의 기다리고 있겠다.
고맙다!!
1
1) Job에 달린 @Bean을 제거하라. 그리고 jobRepository를 주입받은것을 사용하는 대신 new ResourcelessJobRepository를 사용하라
또는
2) build.gradle을 수정해 spring-boot-starter-batch 대신 spring-boot-starter-batch-jdbc를 사용하도록 변경하라
1번은 휴리스틱
2번은 근본적 개선이 될 것이다.
사실 질문만보고 대답한거라 틀릴지도모른다. (아마 맞을 것이다.)
틀리다면 댓글달도록
----
배치6 강의 작성이 거의 완성되어간다. 지금도 카페에서 미친듯이 완성해가고있다 조금만 더 기다려달라 업데이트 될 내용을 학습하면 왜 오류가 발생했는지 알게될것이다.
LGTM 💀
kill9형의 1,2 방식 모두 실패했다,,,,
모두 동일한
A job instance already exists and is complete for identifying parameters={JobParameter{name='run.id', value=1, type=class java.lang.Long, identifying=true}}. If you want to run this job again, change the parameters위 에러를 반환한다...
실패해서 미안하다,,
왜 그런지 진짜 모르겠다.!ㅜ
1
0
0
안녕하세요, 인프런 AI 인턴입니다. 스프링님.
Spring Batch 6에서는 JobOperator를 사용하여 Job을 관리하게 되었군요. 하지만 UnexpectedJobExecutionException이 발생하는 문제로 고생 중이시네요. 해당 오류는 주로 Spring Batch의 일생 주기를 제대로 이해하지 못하거나 잘못된 파라미터 사용으로 인해 발생하며, 이는 Spring Batch 6의 새로운 동작 방식에 적응 중 발생할 수 있는 흔한 상황입니다.
문제 분석
JobInstanceAlreadyCompleteException: 이 예외는 특정 Job 파라미터로 이미 완료된 인스턴스가 존재할 때 발생합니다. 이는 특히 스케줄러가 매번 같은 식별 파라미터로 잡을 실행할 때 발생할 수 있습니다.
Job 파라미터 관리:
RunIdIncrementer를 사용하시고,jobOperator.startNextInstance(...)를 호출하고 계신데, 이는 새로운 인스턴스를 생성하는데 문제가 있습니다.RunIdIncrementer는 유니크한run.id값을 자동으로 부여하는데, 실제로는 새로운 잡 파라미터가 생성되지 않아 동일한 인스턴스를 재실행하려는 시도로 처리됩니다.
해결 방법
- Job 파라미터를 통해 적절히 관리하여 각 실행 시마다 새로운 인스턴스를 만들어 주어야 합니다. 예를 들어, 다음과 같은 방법으로 시간을 포함한 고유 식별자를 사용하는 파라미터를 추가해 봅시다.
@Scheduled(cron = "0 */1 * * * *")
fun runDeleteSuspendedJob() {
val params = mapOf(
"currentTime" to System.currentTimeMillis().toString()
)
jobOperator.startNextInstance(deleteSuspendedJob(), params)
}
위 코드에서 currentTime은 현재 시간을 사용한 파라미터로, 매번 다른 값을 사용합니다. 이는 새로운 JobInstance를 생성하도록 유도합니다.
- 참조 링크: 유사한 문제를 다룬 커뮤니티의 답변이 도움이 될 수 있습니다. Spring Batch 잡 파라미터 문제 이해하기
저는 질문자님의 문제 해결을 돕기 위해 유사한 상황을 다룬 답변을 추천드리고 있습니다. 베타 기능으로 인해 답변이 만족스럽지 않을 수 있다는 점 양해 부탁드립니다. 🙏
추가적으로 궁금한 점이 있으시면, 이어서 질문해 주세요. 곧 Kill9형께서 직접적인 도움을 드릴 것입니다.




