Stepを使って処理を書くには? | Java Springの逆引きメモ

Java Springの逆引きメモ

JavaのSpring frameworkのメモを書いていきます!
初心者の勉強ノートなので間違いがあるかもしれませんが、何かヒントになることがあれば幸いです。

さて、今回は実際にStepタグを使用して処理を記述してみましょう!


まず、準備はこちら。サンプルではDB名を「sample」にしています。順次環境に合わせて名前を変えてください。

  ・Spring Batchを使えるようにするには? (準備編)


  ※DBも作成してください。(サンプルのdataSourceはPostgresになっています)


今回はRepositoryにDBも使用する予定ですが、もうひとつの記事を組み合わせてDBを使用しない方法に修正してもらってもよいです。

  ・Spring Batchを起動するには? (基本編)



処理内容:

  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&amp;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番目を文字列として出力するということです。

この機能により、フィールドの順番を入れ替えています。





以上で説明は終了です!ありがとうございます。

お疲れ様でしたビックリマーク



参照:

・トップ

・SpringBatch機能について

・実際に作成するものとは(Stepの概念について)

・CommandLineJobRunnerとは?

・Spring Batchを起動するには? (基本編)

SpringBatch本家のドキュメント