본문 바로가기

(11)

[Spring] NoUniqueBeanDefinitionException 예외 해결방법

이 해결법을 모르는 것은 아니지만 이 현상을 자주 맞이하는게 아니다보니, 늘 쓰는방식만쓰고 지나갔던 기억이 있다. 이번 기회에 정리해두고 똑같은 상황을 맞이했을 때 조금 더 현명(?)하게 대처할 수 있도록 기록을 남긴다. 총 3가지 예외 해결방법이 있다. @Autowired 필드명 매칭 @Qualifier -> @Qualifier끼리 매칭 (Bean 이름 매칭) @Primary 1. @Autowired 필드명 매칭 Autowired는 우선적으로 타입 매칭을 시도하고 타입이 매칭되는 빈이 여러개라면 이름(파라미터 이름)으로 빈 이름을 매핑한다. (필드명 매칭은 타입 매칭 결과가 1개뿐이라면 발생하지 않는다) @Autowired private FooComponent foo; // FooComponent 타입..

[Spring] 옵션 처리

가끔 빈이 없어도 동작해야할 때가 있다 이 때는 여러가지 방법이 있겠지만 @Autowired 의 옵션 처리를 통해서 처리하는 방법이 있다. @Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Autowired { boolean required() default true; } 이 required 속성의 default 값이 true이기 때문에 만약 자동으로 주입되어야할 빈이 없으면 오류가 발생한다. 자동 주입 대상을 옵션..

[Spring Batch] JobExecutionListener, StepExecutionListener

스프링 배치에서 전 처리, 후 처리를 하는 다양한 종류의 Listener 존재. Listener interface 구현 @Annotation 정의 Job 실행 전과 후에 실행할 수 있는 JobExecutionListener Step 실행 전과 후에 실행할 수 있는 StepExecutionListener Spring Web 모듈로 치면, Controller 전 처리, 후 처리를 담당하는 interceptor와 비슷한 개념으로 생각하면 된다. 예제 코드 StepListener Step에 관련된 모든 Listener는 StepListener를 상속받는다. StepExecutionListener SkipListener ItemReadListener ItemProcessListener ItemWriteListen..

[Spring Batch] 기초

Task 기반 배치 vs Chunk 기반 배치 배치를 처리할 수 있는 방법은 크게 2가지 Tasklet을 사용한 Taks 기반 처리 배치 처리 과정이 비교적 쉬운 경우 쉽게 사용 대량 처리를 하는 경우 더 복잡 하나의 큰 덩어리를 여러 덩어리로 나누어 처리하기 부적합 Chunk를 사용한 chunk(덩어리) 기반 처리 ItemReader, ItemProcessor, ItemWriter의 관계 이해 필요 대량 처리를 하는 경우 Tasklet 보다 비교적 쉽게 구현 ex) 10,000개의 데이터 중 1,000개씩 10개의 덩어리로 수행 이를 Tasklet으로 처리하면 10,000개를 한번에 처리하거나 혹은 수동으로 1,000개씩 분할하여 처리 Chunk 기반 처리 흐름 reader에서 null을 return ..

[Spring Batch] 구조

Batch란…? 사전 정의 큰 단위의 작업을 일괄 처리 대부분 처리량이 많고 비 실시간성 처리에 사용 대용량 데이터 계산, 정산, 통계, 데이터베이스, 변환 etc… 컴퓨터 자원을 최대로 활용 컴퓨터 자원 사용이 낮은 시간대에 배치를 처리하거나 배치만 처리하기 위해 사용자가 사용하지 않는 또 다른 컴퓨터 자원을 사용 사용자 상호작용으로 실행되기 보단, 스케줄러 같은 시스템에 의해 실행되는 대상 ex) 매일 오전 10시에 배치 실행, 매주 월요일 12시 마다 실행 etc… crontab, jenkins… Spring Batch 란…? 배치 처리를 하기 위한 Spring Framework 기반 기술 Spring의 주요 기능 사용 가능 (DI, AOP, PSA) 스프링 배치의 실행 단위: Job, Step 비..

코틀린 스프링에서의 이벤트 처리 (with 유스콘)

