[Spring Batch] 스프링 배치 강좌 1. 프로그래밍의 꽃. 스프링 배치 Hello World!

스프링 배치 강좌 목록: https://www.fwantastic.com/p/spring-batch.html



시작하기에 앞서 스프링 배치 Intro를 읽지 않았다면 먼저 보고 오자.





새로운 것을 배울때 Hello World를 빼먹을수 없다. 초 심플한 배치 job을 만들어보자.






프로젝트 만들기

STS (Spring Tools Suite) 기준으로 작성되었다.
STS 다운로드: https://spring.io/tools


File -> New -> Maven Project -> Create a simple project (skip archetype selection) 클릭.


다음과 같이 입력.
Group Id: com.fwantastic
Artifact Id: spring-batch-example
Version: 0.0.1-SNAPSHOT
Packaging: jar
Name: Spring Batch example
Description: Spring Batch example


앞으로 매번 새로운 프로젝트를 만들지 않고 하나에 프로젝트에 예제들을 계속 추가하자.


pom.xml

pom.xml은 디펜던시를 관리하는 설정 파일이다. 스프링 개발자라면 메이븐을 알아야한다. 만약 모른다면 구글링 해보자. 

프로젝트 root level에 pom.xml 파일을 생성하자. src 폴더와 같은 레벨에 있어야 한다.

이번 예제에선 필요 없는 디펜던시들도 일부러 넣었다. 한 프로젝트에 여러 예제를 넣을 거라서 미리 넣었으니 그냥 이런 것들을 쓰게 될 거구나 라고만 알고 있자. 디펜던시마다 주석을 달았으니 한 번씩 읽어보자.

pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.fwantastic</groupId>
    <artifactId>spring-batch-example</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>Spring Batch example</name>
    <description>Spring Batch example</description>

    <!-- 버전 관리 섹션 -->
    <properties>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <spring.batch.version>4.2.1.RELEASE</spring.batch.version>
        <spring.version>5.2.2.RELEASE</spring.version>
        <hibernate.version>5.4.10.Final</hibernate.version>
        <apache.dbcp2.version>2.7.0</apache.dbcp2.version>
        <h2.version>1.4.200</h2.version>
        <junit.version>4.12</junit.version>
    </properties>

    <dependencies>
        <!-- 스프링 배치의 코어를 담당하는 파트들을 가지고 있다. -->
        <dependency>
            <groupId>org.springframework.batch</groupId>
            <artifactId>spring-batch-core</artifactId>
            <version>${spring.batch.version}</version>
        </dependency>

        <!-- DB table과 Java간의 매핑을 가능케 해준다. -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-orm</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <!-- spring-orm은 인터페이스이고 hibernate은 구현체. -->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-core</artifactId>
            <version>${hibernate.version}</version>
        </dependency>

        <!-- 디비 관련 유틸리티. -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-dbcp2</artifactId>
            <version>${apache.dbcp2.version}</version>
        </dependency>

        <!-- getter/setter를 자동으로 만들어준다. -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.10</version>
            <scope>provided</scope>
        </dependency>


        <!-- ########## 테스트 관련 디펜던시들 ########## -->
        <!-- <scope>test</scope>은 테스트 코드에서만 사용 가능하다. -->

        <!-- 스프링 배치 어플리케이션 테스트 유틸리티. -->
        <dependency>
            <groupId>org.springframework.batch</groupId>
            <artifactId>spring-batch-test</artifactId>
            <version>${spring.batch.version}</version>
            <scope>test</scope>
        </dependency>

        <!-- 단위 테스트 -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>${junit.version}</version>
            <scope>test</scope>
        </dependency>

        <!-- 메모리상에서 돌아가는 디비. 단위 테스트에 많이 쓰임. -->
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <version>${h2.version}</version>
            <scope>test</scope>
        </dependency>

    </dependencies>
</project>




tasklet

Intro에서 step을 간단히 보았다. 설명하지 않은 tasklet 이란게 나오는데, step 정의하는 법을 보자.

<step>
    <tasklet>
        <chunk reader="itemReader" processor="itemProcessor"
            writer="itemWriter" commit-interval="5" />
    </tasklet>
</step>

이처럼 chunk (reader + processor + writer) 를 사용하면 읽기 -> 가공하기 -> 쓰기의 반복인데 chunk 없이 한 번만 실행하도록 하는 것도 가능하다. 



src/main/javacom.fwantastic.example1 패키지를 만들고 아래 클래스를 추가하자.

HelloWorldTasklet.java
package com.fwantastic.example1;

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.repeat.RepeatStatus;

/**
 * 두 개의 메세지만 출력하고 끝나는 단순한 tasklet.
 */
public class HelloWorldTasklet implements Tasklet {

    public RepeatStatus execute(final StepContribution contribution, final ChunkContext chunkContext) throws Exception {
        System.out.println("작업 시작...");
        // 원하는 작업을 여기에서 할 수 있다.
        System.out.println("작업 완료!");
        return null;
    }

}



execute 메소드가 null을 반환하면 RepeatStatus.FINISHED 를 반환하는 것과 같은 의미를 가진다. 한마디로 반복 없이 한 번만 실행된다는 소리다.

HelloWorldTasklettasklet 인터페이스의 구현체다. step이 실행되면 배치 프레임워크가 알아서 execute 메소드를 찾아 실행한다. 그럼  작업 시작... 과  작업 완료!  메세지를 출력하고 끝난다.




job

다음으로 job을 작성해보자. src/main/resources 폴더에 com/fwantastic/example1 폴더를 만들고 아래의 xml 파일을 추가하자.

hello_world_job.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">

    <job id="helloWorldJob"
        xmlns="http://www.springframework.org/schema/batch">
        <description>
        첫 스프링 배치 어플리케이션.
        콘솔에 로그를 찍고 끝나는 간단한 스텝 예제.
        </description>

        <step id="helloWorldStep">
            <tasklet ref="helloWorldTasklet" />
        </step>
    </job>


    <!-- Tasklet -->
    <!-- scope은 빈을 어떻게 생성할지 결정한다. 세개의 scope 종류를 보자.
        - step: 각 스텝마다 스텝에 종속된 고유한 빈을 만든다.
        - prototype: 빈을 reference (참조) 할 때마다 새로운 빈을 반환한다.
        - singleton: 배치 job이 생성될 때 하나의 고유한 빈을 만든다.
    -->
    <bean id="helloWorldTasklet"
        class="com.fwantastic.example1.HelloWorldTasklet" scope="step" />


    <!-- Misc Beans -->
    <!-- 해당 섹션에 있는 빈들은 별도의 xml로 관리하는 것이 좋다.
        재사용을 위해 나중에 common.xml을 만들어보자. -->


    <!-- 스프링 배치의 metadata를 담당하는 빈. -->
    <bean id="jobRepository"
        class="org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean">
    </bean>

    <!-- 스프링 배치 job을 실행하는 빈. -->
    <bean id="jobLauncher"
        class="org.springframework.batch.core.launch.support.SimpleJobLauncher">
        <property name="jobRepository" ref="jobRepository" />
    </bean>

    <bean id="transactionManager"
        class="org.springframework.batch.support.transaction.ResourcelessTransactionManager" />

    <!-- 스프링 배치 job을 테스트할 때 쓰는 유틸리티. -->
    <bean id="jobLauncherTestUtils"
        class="org.springframework.batch.test.JobLauncherTestUtils" />

</beans>






테스트

배치 job을 다 만들었으니 테스트 해보자. src/test/java com.fwantastic.example1 패키지를 만들고 아래 junit 클래스를 추가하자.

TestHelloWorldJob.java
package com.fwantastic.example1;

import org.junit.Assert;
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)
// 배치 job 위치 지정
@ContextConfiguration(locations = { "/com/fwantastic/example1/hello_world_job.xml" })
public class HelloWorldJobTest {

    @Autowired
    private JobLauncherTestUtils jobLauncherTestUtils;

    @Test
    public void testJob() throws Exception {
        final JobExecution jobExecution = jobLauncherTestUtils.launchJob();
        // job이 정상적으로 끝났는지 확인다.
        Assert.assertEquals(ExitStatus.COMPLETED.getExitCode(), jobExecution.getExitStatus().getExitCode());
    }
}



실행하면 콘솔에 여러 메세지들이 찍혀있을 것이다.

INFO: Job: [FlowJob: [name=helloWorldJob]] launched with the following parameters: [{random=278562}]
Dec 11, 2019 9:30:46 PM org.springframework.batch.core.job.SimpleStepHandler handleStep
INFO: Executing step: [helloWorldStep]
작업 시작...
작업 완료!
INFO: Job: [FlowJob: [name=helloWorldJob]] completed with the following parameters: [{random=278562}] and the following status: [COMPLETED]




이로서 길다면 길고 짧다면 짧은 Hello World 예제가 끝이 났다. 처음엔 어려워 보일 수도 있으니 복붙 하지 말고 직접 만들어보는 연습을 충분히 하자.




다음 글

[Spring Batch] 스프링 배치 강좌 2. Metadata와 JobRepository 알아보기