Java Springの逆引きメモ -3ページ目

Java Springの逆引きメモ

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

今までの記事では、処理をシーケンシャルに順番に処理する例のみを見てきました。

 

  ・Stepを使って処理を書くには?

  ・複数のStepをひとまとまりで扱うには?(flow)


ここでは、条件分岐する方法を見ていきましょう。




【サンプル1 (stepタグ内で条件分岐する方法)】

<job id="job2" xmlns="http://www.springframework.org/schema/batch "

 incrementer="jobParametersIncrementer">
   <step id="step.a">
     <tasklet >
       <chunk reader="fileItemReader1" writer="fileItemWriter1" />
     </tasklet>
     <next on="COMPLETED" to="step.b"/>
     <next on="*" to="step.c"/>
   </step>


   <step id="step.b">
     <tasklet >
       <chunk reader="fileItemReader2" writer="fileItemWriter2" />
     </tasklet>
   </step>

   <step id="step.c">
     <tasklet >
       <chunk reader="fileItemReader3" writer="fileItemWriter3" />
     </tasklet>
   </step>
</job>



説明:

step内でnextタグを使用します。

こうすると、stepを抜け出すときの状態をStepExecutionから取得して、一致する場合に次のstepに遷移します。

状態は文字列として保存されていますので、*のようなワイルドカードを使用することもできます。


上記の場合、

 COMPLETED のとき ⇒ step.bに遷移

 上記以外のとき ⇒ step.cに遷移


となります。


使用できる値は、ExitStatus を参考にしてみてください。

また使用できるタグはnextタグだけではありません。

以下のようなタグも使用可能です。

 

 next ・・・条件に合致する場合、次のstep処理に遷移します。

 fail ・・・条件に合致する場合、FAILED状態として処理を終了します。

 end ・・・条件に合致する場合、COMPLETED状態として終了します。restart不可です。

 stop ・・・条件に合致する場合、STOP状態として処理を終了します。restart可能です。


 ※endとfailについては、exit-code属性で、jobを抜けるときのステータスを変更することもできます。



【サンプル2 (リスナーを使用する方法)】

<job id="job2" xmlns="http://www.springframework.org/schema/batch "

 incrementer="jobParametersIncrementer">
   <step id="step.a">
     <tasklet >
       <chunk reader="fileItemReader1" writer="fileItemWriter1" />
       <listeners>
         <listener ref="skipCheckingListener"/>
       </listeners>
     </tasklet>
     <next on="COMPLETED WITH SKIPS" to="step.b"/>
     <next on="*" to="step.c"/>
   </step>


   <step id="step.b">
     <tasklet >
       <chunk reader="fileItemReader2" writer="fileItemWriter2" />
     </tasklet>
   </step>

   <step id="step.c">
     <tasklet >
       <chunk reader="fileItemReader3" writer="fileItemWriter3" />
     </tasklet>
   </step>
</job>


<bean id="skipCheckingListener" class="test.SkipCheckingListener" />



リスナークラスの例

public class SkipCheckingListener extends StepExecutionListenerSupport {

    public ExitStatus afterStep(StepExecution stepExecution) {
        String exitCode = stepExecution.getExitStatus().getExitCode();
        if (!exitCode.equals(ExitStatus.FAILED.getExitCode()) && 
              stepExecution.getSkipCount() > 0) {
            return new ExitStatus("COMPLETED WITH SKIPS");
        }
        else {
            return null;
        }
    }

}


【説明】

リスナーを使う方法だと、先ほどの例と違い、stepを抜け出すときの状態を表す文字列を自由に

決めることができます。

これでかなり自由度が上がると思います。


あとのnextタグなどの使用方法は全く同じです。




【サンプル3 (decisionを使用する方法)】

<job id="job" xmlns="http://www.springframework.org/schema/batch " >
  <step id="step1" parent="s1" next="decision" />

  <decision id="decision" decider="decider">
    <next on="FAILED" to="step2" />
    <next on="COMPLETED" to="step3" />
  </decision>

  <step id="step2" parent="s2" next="step3"/>
  <step id="step3" parent="s3" />