들어가기에 앞서서... | 해당 글은 2021 유스콘에서 발표한 자바 스프링에서의 이벤트 처리 세션에서 진행된 내용을 코틀린으로 따라해보며 정리한 글입니다. 목적 | 스프링에서 이벤트를 왜 써야하고 어떻게 써야하는지 실습해본다. 전체적인 코드는 깃헙 저장소를 참고하면 된다. `UserService쪽에 의존하는 다양한 의존성들을 어떻게 해결하면 좋을지?`를 계속 생각해보자! Spring은 Bean을 관리하기 위해서 ApplicationContext를 기본으로 가져간다. 이번에 다루게 될 Event도 Spring Bean과 똑같이 Context를 관리해주는데 이벤트 또한 ApplicationContext에서 Event를 관리하고 있다. 따라서 Event도 특별한 Bean을 주입받고 실행해주고 있다. Step..

Spring Framework Module

[Spring] NoUniqueBeanDefinitionException 예외 해결방법

728x90

이 해결법을 모르는 것은 아니지만 이 현상을 자주 맞이하는게 아니다보니, 늘 쓰는방식만쓰고 지나갔던 기억이 있다.
이번 기회에 정리해두고 똑같은 상황을 맞이했을 때 조금 더 현명(?)하게 대처할 수 있도록 기록을 남긴다.

총 3가지 예외 해결방법이 있다.

  1. @Autowired 필드명 매칭
  2. @Qualifier -> @Qualifier끼리 매칭 (Bean 이름 매칭)
  3. @Primary

1. @Autowired 필드명 매칭

Autowired는 우선적으로 타입 매칭을 시도하고 타입이 매칭되는 빈이 여러개라면 이름(파라미터 이름)으로 빈 이름을 매핑한다.
(필드명 매칭은 타입 매칭 결과가 1개뿐이라면 발생하지 않는다)

@Autowired
private FooComponent foo; // FooComponent 타입의 빈이 2개 이상이면, 빈 이름이 foo 빈을 매핑!
private final FooComponent foo;

// 주 생성자 or 롬복으로 만든 생성자 주입 방식도 동일하다
public FooClass(FooComponent foo) {
    this.foo = foo
}

2. @Qualifier

@Qualifier 라는 추가 구분자를 붙여줌으로써 빈을 구분짓는 방법이다. 
(실제로 빈 이름을 변경하는 것은 아니다)

만약 @Qualifier 주입을 할 때, value의 값에 매칭되는 Bean을 찾지 못하면 어떻게 될까? 그러면 value로 입력한 값과 일치하는 스프링 빈을 추가로 찾는다(@Qualifier는 추가적인 식별 방식이지 실제로 빈 이름을 변경하는 작업은 아니라고 언급했었다).

만약 그럼에도 일치하는 Bean을 찾지 못한다면 NoSuchBeanDefinitionException 예외가 발생한다.

하지만 @Qualifier는 @Qualifier를 찾는 용도로만 쓰는 것이 명확하고 좋다. 

3. @Primary 사용 

우선순위를 정하는 방법이다.
@Autowired 시에 여러 빈이 매칭되면 @Primary가 우선권을 가진다.
(@Primary 자체가 빈을 생성하기위한 애너테이션은 아니고, 빈 생성을 위한 애너테이션을 동일하게 붙여줘야한다)

@Component
@Primary
public class MainFooComponent implements FooComponent { } // 우선권을 가진다

@Component
public class NormalFooComponent implements FooComponent { }

우선순위

만약 @Primary와 @Qualifier가 둘 다 있다면 어느 것이 먼저 동작하는가?
@Qualifier가 우선 순위가 높다!

(영한님 왈) 자세한 것이 우선권을 가져간다! 

끝맺음

사실 같은 타입에 대해서 빈이 여러개 존재하고, 이 중 하나의 빈만 가지고와서 사용한 케이스가 생각보다 별로 없었던 것 같다. 🤔
이런 케이스에 대해서 Primary를 쓸 것인가 @Qualifier를 쓸 것인가에 대한 고민이 생길 수 있을 것 같다. 
확실하게 Main인 빈이 확실한 경우라면 Primary도 충분히 고려해볼 수 있겠지만 아니라면 @Qualifier를 써야하지 않을까?

728x90
Spring Framework Module/SpringBoot

[Spring] 옵션 처리

728x90

가끔 빈이 없어도 동작해야할 때가 있다
이 때는 여러가지 방법이 있겠지만 @Autowired 의 옵션 처리를 통해서 처리하는 방법이 있다. 

@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
    boolean required() default true;
}

이 required 속성의 default 값이 true이기 때문에 만약 자동으로 주입되어야할 빈이 없으면 오류가 발생한다. 

자동 주입 대상을 옵션으로 처리하는 방법은 아래와 같다.

  1. @Autowired의 required 속성을 false로 바꾼다  ex. @Autowired(required = false) 
  2. org.springframework.lang.@Nullable 자동으로
  3. Optioanl<> 자동 주입할 대상이 없으면 Optional.empty가 된다.

1번의 케이스는 알고있었는데 새롭게 안 사실은 만약 이 옵션에 의해 빈이 주입되지 않았다면 그냥 null이라고 생각했는데, 추가적으로 해당 빈의 메서드를 호출하지 않는다고한다.

2번은 정말로 해당 빈이 null이 된다. ->  메서드 호출이 될테니 NPE가 발생할 확률이 있어 보인다. 🤔

3번의 경우에는 Optional로 결과를 감싼 것이니 사실 뭐 놀랄 것도 없다.

2번, 3번의 경우에 대해서는 null에 대한 예외처리가 필요하고 1번의 경우에는 null 처리 없이도 커버가 된다? 정도의 차이가 있을 듯하다. 
(그런데 메서드 호출을 아예 안해버리면 의미가있나....? 🙄👀)


'에잇... 테스트 클래스도 아니고 요즘 누가 촌스럽게 @Autowired를 써!!' 라고 할 수 있지만 @NullableOptional은 스프링 전반에 걸쳐서 지원된다. 즉 생성자 주입 방식에서도 특정 필드만 사용할 수 있다.

728x90

'Spring Framework Module > SpringBoot' 카테고리의 다른 글

Spring Rest Docs 소개 및 사용법  (0) 2020.12.18
Spring Framework Module/Spring Batch

[Spring Batch] JobExecutionListener, StepExecutionListener

728x90
  • 스프링 배치에서 전 처리, 후 처리를 하는 다양한 종류의 Listener 존재.
    • Listener interface 구현
    • @Annotation 정의
  • Job 실행 전과 후에 실행할 수 있는 JobExecutionListener
  • Step 실행 전과 후에 실행할 수 있는 StepExecutionListener

Spring Web 모듈로 치면, Controller 전 처리, 후 처리를 담당하는 interceptor와 비슷한 개념으로 생각하면 된다.

 

예제 코드

StepListener

Step에 관련된 모든 Listener는 StepListener를 상속받는다.

  • StepExecutionListener

  • SkipListener

  • ItemReadListener

  • ItemProcessListener

  • ItemWriteListener

  • ChunkListener

각 인터페이스 설명

  • SkipListener
    • Skip은 Step의 예외처리 방법 중 하나이다.
    • onSkipInRead: @OnSkipInRead
      • ItemReader에서 Skip이 발생한 경우 호출
    • onSkipInWrite: @OnSkipInWrite
      • ItemWriter에서 Skip이 발생한 경우 호출
    • onSkipInProcess: @OnSkipProcess
      • ItemProcessor에서 Skip이 발생한 경우 호출
  • ItemReadListener
    • beforeRead: @BeforeRead
      • ItemReader.read() 메서드 호출 전 호출
    • afterRead: @AfterRead
      • ItemReader.read() 메서드 호출 후 호출
    • onReadError: @OnReadError
      • ItemReader.read() 메서드에서 에러 발생 시 호출
  • ItemWriterListener
    • beforeWrite: @BeforeRead
      • ItemWriter.write() 메서드 호출 전 호출
    • afterWrite: @AfterRead
      • ItemWriter.write() 메서드 호출 후 호출
    • onWriteError: @OnWriteError
      • ItemWriter.write() 메서드에서 에러 발생 시 호출
  • ItemProcessListener
    • beforeProcess: @BeforeProcess
      • ItemProcess.process() 메서드 호출 전 호출
    • afterProcess: @AfterProcess
      • ItemProcess.process() 메서드 호출 후 호출
    • onProcessError: @OnProcessError
      • ItemProcess.process() 메서드에서 에러 발생 시 호출
  • ChunkListener
    • chunk는 중요한 개념이다.
      • chunk 설정 시, 필수로 chunkSize를 지정했는데, 이 chunkSize는 총 처리될 아이템 갯수가 아니라, 전체 아이템 중에 몇 개의 아이템을 size 만큼씩 묶을 것인지 크기 설정이다.
    • beforeChunk: @BeforeChunk
      • chunk 실행 전 호출
    • afterChunk: @AfterChunk
      • chunk 실행 후 호출
    • afterChunkError: @AfterChunkError
      • chunk 실행 중 에러 발생 시 호출
