티스토리 뷰

현재까지 진행한 대부분의 프로젝트는 복잡하기도 하고 상황에 맞지 않아(WebService, EAI 연동 등) Spring Batch에서 일반적으로 사용하고 있는 chunk 방식 대신에 tasklet을 이용하여 사용하였다. tasklet을 이용한다면 하나의 자바 메소드 안에 내가 필요한 내용만을 쭉 기술해서 사용하면 되므로 Spring Batch에 대한 개념이 없어도 쉽게 접근이 가능하다. 하지만 Spring Batch에서 일반적으로 사용되고 또 권고하는 chunk 방식이 더욱 강력한 성능을 가지고 있기에 공부를 더 하고 이를 활용한 개발방식의 변화도 필요하다. 그래서 Spring Batch를 사용하며 이론적으로는 많이 들었던 개념인 chunk (file2file, file2db, db2file, db2db) 의 예제를 기술해보려한다.

chunk 란? 읽고(Reader) 가공하고(Processor) 를 반복하여 chunk 덩어리로 만든 후 쓸때(Writer) chunk 단위로 트랜잭션을 다루는 것을 의미한다.  즉 chunk 단위로 트랜잭션이 이루어지고 처리가 된다는 것이다. 

 

그럼 chunk 방식 중 file to file 처리는 어떤게 구현을 해야 하는지 살펴보자. (xml config 로 진행한다.)

사용한 spring batch의 version은 4.0.1.RELEASE 이다. 


context-batch-job-file2file.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"
	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">
	
  <batch:job id="file2fileJob">
    <batch:step id="file2fileStep">			
      <batch:tasklet transaction-manager="transactionManager">
        <batch:chunk reader="file2fileReader" processor="fileProcessor" writer="file2fileWriter" commit-interval="100" />
        <batch:listeners>
          <batch:listener ref="sampleStepListener" />
        </batch:listeners>
      </batch:tasklet>
    </batch:step>
  </batch:job>
	... 생략 ...
    
  <bean id="sampleStepListener" class="sample.template.listener.SampleStepListener" />
  <bean id="fileProcessor" class="sample.template.processor.SampleFileProcessor" />
	
</beans>

spring batch의 큰 개념인 job > step > tasklet (chunk) > reader, processor, writer 의 기본 개념은 다음 기회에 소개를 하도록 하겠다. 일단 이런 개념은 기본적으로 잡혀 있어야 한다. 

위의 문구를 쭉 해석해 보면 file2fileJob 이라는 Job을 생성을 했고 이건 file2fileStep을 가지고 있다. file2fileStep은 chunk방식으로 구현이 되는데 file2fileReader와 fileProcessor, file2fileWriter로 구성이 된다. 그리고 commit의 단위는 100으로 설정을 했다. 이는 위에서 얘기한 chunk의 덩어리와 같은 개념이라고 보면 된다. 즉 (file2fileReader로부터 데이터를 읽어와서 fileProcessor를 통해 가공하고) * 100 을 수행한 후에 100개 의 가공된 데이터를 한번에 write 하는거다.

그리고 이걸 동작하기 앞서 sampleStepListener 가 동작을 하도록 한다. 라는 내용이다. reader, processor, writer를 보기 앞서 listener 부터 살펴보자. 

 

SampleStepListener.java

import org.springframework.batch.core.ExitStatus;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.StepExecutionListener;

public class SampleStepListener implements StepExecutionListener {

  @Override
  public void beforeStep(StepExecution stepExecution) {
    System.out.println("========beforeStep========");
  }

  @Override
  public ExitStatus afterStep(StepExecution stepExecution) {
    System.out.println("========afterStep========");
    return null;
  }
}

말 그대로 배치 step의 listener로 step의 앞뒤로 필요한 수행했으면 하는 일들에 대해서 기술해준다. StepExecutionListener를 impl 하여 구현하여야 한다. 지금은 앞뒤로 메세지만 찍어주는 역할을 하는 listener이다.


file2fileReader

<bean id="file2fileReader" class="org.springframework.batch.item.file.FlatFileItemReader" scope="step">
  <property name="resource" value="classpath:/batchdata/student.txt" />
  <property name="linesToSkip" value="1" />
  <property name="lineMapper">
    <bean class="org.springframework.batch.item.file.mapping.DefaultLineMapper">
      <property name="fieldSetMapper">
        <bean class="sample.template.mapper.SampleFieldSetMapper"/>
      </property>
      <property name="lineTokenizer">
        <bean class="org.springframework.batch.item.file.transform.DelimitedLineTokenizer">
          <property name="delimiter" value="," />
        </bean>
      </property>
    </bean>
  </property>
</bean>