</job>

<bean id="decider" class="com.MyDecider"/>



Deciderクラスの例:

public class MyDecider implements JobExecutionDecider {
    public FlowExecutionStatus decide(JobExecution jobExecution, StepExecution stepExecution) {
        if (someCondition) {
            return new FlowExecutionStatus("FAILED");
        }
        else {
            return FlowExecutionStatus.COMPLETED;
        }
    }
}



説明:

先ほどのサンプルでは、リスナーを使う方法もstep内に条件を記述していました。

しかし、deciderを使用すると条件分岐が1つのstepになっています。

これは条件分岐を使いまわしたい場合などには便利かと思います。




条件分岐については上記のものが主な方法かと思います。




参照:

・トップ

・SpringBatch機能について

・Stepを使って処理を書くには?

・複数のStepをひとまとまりで扱うには?(flow)

SpringBatch本家のドキュメント


前回の記事で、stepの処理の記述の仕方を見ました。


 ・Stepを使って処理を書くには?



もし、複数のstepをひとまとまりで扱えて、色々な箇所でかたまりごと使い回せたら楽ですよね。

この記事では、その方法を見ていきましょう!



【サンプル (記述イメージです)】


<job id="job1" xmlns="http://www.springframework.org/schema/batch "  

 incrementer="jobParametersIncrementer">


  <flow id="job1.flowCopy" parent="flow.copy" />

</job>



<flow id="flow.copy" xmlns="http://www.springframework.org/schema/batch ">
 <step id="step.clear" parent="simpleStep" next="step.create" >
   <tasklet ref="methodInvokingTasklet" />
 </step>
 <step id="step.create" parent="simpleStep">
   <tasklet>
     <chunk reader="fileReader" writer="dbWriter" />
   </tasklet>
 </step>
</flow>



【説明】

stepの処理のかたまりを扱うにはflowタグを使用します。

上記のように、jobタブの外部で定義しておけば良いかと思います。


そして、job内でflowを使用するときは、やはりflowタグを使用します。

parentにflow名を指定するだけですビックリマーク


もちろん、job内のflowタグの前後にstepタグを記述することもできます。


簡単ですよね。




参照:

・トップ

・SpringBatch機能について

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

・CommandLineJobRunnerとは?

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

・Stepを使って処理を書くには?





さて、今回は実際に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本家のドキュメント



CommandLineJobRunnerは、Spring Batchで用意されているmain関数を持ったクラスです。


Spring Batchはバッチ処理の流れや条件分岐をXMLの設定ファイルに記述するだけでバッチ処理を表現できます。

実際にそのXMLを実行する方法の1つが、CommandLineJobRunnerなのです。

もちろんSpringBatchのjarが必要ですので、それをダウンロードしておく必要はあります。



【起動方法】

以下に起動例を記述します。


java org.springframework.batch.core.launch.support.CommandLineJobRunner testJob.xml testJob schedule.date=2008/01/24 type=2


 記述方法:

  [バッチのXML設定ファイル] [Job名] [オプション] [パラメタ(0個以上)]


  例題でのそれぞれの値は以下のとおり。

   [バッチのXML設定ファイル] ・・・ testJob.xml

   [Job識別子] ・・・ testJob

   [パラメタ(0個以上)] ・・・ schedule.date=2008/01/24 と type=2


 上記のような感じで実際にコマンドラインから実行できます。

 [Job識別子]は、バッチXMLファイル内でjobタグで設定されているid名(ジョブ名)、

 もしくは実行後にできるJob実行ID(数字)のいづれかを指定できます。

 [Job識別子]に数字が指定された場合にJob実行IDとみなされ、それ以外はジョブ名とみなされます。

 オプション(-restartなど)を指定する場合は、基本的にはパラメタを指定しません

 オプション指定時はパラメタは基本的に無視されるようですが、-nextオプションだけ特殊です

 詳しくは以下の「オプションについて」を参照ください。

 ちなみにバッチXMLファイルは、Springのcontextファイルです。

  


【オプションについて】

上記ではオプションを使用していませんが、以下のようなオプションを使用可能です。
-restart

指定したJob名で、最後にFAILまたはSTOPした実行結果(JobExecution)を検索して再実行します。

再実行は、最後に失敗したステップからリスタートします。

ステップがチャンク処理の場合は、最後にコミットした次のチャンクからリスタートします。

このオプションでは、パラメタを設定しても無視されます。

再実行時のパラメタには、見つかったJobExecution内のパラメタ(JobParameters)が使用されます。

Job名に数字を指定するとJobExecutionのID(Job実行ID)とみなされて、対応するJobExecutionを検索して再実行します。

-stop

実行中の処理を停止します。

指定したJob名に一致する、実行中のジョブをすべて停止します。このオプションではパラメタを設定しても無視されます。

Job名に数字を指定するとJobExecutionのIDとみなされて、対応するJobExecutionを検索して停止します。

停止した場合は、-restartオプションで再実行することが可能です。

-abandon

停止(stop)した処理を放棄します。

指定したJob名に一致する、停止中のジョブをすべて放棄します。このオプションではパラメタを設定しても無視されます。

Job名に数字を指定するとJobExecutionのIDとみなされて、対応するJobExecutionを検索して放棄します。

放棄された処理は再実行することはできなくなります。

-next

指定したJob名に一致するもののうち、最後に実行した実行結果(JobExecution)を検索し、処理を強制実行します。

Job名に数字を指定するとJobExecutionのIDとみなされて、対応するJobExecutionを検索して強制実行します。

もしパラメタを指定された場合は、検索したJobExecutionのパラメタに上書きして強制実行することができます。

【補足】

通常、既に完了(COMPLETED)しているパラメタで起動しようとするとAlreadyCompleteExceptionが発生します。これは同じパラメタに対しては同じJobInstanceのidを生成するからです。

しかし、このオプションを指定すると強制的に実行しようとします。

一度COMPLETEDしたジョブを開始する方法は、このオプションを使用するほかに、SimpleStepFactoryBeanクラスのallowStartIfCompleteプロパティをtrueにする方法もあるようです。

参考:・SpringBatch機能について


 ※Job名を指定した場合も、JobExecutionのIDを指定した場合も、1件もJobExecutionが見つからない場合はエラーになります



【その他のJobの実行方法1

上記以外のJobの実行方法は、プログラムから直接呼ぶ方法があります。

タイトルの内容とはそれるので軽く触れるにとどめます。


起動方法は、JobLauncherで呼び出します。

JobLauncherはSpringの設定ファイル内に記述できます。

上記のコマンドラインでもSpringの設定ファイル内にJobLauncherを記述しなければなりませんが、コマンドラインを使用する場合は1つしか記述できません。


 使用例: パラメタなしの場合

  jobLauncher.run(job, new JobParameters());


また、コマンドラインでは-nextなどのオプションを指定できましたが、それはコマンドラインの機能です。

もしjobLauncherで同じことをしたい場合、自分でJobParametersのインクリメントをする必要があります。

もしくは、CommandLineJobRunnerをnewしてstart()メソッドをオプションを指定して呼び出してもできるかもしれません。



【その他のJobの実行方法2

もうひとつの方法は、JobOperatorを使用する方法です。

実際に検証していないのでSpringBatchのドキュメントとサンプル(JobOperatorFunctionalTests)を参考に書きます。(各自で検証お願いします)


 使用例: パラメタなしの場合

  jobOperator.start("testJob", new JobParameters())

 使用例2: resrartする場合(stopも同様)

  List<Long> instances = jobOperator.getJobInstances("testJob", 0, 1);

  List<Long> executions = operator.getExecutions(instances.get(0));

  jobOperator.restart(executions.get(0));


 使用例3: -nextと同様の実行をする場合

  jobOperator.startNextInstance("testJob");



jobOperatorはSpringの設定ファイルに設定しておき、取得するものとします。

さて、JobOperatorは内部でJobLauncherを使用してJobを実行します。


パラメタなしの例は直感的に分かるかと思いますので、restartする場合を見てみましょう。


まずgetJobInstancesでインスタンスIDを取得しています。

引数は、ジョブの名前、何番目に生成したIDから取得するか、何個取得するか、の3つです。

2番目の引数は、0が最新で、数が大きくなるほど古くなります。

SpringBatchでは、同じジョブ名でも違うパラメタで実行すれば違うインスタンスIDが生成されDBに溜まっていきます。

それを新しいもの順に並び替えて取得するため、上記の場合、最後に実行したジョブインスタンスID1個を取得する例になります。

さらにそれを利用して次のステップでも同様にしてExecutionのIDを取得しています。

(failしたジョブを同じパラメタでrestartしたり、同じパラメタで-nextで実行した場合、ジョブインスタンスIDは同じですが、ExecutionのIDはインクリメントされてDBに溜まっていきます。)

restartメソッドに取得したIDを渡せばリスタート開始です!


コマンドの-nextと同様の起動するのはもっと簡単で、startNextInstanceを呼ぶだけです。

失敗したものが過去にあっても無視して新しいJobInstaceを作成します。



参照:

・トップ

・SpringBatch機能について

・バッチ処理を非同期で起動するには?

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

・Executionとは? (SpringBatch用語)

本家SpringBatchのCommandLineJobRunnerの記事





今までの記事で、Spring Batchの概念、必要な準備を見てきました。

ここでは、実際に簡単なサンプルを動かしてみます。



【準備】

まず、以下の記事のライブラリをダウンロードしてプロジェクトに追加してください。

DBの準備はこの記事ではいりません!


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


  実際には、DBの準備が必要ですが、この記事ではテスト用のごまかす方法で設定をしています。



 <ライブラリの追加>

 上記の記事を参考にjarファイルをダウンロードしたら、プロジェクトに追加します。

 binというディレクトリを作成して、そこにドラッグ&ドロップすれいいだけです。


 次に、ドロップしたjarファイルとプロジェクトを結び付けます。


 「プロジェクト」 ⇒ 「プロパティ」 でプロパティを表示し、「Javaのビルド・パス」を選択します。


 Java Springの逆引きメモ

     ↑「JARの追加」ボタンを押して、

     ドロップしたフォルダ内のjarファイルすべてを選択します。


   

   とりあえず準備は以上です!

   次に、実際にSpring Batchの設定を書いてみましょう。   



【設定の作成】

これからやることを先に記述しておきますかお


<処理内容>

  1.CSVファイルを読み込みます。読み込まれたデータはFieldSetというbatchのクラスに設定されます。

    (FieldSetをPOJOに設定することもできますがメンドウなのでそのまま使ってます。)

  2.読み込んだFieldSetをtoString()してファイルに出力します。


  う~ん。全く意味のない処理ですね(笑)

  でも、自分でプログラミングしなければならない箇所はありませんビックリマーク

  すべてSpringの設定だけで実現できちゃいますドキドキ



<c:/temp/read.csv ファイル(読み込みファイル)>

内容は以下のとおりです。ファイルを指定の場所に配置してください。

1,2,3
5,7,8



<launch-context.xml ファイル>

 Springの設定ファイルの内容です。

 プロジェクトのexampleパッケージの下にでも置いてみてください。

<?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 ">

<!-- SpringBatchの裏で動作しているbeanの設定 -->
<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.MapJobRepositoryFactoryBean" p:transactionManager-ref="transactionManager" />
<!-- ダミーのDB関連クラス --> <bean id="dataSource" class="org.springframework.jdbc.datasource.SingleConnectionDataSource"> </bean> <!-- ダミーのDB関連クラス --> <bean id="transactionManager" class="org.springframework.batch.support.transaction.ResourcelessTransactionManager" > </bean> <!-- 実際のバッチ処理 ============================================================== --> <job id="jobFile" xmlns="http://www.springframework.org/schema/batch" incrementer="jobParametersIncrementer"> <step id="step1" parent="simpleStep"> <tasklet> <chunk reader="fileItemReader" writer="fileItemWriter" /> </tasklet> </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> <bean id="fileItemReader" class="org.springframework.batch.item.file.FlatFileItemReader" scope="step"> <property name="resource" value="file:c:/temp/read.csv" /> <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="ID,bizDate,userName" /> </bean> </property> <property name="fieldSetMapper"> <bean class="org.springframework.batch.item.file.mapping.PassThroughFieldSetMapper" /> </property> </bean> </property> </bean>
<!-- toString()した結果をファイルに出力するWriter --> <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.PassThroughLineAggregator" /> </property> </bean> </beans>




【実行】
では、実際に動かしてみましょう!

①プロジェクトを右クリックし、「実行」 ⇒ 「実行の構成」 を選択します。


Java Springの逆引きメモ


  左側の「Javaアプリケーション」を選択し、

  メインクラスに以下を設定します。


    org.springframework.batch.core.launch.support.CommandLineJobRunner


  

  次に「引数」タブを選択します。

  「プログラムの引数」に、以下を設定します。


    classpath:/example/launch-context.xml jobFile



②実行を押すとプログラムが実行されます。



【実行結果(作成されるファイル: c:/temp/spring_batch-test.txt)】

{ID=1, bizDate=2, userName=3}
{ID=5, bizDate=7, userName=8}





【説明】

さて、うまく動作して、ファイルができましたでしょうか?

うまくいかなかった方は、読み込みファイルの場所や、設定ファイルの場所などを確認してみてください。

  

  読み込みファイル : c:/temp/read.csv

  設定ファイル    : プロジェクト内の、/example/launch-context.xml



では、簡単に設定の内容を見ていきましょう。


XML設定ファイルの前半は、SpringBatchの基本設定で、ほぼどのバッチ処理でも記述しないといけません。

これらは以下の記事の「全体の概要」に詳しく書きましたので見てみてください。

これらの記事での登場人物がいっぱい出てきていて、親近感が湧きますね!!


  ・SpringBatch機能について




<Repositoryの保存先をごまかす>

この中でも特に重要なのがRepositoryで、これは処理結果であるExecutionの保存先です。

通常、Repositoryクラスの先にあるのはDBです。

しかし、今回はDBを使用していません。

どうやってごまかしたのでしょうか?


  マジックは、以下の二つのクラスの使用です。

    MapJobRepositoryFactoryBean ・・・メモリ上にRepositoryを用意する

    ResourcelessTransactionManager ・・・トランザクションをごまかす

  

  これらは、メモリ上にリポジトリを展開するための常套手段のようです。

  メモリに展開するならトランザクションはいらないはずなのですが、何故か必要になります。

  なので、トランザクションをごまかすクラスを使用します。


  ※この方法はテスト用のものです。本番では使用しないでください。

    何故かというと、処理結果が保存されないのでrestartなどが使用できないからです。

    restartとは、処理が途中でFAILしたときにFAILした処理から実施する機能です。
    このためには処理結果(どこまで終了したか?)をどこかに保存しておく必要があります。

    メモリに処理結果を記述するのでJavaが起動し終わるたびにクリアされちゃいますよねシラー

    もし、いつでも最初から処理し、restartなどの機能を使用しないならこれでもいいかもしれません。



<実際のバッチ処理>

実際のバッチ処理の部分では、「Job」というタグがあります。

これが実際のバッチ処理を記述するものです。


先立って以下の記事を読んでおくと分かりやすいと思います。


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


jobの中には、stepがあり、これが実際の具体的な処理になります。