728x90

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

[Spring Batch] 기초  (0) 2023.02.18
[Spring Batch] 구조  (0) 2023.02.18
Spring Framework Module/Spring Batch

[Spring Batch] 기초

728x90

Task 기반 배치 vs Chunk 기반 배치

  • 배치를 처리할 수 있는 방법은 크게 2가지
  • Tasklet을 사용한 Taks 기반 처리
    • 배치 처리 과정이 비교적 쉬운 경우 쉽게 사용
    • 대량 처리를 하는 경우 더 복잡
    • 하나의 큰 덩어리를 여러 덩어리로 나누어 처리하기 부적합
  • Chunk를 사용한 chunk(덩어리) 기반 처리
    • ItemReader, ItemProcessor, ItemWriter의 관계 이해 필요
    • 대량 처리를 하는 경우 Tasklet 보다 비교적 쉽게 구현
    • ex) 10,000개의 데이터 중 1,000개씩 10개의 덩어리로 수행
      • 이를 Tasklet으로 처리하면 10,000개를 한번에 처리하거나 혹은 수동으로 1,000개씩 분할하여 처리

Chunk 기반 처리 흐름
  • reader에서 null을 return 할 때까지 Step은 반복
  • <INPUT, OUTPUT> chunk(int)
    • reader에서 INPUT을 return
    • processor에서 INPUT을 받아 processing 후 OUTPUT을 return
      • INPUT, OUTPUT은 같은 타입일 수도 있음.
    • write에서 List<OUTPUT>을 받아 write 함.

JobParameters 이해

  • 배치 실행에 필요한 값을 parameter를 통해 외부에서 주입
  • JobParameters는 외부에서 주입된 parameter를 관리하는 객체
  • parameter를 JobParameters Spring EL(Expression Language)로 접근
    • String parameter = jobParameters.getString(key, defaultValue);
    • @Value("#{jobParameters[key]}")

@JobScope vs @StepScope

  • @xxxScope는 어떤 시점에 bean을 생성/소멸 시킬 지 bean의 lifecycle을 설정함.
  • @JobScope는 job 실행 시점에 생성/소멸
    • Step에 선언한다.
  • @StepScope는 step 실행 시점에 생성/소멸
    • Tasklet, Chunk(ItemReader, ItemProcessor, ItemWriter)에 선언
  • Spring의 @Scope과 같은 개념
    • @Scope("job") == @JobScope
    • @Scope("step") == @StepScope
  • Job과 Step 라이프사이클에 의해 생성되기 때문에 Thread safe하게 작동한다.
  • @Value("#{jobParameters[key]}")를 사용하기 위해

ItemReader

  • 배치 대상 데이터를 읽기 위한 설정
    • 파일, DB, 네트워크 등에서 읽기 위함.
  • (Chunk 기반) Step에서 ItemReader는 필수
  • 기본 제공되는 ItemReader 구현체
    • ex) file, jdbc, jpa, hibernate, kafka, etc..
  • ItemReader 구현체가 없으면 직접 개발
  • ItemStream은 ExecutionContext로 read, write 정보를 저장
  • CustomItemReader

CSV 파일 데이터 읽기

  • FlatFileItemReader 클래스로 파일에 저장된 데이터를 읽어 객체에 매핑

JDBC 데이터 읽기 - Cursor

  • Cursor 기반 조회
    • 배치 처리가 완료될 때 까지 DB Connection이 연결됌
    • DB Connection 빈도가 낮아 성능이 좋은 반면, 긴 Connection 유지 시간이 필요하다.
    • 하나의 Connection에서 처리되기 때문에, Thread Safe하지 않음
    • 모든 결과를 메모리에 할당하기 때문에, 더 많은 메모리를 사용
  • Paging 기반 조회
    • 페이징 단위로 DB Connection을 연결
    • DB Connection 빈도가 높아 비교적 성능이 낮은 반면, 짧은 Connection 유지 시간 필요
    • 매번 Connection을 하기 때문에 Thread Safe
    • 페이징 단위의 결과만 메모리에 할당하기 때문에, 비교정 더 적은 메모리를 사용한다.

ItemWriter

  • ItemWriter는 마지막으로 배치 처리 대상 데이터를 어떻게 처리하는지를 결정
  • (Chunk 기반) Step에서 ItemWriter는 필수
  • 사용용도 ex) ItemReader에서 읽은 데이터를 DB에 저장, API로 서버에 요청, 파일에 데이터를 write etc…
  • 항상 write를 하지 않음.
    • 데이터를 최종적으로 마무리하는 것이 ItemWriter

CSV 파일 데이터 쓰기

  • FlatFileItemWriter는 데이터가 매핑된 객체를 파일로 write 한다.

JDBC 데이터 쓰기

  • JdbBatchItemWriter는 jdbc를 사용해 DB에 write
  • JdbcBatchItemWriter는 bulk insert/update/delete처리
  • 단건 처리가 아니기 때문에 비교적 높은 성능

JPA 데이터 쓰기

  • JpaItemWriter는 JPA Entity 기반으로 데이터를 DB에 write
  • Entity를 하나씩 EntityManager.persist 또는 EntityManager.merge로 insert (bulk로 업데이트하기에는 좋지 않아 보임)

ItemProcessor

  • ItemReader에서 읽은 데이터를 가공 또는 Filtering
  • Step의 ItemProcessor는 optional
  • ItemProcessor는 필수는 아니지만, 책임 분리를 분리하기 위해 사용
  • ItemProcessor는 I(input)를 O(output)로 변환하거나
  • ItemWriter의 실행 여부를 판단할 수 있도록 filtering 역할을 한다.
    • ItemWriter는 not null만 처리

 

728x90
Spring Framework Module/Spring Batch

[Spring Batch] 구조

728x90

Batch란…?

사전 정의

네이버 영어사전 검색 결과

  • 큰 단위의 작업을 일괄 처리
  • 대부분 처리량이 많고 비 실시간성 처리에 사용
    • 대용량 데이터 계산, 정산, 통계, 데이터베이스, 변환 etc…
  • 컴퓨터 자원을 최대로 활용
    • 컴퓨터 자원 사용이 낮은 시간대에 배치를 처리하거나
    • 배치만 처리하기 위해 사용자가 사용하지 않는 또 다른 컴퓨터 자원을 사용
  • 사용자 상호작용으로 실행되기 보단, 스케줄러 같은 시스템에 의해 실행되는 대상
    • ex) 매일 오전 10시에 배치 실행, 매주 월요일 12시 마다 실행 etc…
    • crontab, jenkins…

Spring Batch 란…?

  • 배치 처리를 하기 위한 Spring Framework 기반 기술
    • Spring의 주요 기능 사용 가능 (DI, AOP, PSA)
  • 스프링 배치의 실행 단위: Job, Step
  • 비교적 간단한 작업(Tasklet) 단위 처리와 대량 묶음(Chunk) 단위 처리

배치 프로그램 실행 시, 주의 사항

  • 별 다름 옵션을 주지 않고 실행하게 되면, 모든 Batch Job이 다 실행되게 된다.
  • 보통은 모든 Job을 한 번에 실행하지 않는다.
    • 따라서 어떤 Job을 실행할 것인지 설정할 수 있다.
    • 실행하려는 특정 Job Name만 설정 값으로 넣어주면 된다.

ex) helloJob Job만 실행하기 위해서는 아래의 설정을 필요로한다.

 

설정한 뒤에 실행 결과

 

별다른 설정이 없는 경우 다른 Job이 실행되는 것을 방지하는 옵션

application.yml

# 모든 배치가 실행되지 않도록 설정 추가!
spring:
  batch:
    job:
      names: ${job.name:NONE}