context-batch-job-file2file.xml 파일의 file2fileReader 의 bean이다. spring batch의 FlatFileItemReader 를 사용하여 구현하였다. 말그대로 플랫(텍스트) 파일에 대해 읽어들이는 역할을 하는 녀석이다. 

resource는 읽을 파일의 위치, linesToSkip은 건너뛸 행번호 (header 정보 제외하기 위함), lineMapper는 라인별로 어떻게 파싱을 해서 model에 넣어줄지를 결정해주는 역할을 한다. 이정도만 알고 있어도 거의 대부분 사용할수 있다. 

lineMapper 중 fieldSetMapper를 한번 살펴보겠다. 이녀석은 읽은 파일을 model과 연결시켜 주는 역할을 한다. 

 

fieldSetMapper

public class SampleFieldSetMapper implements FieldSetMapper<Student> {

  @Override
  public Student mapFieldSet(FieldSet fieldset) throws BindException {
    Student student = new Student();
    student.setUserId(fieldset.readString(0));
    student.setUserName(fieldset.readString(1));
    return student;
  }
}

Batch 작업을 수행하기 위해 model 과 input data의 field와 mapping을 해주는 역할을 하는 클래스이다. 

물론 Student model이 존재해야 하고 이것은 userId와 userName을 가지고 있어야 한다. 

 

lineTokenizer 는 한 row를 어떤 delimeter를 기준으로 끊을까에 대한 것이다. 예제에서는 ' , ' 로 구분을 하고 있다. 

 

student.txt

userId,userName
1000,student000
1001,student001
1002,student002
1003,student003
1004,student004
1005,student005
1006,student006
1007,student007
1008,student008
1009,student009
1010,student010
...

 

즉 이런 플랫 파일이 있다고 치면 1번째 line skip을 하고 2번째 라인부터 ' , ' 로 문자열을 잘라서 첫번째 문자열은 Student.java 라는 Entity 클래스에 userId라는 곳에 매핑을 시키고 두번째 문자열은 userName이라는 문자열로 매핑을 시키는 작업을 하는 것이다. 


fileProcessor

public class SampleFileProcessor implements ItemProcessor<Student, Student>{
	
  public Student process(Student item) throws Exception {
    Student student = item;
    student.setUserName("@" + student.getUserName() + "@");
    return student;
  }
}

context-batch-job-file2file.xml 파일의 fileProcessor이다. 이건 ItemProcessor를 impl 하여 구현하여야 한다. process() 안에 넘겨 받을 값으로 Reader에서 읽어서 model로 옮긴 값을 받아오면 된다. 이 processor는 보통 넘어온 데이터를 가공해주는 역할을 하는데 이해를 쉽게 하기 위해서 userName 앞뒤로 @를 붙이는 로직을 추가하여 return 했다. 


file2fileWriter

<bean id="file2fileWriter" class="org.springframework.batch.item.file.FlatFileItemWriter" scope="step">
  <property name="resource" value="file:///C:/Sample/sample.txt" />
  <property name="shouldDeleteIfExists" value="true" />
  <property name="lineAggregator">
    <bean class="org.springframework.batch.item.file.transform.DelimitedLineAggregator">
      <property name="fieldExtractor">
        <bean class="org.springframework.batch.item.file.transform.BeanWrapperFieldExtractor">
          <property name="names" value="userId, userName" />
        </bean>
      </property>
      <property name="delimiter" value=","/>
    </bean>
  </property>
</bean>

commit-interval이 100으로 설정이 되어있으므로 위의 reader, processor에서 열심히 100번을 반복해가며 100row까지 userName 앞뒤로 @를 붙이는 작업을 수행했다. 100번을 돌면 이 writer에서 100개씩 한번에 처리를 해준다. reader와 마찬가지로 spring batch의 FlatFileItemWriter를 사용해서 write를 해준다. 

 

각 property에 대해서 알아보면 resource는 어디에 write 할건지, shouldDeleteIfExists 파일 덮어쓰기 여부, lineAggregator는 어떤 방식으로 write를 할지에 대해서 정의를 한다. reader와 반대로 fieldExtractor는 entity 로부터 값을 추출해서 delimiter에 따라서 파일에 기록을 하는 것이다. 

 

즉 위와 같은 과정을 거치게 되면 C:/Sample/sample.txt 라는 파일에 

userId,userName
1000,@student000@
1001,@student001@
1002,@student002@
1003,@student003@
1004,@student004@
1005,@student005@
1006,@student006@
1007,@student007@
1008,@student008@
1009,@student009@
1010,@student010@
...

다음과 같이 기록이 되게 되고 모든 데이터가 write가 되면 배치는 끝이 난다. 

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