본문 바로가기

Playlist/Spring Batch

Spring Batch, 배치

Spring 배울때 한번 배웠고 프로젝트에서도 사용했지만, 복습차원에서 한번 더 쓰면서 공부한다

 


Spring Batch

대량의 데이터를 처리하고 관리하기 위한 엔터프라이즈급 애플리케이션을 구축하기 위한 경량 배치 프레임워크

@Scheduled 애너테이션을 활용하여 스케쥴러를 구성하여 배치를 동작시키는 방법을 사용해본 적이 있다

 

Batch

특정 주기마다 데이터를 처리하는 작업. 정산이나 통계 혹은 로깅관리에 쓰이며 매주, 매월마다 한 번 실행되게끔 한다

 

배치작업은 Spring Framework를 이용하지 않고 순수한 Java언어로도 만들 수 있다. 애초에 Spring 특성상 Spring을 벗겨내도 순수한 자바언어로 동작하게끔 할 수 있다. 그래도 프레임워크를 활용하면 훨씬 간단하게 배치 프로그램을 만들 수 있겠지?

 

Spring Batch는 어디에 사용되는가?

주로 데이터 마이그레이션, 주기적 데이터 처리에 많이 사용된다. 하나의 시스템에서 다른 시스템으로 데이터를 이동하거나 형식을 변경할 때 또는 매일, 매주 정해진 시간처럼 일정한 주기마다 실행되어야 하는 작업을 자동화할 때 사용함

 

배치에 사용되는 용어들에 대해

Job 배치 처리의 전체 실행 단위
실제 객체를 의미한다
Step Job 내의 독립된 작업 단위
Job은 최소한 1개를 Step을 가져야한다
Tasklet 단일 작업 또는 Chunk 기반 처리(읽기, 처리, 쓰기)를 수행함
JobLauncher Job을 실행하는 객체
ItemReader
ItemProcessor
ItemWritter
Chunk 기반 처리에서 데이터를 읽고, 가공하고, 쓰는 역할을 담당

 


기본적인 배치 사용하기


1. 의존성 추가

배치가 잘 돌아가나 확인만 해볼것이다.

 

의존성 추가 확인

 

build.gradle에 잘 추가되었는지 확인한다

 

* Lombok, Spring JPA도 추가해주자. 사실 JPA가 없으면 돌아가질 않는다.

# lombok
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'

#Spring jpa
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'

 

 

밑에서 h2를 사용할거니까 h2에 대한 의존성도 추가해주자

#h2
runtimeOnly 'com.h2database:h2'

 

 

추가적으로 configuration-processor을 annotationProcessor 형태로 등록할 수 있다고 한다.

이걸 추가해주면 Lombok처럼 Spring Batch에 도움이 되는 Annotation들을 지원해준다고 한다.

annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'

 


 

2. yml파일 설정

application.yml 파일에 배치 관련 설정을 해줄 수 있다고 한다. 좀 길게 적었는데..

여기에서 중요한 점은, 배치처리에 대한 연습이나 실전 테스트를 할땐 반드시 DB가 필요하다.

h2 기본 설정을 하던지, 자기 PC 내부 DB를 활용하던지(MySQL, PostgresSQL, 등 cmd로 접근 가능한 DB)

어느것이든 하나의 DB와 연결시켜주면 Batch처리에 대한 활용이 가능하다. 

application.yml

 

Postgre 세팅도 해두긴 했는데, 최대한 간단하게 테스트하기 위해 h2를 사용했다.

 

 

이후 main 메서드를 가진 Spring 클래스에 @EnableBatchProcessing 애너테이션을 명시해주면 Batch 사용 준비가 끝난다.

@EnableBatchProcessing

 

 


 

3. Batch 처리하기

나는 content/config/ContentConfig.java의 형태로 패키지와 클래스를 만들었다.

패키지 구조

 

 

그리고 이 ContentConfig 클래스를 Bean으로 등록해야한다. @Configuration 애너테이션을 붙여주자.

이렇게!

 

 

그리고 JobBuilderFactory와 StepBuilderFactory 타입의 필드를 만들어보려고 했는데, Spring Batch에서 지원을 종료했다고 한다. 정확하게 말하면 저 두 클래스가 '사라졌다'고 보는게 좋겠다.

 

3.2.5 버전의 Spring Boot 기준으로 사라졌다.

링크

https://multifrontgarden.tistory.com/310

https://github.com/spring-projects/spring-batch/issues/4188

 

그래서 JobBuilder를 사용해보는데 얘도 제거될 예정이라고 한다..

버전 5.1 이후 사용되지 않으며 제거될 예정이라고 한다. 와!

 

그래서 조사해보니, Spring Batch 5.2.4 API와 관련된 자료가 있었다.

https://docs.spring.io/spring-batch/docs/current/api/org/springframework/batch/core/configuration/support/DefaultBatchConfiguration.html

 

batch 전반에 관한 패키지는 여기

https://docs.spring.io/spring-batch/docs/current/api/org/springframework/batch/package-summary.html

 

Configuration에 관한건 DefaultBatchConfiguration을 상속받아서 사용하면 되겠는데?

참고할만한 링크

https://alwayspr.tistory.com/49

https://cwangg897.tistory.com/214

 

@Configuration을 붙여서 클래스를 Bean으로 만들어줬으니 @EnableBatchProcessing을 붙여줘도 되는데 

Spring Batch 5.x 버전부터는 이 애너테이션이 필요없어졌다고 한다.

또한 @EnableBatchProcessing 애너테이션을 선언할 경우 autoConfiguration에 의해 자동으로 Bean들이 등록되지 못한다.

출처 : https://velog.io/@hoyo1744/SpringBoot-3.x-Spring-Batch-5.x

 

그래서 DefaultBatchConfiguration 상속만 해준다.

DefaultBatchConfiguration을 상속한 모습

 

 

이걸 상속해주지 않아도 배치처리는 할 수 있다고 하는데, 상속해주면 간편하게 오버라이드 할 수는 있더라.

 

작성한 Config 내용이다.

 

읽기전에 정리

Job 스프링배치 작업을 정의하는 클래스 
JobBuilder Job 객체를 생성하기 위한 Builder 클래스
Step 스프링 배치 작업 단계를 정의하는 클래스
StepBuilder Step 객체를 생성하기 위한 빌더 클래스
Tasklet 스프링 배치 작업에서 실행될 Tasklet을 정의하는 클래스

 

import lombok.extern.slf4j.Slf4j;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.support.DefaultBatchConfiguration;
import org.springframework.batch.core.job.builder.JobBuilder;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.step.builder.StepBuilder;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.PlatformTransactionManager;

@Slf4j
@Configuration
public class ContentConfig extends DefaultBatchConfiguration {
    @Bean
    public Job helloJob(JobRepository jobRepository, Step helloStep) {
        return new JobBuilder("helloJob", jobRepository)
                .start(helloStep)
                .build();
    }

    @Bean
    public Step helloStep(JobRepository jobRepository,
                          Tasklet helloTasklet,
                          PlatformTransactionManager platformTransactionManager) {
        return new StepBuilder("helloTasklet", jobRepository)
                .tasklet(helloTasklet, platformTransactionManager)
                .build();
    }

    @Bean
    public Tasklet helloTasklet() {
        return ((contribution, chunkContext) -> {
            log.info("helloTasklet");
            System.out.println("Hello World Spring Batch");
            return RepeatStatus.FINISHED;
        });
    }
}

 

 

이후에 실행해보면 Hello World가 콘솔에 출력된다고 하는데..

난 출력이 안됐다.. 왜지?

로그도 제대로 출력되지 않았다.. Batch에 대한건 잘 실행된것 같은데..

 

음.....

 

 


 

☆원인을 찾았음★

Spring Boot 3.x + Spring Batch 5에서 BatchAutoConfiguration은 아래처럼 생겨먹었다.

@AutoConfiguration
@ConditionalOnMissingBean(
    value = { DefaultBatchConfiguration.class },
    annotation = { EnableBatchProcessing.class }
)
public class BatchAutoConfiguration {

}

 

위 BatchAutoConfiguration에서 BatchDataSourceScriptDatabaseInitializer를 만들어서

schema-h2.sql을 실행한 다음 BATCH에 해당하는 테이블을 자동으로 생성해준다.

 

여기서가 중요한데,

DefaultBatchConfiguration 타입의 빈이 하나라도 있으면 BatchAutoConfiguration 전체가 비활성되어버린다고 한다.

 

위에서 내가 적은 ContentConfig를 다시 보자. 

import lombok.extern.slf4j.Slf4j;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.support.DefaultBatchConfiguration; // import
import org.springframework.batch.core.job.builder.JobBuilder;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.step.builder.StepBuilder;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.PlatformTransactionManager;

@Slf4j
@Configuration
public class ContentConfig extends DefaultBatchConfiguration { // DefaultBatchConfiguration 상속
	// ...
}

 

지금 내가 작성한 ContentConfig는 DefaultBatchConfiguration을 상속하고 있다.

 

그래서 스프링은 ContentConfig를 DefaultBatchConfiguration 타입의 Bean으로 인식하기 때문에

@ConditionalOnMissingBean(DefaultBatchConfiguration.class)의 조건이 깨진다.

 

따라서 BatchAutoConfiguration이 돌지 않고 그 안에 있는 BatchDataSourceScriptDatabaseInitializer도 만들어지지 않는다.

 

이러면 내가 application.yml에 작성한 spring.batch.jdbc.initialize-schema: always도 완전히 무시된다.

테이블을 자동으로 만들어주지 않는다는 소리임

그래서 내가 postgre를 통해 cmd로 DB를 만들어두건, H2를 쓰건 테이블이 자동으로 생성되지 않았던 것이다.

그래서 내가 컨트롤러로 요청을 하면 BATCH_JOB_INSTANCE를 찾다가 계속 뻗어버리는것임

 

덩달아 ContentConfig는 현재 DefaultBatchConfiguration을 상속하고 있기 때문에 안에 적힌 메서드는 완전히 무시된다.

 

해결법은 무엇인가??????

저 상속만 지워주면 된다..

ContentConfig

 

이 상태에서 application.yml에 batch.job.enabled: true로 맞춰주고 실행해보면

이걸 true, false로 바꿔줌으로 배치처리를 자동으로 하게 하느냐, 하지 않게 하느냐를 정할 수 있음

 

Hello World Spring Batch

 

 

내가 ContentConfig 내 helloTasklet에 작성한 출력문이 로그에 출력된다.

 

 


 

특정 메서드가 호출되었을때 배치를 시작하게끔 하기

우선 application.yml의 batch.job.enabled: false로 바꿔준다

바로 위에 적혀있으니 따로 첨부는 하지 않겠다.

 

그리고 요청이 들어왔을 때 배치처리를 하게끔 해야하니까 컨트롤러, 서비스를 각각 만들어주자

 

BatchController.java

BatchController.java

 

 

BatchService.java

핵심이 되는 BatchService

 

이 서비스를 통해 요청에 따른 Spring Batch Job을 실제로 실행시킬 수 있다.

 

이건 내가 실습을 통해 다시 사용했을때 로직의 흐름을 적어두기로 하겠다. 나는 페르마가 좋더라..

 

아무튼 두 클래스를 작성하고 다시 실행하면,

application.yml에 job.enabled: false로 바꿨기 때문에 배치가 자동으로 실행되지 않는다.

 

실행시키고 싶다면 Controller에 맵핑된 주소가 호출되어야한다.

나는 localhost:8080/batch를 주소에 입력했다.

 

 

호출하기 전 로그

눈비비고 찾아봐도 Batch 로그에 대한건 보이지 않는다.

 

 

그리고 localhost:8080/batch를 입력해서 호출하고..

호출

 

 

 

로그를 확인하면..

Hello World Spring Batch

 

잘 작동한다!

 

 


 

복습할때 편의를 위해 코드를 밑에 남긴다

 

build.gradle의 의존성

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-batch'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-web'

    runtimeOnly 'com.h2database:h2'

    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'

    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}

 

 

application.yml

server:
  port: 8080
spring:
  application:
    name: testbatch2
  threads:
    virtual:
      enabled: true
  batch:
    jdbc:
      initialize-schema: always
    job:
      enabled: false
  datasource:
    url: jdbc:h2:mem:testbatch2
    driver-class-name: org.h2.Driver
    username: sa
    password:
  jpa:
    defer-datasource-initialization: true
    hibernate:
      ddl-auto: create
    show-sql: true
    properties:
      hibernate:
        default_batch_fetch_size: 100
        format_sql: true
        highlight_sql: true
        use_sql_comments: true
  h2:
    console:
      enabled: true
      path: /h2-console
logging:
  level:
    com.ll.sbb20240111: DEBUG
    org.hibernate.SQL: DEBUG
    org.hibernate.orm.jdbc.bind: TRACE
    org.hibernate.orm.jdbc.extract: TRACE
    org.springframework.transaction.interceptor: TRACE

 

 

 

content/ContentConfig.java

import lombok.extern.slf4j.Slf4j;
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.batch.core.step.builder.StepBuilder;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.PlatformTransactionManager;

@Slf4j
@Configuration
public class ContentConfig {
    @Bean
    public Job helloJob(JobRepository jobRepository, Step helloStep) {
        return new JobBuilder("helloJob", jobRepository)
                .start(helloStep)
                .build();
    }

    @Bean
    public Step helloStep(JobRepository jobRepository,
                          Tasklet helloTasklet,
                          PlatformTransactionManager platformTransactionManager) {
        return new StepBuilder("helloTasklet", jobRepository)
                .tasklet(helloTasklet, platformTransactionManager)
                .build();
    }

    @Bean
    public Tasklet helloTasklet() {
        return ((contribution, chunkContext) -> {
            log.info("helloTasklet");
            System.out.println("Hello World Spring Batch");
            return RepeatStatus.FINISHED;
        });
    }
}

 

 

 

content/BatchController.java

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
@RequiredArgsConstructor
public class BatchController {
    private final BatchService batchService;

    @GetMapping("/batch")
    @ResponseBody
    public String simple() {
        batchService.runSimpleJob();
        return "runSimpleJob OK";
    }
}

 

 

 

content/BatchService.java

import lombok.RequiredArgsConstructor;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class BatchService {
    private final JobLauncher jobLauncher;
    private final Job simpleJob;

    public void runSimpleJob() {
        try {
            JobParameters jobParameters = new JobParametersBuilder().toJobParameters();
            jobLauncher.run(simpleJob, jobParameters);
        } catch(Exception e) {
            e.printStackTrace();
        }
    }
}

 

'Playlist > Spring Batch' 카테고리의 다른 글

BatchConfig  (0) 2025.12.17
SportContentTasklet  (0) 2025.12.17
TvSeriesTasklet  (0) 2025.12.17
MovieTasklet  (0) 2025.12.17
ContentScheduler  (0) 2025.12.17