위와 같이 설정하면 job.name 파라미터로 Job을 실행할 수 있게되고, 추가적으로 설정 값이 없으면 아무런 배치가 실행되지 않도록 막을 수도 있다.

(설정 파라미터 값 spring.batch.job.names에 Job Name을 설정할 필요 없이, job.name 프로퍼티에 Job Name을 추가하면 동일하게 작동한다!)

맘시터 서비스는…?

config 설정에서 jobNames가 없는 경우 jobNames가 NONE 으로 설정되도록 config를 설정함.

 

Spring Batch 기본 구조

실제로 예제를 실행해보면 Bean을 생성만했을 뿐인데, 자동으로 Job이 실행 된다.

Spring Batch는 Job time에 Bean이 생성되면 JobLauncher 객체에 의해서 Job을 수행한다.
JobLauncher --(실행) → Job --(실행) → Step 구조가 된다.

JobRepository는 DB or memory에 Spring Batch가 실행될 수 있도록 배치의 메타데이터를 관리하는 클래스
스프링 배치의 전반적인 데이터를 관리하는 클래스 정도로 이해하면된다.

Job

  • Job은 JobLauncher에 의해 실행
  • Job은 배치의 실행 단위를 의미
  • Job은 N개의 Step을 실행할 수 있으며, 흐름(Flow)을 관리할 수 있음.
    • ex) A Step 실행 후, 조건에 따라 B Step 또는 C Step을 실행하도록 설정할 수 있음.

Step

  • Job의 세부 실행 단위이며, Job은 최소 1개 이상의 Step으로 구성
  • Step의 실행 단위는 크게 2가지로 나뉨
    • Task 기반: 하나의 작업 기반으로 실행
    • Chunk 기반: 하나의 큰 덩어리를 n개씩 나눠서 실행 (chunk = 덩어리 라는 뜻을 가짐
      • ex) 처리 대상이 한 번에 실행해도 컴퓨터 자원에 문제가 없으면 Tasklet을 사용
      • ex) 10,000명의 회원을 1,000명 씩 나눠서 처리한다면 Chunk
  • ItemReader : 배치 처리 대상 객체를 읽어 ItemProcessor 또는 ItemWriter에게 전달
  • ItemProcessor: input 객체를 output 객체로 filtering 또는 processing해 ItemWriter에게 전달
    • ex) ItemReader에서 읽은 데이터를 수정 또는 ItemWriter 대상인지 filtering 한다.
    • ItemProcessor는 optional (있을 수도 있고 없을 수도 있음)
    • ItemProcessor가 하는 일은 ItemReader 또는 ItemWriter가 대신할 수 있다.
  • ItemWriter: 배치 처리 대상 객체를 최종 처리
    • ex) DB Update를 하거나, 처리 대상 사용자에게 알림을 보낸다.

스프링 배치 테이블 구조

Spring Batch Table 구조

 
  • 배치 실행을 위한 메타 데이터가 저장되는 테이블
  • BATCH_JOB_INSTANCE
    • Job이 실행되며 생성되는 최상위 계층의 테이블
    • job_namejob_key를 기준으로 하나의 row가 생성되며, 같은 job_name과 job_key가 저장될 수 없다.
    • job_key는 BATCH_JOB_EXECUTION_PARAMS에 저장되는 Parameter를 나열해 암호화해 저장
  • BATCH_JOB_EXECUTION
    • Job이 실행되는 동안 시작/종료 시간, Job 상태 등을 관리
  • BATCH_JOB_EXECUTION_PARAMS
    • Job을 실행하기 위해 주입된 parameter 정보 저장
  • BATCH_JOB_EXECUTION_CONTEXT
    • Job이 실행되며 공유해야할 데이터를 직렬화해 저장
  • BATCH_STEP_EXECUTION
    • Step이 실행되는 동안 필요한 데이터 또는 실행된 결과 저장
  • BATCH_STEP_EXECUTION_CONTEXT
    • Step이 실행되며 공유해야할 데이터를 직렬화해 저장
  • External Library 하단에 org.springframework/batch/core 하위에 schema 스크립트 파일 존재
  • 스프링 배치를 실행하고 관리하기 위한 테이블
  • schema.sql 설정
    • schema-**.sql의 실행 구분
      • DB 종류별로 script가 구분됌.
    • spring.batch.initialize-schma config로 구분(properties 기준)
    • ALWAYS, EMBEDDED, NEVER로 구분
      • ALWAYS: 항상 실행
      • EMBEDDED: 내장 DB일 때만 실행 (default)
      • NEVER: 항상 실행 안함 (운영환경)

 
 

Job, JobInstance, JobExecution 이해

 
  • JobInstance: BATCH_JOB_INSTANCE 테이블과 매핑
  • JobExecution: BATCH_JOB_EXECUTION 테이블과 매핑
  • JobParameters: BATCH_JOB_EXECUTION_PARAMS 테이블과 매핑
  • ExecutionContext: BATCH_JOB_EXECUTION_CONTEXT 테이블과 매핑
  • JobInstance의 생성 기준은 JobParameters 중복 여부에 따라 생성된다.
  • 다른 parameter로 Job이 실행되면 JobInstance가 생성된다.
  • 같은 parameter로 Job이 실행되면, 이미 생성된 JobInstance가 실행된다.
  • JobExecution은 (재실행 여부와 상관없이) 항상 새롭게 생성된다.
  • ex)
    • 처음 Job 실행 시, data parameter가 2022-01-01로 실행 됐다면, 1번 JobInstance가 생성
    • 다음 Job 실행 시, data parameter가 2022-01-02로 실행 됐다면, 2번 JobInstance가 생성
    • 다음 Job 실행 시, data parameter가 2022-01-02로 실행 됐다면, 2번 JobInstance가 재 실행
      • 이때 Job이 재실행 대상이 아닌 경우 에러가 발생
  • Parameter가 없는 Job을 항상 새로운 JobInstance가 실행되도록 RunIncrementer가 제공
    • RunIdIncrementer는 항상 다른 run.id를 Parameter로 설정

Step, StepExecution 이해

  • StepExecution: BATCH_STEP_EXECUTION 테이블과 매핑
  • ExecutionContext: BATCH_STEP_EXECUTION_CONTEXT 테이블과 매칭
    • ExecutionContext 클래스는 Job과 Step에 Context를 모두 맵핑할 수 있는 클래스이다.
  • Job 내에서 공유할 수 있는 BATCH_JOB_EXECUTION_CONTEXT
  • 하나의 Step에서 공유할 수 있는 BATCH_STEP_EXECUTION_CONTEXT
  • 예제 참고
    • 참고로 mysql DB에서 batch 실습을 진행할 때는 schema.mysql.sql의 내용 그대로 DB에 insert 하면된다. 
728x90
Spring Framework Module

코틀린 스프링에서의 이벤트 처리 (with 유스콘)

728x90

들어가기에 앞서서...

| 해당 글은 2021 유스콘에서 발표한 자바 스프링에서의 이벤트 처리 세션에서 진행된 내용을 코틀린으로 따라해보며 정리한 글입니다.

목적

| 스프링에서 이벤트를 왜 써야하고 어떻게 써야하는지 실습해본다.

전체적인 코드는 깃헙 저장소를 참고하면 된다.

이 코드를 이벤트 형식으로 변경하여 결합이 느슨해지도록 하는 것이 목표이다!

`UserService쪽에 의존하는 다양한 의존성들을 어떻게 해결하면 좋을지?`를 계속 생각해보자!

Spring은 Bean을 관리하기 위해서 ApplicationContext를 기본으로 가져간다.
이번에 다루게 될 Event도 Spring Bean과 똑같이 Context를 관리해주는데

AbstractApplicationContext class 내부 코드

이벤트 또한 ApplicationContext에서 Event를 관리하고 있다.
따라서 Event도 특별한 Bean을 주입받고 실행해주고 있다.

다이어 그램으로 본 모습
EventPublisher

Step1: 상속 기반의 이벤트로 변경 해본다.

Event 객체는 ApplicationEvent를 EventListener는 ApplicationListener 인터페이스를 상속 및 구현하여 변경한다.

일부 코드만 기재하겠다.

UserAdminEvent 객체

class UserAdminEvent(
    source: Any,
    val userName: String
) : ApplicationEvent(source)

UserAdmin

@Component
class AdminEventListener : ApplicationListener<UserAdminEvent> {
    override fun onApplicationEvent(event: UserAdminEvent) {
        logger.info("어드민 서비스: ${event.userName} 님이 가입했습니다.")
    }

    companion object {
        private val logger = LoggerFactory.getLogger(AdminEventListener::class.java)
    }
}

비교하기 쉬우라고 이렇게 메서드를 매번 호출했다. 하나의 메서드로 묶어서 한 번만 호출하도록 캡슐화하는 것을 추천한다!

Step1을 완료했다 👏👏👏👏

이전 코드와 큰 차이점은 기존의 dependency가 있었던 Service들이 많이 사라졌다. 
이런 부분들을 Event를 활용해서 Coupling(= 결합도)를 많이 줄일 수 있다는 것이 큰 장점이다.

위 방식의 문제점은 무엇일까? 
매번 상속 혹은 구현하고 Component를 등록해주는 절차가 필요하다. 
스프링 4.2 부터 ApplicationEvent 를 상속 받아 객체를 생성하지 않아도 이벤트 객체 처럼 활용할 수 있다.  
ApplicationListenerMethodAdapter 객체에서 어노테이션을 찾아서 실행한다!

위와 같이 Event 객체와 EventListener 객체는 모두 상속(or 구현)을 하고있다! 
이러면 Event에 종속적인 것이 아닌가? 라는 의문을 품어볼 수도 있다. 
이런 문제점을 스프링 4.2부터는 애너테이션을 통해서 개선했다. 

애너테이션 기반으로 변경되면서 Event 객체는 별도의 Bean으로 관리되지 않고 순수 자바 객체를 사용할 수 있다. 
EventListener의 메서드에 @EventListener 애너테이션을 붙임으로써 Event를 읽어들여 응답할 수 있다. 

예제코드는 아래와 같다. 

@Component
class UserSenderEventListener {
    @EventListener
    fun onApplicationEvent(event: UserSenderEvent) {
        logger.info("환영 이메일 발송 성공 : ${event.email}")
    }

    @EventListener
    fun handleSMS(event: UserSenderEvent) {
        logger.info("환영 SMS 발송 성공: ${event.phoneNumber}")
    }

    companion object {
        private val logger = LoggerFactory.getLogger(UserSenderEventListener::class.java)
    }
}

또 다른 차이점이 있다면 이전에는 하나의 클래스당 하나의 EventListener를 만들 수 밖에 없었지만, 애너테이션 방식에서는 하나의 클래스에서 여러 EventListener를 만들 수 있다. 

또한 상속을 받는 것이 아니기 때문에 테스트 코드를 작성했다면, 테스트 코드 또한 변경이 필요하다! (예제코드 참고!)

여기서 끝이라고 생각할 수 있지만, 한 가지 간과한 부분이 있다. 
우리는 UserService 클래스 상단에 @Transactional 애너테이션을 붙였기 때문에, 메서드가 온전히 다 실행되거나 혹은 하나도 실행되지 않거나 하는 즉, 원자성을 보장한다고 생각할 것이다. (트랜잭션을 조금이라도 공부해봤다면 정말 심각한 일이라는 것을 알 것이다! ex. 은행 송금 상황 시 통장에서 돈만 인출되고 상대방에게 입금은 안 된 경우) 

이 문제는 EventListener를 (애너테이션으로)등록할 때, @EventListener가 아닌 @TransactionalEventListener으로 바꿔서 사용하면 된다. @TransactionalEventListener 의 phase 속성을 상황에 맞게 알맞게 설정해야한다. (default 설정은 TransactionPhase.AFTER_COMMIT 이고, 그 외에는 AFTER_ROLLBACK, AFTER_COMPLETION, BEFORE_COMMIT 등 총 4가지 속성이 있다)

Step2를 더 넘어서서 도메인 이벤트를 활용해보는 방법도 있다.
해당 개념에 대해서는 예제 코드의 INDEX.md 의 내용을 참고해서 스스로 학습해보면 좋을 것 같다.

Evenet의 단점은 Listener와 Event Object를 관리해야한다는 것이다. 
하지만 그 만큼 역할도 명확해지고 UserService에 대한 의존성, 결합성들이 해소될 수 있다는 장점이 크다. 
이런 부분들을 잘 고려해서 Event를 선택해서 사용하도록 하자! 

728x90