Quantcast
Channel: Java Springの逆引きメモ
Viewing all 21 articles
Browse latest View live

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

$
0
0

ここまでの記事で、SpringBatchのバッチ処理の記述の仕方を見てきました。


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

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

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



しかし、ファイルの読み書きばかりでした。

この記事ではDBを扱うサンプルを見てみようと思います。



【処理内容】

まず、これからどのようなことをやろうとしているかを書いておきます。


 ①ファイルからデータを読み込みます。

 ②DBのテーブルに読み込んだデータを書き込みます。



【準備】

SpringBatchの準備は今までと同じです。以下の記事を参考に準備をお願いします!

 

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


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



さらに、今回はデータ書き込み先のテーブルも作っておきます。


<テーブル (使用するDBはPostgeresです)>

CREATE TABLE member
(
id integer NOT NULL,
uname varchar(20),
age integer,
in_date date
)

 ※他のDBを使用する場合は、DBに合わせてcreate文、dataSourceの設定内容を変更してください。



 <パッケージの作成>
 プロジェクトには/sample パッケージを作成しておいてください。



【 /sample/back-context.xml ファイル(Batchバックグラウンドの設定)】


 以下の記事と同じ物を使いまわします。

 リンク内の同じ項目の内容をファイルにしてください。

   

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



【SimpleMapFieldSetMapperクラス】
 これも前回の記事を使いまわしますので、同じ項目の内容をコピーしてください。

  

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



【 /sample/sample-db.txt ファイル(読み込みファイル)】

1,太郎,18
2,次郎,29
3,さんま,53
4,タモリ,54
5,鶴野,28



【 /sample/runDb-context.xml ファイル(バッチ処理の設定)】

<?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="jobDb" xmlns="http://www.springframework.org/schema/batch
 " incrementer="jobParametersIncrementer">
 
  <step id="step1" parent="simpleStep" >
   <tasklet >
    <chunk reader="fileItemReader" writer="dbItemWriter" 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="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/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,uname,age" />
     </bean>
    </property>
    <property name="fieldSetMapper">
     <bean class="sample.SimpleMapFieldSetMapper" >
      <property name="types" value="int,String,int" />
     </bean>
    </property>
   </bean>
  </property>
 </bean>


 <!-- SQLを実行していく(readしたMapをプレースホルダに設定して実行する) 
 -->
 <bean id="dbItemWriter" class="org.springframework.batch.item.database.JdbcBatchItemWriter">
  <property name="dataSource" ref="dataSource" />
  <property name="itemSqlParameterSourceProvider">
   <bean class="sample.MapSqlParameterSourceProvider" />
  </property>
  <property name="itemPreparedStatementSetter" >
   <bean class="org.springframework.batch.item.database.support.ColumnMapItemPreparedStatementSetter" />
  </property>
  <property name="sql" value="
   insert into member(id, uname, age)
   values(:id, :uname, :age)
  " />
 </bean>
 
 
</beans>







【 /sample/MapSqlParameterSourceProvider (SpringJdbc関連のクラス)】

package sample;
import java.util.Map;
import org.springframework.batch.item.database.ItemSqlParameterSourceProvider;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.SqlParameterSource;

public class MapSqlParameterSourceProvider implements
  ItemSqlParameterSourceProvider<Map<String, Object>> {
 @Override
 public SqlParameterSource createSqlParameterSource(Map<String, Object> map) {
  return new MapSqlParameterSource(map);
 }
}




【起動方法】

前回までと同じです。

以下のリンクの【実行】と同じことをしてみてください。

ただし、「プログラムの引数」はちょっと変えます。


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


  プログラムの引数 : classpath:/sample/runDb-context.xml jobDb

 


  ※ただし、2回目以降は状態がCOMPLETEDになっているため実行エラーが出ます。

   この場合、プログラムの引数に -nextを付け加えてみてください。

   (参考:・CommandLineJobRunnerとは?

   あと、重複キー違反も起きると思うのでテーブルのデータも一度クリアしてください。




【結果】

2010/08/19 23:29:03.921 [] INFO :: Job: [FlowJob: [name=jobDb]]

        launched with the following parameters: [{run.id=1}]
2010/08/19 23:29:04.062 [] INFO :: Executing step: [step1]
2010/08/19 23:29:04.265 [] INFO :: Job: [FlowJob: [name=jobDb]]

        completed with the following parameters: [{run.id=1}] and the following status: [COMPLETED]




【説明】

どうでしょうか?

無事、DBにデータが書き込まれましたでしょうか?


やっていることは今までの記事の内容と同じです。


①ItemReaderでファイルからデータを読み込みます。
  これは前回の記事、そのままです。

  ですので、説明は前回の記事に譲ります。


   ・Stepを使って処理を書くには?   (作成したクラスSimpleMapFieldSetMapperについて


②ItemWriterで読み込んだデータを書き込みます。

 JdbcBatchItemWriterを使用しています。


再掲:

<bean id="dbItemWriter" class="org.springframework.batch.item.database.JdbcBatchItemWriter">
 <property name="dataSource" ref="dataSource" />
 <property name="itemSqlParameterSourceProvider">
   <bean class="sample.MapSqlParameterSourceProvider" /> 

 </property>
 <property name="itemPreparedStatementSetter" >
   <bean class="org.springframework.batch.item.database.support.ColumnMapItemPreparedStatementSetter" />
 </property>
 <property name="sql" value="
   insert into member(id, uname, age)
   values(:id, :uname, :age)

 " />
</bean>


  JdbcBatchItemWriterは、SpringJdbcのバッチ機能を使用してDBにアクセスします。

  SpringJdbcの詳細は、・SpringJDBCの機能について を参照してください。

  SpringJdbcは、"?"をプレースホルダにするタイプと、":名前"をプレースホルダにするタイプがあります。

  上記では、後者を使用しています。(sqlプロパティを見てみてください。)

  itemSqlParameterSourceProviderは、プレースホルダに使用する値をどのように与えるかを

  定義するプロパティです。

  ItemReaderは、LinkedHashMapを返すように設定したので、Mapのキー名をプレースホルダに

  展開したいのですが、そのようなクラスは既存のSpringBatchでは用意されていません。

  ですので、自作しました。(MapSqlParameterSourceProvider

  SpringJdbcのクラスである、MapSqlParameterSourceにMapを渡すだけの単純なクラスです。

  本当はMapではなく、Beanを使用すると自作する必要が無いのですが、実験をするときは

  やはりメンドウなので、こんなクラスを作っておくと使いまわせますし便利ですよね!


  

  さて、この例で分かったかと思いますが、Spring BatchはSpringJdbcの機能を簡単に扱えます

  バッチ処理をDBで扱う場合、たいていはSQL文の羅列をコマンドでDBに流すことになると思います。

  それはそれで分かりやすいし便利ですが、正常に終わらなかったときは事です。

  エラーなどで途中で処理が終わった場合、どこまで終わっているのか?、どのような復旧処理をしてから

  再実行すればいいのか?、途中から実行できないのか?、など問題点にいとまがありませんショック!


  SpringBatchでは、処理がどのstepまで終わっているか?などをDBに保存しています。

  restartすればDBの値を参考に前回停止したところから処理を再開してくれます。

  コマンドでDBに流す場合と同じく、SQL文を書くだけのところは変わらないのに、これだけ違うのです。


  すごく便利だと思いません?



  


JdbcBatchItemWriterについて】

クラス名に、Batchという言葉が入っています。

Batchというのは、指定の回数だけ一気にデータをDBに送り込むことを示しています。

標準のJavaのJDBCのクラスにもBatchというものがあるので、一般的な機能のようです。


データを一気に送り込むことで、1回1回送り込むよりもDBの処理が早くなります。

バッチ処理を行うときは大量データを送ることが多いと思います。

かなり重宝します!


また、先ほどコマンドでDBに流し込む話をしましたが、その弱点はもう一つあります。

指定の回数だけ一気にデータを送り込んだり、トランザクションのコミットタイミングを制御するのが難しいことです。


SpringBatchを使用すれば、コミットタイミングについても設定だけで可能になります


コミットタイミングについては別の記事で見てみようと思います。




【その他のORマッピングフレームワーク】

上記ではSpringJDBCを利用できることがわかりました。

では、他のORマッピングについてはどうでしょうか?

他にも使えるものがあります。以下のものです。


  ・IBatis

  ・Hibernate


JavaDocを確認してみてください。

また、使えなくても使えるように仲介者クラスを作っておけば大丈夫かと思います。



ここではItemWrtiterしか紹介しませんでしたが、もちろんItemReaderも用意されています。

特に、ページングと言う機能は便利ですので以下で簡単に紹介しておきます。



【ItemReaderのページング機能とは】

簡単にページングについて紹介します。

SpringBatchではページング以外のDB読み込みの機能もありますが、ページングの機能だけで十分ではないかなと思っています。

まあ、とにかく見てみましょう。


例題ではIBatisを使用しますが、他のものでも考え方は同じです。


 <bean id="dbReader"
   class="org.springframework.batch.item.database.IbatisPagingItemReader">
    <property name="queryId" value="getPagingMember" />
    <property name="sqlMapClient" ref="sqlMapClient" />
    <property name="pageSize" value="70000" />
 </bean>


 iBatisのSQL文設定ファイル:

  

  <select id="getPagingMember" resultClass="java.util.HashMap">
    select
      id,
      name,
      age
    from member

    order by id asc
    offset #_skiprows# LIMIT #_pagesize#
  </select>



 説明:

  ページングとは、WEBのページングのように、ページ番号と1ページのデータ数を指定した検索です。

  ページ番号は0~です。

  IbatisPagingItemReaderは、#_skiprows#, #_pagesize#さらに、#_page#というパラメタを

  自動で設定してくれます。

  これらをうまく組み合わせてSQL文で返却するレコードを調整します。

  そのPostgresの例題が、上記のSQL文です。


  参考までにOracleの例も書いておきます。

  Oracleの場合、limitが使えない分やっかいです。


   select
    id,
    name,
    age
   from (
    SELECT row_number() over(order by id) rn,
      id,
      name,
      age
    from member
   )
   where

    rn > (#_page# * #_pagesize#)
    and rn <= ( (#_page# + 1) * #_pagesize# )

   order by id

    ※本家SpringBatchのドキュメントでは、ROW_NUMを使用する方法がかかれていましたが

     上記のようにwindow関数を使用する方がきれいかなぁと個人的に思っています。



  補足:

   ページングは、順番が変わらないフィールドをorder byの対象にすべきだと思います。

   処理途中から再実行するときに、再実行までに誰かがデータを増やした場合、順番が変わってしまうと

   次に処理するデータが狂ってしまうからです。

   たいていの場合、一意のフィールドやシーケンス番号を入れているフィールドなどがあるかと思います。

   それらのフィールドを組み合わせれば順番が変わらないようなSQL文も頑張れば作れるでしょう。





さて、大まかですがDBの読み書きについてみてきましたが、分かりましたでしょうか?

いつも分かりやすいか?と自問しながら記事を書いているので、理解できましたらうれしいです!!




参照:

・トップ

・SpringBatch機能について

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

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

・CommandLineJobRunnerとは?

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

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

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

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




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

$
0
0





SpringBatchには、コミット数を設定できる機能があると今までの記事で書いてきました。

ここでは、その機能についてみてみましょう!


ついでに、

処理の途中でエラーが発生したときにrestartさせた動きも見てみましょう




ちなみに、

コミットはDBだけでなく、ファイルの書き込みにも関係していますので、

設定する数をうまく選んで処理スピードが速くなるようにしましょう!!



【サンプル】

サンプルは、前回の記事と同じものを使用します。


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


  上記の内容をすべて実行したら、以下の準備もお忘れなく!

    ・DBのmemberテーブルのデータをすべてクリアしておく

    ・起動の引数に、-nextをつけて実行する (つけないとAlreadyCompleted例外が発生すると思います)

【説明】


再掲:

<!-- ジョブの処理 -->
<job id="jobDb" xmlns="http://www.springframework.org/schema/batch "

  incrementer="jobParametersIncrementer">

   <step id="step1" parent="simpleStep" >
     <tasklet >
       <chunk reader="fileItemReader" writer="dbItemWriter" commit-interval="2" />
     </tasklet>
  </step>
</job>


  commit-interval="2"となっています。

 つまり、コミットタイミングが2個と設定されています。

 データ2個ごとにデータがコミットされます。


 サンプルでは、ファイルからの読み込みになっているので、2行を溜め込んで、2行をDBに書き込んで

 コミットすると言うことになります。

 処理のイメージは以下のとおりです。


  List items = new Arraylist();


  for(int i = 0; i < commitInterval; i++){
    items.add(itemReader.read());
  }


  itemWriter.write(items);


  なんとなく動きが理解できましたでしょうか?

  write()が内部で実際どのような処理になっているかは、使用するItemWriterによります。

  ちなみにFlatFileItemWriterでも、commitIntervalの数を上げると格段に処理が早くなります。

  しかし、数を上げすぎるとDBでもファイルでも、Out Of Memory例外が発生することもあります。

  もちろん、起動時にheapサイズなどを設定しておけば対応できますが、限界があるので

  最適値というものが存在します。

  何回か走行して最適値を見つけましょう!



  <ページングを使用したDBの読み込みと書き込みのコミットについて>

  ページングItemReaderの機能を使用すると、一気に指定のページサイズ分だけデータを読み込みます。

  そして、WriterがDBに値を書き込みます。

  一気に操作する数の設定項目が、「ページサイズ」と「コミット数(commitInterval)」2つあります。

  2つの設定が実際の動作にどのように関わるかを簡単に見てみます。

  実際に実験した内容です。

  

   <<設定値>>

   ページサイズ: 1000

   コミット数  :  100

  

   <<動作>>

   1.select文が実行され、1000レコードが読み込まれる。

   2.insert文をバッチ機能で100レコード一気に挿入する。

   3.2を10回繰り返し、1000レコード分が処理される。

   4.1に戻って繰り返し。select文でデータ取れなくなるまで実行される。


  なんとなく動作わかりましたでしょうか?

  ちなみに、何回SQL文が実行されるでしょうか?

  もし対象レコードが10000レコードあるとすると、

    select文 = 10000÷1000 = 10回

    insert文 = (10000÷1000)×(1000÷100) = 100回 

         (バッチ機能は1度SQL解釈してデータを一気に流し込みます)

  

  ついでに、ページ機能、commitInterval機能を使用しなかった場合も見ておきましょう。

   select文 = 1回

   insert文 = 10000回



  select文が少ない分、insert文の発行数が増えるので遅くなるのですね。





【restartの動作について】

では次に途中でエラーが発生した場合の動作を見てみましょう。


<準備>

前回の記事のサンプルでエラーを起こさせるには、以下のようにデータを書き替えます。


<データ>

1,太郎,18
2,次郎,29
3,さんま,53
abc,タモリ,54
5,鶴野,28



4行目が文字になっているので変換エラーが起きます。

しかも、コミット数は2にしているので、3行目のinsert処理をしてからエラーが起きます。

つまり、ちゃんとロールバックされていれば3行目の反映が無視され、2行目までが登録されているはずです



<実行結果>

Java Springの逆引きメモ


↓ログ

2010/08/21 11:39:50.312 [] INFO :: Job: [FlowJob: [name=jobDb]] launched with the following parameters: [{run.id=12}]
2010/08/21 11:39:50.937 [] INFO :: Executing step: [step1]
2010/08/21 11:39:51.156 [] ERROR :: Parsing error at line: 4 in resource=class path resource

     [sample/sample-db.txt], input=[abc,タモリ,54]
org.springframework.batch.item.file.FlatFileParseException: Parsing error at line: 4, input=[abc,タモリ,54]
at org.springframework.batch.item.file.mapping.DefaultLineMapper.mapLine(DefaultLineMapper.java:49)
at org.springframework.batch.item.file.FlatFileItemReader.doRead(FlatFileItemReader.java:188)
at org.springframework.batch.item.support.AbstractItemCountingItemStreamItemReader.read(AbstractItemCountingItemStreamItemReader.java:85)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:307)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:182)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:149)
(中略)
Caused by: java.lang.NumberFormatException: Unparseable number: abc
at org.springframework.batch.item.file.transform.DefaultFieldSet.parseNumber(DefaultFieldSet.java:688)
at org.springframework.batch.item.file.transform.DefaultFieldSet.readInt(DefaultFieldSet.java:290)
at sample.SimpleMapFieldSetMapper.read(SimpleMapFieldSetMapper.java:42)
at sample.SimpleMapFieldSetMapper.mapFieldSet(SimpleMapFieldSetMapper.java:34)
at sample.SimpleMapFieldSetMapper.mapFieldSet(SimpleMapFieldSetMapper.java:1)
at org.springframework.batch.item.file.mapping.DefaultLineMapper.mapLine(DefaultLineMapper.java:46)
... 43 more
2010/08/21 11:39:51.234 [] INFO :: Job: [FlowJob: [name=jobDb]] completed with the following parameters: [{run.id=12}] and the following status: [FAILED]



<さらにrestartする>

さて、予定通りエラーになりました。

ログにはちゃんとエラーになった行番号とデータの内容が出力されています!

DBのテーブルの中身を確認してみてください。2行目までしか登録されてないですよね!


では、restartしてみましょう。

まず、ファイルのデータを元に戻してください。(これ忘れずに!)

 

1,太郎,18
2,次郎,29
3,さんま,53
4,タモリ,54
5,鶴野,28



次にコマンドの実行するときに引数タブ内の記述で、-restartをつけて実行してください。


  classpath:/sample/runDb-context.xml jobDb -restart



<処理結果>

Java Springの逆引きメモ


↓ログ

2010/08/21 11:48:29.078 [] INFO :: Job: [FlowJob: [name=jobDb]] launched with the following parameters: [{run.id=12}]
2010/08/21 11:48:29.296 [] INFO :: Executing step: [step1]
2010/08/21 11:48:29.468 [] INFO :: Job: [FlowJob: [name=jobDb]]

    completed with the following parameters: [{run.id=12}] and the following status: [COMPLETED]



正常に終了しましたね。

DBのテーブルも見てみてください。

残りのデータが全部登録されました!



【まとめ】

SpringBatchを使用するだけで、手間はSQLのコマンドシェルみたいなものを作るのと同じくらいで

コミットタイミングの制御、およびrestartの機能まで実装できることを見ました。

かなり便利です。

少し覚えることはありますが、直感に近いのでそれほど惑わないと思います。


同様のSpringを使用したバッチ機能にTERAバッチというフレームワークがありますが

こちらはどうなんでしょうね。

気になります。



参照:

・トップ

・SpringBatch機能について

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

・CommandLineJobRunnerとは?

・Executionとは? (SpringBatch用語)

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

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

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

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

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

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



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

$
0
0

Springの掲示板を読んでいたら、面白い記事がありましたので触れてみたいと思います。

まさに、この記事のタイトルの内容です。



http://forum.springsource.org/showthread.php?t=48530


それほど難しくないので、まずはサンプルを見てみましょう!



※ここではItemWriterだけを記述します。

ItemReaderなどの設定は他の記事を参照してください。

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

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


※あと、ライブラリとしてmail.jarとactivation.jarが必要になるので、ダウンロードして配置してください

ちなみに、Java1.6以降ではactivation.jarは必要ないようです。




【メール送信ItemWriterクラス】
package sample;

import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.item.file.transform.LineAggregator;
import org.springframework.mail.MailSender;
import org.springframework.mail.SimpleMailMessage;

public class MailItemWriter<T> implements ItemWriter<T> {
 private static final String DEFAULT_LINE_SEPARATOR = System.getProperty("line.separator");
 protected static final Log log = LogFactory.getLog(MailItemWriter.class);
 
 private int maxSendCount = 10;
 private int sendedCount = 0;
 private LineAggregator<T> lineAggregator;
 private String lineSeparator = DEFAULT_LINE_SEPARATOR;
 private MailSender mailSender;
 private String mailSubject;
 private String mailFrom;
 private String[] mailTo;
 
 
 
 public int getMaxSendCount() {
  return maxSendCount;
 }
 public void setMaxSendCount(int maxSendCount) {
  this.maxSendCount = maxSendCount;
 }
 public LineAggregator<T> getLineAggregator() {
  return lineAggregator;
 }
 public void setLineAggregator(LineAggregator<T> lineAggregator) {
  this.lineAggregator = lineAggregator;
 }
 public String getLineSeparator() {
  return lineSeparator;
 }
 public void setLineSeparator(String lineSeparator) {
  this.lineSeparator = lineSeparator;
 }
 public MailSender getMailSender() {
  return mailSender;
 }
 public void setMailSender(MailSender mailSender) {
  this.mailSender = mailSender;
 }
 
 public String getMailSubject() {
  return mailSubject;
 }
 public void setMailSubject(String mailSubject) {
  this.mailSubject = mailSubject;
 }
 public String getMailFrom() {
  return mailFrom;
 }
 public void setMailFrom(String mailFrom) {
  this.mailFrom = mailFrom;
 }
 public String[] getMailTo() {
  return mailTo;
 }
 public void setMailTo(String[] mailTo) {
  this.mailTo = mailTo;
 }
 @Override
 public void write(List<? extends T> items) throws Exception {
  StringBuilder buf = new StringBuilder();
  if(this.sendedCount > this.maxSendCount) 
   throw new IndexOutOfBoundsException("送信数がmaxSendCountを超えました");
  
  try{
   for (T item : items) {
    buf.append(this.lineAggregator.aggregate(item) + this.lineSeparator);
   }
   
   //
   sendMail(buf.toString());
   
  }catch(Exception e){
   log.error("item書き込み&送信中にエラーが発生しました。", e);
   throw e;
  }
  
 }
 
 protected void sendMail(String text) throws Exception {
  SimpleMailMessage msg = new SimpleMailMessage();
  msg.setSubject(this.mailSubject + "[" + this.sendedCount + "]");
  msg.setFrom(this.mailFrom);
  msg.setTo(this.mailTo);
  msg.setText(text);
  
  try{
   this.mailSender.send(msg);
   ++this.sendedCount;
   
  }catch(Exception e){
   log.error("エラー発生時メールの送信に失敗", e);
   throw e;
  }
 }
}





【Springの設定ファイル一部抜粋】


 <!-- メール送信テスト -->
 <bean id="mailItemWriter" class="sample.MailItemWriter">
  <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$03d,%3$d" />
   </bean>
  </property>
  <property name="mailSender" ref="mailSender" />
  <property name="mailFrom" value="test@test.co.jp" />
  <property name="mailTo" value="xxx@mail.co.jp" />
  <property name="mailSubject" value="mailCsv" />
 </bean>

<!-- メールセンダー -->
 <bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
  <property name="defaultEncoding" value="ISO-2022-JP" />
  <property name="host" value="smtp.XXX.ne.jp" />
  <property name="username" value="Xxxxx" />
 </bean>






【説明】


メール送信のための機構もSpringは用意しています。

それがMailSenderです。

設定ファイルにそれを書くだけでOKです。


実際のデータ作成とメール送信する実装はItemWriterにあります。



再掲:ItemWriter


public void write(List<? extends T> items) throws Exception {
 StringBuilder buf = new StringBuilder();
 if(this.sendedCount > this.maxSendCount)
   throw new IndexOutOfBoundsException("送信数がmaxSendCountを超えました");

 try{
   for (T item : items) {
     buf.append(this.lineAggregator.aggregate(item) + this.lineSeparator);
   }

   //
   sendMail(buf.toString());

 }catch(Exception e){
   log.error("item書き込み&送信中にエラーが発生しました。", e);
   throw e;
 }
}

データの作成は、FlatFileItemWriterクラスで使用していたlineAggregatorを流用しています。

これでFormatter変換も、区切り文字も自在にできます。

しかも、何も自作しなくていいですね!にひひ


そして、作成したデータをmailSenderに送っているだけです。



ちなみに、メールを何回も送ると負荷がかかって危ないので、最大送信数を儲けています。


何かあっさりできましたね(笑)

Springがいろいろ用意してくれているのと、DIのおかげですね。



【拡張など】

もっと複数のデータ処理結果を送りたい場合、もしくは、ヘッダー・フッターなどを付けたい場合。

それほど難しくないです。

まずヘッダー・フッターについてはFlatFileItemWriterの真似をしましょう。

FlatFileItemWriterではheaderCallbackプロパティを持っていますよね。これをつけて、内部で処理の最初に

呼び出してあげればいいですよね。


また、複数のデータ処理結果を送りたい場合は、それぞれの処理結果をファイルに保存しましょう。

ItemReaderの中には複数のファイルをまとめて扱うクラスがあります。

MultiResourceItemReaderです。

readerにこのクラスを指定すれば、複数になってしまった処理結果ファイルをひとまとめにできます。

あとはwriterに上のメールItemWriterを指定すればよいでしょう。


結構、再利用でき、便利ですね!



【補足】

念のため補足しておきます。

上記のサンプルは簡単にするため、restartできるようには考えていません

なぜならJobが終了したときにWriterクラス内部で持っているsendedCount も消えてしまうからです。

restartしたときにメールのタイトルが「mailCsv[0]」から始まってしまいます。

Javaが終了するので内部のインスタンスが消えてしまうので当たり前ですよね。

(このサンプルでは問題はメールタイトルだけですが、restartできるようにする方法は知っておきましょう)

しかし、値をを保持し、restartできるようにすることは簡単です。

StepExectutionの中に持っているExecutionContextに値を保存し、そこから値を取得してメールのタイトルに使用すればOKです。

もっと詳しいことは他の記事で書いてみようと思います。

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



参照:

・トップ

・SpringBatch機能について

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

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

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

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

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

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

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

・既に用意されている機能(クラス)を探すには?

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

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

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




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

$
0
0


Spring BatchをWEBから起動したい場合があるかもしれません。


または、サーバにBatchをおいておいて、他のサーバからキックだけしたい場合があるかもしれません。


こんなときは「非同期」で起動したくなると思います。


この記事ではSpringBatchを非同期で起動する方法を見てみます。

実はとっても簡単です。



【サンプル(SpringBatch設定ファイル一部抜粋)】


<bean id="jobLauncher"
class="org.springframework.batch.core.launch.support.SimpleJobLauncher">
<property name="jobRepository" ref="jobRepository" />
<property name="taskExecutor">
<bean class="org.springframework.core.task.SimpleAsyncTaskExecutor" />
</property>
</bean>



【起動例(プログラム)】


JobLauncher jobLauncher = (JobLauncher )applicationContext.getBean("jobLauncher");

Job job = (Job)applicationContext.getBean("job");

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



【説明】

jobLauncherのtaskExecutorプロパティにSimpleAsyncTaskExecutorを設定すれば非同期になります。

簡単ですよね。


起動も簡単です。

jobLauncherのrunメソッドを呼ぶだけです!


その他の起動方法として、以下の記事でプログラムから起動する方法も記述しています。
  ・CommandLineJobRunnerとは?



【注意点】

コマンドラインから実行する場合、実行が終了すればJavaが落ち、StepやJobのインスタンスも解放されます。

でもWEBでバッチ実行する場合、StepやJobのインスタンスは当然解放されません。

そうすると、Stepの内部でファイルを扱えばそのインスタンスが残っている可能性もあります。

その状態でJobが再度起動され、同じStepが実行されればファイルの読み込み・書き込みがおかしくなるかも知れません。

そうするとStep毎にItemReaderやItemWriterなどのインスタンスが生成される方が良くなります。


このとき生きてくるのがSpringBatch用の新scopeである「step」です。

生成の問題の回避のため、ItemReaderやItemWriterなどのbeanのscope属性にstepを使用してみてください!


他の記事でちょくちょく使ってますのでサンプルを見てもらえればと思います。



【おまけ】

Spring Batchをサーバーにおいておいて、他のサーバからキックするという使い方もできます。

Spring RemoteでRMIなどでSpringBatchのJobを公開しておいて、他のサーバからそれをキックするという

やり方です。(Jobを起動する仲介者クラスさえ作ればうまく行きます)

そうするとバッチ処理を一箇所におけるので1つのやり方だと思います。


ただ、RMIなどは新しいバージョンをリリースしたい場合に停止しないといけないとかあるかもしれません。

RMIの機能を調べていないので分かりませんが、もしそうなら、WEBからバッチをキックしている場合、

WEBにも影響が行くことになり、リリースが大変になるかもしれません。

でも、何か他の方法があって、いいやり方があるかもしれません。


いろいろ発展性はありそうで、面白そうですよね!




参照:

・トップ

・SpringBatch機能について

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

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

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

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

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

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

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

・既に用意されている機能(クラス)を探すには?

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

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





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

$
0
0

SpringBatchで用意しているItemReader、ItemWriterについては、既にrestartできるように設計されています。

自作で作った場合は自分で設計しなければなりません。

restartできるように設計するときに、どのように考えればよいか?

また、実際にどのようにプログラムすればよいかを簡単に見ていきます。


 ※注意: Springが用意しているクラスの中にはテストや検証を簡単にするものがあり、

  これらはrestart設計されてません。


【restart可能にするための基本的考え方】

restartするということは、前回どこか途中で終了しています。

基本的にはどこまで読み込んだか?どこまで書き込んだか?を保存しておく必要があります。

そして、Stepを開始したときに保存した値を読み込んで、途中で終わっていればそこまでの処理を実行する必要があります。


例えば、前回ItemWriterが5回目で失敗した場合、ItemReaderで4回目までデータを読み飛ばしてStep処理を始めれば、後はJobに任せれば5個目のデータを読み込んでItemProcessor、ItemWriterにデータを渡すのでOKということにりますね!


そうすると知っておくべきポイントが以下のようになるかと思います。

 ・最後のデータの位置を保存する方法

 ・Stepが開始したときにデータを読み飛ばす方法


では上記をこれから見ていくことにしましょう!



【最後のデータの位置を保存する方法】

まず、ExcecutionContextについて知っておきましょう!

簡単に言うと、データを保存しておくMapです。

StepExecutionとJobExecutionそれぞれに別々のExcecutionContextが保持されています。

Stepについては、Stepごとに別々のインスタンスで保持されています。

詳しい説明は、・Executionとは? (SpringBatch用語) を参照ください。


まあ、ほぼこれが答えです(笑)


 例: executionContext.putInt("sendMailCnt", this.sendedCount);


こんな風にすれば値が保持されます。

そして、設定した値がDBに保存されることになります。


保存についてはもうひとつ重要なポイントがあります。

ItemWriterがコミットしたときに保存しなければなりません。

なぜならもし、ItemReaderがデータを読み込んだときに保存すれば、その後ロールバックしたら数がずれちゃいますよね?

例えば、コミット間隔が5個で、10個目のデータまで読み込み、ItemWriterで8個目のデータ書き込みでロールバックして終了した場合。

コミットされているのは5個目までなので、restart時は6個目からItemReaderが読み込み始めないといけません。

もし10個目のデータを読み込んだ段階で10という値を保存してしまえば11個目から読み込み始めてしまいますよね。


そこで、コミットがあったときに読み込んだ個数を保存するようにします


方法: 

まず、ItemReaderにItemStreamをimplementsします。 

ItemStreamはStepをopen/close/commitするときに呼び出されるメソッドを用意しています。

implementsしたItemReader、ItemWriterなどは自動でそれらのメソッドが呼ばれます。


例: commitしたときにread数を保存する


public void update(ExecutionContext executionContext)
throws ItemStreamException

{

  executionContext.putInt("sendMailCnt", this.sendedCount);

}


上記のような感じになります。




【Stepが開始したときにデータを読み飛ばす方法】

さてどこまで読み込んだかを保存できたところで次に、restartしたときにデータを読み飛ばす方法を見てみます。

実はこの答えの半分は、ItemStreamにあります。

ItemStreamのopenメソッドに読み飛ばしの処理を実装すればOKです。

例えば以下のような具合です。


例:

public void open(ExecutionContext executionContext) throws ItemStreamException

{

  int itemCount = executionContext.getInt("sendMailCnt", 0);

  this.sendedCount = itemCount;

  for (int i = 0; i < itemCount; ++i)

   readData(); //データ1個を読むだけのメソッドを作っておく

}




これでなんとなくやり方わかりましたでしょうか?

open/updateはSpringBatchの方で自動で呼んでくれるので、呼び出すための処理は記述する必要はありませんビックリマーク

ItemWriterにもそのときそのときの状態を持たなければrestartできない場合は、ItemReaderと同様にItemStreamをimplementsして同様のことをしてください。


それ程難しくないですよね。


せっかくなので自作のクラスについてもrestart可能にしましょう!


もしそれがかなわない場合は、設定ファイルのjobタグの属性にrestartable="false" を追加しておきましょう!





参照:

・トップ
・SpringBatch機能について

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

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

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

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

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

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

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

・既に用意されている機能(クラス)を探すには?

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

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

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

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

・CommandLineJobRunnerとは?

・Executionとは? (SpringBatch用語)


ItemReaderで読み込んだデータを妥当性チェックするには? (valang方式)

$
0
0

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

ここでは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の外部記事

MessageSourceを使いやすくするには?

$
0
0

SpringではMessageSourceも用意されており、設定ファイルに設定すれば使用できます。

しかし、そのままでは少し使いにくいので使いやすくする方法を見てみましょう!



【Springの設定ファイルのサンプル(applicationContext.xml)】


<bean id="messageSource"
class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename" value="messages"/>
</bean>


<bean id="sample" class="sample.Sample">
<property name="messageSource" ref="messageSource"/>
</bean>



【Sampleクラス(sample.Sample)】


public class Sample{



public void setMessageSource(MessageSource messages) {
this.messages = new MessageSourceAccessor(messages);
}


public String obtainMsg(){

return this.messages.getMessage("sample.msg", "メッセージのデフォルトです");
}


}



【メッセージファイル(messages_jp.properties)】


sample.msg=サンプルメッセージ




【説明】


特に説明の必要は無いかもしれません。


this.messages = new MessageSourceAccessor(messages);

単純にMessageSourceAccessorでラップしているだけです。

この便利さは、もとのMessageSourceの使い方を見ないと分からないかと思います。


 <MessageSourceの使い方>

 getMessage(sample.msg", null, "メッセージのデフォルトです", Locale.getDefault());


 <MessageSourceAccessorの使い方(再掲)>

 getMessage("sample.msg", "メッセージのデフォルトです");


いくつか引数を入力しなくて良くなっていますよね!

ほんの少しの違いですが、何回も書くと意外と疲れます。

このクラスもうまく使ってみてくださいニコニコ




【おまけ】

SpringにはVMを再起動しなくてもリロードできるメッセージリソースもあります。

 ReloadableResourceBundleMessageSource


また、テスト用にプログラム上で書き換えられるメッセージリソースもあります。

 StaticMessageSource


わざわざ自作するようなことはやめて、まずはこれらが使えないか検討してみましょう!




参考:

・トップ

・DIの設定ファイルで外部ファイル(プロパティファイル)を参照するには?

・DIの設定ファイル内から外部DIファイルを参照するには?

・プロパティファイルをPropertiesクラスのbeanにするには?




DIの機能について

$
0
0

Springは世界中で使用されているフレームワークで、GoogleAppEngineや、VMForceなどでも使用可能になっていて、使い方が分からないと乗り遅れている感があります。

しかし、「じゃあ、Springって何!?」って調べ始めると、DI(依存性の注入)とか、IoC(制御の反転)とか出てきて、「?」が広がっていきます


ここでは、難しい説明は他の書籍やホームページに任せて、もっと簡単な説明をしてみたいと思います

説明は自分の解釈で書きますのでそこは注意をお願いしたいところですが、

導入の解釈としてはこれで十分だと思っていますグッド!(おそらく・・・)

使っていくうちになんとなくDIとか分かっていくものじゃないでしょうか。(たぶん)



【SpringのDIって何?】


ひと言で言うなら、


 「設定情報を外部定義化できる機能」


です。

外部定義というと、Windowsでいうとiniファイル、Javaなら.propertiesファイルなどを思い浮かべますが、

それらは固定値をコンパイルなしに変更できる機能です。

Springがすごいところは、


 「使用するクラスも外部定義化でき、変更できる」


ところです。

どういうことかは、この後、みていきます。




【Springの外部定義化】

通常、外部定義をする場合、次のような実装をする必要があります。

 ①外部定義ファイルから値を取り出す機能の実装

 ②取り出した値を、使用するクラスもしくはメソッドに渡す機能の実装


この程度ならたいしたこと無いでしょ!って思うかもしれませんが、設定が増えてくると

これだけでもかなりのコード量になり、おそろしく大変になります


Springでは上記のコードを実装する必要がなくなります。

その具体的な例を見ていきましょう!


まず、Springでは外部定義にXMLを使用します。

以下のような感じです。

例では、データをファイル出力することを考えます。



 <設定XMLの記述例>

 <bean id="fileWriter" class="com.file.MyFileWriter">
   <property name="extractor" ref="csvExtractor"/>
 </bean>


 <bean id="csvExtractor" class="com.file.MyCsvExtrator">

   <property name="delimiter" value=","/>
 </bean>



 beanは1つのクラスを表し、そのクラスをnewした実体を内部で持っています。

 id属性は、その実体に名前をつけたものと思ってもらえば大丈夫です。

 上記の場合、2つのクラスが登場していて、以下のようにしたのと同じことになります。


 MyFileWriter fileWriter = MyFileWriter();

 MyCsvExtrator csvExtractor = new MyCsvExtrator();


 //propertyタグでの設定は、setterを表す(文字列を設定)

 csvExtractor.setDelimiter(",");

 

 //propertyタグでの設定、setterを表す(クラス自体も設定できる)

 fileWriter.setExtractor(csvExtractor);


  ※CsvExtractorは、与えられた1行分のデータをカンマ区切り文字列に変換するクラスとしています。


 なんとなく分かるでしょうか?

 newして、setterで値を設定しているだけです。

 propertyタグはsetterを表していて、値を設定する場合はvalue属性を使用し、クラス(bean)を設定する場合は

 ref属性を使用します。


 これで、最初の課題であった①②の、値をファイルから取得すること、値をクラスに設定すること、の

 2つを実現できているのが分かるでしょうか?

 区切り文字も外部定義化できています。

  

  再掲:

  <bean id="csvExtractor" class="com.file.MyCsvExtrator">

    <property name="delimiter" value=","/> ←ここを" "に変えれば空白区切りになる!!
  </bean>


 XMLファイルを変更すれば、コンパイルなしに任意の区切り文字にできます!

 もちろん、MyFileWriter、CsvExtractorの実装は必要ですが、それはSpringを使用しなくても同じです。

 



【動作の変更】
お話は、まだまだ続きます!


上記の例では、値の設定だけでなく、クラスも設定しています。


 再掲: fileWriter.setExtractor(csvExtractor);

これを利用すると、動作の変更もできます。


 

 <設定XMLの記述例>

 <bean id="fileWriter" class="com.file.MyFileWriter">
   <property name="extractor" ref="fixedExtractor"/>
 </bean>


 <bean id="csvExtractor" class="com.file.MyCsvExtrator">

   <property name="delimiter" value=","/>
 </bean>


 <!-- 追記 -->

 <bean id="fixedExtractor" class="com.file.MyFixedExtrator">

 </bean>


 ※MyFixedExtratorは、与えられた1行分のデータを固定長で文字列に変換するクラスとしています


 
fixedExtractorを追記しました。

そして、fileWriterも書き変えています。


 再掲:

 <bean id="fileWriter" class="com.file.MyFileWriter">
   <property name="extractor" ref="fixedExtractor"/> ←設定するクラスを変更しました
 </bean>


 これだけで、カンマ区切りファイル出力から、固定長によるファイル出力に変わりました


 通常の設定ファイルでも動作を変えることはできますが、相当コードを記述しなければなりません。

 しかしSpringでは設定ファイルのためのコード記述は必要なく、ref属性を変更するだけです!


 簡単ですよね!


この例でなんとなく分かったかと思いますが、Springを使うと、

 

   「必要な機能の実装のみに専念できる!」


ということになります。

今まで煩わされた外部定義化の実装に工数をかける必要がなくなります!




【設定したクラス(bean)を実際に使用する】

ちょっとあとまわしになりましたが、上記で設定した内容をプログラム上で使用する方法に軽く触れます。


 <使用方法例>

 //XMLファイルのロード(他にもロードするクラスはあります。状況に合わせて使用するクラスを決めます)

 ApplicationContext ac = new FileSystemXmlApplicationContext("c:/app/context.xml");

 

 //クラスの取得

 MyFileWriter fileWriter = (MyFileWriter)ac.getBean("fileWriter");

 

 //ファイル出力

 fileWriter.output("c:/app/out.txt");


 

簡単ですよね!

ファイルをロードして、getBean()で指定のbeanを取得するだけです!


さて、さきほどの内容とあわせると、とってもいいことがあります。

それがここでのポイントですビックリマーク


 さきほど、MyFixedExtratorを使用するように変更しました。

 この影響はあるでしょうか?

 上記のプログラムをみると、関係ありそうなところは、ac.getBean("fileWriter");だけです。

 しかも、bean名「fileWriter」は変更しないので影響が一切ありません!!


 

これがポイントです。

Springを適切に使用すれば、拡張も変更も既存のコードを変更することなく容易にできるようになります。

また、変更が容易なため、テスト用のモックに切り替えることも簡単になり、テストの効率が上がります!


Spring導入部の説明は以上です。

ここでの説明は話を簡単にするため、省略している便利な機能がいくつもあります。

あとはどんどん使って実践あるのみです!




【補足】

Springの設定ファイルは、jarファイルの中に含めることがほとんどです。

その場合、jarの中のファイルを変更することは大変です。

でも大丈夫!

当然、Springはそのような場合でも対応できます。

以下を参照してみてください!

 

 

 ・DIの設定ファイルで外部ファイル(プロパティファイル)を参照するには?

 ・DIの設定ファイル内から外部DIファイルを参照するには?




参考:

・トップ

・DIの設定ファイルを書くには?

・DIの設定ファイルで外部ファイル(プロパティファイル)を参照するには?

・DIの設定ファイル内から外部DIファイルを参照するには?

・プロパティファイルをPropertiesクラスのbeanにするには?

・MessageSourceを使いやすくするには?

・SpringJDBCの機能について

・DIxAOPの機能について

Strutsと連携するには?(理論編)

Spring IDEを使ってみよう

Spring Framework について









目次

$
0
0

このブログでは、JavaのSpringの記事をメインに書いていきます。

その目次です。



【一般的準備(Springとは無関係です)】

・TomcatのGETの文字化け対策

・windowsでTomcatを使用する場合の注意点


【Spring準備編】

・Springを使用するには?

・SpringでWEB作成するには?(基本的な作成例)

・Springとは? (機能一覧と概要)

・Springにおけるファイル構成



【Springのコア機能】

・DIの機能について

・DIの設定ファイルを書くには?

・DIの設定ファイルで外部ファイル(プロパティファイル)を参照するには?

 (↑環境変数やシステムプロパティを参照する方法も記載しています)

・DIの設定ファイル内から外部DIファイルを参照するには?

・プロパティファイルをPropertiesクラスのbeanにするには?

・MessageSourceを使いやすくするには?



【SpringMVC機能】
・SpringMVCの機能について

・Controller内でApplicationContextを取得するには?

・リクエストの文字コードを設定ファイルで制御するには?

・SpringMVCを使用してWEBを作るには? (基礎編)

・Validatorでパラメタの妥当性チェックをするには?

・SpringMVCを使用してWEBを作るには? (実践編) : (パラメタとオブジェクトのバインドの方法)

・パラメタをオブジェクトの日付型データと結びつけるには? (型のバインドの追加方法)

・tilesについて




【SpringModules機能】

・SpringModulesの機能について

・Strutsのように設定ファイルでパラメタの妥当性チェックをするには?

・日本語の妥当性チェックを追加するには? (独自のチェックの追加の仕方)



【SpringSecurity機能】
・SpringSecurityの機能について

・実際に認証と認可をWEBにつけるには? (基礎編:設定方法)

・DBのユーザ情報を使用して認証するには? (実践編:DBを使用する方法)

・ログイン後の画面にログインしたユーザを表示するには? (ログイン情報の表示方法)

・ロール(権限)によって画面のリンクの表示/非表示を制御するには?

・閲覧許可がある画面のリンクのみ表示するには?

・拡張して、独自の機能をつけるには?




【SpringJDBC機能】

・SpringJDBCの機能について

・SpringJDBCで作成したDaoとビジネスロジックを連携するには?

・DBからデータを取得するには?(SimpleJdbcTemplate のサンプル)

・DBからデータを取得するには?(NamedParameterJdbcTemplateのサンプル)

・NamedParameterJdbcTemplateをうまく使うには? (HashMap版)

・NamedParameterJdbcTemplateをうまく使うには?(POJO版)

・結果をMapの配列で受け取るには?

・DBにデータを登録するには?

・DBへのデータ登録を簡単にするには?

・実行したSQL文のログを出力するには?



【DIxAOP機能】

・DIxAOPの機能について

・Springに付属のAOPサンプルのログトレースを動かすには?

・ログを出力するには?(独自のAOP処理の追加の仕方)

・オブジェクトを読み取り専用にするには? (オブジェクトへの適用の仕方)



【Springトランザクション機能】

・Springのトランザクション機能について
・トランザクションのコードを書かないようにするには?(宣言的トランザクション)

・アノテーションによるトランザクション管理
・プログラムによるトランザクション管理(TransactionTemplate)

・プログラムによるトランザクション管理(PlatformTransactionManager)



【SpringBatch機能】
・SpringBatch機能について

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

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

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

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

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

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

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

・既に用意されている機能(クラス)を探すには?

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

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

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

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

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

・ItemReaderで読み込んだデータを妥当性チェックするには? (valang方式)



【Strutsとの連携】
Strutsと連携するには?(理論編)

Strutsと連携するには?(実践編)

連携方法を拡張して、より自由度が高い連携をするには?

検索画面を簡単に実現するには?




【語句の説明】

・AOPとは?

・Anthenticationオブジェクト(SpringSecurity)とは

・CommandLineJobRunnerとは?

・Executionとは? (SpringBatch用語)

・Springとは?

・tilesとは?

・XMLとは?

・システムプロパティとは?

・フィルタとは?(tomcat)


test

$
0
0
アメンバー限定公開記事です。

既に用意されている機能(クラス)を探すには?

$
0
0

Spring Batchは処理の流れを記述する機能がメインの機能で、interfaceを継承して処理を実装します。


しかし、SpringBatchが元から用意してくれている実装クラスも数多く存在します。


ところが、UserGuideなどにもそれ程しっかり実装クラスを書いてくれていなかったりします。

これを探す方法はいくつかあるかと思いますが、良くやる手を書いておこうと思います。

たいしたことではないので、簡単にいきます。



【実装クラスを探す方法】


まず探したい機能の、interfaceを知っておく必要があります。

これはそれ程難しくありません。なぜならそれほど多くないからです。


主なinterface:

  ・ItemReader

  ・ItemWriter

  ・ItemProcessor

  ・Tasklet

  ・Step

  ・StepExecutionListener

  

さらに、これらのinterfaceの継承クラスのsetterなどで使用するinterfaceなどもありますが、検索の仕方は同じです。

まず、interface名 + spring batch でgoogleで検索します。

すると目的のinterfaceのjavadocが見つかるはずです。




<検索結果例>

ItemReader (Spring Batch 2.1.2.RELEASE API)
- [ このページを訳す ]

public interface ItemReader<T>. Strategy interface for providing the data. Implementations are expected to be stateful and ... Implementations need *not* be thread safe and clients of a ItemReader need to be aware that this is the case. ...
static.springsource.org/spring.../ItemReader.html - キャッシュ - 類似ページ



そのページの、All Known Implementing Classes: を見ると、実装クラスが分かります。

Springはクラス名が機能の内容を表しているので、それらしいクラスのリンクを押して

処理内容を確認してみればOKです。


ここで目的の機能が見つからなければ、おそらく自作するしかないでしょう。


残念~!ですがむっ



参照:

・トップ






既に用意されている機能(クラス)を探すには?

$
0
0

Spring Batchは処理の流れを記述する機能がメインの機能で、interfaceを継承して処理を実装します。


しかし、SpringBatchが元から用意してくれている実装クラスも数多く存在します。


ところが、UserGuideなどにもそれ程しっかり実装クラスを書いてくれていなかったりします。

これを探す方法はいくつかあるかと思いますが、良くやる手を書いておこうと思います。

たいしたことではないので、簡単にいきます。



【実装クラスを探す方法】


まず探したい機能の、interfaceを知っておく必要があります。

これはそれ程難しくありません。なぜならそれほど多くないからです。


主なinterface:

  ・ItemReader

  ・ItemWriter

  ・ItemProcessor

  ・Tasklet

  ・Step

  ・StepExecutionListener

  

さらに、これらのinterfaceの継承クラスのsetterなどで使用するinterfaceなどもありますが、検索の仕方は同じです。

まず、interface名 + spring batch でgoogleで検索します。

すると目的のinterfaceのjavadocが見つかるはずです。




<検索結果例>

ItemReader (Spring Batch 2.1.2.RELEASE API)
- [ このページを訳す ]

public interface ItemReader<T>. Strategy interface for providing the data. Implementations are expected to be stateful and ... Implementations need *not* be thread safe and clients of a ItemReader need to be aware that this is the case. ...
static.springsource.org/spring.../ItemReader.html - キャッシュ - 類似ページ



そのページの、All Known Implementing Classes: を見ると、実装クラスが分かります。

Springはクラス名が機能の内容を表しているので、それらしいクラスのリンクを押して

処理内容を確認してみればOKです。


ここで目的の機能が見つからなければ、おそらく自作するしかないでしょう。


残念~!ですがむっ



参照:

・トップ






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

$
0
0

ここまでの記事で、SpringBatchのバッチ処理の記述の仕方を見てきました。


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

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

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



しかし、ファイルの読み書きばかりでした。

この記事ではDBを扱うサンプルを見てみようと思います。



【処理内容】

まず、これからどのようなことをやろうとしているかを書いておきます。


 ①ファイルからデータを読み込みます。

 ②DBのテーブルに読み込んだデータを書き込みます。



【準備】

SpringBatchの準備は今までと同じです。以下の記事を参考に準備をお願いします!

 

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


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



さらに、今回はデータ書き込み先のテーブルも作っておきます。


<テーブル (使用するDBはPostgeresです)>

CREATE TABLE member
(
id integer NOT NULL,
uname varchar(20),
age integer,
in_date date
)

 ※他のDBを使用する場合は、DBに合わせてcreate文、dataSourceの設定内容を変更してください。



 <パッケージの作成>
 プロジェクトには/sample パッケージを作成しておいてください。



【 /sample/back-context.xml ファイル(Batchバックグラウンドの設定)】


 以下の記事と同じ物を使いまわします。

 リンク内の同じ項目の内容をファイルにしてください。

   

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



【SimpleMapFieldSetMapperクラス】
 これも前回の記事を使いまわしますので、同じ項目の内容をコピーしてください。

  

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



【 /sample/sample-db.txt ファイル(読み込みファイル)】

1,太郎,18
2,次郎,29
3,さんま,53
4,タモリ,54
5,鶴野,28



【 /sample/runDb-context.xml ファイル(バッチ処理の設定)】<?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="jobDb" xmlns="http://www.springframework.org/schema/batch " incrementer="jobParametersIncrementer">   <step id="step1" parent="simpleStep" >   <tasklet >    <chunk reader="fileItemReader" writer="dbItemWriter" 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="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/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,uname,age" />     </bean>    </property>    <property name="fieldSetMapper">     <bean class="sample.SimpleMapFieldSetMapper" >      <property name="types" value="int,String,int" />     </bean>    </property>   </bean>  </property> </bean> <!-- SQLを実行していく(readしたMapをプレースホルダに設定して実行する)  --> <bean id="dbItemWriter" class="org.springframework.batch.item.database.JdbcBatchItemWriter">  <property name="dataSource" ref="dataSource" />  <property name="itemSqlParameterSourceProvider">   <bean class="sample.MapSqlParameterSourceProvider" />  </property>  <property name="itemPreparedStatementSetter" >   <bean class="org.springframework.batch.item.database.support.ColumnMapItemPreparedStatementSetter" />  </property>  <property name="sql" value="   insert into member(id, uname, age)   values(:id, :uname, :age)  " /> </bean>  </beans>【 /sample/MapSqlParameterSourceProvider (SpringJdbc関連のクラス)】package sample;import java.util.Map;import org.springframework.batch.item.database.ItemSqlParameterSourceProvider;import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;import org.springframework.jdbc.core.namedparam.SqlParameterSource;public class MapSqlParameterSourceProvider implements  ItemSqlParameterSourceProvider<Map<String, Object>> { @Override public SqlParameterSource createSqlParameterSource(Map<String, Object> map) {  return new MapSqlParameterSource(map); }}



【起動方法】

前回までと同じです。

以下のリンクの【実行】と同じことをしてみてください。

ただし、「プログラムの引数」はちょっと変えます。


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


  プログラムの引数 : classpath:/sample/runDb-context.xml jobDb

 


  ※ただし、2回目以降は状態がCOMPLETEDになっているため実行エラーが出ます。

   この場合、プログラムの引数に -nextを付け加えてみてください。

   (参考:・CommandLineJobRunnerとは?

   あと、重複キー違反も起きると思うのでテーブルのデータも一度クリアしてください。




【結果】

2010/08/19 23:29:03.921 [] INFO :: Job: [FlowJob: [name=jobDb]]

        launched with the following parameters: [{run.id=1}]
2010/08/19 23:29:04.062 [] INFO :: Executing step: [step1]
2010/08/19 23:29:04.265 [] INFO :: Job: [FlowJob: [name=jobDb]]

        completed with the following parameters: [{run.id=1}] and the following status: [COMPLETED]




【説明】

どうでしょうか?

無事、DBにデータが書き込まれましたでしょうか?


やっていることは今までの記事の内容と同じです。


①ItemReaderでファイルからデータを読み込みます。
  これは前回の記事、そのままです。

  ですので、説明は前回の記事に譲ります。


   ・Stepを使って処理を書くには?   (作成したクラスSimpleMapFieldSetMapperについて


②ItemWriterで読み込んだデータを書き込みます。

 JdbcBatchItemWriterを使用しています。


再掲:

<bean id="dbItemWriter" class="org.springframework.batch.item.database.JdbcBatchItemWriter">
 <property name="dataSource" ref="dataSource" />
 <property name="itemSqlParameterSourceProvider">
   <bean class="sample.MapSqlParameterSourceProvider" /> 

 </property>
 <property name="itemPreparedStatementSetter" >
   <bean class="org.springframework.batch.item.database.support.ColumnMapItemPreparedStatementSetter" />
 </property>
 <property name="sql" value="
   insert into member(id, uname, age)
   values(:id, :uname, :age)

 " />
</bean>


  JdbcBatchItemWriterは、SpringJdbcのバッチ機能を使用してDBにアクセスします。

  SpringJdbcの詳細は、・SpringJDBCの機能について を参照してください。

  SpringJdbcは、"?"をプレースホルダにするタイプと、":名前"をプレースホルダにするタイプがあります。

  上記では、後者を使用しています。(sqlプロパティを見てみてください。)

  itemSqlParameterSourceProviderは、プレースホルダに使用する値をどのように与えるかを

  定義するプロパティです。

  ItemReaderは、LinkedHashMapを返すように設定したので、Mapのキー名をプレースホルダに

  展開したいのですが、そのようなクラスは既存のSpringBatchでは用意されていません。

  ですので、自作しました。(MapSqlParameterSourceProvider

  SpringJdbcのクラスである、MapSqlParameterSourceにMapを渡すだけの単純なクラスです。

  本当はMapではなく、Beanを使用すると自作する必要が無いのですが、実験をするときは

  やはりメンドウなので、こんなクラスを作っておくと使いまわせますし便利ですよね!


  

  さて、この例で分かったかと思いますが、Spring BatchはSpringJdbcの機能を簡単に扱えます

  バッチ処理をDBで扱う場合、たいていはSQL文の羅列をコマンドでDBに流すことになると思います。

  それはそれで分かりやすいし便利ですが、正常に終わらなかったときは事です。

  エラーなどで途中で処理が終わった場合、どこまで終わっているのか?、どのような復旧処理をしてから

  再実行すればいいのか?、途中から実行できないのか?、など問題点にいとまがありませんショック!


  SpringBatchでは、処理がどのstepまで終わっているか?などをDBに保存しています。

  restartすればDBの値を参考に前回停止したところから処理を再開してくれます。

  コマンドでDBに流す場合と同じく、SQL文を書くだけのところは変わらないのに、これだけ違うのです。


  すごく便利だと思いません?



  


JdbcBatchItemWriterについて】

クラス名に、Batchという言葉が入っています。

Batchというのは、指定の回数だけ一気にデータをDBに送り込むことを示しています。

標準のJavaのJDBCのクラスにもBatchというものがあるので、一般的な機能のようです。


データを一気に送り込むことで、1回1回送り込むよりもDBの処理が早くなります。

バッチ処理を行うときは大量データを送ることが多いと思います。

かなり重宝します!


また、先ほどコマンドでDBに流し込む話をしましたが、その弱点はもう一つあります。

指定の回数だけ一気にデータを送り込んだり、トランザクションのコミットタイミングを制御するのが難しいことです。


SpringBatchを使用すれば、コミットタイミングについても設定だけで可能になります


コミットタイミングについては別の記事で見てみようと思います。




【その他のORマッピングフレームワーク】

上記ではSpringJDBCを利用できることがわかりました。

では、他のORマッピングについてはどうでしょうか?

他にも使えるものがあります。以下のものです。


  ・IBatis

  ・Hibernate


JavaDocを確認してみてください。

また、使えなくても使えるように仲介者クラスを作っておけば大丈夫かと思います。



ここではItemWrtiterしか紹介しませんでしたが、もちろんItemReaderも用意されています。

特に、ページングと言う機能は便利ですので以下で簡単に紹介しておきます。



【ItemReaderのページング機能とは】

簡単にページングについて紹介します。

SpringBatchではページング以外のDB読み込みの機能もありますが、ページングの機能だけで十分ではないかなと思っています。

まあ、とにかく見てみましょう。


例題ではIBatisを使用しますが、他のものでも考え方は同じです。


 <bean id="dbReader"
   class="org.springframework.batch.item.database.IbatisPagingItemReader">
    <property name="queryId" value="getPagingMember" />
    <property name="sqlMapClient" ref="sqlMapClient" />
    <property name="pageSize" value="70000" />
 </bean>


 iBatisのSQL文設定ファイル:

  

  <select id="getPagingMember" resultClass="java.util.HashMap">
    select
      id,
      name,
      age
    from member

    order by id asc
    offset #_skiprows# LIMIT #_pagesize#
  </select>



 説明:

  ページングとは、WEBのページングのように、ページ番号と1ページのデータ数を指定した検索です。

  ページ番号は0~です。

  IbatisPagingItemReaderは、#_skiprows#, #_pagesize#さらに、#_page#というパラメタを

  自動で設定してくれます。

  これらをうまく組み合わせてSQL文で返却するレコードを調整します。

  そのPostgresの例題が、上記のSQL文です。


  参考までにOracleの例も書いておきます。

  Oracleの場合、limitが使えない分やっかいです。


   select
    id,
    name,
    age
   from (
    SELECT row_number() over(order by id) rn,
      id,
      name,
      age
    from member
   )
   where

    rn > (#_page# * #_pagesize#)
    and rn <= ( (#_page# + 1) * #_pagesize# )

   order by id

    ※本家SpringBatchのドキュメントでは、ROW_NUMを使用する方法がかかれていましたが

     上記のようにwindow関数を使用する方がきれいかなぁと個人的に思っています。



  補足:

   ページングは、順番が変わらないフィールドをorder byの対象にすべきだと思います。

   処理途中から再実行するときに、再実行までに誰かがデータを増やした場合、順番が変わってしまうと

   次に処理するデータが狂ってしまうからです。

   たいていの場合、一意のフィールドやシーケンス番号を入れているフィールドなどがあるかと思います。

   それらのフィールドを組み合わせれば順番が変わらないようなSQL文も頑張れば作れるでしょう。





さて、大まかですがDBの読み書きについてみてきましたが、分かりましたでしょうか?

いつも分かりやすいか?と自問しながら記事を書いているので、理解できましたらうれしいです!!




参照:

・トップ

・SpringBatch機能について

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

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

・CommandLineJobRunnerとは?

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

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

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

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



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

$
0
0





SpringBatchには、コミット数を設定できる機能があると今までの記事で書いてきました。

ここでは、その機能についてみてみましょう!


ついでに、

処理の途中でエラーが発生したときにrestartさせた動きも見てみましょう




ちなみに、

コミットはDBだけでなく、ファイルの書き込みにも関係していますので、

設定する数をうまく選んで処理スピードが速くなるようにしましょう!!



【サンプル】

サンプルは、前回の記事と同じものを使用します。


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


  上記の内容をすべて実行したら、以下の準備もお忘れなく!

    ・DBのmemberテーブルのデータをすべてクリアしておく

    ・起動の引数に、-nextをつけて実行する (つけないとAlreadyCompleted例外が発生すると思います)

【説明】


再掲:

<!-- ジョブの処理 -->
<job id="jobDb" xmlns="http://www.springframework.org/schema/batch "

  incrementer="jobParametersIncrementer">

   <step id="step1" parent="simpleStep" >
     <tasklet >
       <chunk reader="fileItemReader" writer="dbItemWriter" commit-interval="2" />
     </tasklet>
  </step>
</job>


  commit-interval="2"となっています。

 つまり、コミットタイミングが2個と設定されています。

 データ2個ごとにデータがコミットされます。


 サンプルでは、ファイルからの読み込みになっているので、2行を溜め込んで、2行をDBに書き込んで

 コミットすると言うことになります。

 処理のイメージは以下のとおりです。


  List items = new Arraylist();


  for(int i = 0; i < commitInterval; i++){
    items.add(itemReader.read());
  }


  itemWriter.write(items);


  なんとなく動きが理解できましたでしょうか?

  write()が内部で実際どのような処理になっているかは、使用するItemWriterによります。

  ちなみにFlatFileItemWriterでも、commitIntervalの数を上げると格段に処理が早くなります。

  しかし、数を上げすぎるとDBでもファイルでも、Out Of Memory例外が発生することもあります。

  もちろん、起動時にheapサイズなどを設定しておけば対応できますが、限界があるので

  最適値というものが存在します。

  何回か走行して最適値を見つけましょう!



  <ページングを使用したDBの読み込みと書き込みのコミットについて>

  ページングItemReaderの機能を使用すると、一気に指定のページサイズ分だけデータを読み込みます。

  そして、WriterがDBに値を書き込みます。

  一気に操作する数の設定項目が、「ページサイズ」と「コミット数(commitInterval)」2つあります。

  2つの設定が実際の動作にどのように関わるかを簡単に見てみます。

  実際に実験した内容です。

  

   <<設定値>>

   ページサイズ: 1000

   コミット数  :  100

  

   <<動作>>

   1.select文が実行され、1000レコードが読み込まれる。

   2.insert文をバッチ機能で100レコード一気に挿入する。

   3.2を10回繰り返し、1000レコード分が処理される。

   4.1に戻って繰り返し。select文でデータ取れなくなるまで実行される。


  なんとなく動作わかりましたでしょうか?

  ちなみに、何回SQL文が実行されるでしょうか?

  もし対象レコードが10000レコードあるとすると、

    select文 = 10000÷1000 = 10回

    insert文 = (10000÷1000)×(1000÷100) = 100回 

         (バッチ機能は1度SQL解釈してデータを一気に流し込みます)

  

  ついでに、ページ機能、commitInterval機能を使用しなかった場合も見ておきましょう。

   select文 = 1回

   insert文 = 10000回



  select文が少ない分、insert文の発行数が増えるので遅くなるのですね。





【restartの動作について】

では次に途中でエラーが発生した場合の動作を見てみましょう。


<準備>

前回の記事のサンプルでエラーを起こさせるには、以下のようにデータを書き替えます。


<データ>

1,太郎,18
2,次郎,29
3,さんま,53
abc,タモリ,54
5,鶴野,28



4行目が文字になっているので変換エラーが起きます。

しかも、コミット数は2にしているので、3行目のinsert処理をしてからエラーが起きます。

つまり、ちゃんとロールバックされていれば3行目の反映が無視され、2行目までが登録されているはずです



<実行結果>

Java Springの逆引きメモ


↓ログ

2010/08/21 11:39:50.312 [] INFO :: Job: [FlowJob: [name=jobDb]] launched with the following parameters: [{run.id=12}]
2010/08/21 11:39:50.937 [] INFO :: Executing step: [step1]
2010/08/21 11:39:51.156 [] ERROR :: Parsing error at line: 4 in resource=class path resource

     [sample/sample-db.txt], input=[abc,タモリ,54]
org.springframework.batch.item.file.FlatFileParseException: Parsing error at line: 4, input=[abc,タモリ,54]
at org.springframework.batch.item.file.mapping.DefaultLineMapper.mapLine(DefaultLineMapper.java:49)
at org.springframework.batch.item.file.FlatFileItemReader.doRead(FlatFileItemReader.java:188)
at org.springframework.batch.item.support.AbstractItemCountingItemStreamItemReader.read(AbstractItemCountingItemStreamItemReader.java:85)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:307)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:182)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:149)
(中略)
Caused by: java.lang.NumberFormatException: Unparseable number: abc
at org.springframework.batch.item.file.transform.DefaultFieldSet.parseNumber(DefaultFieldSet.java:688)
at org.springframework.batch.item.file.transform.DefaultFieldSet.readInt(DefaultFieldSet.java:290)
at sample.SimpleMapFieldSetMapper.read(SimpleMapFieldSetMapper.java:42)
at sample.SimpleMapFieldSetMapper.mapFieldSet(SimpleMapFieldSetMapper.java:34)
at sample.SimpleMapFieldSetMapper.mapFieldSet(SimpleMapFieldSetMapper.java:1)
at org.springframework.batch.item.file.mapping.DefaultLineMapper.mapLine(DefaultLineMapper.java:46)
... 43 more
2010/08/21 11:39:51.234 [] INFO :: Job: [FlowJob: [name=jobDb]] completed with the following parameters: [{run.id=12}] and the following status: [FAILED]



<さらにrestartする>

さて、予定通りエラーになりました。

ログにはちゃんとエラーになった行番号とデータの内容が出力されています!

DBのテーブルの中身を確認してみてください。2行目までしか登録されてないですよね!


では、restartしてみましょう。

まず、ファイルのデータを元に戻してください。(これ忘れずに!)

 

1,太郎,18
2,次郎,29
3,さんま,53
4,タモリ,54
5,鶴野,28



次にコマンドの実行するときに引数タブ内の記述で、-restartをつけて実行してください。


  classpath:/sample/runDb-context.xml jobDb -restart



<処理結果>

Java Springの逆引きメモ


↓ログ

2010/08/21 11:48:29.078 [] INFO :: Job: [FlowJob: [name=jobDb]] launched with the following parameters: [{run.id=12}]
2010/08/21 11:48:29.296 [] INFO :: Executing step: [step1]
2010/08/21 11:48:29.468 [] INFO :: Job: [FlowJob: [name=jobDb]]

    completed with the following parameters: [{run.id=12}] and the following status: [COMPLETED]



正常に終了しましたね。

DBのテーブルも見てみてください。

残りのデータが全部登録されました!



【まとめ】

SpringBatchを使用するだけで、手間はSQLのコマンドシェルみたいなものを作るのと同じくらいで

コミットタイミングの制御、およびrestartの機能まで実装できることを見ました。

かなり便利です。

少し覚えることはありますが、直感に近いのでそれほど惑わないと思います。


同様のSpringを使用したバッチ機能にTERAバッチというフレームワークがありますが

こちらはどうなんでしょうね。

気になります。



参照:

・トップ

・SpringBatch機能について

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

・CommandLineJobRunnerとは?

・Executionとは? (SpringBatch用語)

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

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

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

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

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

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



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

$
0
0

Springの掲示板を読んでいたら、面白い記事がありましたので触れてみたいと思います。

まさに、この記事のタイトルの内容です。



http://forum.springsource.org/showthread.php?t=48530


それほど難しくないので、まずはサンプルを見てみましょう!



※ここではItemWriterだけを記述します。

ItemReaderなどの設定は他の記事を参照してください。

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

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


※あと、ライブラリとしてmail.jarとactivation.jarが必要になるので、ダウンロードして配置してください

ちなみに、Java1.6以降ではactivation.jarは必要ないようです。




【メール送信ItemWriterクラス】package sample;import java.util.List;import org.apache.commons.logging.Log;import org.apache.commons.logging.LogFactory;import org.springframework.batch.item.ItemWriter;import org.springframework.batch.item.file.transform.LineAggregator;import org.springframework.mail.MailSender;import org.springframework.mail.SimpleMailMessage;public class MailItemWriter<T> implements ItemWriter<T> { private static final String DEFAULT_LINE_SEPARATOR = System.getProperty("line.separator"); protected static final Log log = LogFactory.getLog(MailItemWriter.class);  private int maxSendCount = 10; private int sendedCount = 0; private LineAggregator<T> lineAggregator; private String lineSeparator = DEFAULT_LINE_SEPARATOR; private MailSender mailSender; private String mailSubject; private String mailFrom; private String[] mailTo;    public int getMaxSendCount() {  return maxSendCount; } public void setMaxSendCount(int maxSendCount) {  this.maxSendCount = maxSendCount; } public LineAggregator<T> getLineAggregator() {  return lineAggregator; } public void setLineAggregator(LineAggregator<T> lineAggregator) {  this.lineAggregator = lineAggregator; } public String getLineSeparator() {  return lineSeparator; } public void setLineSeparator(String lineSeparator) {  this.lineSeparator = lineSeparator; } public MailSender getMailSender() {  return mailSender; } public void setMailSender(MailSender mailSender) {  this.mailSender = mailSender; }  public String getMailSubject() {  return mailSubject; } public void setMailSubject(String mailSubject) {  this.mailSubject = mailSubject; } public String getMailFrom() {  return mailFrom; } public void setMailFrom(String mailFrom) {  this.mailFrom = mailFrom; } public String[] getMailTo() {  return mailTo; } public void setMailTo(String[] mailTo) {  this.mailTo = mailTo; } @Override public void write(List<? extends T> items) throws Exception {  StringBuilder buf = new StringBuilder();  if(this.sendedCount > this.maxSendCount)    throw new IndexOutOfBoundsException("送信数がmaxSendCountを超えました");    try{   for (T item : items) {    buf.append(this.lineAggregator.aggregate(item) + this.lineSeparator);   }      //   sendMail(buf.toString());     }catch(Exception e){   log.error("item書き込み&送信中にエラーが発生しました。", e);   throw e;  }   }  protected void sendMail(String text) throws Exception {  SimpleMailMessage msg = new SimpleMailMessage();  msg.setSubject(this.mailSubject + "[" + this.sendedCount + "]");  msg.setFrom(this.mailFrom);  msg.setTo(this.mailTo);  msg.setText(text);    try{   this.mailSender.send(msg);   ++this.sendedCount;     }catch(Exception e){   log.error("エラー発生時メールの送信に失敗", e);   throw e;  } }}【Springの設定ファイル一部抜粋】 <!-- メール送信テスト --> <bean id="mailItemWriter" class="sample.MailItemWriter">  <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$03d,%3$d" />   </bean>  </property>  <property name="mailSender" ref="mailSender" />  <property name="mailFrom" value="test@test.co.jp" />  <property name="mailTo" value="xxx@mail.co.jp" />  <property name="mailSubject" value="mailCsv" /> </bean><!-- メールセンダー --> <bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">  <property name="defaultEncoding" value="ISO-2022-JP" />  <property name="host" value="smtp.XXX.ne.jp" />  <property name="username" value="Xxxxx" /> </bean>




【説明】


メール送信のための機構もSpringは用意しています。

それがMailSenderです。

設定ファイルにそれを書くだけでOKです。


実際のデータ作成とメール送信する実装はItemWriterにあります。



再掲:ItemWriter


public void write(List<? extends T> items) throws Exception {
 StringBuilder buf = new StringBuilder();
 if(this.sendedCount > this.maxSendCount)
   throw new IndexOutOfBoundsException("送信数がmaxSendCountを超えました");

 try{
   for (T item : items) {
     buf.append(this.lineAggregator.aggregate(item) + this.lineSeparator);
   }

   //
   sendMail(buf.toString());

 }catch(Exception e){
   log.error("item書き込み&送信中にエラーが発生しました。", e);
   throw e;
 }
}

データの作成は、FlatFileItemWriterクラスで使用していたlineAggregatorを流用しています。

これでFormatter変換も、区切り文字も自在にできます。

しかも、何も自作しなくていいですね!にひひ


そして、作成したデータをmailSenderに送っているだけです。



ちなみに、メールを何回も送ると負荷がかかって危ないので、最大送信数を儲けています。


何かあっさりできましたね(笑)

Springがいろいろ用意してくれているのと、DIのおかげですね。



【拡張など】

もっと複数のデータ処理結果を送りたい場合、もしくは、ヘッダー・フッターなどを付けたい場合。

それほど難しくないです。

まずヘッダー・フッターについてはFlatFileItemWriterの真似をしましょう。

FlatFileItemWriterではheaderCallbackプロパティを持っていますよね。これをつけて、内部で処理の最初に

呼び出してあげればいいですよね。


また、複数のデータ処理結果を送りたい場合は、それぞれの処理結果をファイルに保存しましょう。

ItemReaderの中には複数のファイルをまとめて扱うクラスがあります。

MultiResourceItemReaderです。

readerにこのクラスを指定すれば、複数になってしまった処理結果ファイルをひとまとめにできます。

あとはwriterに上のメールItemWriterを指定すればよいでしょう。


結構、再利用でき、便利ですね!



【補足】

念のため補足しておきます。

上記のサンプルは簡単にするため、restartできるようには考えていません

なぜならJobが終了したときにWriterクラス内部で持っているsendedCount も消えてしまうからです。

restartしたときにメールのタイトルが「mailCsv[0]」から始まってしまいます。

Javaが終了するので内部のインスタンスが消えてしまうので当たり前ですよね。

(このサンプルでは問題はメールタイトルだけですが、restartできるようにする方法は知っておきましょう)

しかし、値をを保持し、restartできるようにすることは簡単です。

StepExectutionの中に持っているExecutionContextに値を保存し、そこから値を取得してメールのタイトルに使用すればOKです。

もっと詳しいことは他の記事で書いてみようと思います。

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



参照:

・トップ

・SpringBatch機能について

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

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

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

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

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

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

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

・既に用意されている機能(クラス)を探すには?

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

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

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





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

$
0
0

Spring BatchをWEBから起動したい場合があるかもしれません。


または、サーバにBatchをおいておいて、他のサーバからキックだけしたい場合があるかもしれません。


こんなときは「非同期」で起動したくなると思います。


この記事ではSpringBatchを非同期で起動する方法を見てみます。

実はとっても簡単です。



【サンプル(SpringBatch設定ファイル一部抜粋)】


<bean id="jobLauncher"
class="org.springframework.batch.core.launch.support.SimpleJobLauncher">
<property name="jobRepository" ref="jobRepository" />
<property name="taskExecutor">
<bean class="org.springframework.core.task.SimpleAsyncTaskExecutor" />
</property>
</bean>



【起動例(プログラム)】


JobLauncher jobLauncher = (JobLauncher )applicationContext.getBean("jobLauncher");

Job job = (Job)applicationContext.getBean("job");

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



【説明】

jobLauncherのtaskExecutorプロパティにSimpleAsyncTaskExecutorを設定すれば非同期になります。

簡単ですよね。


起動も簡単です。

jobLauncherのrunメソッドを呼ぶだけです!


その他の起動方法として、以下の記事でプログラムから起動する方法も記述しています。
  ・CommandLineJobRunnerとは?



【注意点】

コマンドラインから実行する場合、実行が終了すればJavaが落ち、StepやJobのインスタンスも解放されます。

でもWEBでバッチ実行する場合、StepやJobのインスタンスは当然解放されません。

そうすると、Stepの内部でファイルを扱えばそのインスタンスが残っている可能性もあります。

その状態でJobが再度起動され、同じStepが実行されればファイルの読み込み・書き込みがおかしくなるかも知れません。

そうするとStep毎にItemReaderやItemWriterなどのインスタンスが生成される方が良くなります。


このとき生きてくるのがSpringBatch用の新scopeである「step」です。

生成の問題の回避のため、ItemReaderやItemWriterなどのbeanのscope属性にstepを使用してみてください!


他の記事でちょくちょく使ってますのでサンプルを見てもらえればと思います。



【おまけ】

Spring Batchをサーバーにおいておいて、他のサーバからキックするという使い方もできます。

Spring RemoteでRMIなどでSpringBatchのJobを公開しておいて、他のサーバからそれをキックするという

やり方です。(Jobを起動する仲介者クラスさえ作ればうまく行きます)

そうするとバッチ処理を一箇所におけるので1つのやり方だと思います。


ただ、RMIなどは新しいバージョンをリリースしたい場合に停止しないといけないとかあるかもしれません。

RMIの機能を調べていないので分かりませんが、もしそうなら、WEBからバッチをキックしている場合、

WEBにも影響が行くことになり、リリースが大変になるかもしれません。

でも、何か他の方法があって、いいやり方があるかもしれません。


いろいろ発展性はありそうで、面白そうですよね!




参照:

・トップ

・SpringBatch機能について

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

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

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

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

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

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

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

・既に用意されている機能(クラス)を探すには?

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

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





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

$
0
0

SpringBatchで用意しているItemReader、ItemWriterについては、既にrestartできるように設計されています。

自作で作った場合は自分で設計しなければなりません。

restartできるように設計するときに、どのように考えればよいか?

また、実際にどのようにプログラムすればよいかを簡単に見ていきます。


 ※注意: Springが用意しているクラスの中にはテストや検証を簡単にするものがあり、

  これらはrestart設計されてません。


【restart可能にするための基本的考え方】

restartするということは、前回どこか途中で終了しています。

基本的にはどこまで読み込んだか?どこまで書き込んだか?を保存しておく必要があります。

そして、Stepを開始したときに保存した値を読み込んで、途中で終わっていればそこまでの処理を実行する必要があります。


例えば、前回ItemWriterが5回目で失敗した場合、ItemReaderで4回目までデータを読み飛ばしてStep処理を始めれば、後はJobに任せれば5個目のデータを読み込んでItemProcessor、ItemWriterにデータを渡すのでOKということにりますね!


そうすると知っておくべきポイントが以下のようになるかと思います。

 ・最後のデータの位置を保存する方法

 ・Stepが開始したときにデータを読み飛ばす方法


では上記をこれから見ていくことにしましょう!



【最後のデータの位置を保存する方法】

まず、ExcecutionContextについて知っておきましょう!

簡単に言うと、データを保存しておくMapです。

StepExecutionとJobExecutionそれぞれに別々のExcecutionContextが保持されています。

Stepについては、Stepごとに別々のインスタンスで保持されています。

詳しい説明は、・Executionとは? (SpringBatch用語) を参照ください。


まあ、ほぼこれが答えです(笑)


 例: executionContext.putInt("sendMailCnt", this.sendedCount);


こんな風にすれば値が保持されます。

そして、設定した値がDBに保存されることになります。


保存についてはもうひとつ重要なポイントがあります。

ItemWriterがコミットしたときに保存しなければなりません。

なぜならもし、ItemReaderがデータを読み込んだときに保存すれば、その後ロールバックしたら数がずれちゃいますよね?

例えば、コミット間隔が5個で、10個目のデータまで読み込み、ItemWriterで8個目のデータ書き込みでロールバックして終了した場合。

コミットされているのは5個目までなので、restart時は6個目からItemReaderが読み込み始めないといけません。

もし10個目のデータを読み込んだ段階で10という値を保存してしまえば11個目から読み込み始めてしまいますよね。


そこで、コミットがあったときに読み込んだ個数を保存するようにします


方法: 

まず、ItemReaderにItemStreamをimplementsします。 

ItemStreamはStepをopen/close/commitするときに呼び出されるメソッドを用意しています。

implementsしたItemReader、ItemWriterなどは自動でそれらのメソッドが呼ばれます。


例: commitしたときにread数を保存する


public void update(ExecutionContext executionContext)
throws ItemStreamException

{

  executionContext.putInt("sendMailCnt", this.sendedCount);

}


上記のような感じになります。




【Stepが開始したときにデータを読み飛ばす方法】

さてどこまで読み込んだかを保存できたところで次に、restartしたときにデータを読み飛ばす方法を見てみます。

実はこの答えの半分は、ItemStreamにあります。

ItemStreamのopenメソッドに読み飛ばしの処理を実装すればOKです。

例えば以下のような具合です。


例:

public void open(ExecutionContext executionContext) throws ItemStreamException

{

  int itemCount = executionContext.getInt("sendMailCnt", 0);

  this.sendedCount = itemCount;

  for (int i = 0; i < itemCount; ++i)

   readData(); //データ1個を読むだけのメソッドを作っておく

}




これでなんとなくやり方わかりましたでしょうか?

open/updateはSpringBatchの方で自動で呼んでくれるので、呼び出すための処理は記述する必要はありませんビックリマーク

ItemWriterにもそのときそのときの状態を持たなければrestartできない場合は、ItemReaderと同様にItemStreamをimplementsして同様のことをしてください。


それ程難しくないですよね。


せっかくなので自作のクラスについてもrestart可能にしましょう!


もしそれがかなわない場合は、設定ファイルのjobタグの属性にrestartable="false" を追加しておきましょう!





参照:

・トップ
・SpringBatch機能について

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

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

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

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

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

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

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

・既に用意されている機能(クラス)を探すには?

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

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

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

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

・CommandLineJobRunnerとは?

・Executionとは? (SpringBatch用語)


ItemReaderで読み込んだデータを妥当性チェックするには? (valang方式)

$
0
0

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

ここでは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 validatorStrutsと同じように、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の外部記事

MessageSourceを使いやすくするには?

$
0
0

SpringではMessageSourceも用意されており、設定ファイルに設定すれば使用できます。

しかし、そのままでは少し使いにくいので使いやすくする方法を見てみましょう!



【Springの設定ファイルのサンプル(applicationContext.xml)】


<bean id="messageSource"
class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename" value="messages"/>
</bean>


<bean id="sample" class="sample.Sample">
<property name="messageSource" ref="messageSource"/>
</bean>



【Sampleクラス(sample.Sample)】


public class Sample{



public void setMessageSource(MessageSource messages) {
this.messages = new MessageSourceAccessor(messages);
}


public String obtainMsg(){

return this.messages.getMessage("sample.msg", "メッセージのデフォルトです");
}


}



【メッセージファイル(messages_jp.properties)】


sample.msg=サンプルメッセージ




【説明】


特に説明の必要は無いかもしれません。


this.messages = new MessageSourceAccessor(messages);

単純にMessageSourceAccessorでラップしているだけです。

この便利さは、もとのMessageSourceの使い方を見ないと分からないかと思います。


 <MessageSourceの使い方>

 getMessage(sample.msg", null, "メッセージのデフォルトです", Locale.getDefault());


 <MessageSourceAccessorの使い方(再掲)>

 getMessage("sample.msg", "メッセージのデフォルトです");


いくつか引数を入力しなくて良くなっていますよね!

ほんの少しの違いですが、何回も書くと意外と疲れます。

このクラスもうまく使ってみてくださいニコニコ




【おまけ】

SpringにはVMを再起動しなくてもリロードできるメッセージリソースもあります。

 ReloadableResourceBundleMessageSource


また、テスト用にプログラム上で書き換えられるメッセージリソースもあります。

 StaticMessageSource


わざわざ自作するようなことはやめて、まずはこれらが使えないか検討してみましょう!




参考:

・トップ

・DIの設定ファイルで外部ファイル(プロパティファイル)を参照するには?

・DIの設定ファイル内から外部DIファイルを参照するには?

・プロパティファイルをPropertiesクラスのbeanにするには?




DIの機能について

$
0
0

Springは世界中で使用されているフレームワークで、GoogleAppEngineや、VMForceなどでも使用可能になっていて、使い方が分からないと乗り遅れている感があります。

しかし、「じゃあ、Springって何!?」って調べ始めると、DI(依存性の注入)とか、IoC(制御の反転)とか出てきて、「?」が広がっていきます


ここでは、難しい説明は他の書籍やホームページに任せて、もっと簡単な説明をしてみたいと思います

説明は自分の解釈で書きますのでそこは注意をお願いしたいところですが、

導入の解釈としてはこれで十分だと思っていますグッド!(おそらく・・・)

使っていくうちになんとなくDIとか分かっていくものじゃないでしょうか。(たぶん)



【SpringのDIって何?】


ひと言で言うなら、


 「設定情報を外部定義化できる機能」


です。

外部定義というと、Windowsでいうとiniファイル、Javaなら.propertiesファイルなどを思い浮かべますが、

それらは固定値をコンパイルなしに変更できる機能です。

Springがすごいところは、


 「使用するクラスも外部定義化でき、変更できる」


ところです。

どういうことかは、この後、みていきます。




【Springの外部定義化】

通常、外部定義をする場合、次のような実装をする必要があります。

 ①外部定義ファイルから値を取り出す機能の実装

 ②取り出した値を、使用するクラスもしくはメソッドに渡す機能の実装


この程度ならたいしたこと無いでしょ!って思うかもしれませんが、設定が増えてくると

これだけでもかなりのコード量になり、おそろしく大変になります


Springでは上記のコードを実装する必要がなくなります。

その具体的な例を見ていきましょう!


まず、Springでは外部定義にXMLを使用します。

以下のような感じです。

例では、データをファイル出力することを考えます。



 <設定XMLの記述例>

 <bean id="fileWriter" class="com.file.MyFileWriter">
   <property name="extractor" ref="csvExtractor"/>
 </bean>


 <bean id="csvExtractor" class="com.file.MyCsvExtrator">

   <property name="delimiter" value=","/>
 </bean>



 beanは1つのクラスを表し、そのクラスをnewした実体を内部で持っています。

 id属性は、その実体に名前をつけたものと思ってもらえば大丈夫です。

 上記の場合、2つのクラスが登場していて、以下のようにしたのと同じことになります。


 MyFileWriter fileWriter = MyFileWriter();

 MyCsvExtrator csvExtractor = new MyCsvExtrator();


 //propertyタグでの設定は、setterを表す(文字列を設定)

 csvExtractor.setDelimiter(",");

 

 //propertyタグでの設定、setterを表す(クラス自体も設定できる)

 fileWriter.setExtractor(csvExtractor);


  ※CsvExtractorは、与えられた1行分のデータをカンマ区切り文字列に変換するクラスとしています。


 なんとなく分かるでしょうか?

 newして、setterで値を設定しているだけです。

 propertyタグはsetterを表していて、値を設定する場合はvalue属性を使用し、クラス(bean)を設定する場合は

 ref属性を使用します。


 これで、最初の課題であった①②の、値をファイルから取得すること、値をクラスに設定すること、の

 2つを実現できているのが分かるでしょうか?

 区切り文字も外部定義化できています。

  

  再掲:

  <bean id="csvExtractor" class="com.file.MyCsvExtrator">

    <property name="delimiter" value=","/> ←ここを" "に変えれば空白区切りになる!!
  </bean>


 XMLファイルを変更すれば、コンパイルなしに任意の区切り文字にできます!

 もちろん、MyFileWriter、CsvExtractorの実装は必要ですが、それはSpringを使用しなくても同じです。

 



【動作の変更】
お話は、まだまだ続きます!


上記の例では、値の設定だけでなく、クラスも設定しています。


 再掲: fileWriter.setExtractor(csvExtractor);

これを利用すると、動作の変更もできます。


 

 <設定XMLの記述例>

 <bean id="fileWriter" class="com.file.MyFileWriter">
   <property name="extractor" ref="fixedExtractor"/>
 </bean>


 <bean id="csvExtractor" class="com.file.MyCsvExtrator">

   <property name="delimiter" value=","/>
 </bean>


 <!-- 追記 -->

 <bean id="fixedExtractor" class="com.file.MyFixedExtrator">

 </bean>


 ※MyFixedExtratorは、与えられた1行分のデータを固定長で文字列に変換するクラスとしています


 
fixedExtractorを追記しました。

そして、fileWriterも書き変えています。


 再掲:

 <bean id="fileWriter" class="com.file.MyFileWriter">
   <property name="extractor" ref="fixedExtractor"/> ←設定するクラスを変更しました
 </bean>


 これだけで、カンマ区切りファイル出力から、固定長によるファイル出力に変わりました


 通常の設定ファイルでも動作を変えることはできますが、相当コードを記述しなければなりません。

 しかしSpringでは設定ファイルのためのコード記述は必要なく、ref属性を変更するだけです!


 簡単ですよね!


この例でなんとなく分かったかと思いますが、Springを使うと、

 

   「必要な機能の実装のみに専念できる!」


ということになります。

今まで煩わされた外部定義化の実装に工数をかける必要がなくなります!




【設定したクラス(bean)を実際に使用する】

ちょっとあとまわしになりましたが、上記で設定した内容をプログラム上で使用する方法に軽く触れます。


 <使用方法例>

 //XMLファイルのロード(他にもロードするクラスはあります。状況に合わせて使用するクラスを決めます)

 ApplicationContext ac = new FileSystemXmlApplicationContext("c:/app/context.xml");

 

 //クラスの取得

 MyFileWriter fileWriter = (MyFileWriter)ac.getBean("fileWriter");

 

 //ファイル出力

 fileWriter.output("c:/app/out.txt");


 

簡単ですよね!

ファイルをロードして、getBean()で指定のbeanを取得するだけです!


さて、さきほどの内容とあわせると、とってもいいことがあります。

それがここでのポイントですビックリマーク


 さきほど、MyFixedExtratorを使用するように変更しました。

 この影響はあるでしょうか?

 上記のプログラムをみると、関係ありそうなところは、ac.getBean("fileWriter");だけです。

 しかも、bean名「fileWriter」は変更しないので影響が一切ありません!!


 

これがポイントです。

Springを適切に使用すれば、拡張も変更も既存のコードを変更することなく容易にできるようになります。

また、変更が容易なため、テスト用のモックに切り替えることも簡単になり、テストの効率が上がります!


Spring導入部の説明は以上です。

ここでの説明は話を簡単にするため、省略している便利な機能がいくつもあります。

あとはどんどん使って実践あるのみです!




【補足】

Springの設定ファイルは、jarファイルの中に含めることがほとんどです。

その場合、jarの中のファイルを変更することは大変です。

でも大丈夫!

当然、Springはそのような場合でも対応できます。

以下を参照してみてください!

 

 

 ・DIの設定ファイルで外部ファイル(プロパティファイル)を参照するには?

 ・DIの設定ファイル内から外部DIファイルを参照するには?




参考:

・トップ

・DIの設定ファイルを書くには?

・DIの設定ファイルで外部ファイル(プロパティファイル)を参照するには?

・DIの設定ファイル内から外部DIファイルを参照するには?

・プロパティファイルをPropertiesクラスのbeanにするには?

・MessageSourceを使いやすくするには?

・SpringJDBCの機能について

・DIxAOPの機能について

Strutsと連携するには?(理論編)

Spring IDEを使ってみよう

Spring Framework について








Viewing all 21 articles
Browse latest View live