ここでは1つしか記述していませんが、複数記述できます。


 再掲:

  <job id="jobFile" xmlns="http://www.springframework.org/schema/batch " 

     incrementer="jobParametersIncrementer">
   <step id="step1" parent="simpleStep">
      <tasklet>
    <chunk reader="fileItemReader" writer="fileItemWriter" />
      </tasklet>
   </step>
  </job>


さらにstepの中には、readerwriterprocessorがあります。

ここではprocessorは使用していません。


 stepの処理は、read ⇒ process ⇒ write の順番に処理されます。

 この流れをreadでデータ読み込めなくなるまで実施し、stepが終了します。


 ここでは、readerでCSVファイルの読み込み、writerでファイルへの書き込みをしています。



<stepについて>
実際のstep処理は、stepというタグで記述されていますが、このタグの具体的なクラスは、

  SimpleStepFactoryBean

です。

順番に処理するような単純な処理であればこれを使用すれば十分です。

stepタグにはparentというタグがあります。

この中で上記のSimpleStepFactoryBeanが使用されています。

parentはJavaのクラスのように継承するクラスで、設定されたpropertyを上書きすることもできます。

SimpleStepFactoryBeanのプロパティは以下のとおりです。


【SimpleStepFactoryBeanのプロパティ】

プロパティ名 設定内容
allowStartIfComplete すでにJOBが処理成功(COMPLETE)になっていると通常、AlreadyComplete例外が発生します。しかし、この値を操作することで回避できます。
beanName stepが作られたときのstep名になります。
chunkCompletionPolicy チャンク単位でのCompletePolicyを設定します。
chunkOperations chunkOperationsを設定します。境界、トランザクションなどの条件を操作できます。
commitInterval 何回readが行われるとコミットが実施されるかを設定します。
exceptionHandler 例外ハンドラを設定します。
isolation ビジネスのisolationレベルを設定します。
isReaderTransactionalQueue Flag to signal that the reader is transactional (usually a JMS consumer) so that items are re-presented after a rollback.
itemProcessor stepタグの中でもここでもprocessorを設定できます。ただし、step内でも設定した場合、step内の設定で上書きされます。
itemReader 同上
itemWriter 同上
jobRepository 同上
listeners 同上
propagation 同上。DBトランザクションの設定。
singleton Public setter for the singleton flag.
startLimit Public setter for the start limit for the step.
stepOperations Public setter for the stepOperations.
streams The streams to inject into the Step.
taskExecutor Public setter for the TaskExecutor.
throttleLimit Public setter for the throttle limit.
transactionManager Public setter for the PlatformTransactionManager.
TransactionTimeout



<実際のファイル処理>

おおまかな流れはつかめたと思います。

ここでは、ファイルの読み込み、書き込みに使用しているクラスを見ていきましょう。


 FlatFileItemReader

  これはカンマ区切り、タブ区切りなどのテキストファイルを読み込むクラスで、

  Springが用意してくれいているクラスです。

  区切り文字は他のクラスDelimitedLineTokenizerで設定します。

  

  また、読み込んだデータはPOJOに設定することもできますが、POJOを用意するのが

  メンドウでしたのでFieldSetをそのまま渡すクラスPassThroughFieldSetMapperを使用してみました。



 FlatFileItemWriter

  これはテキストファイルにデータを書き込むクラスです。

  ここではFieldSet をtoString()するクラスPassThroughLineAggregatorを使用してみました。




 

どうでしょう?

何もプログラムしなくても結構いろいろできますね!


SpringBatchでは、たくさんの処理用のreader, writerのクラスが用意されています。 

ファイルだけでなくDBから値を取得するクラスや、DBに書き込みするクラスもあります。

また、ファイルの妥当性チェックも設定だけで実行する仕組みが用意されています。


 ファイルの読み込みに関しては、複数の行を1つのラインのように読み込む機能もあるようです。


自分は結構いろいろ用意されていておもしろい!って思いましたが、

面白さが伝わりましたでしょうか?





参照:

・トップ

・SpringBatch機能について

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

・CommandLineJobRunnerとは?
Spring Batchについて