본문 바로가기

표준프레임워크_eGovFrame/개발환경_Development Environment

[MacOS]eGovFrame 3.8 Batch Template Project > File(SAM) File to File CommanLine 방식

MacOS Mojave 표준프레임워크 3.8 배치 템플릿 File(SAM) File to File CommandLine

 

MacOS Mojave / Window 10 pro 도 가능

OpenJDK 1.8

Eclipse Oxygen

eGovFrame 3.8

 

표준프레임워크에는 배치 템플릿을 만들 수 있는 총 6가지의 방법이 있습니다.

 

배치 템플릿 입력 리소스 유형과 실행 유형을 선택할 수 있는 eGovFrame기반의 배치 템플릿 프로젝트 자동 생성 마법사를 제공한다. 배치 템플릿 마법사 진행 과정

 

  1. 배치 템플릿 입력 리소스 유형에 따른 구분

    • File(SAM) : 입력 리소스 유형이 File(SAM)인 배치실행환경을 제공한다. '입력 리소스 유형이 File(SAM)'이라는 것은 아래와 같이 File to File, File to DB 두 가지 유형을 지칭한다.

      • File to File : File(SAM) 형태의 자료에서 원천 데이터를 입력받아 File 형태의 배치실행 결과물을 제공하는 유형

      • File to DB : File(SAM) 형태의 자료에서 원천 데이터를 입력받아 데이터베이스 테이블 형태의 배치실행 결과물을 제공하는 유형

    • DB : 입력 리소스 유형이 데이터베이스 테이블인 배치실행환경을 제공한다. '입력 리소스 유형이 DB'라는 것은 아래와 같이 DB to File, DB to DB 두 가지 유형을 지칭한다.

      • DB to File : 데이터베이스 테이블 형태의 자료에서 원천 데이터를 입력받아 File 형태의 배치실행 결과물을 제공하는 유형

      • DB to DB : 데이터베이스 테이블 형태의 자료에서 원천 데이터를 입력받아 데이터베이스 테이블 형태의 배치실행 결과물을 제공하는 유형

  2. 배치 템플릿 실행 유형에 따른 구분

    • Scheduler : Scheduler를 이용하여 사용자가 설정한 날짜나 주기에 따라 배치작업을 실행할 수 있는 배치실행환경

    • CommandLine: 명령 프롬프트 혹은 명령 실행 파일을 이용하여 배치작업을 실행할 수 있는 배치실행환경 

    • Web: 웹 기반 어플리케이션 에서 배치작업을 실행할 수 있는 배치실행환경

(두 OS 테스트 결과 Window 10 pro도 아래와 같은 방식으로 가능합니다.

먼저 File(SAM) 방식에서 file to file 방식으로 batch 템플릿 프로젝트를 만들어보겠습니다.

 

Perspective가 eGovFrame으로 설치되었다면 eclipse 상단 메뉴에 eGovFrame > Start > New Batch Template Project

Perspective가 eGovFrame으로 설치되었다면 eclipse 상단 메뉴에 eGovFrame > Start > New Batch Template Project 를 누릅니다.

다음 New eGovFrame Batch Template Project란 화면이 뜰 것이고, 다음은 File(SAM) 클릭을 하세요.

그 다음은 CommanLine을 선택합니다.

다음은 프로젝트 이름을 정해주고 Finish 버튼을 눌러 프로젝트를 만듭시다.

프로젝트 구조가 아래와 같이 만들어질 것입니다.

src/main/java 내에 egovframework.example.bat.commandline 패키지에 EgovCommandLineJobRunner.java 파일에 가보면, 아래와 같은 클래스 코드들이 있다.

public class EgovCommandLineJobRunner {

	private static final Logger LOGGER = LoggerFactory.getLogger(EgovCommandLineJobRunner.class);

	/**
	 * Launch a batch job using a {@link EgovCommandLineRunner}. Creates a new
	 * Spring context for the job execution, and uses a common parent for all
	 * such contexts. No exception are thrown from this method, rather
	 * exceptions are logged and an integer returned through the exit status in
	 * a {@link JvmSystemExiter} (which can be overridden by defining one in the
	 * Spring context).<br/>
	 * Parameters can be provided in the form key=value, and will be converted
	 * using the injected {@link JobParametersConverter}.
	 *
	 * @param args <p>
	 * <ul>
	 * <li>-restart: (optional) if the job has failed or stopped and the most
	 * should be restarted. If specified then the jobIdentifier parameter can be
	 * interpreted either as the name of the job or the id of teh job execution
	 * that failed.</li>
	 * <li>-next: (optional) if the job has a {@link JobParametersIncrementer}
	 * that can be used to launch the next in a sequence</li>
	 * <li>jobPath: the xml application context containing a {@link Job}
	 * <li>jobIdentifier: the bean id of the job or id of the failed execution
	 * in the case of a restart.
	 * <li>jobParameters: 0 to many parameters that will be used to launch a
	 * job.
	 * </ul>
	 * The options (<code>-restart, -next</code>) can occur anywhere in the
	 * command line.
	 * </p>
	 */
	public static void main(String[] args) throws Exception {
		/*
		 * CommandLine상에서 실행하기 위해서는 jobPath와 jobIdentifier을 인수로 받아야 한다.
		 * jobPath: Batch Job 실행에 필요한 context 정보가 기술된 xml
		 * jobIdentifier: 실행할 Batch Job의 이름
		 *
		 * 실행예시) 'java EgovCommandLineRunner jobPath jobIdentifier jobParamter1 jobParamter2 ...'
		 * 이클립스 기본 세팅 위치: .settings/EgovCommandLineJobRunner.launch
		 * jobPath: /egovframework/batch/context-commandline.xml
		 * jobIdentifier: delimitedToDelimitedJob(기본세팅), fixedLengthToFixedLengthJob, fixedLengthToIbatisJob, fixedLengthToJdbcJob 중 택일
		 */
		EgovCommandLineRunner command = new EgovCommandLineRunner();

		List<String> newargs = new ArrayList<String>(Arrays.asList(args));
		newargs.add("timestamp=" + new Date().getTime());

		try {
			if (System.in.available() > 0) {
				BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
				String line = " ";
				while (StringUtils.hasLength(line)) {
					if (!line.startsWith("#") && StringUtils.hasText(line)) {
						LOGGER.debug("Stdin arg: {}", line);
						newargs.add(line);
					}
					line = reader.readLine();
				}
			}
		} catch (IOException e) {
			LOGGER.warn("Could not access stdin (maybe a platform limitation)");
			LOGGER.debug("Exception details", e);
		}

		Set<String> opts = new HashSet<String>();
		List<String> params = new ArrayList<String>();

		int count = 0;
		String jobPath = null;
		String jobIdentifier = null;

		for (String arg : newargs) {
			if (arg.startsWith("-")) {
				opts.add(arg);
			} else {
				switch (count) {
					case 0:
						jobPath = arg;
						break;
					case 1:
						jobIdentifier = arg;
						break;
					default:
						params.add(arg);
						break;
				}
				count++;
			}
		}

		if (jobPath == null || jobIdentifier == null) {
			String message = "At least 2 arguments are required: JobPath and jobIdentifier.";
			LOGGER.error(message);
			command.setMessage(message);
			command.exit(1);
		}

		String[] parameters = params.toArray(new String[params.size()]);

		int result = command.start(jobPath, jobIdentifier, parameters, opts);
		command.exit(result);
	}
}

class file을 command 방식으로 실행 시킴을 알 수 있다.

arg라는 argument를 받는다. 그럼 당연히 commanLine 방식에 arg를 넣어주면 된다.

아래처럼 프로젝트 우클릭 > Run as > Run Configurations 클릭 한다.

Java Application > EgovCommandLinJobRunner를 클릭 후 Arguments 탭을 클릭합니다.

Argument 탭을 선택하여 아래와 같이 jobPath jobIdentifier를 입력합니다. jobIdentifier 뒤에 붙은 Argument는 Job Parameter로 사용되며, 여러 개의 Argument를 넣을 수 있습니다. JobPath는 프로젝트 내에 /egovframework/batch/context-commandline.xml 하위에 job 파일을 찾습니다. JobIdentifier는 아래 4 종류의 Job에서 선택하면 됩니다.

 

Job Reader Writer Job 설명
delimitedToDelimitedJob FlatFileItemReader FlatFileItemWriter File(SAM) 형태의 자료에서 원천 데이터를 구분자 기준으로 입력받아 배치작업 처리 후, 구분자 방식의 데이터를 저장하는 파일로 결과물을 생성하는 Job
fixedLengthToFixedLengthJob FlatFileItemReader FlatFileItemWriter File(SAM) 형태의 자료에서 원천 데이터를 고정길이 방식으로 입력받아 배치작업 처리 후, 고정길이 지정 방식의 데이터를 저장하는 파일로 결과물을 생성하는 Job
fixedLengthToIbatisJob FlatFileItemReader IbatisBatchItemWriter File(SAM) 형태의 자료에서 원천 데이터를 고정길이 방식으로 입력받아 배치작업 처리 후, iBatis를 이용해 데이터베이스 테이블에 데이터를 저장하는 Job
fixedLengthToJdbcJob FlatFileItemReader JdbcBatchItemWriter File(SAM) 형태의 자료에서 원천 데이터를 고정길이 방식으로 입력받아 배치작업 처리 후, JDBC를 이용해 데이터베이스 테이블에 데이터를 저장하는 Job

commandLine에서 구분자 Job으로 파일을 읽어 파일로 쓰겠습니다. 물론 중간에 ItemProcessor도 들어가 있습니다.(템플릿 샘플 소스 참조해보세요.)

아래와 같이 /egovframework/batch/context-commandline.xml delimitedToDelimitedJob  옵션을 줍니다.

옵션을 주고 Apply > Run을 누릅니다.

아래와 같이 로그가 떨어질 것 입니다.

 

2019-08-11 02:01:14,009  INFO [org.springframework.context.support.ClassPathXmlApplicationContext] Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@b59d31: startup date [Sun Aug 11 02:01:14 KST 2019]; root of context hierarchy
2019-08-11 02:01:14,041  INFO [org.springframework.beans.factory.xml.XmlBeanDefinitionReader] Loading XML bean definitions from class path resource [egovframework/batch/context-commandline.xml]
2019-08-11 02:01:14,098  INFO [org.springframework.beans.factory.xml.XmlBeanDefinitionReader] Loading XML bean definitions from class path resource [egovframework/batch/context-batch-job-launcher.xml]
2019-08-11 02:01:14,115  INFO [org.springframework.beans.factory.xml.XmlBeanDefinitionReader] Loading XML bean definitions from class path resource [egovframework/batch/context-batch-datasource.xml]
2019-08-11 02:01:14,158  INFO [org.springframework.beans.factory.xml.XmlBeanDefinitionReader] Loading XML bean definitions from class path resource [egovframework/batch/context-batch-sqlmap.xml]
2019-08-11 02:01:14,173  INFO [org.springframework.beans.factory.xml.XmlBeanDefinitionReader] Loading XML bean definitions from file [/Users/dy20c/edu38/egov.file2file/target/classes/egovframework/batch/job/delimitedToDelimitedJob.xml]
2019-08-11 02:01:14,193  INFO [org.springframework.beans.factory.xml.XmlBeanDefinitionReader] Loading XML bean definitions from file [/Users/dy20c/edu38/egov.file2file/target/classes/egovframework/batch/job/abstract/eGovBase.xml]
2019-08-11 02:01:14,226  INFO [org.springframework.beans.factory.xml.XmlBeanDefinitionReader] Loading XML bean definitions from file [/Users/dy20c/edu38/egov.file2file/target/classes/egovframework/batch/job/fixedLengthToFixedLengthJob.xml]
2019-08-11 02:01:14,240  INFO [org.springframework.beans.factory.xml.XmlBeanDefinitionReader] Loading XML bean definitions from file [/Users/dy20c/edu38/egov.file2file/target/classes/egovframework/batch/job/abstract/eGovBase.xml]
2019-08-11 02:01:14,256  INFO [org.springframework.beans.factory.xml.XmlBeanDefinitionReader] Loading XML bean definitions from file [/Users/dy20c/edu38/egov.file2file/target/classes/egovframework/batch/job/fixedLengthToIbatisJob.xml]
2019-08-11 02:01:14,270  INFO [org.springframework.beans.factory.xml.XmlBeanDefinitionReader] Loading XML bean definitions from file [/Users/dy20c/edu38/egov.file2file/target/classes/egovframework/batch/job/abstract/eGovBase.xml]
2019-08-11 02:01:14,284  INFO [org.springframework.beans.factory.xml.XmlBeanDefinitionReader] Loading XML bean definitions from file [/Users/dy20c/edu38/egov.file2file/target/classes/egovframework/batch/job/fixedLengthToJdbcJob.xml]
2019-08-11 02:01:14,296  INFO [org.springframework.beans.factory.xml.XmlBeanDefinitionReader] Loading XML bean definitions from file [/Users/dy20c/edu38/egov.file2file/target/classes/egovframework/batch/job/abstract/eGovBase.xml]
2019-08-11 02:01:14,493  INFO [org.springframework.beans.factory.support.DefaultListableBeanFactory] Overriding bean definition for bean 'delimitedToDelimitedJob.delimitedToDelimitedStep.delimitedItemReader' with a different definition: replacing [Generic bean: class [org.springframework.batch.item.file.FlatFileItemReader]; scope=step; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=false; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in file [/Users/dy20c/edu38/egov.file2file/target/classes/egovframework/batch/job/delimitedToDelimitedJob.xml]] with [Root bean: class [org.springframework.aop.scope.ScopedProxyFactoryBean]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in BeanDefinition defined in file [/Users/dy20c/edu38/egov.file2file/target/classes/egovframework/batch/job/delimitedToDelimitedJob.xml]]
2019-08-11 02:01:14,493  INFO [org.springframework.beans.factory.support.DefaultListableBeanFactory] Overriding bean definition for bean 'delimitedToDelimitedJob.delimitedToDelimitedStep.delimitedItemWriter' with a different definition: replacing [Generic bean: class [org.springframework.batch.item.file.FlatFileItemWriter]; scope=step; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=false; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in file [/Users/dy20c/edu38/egov.file2file/target/classes/egovframework/batch/job/delimitedToDelimitedJob.xml]] with [Root bean: class [org.springframework.aop.scope.ScopedProxyFactoryBean]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in BeanDefinition defined in file [/Users/dy20c/edu38/egov.file2file/target/classes/egovframework/batch/job/delimitedToDelimitedJob.xml]]
2019-08-11 02:01:14,494  INFO [org.springframework.beans.factory.support.DefaultListableBeanFactory] Overriding bean definition for bean 'fixedLengthToFixedLengthJob.fixedLengthToFixedLengthStep.fixedLengthItemReader' with a different definition: replacing [Generic bean: class [org.springframework.batch.item.file.FlatFileItemReader]; scope=step; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=false; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in file [/Users/dy20c/edu38/egov.file2file/target/classes/egovframework/batch/job/fixedLengthToFixedLengthJob.xml]] with [Root bean: class [org.springframework.aop.scope.ScopedProxyFactoryBean]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in BeanDefinition defined in file [/Users/dy20c/edu38/egov.file2file/target/classes/egovframework/batch/job/fixedLengthToFixedLengthJob.xml]]
2019-08-11 02:01:14,494  INFO [org.springframework.beans.factory.support.DefaultListableBeanFactory] Overriding bean definition for bean 'fixedLengthToFixedLengthJob.fixedLengthToFixedLengthStep.formatItemWriter' with a different definition: replacing [Generic bean: class [org.springframework.batch.item.file.FlatFileItemWriter]; scope=step; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=false; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in file [/Users/dy20c/edu38/egov.file2file/target/classes/egovframework/batch/job/fixedLengthToFixedLengthJob.xml]] with [Root bean: class [org.springframework.aop.scope.ScopedProxyFactoryBean]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in BeanDefinition defined in file [/Users/dy20c/edu38/egov.file2file/target/classes/egovframework/batch/job/fixedLengthToFixedLengthJob.xml]]
2019-08-11 02:01:14,494  INFO [org.springframework.beans.factory.support.DefaultListableBeanFactory] Overriding bean definition for bean 'fixedLengthToIbatisJob.fixedLengthToIbatisStep.fixedLengthItemReader' with a different definition: replacing [Generic bean: class [org.springframework.batch.item.file.FlatFileItemReader]; scope=step; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=false; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in file [/Users/dy20c/edu38/egov.file2file/target/classes/egovframework/batch/job/fixedLengthToIbatisJob.xml]] with [Root bean: class [org.springframework.aop.scope.ScopedProxyFactoryBean]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in BeanDefinition defined in file [/Users/dy20c/edu38/egov.file2file/target/classes/egovframework/batch/job/fixedLengthToIbatisJob.xml]]
2019-08-11 02:01:14,495  INFO [org.springframework.beans.factory.support.DefaultListableBeanFactory] Overriding bean definition for bean 'fixedLengthToJdbcJob.fixedLengthToJdbcStep.fixedLengthItemReader' with a different definition: replacing [Generic bean: class [org.springframework.batch.item.file.FlatFileItemReader]; scope=step; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=false; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in file [/Users/dy20c/edu38/egov.file2file/target/classes/egovframework/batch/job/fixedLengthToJdbcJob.xml]] with [Root bean: class [org.springframework.aop.scope.ScopedProxyFactoryBean]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in BeanDefinition defined in file [/Users/dy20c/edu38/egov.file2file/target/classes/egovframework/batch/job/fixedLengthToJdbcJob.xml]]
2019-08-11 02:01:14,503  INFO [org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor] JSR-330 'javax.inject.Inject' annotation found and supported for autowiring
2019-08-11 02:01:14,528  INFO [org.springframework.context.support.PostProcessorRegistrationDelegate$BeanPostProcessorChecker] Bean 'jobRegistry' of type [org.springframework.batch.core.configuration.support.MapJobRegistry] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2019-08-11 02:01:14,568  INFO [org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseFactory] Starting embedded database: url='jdbc:hsqldb:mem:dataSource-hsql', username='sa'
2019-08-11 02:01:14,635  INFO [org.springframework.jdbc.datasource.init.ScriptUtils] Executing SQL script from class path resource [db/sampledb.script]
2019-08-11 02:01:14,650  INFO [org.springframework.jdbc.datasource.init.ScriptUtils] Executed SQL script from class path resource [db/sampledb.script] in 14 ms.
2019-08-11 02:01:14,837  INFO [org.springframework.batch.core.repository.support.JobRepositoryFactoryBean] No database type set, using meta data indicating: HSQL
2019-08-11 02:01:14,854  INFO [org.springframework.batch.core.launch.support.SimpleJobLauncher] No TaskExecutor has been set, defaulting to synchronous executor.
2019-08-11 02:01:15,068  INFO [org.springframework.batch.core.launch.support.SimpleJobLauncher] Job: [FlowJob: [name=delimitedToDelimitedJob]] launched with the following parameters: [{timestamp=1565456473982}]
2019-08-11 02:01:15,077  INFO [org.springframework.batch.core.job.SimpleStepHandler] Executing step: [delimitedToDelimitedStep]
2019-08-11 02:01:15,119  INFO [org.springframework.batch.core.launch.support.SimpleJobLauncher] Job: [FlowJob: [name=delimitedToDelimitedJob]] completed with the following parameters: [{timestamp=1565456473982}] and the following status: [COMPLETED]
2019-08-11 02:01:15,119  WARN [egovframework.rte.bat.core.launch.support.EgovCommandLineRunner] EgovCommandLineRunner's Job Information
2019-08-11 02:01:15,120  WARN [egovframework.rte.bat.core.launch.support.EgovCommandLineRunner] jobName=delimitedToDelimitedJob
2019-08-11 02:01:15,120  WARN [egovframework.rte.bat.core.launch.support.EgovCommandLineRunner] jobParamters={timestamp=1565456473982}
2019-08-11 02:01:15,120  WARN [egovframework.rte.bat.core.launch.support.EgovCommandLineRunner] jobExecutionTime=0.049s
2019-08-11 02:01:15,120  INFO [org.springframework.context.support.ClassPathXmlApplicationContext] Closing org.springframework.context.support.ClassPathXmlApplicationContext@b59d31: startup date [Sun Aug 11 02:01:14 KST 2019]; root of context hierarchy
2019-08-11 02:01:15,126  INFO [org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseFactory] Shutting down embedded database: url='jdbc:hsqldb:mem:dataSource-hsql'

point is 아래 부분입니다.

[org.springframework.batch.core.launch.support.SimpleJobLauncher] No TaskExecutor has been set, defaulting to synchronous executor.
[org.springframework.batch.core.launch.support.SimpleJobLauncher] Job: [FlowJob: [name=delimitedToDelimitedJob]] launched with the following parameters: [{timestamp=1565456473982}]
[org.springframework.batch.core.job.SimpleStepHandler] Executing step: [delimitedToDelimitedStep]
[org.springframework.batch.core.launch.support.SimpleJobLauncher] Job: [FlowJob: [name=delimitedToDelimitedJob]] completed with the following parameters: [{timestamp=1565456473982}] and the following status: [COMPLETED]
[egovframework.rte.bat.core.launch.support.EgovCommandLineRunner] EgovCommandLineRunner's Job Information
[egovframework.rte.bat.core.launch.support.EgovCommandLineRunner] jobName=delimitedToDelimitedJob
[egovframework.rte.bat.core.launch.support.EgovCommandLineRunner] jobParamters={timestamp=1565456473982}
[egovframework.rte.bat.core.launch.support.EgovCommandLineRunner] jobExecutionTime=0.049s

Job 내용을 어떻게 수행했는지 찾아보겠습니다.

delimitedToDelimitedJob 잡을 수행했기 때문에,  src/main/resourcs/egovframework/job/delimitedToDelimitedJob.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"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
		http://www.springframework.org/schema/batch http://www.springframework.org/schema/batch/spring-batch-3.0.xsd">

	<import resource="abstract/eGovBase.xml" />

	<job id="delimitedToDelimitedJob" parent="eGovBaseJob" xmlns="http://www.springframework.org/schema/batch">
		<step id="delimitedToDelimitedStep" parent="eGovBaseStep">
			<tasklet>
				<chunk reader="delimitedToDelimitedJob.delimitedToDelimitedStep.delimitedItemReader"
					processor="delimitedToDelimitedJob.delimitedToDelimitedStep.itemProcessor"
					writer="delimitedToDelimitedJob.delimitedToDelimitedStep.delimitedItemWriter"
					commit-interval="2" />
			</tasklet>
		</step>
	</job>

	<bean id="delimitedToDelimitedJob.delimitedToDelimitedStep.delimitedItemReader"
		class="org.springframework.batch.item.file.FlatFileItemReader" scope="step">
		<property name="resource" value="file:./src/main/resources/egovframework/batch/data/inputs/csvData.csv" />
		<property name="lineMapper">
			<bean class="egovframework.rte.bat.core.item.file.mapping.EgovDefaultLineMapper">
				<property name="lineTokenizer">
					<bean class="egovframework.rte.bat.core.item.file.transform.EgovDelimitedLineTokenizer">
						<property name="delimiter" value="," />
					</bean>
				</property>
				<property name="objectMapper">
					<bean class="egovframework.rte.bat.core.item.file.mapping.EgovObjectMapper">
						<property name="type"
							value="egovframework.example.bat.domain.trade.CustomerCredit" />
						<property name="names" value="name,credit" />
					</bean>
				</property>
			</bean>
		</property>
	</bean>

	<bean id="delimitedToDelimitedJob.delimitedToDelimitedStep.delimitedItemWriter"
	    class="org.springframework.batch.item.file.FlatFileItemWriter" scope="step">
		<property name="resource" value="file:./target/test-outputs/csvOutput.csv" />
		<property name="lineAggregator">
			<bean class="org.springframework.batch.item.file.transform.DelimitedLineAggregator">
				<property name="delimiter" value="," />
				<property name="fieldExtractor">
					<bean class="egovframework.rte.bat.core.item.file.transform.EgovFieldExtractor">
						<property name="names" value="name,credit" />
					</bean>
				</property>
			</bean>
		</property>
	</bean>

	<bean id="delimitedToDelimitedJob.delimitedToDelimitedStep.itemProcessor"
	    class="egovframework.example.bat.domain.trade.CustomerCreditIncreaseProcessor" />

</beans>

./src/main/resources/egovframework/batch/data/inputs/csvData.csv

파일을 아래와 같은 방식으로 읽었습니다. (논리적인 구조 파악 필요) 

FlatFile ItemReader

플랫파일은 2차원 데이터를 포함하는 유형의 파일이다. 스프링 배치 프레임워크에서는 플랫파일을 읽고 파싱하는 기본적인 기능을 제공하는 FlatFileItemReader 클래스를 통해 플랫파일에 대한 읽기 처리를 합니다. 

FlatFileItemReader

FlatFileItemReader는 Resource, LineMapper, FieldSetMapper, LineTokenizer에 기본적으로 의존성을 갖으며, LineTokenizer에 따라 구분자(Delimited)와 고정길이(Fixed Length) 방식으로 FlatFileItemReader를 사용할 수 있습니다, 

 

구분데이터 형태설명

LineMapper 플랫파일 1 라인(String) → Object 플랫파일 데이터에서 읽은 1 라인(String)을 Object로 변환하는 총 과정(LineTokenizer, FieldSetMapper 과정을 포함한다.)
LineTokenizer String → Tokens → FieldSet 플랫파일에서 읽은 1 라인(String)을 구분자 방식 또는 고정길이 방식으로 토크나이징 한 후 FieldSet 형태로 변환하는 과정 
- DelimitedLineTokenizer (구분자) : 1 라인의 String을 구분자 기준으로 나누어 토큰화 하는 방식 
- FixedLengthTokenizer (고정길이) : 1 라인의 String을 사용자가 설정한 고정길이 기준으로 나누어 토큰화 하는 방식
FieldSetMapper FieldSet → Object FieldSet 형태의 데이터를 원하는 Object로 변환하는 과정

그럼 어떻게 썼을까요?

FlatFile ItemWriter

FlatFileItemWriter는 Resource, LineAggregator에 기본적으로 의존성을 갖으며, LineAggregator에 따라 구분자(Delimited)와 고정길이(Fixed Length) 방식으로 쓸 수 있다. 

 

구분데이터 형태설명

LineAggregator Item → String ItemReader, ItemProcessor 과정을 거친 Item을 1 라인의 String으로 변환하는 총 과정(FieldExtractor 과정을 포함한다.) 
- DelimitedLineAggregator (구분자) : Item의 Field와 Field 사이에 구분자를 삽입하여 1라인의 String으로 변환하는 과정 
- FormatterLineAggregator(고정길이 포맷) : Item의 Field를 사용자가 설정한 포맷 기준으로 1라인의 String으로 변환하는 과정
FieldExtractor Item → Fields Item에서 Field 값들을 꺼내어 Object[]로 변환하는 과정

아래 Delimited(구분자), Fixed Length(고정길이) 방식으로 설정한 FlatFileItemWriter의 예시를 통해 FlatFileItemWriter, LineAggregator, FieldExtractor의 의존 관계를 볼 수 있습니다.

 

위의 방식으로 씌여진거죠. 

메커니즘 구조를 알면 파일을 읽고 쓰는 방식을 알 수 있죠.

 

file:./target/test-outputs/csvOutput.csv 에 파일이 생겼을까요?

아직 Run을 돌리기전 target 부분이 생기기 전입니다. Batch를 돌린 후 폴더 Refresh나 폴더를 찾아가보면, 아래와 같이 파일이 생겼으며 확인 또한 가능합니다.

파일을 열어보면, 입력 데이터가 변경이 되었음을 알 수 있습니다.

읽어들이는 ItemReader의 첫 데이터 파일

쓰는 ItemWriter의 변경된 데이터 파일

왜 바꼈을까요?

표준프레임워크는 Reader와 Writer의 표준을 제공하지만, 개발자가 Business 로직을 통해 데이터를 변경할 수 있습니다.

 

Processor는 요녀석입니다.  

<bean id="delimitedToDelimitedJob.delimitedToDelimitedStep.itemProcessor"
	    class="egovframework.example.bat.domain.trade.CustomerCreditIncreaseProcessor" />

클래스를 가보시면 아래와 같은 비즈니스 로직이 있습니다. 해당 클래스 로직을 통해 데이터를 읽어들인 후 Processor를 통해 비즈니스로직을 수행해 데이터를 다시 Write 하는 걸 알 수 있습니다. 

 

/*
 * Copyright 2006-2007 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package egovframework.example.bat.domain.trade;

import java.math.BigDecimal;

import org.springframework.batch.item.ItemProcessor;

/**
 * @author 배치실행개발팀
 * @since 2012. 07.25
 * @version 1.0
 * @see
 *  <pre>
 *      개정이력(Modification Information)
 *
 *   수정일      수정자           수정내용
 *  ------- -------- ---------------------------
 *  2012. 07.25  배치실행개발팀     최초 생성
 *  </pre>
 */
public class CustomerCreditIncreaseProcessor implements ItemProcessor<CustomerCredit, CustomerCredit> {

	// 증가할 수
	public static final BigDecimal FIXED_AMOUNT = new BigDecimal("5");

	/**
	 * FIXED_AMOUNT만큼 증가 시킨 후 return
	 */
	@Override
	public CustomerCredit process(CustomerCredit item) throws Exception {
		return item.increaseCreditBy(FIXED_AMOUNT);
	}
}

 

그럼 템플릿을 만들어봤는데,  충분히 이해되셨을거라 생각하고 이만 마치겠습니다~