ItemReaderで読み込んだデータを妥当性チェックするには? (valang方式) | Java Springの逆引きメモ

Java Springの逆引きメモ

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

以前の記事でおおよそのバッチ処理のステップの設定を見てきました。

ここではItemReaderで読み込んだデータについて、妥当性チェックをする方法を見ていきます。



【予備知識】

最初にいくつか触れておきたいことがあります。

SpringBatchには妥当性チェックをするためのValidatorの準備がされています。

しかし、用意されているのはinterfaceだけで、チェックの実装はされていません。

チェックの実装については、以下のような解決方法があります。


 ①自分でValidatorをimplementsして作成する。

 ②SpringModulesを利用する。


①はあまりやりたくないですよね?汗

そんなわけ②をみていきます。


ちなみにSpringModulesは、他の記事で紹介していますようにSpringの妥当性チェックのためのフレームワークです。

 参考: ・SpringModulesの機能について


Strutsの妥当性チェックと同じApacheのライブラリを裏で使用しています。

Bean(setter/getterがあるクラス)をチェックの対象にしているので、Mapをチェック対象にはできないようです。


それと、Spring自体にもValidatorという同じ名前のinterfaceが用意されていますが、

SpringBatchのValidatorとは違う物であることに注意してください


 ただし、ラッパーが用意されていますのでSpringのValidator由来のクラスもSpringBatchでも使用できます。



 <SpringModulesで用意されている妥当性チェックの機能>

  主に以下の機能が用意されています。

機能 内容
valang

SpringModulesが独自で作成したチェック用言語。

Springの設定ファイルにEL式のような感じでチェック内容を記述する。

common validator Strutsと同じように、validator-rules.xmlとvalidator.xmlファイルを用意して、validator.xmlにチェック内容を記述していきます。

アノテーション

アノテーションを利用する方法も用意されているようです。

まだ、調べていませんので分かりましたら追記するかもしれません。



  以下では、valangを使用してみようと思います。

  valangで使用できる記号等は以下を参照してみてください。

    http://www.springbyexample.org/examples/spring-modules-validation-module.html
 


 valangを使用する上での注意点:

  tomcatのライブラリである、servlet-api.jar をビルド・パスに追加しておいてください。

  何故かvalangのモジュールがServletContextをimportしているため、クラスが見つからないエラーが出ます。

  (将来は直るかもしれませんが)

  tomcatのインストールディレクトリ内を探せばすぐ見つかります!



【サンプルの動作内容】

これから見るサンプルでは、以下の動作をするものを作ります。

 1.ファイルからデータの読み込み (ファイルはパッケージ配下に置きます)

 2.Accountクラス(Bean)に読み込んだ値をマップします。

 3.Accountクラスを妥当性チェックします。

 4.Accountクラスの内容をファイルに書き込みます。



それでは早速いってみましょービックリマーク




【/sample/test.txt ファイル (データファイル。sampleパッケージ内に置く)】

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バックグラウンドの設定)】

以下の記事の同じタイトルの箇所をコピーしてください。


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


  ↑DBの設定などもお忘れなく。

【/sample/runValid-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:aop ="http://www.springframework.org/schema/aop " 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/aop http://www.springframework.org/schema/aop/spring-aop-2.5.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="jobValid" xmlns="http://www.springframework.org/schema/batch " incrementer="jobParametersIncrementer"> <step id="step1" parent="simpleStep" > <tasklet > <chunk reader="fileItemReader" processor="validatingItemProcessor" writer="fileItemWriter" commit-interval="2" /> </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="commitInterval" value="1" /> </bean> <!-- ファイルを読み込みBeanに入れる --> <bean id="fileItemReader" class="org.springframework.batch.item.file.FlatFileItemReader" scope="step"> <property name="resource" value="classpath:/sample/sample-db.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="id,name,date" /> </bean> </property> <property name="fieldSetMapper"> <bean class="sample.AccountFieldSetMapper" /> </property> </bean> </property> </bean> <!-- 読み込んだ結果をファイルに出力していく --> <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.DelimitedLineAggregator"> <property name="fieldExtractor"> <bean class="org.springframework.batch.item.file.transform.BeanWrapperFieldExtractor" > <property name="names" value="id,name,date" /> </bean> </property> </bean> </property> </bean> <!-- 妥当性チェックプロセッサ --> <bean id="validatingItemProcessor" class="org.springframework.batch.item.validator.ValidatingItemProcessor"> <property name="validator" ref="validator" /> </bean> <!-- SpringModules用からSpringBatch用へ変換するためのラッパー。 --> <bean id="validator" class="org.springframework.batch.item.validator.SpringValidator"> <property name="validator" ref="valangValidator" /> </bean> <!-- 妥当性チェックするクラス --> <bean id="valangValidator" class="org.springmodules.validation.valang.ValangValidator"> <property name="valang"> <value> <![CDATA[ { id : ? >= 0 AND ? <= 5 : 'IDは0~5です。' : 'errors.range': 'ID',0,5 } { name : size(?) <= 5 : '名前は5文字以下です。' : 'errors.maxlength': '名前',5 } { date : ? IS NOT NULL AND ? > [2010-05-05] : '日付は5/5以降を入れてください。' : 'errors.date': '日付','5/5'} ]]> </value> </property> </bean> </beans> 【Accountクラス(Beanクラス。sampleパッケージ内に置く)】 package sample; import java.util.Date; public class Account { private int id; private String name; private Date date; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Date getDate() { return date; } public void setDate(Date date) { this.date = date; } } 【AccountFieldSetMapper クラス(Accountに読み込みデータを設定するクラス)】 package sample; import org.springframework.batch.item.file.mapping.FieldSetMapper; import org.springframework.batch.item.file.transform.FieldSet; import org.springframework.validation.BindException; public class AccountFieldSetMapper implements FieldSetMapper<Account> { @Override public Account mapFieldSet(FieldSet fs) throws BindException { Account a = new Account(); a.setId(fs.readInt("id", -1)); a.setName(fs.readString("name")); a.setDate(fs.readDate("date", "yyyy/MM/dd", null)); return a; } }





【起動方法】

起動方法は、以下のとおりです。

 メインクラス    : org.springframework.batch.core.launch.support.CommandLineJobRunner

 プログラムの引数: classpath:/sample/runValid-context.xml jobValid -next


 ※詳しいやり方は、・Spring Batchを起動するには? (基本編) の真ん中当たりの【実行】を参照。


 ※-nextは何回でもサンプルを実行できるようにつけたオプションです。

  詳しくは他の記事を読んでみてください。



【わざとエラーを出す】

サンプルのテキストファイルを以下のようにして実行してみてください。

エラーが出るはずです!


<修正後のファイル>

1,太郎,2010/05/03
2,次郎,2010/07/07
3,さんま,2010/08/08
4,タモリ,2010/09/09
5,鶴野,2010/10/10



<実行結果(エラーログの内容)>

org.springframework.batch.item.validator.ValidationException: Validation failed for sample.Account@15d4de6 :
Field error in object 'item' on field 'date': rejected value [Mon May 03 00:00:00 JST 2010];

codes [errors.date.item.date,errors.date.date,errors.date.java.util.Date,errors.date];

arguments [日付,5/5];

default message [日付は5/5以降を入れてください。]




【説明】

妥当性チェック自体は、バッチフレームワークのプロセッサで行います。

validatingItemProcessorですね。

ItemReaderでは、データを読み込み、sample.AccountFieldSetMapperがAccountオブジェクトに値を設定しています。


AccountFieldSetMapperクラス>

  マッパークラスです。

  FieldSetから値を受け取り、設定するだけです。


   再掲(一部抜粋):

     Account a = new Account();
     a.setId(fs.readInt("id", -1));


  readXxxx()の1番目の引数はフィールド名で、2番目の引数は値が存在しないときに返却される値です。

  つまり、デフォルト値です。

  もし、idの値が数値でない場合は例外が発生します。



