Spring Batch 之 Sample(CSV文件操作(zuo))(四)
本文(wen)將通過一個(ge)完整(zheng)的(de)實(shi)例,與大(da)家一起討論(lun)運用Spring Batch對CSV文(wen)件的(de)讀寫操作。此(ci)實(shi)例的(de)流程是(shi):讀取(qu)一個(ge)含(han)有四個(ge)字(zi)段的(de)CSV文(wen)件(ID,Name,Age,Score),對讀取(qu)的(de)字(zi)段做簡單的(de)處理,然后輸出到另外一個(ge)CSV文(wen)件中。
工程結構如下圖:

JobLaunch類(lei)用(yong)來(lai)啟動Job, CsvItemProcessor類(lei)用(yong)來(lai)對Reader取(qu)得(de)的數據(ju)(ju)進行處理, Student類(lei)是(shi)一個(ge)POJO類(lei),用(yong)來(lai)存(cun)放映射的數據(ju)(ju)。 inputFile.csv是(shi)數據(ju)(ju)讀取(qu)文(wen)件(jian), outputFile.csv是(shi)數據(ju)(ju)輸出文(wen)件(jian)。
application.xml文(wen)件配置如前篇文(wen)章(zhang),不再贅(zhui)述。
batch.xml文件(jian)中Job配置如下(xia):
<job id="csvJob">
<step id="csvStep">
<tasklet transaction-manager="transactionManager">
<chunk reader="csvItemReader" writer="csvItemWriter" processor="csvItemProcessor" commit-interval="1">
</chunk>
</tasklet>
</step>
</job>
這(zhe)個(ge)文件(jian)里配置了(le)這(zhe)次運行(xing)的JOB:csvJob。本Job包(bao)含(han)一個(ge)Step,完(wan)成(cheng)一個(ge)完(wan)整(zheng)的CSV文件(jian)讀寫功能。分別(bie)由 csvItemReader完(wan)成(cheng)CSV文件(jian)的讀操(cao)(cao)作,由 csvItemProcessor完(wan)成(cheng)對(dui)取得數據的處理,由 csvItemWriter完(wan)成(cheng)對(dui)CSV文件(jian)的寫操(cao)(cao)作。
batch.xml文件中csvItemReader配置如(ru)下:
<!-- 讀取(qu)csv文(wen)件 -->
<bean:bean id="csvItemReader"
class="org.springframework.batch.item.file.FlatFileItemReader" scope="step">
<bean:property name="resource" value="classpath:inputFile.csv"/>
<bean:property name="lineMapper">
<bean:bean
class="org.springframework.batch.item.file.mapping.DefaultLineMapper">
<bean:property name="lineTokenizer" ref="lineTokenizer"/>
<bean:property name="fieldSetMapper">
<bean:bean
class="org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper">
<bean:property name="prototypeBeanName" value="student"></bean:property>
</bean:bean>
</bean:property>
</bean:bean>
</bean:property>
</bean:bean>
<bean:bean id="student" class="com.wanggc.springbatch.sample.csv.Student"></bean:bean>
<!-- lineTokenizer -->
<bean:bean id="lineTokenizer" class="org.springframework.batch.item.file.transform.DelimitedLineTokenizer">
<bean:property name="delimiter" value=","/>
<bean:property name="names">
<bean:list>
<bean:value>ID</bean:value>
<bean:value>name</bean:value>
<bean:value>age</bean:value>
<bean:value>score</bean:value>
</bean:list>
</bean:property>
</bean:bean>
csvItemReader實現的(de)(de)(de)是Spring Batch提供(gong)FlatFileItemReader類(lei),此類(lei)主要用于(yu)Flat文(wen)件(jian)的(de)(de)(de)讀操(cao)作。它包含兩個(ge)必要的(de)(de)(de)屬性 resource和 lineMapper。前者指定要讀取的(de)(de)(de)文(wen)件(jian)的(de)(de)(de)位置,后者是將(jiang)文(wen)件(jian)的(de)(de)(de)每(mei)一行(xing)映(ying)射成一個(ge)Pojo對象。其中 lineMapper也(ye)有兩個(ge)重要屬性 lineTokenizer和 fieldSetMapper, lineTokenizer將(jiang)文(wen)件(jian)的(de)(de)(de)一行(xing)分解(jie)成一個(ge) FieldSet,然后由 fieldSetMapper映(ying)射成Pojo對象。
這(zhe)種(zhong)方(fang)式與(yu)DB的(de)(de)讀操作(zuo)非常類似(si)(si)。lineMapper類似(si)(si)于(yu)ResultSet,文(wen)件(jian)中的(de)(de)一(yi)行(xing)類似(si)(si)于(yu)Table中的(de)(de)一(yi)條(tiao)記(ji)錄,被封裝成的(de)(de)FieldSet,類似(si)(si)于(yu)RowMapper。至于(yu)怎么將(jiang)一(yi)條(tiao)記(ji)錄封裝,這(zhe)個工作(zuo)由lineTokenizer的(de)(de)繼承類DelimitedLineTokenizer完(wan)成。DelimitedLineTokenizer的(de)(de)delimiter屬性(xing)決定文(wen)件(jian)的(de)(de)一(yi)行(xing)數據(ju)按(an)照(zhao)(zhao)什么分(fen)(fen)解(jie),默認的(de)(de)是“,”, names屬性(xing)標示(shi)分(fen)(fen)解(jie)的(de)(de)每個字(zi)(zi)段(duan)的(de)(de)名(ming)(ming)字(zi)(zi),傳給(gei)fieldSetMapper(本實例(li)用(yong)的(de)(de)是BeanWrapperFieldSetMapper)的(de)(de)時候,就可以按(an)照(zhao)(zhao)這(zhe)個名(ming)(ming)字(zi)(zi)取得相應的(de)(de)值。fieldSetMapper的(de)(de)屬性(xing)prototypeBeanName,是映(ying)(ying)射(she)Pojo類的(de)(de)名(ming)(ming)字(zi)(zi)。設置了此屬性(xing)后,框(kuang)架就會將(jiang)lineTokenizer分(fen)(fen)解(jie)成的(de)(de)一(yi)個FieldSet映(ying)(ying)射(she)成Pojo對(dui)象,映(ying)(ying)射(she)是按(an)照(zhao)(zhao)名(ming)(ming)字(zi)(zi)來完(wan)成的(de)(de)(lineTokenizer分(fen)(fen)解(jie)時標注的(de)(de)名(ming)(ming)字(zi)(zi)與(yu)Pojo對(dui)象中字(zi)(zi)段(duan)的(de)(de)名(ming)(ming)字(zi)(zi)對(dui)應)。
總之,FlatFileItemReader讀(du)取(qu)(qu)一(yi)條記錄(lu)由以下四步(bu)完成:1,從(cong)resource指定的(de)文件中讀(du)取(qu)(qu)一(yi)條記錄(lu);2,lineTokenizer將這條記錄(lu)按照delimiter分解成Fileset,每個字(zi)段的(de)名字(zi)由names屬(shu)性取(qu)(qu)得;3,將分解成的(de)Fileset傳遞給fieldSetMapper,由其按照名字(zi)映射成Pojo對(dui)象(xiang);4,最終由FlatFileItemReader將映射成的(de)Pojo對(dui)象(xiang)返回(hui),框架將返回(hui)的(de)對(dui)象(xiang)傳遞給Processor。
csvItemProcessor實現的(de)(de)是ItemProcessor類。此(ci)(ci)類接受Reader映射成的(de)(de)Pojo對(dui)象,可以對(dui)此(ci)(ci)對(dui)象做(zuo)相應(ying)的(de)(de)業(ye)務邏輯處(chu)理(li),然(ran)后(hou)返(fan)回,框(kuang)架就會將返(fan)回的(de)(de)結果傳遞給Writer進行寫操作。具體實現代碼(ma)如下:
package com.wanggc.springbatch.sample.csv;
import org.springframework.batch.item.ItemProcessor;
import org.springframework.stereotype.Component;
/**
* ItemProcessor類。
*/
@Component("csvItemProcessor")
public class CsvItemProcessor implements ItemProcessor<Student, Student> {
/**
* 對取到的數據進行簡單的處理。
*
* @param student
* 處理前的數據。
* @return 處理后的數據。
* @exception Exception
* 處理是發生的任何異常。
*/
@Override
public Student process(Student student) throws Exception {
/* 合并(bing)ID和名字 */
student.setName(student.getID() + "--" + student.getName());
/* 年齡(ling)加2 */
student.setAge(student.getAge() + 2);
/* 分數加10 */
student.setScore(student.getScore() + 10);
/* 將處(chu)理后的結果傳遞給(gei)writer */
return student;
}
}
batch.xml文(wen)件中csvItemReader配(pei)置如(ru)下:
<!-- 寫CSV文件 -->
<bean:bean id="csvItemWriter"
class="org.springframework.batch.item.file.FlatFileItemWriter" scope="step">
<bean:property name="resource" value="file:src/outputFile.csv"/>
<bean:property name="lineAggregator">
<bean:bean
class="org.springframework.batch.item.file.transform.DelimitedLineAggregator">
<bean:property name="delimiter" value=","></bean:property>
<bean:property name="fieldExtractor">
<bean:bean
class="org.springframework.batch.item.file.transform.BeanWrapperFieldExtractor">
<bean:property name="names" value="name,age,score"></bean:property>
</bean:bean>
</bean:property>
</bean:bean>
</bean:property>
</bean:bean>
csvItemWriter實(shi)現的(de)是FlatFileItemWriter類(lei)。此類(lei)與FlatFileItemReader類(lei)相(xiang)似(si)(si),也有兩個(ge)重要的(de)屬(shu)(shu)性:resource和lineAggregator。前者(zhe)是要輸出(chu)的(de)文件的(de)路徑,后者(zhe)和lineTokenizer類(lei)似(si)(si)。lineAggregator(本(ben)實(shi)例用DelimitedLineAggregator類(lei))也有兩個(ge)重要的(de)屬(shu)(shu)性:delimiter和fieldExtractor。Delimiter標示輸出(chu)的(de)字(zi)段(duan)以什(shen)么分(fen)割,后者(zhe)將(jiang)Pojo對象(xiang)(xiang)組裝成(cheng)(cheng)由Pojo對象(xiang)(xiang)的(de)字(zi)段(duan)組成(cheng)(cheng)的(de)一(yi)(yi)個(ge)字(zi)符(fu)串(chuan)(chuan)。同(tong)樣FlatFileItemWriter寫一(yi)(yi)條記錄(lu)也有以下(xia)四步完成(cheng)(cheng):1,Processor傳遞過來(lai)一(yi)(yi)個(ge)對象(xiang)(xiang)給lineAggregator;2,lineAggregator將(jiang)其這個(ge)對象(xiang)(xiang)轉化成(cheng)(cheng)一(yi)(yi)個(ge)數(shu)組;3,再由lineAggregator的(de)屬(shu)(shu)性fieldExtractor將(jiang)數(shu)組轉化成(cheng)(cheng)按照delimiter分(fen)割一(yi)(yi)個(ge)字(zi)符(fu)串(chuan)(chuan);4,將(jiang)這個(ge)字(zi)符(fu)串(chuan)(chuan)輸出(chu)。
這(zhe)樣(yang),一條數據的讀、處理、寫操作(zuo)就基本(ben)完成了(le)。當然,讀和(he)寫也(ye)可(ke)以自(zi)己寫類來(lai)處理,只是要(yao)注意繼(ji)承(cheng)FlatFileItemReader和(he)FlatFileItemWriter就可(ke)以了(le)。
實例中用(yong)到的Student類代碼如(ru)下:
package com.wanggc.springbatch.sample.csv;
/** Pojo類(lei)_Student */
public class Student {
/** ID */
private String ID = "";
/** 名(ming)字 */
private String name = "";
/** 年(nian)齡 */
private int age = 0;
/** 分數 */
private float score = 0;
/*getter 和(he)setter已刪除*/
}
實例中用(yong)到的(de)輸入數(shu)據如下:

實(shi)例輸出結果如下:

本(ben)文(wen)的配(pei)置要注意以下兩點:
1, 注意(yi)Writer的resource要(yao)寫成(cheng)“file:******”形式,不能用“classpath:******”形式。
2, 如果將Job配置中commit-interval屬性配置為大于1時,每次commit的都是最后一條記錄,前面讀取的被覆蓋了。具體原因不明,如果將Reader的fieldSetMapper屬性自己重寫,就可以解決這個問題。(注:student bean添加scope屬性可以解決此問題:scope:"prototype".2011/12/16)
下次,將和大家一(yi)起討(tao)論關于XML文件的讀寫(xie)問(wen)題(ti)。
歡迎轉載,請注明出處!
感謝您的閱讀,請關注后續博客!
共享視頻教程請訪問:
