728x90

AOP(Aspect Oriented Programming)

  • OOP를 보완하는 수단으로, 흩어진 Aspect를 모듈화할 수 있는 프로그래밍 기법

image

  • 동일한 기능이 흩어져 있으면 유지보수하는데 어려움이 존재
  • 각 클래스 내에 흩어진 관심사를 묶어서 모듈화
  • 애플리케이션 전체에 걸쳐 사용되는 기능을 재사용

 

AOP 주요 용어

  • Aspect : 관심사를 모듈화한 것
  • Target : 적용이 되는 대상
  • Advice : 해야할 일(실제 수행되는 코드)
  • Join point : 메서드 실행 시점(Advice를 실제로 실행하고자 하는 위치)
  • Pointcut : 대상 내에 어디에 적용이 되어야 하는지에 대한 정보(Join point를 선정하는 방법)
  • Weaving : Aspect가 target에 적용되는 전체적인 과정, PointCut으로 지정된 JoinPoint에 Advice가 적용되어 Target을 호출 시 AOP Proxy가 만들어지는 과정

image

AOP 구현체

 

AOP 적용 방법

  • 컴파일
  • 로드 타임
  • 런타임

 

Spring AOP가 사용하는 방법 → 런타임 (Dynamic Proxy 기법으로 구현)

  • A라는 Class 타입의 Bean을 생성할 때, A 타입의 Proxy Bean을 생성
  • AOP가 적용된 Target 메서드를 호출 할 때 실제 메서드가 호출되는 것이 아니라 Advice가 요청을 대신 랩핑(Wrraping) 클래스로써 받고 그 랩핑 클래스가 Target을 호출



Spring AOP : Proxy 기반 AOP

 

Spring AOP의 특징

  • 프록시 기반의 AOP 구현체
  • 스프링 빈에만 AOP를 적용할 수 있음
  • 모든 AOP 기능을 제공하는 것이 목적이 아니라 스프링 IoC와 연동

 

프록시 패턴

image

  • 프록시 패턴을 적용하는 이유 : 기존 코드 변경없이 접근 제어 또는 부가 기능 추가

 

만약 성능(시간)을 측정하는 기능을 추가해야 한다면?

@Service
public class EventServiceImpl implements EventService {

    @Override
    public void createEvent() {
        long begin = System.currentTimeMillis();

        try {
            Thread.sleep(1000);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("Created an event");

        System.out.println(System.currentTimeMillis() - begin);
    }

    @Override
    public void publishEvent() {
        long begin = System.currentTimeMillis();

        try {
            Thread.sleep(2000);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("Published an event");

        System.out.println(System.currentTimeMillis() - begin);
    }

    @Override
    public void deleteEvent() {
        System.out.println("Deleted an event");
    }
}
  • 성능(시간)을 측정하는 부가적인 기능을 추가할 경우 기존 코드를 수정해야 하는 상황이 발생
  • 프록시 패턴을 이용하여 기존 클래스를 두고 프록시 클래스를 작성하여 기능을 위임하고 부가적인 기능을 작성할 수 있음
@Primary
@Service
@RequiredArgsConstructor
public class ProxyEventServiceImpl implements EventService {

    private final EventServiceImpl eventServiceImpl;

    @Override
    public void createEvent() {
        long begin = System.currentTimeMillis();
        eventServiceImpl.createEvent();
        System.out.println(System.currentTimeMillis() - begin);
    }

    @Override
    public void publishEvent() {
        long begin = System.currentTimeMillis();
        eventServiceImpl.publishEvent();
        System.out.println(System.currentTimeMillis() - begin);
    }

    @Override
    public void deleteEvent() {
        eventServiceImpl.deleteEvent();
    }
}
  • 프록시 클래스를 활용하여 기능을 위임
  • 하지만 이 경우에 모든 클래스에 대해 프록시 클래스를 작성해야 하는 비용이 발생
  • 추가로 프록시 클래스 내에도 동일한 기능에 대한 중복 코드가 발생하고 다른 클래스에서 재사용이 어려움

 

Spring AOP 등장 배경

 

Proxy Bean 생성 과정

  1. 원본 클래스의 Bean이 등록
  2. AbstractAutoProxyCreator을 통해 원본 클래스를 감싸는 Proxy Bean을 생성
  3. Proxy Bean을 원본 클래스의 Bean 대신에 등록

 

@AOP

  • 애노테이션 기반의 스프링 AOP

 

의존성 추가

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-aop'
}

 

Aspect 정의 : @Aspect

  • Bean으로 등록해야 하므로 (컴포넌트 스캔을 사용한다면) @Componet도 추가

 

PointCut 정의

 

execution

  • ex. @Around("execution(* com.example..*.EventService.*(..))")
  • execution은 기존 코드를 완전히 건드리지 않고 aspect 내에 작성된 표현식으로 기능을 수행할 수 있음
  • 하지만 pointcut 조합이 어려움
    • ex. &&, ||, !

 

@annotation

  • ex. @Around("@annotation(PerfLogging)")
  • 애노테이션 내 @Retention
    • RetentionPolicy.CLASS : 애노테이션 정보가 바이트 코드까지 남아 있음 (default)
    • RetentionPolicy.SOURCE : 컴파일 후에 사라짐
    • RetentionPolicy.RUNTIME : 런타임까지 유지 (굳이 할 필요 없음)
  • 애노테이션을 정의하고, 해당 애노테이션을 원하는 메소드에 추가

 

Example

// aspect
@Aspect
@Component
public class PerfAspect {

    // @Around("execution(* com.example..*.EventService.*(..))") // execution
    @Around("@annotation(PerfLogging)")
    public Object logPerf(ProceedingJoinPoint pjp) throws Throwable {
        long begin = System.currentTimeMillis();
        Object retVal = pjp.proceed();
        System.out.println(System.currentTimeMillis() - begin);
        return retVal;
    }
}

// interface
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface PerfLogging {
}

// service
@Service
public class EventServiceImpl implements EventService {

    @PerfLogging
    @Override
    public void createEvent() {
        try {
            Thread.sleep(1000);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("Created an event");
    }

    ...
}



cf. pointcut 관련

 

참고

728x90

'Spring' 카테고리의 다른 글

[Spring] @Cacheable, @CachePut, @CacheEvict  (0) 2024.08.17
728x90

Overview

캐시는 서버의 부담을 줄이고, 성능을 높이기 위해 사용되는 기술이다

  • 예를 들어 어떤 요청을 처리하는데 계산이 복잡하거나 혹은 DB에서 조회하는게 오래 걸리는 등에 적용하여 결과를 저장해두고 가져옴으로써 빠르게 처리할 수 있다.
    • 캐시는 값을 저장해두고 불러오기 때문에 반복적으로 동일한 결과를 반환하는 경우에 용이하다.
  • 만약 매번 다른 결과를 돌려줘야 하는 상황에 캐시를 적용한다면 오히려 성능이 떨어지게 된다.
    • 오히려 캐시에 저장하거나 캐시를 확인하는 작업 때문에 부하가 생기기 때문이다.
  • 그러므로 캐시는 동일한 결과를 반환하는 반복적인 작업과 시간이 오래 걸려서 서버(애플리케이션)에 부담이 되는 경우에 적용하면 좋다.

스프링은 AOP 방식으로 편리하게 메소드에 캐시를 적용하는 기능을 제공하고 있다.

 

 

Spring에서 캐시 적용하기

스프링에서는 `@Cacheable`, `@CachePut`, `@CacheEvict`어노테이션을를 활용해서 캐시를 적용할 수 있다.

 

@EnableCaching

@Cacheable과 같은 어노테이션 기반의 캐시 기능을 사용하기 위해서 `@EnableCaching`를 추가한다.

@EnableCaching
@Configuration
public class CacheConfig {
    ...
}

 

캐시를 관리해줄 CacheManager를 Bean으로 등록한다.

 

스프링 공식 문서 - 캐시 추상화 (Cache Abstarction)에서 현재 지원되는 다양한 매니저의 종류를 확인할 수 있다.

 

 

@Cacheable

캐시를 저장/조회를 설정한다.

  • 보통 메소드에 적용한다.
    • 클래스나 인터페이스에 지정할 수도 있지만 그런 경우는 극히 적다.
  • 캐시에 데이터가 없을 경우에는 기존의 로직을 실행한 후에 캐시에 데이터를 추가한다.
  • 캐시에 데이터가 있으면 캐시의 데이터를 반환한다.

메소드의 파라미터가 1개인 경우

@Cacheable("todayWebtoon")
public Webtoon getTodayWebtoon(String webtoonNo) {
    // logic
}

 

파라미터로 넘어온 `webtoonId`를 기준으로 `todayWebtoon` 캐시에서 값을 조회한다.

  • 값이 없으면 logic을 실행하고, 반환 값을 저장한다.
  • 값이 있으면 저장된 값을 반환한다.

만약 파라미터가 없는 경우 디폴트 값을 key로 활용하면 된다.

 

메소드의 파라미터가 여러 개인 경우

@Cacheable(value = "todayWebtoon", key = "webtoonNo")
public Webtoon getTodayWebtoon(String webtoonNo, Date date) {
    // logic
}

 

파라미터가 여러 개인 경우 key를 지정해준다.


메소드의 파라미터가 객체인 경우

@Cacheable(value = "todayWebtoon", key = "#webtoon.webtoonNo")
public Webtoon getTodayWebtoon(Webtoon webtoon, Date date) {
    // logic
}

 

Key값의 지정에는 SpEL이 사용된다. 그렇기 때문에 만약 파라미터가 객체라면 다음과 같이 하위 속성에 접근하면 된다.

 

 

@CachePut

캐시에 값을 저장하는 용도로만 사용된다.

@CachePut(value = "todayWebtoon", key = "webtoonNo")
public Webtoon updateWebtoon(String webtoonNo) {
    // logic
}

 

실행 결과를 캐시에 저장하지만, 조회 시에 저장된 캐시의 내용을 사용하지는 않고 항상 메소드의 로직을 실행한다.

 

@CacheEvict

캐시를 제거하는 용도로 사용된다.

 

만약 값이 달라지거나 현재 저장된 값이 무의미한 값이라면 제거되어야 한다.

 

캐시를 제거하기 위해서는 크게 두 가지 방법이 존재한다.

  • 일정한 주기로 캐시를 제거
  • 값이 변할 때 캐시를 제거

예를 들어 하루에 한 번씩 바뀌는 정보라면 batch 또는 scheduled 기능을 이용하여 일정 시간 주기로 캐시를 삭제한다.


특정 캐시 내의 값을 모두 제거하는 경우

@CacheEvict(value = "todayWebtoon", allEntries = true)
public void clearTodayWebtoon() {
    // logic    
}

 


캐시 내 특정 값만 제거하는 경우

@CacheEvict(value = "todayWebtoon", key = "webtoon.webtoonNo")
public void clearWebtoon(Webtoon webtoon) {
    // logic    
}

 

728x90

'Spring' 카테고리의 다른 글

[Spring] AOP (Aspect Oriented Programming)  (0) 2024.08.28
728x90
📌 들어가기 앞서

본 글은 향로 선생님의 4. Spring Batch 가이드 - Spring Batch Job Flow 의 내용을 바탕으로 작성되었습니다.
내용을 효과적으로 읽기 위해서는 위 글을 우선적으로 읽어보시는 것을 권장드립니다.


본 글은 아래의 내용을 포함하고 있습니다.
• Spring Batch Job Flow와 흐름 제어


원본 글에 대한 인용 및 요약은 음영 처리된 형태로 표시됩니다.
글의 작성 방향은 이 글을 참고해주시면 감사하겠습니다.

관련 시리즈

1. Spring Batch 5 가이드 - 배치 어플리케이션이란?
2. Spring Batch 5 가이드 - Batch Job 실행해보기
3. Spring Batch 5 가이드 - 메타테이블엿보기
4. Spring Batch 5 가이드 - Spring Batch Job Flow
5. Spring Batch 5 가이드 - Spring Batch Scope & Job Parameter
6. Spring Batch 5 가이드 - Chunk 지향 처리
7. Spring Batch 5 가이드 - ItemReader
8. Spring Batch 5 가이드 - ItemWriter
9. Spring Batch 5 가이드 - ItemProcessor 

 

앞서 Spring Batch의 Job을 구성하는데는 Step이 있다고 말씀드렸습니다.

Step은 실제 Batch 작업을 수행하는 역할을 합니다. 이전에 작성한 코드를 보시면 Job은 코드가 거의 없죠?
실제로 Batch 비지니스 로직을 처리하는 (ex: log.info()) 기능은 Step에 구현되어 있습니다.

이처럼 Step에서는 Batch로 실제 처리하고자 하는 기능과 설정을 모두 포함하는 장소라고 생각하시면 됩니다.

Batch 처리 내용을 담다보니, Job 내부의 Step들간에 순서 혹은 처리 흐름을 제어할 필요가 있는데요.
이번엔 여러 Step들을 어떻게 관리할지에 대해서 알아보겠습니다.

 

4.1. next

next()는 순차적으로 Step들 연결시킬때 사용됩니다.
step1 -> step2 -> stpe3 순으로 하나씩 실행시킬때 next()는 좋은 방법입니다.
@Configuration
class SimpleNextJobConfiguration(
    private val jobRepository: JobRepository,
    private val transactionManager: PlatformTransactionManager,
) {
    private val log = logger()

    @Bean
    fun stepNextJob(): Job {
        return JobBuilder("stepNextJob", jobRepository)
            .start(step1())
            .next(step2())
            .next(step3())
            .build()
    }

    @Bean
    fun step1(): Step {
        return StepBuilder("step1", jobRepository)
            .tasklet(
                { _, _ ->
                    log.info(">>>>> This is Step1")
                    RepeatStatus.FINISHED
                },
                transactionManager,
            )
            .build()
    }

    @Bean
    fun step2(): Step {
        return StepBuilder("step2", jobRepository)
            .tasklet(
                { _, _ ->
                    log.info(">>>>> This is Step2")
                    RepeatStatus.FINISHED
                },
                transactionManager,
            )
            .build()
    }

    @Bean
    fun step3(): Step {
        return StepBuilder("step3", jobRepository)
            .tasklet(
                { _, _ ->
                    log.info(">>>>> This is Step3")
                    RepeatStatus.FINISHED
                },
                transactionManager,
            )
            .build()
    }
}

실행 결과

 

4.1.1. 지정한 Batch Job 만 실행

배치 코드 내에 2개 이상의 Job이 정의되어 있는 경우 아래와 같은 에러가 발생합니다.

Caused by: java.lang.IllegalArgumentException: Job name must be specified in case of multiple jobs

Job name must be specified in case of multiple jobs

 

이러한 경우 application.yml 파일 내에 아래와 같이 실행할 job name을 지정할 수 있게 하고,
Program argument 혹은 Environment vairables로 jobName을 지정해주시면 됩니다.

# application.yml

spring.batch.job.name: ${jobName:NONE}
Spring Boot 3.x.x 로 버전이 올라가면서 기존에 사용했던 spring.batch.job.names 프로퍼티는 spring.batch.job.name 으로 대체되었습니다.

관련 이슈: Remove support for running multiple Spring Batch jobs

 

 

4.2. 조건 별 흐름 제어

Next가 순차적으로 Step의 순서를 제어한다는 것을 알게 됐습니다.

여기서 중요한 것은, 앞의 Step에서 오류가 나면 나머지 뒤에 있는 Step 들은 실행되지 못한다는 것입니다.

하지만 상황에 따라 정상일때는 Step B로, 오류가 났을때는 Step C로 수행해야할때가 있습니다.

이 경우 FlowBuilder에서 제공하는 아래 메서드를 통해 Job의 흐름 및 순서를 제어할 수 있습니다.

  • .on()
    • 캐치할 ExitStatus 지정
    • * 일 경우 모든 ExitStatus가 지정
  • to()
    • 다음으로 이동할 Step 지정
  • from()
    • 일종의 이벤트 리스너 역할
    • 상태값을 보고 일치하는 상태라면 to()에 포함된 step을 호출
    • step1의 이벤트 캐치가 FAILED로 되있는 상태에서 추가로 이벤트 캐치하려면 from을 써야만 함
  • end()
    • end는 FlowBuilder를 반환하는 end와 FlowBuilder를 종료하는 end 2개가 있음
    • on("*")뒤에 있는 end는 FlowBuilder를 반환하는 end
    • build() 앞에 있는 end는 FlowBuilder를 종료하는 end
    • FlowBuilder를 반환하는 end 사용시 계속해서 from을 이어갈 수 있음

 

아래의 시나리오를 가정하여 예제를 작성해보겠습니다.

  • step1 실패 시나리오: step1 -> step3
  • step1 성공 시나리오: step1 -> step2 -> step3
@Configuration
class StepNextConditionalJobConfiguration(
    private val jobRepository: JobRepository,
    private val transactionManager: PlatformTransactionManager,
) {
    private val log = logger()

    @Bean
    fun stepNextConditionalJob(): Job {
        return JobBuilder("stepNextConditionalJob", jobRepository)
            .start(step1())
                .on("FAILED") // FAILED 일 경우
                .to(step3()) // step3으로 이동한다.
                .on("*") // step3의 결과 관계 없이
                .end() // step3으로 이동하면 Flow가 종료한다.
            .from(step1()) // step1로부터
                .on("*") // FAILED 외에 모든 경우
                .to(step2()) // step2로 이동한다.
                .next(step3()) // step2가 정상 종료되면 step3으로 이동한다.
                .on("*") // step3의 결과 관계 없이
                .end() // step3으로 이동하면 Flow가 종료한다.
            .end() // Job 종료
            .build()
    }

    @Bean
    fun step1(): Step {
        return StepBuilder("step1", jobRepository)
            .tasklet(
                { _, _ ->
                    log.info(">>>>> This is Step1")
                    RepeatStatus.FINISHED
                },
                transactionManager,
            )
            .build()
    }

    @Bean
    fun step2(): Step {
        return StepBuilder("step2", jobRepository)
            .tasklet(
                { _, _ ->
                    log.info(">>>>> This is Step2")
                    RepeatStatus.FINISHED
                },
                transactionManager,
            )
            .build()
    }

    @Bean
    fun step3(): Step {
        return StepBuilder("step3", jobRepository)
            .tasklet(
                { _, _ ->
                    log.info(">>>>> This is Step3")
                    RepeatStatus.FINISHED
                },
                transactionManager,
            )
            .build()
    }
}

 

4.2.1. BatchStatus vs. ExitStatus

조건 별 흐름제어를 설정할 때 FlowBuilder에서 제공하는 on() 메서드는 BatchStatus가 아닌 ExitStatus를 기준으로 제어합니다.

@Configuration
class StepNextConditionalJobConfiguration(
    private val jobRepository: JobRepository,
    private val transactionManager: PlatformTransactionManager,
) {
    @Bean
    fun stepNextConditionalJob(): Job {
        return JobBuilder("stepNextConditionalJob", jobRepository)
            .start(step1())
                .on("FAILED") // ExitStExitStatus.FAILED 를 의미
                .to(step3())
            ...
    }
    ...
 }

 

BatchStatus는 Job 또는 Step 의 실행 결과를 Spring에서 기록할 때 사용하는 Enum입니다. BatchStatus로 사용 되는 값은 아래와 같습니다.

BatchStatus

 

반면 흐름 제어에서 참조하는 ExitStatus는 Step의 실행 후 상태를 얘기합니다. ExitStatus는 Enum class가 아닙니다.

ExitStatus

 

Spring Batch는 기본적으로 ExitStatus의 exitCode는 Step의 BatchStatus와 같도록 설정이 되어 있습니다.

 

만약 흐름 제어를 위해 별도의 커스텀 ExitStatus 가 필요한 경우 아래와 같이 별도의 exitCode를 반환할 수 있게 설정해주시면 됩니다.

class SkipCheckingListener : StepExecutionListener {
        override fun afterStep(stepExecution: StepExecution): ExitStatus? {
            val exitCode = stepExecution.exitStatus.exitCode
            return if (exitCode != ExitStatus.FAILED.exitCode &&
                stepExecution.skipCount > 0
            ) {
                ExitStatus("COMPLETED WITH SKIPS") // 커스텀 exitCode 반환
            } else {
                null
            }
        }
    }

 

4.3 decide

자 위에서 (4.2)에서 Step의 결과에 따라 서로 다른 Step으로 이동하는 방법을 알아보았습니다.

이번에는 다른 방식의 분기 처리를 알아 보겠습니다. 위에서 진행했던 방식은 2가지 문제가 있습니다.

1. Step이 담당하는 역할이 2개 이상 존재
    - 실제 해당 Step이 처리해야할 로직 외에도 분기처리를 시키기 위해 ExitStatus 조작이 필요합니다.

2. 다양한 분기 로직 처리의 어려움
    - ExitStatus를 커스텀하게 고치기 위해선 Listener를 생성하고 Job Flow에 등록하는 등 번거로움이 존재합니다.

명확하게 Step들간의 Flow 분기만 담당하면서 다양한 분기처리가 가능한 타입이 있으면 편하겠죠?

그래서 Spring Batch에서는 Step들의 Flow속에서 분기만 담당하는 타입이 있습니다. JobExecutionDecider 라고 하며, 이를 사용한 샘플 코드를 한번 만들어보겠습니다.

 

@Configuration
class DeciderJobConfiguration(
    private val jobRepository: JobRepository,
    private val transactionManager: PlatformTransactionManager,
) {
    private val log = logger()

    @Bean
    fun deciderJob(): Job {
        return JobBuilder("deciderJob", jobRepository)
            .start(deciderStartStep())
            .next(decider()) // 홀수 | 짝수 구분
            .from(decider()) // decider의 상태가
            .on("ODD") // ODD라면
            .to(oddStep()) // oddStep로 간다.
            .from(decider()) // decider의 상태가
            .on("EVEN") // ODD라면
            .to(evenStep()) // evenStep로 간다.
            .end() // builder 종료
            .build()
    }

    @Bean
    fun deciderStartStep(): Step {
        return StepBuilder("deciderStartStep", jobRepository)
            .tasklet(
                { _, _ ->
                    log.info(">>>>> Start Step")
                    RepeatStatus.FINISHED
                },
                transactionManager,
            )
            .build()
    }

    @Bean
    fun evenStep(): Step {
        return StepBuilder("evenStep", jobRepository)
            .tasklet(
                { _, _ ->
                    log.info(">>>>> 짝수입니다")
                    RepeatStatus.FINISHED
                },
                transactionManager,
            )
            .build()
    }

    @Bean
    fun oddStep(): Step {
        return StepBuilder("oddStep", jobRepository)
            .tasklet(
                { _, _ ->
                    log.info(">>>>> 홀수입니다")
                    RepeatStatus.FINISHED
                },
                transactionManager,
            )
            .build()
    }

    @Bean
    fun decider(): JobExecutionDecider {
        return OddDecider()
    }

    class OddDecider : JobExecutionDecider {
        private val log = logger()

        override fun decide(jobExecution: JobExecution, stepExecution: StepExecution?): FlowExecutionStatus {
            val pivot = Random().nextInt(50) + 1
            log.info("랜덤숫자: {}", pivot)

            return if (pivot % 2 == 0) {
                FlowExecutionStatus("EVEN")
            } else {
                FlowExecutionStatus("ODD")
            }
        }
    }
}

실행 결과

 

분기 로직에 대한 모든 일은 OddDecider가 전담하고 있습니다.
아무리 복잡한 분기로직이 필요하더라도 Step과는 명확히 역할과 책임이 분리된채 진행할 수 있습니다.

728x90
728x90
📌 들어가기 앞서

본 글은 향로 선생님의 3. Spring Batch 가이드 - 메타테이블엿보기 의 내용을 바탕으로 작성되었습니다.
내용을 효과적으로 읽기 위해서는 위 글을 우선적으로 읽어보시는 것을 권장드립니다.


본 글은 아래의 내용을 포함하고 있습니다.
• Spring Batch 메타데이터 스키마 설명


원본 글에 대한 인용 및 요약은 음영 처리된 형태로 표시됩니다.
글의 작성 방향은 이 글을 참고해주시면 감사하겠습니다.

관련 시리즈

1. Spring Batch 5 가이드 - 배치 어플리케이션이란?
2. Spring Batch 5 가이드 - Batch Job 실행해보기
3. Spring Batch 5 가이드 - 메타테이블엿보기
4. Spring Batch 5 가이드 - Spring Batch Job Flow
5. Spring Batch 5 가이드 - Spring Batch Scope & Job Parameter
6. Spring Batch 5 가이드 - Chunk 지향 처리
7. Spring Batch 5 가이드 - ItemReader
8. Spring Batch 5 가이드 - ItemWriter
9. Spring Batch 5 가이드 - ItemProcessor 

 

지난 글(2. Spring Batch 5 가이드 - Batch Job 실행해보기)의 말미에 Spring Batch의 메타데이터 스키마에 대해 간략히 소개를 했습니다.

 

이번 글에서는 각 메타 데이터의 역할이 무엇인지, 어떤 정보를 담고 있는지 정리해보겠습니다.

https://docs.spring.io/spring-batch/reference/schema-appendix.html

TL;DR

Spring Batch meta-data schema : https://docs.spring.io/spring-batch/reference/schema-appendix.html
  • BATCH_JOB_INSTANCE
    • 배치 Job의 생성 정보를 담는 테이블
  • BATCH_JOB_EXECUTION
    • 배치 Job의 개별 실행 정보(성공, 실패 등)를 담는 테이블
    • Job Instance(BATCH_JOB_INSTANCE) 하나에 여러 개의 Job Execution(BATCH_JOB_EXECUTION)이 생성
  • BATCH_JOB_EXECUTION_PARAM
    • 배치 Job에서 사용되는 파라미터 값들을 담는 테이블
    • 과거에는 타입 별로 파라미터를 담을 수 있는 컬럼이 존재했지만, 현재는 파라미터의 타입과 값을 지정하여 정보를 저장
  • BATCH_JOB_EXECUTION_CONTEXT
    • 배치 Job 실행 중에 사용된 정보에 대한 Context를 저장하기 위한 테이블
    • Job Execution 하나당 하나씩의 Job Execution Context를 갖고, 해당 Context 정보를 통해 동일한 Job Scope 내에서 데이터를 공유 할 수 있음
    • Context 데이터를 통해 오류가 발생한 이후 중단된 부분부터 시작할 수 있음
  • BATCH_STEP_EXECUTION
    • 배치 Step의 개별 실행 정보를 담는 테이블
  • BATCH_STEP_EXECUTIOIN_CONTEXT
    • 배치 Step 실행 중에 사용된 정보에 대한 Context를 저장하기 위한 테이블

 

 

3.1. BATCH_JOB_INSTANCE

3.1.1. BATCH_JOB_INSTANCE 테이블

BATCH_JOB_INSTANCE 테이블은 Job Parameter에 따라 생성되는 테이블입니다.

이 Job Parameter가 생소할텐데요. 간단하게 말씀드리면, Spring Batch가 실행될때 외부에서 받을 수 있는 파라미터입니다.

예를 들어, 특정 날짜를 Job Parameter로 넘기면 Spring Batch에서는 해당 날짜 데이터로 조회/가공/입력 등의 작업을 할 수 있습니다.

같은 Batch Job 이라도 Job Parameter가 다르면 Batch_JOB_INSTANCE에는 기록되며, Job Parameter가 같다면 기록되지 않습니다.

 

BATCH_JOB_INSTANCE 테이블은 배치 Job의 생성 정보를 담는 테이블입니다.

CREATE TABLE BATCH_JOB_INSTANCE  (
  JOB_INSTANCE_ID	BIGINT  PRIMARY KEY,
  VERSION		BIGINT,
  JOB_NAME		VARCHAR(100) NOT NULL,
  JOB_KEY		VARCHAR(32) NOT NULL
);
컬럼명 설명
JOB_INSTANCE_ID Job 고유 ID
VERSION 버전 정보 (update 될 때마다 1씩 증가)
JOB_NAME Job 빌드 시 생성하는 이름
JOB_KEY 동일한 Job의 개별 인스턴스를 구분하기 위한 고유 값

동일한 JOB_NAEM을 갖는 Job Instance는 서로 다른 Job Parameters에 의해 고유한 JOB_KEY를 갖음

 

 

3.2. BATCH_JOB_EXECUTION

3.2.1. BATCH_JOB_EXECUTION 테이블

BATCH_JOB_EXECUTION 테이블은 배치 Job의 개별 실행 정보를 담는 테이블입니다.

JOB_EXECUTION와 JOB_INSTANCE는 부모-자식 관계입니다.
JOB_EXECUTION은 자신의 부모 JOB_INSTACNE가 성공/실패했던 모든 내역을 갖고 있습니다. 
CREATE TABLE BATCH_JOB_EXECUTION  (
  JOB_EXECUTION_ID	BIGINT  PRIMARY KEY ,
  VERSION		BIGINT,
  JOB_INSTANCE_ID	BIGINT NOT NULL,
  CREATE_TIME		TIMESTAMP NOT NULL,
  START_TIME		TIMESTAMP DEFAULT NULL,
  END_TIME		TIMESTAMP DEFAULT NULL,
  STATUS		VARCHAR(10),
  EXIT_CODE		VARCHAR(20),
  EXIT_MESSAGE		VARCHAR(2500),
  LAST_UPDATED		TIMESTAMP,
  constraint JOB_INSTANCE_EXECUTION_FK foreign key (JOB_INSTANCE_ID)
  references BATCH_JOB_INSTANCE(JOB_INSTANCE_ID)
) ;
컬럼명 설명
JOB_EXECUTION_ID Job 실행의 고유 ID
VERSION 버전 정보 (update 될 때마다 1씩 증가)
JOB_INSTANCE_ID Job 고유 ID
CREATE_TIME Job 실행 생성 일시
START_TIME Job 실행 생성 일시
END_TIME Job 실행 종료 일시
STATUS 실행 상태 (BatchStatus)
EXIT_CODE 종료 코드
EXIT_MESSAGE Job 수행 종료(실패) 시 메세지
LAST_UPDATED 최종 업데이트 일시

 

 

3.2.2. BATCH_JOB_EXECUTION_PARAM 테이블

BATCH_JOB_EXECUTION_PARAM 테이블은 배치 Job에서 사용되는 파라미터 값들을 담는 테이블입니다.

BATCH_JOB_EXECUTION_PARAM 3.x.x vs 5.1.2

이전에는 String, Date, Long, Double 네가지 타입의 값만 사용이 가능했고, 해당 컬럼에 값을 저장했다면,
현재는 파라미터의 타입과 값을 지정하여 정보를 저장합니다. 

CREATE TABLE BATCH_JOB_EXECUTION_PARAMS  (
  JOB_EXECUTION_ID	BIGINT NOT NULL ,
  PARAMETER_NAME	VARCHAR(100) NOT NULL ,
  PARAMETER_TYPE	VARCHAR(100) NOT NULL ,
  PARAMETER_VALUE	VARCHAR(2500) ,
  IDENTIFYING		CHAR(1) NOT NULL ,
  constraint JOB_EXEC_PARAMS_FK foreign key (JOB_EXECUTION_ID)
  references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID)
);
컬럼명 설명
JOB_EXECUTION_ID Job 실행의 고유 ID
PARAMETER_NAME 파라미터명
PARAETER_TYPE 파라미터 타입
PARAMETER_VALUE 파라미터 값
IDENTIFYING Job Parameter가 Job Instance 생성 시 관여했는지 여부를 나타내는 플래그

 

 

3.2.3. BATCH_JOB_EXECUTION_CONTEXT 테이블

BATCH_JOB_EXECUTION_CONTEXT 테이블은 배치 Job 실행 중에 사용된 정보에 대한 Context를 저장하기 위한 테이블입니다.

  • Job Execution 하나당 하나씩의 Job Execution Context를 갖고, 해당 Context 정보를 통해 동일한 Job Scope 내에서 데이터를 공유 할 수 있습니다.
  • Context 데이터를 통해 오류가 발생한 이후 중단된 부분부터 시작할 수 있습니다.
CREATE TABLE BATCH_JOB_EXECUTION_CONTEXT  (
  JOB_EXECUTION_ID	BIGINT PRIMARY KEY,
  SHORT_CONTEXT		VARCHAR(2500) NOT NULL,
  SERIALIZED_CONTEXT	CLOB,
  constraint JOB_EXEC_CTX_FK foreign key (JOB_EXECUTION_ID)
  references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID)
) ;
컬럼명 설명
JOB_EXECUTION_ID Job 실행의 고유 ID
SHORT_CONTEXT String 형태의 Context
SERIALIZED_CONTEXT Serializied 된 전체 Context

 

 

3.3. BATCH_STEP_EXECUTION

3.3.1. BATCH_STEP_EXECUTION 테이블

BATCH_STEP_EXECUTION 테이블은 배치 Step의 개별 실행 정보를 담는 테이블입니다.

CREATE TABLE BATCH_STEP_EXECUTION  (
  STEP_EXECUTION_ID	BIGINT NOT NULL PRIMARY KEY,
  VERSION		BIGINT NOT NULL,
  STEP_NAME		VARCHAR(100) NOT NULL,
  JOB_EXECUTION_ID	BIGINT NOT NULL,
  CREATE_TIME		TIMESTAMP NOT NULL,
  START_TIME		TIMESTAMP DEFAULT NULL,
  END_TIME		TIMESTAMP DEFAULT NULL,
  STATUS		VARCHAR(10),
  COMMIT_COUNT		BIGINT,
  READ_COUNT		BIGINT,
  FILTER_COUNT		BIGINT,
  WRITE_COUNT		BIGINT,
  READ_SKIP_COUNT	BIGINT,
  WRITE_SKIP_COUNT	BIGINT,
  PROCESS_SKIP_COUNT	BIGINT,
  ROLLBACK_COUNT	BIGINT,
  EXIT_CODE		VARCHAR(20),
  EXIT_MESSAGE		VARCHAR(2500),
  LAST_UPDATED		TIMESTAMP,
  constraint JOB_EXECUTION_STEP_FK foreign key (JOB_EXECUTION_ID)
  references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID)
);
컬럼명 설명
STEP_EXECUTION_ID Step 실행의 고유 ID
VERSION 버전 정보 (update 될 때마다 1씩 증가)
STEP_NAME Step 이름
JOB_EXECUTION_ID Job 실행의 고유 ID
CREATE_TIME Step 실행 생성 일시
START_TIME Step 실행 생성 일시
END_TIME Step 실행 종료 일시
STATUS 실행 상태 (BatchStatus)
COMMIT_COUNT 트랜잭션당 커밋 수
READ_COUNT 조회한 item 수
FILTER_COUNT 필터링된 item 수
WRITE_COUNT 저장된 item 수
READ_SKIP_COUNT 조회 skip한 item 수
WRITE_SKIP_COUNT 저장 skip한 item 수
PROCESS_SKIP_COUNT 가공(Process) skip된 item 수
ROLLBACK_COUNT 롤백 발생 횟수
EXIT_CODE 종료 코드
EXIT_MESSAGE Step 수행 종료(실패) 시 메세지
LAST_UPDATED 최종 업데이트 일시

 

 

3.3.2. BATCH_STEP_EXECUTION_CONTEXT 테이블

BATCH_STEP_EXECUTIOIN_CONTEXT 테이블은 배치 Step 실행 중에 사용된 정보에 대한 Context를 저장하기 위한 테이블입니다.

  • BATCH_JOB_EXECUTION_CONTEXT의 Step 버전이라고 생각하시면 됩니다.
CREATE TABLE BATCH_STEP_EXECUTION_CONTEXT  (
  STEP_EXECUTION_ID	BIGINT PRIMARY KEY,
  SHORT_CONTEXT		VARCHAR(2500) NOT NULL,
  SERIALIZED_CONTEXT	CLOB,
  constraint STEP_EXEC_CTX_FK foreign key (STEP_EXECUTION_ID)
  references BATCH_STEP_EXECUTION(STEP_EXECUTION_ID)
);
컬럼명 설명
STEP_EXECUTION_ID Step 실행의 고유 ID
SHORT_CONTEXT String 형태의 Context
SERIALIZED_CONTEXT Serializied 된 전체 Context

 

 

3.4. JOB, JOB_INSTANCE, JOB_EXECUTION

위의 메타데이터 스키마와 Spring Batch에서 프로세스를 연관지어서 정리해보면 아래와 같습니다.

  1. Spring Batch를 통해 Job을 정의(e.g. SimpleJob)한다.
  2. Job 실행 시 JobParameter를 통해 특정 일시 또는 조건을 설정한다. (= JobParameter에 따른 JobInstance 생성)
  3. 특정 조건에 의해 생성된 JobInstance가 실행될 때마다 개별 실행에 따른 JobExecution 정보가 저장된다.
728x90
728x90
📌 들어가기 앞서

본 글은 향로 선생님의 2. Spring Batch 가이드 - Batch Job 실행해보기 의 내용을 바탕으로 작성되었습니다.
내용을 효과적으로 읽기 위해서는 위 글을 우선적으로 읽어보시는 것을 권장드립니다.


본 글은 아래의 내용을 포함하고 있습니다.
• Spring Batch 프로젝트 생성하기
• Spring Batch 기본 용어
• 간단한 Job과 Step을 구성하여 배치 실행하기
• MySQL과 함께 실행하기
• Spring Batch 5에서 추가로 변경된 사항


원본 글에 대한 인용 및 요약은 음영 처리된 형태로 표시됩니다.
글의 작성 방향은 이 글을 참고해주시면 감사하겠습니다.

관련 시리즈

1. Spring Batch 5 가이드 - 배치 어플리케이션이란?
2. Spring Batch 5 가이드 - Batch Job 실행해보기
3. Spring Batch 5 가이드 - 메타테이블엿보기
4. Spring Batch 5 가이드 - Spring Batch Job Flow
5. Spring Batch 5 가이드 - Spring Batch Scope & Job Parameter
6. Spring Batch 5 가이드 - Chunk 지향 처리
7. Spring Batch 5 가이드 - ItemReader
8. Spring Batch 5 가이드 - ItemWriter
9. Spring Batch 5 가이드 - ItemProcessor 

작업한 코드는 Github에 작성되어 있습니다.
필요한 경우 참고하시면 됩니다.

 

2.1. Spring Batch 프로젝트 생성하기

기존 향로님이 진행하셨던 개발 환경은 아래와 같습니다.

  • IntelliJ IDEA 2018.2
  • Spring Boot 2.0.4
  • Java 8
  • Gradle

 

본 시리즈에서는 아래의 개발 환경으로 예제 코드가 작성됩니다.

  • Spring Boot 3.3.0 (Spring Batch 5.1.2)
  • Kotlin JVM 1.9.24
  • JDK 17
  • Gradle (+ Kotlin DSL)

 

아래 파일은 위 환경에 따른 Gradle 파일입니다.

plugins {
	id("org.springframework.boot") version "3.3.0"
	id("io.spring.dependency-management") version "1.1.5"
	kotlin("jvm") version "1.9.24"
	kotlin("plugin.spring") version "1.9.24"
}

group = "com.ruthetum"
version = "0.0.1-SNAPSHOT"

java {
	toolchain {
		languageVersion = JavaLanguageVersion.of(17)
	}
}

repositories {
	mavenCentral()
}

dependencies {
	implementation("org.springframework.boot:spring-boot-starter-batch")
	implementation("org.jetbrains.kotlin:kotlin-reflect")
	runtimeOnly("com.h2database:h2")
	runtimeOnly("com.mysql:mysql-connector-j")
	testImplementation("org.springframework.boot:spring-boot-starter-test")
	testImplementation("org.jetbrains.kotlin:kotlin-test-junit5")
	testImplementation("org.springframework.batch:spring-batch-test")
	testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}

kotlin {
	compilerOptions {
		freeCompilerArgs.addAll("-Xjsr305=strict")
	}
}

tasks.withType<Test> {
	useJUnitPlatform()
}

 

 

Spring Batch 기본 용어

간단한 Job을 선언하는 방법을 확인하기 전에 기본적인 Spring Batch의 용어들에 대해 간단하게 설명드리겠습니다.

  • JoLauncher : Job을 실행시키는 컴포넌트
  • Job : 배치 작업
  • JobRepository : Job 실행과 Job, Step을 저장
  • Step : 배치 작업의 단계
  • ItemReader, ItemProcesser, ItemWriter : 데이터를 읽고 처리하고 쓰는 구성

 

Job은 1개 이상의 Step으로 구성되어 배치 작업을 수행합니다.

 

후에 설명을 드리겠지만 Step 같은 경우 Chunk 기반의 작업을 수행하는 경우와 Tasklet 기반의 작업을 수행하는 경우로 구분됩니다.

  • 일반적으로 Chunk 기반 스텝을 많이 사용합니다.
  • Tasklet 스탭은 하나의 트랜잭션 내에서 작동하고, 단순한 처리를 할 때 사용하게 됩니다.

 

 

2-2. Simple Job 생성하기

기본 용어를 이해한 상태에서 간단한 Job을 구성해보도록 하겠습니다.

먼저 Java와 Spring Batch 4 기준으로 간단한 Job을 구성하게 되면 아래와 같은 형태로 작성되어집니다.

@Slf4j // log 사용을 위한 lombok 어노테이션
@RequiredArgsConstructor // 생성자 DI를 위한 lombok 어노테이션
@Configuration
public class SimpleJobConfiguration {
    private final JobBuilderFactory jobBuilderFactory; // 생성자 DI 받음
    private final StepBuilderFactory stepBuilderFactory; // 생성자 DI 받음

    @Bean
    public Job simpleJob() {
        return jobBuilderFactory.get("simpleJob")
                .start(simpleStep1())
                .build();
    }

    @Bean
    public Step simpleStep1() {
        return stepBuilderFactory.get("simpleStep1")
                .tasklet((contribution, chunkContext) -> {
                    log.info(">>>>> This is Step1");
                    return RepeatStatus.FINISHED;
                })
                .build();
    }
}

 

각 컴포넌트 및 라인에서 의미하는 바는 아래와 같습니다.

@Configuration
    - Spring Batch의 모든 Job은 @Configuration으로 등록해서 사용합니다.

jobBuilderFactory.get("simpleJob")
    - simpleJob 이란 이름의 Batch Job을 생성합니다.
    - job의 이름은 별도로 지정하지 않고, 이렇게 Builder를 통해 지정합니다.

stepBuilderFactory.get("simpleStep1")
    - simpleStep1 이란 이름의 Batch Step을 생성합니다.
    - jobBuilderFactory.get("simpleJob")와 마찬가지로 Builder를 통해 이름을 지정합니다.

tasklet((contribution, chunkContext))
    - Step 안에서 수행될 기능들을 명시합니다.
    - Tasklet은 Step안에서 단일로 수행될 커스텀한 기능들을 선언할때 사용합니다.
    - 여기서는 Batch가 수행되면 log.info(">>>>> This is Step1") 가 출력되도록 합니다.

 

기본적으로 Spring 및 Spring Batch에서 활용되는 어노테이션 및 주요 컴포넌트는 대부분 동일합니다.

다만 Spring Batch의 버전이 올라가면서 몇몇 어노테이션 지원 및 Spring Batch에서 배치를 실행하기 위해 정의하는 Job에 대한 설정 방법에 일부 수정이 있습니다.

 

[Batch 4 vs. Batch 5] JobBuilderFactory, StepBuilderFactory deprecated

먼저 Job과 Step을 선언하는 경우 JobBuilderFactory, StepBuilderFactory는 Batch 5를 기준으로 지원하지 않게 되었습니다.

  • 이에 따라 더이상 Builder Factory를 사용하지 않고 JobBuilder, StepBuilder를 사용합니다.

 

[Batch 4 vs. Batch 5] JobRepository, TransactionManager 

Spring Batch 5에서는 내부의 필요한 객체를 명시적으로 표시해주는 형태로 변경되었습니다.

  • 이에 따라 기존에는 생략했던 JobRepository, TransactionManager를 선언하여 Job을 구성하게 됩니다.

 

변경점을 고려하여 위에서 작성했던 예제 코드를 Kotlin과 Srping Batch 5 기준으로 작성하면 아래와 같이 변경됩니다.

@Configuration
class SimpleJobConfiguration(
    private val jobRepository: JobRepository,
    private val transactionManager: PlatformTransactionManager,
) {
    private val log = logger()

    @Bean
    fun simpleJob(): Job {
        return JobBuilder("simpleJob", jobRepository)
            .start(simpleStep1())
            .build()
    }

    @Bean
    fun simpleStep1(): Step {
        return StepBuilder("simpleStep1", jobRepository)
            .tasklet(simpleTasklet(), transactionManager)
            .build()
    }

    fun simpleTasklet(): Tasklet {
        return Tasklet { _, _ ->
            log.info(">>>>> This is Step1");
            RepeatStatus.FINISHED
        }
    }
}

 

실행 결과는 아래와 같습니다.

 

정상적으로 Job과 Step이 실행됨을 확인할 수 있습니다.

 

 

2-3. MySQL 환경에서 Spring Batch 실행해보기

이전 과정에서 굉장히 간단하게 Spring Batch가 수행되었습니다.
Spring Batch는 어플리케이션 코드만 작성하면 되는구나! 라고 생각하실수 있으실텐데요.

실제로는 그렇지 않습니다. Spring Batch에선 메타 데이터 테이블들이 필요합니다.

Spring Batch의 메타 데이터는 다음과 같은 내용들을 담고 있습니다.
- 이전에 실행한 Job이 어떤 것들이 있는지
- 최근 실패한 Batch Parameter가 어떤것들이 있고, 성공한 Job은 어떤것들이 있는지
- 다시 실행한다면 어디서 부터 시작하면 될지어떤 Job에 어떤 Step들이 있는지
- Step들 중 성공한 Step과 실패한 Step들은 어떤것들이 있는지

등등 Batch 어플리케이션을 운영하기 위한 메타데이터가 여러 테이블에 나눠져 있습니다.

 

아래 테이블은 

 

https://docs.spring.io/spring-batch/reference/schema-appendix.html

 

Spring Batch의 버전이 올라감에 따라 메타 데이터 스키마에도 작은 변경이 있었고, 해당 내용에 대해서는 다음 글에서 설명드리겠습니다.

테이블의 정의나 관계는 크게 변하지 않았고, Job Parameter 정보를 담는 BATCH_JOB_EXECUTION_PARAMS의 테이블 정의가 일부 변경되었습니다.

 

이 테이블들이 있어야만 Spring Batch가 정상 작동합니다.

기본적으로 H2 DB를 사용할 경우엔 해당 테이블을 Boot가 실행될때 자동으로 생성해주지만, MySQL이나 Oracle과 같은 DB를 사용할때는 개발자가 직접 생성해야만 합니다.

그럼 이 테이블들의 스키마가 궁금하실텐데요.
이미 Spring Batch에 해당 스키마가 존재하고 있고, 이를 그대로 복사해서 create table 하면 됩니다.

 

각 데이터베이스 별로 Native Query를 확인하고 싶으신 경우 Spring Batch Github을 통해 확인하실 수 있습니다.

e.g. MySQL Metadata table schema

 

 

2.4. Spring Batch 5에서 추가로 변경된 사항

본 내용은 원본 글에는 포함되어 있지 않은 Spring Batch 5를 적용할 때  Batch 4와 다른 부분에 대해서 내용을 정리합니다.

 

[Batch 4 vs. Batch 5] @EnableBatchProcessing 

Spring Boot 3.0부터는 @EnableBatchProcessing 사용을 권장하지 않습니다.

  • 정확히는 필수적으로 사용해야 하는 어노테이션이 아니게 되었습니다.
  • @EnableBatchProcessing 어노테이션의 경우 Batch와 관련된 기본 Bean들을 자동으로 등록해주는 기능을 담당합니다.
  • 그래서 버전 4까지는 위 어노테이션을 사용하여 기본 Bean들을 등록했지만, 버전5부터는 해당 어노테이션을 사용하지 않아도 Bean 등록이 진행됩니다.
 
728x90
728x90
📌 들어가기 앞서

본 글은 향로 선생님의 1. Spring Batch 가이드 - 배치 어플리케이션이란? 의 내용을 바탕으로 작성되었습니다.
내용을 효과적으로 읽기 위해서는 위 글을 우선적으로 읽어보시는 것을 권장드립니다.


본 글은 아래의 내용을 포함하고 있습니다.
• 배치 애플리케이션의 정의 및 Spring Batch 등장 배경
• Batch vs Scheduler(Quartz) 개념 비교
• 배치 애플리케이션 사례


원본 글에 대한 인용 및 요약은 음영 처리된 형태로 표시됩니다.
글의 작성 방향은 이 글을 참고해주시면 감사하겠습니다.

관련 시리즈

1. Spring Batch 5 가이드 - 배치 어플리케이션이란?
2. Spring Batch 5 가이드 - Batch Job 실행해보기
3. Spring Batch 5 가이드 - 메타테이블엿보기
4. Spring Batch 5 가이드 - Spring Batch Job Flow
5. Spring Batch 5 가이드 - Spring Batch Scope & Job Parameter
6. Spring Batch 5 가이드 - Chunk 지향 처리
7. Spring Batch 5 가이드 - ItemReader
8. Spring Batch 5 가이드 - ItemWriter
9. Spring Batch 5 가이드 - ItemProcessor 

1. 배치 애플리케이션이란?

배치(Batch)는 일괄처리 란 뜻을 갖고 있습니다.

일반적인 웹 애플리케이션(Tomcat + Spring MVC)의 경우 큰 데이터를 읽고, 가공하고, 저장한다면 서버는 CPU 및 I/O 등의 자원을 소모하기 때문에 다른 요청을 처리하지 못 하게 됩니다.
또한 하루에 1번 또는 단발적으로 수행되는 이러한 요청을 API로 구성하는 것은 리소스적으로 낭비입니다.

이렇게 단발적으로 대용량 데이터를 처리하는 애플리케이션을 배치 애플리케이션이라고 합니다.

배치 애플리케이션의 경우 데이터가 너무 많아서 처리 중 실패가 나는 경우 실패한 지점부터 작업을 처리하거나, 같은 파라미티로 같은 함수를 실행할 경우 실행 여부에 따라 작업을 제어해야할 수도 있습니다.

즉, 비즈니스 로직 외에 부가적으로 신경써야 할 부분들이 많다는 것을 알 수 있습니다.

여기서 한가지 생각해볼것이, 웹 애플리케이션을 개발할 때 저희는 비지니스 로직에 최대한 집중할 수 있습니다.
그건 왜일까요? 바로 Spring MVC를 사용하기 때문입니다.
Spring MVC를 사용함으로 비지니스 로직에 최대한 집중할 수 있었습니다.

그럼 Spring에서 이런 배치 어플리케이션을 지원하는 모듈이 없을까요?
Spring 진영에선 Spring Batch가 있습니다.

 

배치 애플리케이션

  • 사용자의 개입 없이 일괄적으로 작업을 처리하는 애플리케이션 (대체로 대용량 데이터를 처리)
  • 서비스를 운영하는 관점에서 주기적으로 작업을 처리하기 위해 배치 애플리케이션 사용

 

배치 애플리케이션이 필요한 경우

  1. 필요한 데이터를 모아서 처리해야할 때 (e.g. 월별 거래 명세서 생성)
  2. 일부러 지연시켜 처리할 때 (e.g. 주문한 상품을 바로 배송 처리하지 않고, 일정 시간 뒤 처리)
  3. 자원을 효율적으로 활용해야할 때 (e.g. 트래픽이 적은 시간 대에 서버 리소스를 활용)

 

1.1. Spring Batch?

Spring Batch 프로젝트는 Accenture와 Spring Source의 공동 작업으로 2007년에 탄생했습니다.

Spring Batch는 Spring의 특성을 그대로 가져왔습니다.
그래서 DI, AOP, 서비스 추상화 등 Spring 프레임워크의 3대 요소를 모두 사용할 수 있으면서, Accenture의 Batch 노하우가 담긴 아키텍처를 사용할 수 있습니다.

 

Spring Batch 5 에서 지원하는 Reader & Writer는 아래와 같습니다.

DataSource 구분 설명
Database JDBC 페이징, 커서, 일괄 업데이트 등 사용 가능
Hibernate 페이징, 커서 사용 가능
JPA 페이징 사용 가능
File Flat file 지정한 구분자를 통해 파싱 지원
XML XML 파싱 지원
JSON JSON 파싱 지원
  • Flat file
    • 플랫 파일은 한 개 또는 그 이상의 레코드가 포함된 특정 파일을 의미합니다. 
    • 일반 텍스트 형태로 저장되는 데이터로, 데이터 포맷이나 의미를 정의하는 메타데이터가 존재하지 않습니다.

 

세부적인 설명은 이후 ItemReader, ItemWriter 편에서 진행합니다.

 

 

1.2. Batch vs Quartz?

Quartz는 스케줄러의 역할이지, Batch 와 같이 대용량 데이터 배치 처리에 대한 기능을 지원하지 않습니다.

반대로 Batch 역시 Quartz의 다양한 스케줄 기능을 지원하지 않아서 보통은 Quartz + Batch를 조합해서 사용합니다.

정해진 스케줄마다 Quartz가 Spring Batch를 실행하는 구조라고 보시면 됩니다.

 

Batch와 Scheduler(Quartz)는 다른 개념입니다.

 

 

1.3. Batch 사례

1. 일매출 집계
2. ERP 연동

데이터 처리 배치 애플리케이션

  1. 각 서비스의 데이터를 데이터 웨어하우스에 저장할 때 = ETL(Extract Transform Load)
  2. 아마존에서 연관 상품을 추천하는 데이터 모델을 만들 때
  3. 유저 리텐션, 엑티브 상태 등 마케팅에 참고할 데이터 지표를 집계할 때

 

서비스 배치 애플리케이션

  1. 메세지, 이메일, 푸시 등을 발송할 때
  2. 데이터를 마이그레이션할 때
  3. 실패한 트랜잭션을 재처리할 때
  4. 쿠폰, 포인트 등이 만료되었을 때 소진시키는 처리를 할 때
  5. 월말 또는 월초에 특징 데이터를 생성할 때 (e.g. 월별 거래 명세서)

 

 
728x90
728x90

본격적인 글을 작성하기에 앞서 본 시리즈의 게재 목적과 함께 작성 배경을 정리해보고자 합니다.

 

0.1. 배경

현재 업계를 보면 많은 회사들에서 목적 및 상황에 맞게 다양한 언어와 기술을 사용하고 있습니다.

다만 국내의 회사들 중 흔히 서버 혹은 백엔드 개발자로 분류되는 직군에서는 여러가지 사유로 인해 스프링 프레임워크를 많이 사용하고 있습니다.


그리고 데이터를 효과적으로 처리하기 위해 배치 애플리케이션을 구현할 때에도
이러한 배경 및 배경이 갖고 있는 다양한 장점 때문에 스프링 배치는 좋은 옵션으로 선택받습니다.

 

하지만 스프링 배치의 경우 스프링 MVC에 비해 비교적 사용 및 사용자가 적고,
이에 따른 인과 결과로 인해 국내에 양질의 자료가 많이 존재하지 않습니다.

 

개인적으로 생각할 때 2019~2020년 경에 향로 선생님이 블로그에 연재하신 Spring Batch 가이드가 국내에 존재하는 자료들 중 매우 친절하고 자세하게 설명이 되어있다고 생각합니다.

 

하지만 과거에 연재된 만큼 현재 작성 시점 기준과 비교해봤을 때 스프링 배치의 최신 버전이 변경됐고(4.x → 5.x),
현재 저는 Kotlin을 애용하고 있었기에 블로그에 게시된 내용을 바로바로 예제로 작성하는 과정에서 일부 지연이 있었습니다.

 

따라서 이번 기회에 위의 두 가지 사안을 반영해서 향로 선생님께서 작성하신 Spring Batch 가이드를 2024년 기준으로 오마주하는 형태로 내용을 정리하고자 합니다.

 

 

0.2. 작성 방향

내용은 향로 선생님의 글을 기반으로 아래의 내용이 포함될 예정입니다.

  • 원본 글의 요약
  • 버전 업그레이드에 따른 변경사항 적용
  • Kotlin 예제 코드
  • 추가적으로 학습한 내용

원본 글에 대한 인용 및 요약은 아래와 같이 음영 처리된 상자로 표시합니다.

Spring Batch 프로젝트는 Accenture와 Spring Source의 공동 작업으로 2007년에 탄생했습니다.
Accenture는 수년간의 노력으로 그들만의 배치 프레임워크를 만들었고, 그를 통해 얻은 경험을 가지고 있었습니다.
즉, Accenture의 배치 노하우 & 기술력과 Spring 프레임워크가 합쳐져 만들어진 것이 Spring Batch입니다.

 

본 게시글을 효과적으로 읽기 위해서는 각 게시글의 원본(향로 선생님의 글) 내용을 우선적으로 읽어보시는 것을 권장드립니다.

 

 

0.3. 작성 범위

현재 목표로 하는 커버 범위는 게재된 Spring Batch 가이드 내에서 아래 글들을 대상으로 계획하고 있습니다.

  1. Spring Batch 가이드 - 배치 어플리케이션이란?
  2. Spring Batch 가이드 - Batch Job 실행해보기
  3. Spring Batch 가이드 - 메타테이블엿보기
  4. Spring Batch 가이드 - Spring Batch Job Flow
  5. Spring Batch 가이드 - Spring Batch Scope & Job Parameter
  6. Spring Batch 가이드 - Chunk 지향 처리
  7. Spring Batch 가이드 - ItemReader
  8. Spring Batch 가이드 - ItemWriter
  9. Spring Batch 가이드 - ItemProcessor

상황에 따라 Spring Batch 5 마이그레이션 가이드, 추가적인 이용 방법 및 테스트 관련 글도 포함할 예정입니다.

 

0.4. 대상 독자

본 시리즈는 아래의 분들에게 유용할 것으로 예상됩니다.

  • 스프링 배치를 처음 시작하거나 버전 5를 새롭게 도입하시는 분
  • Kotlin 기반으로 스프링 배치 기반의 애플리케이션을 작성하시는 분

 

0.5. 함께 읽어보면 좋은 자료

스프링 배치에 대한 지식을 높이기 위해 본 시리즈와 함께 읽어보면 좋은 문서 및 블로그를 소개합니다.

728x90

+ Recent posts