배치 수행시 다수의 리소스를 처리하고자 할 경우에는 일반적인 Job설정으로 처리할 수 없다. 전자정부 배치프레임워크에서는 MultiData Processing을 통해 다수의 리소스를 읽어 다수의 결과로 처리하거나 다수의 리소스를 읽어 하나의 결과로 처리하는 기능을 제공한다.
다수(N개)의 리소스를 처리하는 방식은 N→1, N→N으로 구분된다.
다수의 파일을 대상으로 동일한 유형의 Batch처리를 하고자 할 경우 MultiResourceItemReader를 사용하면 편리하다.
예를 들어, 아래와 같이 'file~'로 시작하는 파일명을 가진 파일들에 대해 일괄 변경을 수행하고자 할 경우에도 적용 가능하다.
file-1.txt file-2.txt ignored.txt
Job수행에 사용되는 Reader및 Writer설정은 일반적인 Job과 동일하다.
<job id="multiResourceIoJob" xmlns="http://www.springframework.org/schema/batch"> <step id="multiResourceIoStep1"> <tasklet> <chunk reader="itemReader" processor="itemProcessor" writer="itemWriter" commit-interval="2"/> </tasklet> </step> </job>
MultiResourceItemReader를 통해 여러 개의 리소스를 읽어온 다음, 1개의 리소스를 처리하는 Reader에게 데이터처리를 위임한다.
이 때, input resource경로에 *를 사용하여 다수의 파일을 처리 가능하다.
<bean id="itemReader" class="org.springframework.batch.item.file.MultiResourceItemReader" scope="step"> <property name="delegate"> <bean class="org.springframework.batch.item.file.FlatFileItemReader"> <property name="lineMapper"> <bean class="org.springframework.batch.item.file.mapping.DefaultLineMapper"> <property name="lineTokenizer"> <bean class="org.springframework.batch.item.file.transform.DelimitedLineTokenizer"> <property name="delimiter" value="," /> <property name="names" value="name,credit" /> </bean> </property> <property name="fieldSetMapper"> <bean class="org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper"> <property name="targetType" value="egovframework.brte.sample.common.domain.trade.CustomerCredit" /> </bean> </property> </bean> </property> </bean> </property> <property name="resources" value="classpath:data/input/file-*.txt" /> </bean>
MultiResourceItemWriter를 통해 출력파일의 개수를 지정한 다음, 1개의 리소스를 처리하는 Writer에게 데이터처리를 위임한다.
<bean id="itemWriter" class="org.springframework.batch.item.file.MultiResourceItemWriter" scope="step"> <property name="resource" value="#{jobParameters['output.file.path']}" /> <property name="itemCountLimitPerResource" value="6" /> <property name="delegate" ref="delegateWriter" /> </bean> <bean id="delegateWriter" class="org.springframework.batch.item.file.FlatFileItemWriter"> <property name="lineAggregator"> <bean class="org.springframework.batch.item.file.transform.DelimitedLineAggregator"> <property name="delimiter" value="," /> <property name="fieldExtractor"> <bean class="org.springframework.batch.item.file.transform.BeanWrapperFieldExtractor"> <property name="names" value="name,credit" /> </bean> </property> </bean> </property> </bean>
스프링 배치에서는 Composite처리와 관련하여 CompositeWriter만을 제공하고 있다. 이에 전자정부 배치프레임워크에서는 CompositeReader를 추가적으로 제공한다.
CompositeReader의 일반적인 처리 프로세스는 아래와 같다.
✔ 주의! CompositeReader는 등록된 모든 Reader로부터 데이터를 한 라인씩 순서대로 읽어와서 배열에 넣어주는 역할까지 수행한다.따라서,Writer를 바로 사용하면 안되고 Processor에서 배열을 읽어서 처리하는 과정이 반드시 필요하다.
CompositeReader에는 Reader에서 리소스를 처리해서 Processor로 VO를 전달하는 유형과 Reader자체를 그대로 Processor로 전달하는 유형으로 나뉘며,
Processor에서는 전달된 데이터 타입에 맞게 비즈니스 로직을 구현할 수 있다.
CompositeItem처리를 위한 Job설정은 reader에 compositeItemReader를 설정하고, processor를 지정해야 한다.
<job id="compositeItemJob" xmlns="http://www.springframework.org/schema/batch"> <step id="compositeItemStep"> <tasklet> <chunk reader="compositeItemReader" processor="itemProcessor" writer="itemWriter" commit-interval="5" /> </tasklet> </step> </job>
ComposteItemReader를 설정하기 위해서는 Reader로 사용할 Class, returnType, itemReaderList 항목을 작성해야 한다.
<bean id="compositeItemReader" class="egovframework.brte.core.item.composite.reader.EgovCompositeFileReader"> <property name="itemsMapper"> <bean class="egovframework.brte.core.item.composite.EgovCompositeItemMapper" /> </property> <property name="returnType" value="vo" /> <property name="itemReaderList"> <list> <ref bean="itemReader1" /> <ref bean="itemReader2" /> </list> </property> </bean> <bean id="itemReader1" class="org.springframework.batch.item.file.FlatFileItemReader" scope="step"> <property name="resource" value="#{jobParameters[inputFile]}" /> <property name="lineMapper"> <bean class="org.springframework.batch.item.file.mapping.DefaultLineMapper"> <property name="lineTokenizer"> <bean class="org.springframework.batch.item.file.transform.FixedLengthTokenizer"> <property name="names" value="name,credit" /> <property name="columns" value="1-9,10-11" /> </bean> </property> <property name="fieldSetMapper"> <bean class="org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper"> <property name="targetType" value="egovframework.brte.sample.common.domain.trade.CustomerCredit" /> </bean> </property> </bean> </property> </bean> <bean id="itemReader2" class="org.springframework.batch.item.file.FlatFileItemReader" scope="step"> <property name="lineMapper"> <bean class="org.springframework.batch.item.file.mapping.DefaultLineMapper"> <property name="lineTokenizer"> <bean class="org.springframework.batch.item.file.transform.DelimitedLineTokenizer"> <property name="delimiter" value=","/> <property name="names" value="name,credit" /> </bean> </property> <property name="fieldSetMapper"> <bean class="org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper"> <property name="targetType" value="egovframework.brte.sample.common.domain.trade.CustomerCredit" /> </bean> </property> </bean> </property> <property name="resource" value="#{jobParameters[inputFile]}" /> </bean>
✔ CompositeItemReader에서 사용가능한 Class는 총 3개가 존재하며 각 Class별 특징은 다음과 같다.
종류 | 설명 |
---|---|
EgovCompositeFileReader | FlatFile(FixedLength,Delimited), XMLFile 등 파일처리를 위한 용도로 사용 |
EgovCompositeCursorReader | JdbcCursorItemReader를 통해 DB리소스 처리를 하고자 할 경우에 사용 |
EgovCompositePagingReader | JdbcPagingItemReader, IbatisPagingItemReader 등 Paging단위로 DB리소스 처리를 하고자 할 경우에 사용 |
CompositeItemReader를 통해 넘어온 VO 또는 Reader를 Processor에서 꺼내와서 처리하는 로직을 구현해야 한다.
public class FileItemProcessor implements ItemProcessor<EgovCompositeDataProvider, TargetVO> { public TargetVO process(EgovCompositeDataProvider eprovider) throws Exception { Object[] obj = eprovider.getMapItems(); //배열에서 항목을 꺼내오는 로직 CustomerCredit vo1 = (CustomerCredit)obj[0]; CustomerCredit vo2 = (CustomerCredit)obj[1]; //비즈니스 로직 처리 가능 TargetVO vo = new TargetVO(); if(vo1 !=null) { vo.setId(vo1.getId()); } if(vo2 != null) { vo.setName(vo2.getName()); } return vo; } }
✔ 주의!