validatingItemProcessorクラス>

  プロセッサのクラスですので、ItemReaderでAccountが作られた後に呼ばれます。

  このクラスはSpringの設定ファイルでDIしたvalidatorを使用して妥当性チェックを実行するだけです。


  では、validatorを見ていきましょう。


  validatorには、SpringValidatorクラスが設定されています。

  このクラスが冒頭で述べたラッパークラスで、valangValidatorをSpringBatchで

  使用できるようにしているクラスです。

  は、SpringModulesのクラスですので、そのままではvalidatingItemProcessorに設定できません。



valangValidator

  次に見ていくのは以下のBeanです。

  validatingItemProcessor内部で実際にチェックをしているのは以下の設定です。


 再掲:

 <bean id="valangValidator"
  class="org.springmodules.validation.valang.ValangValidator">
  <property name="valang">
    <value>
  <![CDATA[
 { id : ? >= 0 AND ? <= 5 : 'IDは0~5です。' : 'errors.range': 'ID',0,5 }
 { name : size(?) <= 5 : '名前は5文字以下です。' : 'errors.maxlength': '名前',5 }
 { date : ? IS NOT NULL AND ? > [2010-05-05] : '日付は5/5以降を入れてください。' : 'errors.date': '日付','5/5'}

  ]]>
   </value>
  </property>
 </bean>

  赤字の部分がvalang式になります。

  コロン(:)で区切られています。

  形式は、以下のような感じです。


  {フィールド名 : valag式 : デフォルトメッセージ : エラーコード : エラー引数}

     

     ※フィールド名について

     idのような指定だけでなくネストしたaaa.bbbのような指定も可能です。

     また、aaaの結果がMapなら、aaa[id]のような指定も可能です。


     ※エラーコードは、メッセージリソースからエラーメッセージを探すときのキーです。

     ここではメッセージリソースを設定していませんのでデフォルトメッセージが表示されます。


  しかし、メッセージリソースを設定してもそのままではエラーメッセージを検索してくれないようです。

  もしどうしてもメッセージリソースを利用したい場合、SpringValidatorを拡張して、メッセージリソースから

  エラーメッセージを検索するようにすればよいでしょう。



  valang式はここでは特に説明しませんが、参考のURLを見ればすぐ分かるかと思います。


  

【他の妥当性チェックの方法への変更】

 他の方法、例えばStrutsに慣れた方ならCommon Validateを使用するほうが使いやすいかもしれません。

 そうすると、変更をかけたくなります。

 変更は簡単です!

 

 SpringのラッパークラスにDIするbeanを変更するだけです。

 再掲:

  <!-- SpringModules用からSpringBatch用へ変換するためのラッパー。 -->
  <bean id="validator" class="org.springframework.batch.item.validator.SpringValidator">
    <property name="validator" ref="valangValidator" /> ←refの値を変更します。
  </bean>

 もちろん、必要なvalidate-rule.xml, validate.xmlなどのファイルの用意と

 validatorFactoryなどのbeanの設定もお忘れなく!

 以下を参考にしてもらえればいいかと思います。

   ・SpringModulesの機能について



さて、おおよそ使い方は分かりましたでしょうか?

valang式の記法は覚えないといけませんが全然難しくないですよねにひひ


うまく使って、コード量を減らしていきましょう!




参照:

・トップ

・SpringBatch機能について

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

・CommandLineJobRunnerとは?

・Executionとは? (SpringBatch用語)

・WEB上で定期的に何か処理をするには?

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

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

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

・条件分岐をさせるには?

・DBにデータを読み書きするには?

・データの書き込み(コミット)タイミングを制御するには? (restart機能もついでに見る!)

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

・ItemWriterの出力先をメールにするには?

・自作クラスをrestartできるようにするには?

SpringModulesの外部記事