さて、今回は実際にStepタグを使用して処理を記述してみましょう!
まず、準備はこちら。サンプルではDB名を「sample」にしています。順次環境に合わせて名前を変えてください。
・Spring Batchを使えるようにするには? (準備編)
※DBも作成してください。(サンプルのdataSourceはPostgresになっています)
今回はRepositoryにDBも使用する予定ですが、もうひとつの記事を組み合わせてDBを使用しない方法に修正してもらってもよいです。
処理内容:
1.CSVファイルを読み込む
2.読み込んだデータのフィールドの順番と、日付の書式を変更してファイルに出力
3.上記2のファイルをコピーし増やす
では、早速いってみましょう!
【注意点等】
ファイルは、sampleパッケージを作成し、そこにおいてください。
また、上記の「Spring Batchを起動するには?」のようにeclipseからコマンドラインで実行してください。
このときDBを使用する場合、前回の結果が残るのでeclipse上の実行設定で引数タブを
以下のように設定してください。(参考 )
classpath:/sample/run-context.xml jobFile -next
【/sample/test.txt ファイル (データファイル)】
2010/08/21,太郎,18
2007/11/01,次郎,29
2009/03/12,さんま,53
2010/07/07,タモリ,54
2010/12/24,鶴野,28
【/sample/back-context.xml ファイル(Batchバックグラウンドの設定)】 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans " xmlns:p="http://www.springframework.org/schema/p " 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.1.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd "> <bean id="jobOperator" class="org.springframework.batch.core.launch.support.SimpleJobOperator" p:jobLauncher-ref="jobLauncher" p:jobExplorer-ref="jobExplorer" p:jobRepository-ref="jobRepository" p:jobRegistry-ref="jobRegistry" /> <bean id="jobExplorer" class="org.springframework.batch.core.explore.support.JobExplorerFactoryBean" p:dataSource-ref="dataSource" /> <bean id="jobRegistry" class="org.springframework.batch.core.configuration.support.MapJobRegistry" /> <bean class="org.springframework.batch.core.configuration.support.JobRegistryBeanPostProcessor"> <property name="jobRegistry" ref="jobRegistry"/> </bean> <bean id="jobLauncher" class="org.springframework.batch.core.launch.support.SimpleJobLauncher"> <property name="jobRepository" ref="jobRepository" /> </bean> <bean id="jobRepository" class="org.springframework.batch.core.repository.support.JobRepositoryFactoryBean" p:dataSource-ref="dataSource" p:transactionManager-ref="transactionManager" /> <!-- DB関連クラス --> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="org.postgresql.Driver"/> <property name="url" value="jdbc:postgresql://localhost/sample?useUnicode=true&characterEncoding=utf-8" /> <property name="username" value="postgres" /> <property name="password" value="postgres" /> </bean> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> </beans> 【/sample/run-context.txt ファイル(ジョブの処理の記述)】 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans " xmlns:p="http://www.springframework.org/schema/p " 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.1.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd "> <import resource="classpath:/sample/back-context.xml"/> <!-- ジョブの処理 --> <job id="jobFile" xmlns="http://www.springframework.org/schema/batch " incrementer="jobParametersIncrementer"> <step id="step1" parent="simpleStep" next="step2"> <tasklet > <chunk reader="fileItemReader" writer="fileItemWriter" /> </tasklet> </step> <step id="step2"> <tasklet ref="copyFile" /> </step> </job> <!-- enables the functionality of JobOperator.startNextInstance(jobName) --> <bean id="jobParametersIncrementer" class="org.springframework.batch.core.launch.support.RunIdIncrementer" /> <bean id="simpleStep" class="org.springframework.batch.core.step.item.SimpleStepFactoryBean" abstract="true"> <property name="jobRepository" ref="jobRepository" /> <property name="startLimit" value="100" /> <property name="commitInterval" value="1" /> </bean> <!-- ファイルを読み込みMapに入れる --> <bean id="fileItemReader" class="org.springframework.batch.item.file.FlatFileItemReader" scope="step"> <property name="resource" value="classpath:/sample/test.txt" /> <property name="encoding" value="shift_jis" /> <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="date,name,age" /> </bean> </property> <property name="fieldSetMapper"> <bean class="sample.SimpleMapFieldSetMapper" > <property name="types" value="Date,String,int" /> </bean> </property> </bean> </property> </bean> <!-- Mapをvalues()に展開(Extract)し、フォーマット変換した結果をファイルに出力していく --> <bean id="fileItemWriter" class="org.springframework.batch.item.file.FlatFileItemWriter"> <property name="resource" value="file:c:/temp/spring_batch-test.txt" /> <property name="lineAggregator"> <bean class="org.springframework.batch.item.file.transform.FormatterLineAggregator"> <property name="fieldExtractor"> <bean class="org.springframework.batch.item.file.transform.PassThroughFieldExtractor" > </bean> </property> <property name="format" value="%2$s,%1$tY%1$tm%1$td,%3$d" /> </bean> </property> </bean> <!-- taskletタイプのクラス。DOSコマンドのファイルコピーを実施 --> <bean id="copyFile" class="org.springframework.batch.core.step.tasklet.SystemCommandTasklet"> <property name="command" value="cmd /C copy c:\temp\spring_batch-test.txt c:\temp\spring_batch-test2.txt" /> <property name="timeout" value="10000" /> </bean> </beans> 【SimpleMapFieldSetMapperクラス】 package sample; import java.util.LinkedHashMap; import org.springframework.batch.item.file.mapping.FieldSetMapper; import org.springframework.batch.item.file.transform.FieldSet; import org.springframework.validation.BindException; /** * LinkedHashMapにマップします。 * */ public class SimpleMapFieldSetMapper implements FieldSetMapper<LinkedHashMap<String, Object>> { private String[] types; public String[] getTypes() { return types; } public void setTypes(String[] types) { this.types = types; } @Override public LinkedHashMap<String, Object> mapFieldSet(FieldSet fs) throws BindException { // LinkedHashMap<String, Object> map = new LinkedHashMap<String, Object>(); String[] names = fs.getNames(); for(int i=0; i< names.length; ++i){ map.put(names[i], read(fs, i)); } return map; } protected Object read(FieldSet fs, int i){ String type = this.types[i]; if(type.equals("Date")) return fs.readDate(i, "yyyy/MM/dd"); if(type.equals("int")) return fs.readInt(i); return fs.readString(i); } }
【実行結果】
コンソール上の出力:
2010/08/17 23:32:56.281 [] INFO :: Job: [FlowJob: [name=jobFile]]
launched with the following parameters: [{run.id=1}]
2010/08/17 23:32:56.546 [] INFO :: Executing step: [step1]
2010/08/17 23:32:57.031 [] INFO :: Executing step: [step2]
2010/08/17 23:32:58.093 [] INFO :: Job: [FlowJob: [name=jobFile]]
completed with the following parameters: [{run.id=1}] and the following status: [COMPLETED]
ファイル出力:
C:/temp/spring_batch-test.txt
C:/temp/spring_batch-test2.txt (←step2で上のファイルをコピーした結果作成されます)
入力ファイルの内容:
2010/08/21,太郎,18
2007/11/01,次郎,29
2009/03/12,さんま,53
2010/07/07,タモリ,54
2010/12/24,鶴野,28
出力ファイルの内容:
太郎,20100821,18
次郎,20071101,29
さんま,20090312,53
タモリ,20100707,54
鶴野,20101224,28
【説明】
ジョブは、stepタグで構成されることは以前の記事でも書きました。
ここではstepをどのように書けばよいかを見ていきます。
再掲:
<!-- ジョブの処理 -->
<job id="jobFile" xmlns="http://www.springframework.org/schema/batch" incrementer="jobParametersIncrementer">
<step id="step1" parent="simpleStep" next="step2">
<tasklet >
<chunk reader="fileItemReader" writer="fileItemWriter" />
</tasklet>
</step>
<step id="step2">
<tasklet ref="copyFile" />
</step>
</job>
<stepの記述>
stepに指定できるものは主に以下のものです。
①parent属性で基本になるbeanを指定します。(step1。ここではsimpleStepを指定)
simpleStepは、SimpleStepFactoryBeanクラスで、SpringのFactoryBeanを継承しています。
FactoryBeanはクラスを作るクラスで、SimpleStepFactoryBeanは単純なStepクラスを作成します。
上記の場合、作成された単純なStepが stepタグのid="step1"に設定されることになります。
このクラスを生成するFactory機能自体はSpringBatchではなく、Spring自体の機能です。
また、SimpleStepFactoryBeanで設定したプロパティはStepタグ内で上書きできます。
このFactoryを使用する他にも、Step自体を自作する方法もあります。
②taskletタグで、taskletを継承したクラスを指定します。(step2。ここではcopyFileを指定)
指定しているcopyFileは、SpringBatchが用意しているクラスで、
システムのコマンドを実行するtaskletです。
timeoutプロパティは必ず指定する必要があります。
そのままcopyコマンドを実行できないので、cmdコマンドからcopyを実行しています。
その他のtaskletには、MethodInvokingTaskletAdapterのように
他のbeanのメソッドを実行するものもあります。
もちろん、Taskletインターフェースを継承して自作のTaskletを作成してもOKです。
③他のjobを呼び出します。(ここでは記述していません)
stepとして、他のjob名を指定してコマンドのように呼び出すことができます。
このときパラメタも指定できます。ここでは詳しく説明しません。
<idについて>
stepタグなどのidは、裏ではSpringのbeanタグのidと同じ扱いになっています。
ですので、beanのidと重なるとエラーが発生します。
個人的には、stepなどのidについては"step.xxx"にするとか、プレフィックスをつけておいたほうが
idが衝突する確率が下がるので良いのかなと思っています。
<処理の順番>
もう1つ重要な要素があります。
next属性です。
なんとなく想像付くと思いますが、stepの処理が終わった後に次のstepがどれかを指定します。
一度も通らないstepが存在するとエラーになります。
jobタグ内に一番最初に記述されたstepが一番最初に処理されますが、その後の記述の順番は処理の順番と関係しません。
すべてnextなどで順番を記述するからです。
【作成したクラスSimpleMapFieldSetMapperについて】
今回はクラスを作っています。
再掲:
<bean class="sample.SimpleMapFieldSetMapper" >
<property name="types" value="Date,String,int" />
</bean>
これは、FileItemReaderで使用されるクラスで、読み込んだデータをbeanに設定していく方法を定義したクラスです。
beanに設定していくことをmapするという言い方をするようです。
本当はbeanはgetter/setterが記述されたPOJOクラスであるべきですが、いろいろ実験する場合に
1つ1つファイルごとにbeanを作るのはメンドウなので、LinkedHashMapにmapするクラスを作りました。
このクラスは型変換もしてくれます。(String,Date,intだけですが)
たぶん割と遊べるクラスになっていると思いますので、他のItemWriterクラスなどを試すのにも使用してみてください。
【FormatterLineAggregatorについて(ItemWriter関連)】
このクラスは、ファイル書き込み用のクラスの中で設定されています。
ItemReaderで取得したデータをファイルにどのように展開するかを定義するクラスです。
再掲:
<bean class="org.springframework.batch.item.file.transform.FormatterLineAggregator">
<property name="fieldExtractor">
<bean class="org.springframework.batch.item.file.transform.PassThroughFieldExtractor" >
</bean>
</property>
<property name="format" value="%2$s,%1$tY%1$tm%1$td,%3$d" />
</bean>
PassThroughFieldExtractorは単純なクラスで、受け取ったデータを何も加工せずに展開します。
例えば、渡ってきたデータがMapの場合、values()メソッドを呼び出すだけです。
Listが渡ってくれば、toArray()メソッドを呼び出すだけです。
展開の結果をどのような書式にするかを記述しているのがformatプロパティです。
%2$sという記述は、配列の2番目を文字列として出力するということです。
この機能により、フィールドの順番を入れ替えています。
以上で説明は終了です!ありがとうございます。
お疲れ様でした
参照: