Načtení csv souboru pomocí FlatFileItemReader ve Spring Batch

Tento příspěvek volně navazuje na článek Spring Batch 3. díl, ve kterém byla ukázána práce s chunk a definovanými objekty reader, processor, writer. V dnešním příspěvku se seznámíme s tím, jak načítat data z .csv souboru pomocí FlatFileItemReaderu. FlatFileItemReader je ItemReader, který čte řádky ze vstupu. Vstup je určen elementem <property name="resource" value="classpath:reportCS.csv"/>. V tomto případě se bude číst ze souboru reportCS.csv, který se nachází na classpath.

Prostý databázový soubor (též plochý databázový soubor, anglicky flat file database) je jednoduchá databáze (většinou tabulka) uložená v textovém souboru ve formě prostého textu. Takový soubor může mít příponu například .txt, .ini, .conf, ale i .dbf apod. Zdroj: cs.wikipedia.org

Zde je ukázka FlatFileItemReaderu.

<bean id="csvFileItemReader" class="org.springframework.batch.item.file.FlatFileItemReader">
	<property name="resource" value="classpath:reportCS.csv"/>
	<property name="encoding" value="UTF-8"/>
	<property name="linesToSkip" value="1"/>
	<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="names" value="Name,Surname,Location,Job,Earning"/>
				</bean>
			</property>
			<property name="fieldSetMapper">
				<bean class="org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper">
					<property name="prototypeBeanName" value="employee"></property>
				</bean>
			</property>
		</bean>
	</property>
</bean>

resource: zdroj dat pro čtení
encoding: kódování zdrojového souboru
linesToSkip: nastaví počet řádků souboru, které se mají přeskočit před začátkem čtení
lineMapper: nastaví line mapper, mapování načteného řádku

V tomto příkladu používám DefaultLineMapper, který má následující property.
lineTokenizer: nastaví line tokenizer, který určuje způsob, jakým je řetězec (načtená řádka) rozdělen, zde se používá výchozí oddělovač čárka
fieldSetMapper: nastaví mapování dat do objektu, zde se data mapují na jednotlivé fieldy objektu

Zde je jednoduchý Spring Batch projekt, který používá výše uvedený FlatFileItemReader.

Struktura projektu

│   pom.xml
│
└───src
    ├───main
    │   └───java
    │       └───batch
    │               Employee.java
    │               ExecuteBatchJob.java
    │               ResultWriter.java
    │
    └───resources
            context.xml
            jobs.xml
            reportCS.csv

context.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-3.2.xsd">
	
	<bean id="jobRepository" class="org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean">
		<property name="transactionManager" ref="transactionManager" />
	</bean>
	
	<bean id="jobLauncher" class="org.springframework.batch.core.launch.support.SimpleJobLauncher">
		<property name="jobRepository" ref="jobRepository" />
	</bean>
	
	<bean id="transactionManager" class="org.springframework.batch.support.transaction.ResourcelessTransactionManager" />
</beans>

jobs.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:batch="http://www.springframework.org/schema/batch"
	xmlns:task="http://www.springframework.org/schema/task"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/batch
	http://www.springframework.org/schema/batch/spring-batch-2.2.xsd
	http://www.springframework.org/schema/beans
	http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">
	
	<import resource="context.xml" />
	
	<batch:job id="myJob">
		<batch:step id="myStep">
			<batch:tasklet>
				<batch:chunk reader="csvFileItemReader" writer="resultWriter" commit-interval="2"/>
			</batch:tasklet>
		</batch:step>
	</batch:job>
	
	<bean id="employee" class="batch.Employee" scope="prototype"/>
	<bean id="resultWriter" class="batch.ResultWriter"/>
	<bean id="csvFileItemReader" class="org.springframework.batch.item.file.FlatFileItemReader">
		<property name="resource" value="classpath:reportCS.csv"/>
		<property name="encoding" value="UTF-8"/>
		<property name="linesToSkip" value="1"/>
		<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="names" value="Name,Surname,Location,Job,Earning"/>
					</bean>
				</property>
				<property name="fieldSetMapper">
					<bean class="org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper">
						<property name="prototypeBeanName" value="employee"></property>
					</bean>
				</property>
			</bean>
		</property>
	</bean>
</beans>

reportCS.csv

Name,Surname,Location,Job,Earning
Václav,Zelený,Čáslav,skladník,22000
Jana,Skočdopolová,Praha,účetní,27900
Jiří,Nohavička,Aš,prodavač,19000
Jan,Starý,Náměšť nad Oslavou,manažer,33000
Tereza,Nová,Benešov,kuchařka,14000

V ItemReaderu přeskakujeme první řádek proto, abychom nenačetli popis Name,Surname,Location,Job,Earning.

Employee.java představuje třídu, do jejichž instancí se bude mapovat načtený vstup. Každý řádek do nové instance. To, že se pokaždé vytvoří nová instance je díky nastavení scope na prototype: <bean id="employee" class="batch.Employee" scope="prototype"/>. Více o singleton a prototype ve Springu najdete v tomto příspěvku.

public class Employee {

	private String name;
	private String surname;
	private String location;
	private String job;
	private String earning;

	// setters and getters
}

ResultWriter.java pouze vypíše získané informace do konzole.

import java.util.List;

import org.springframework.batch.item.ItemWriter;

public class ResultWriter implements ItemWriter{

	public void write(List<? extends Employee> items) throws Exception {
		System.out.println("Writing");
		for (Employee item : items) {
			System.out.println(item.getSurname() + "\t" + item.getEarning());
		}
	}
}

ExecuteBatchJob.java

import org.apache.log4j.Logger;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class ExecuteBatchJob {
	static Logger logger = Logger.getLogger("ExecuteBatchJob");

	public static void main(String[] args) {
		String[] springConfig = {"jobs.xml"};
		ApplicationContext context = new ClassPathXmlApplicationContext(springConfig);
		
		JobLauncher jobLauncher = (JobLauncher) context.getBean("jobLauncher");
		Job job = (Job) context.getBean("myJob");
		
		try {
			JobExecution execution = jobLauncher.run(job, new JobParameters());
			logger.info("Exit Status: " + execution.getStatus());
			logger.info("Start: " + execution.getStartTime() + "\tEnd: " + execution.getEndTime());
			logger.info("Exception list size: " + execution.getAllFailureExceptions().size());
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (context != null) {
				context = null;
			}
		}
		logger.info("Done");
	}
}

Výsledek spuštění.

Writing
Zelený	        22000
Skočdopolová	27900
Writing
Nohavička	19000
Starý	        33000
Writing
Nová	        14000
2016-12-27 16:17:41,128 INFO  ExecuteBatchJob -Exit Status: COMPLETED
2016-12-27 16:17:41,128 INFO  ExecuteBatchJob -Start: Tue Dec 27 16:17:41 CET 2016	End: Tue Dec 27 16:17:41 CET 2016
2016-12-27 16:17:41,128 INFO  ExecuteBatchJob -Exception list size: 0
2016-12-27 16:17:41,129 INFO  ExecuteBatchJob -Done

Commit interval je nastaven na 2, proto se vykonává po dvou. ResultWriter před každým zavoláním vypíše „Writing“.


Zdroj:

Napsat komentář