Maenya's Techlog

[20210915] Spring Boot Batch (Maven) 프로그램 제작기 - 스프링 배치 예제 소스 본문

개발자의 삶/업무일지

[20210915] Spring Boot Batch (Maven) 프로그램 제작기 - 스프링 배치 예제 소스

ming235 2021. 9. 16. 03:01

사내가 워낙 폐쇄망이라 gradle 프로젝트 만들기는 실패하고

아쉬운대로 maven 배치 프로그램을 제작을 시작했다.

 

[ 개발환경 ]

  • java 1.8
  • maven 3.5.3
  • spring boot 2.5.4
  • intelli-j 2021.2

 

대략 프로젝트 내용은 매일 아침마다 오픈소스 Rest api를 호출하여 데이터를 받아오고,

받아온 데이터를 파싱하여 sftp 서버에 올리는 작업을 하루에 한 번씩 반복하도록 제작해야 한다. 

 

 

1. intelliJ로 spring initializr 프로젝트를 새로 생성한다.

 

2. pom.xml 의존성 추가 

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.ort/POM/4.0.0"
		 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.5.4</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.example</groupId>
	<artifactId>batch-mvn</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>batch-mvn</name>
	<description>Demo project for Spring Boot</description>
	<properties>
		<java.version>1.8</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-batch</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>

		<dependency>
			<groupId>com.h2database</groupId>
			<artifactId>h2</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.batch</groupId>
			<artifactId>spring-batch-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
				<configuration>
					<excludes>
						<exclude>
							<groupId>org.project.lombok</groupId>
							<artifactId>lombok</artifactId>
						</exclude>
					</excludes>
				</configuration>
			</plugin>
		</plugins>
	</build>

</project>

 

3. main 클래스

 

import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@EnableBatchProcessing	// 배치기능을 사용하기 위함
@SpringBootApplication
public class BatchMvnApplication {


    @Bean
    public RestTemplate restTemplate() { // Rest api를 사용하기위함
        return new RestTemplate();
    }
    
    public static void main(String[] args) {
        SpringApplication.run(BatchMvnApplication.class, args);
    }

}

 

4. 엔티티 작성

나는 테스트로 환율정보 오픈api를 받아올 것이기에 그에 맞는 항목들을 작성해 주었다.

import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

/**
 * RestAPI로 넘어온 데이터를 담고 쓰기 위한 용도.
 *
 * @author leesm
 * @version 1.0
 * @see
 */
@Getter
@Setter
@NoArgsConstructor
public class ExchangeDto {

    private String code;
    private String currencyCode;
    private String currencyName;
    private String country;
    private String name;
    private String date;
    private String time;
    private long recurrenceCount;
    private double basePrice;
    private double openingPrice;
    private double highPrice;
    private double lowPrice;
    private String provider;
    
}

 

 

 

 

5. batch job 을 실행시킬 클래스

이 클래스가 가장 중요한데, 일단은 Rest api uri를 하드코딩 해서 테스트 해보기로 했다.

앞서 main클래스에 선언해 준 @EnableBatchProcessing으로 인해 시스템은 Batch job을 돌리려고 하는데

Job 객체가 붙어있는 메소드를 자동으로 찾아서 실행시킨다고 생각하면 된다.

해당 메소드에 선언된 순서대로 배치 로직이 진행된다. 

나는 일단 예시로 step1에 하드코딩한 rest api를 호출해서 데이터를 받아봤다. 

import com.example.batchmvn.dto.ExchangeDto;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;

import java.util.Arrays;
import java.util.List;

@Slf4j
@RequiredArgsConstructor
@Configuration
public class BatchJob {

    private final String BATCH_NAME = "배치 시작합니다 by 승민";

    private final JobBuilderFactory jobBuilderFactory;
    private final StepBuilderFactory stepBuilderFactory;

	// restApi 우선 하드코딩
    private final String bankApiUrl = "https://quotation-api-cdn.dunamu.com/v1/forex/recent?codes=FRX.KRWUSD";  
    private final RestTemplate restTemplate;
    private String testData = "";
    
    @Bean
    public Job job(){
        return jobBuilderFactory.get(BATCH_NAME)
                .start(step1())
                .next(step2())
                .build();
    }

    /**
     * rest api 호출 부분
     * @return
     */
    @Bean
    public Step step1() {
        ResponseEntity<ExchangeDto[]> response = restTemplate.getForEntity(bankApiUrl, ExchangeDto[].class );
        // api에서 받아온 json 데이터를 엔티티로 변환
        ExchangeDto[] resultData = response.getBody();
        List<ExchangeDto> dtoList = Arrays.asList(resultData);

		// 데이터를 리스트 형태로 받고 그 안에서 name이라는 속성을 꺼내어 로그에 찍는다
        if (dtoList.size() > 0) {
            testData = dtoList.get(0).getName();
        }

        return stepBuilderFactory.get(BATCH_NAME + "Step1")
                .tasklet((stepContribution, chunkContext) -> {
                    log.info(BATCH_NAME + "@@@@@@@ Step1 Started :: testData :::::" + testData);
                    return RepeatStatus.FINISHED;
                }).build();
    }

    @Bean
    public Step step2() {
        return stepBuilderFactory.get(BATCH_NAME + "Step2")
                .tasklet((stepContribution, chunkContext) -> {
                    log.info((BATCH_NAME + "매냐 Step2 Started"));
                    return RepeatStatus.FINISHED;
                }).build();
    }


}

 

6. application.properties 또는 application.yml 설정파일 작성

둘 중 하나만 src/main/resource 위치에 생성해주면 되는데, 나는 편의를 위해서 yml 파일을 생성했다.

spring:
  profiles: local
  datasource:
    hikari:
      jdbc-url: jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
      username: sa
      password:
      driver-class-name: org.h2.Driver
  jpa:
    show-sql: true

 

 

이렇게 Run을 해주면 

Spring boot가 실행되고,

 

정상적으로 Rest api를 호출하여 아까 로그로 작성한 리스트의 첫번째 name속성을 찍어내고 있다.

 

 

이로써 간단한 배치 프로그램을 제작하고 그 안에서 rest api 호출하는 것 까지 테스트 해보았다.