강의

멘토링

커뮤니티

인프런 커뮤니티 질문&답변

스프링님의 프로필 이미지
스프링

작성한 질문수

죽음의 Spring Batch: 새벽 3시의 처절한 공포는 이제 끝이다.

1장. 작전3: Spring Batch Listener와 활용 (리스너... 실행과 죽음의 모든 순간을 지켜보는 자 ☠️)

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 강의 기다리고 있겠다.
고맙다!!

KILL-9님의 프로필 이미지
KILL-9
지식공유자

굿굿이다 형 💀💀 
아 그러고보니 @EnableMongo~~ 빼먹었군… 

1

KILL-9님의 프로필 이미지
KILL-9
지식공유자

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

위 에러를 반환한다...
실패해서 미안하다,,
왜 그런지 진짜 모르겠다.!ㅜ

KILL-9님의 프로필 이미지
KILL-9
지식공유자

아니?? 빌드 그래들과 어플리케이션.yml 전달 부탁한다

내일
아침중 해결해보겠다 💀💀💀
KILL-9님의 프로필 이미지
KILL-9
지식공유자

부트를 사용중인지 DefaultBatchConfiguration을 상속하고있는지 등등 안내바란다 💀💀

1

스프링님의 프로필 이미지
스프링
질문자

헤이 봇, 나는 이미 너가 알려준 방법을 gpt에게 전달 받아봤다. 너가 말한 메서드는 이미 지원 종료다.
나는 그저 기다린다. kill9,,,,

0

KILL-9님의 프로필 이미지
KILL-9
지식공유자

너의 절망적 애원에 하던일을
중지하고 등장했다

쥐피티 따위는 느리다 배치6을 알리가 없지
분석해보겠다 💀

0

안녕하세요, 인프런 AI 인턴입니다. 스프링님.

Spring Batch 6에서는 JobOperator를 사용하여 Job을 관리하게 되었군요. 하지만 UnexpectedJobExecutionException이 발생하는 문제로 고생 중이시네요. 해당 오류는 주로 Spring Batch의 일생 주기를 제대로 이해하지 못하거나 잘못된 파라미터 사용으로 인해 발생하며, 이는 Spring Batch 6의 새로운 동작 방식에 적응 중 발생할 수 있는 흔한 상황입니다.

문제 분석

  1. JobInstanceAlreadyCompleteException: 이 예외는 특정 Job 파라미터로 이미 완료된 인스턴스가 존재할 때 발생합니다. 이는 특히 스케줄러가 매번 같은 식별 파라미터로 잡을 실행할 때 발생할 수 있습니다.

  2. 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를 생성하도록 유도합니다.

저는 질문자님의 문제 해결을 돕기 위해 유사한 상황을 다룬 답변을 추천드리고 있습니다. 베타 기능으로 인해 답변이 만족스럽지 않을 수 있다는 점 양해 부탁드립니다. 🙏

추가적으로 궁금한 점이 있으시면, 이어서 질문해 주세요. 곧 Kill9형께서 직접적인 도움을 드릴 것입니다.

스프링님의 프로필 이미지
스프링

작성한 질문수

질문하기