티스토리 뷰

spring batch는 여러 회사가 제각기 만들던 batch framework을 통합시켰을만큼 잘 만들어진 batch framework이다. 많은 사람들이 사용을 하고 또 표준이 되었다는건 그만큼 강력한 기능과 누구나 쉽게 다룰수 있는 편의성 때문일것이다. 이런 spring batch를 사용하기 위해 어떤 일들을 해야 하는지 알아보자. 

 

참고로 jdk11, spring batch 버전은 4.0.1.RELEASE을 사용을 했다. 


pom.xml 

<dependencies>
  <dependency>
    <groupId>org.springframework.batch</groupId>
    <artifactId>spring-batch-core</artifactId>
    <version>${org.springframework.batch.version}</version>
  </dependency>
  <dependency>
    <groupId>org.springframework.batch</groupId>
    <artifactId>spring-batch-infrastructure</artifactId>
    <version>${org.springframework.batch.version}</version>
  </dependency>
  <dependency>
    <groupId>org.springframework.retry</groupId>
    <artifactId>spring-retry</artifactId>
    <version>1.2.2.RELEASE</version>
  </dependency>
	...
 </dependencies>
 
 
 <build>
  <defaultGoal>package</defaultGoal>
  <directory>${basedir}/target</directory>
  <finalName>${project.name}-${project.version}</finalName>
  <resources>
    <resource>
      <directory>src/main/java</directory>
      <includes>
	    <include>**/*.xml</include>
        <include>**/*.sql</include>
      </includes>
    </resource>
    <resource>
      <directory>src/main/resources</directory>
    </resource>
  </resources>

  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-compiler-plugin</artifactId>
      <version>3.8.0</version>
      <configuration>
        <source>${java.version}</source>
        <target>${java.version}</target>
        <compilerVersion>${java.version}</compilerVersion>
        <encoding>${encoding}</encoding>
      </configuration>
    </plugin>
			
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-dependency-plugin</artifactId>
      <version>2.8</version>
        <executions>
          <execution>
            <id>copy</id>
            <phase>install</phase>
            <goals>
              <goal>copy-dependencies</goal>
            </goals>
            <configuration>
              <outputDirectory>${project.build.directory}/lib</outputDirectory>
            </configuration>
          </execution>
        </executions>
      </plugin>

      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-jar-plugin</artifactId>
        <version>2.4</version>
        <configuration>
          <archive>
            <manifest>
              <addClasspath>true</addClasspath>
                <mainClass>sample.batch.CommandLineBatchRunner</mainClass>
                <classpathPrefix>dependency-jars/</classpathPrefix>
            </manifest>
          </archive>
        </configuration>
      </plugin>
    </plugins>
 </build>

 

pom.xml의 dependency는 spring batch를 사용하는데 있어서 꼭 필요한 라이브러리인 spring-batch-core와 spring-batch-infrastructure, 그리고 재처리를 위한 spring-retry 등을 넣어준다.

 

그리고 build는 어떤 내용을 어떻게 빌드할것이며 빌드한것을 어느곳에 위치시킬지, 배치 실행은 어느 클래스를 통해서 할지에 대한 내용을 기술한다. java version에 맞는 maven plugin을 적절하게 선택을 해줘야 한다. 


위와 같이 pom.xml 파일을 정의하고 maven update를 수행하고 나면 설정한 library 들이 들어온 것을 확인할수 있을것이다. 여기서 spring-batch-core.jar 를 한번 열어보면 

 

 

이런 properties 파일들이 db 별로 쭉 존재하는것을 찾을 수가 있을 것이다. 이 파일들은 spring batch 를 실행하는데 있어서 필수로 필요한 정보를 담은 파일들이다. spring batch 에서 기본으로 제공을 해주는 파일들이다. 이중 batch.schema.script 파일이 존재하는데 이 경로를 따라가면 spring batch를 실행하기 위해 꼭 필요한 테이블들의 정보가 있다. 나는 oracle 환경에서 작업을 진행할 것이므로 이중 schema-oracle10g.sql 파일을 열어본다. 

 

schema-oracle10g.sql

-- Autogenerated: do not edit this file

CREATE TABLE BATCH_JOB_INSTANCE  (
	JOB_INSTANCE_ID NUMBER(19,0)  NOT NULL PRIMARY KEY ,
	VERSION NUMBER(19,0) ,
	JOB_NAME VARCHAR2(100) NOT NULL,
	JOB_KEY VARCHAR2(32) NOT NULL,
	constraint JOB_INST_UN unique (JOB_NAME, JOB_KEY)
) ;

CREATE TABLE BATCH_JOB_EXECUTION  (
	JOB_EXECUTION_ID NUMBER(19,0)  NOT NULL PRIMARY KEY ,
	VERSION NUMBER(19,0)  ,
	JOB_INSTANCE_ID NUMBER(19,0) NOT NULL,
	CREATE_TIME TIMESTAMP NOT NULL,
	START_TIME TIMESTAMP DEFAULT NULL ,
	END_TIME TIMESTAMP DEFAULT NULL ,
	STATUS VARCHAR2(10) ,
	EXIT_CODE VARCHAR2(2500) ,
	EXIT_MESSAGE VARCHAR2(2500) ,
	LAST_UPDATED TIMESTAMP,
	JOB_CONFIGURATION_LOCATION VARCHAR(2500) NULL,
	constraint JOB_INST_EXEC_FK foreign key (JOB_INSTANCE_ID)
	references BATCH_JOB_INSTANCE(JOB_INSTANCE_ID)
) ;

CREATE TABLE BATCH_JOB_EXECUTION_PARAMS  (
	JOB_EXECUTION_ID NUMBER(19,0) NOT NULL ,
	TYPE_CD VARCHAR2(6) NOT NULL ,
	KEY_NAME VARCHAR2(100) NOT NULL ,
	STRING_VAL VARCHAR2(250) ,
	DATE_VAL TIMESTAMP DEFAULT NULL ,
	LONG_VAL NUMBER(19,0) ,
	DOUBLE_VAL NUMBER ,
	IDENTIFYING CHAR(1) NOT NULL ,
	constraint JOB_EXEC_PARAMS_FK foreign key (JOB_EXECUTION_ID)
	references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID)
) ;

CREATE TABLE BATCH_STEP_EXECUTION  (
	STEP_EXECUTION_ID NUMBER(19,0)  NOT NULL PRIMARY KEY ,
	VERSION NUMBER(19,0) NOT NULL,
	STEP_NAME VARCHAR2(100) NOT NULL,
	JOB_EXECUTION_ID NUMBER(19,0) NOT NULL,
	START_TIME TIMESTAMP NOT NULL ,
	END_TIME TIMESTAMP DEFAULT NULL ,
	STATUS VARCHAR2(10) ,
	COMMIT_COUNT NUMBER(19,0) ,
	READ_COUNT NUMBER(19,0) ,
	FILTER_COUNT NUMBER(19,0) ,
	WRITE_COUNT NUMBER(19,0) ,
	READ_SKIP_COUNT NUMBER(19,0) ,
	WRITE_SKIP_COUNT NUMBER(19,0) ,
	PROCESS_SKIP_COUNT NUMBER(19,0) ,
	ROLLBACK_COUNT NUMBER(19,0) ,
	EXIT_CODE VARCHAR2(2500) ,
	EXIT_MESSAGE VARCHAR2(2500) ,
	LAST_UPDATED TIMESTAMP,
	constraint JOB_EXEC_STEP_FK foreign key (JOB_EXECUTION_ID)
	references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID)
) ;

CREATE TABLE BATCH_STEP_EXECUTION_CONTEXT  (
	STEP_EXECUTION_ID NUMBER(19,0) NOT NULL PRIMARY KEY,
	SHORT_CONTEXT VARCHAR2(2500) NOT NULL,
	SERIALIZED_CONTEXT CLOB ,
	constraint STEP_EXEC_CTX_FK foreign key (STEP_EXECUTION_ID)
	references BATCH_STEP_EXECUTION(STEP_EXECUTION_ID)
) ;

CREATE TABLE BATCH_JOB_EXECUTION_CONTEXT  (
	JOB_EXECUTION_ID NUMBER(19,0) NOT NULL PRIMARY KEY,
	SHORT_CONTEXT VARCHAR2(2500) NOT NULL,
	SERIALIZED_CONTEXT CLOB ,
	constraint JOB_EXEC_CTX_FK foreign key (JOB_EXECUTION_ID)
	references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID)
) ;

CREATE SEQUENCE BATCH_STEP_EXECUTION_SEQ START WITH 0 MINVALUE 0 MAXVALUE 9223372036854775807 NOCYCLE;
CREATE SEQUENCE BATCH_JOB_EXECUTION_SEQ START WITH 0 MINVALUE 0 MAXVALUE 9223372036854775807 NOCYCLE;
CREATE SEQUENCE BATCH_JOB_SEQ START WITH 0 MINVALUE 0 MAXVALUE 9223372036854775807 NOCYCLE;

 

위와 같은 script를 db에 넣고 돌려주기만 하면 된다. 이것의 역할은 간단하게 배치의 실행이력에 대한 관리를 해주는 것이라고 보면 된다. 어떤 Job이 언제 실행되었고 성공했는지 실패했는지 등등에 대한 정보가 들어간다.


이제 application context에 대한 구성이 필요하다. spring batch이기 때문에 당연히 spring의 기본사상인 application context를 구성해야한다. application context를 구성하는 방법 중 xml 을 사용을 해서 구성을 하였다. 

 

context-batch.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:p="http://www.springframework.org/schema/p"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:util="http://www.springframework.org/schema/util"
	xmlns:aop="http://www.springframework.org/schema/aop"
	xmlns:tx="http://www.springframework.org/schema/tx"
	xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.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/jee http://www.springframework.org/schema/jee/spring-jee.xsd
			http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">

	<import
		resource="classpath:/spring/jobs/**/context-batch-job-*.xml" />

	<context:annotation-config />

	<bean id="propertyConfigurer"
		class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
		<property name="locations">
			<list>
				<value>classpath:/config/application-${spring.profiles.active}.properties
				</value>
			</list>
		</property>
	</bean>

	<bean id="hikariConfig" class="com.zaxxer.hikari.HikariConfig">
		<property name="driverClassName"
			value="${db.DriverClassName}" />
		<property name="jdbcUrl" value="${db.Url}" />
		<property name="username" value="${db.UserName}" />
		<property name="password" value="${db.Password}" />
	</bean>
	<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource"
		destroy-method="close">
		<constructor-arg ref="hikariConfig" />
	</bean>

	<bean id="transactionManager"
		class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSource" />
	</bean>

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

	<bean id="sqlSessionTemplate"
		class="org.mybatis.spring.SqlSessionTemplate">
		<constructor-arg ref="sqlSessionFactory" />
		<constructor-arg value="BATCH" />
	</bean>

	<bean id="sqlSessionFactory"
		class="org.mybatis.spring.SqlSessionFactoryBean">
		<property name="dataSource" ref="dataSource" />
		<property name="configLocation"
			value="classpath:/mybatis/mybatis-config.xml" />
		<property name="mapperLocations">
			<list>
				<value>classpath*:sample/**/*.sqlx</value>
			</list>
		</property>
	</bean>

	<bean id="nativeJdbcExtractor"
		class="org.springframework.jdbc.support.nativejdbc.SimpleNativeJdbcExtractor" />

	<bean id="oracleLobhandler"
		class="org.springframework.jdbc.support.lob.OracleLobHandler">
		<property name="nativeJdbcExtractor"
			ref="nativeJdbcExtractor" />
	</bean>

	<bean id="jdbcTemplate"
		class="org.springframework.jdbc.core.JdbcTemplate">
		<property name="dataSource" ref="dataSource" />
	</bean>

	<bean id="commonDao" class="sample.CommonDao">
		<property name="sqlSessionFactory" ref="sqlSessionFactory" />
	</bean>

	<bean id="jobLauncher"
		class="org.springframework.batch.core.launch.support.SimpleJobLauncher">
		<property name="jobRepository" ref="jobRepository" />
	</bean>

	<bean
		class="org.springframework.batch.core.configuration.support.JobRegistryBeanPostProcessor">
		<property name="jobRegistry" ref="jobRegistry" />
	</bean>

	<bean id="jobRepository"
		class="org.springframework.batch.core.repository.support.JobRepositoryFactoryBean">
		<property name="transactionManager" ref="transactionManager" />
		<property name="dataSource" ref="dataSource" />
		<property name="isolationLevelForCreate"
			value="ISOLATION_DEFAULT" />
	</bean>


	<bean id="jobRegistry"
		class="org.springframework.batch.core.configuration.support.MapJobRegistry" />

	<bean id="jobOperator"
		class="org.springframework.batch.core.launch.support.SimpleJobOperator"
		p:jobLauncher-ref="jobLauncher" p:jobExplorer-ref="jobExplorer"
		p:jobRepository-ref="jobRepository" p:jobRegistry-ref="jobRegistry" />

	<bean id="jobExplorer"
		class="org.springframework.batch.core.explore.support.JobExplorerFactoryBean"
		p:dataSource-ref="dataSource" />

</beans>

 

이중 properties 파일, mybatis 설정, datasource 설정 등 필요한 부분만 내게 맞게 수정한다. 나머지 생소해 보이는 spring batch 에 대한 bean 설정은 이대로 사용해도 무방하다. 

 

그리고 job에 대한 정의도 bean으로 한다. 위에서 /spring/jobs/**/context-batch-job-*.xml 에 대해 import를 하고 있는데 이것은 job에 대한 정의를 이곳에 모아놓고 관리하기 위함이다. 해당 위치에 context-batch-job-*.xml 에 해당하는 파일을 만들어 본다. 

 

context-batch-job-tasklet.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:batch="http://www.springframework.org/schema/batch"
    xmlns:task="http://www.springframework.org/schema/task"
	xsi:schemaLocation="
	http://www.springframework.org/schema/batch http://www.springframework.org/schema/batch/spring-batch.xsd
	http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
	http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd">
	
	<batch:job id="tasklet-job">
		<batch:step id="tasklet-step">			
			<batch:tasklet ref="studentTasklet" />
		</batch:step>
	</batch:job>
	
	<bean id="studentTasklet" class="sample.tasklet.StudentTasklet" />
</beans>

 

spring batch는 기본적으로 job > step > tasklet or reader,processor,writer 로 구성이 되어 있다. 이 규칙에 맞게 batch job을 정의를 해야 한다. tasklet-job이라는 job은 tasklet-step이라는 step을 가지고 있고 이 tasklet-step은 studentTasklet 이라는 tasklet을 가지고 있다. 물론 위처럼 tasklet 클래스를 정의를 하지 않은 상태에서 bean을 먼저 만든다면 오류가 발생할것이다. 다음과 같이 tasklet 을 작성해 보도록 하자. 

 


tasklet 작성

public class StudentTasklet implements Tasklet{
 
    @Autowired
	private CommonDao commonDao;
     
    public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
        
        List<Object> list = commonDao.selectList("student.retrieveStudent", params);

        //... 중략 ...
        
        for(int inx = 0 ; inx < size ; inx++) {
        	Student student = (Student) list.get(inx);
        	System.out.println(student.getName() + "##################### " + key);
        }
    	
       //... 중략 ...
        
        return RepeatStatus.FINISHED;
         
    }

 

tasklet 을 작성하는데 있어서 핵심은 Tasklet 을 implements 하는것이다. 그에 따라 execute 메소드를 구현하면 되는 것이다. execute에는 배치를 수행하기 위한 내용을 기술하면 된다.  


그다음은 배치를 실행시키기 위해 위의 pom.xml 설정에서 살짝 나온 main 클래스에 대한 작성이다. 

 

CommandLineBatchRunner.java

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;

import org.springframework.batch.core.launch.support.CommandLineJobRunner;

public class CommandLineBatchRunner {
	
	private static final String TIMESTAMP = "timestamp";
	
	public static void main(String[] args) throws Exception {
		
		List<String> param = new ArrayList<String>(Arrays.asList(args));
		param.add(TIMESTAMP + "=" + new Date().getTime());
		CommandLineJobRunner.main(param.toArray(new String[] {}));
	}
}

그냥 spring batch 에서 제공을 하는 CommandLineJobRunner를 사용해도 무방하지만 매번 다른 param 값을 넣어줘야 하는 수고가 있기에 custom Runner를 만들었다. 현재 시간을 param 값으로 넣어줘서 수고를 덜어줄 수 있다. 

 


여기까지 다 되었다면 이제 빌드를 하고 실행을 하는 일만 남았다. 

 

빌드는 어떻게 할지 위의 pom.xml에 기술을 해 놨고 설정해 놓은대로 빌드가 될것이다. 

 

 

프로젝트 우클릭 > Run As > Maven install 을 수행하면 빌드가 수행이 될것이고 이것의 결과물이 target 폴더 아래에 jar로 생성이 될것이다. 

 


실행방법은 윈도우 환경이라면 cmd, 리눅스 환경이라면 sh 파일을 만들어서 실행시킬수도 있고 아니면 직접 명령어로 실행을 할수도 있다. 

 

실행 명령어는 다음과 같다. 

 

java -Dspring.profiles.active=local -classpath "..\target\classes;..\target\lib\*" sample.batch.CommandLineBatchRunner spring\context-batch.xml tasklet-job

 

CommandLineBatchRunner에 applicationContext에 대한 정의를 한 context-batch.xml 과 수행시킬 job에 대한 정보를 param 값으로 넣어준다. 다른 job을 실행시키기 위해서는 맨 뒤에 job의 bean id 만 교체해주면 된다. 

 

실행을 시키고 나서 결과에 대한 확인은 위에 schema-oracle10g.sql 스크립트를 통해 생성한 table에서 확인을 할 수 있다. 

 

끝!

댓글
최근에 올라온 글
최근에 달린 댓글
«   2024/05   »
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