[퍼옴] [Spring Batch] 스프링 배치 강좌 3. JobExecutionContext를 활용하여 스텝간 데이터 공유하기
2021. 4. 14. 14:39ㆍspring
728x90
반응형
목적
이전 글에서 배운Metadata와JobRepository를 활용해본다.
여러 개의 스텝을 만들고 스텝들이 어떻게JobExecutionContext를 사용하여 데이터를 공유하는지 알아보자.
common-context.xml
Job들이 공통으로 사용하는 빈들을 모아 앞으로 재사용 하도록 하자.
src/main/resource폴더에common-context.xml파일을 생성하자.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:batch="http://www.springframework.org/schema/batch"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xsi:schemaLocation="http://www.springframework.org/schema/batch http://www.springframework.org/schema/batch/spring-batch.xsd
http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-4.3.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">
<bean id="jobRepository"
class="org.springframework.batch.core.repository.support.JobRepositoryFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="transactionManager"
ref="transactionManager" />
</bean>
<bean id="jobLauncher"
class="org.springframework.batch.core.launch.support.SimpleJobLauncher">
<property name="jobRepository" ref="jobRepository" />
</bean>
<bean name="dataSource"
class="org.apache.commons.dbcp2.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="org.h2.Driver" />
<property name="url"
value="jdbc:h2:mem:test;MODE=Oracle" />
<property name="username" value="sa" />
<property name="password" value="" />
</bean>
<bean name="sessionFactory"
class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
</bean>
<bean id="transactionManager"
class="org.springframework.orm.hibernate5.HibernateTransactionManager"
lazy-init="true">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<!-- This tells Spring to activate annotation-driven transactions -->
<tx:annotation-driven
transaction-manager="transactionManager" />
<jdbc:initialize-database
data-source="dataSource">
<jdbc:script
location="org/springframework/batch/core/schema-drop-h2.sql" />
<jdbc:script
location="org/springframework/batch/core/schema-h2.sql" />
</jdbc:initialize-database>
<bean id="jobLauncherTestUtils"
class="org.springframework.batch.test.JobLauncherTestUtils" />
<bean id="jdbcTemplate"
class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource" />
</bean>
</beans>
tasklet
아래tasklet을src/main/java에com.fwantastic.example2패키지에 생성하자. 자세한 설명은 주석을 참조하자.
CounterIncrementerTasklet.java
package com.fwantastic.example2;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.repeat.RepeatStatus;
/**
* JobExecutionContext에서 MY.COUNTER 값을 꺼내오고, 콘솔에 출력 한 다음, 카운터 + 1의 값을 저장한다.
*/
public class CounterIncrementerTasklet implements Tasklet {
private static final String MY_COUNTER_KEY = "MY.COUNTER";
private static final int DEFAULT_VALUE = 0;
private Integer counter;
public RepeatStatus execute(final StepContribution contribution, final ChunkContext chunkContext) throws Exception {
// JobExecutionContext를 access 하는 방법.
final ExecutionContext jobExecutionContext = chunkContext.getStepContext().getStepExecution().getJobExecution()
.getExecutionContext();
// setter를 통해 값이 지정되지 않은 경우 jobExecutionContext에서 값을 불러온다.
// jobExecutionContext에 MY.COUNTER라는 키/페어가 없을 경우 0을 기본 값으로 가진다.
if (counter == null) {
counter = jobExecutionContext.getInt(MY_COUNTER_KEY, DEFAULT_VALUE);
}
// 콘솔에 카운터 값 출력.
System.out.println("카운터: " + counter);
// 카운터 + 1의 값을 jobExecutionContext에 저장.
jobExecutionContext.put(MY_COUNTER_KEY, counter + 1);
return RepeatStatus.FINISHED;
}
public void setCounter(final Integer counter) {
this.counter = counter;
}
}
job
**src/main/resources폴더에com/fwantastic/example2폴더를 만들고 아래의 xml을 추가하자.
job2.xml**
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:batch="http://www.springframework.org/schema/batch"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xsi:schemaLocation="http://www.springframework.org/schema/batch http://www.springframework.org/schema/batch/spring-batch.xsd
http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-4.3.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">
<import resource="classpath:/common-context.xml" />
<job id="myJob2"
xmlns="http://www.springframework.org/schema/batch">
<description>
<![CDATA[
여러 개의 스텝 사용과 JobExecutionContext를 사용해 스텝 간 데이터를 공유하는 예제.
myStep1:
jobExecutionContext에서 MY.COUNT 값을 불러옴.
처음엔 값이 없기 때문에 기본값인 0을 콘솔에 출력하고 1을 jobExecutionContext에 저장.
myStep2:
jobExecutionContext에서 MY.COUNT 값을 불러옴.
1을 콘솔에 출력하고 2을 jobExecutionContext에 저장.
myStep3:
SPEL을 사용 해서 jobExecutionContext에서 MY.COUNT 값을 불러와 NoOpTasklet의 setter를 통해 넘겨줌.
2를 콘솔에 출력하고 3을 jobExecutionContext에 저장.
JobExecutionContext는 Map<String , Object>와 같다고 생각하면 된다.
공유하고자 하는 객체를 맵에 넣으면 Job 안에 있는 모든 컴포넌트들이 사용 할 수 있다.
]]>
</description>
<step id="myStep1" next="myStep2">
<tasklet ref="counterIncrementerTasklet" />
</step>
<step id="myStep2" next="myStep3">
<tasklet ref="counterIncrementerTasklet" />
</step>
<step id="myStep3">
<tasklet ref="counterIncrementerTasklet2" />
</step>
</job>
<bean id="counterIncrementerTasklet"
class="com.fwantastic.example2.CounterIncrementerTasklet"
scope="step" />
<bean id="counterIncrementerTasklet2"
class="com.fwantastic.example2.CounterIncrementerTasklet"
scope="step">
<property name="counter"
value="#{jobExecutionContext['MY.COUNTER']}" />
</bean>
</beans>
테스트
**src/test/java에com.fwantastic.example2**패키지 만들고 아래 junit 클래스를 추가하자.
MyJobTest.java****
package com.fwantastic.example2;
import java.util.concurrent.atomic.AtomicBoolean;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.batch.core.ExitStatus;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.test.JobLauncherTestUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "/com/fwantastic/example2/job2.xml" })
public class MyJobTest {
@Autowired
private JobLauncherTestUtils jobLauncherTestUtils;
// AtomicBoolean을 사용해 한 테스트 케이스에 Job이 여러 번 실행되는 걸 방지한다.
private static AtomicBoolean isLaunched = new AtomicBoolean(false);
private JobExecution jobExecution;
@Before
public void setUp() throws Exception {
if (!isLaunched.getAndSet(true)) {
jobExecution = jobLauncherTestUtils.launchJob();
}
}
@Test
public void testExitCode() {
// Job이 에러 없이 종료되었는지 확인
Assert.assertEquals(ExitStatus.COMPLETED.getExitCode(), jobExecution.getExitStatus().getExitCode());
}
}
실행결과
INFO: Job: [FlowJob: [name=myJob2]] launched with the following parameters: [{random=629547}]
Dec 13, 2019 5:22:00 PM org.springframework.batch.core.job.SimpleStepHandler handleStep
INFO: Executing step: [myStep1]
카운터: 0
Dec 13, 2019 5:22:00 PM org.springframework.batch.core.step.AbstractStep execute
INFO: Step: [myStep1] executed in 14ms
Dec 13, 2019 5:22:00 PM org.springframework.batch.core.job.SimpleStepHandler handleStep
INFO: Executing step: [myStep2]
카운터: 1
Dec 13, 2019 5:22:00 PM org.springframework.batch.core.step.AbstractStep execute
INFO: Step: [myStep2] executed in 4ms
Dec 13, 2019 5:22:00 PM org.springframework.batch.core.job.SimpleStepHandler handleStep
INFO: Executing step: [myStep3]
카운터: 2
Dec 13, 2019 5:22:00 PM org.springframework.batch.core.step.AbstractStep execute
INFO: Step: [myStep3] executed in 34ms
Dec 13, 2019 5:22:00 PM org.springframework.batch.core.launch.support.SimpleJobLauncher$1 run
INFO: Job: [FlowJob: [name=myJob2]] completed with the following parameters: [{random=629547}] and the following status: [COMPLETED] in 112ms
각 스텝이JobExecutionContext를 사용해 데이터를 공유하는 방법을 알아보았다. 다음은CSV (Comma Separated Values)파일을 읽어서디비에 저장하는 방법을 알아보자.
728x90
반응형
'spring' 카테고리의 다른 글
[퍼옴] [Spring Batch] 스프링 배치 강좌 4. CSV 파일 읽어서 JDBC로 디비에 저장하기 (0) | 2021.04.14 |
---|---|
[퍼옴] [Spring Batch] 스프링 배치 강좌 4. CSV 파일 읽어서 JDBC로 디비에 저장하기 (0) | 2021.04.14 |
[퍼옴] [Spring Batch] 스프링 배치 강좌 2. Metadata와 JobRepository 알아보기 (0) | 2021.04.14 |
[퍼옴] [Spring Batch] 스프링 배치 강좌 1. 프로그래밍의 꽃. 스프링 배치 Hello World! (0) | 2021.04.14 |
[퍼옴] [Spring Batch] Intro - 스프링 배치 기본 개념 익히기 (0) | 2021.04.14 |