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: