일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 |
- 멀티모듈
- 회고
- batchframework
- batch framework
- Kotlin
- 문서화
- 스프링배치
- 스타트업
- 스프링
- springboot
- 백엔드
- API
- multimodule
- springrestdocs
- react
- batch
- 서브모듈
- submodule
- 스프링부트
- 스프링부트배치
- springbootbatch
- spring
- 성장
- 프론트엔드
- Today
- Total
노트북을 열고.
[spring boot batch] 4. 배치실행의 영역. Scope 본문
1. Scope 그리고 스프링의 기본 Scope, Singleton
앞선 장에서 설명드리지 않았으나 무척이나 중요한 역할을 하던 컴포넌트가 있습니다.
바로 배치가 실행될 때 Spring Bean을 생성하는 시점을 명시하는 @JobScope와 @StepScope 입니다.
@Bean
@StepScope
public ListItemReader<Member> unPaidMemberReader() {
log.info("********** This is unPaidMemberReader");
List<Member> activeMembers = memberRepository.findByStatusEquals(MemberStatus.ACTIVE);
log.info(" - activeMember SIZE : " + activeMembers.size());
List<Member> unPaidMembers = new ArrayList<>();
for (Member member : activeMembers) {
if(member.isUnpaid()) {
unPaidMembers.add(member);
}
}
log.info(" - unPaidMember SIZE : " + unPaidMembers.size());
return new ListItemReader<>(unPaidMembers);
}
먼저 두 컴포넌트를 이해하기 위해 SpringFramework에서 기본 Scope에 대해 알아봅시다.
Spring은 기본 Scope는 Singleton입니다. 클래스 Instance를 오직 1개만 만들어 참조하는 것이 Singleton입니다.
ApplicationContext context =
new GenericXmlApplicationContext("applicationContext.xml");
BondDao bondDao1 = context.getBean("bondDAO",BondDao.class);
BondDao bondDao2 = context.getBean("bondDAO",BondDao.class);
System.out.println("bondDao 1 : " + bondDao1);
System.out.println("bondDao 2 : " + bondDao2);
System.out.println("bondDao1 == bondDao2 ? " + bondDao1==bondDao2);
위의 코드는 다음의 결과를 보여줍니다.
bondDao1 : spring.dao.BondDAO@2903874
bondDao2 : spring.dao.BondDAO@2903874
bondDao1 == bondDao2 ? true
같은 Bean에서 각각 가져와서 만든 bondDao1 그리고 bondDao2 인스턴스는 서로 같은 값을 참조하고 있으니 동일한 인스턴스입니다. 즉 ApplicationContext로부터 Bean을 가져올 때 마다 인스턴스를 새로 생성하지 않는다는 의미입니다.
단순하게 응용을 해 보겠습니다.
위 애플리케이션의 bondDao 클래스에서 데이터 조회를 하는 select메소드를 호출한다고 합니다. 이 애플리케이션은 여러명이 사용할테니 동시다발적으로 bondDao에 있는 select메소드를 호출할 수 있겠죠. 만약 1000개의 호출요청을 들어올 때 그에 맞춰 1000개의 인스턴스를 생성한다면 그야말로 엄청난 자원낭비가 될 수 있습니다. 어차피 동일한 Bean에서 가져올테니까요.
그래서 SpringFramework은 각각에 Beans들을 기본적으로 Singletone으로 관리를 하고 여러 Thread에서 이를 공유하여 사용할 수 있게 해줍니다. 다시말해 스프링의 기본 Scope는 Singletone 입니다.
2. @JobScope 그리고 @StepScope
@JobScope 그리고 @StepScope는 방금 소개한 스프링의 기본 Scope인 Singleton과는 대치되는 역할입니다. @JobScope 그리고 @StepScope가 붙어 있는 메소드에서는 바로 그 메소드의 실행지점에 해당 @Bean을 Spring Bean으로 생성합니다.
이것은 Bean의 생성시점이 스프링 애플리케이션이 실행되는 시점이 아닌 @JobScope/@StepScope가 명시된 메소드의 실행될 때까지 지연시킨 다는 것(LateBinding)을 의미합니다.
SpringBatch에서는 이렇게 Bean 생성을 지연시킴으로써 얻게되는 장점은 다음과 같습니다.
1. JobParameter를 특정메서드가 실행하는 시점까지 지연시켜 할당시킬 수 있습니다.
애플리케이션이 구동되는 시점이 아니라 비즈니스로직이 구현되는 어디든 JobParameter를 할당함으로 유연한 설계를 가능하게 합니다.
2. 병렬처리에 안전합니다.
앞서 unPaidMemberReader메소드는 Step 구성요소(Itemreader=읽고, ItemProcessor=처리하고, ItemWriter=쓰고)중 데이터를 읽어오는 역할이 정의된 메소드입니다.
이 메소드가 서로 다른 Step으로 부터 동시에 병렬실행이 된다면 서로의 상태를 간섭을 받게 될 수 있습니다. 하지만 앞서 @StepScope를 명시해 놓을으로써 각각의 Step에서 실행될 때 서로의 상태를 침범하지 않고 처리를 완료할 수 있습니다.
@JobScope는 Step선언문에서만 사용이 가능합니다.
@StepScope는 Step을 구성하는 ItemReader, ItemWriter, ItemProcessor에서 사용 가능합니다.
public class unPaidMemberConfig {
private MemberRepository memberRepository;
private JobBuilderFactory jobBuilderFactory;
private StepBuilderFactory stepBuilderFactory;
@Bean
public Job unPaidMemberJob(
) {
log.info("********** This is unPaidMemberJob");
return jobBuilderFactory.get("unPaidMemberJob")
.preventRestart()
.start(this.unPaidMemberJobStep(null)) //1
.build();
}
@Bean
@JobScope //2
public Step unPaidMemberJobStep(@Value("#{jobParameters[requestDate]}") String requestDate) {
log.info("********** This is unPaidMemberJobStep");
log.info("********** This is requestDate of unPaidMemberJobStep : {}", requestDate); //3
return stepBuilderFactory.get("unPaidMemberJobStep")
.<Member, Member> chunk(10)
.reader(unPaidMemberReader())
.processor(this.unPaidMemberProcessor())
.writer(this.unPaidMemberWriter())
.build();
}
...
}
1 | unPiadMemberJobStep의 파라미터로 null을 주었습니다. |
2 |
Bean으로 등록된 메소드에 @JobScope를 선언하였습니다. JobParameter는 다음과 같이 SpEL로 선언해서 사용합니다. |
3 | 해당 메소드에 실행되면서 실제 JobParameter로 할당받는 값을 log를 직접 찍어 확인하고자 합니다. |
그리고 다음과 같이 JobParameter 값을 변경한 이후 애플리케이션을 실행시켜 봅시다.
실행완료이후 consolelog에서 방금전 작성한 로그의 값을 확인해 보면 requestDate의 값이 201903_1로 할당받은 것을 확인하실 수 있습니다.
이렇게 JobParameters는 Step 또는 Step의 구성요소라 할 수 있는 읽고=ItemReader, 처리하고=ItemProcessor, 쓰고=ItemWriter와 같은 SpringBatch 컴포넌트 Bean의 생성 시점에 호출할 수 있습니다.
3. @JobScope(또는 StepScope) 와 JabParameter
지금까지의 내용을 살펴보면 @JobScope(StepScope)가 선언된 @Bean은 오직 그 Bean이 실행되는 시점에 스프링의 Bean으로 생성합니다.
또 그렇게 @JobScope(또는 StepScope)를 명시하면서 까지 스프링 Bean 생성을 지연시킬만 이유는 결국 새로운 JobInstance를 생성할 수 있는 JobParameter의 값을 애플리케이션 실행레벨(구동시점)이 아닌 비즈니스구현부 단계에서 할당되게 함으로써 보다 훨씬 더 유연한 프로세스 설계를 가능하게 하기 때문입니다.
이쯤이면 @JobScope(또는 StepScope)는 결국 JobParameter의 바인딩(LateBinding)을 위해, 더 나아가선 JobInstance의 생성과 서로 밀접하고 연쇄적인 관계를 이해하실 수 있을 것입니다.
부족한 지식으로 미약하게나마 스프링에서의 Scope 그리고 그 Scope안에서 할당되는 JobParameter의 관계에 대해서는 설명드렸습니다. 특히나 SpringBatch에서의 Scope의 개념은 향후 Batch설계의 복잡도에 따라 아주 섬세하게 고려해야할 만큼 매우 중요한 개념이니 꼭 숙지하셨으면 좋겠습니다.
다음편에서는 그동안 언급해왔던 Step의 삼형제, 읽고=ItemReader, 처리하고=ItemProcessor, 쓰고=ItemWriter 를 하나로 퉁친(?) 또 다른 컴포넌트인 Tasklet과 Step의 흐름을 제어할 수 있는 다양한 분기요소에 대해서 설명드리겠습니다.
감사합니다.
'SpringBoot' 카테고리의 다른 글
나의 첫 SpringRestDocs 적용기 part 1 (425) | 2020.05.16 |
---|---|
[spring boot batch] 3. 배치실행의 모든기록. 메타테이블 (797) | 2019.09.02 |
[spring boot batch] 2. 미납회원 배치처리 구현 (876) | 2019.07.30 |
[spring boot batch] 1. 간단한 대용량 배치처리, 스프링부트배치 (785) | 2019.07.30 |
[SubModule] 4. 프로퍼티 설정하기 (432) | 2019.07.30 |