Spring Batch

MyBatis-Spring 1.1.0 以降では、 Spring Batch を構築するための Bean として MyBatisPagingItemReaderMyBatisBatchItemWriter が用意されています。 これらの Bean 及びこのドキュメントは、いずれも Spring Batch の一部として提供されていた iBATIS 2.x 用のものを移植したものです。

NOTE ここで扱うのは Spring Batch を使ったバッチ処理で、MyBatis の SqlSession を利用したバッチ処理ではありません。

MyBatisPagingItemReader

この Bean は、MyBatis を利用してデータベースからページ単位でレコードを読み出す ItemReader です。

結果を取得する際は setQueryId プロパティで指定したクエリが実行されます。 1ページあたりの件数は setPageSize プロパティで指定することができます。 read() メソッドが呼び出されると、必要に応じて追加のページを取得するクエリが実行されます。 実行されるクエリでは、Reader によって提供されるページング処理を行う際に必要となるパラメーターを使って期待される結果を返す SQL 文を記述することになります(実際の SQL 文は方言依存です)。 提供されるパラメーターは次の通りです。

  • _page: 取得対象のページ番号(最初のページは0)
  • _pagesize: 1ページあたりの件数
  • _skiprows: _page_pagesize の積

これらのパラメーターは、例えば次のように SELECT 文中で指定することができます。

<select id="getEmployee" resultMap="employeeBatchResult">
  SELECT id, name, job FROM employees ORDER BY id ASC LIMIT #{_skiprows}, #{_pagesize}
</select>

設定例:

<bean id="reader" class="org.mybatis.spring.batch.MyBatisPagingItemReader">
  <property name="sqlSessionFactory" ref="sqlSessionFactory" />
  <property name="queryId" value="getEmployee" />
</bean>

さらに複雑な例:

<bean id="dateBasedCriteriaReader"
    class="org.mybatis.spring.batch.MyBatisPagingItemReader"
    p:sqlSessionFactory-ref="batchReadingSessionFactory"
    p:parameterValues-ref="datesParameters"
    p:queryId="com.my.name.space.batch.ExampleMapper.queryUserInteractionsOnSpecificTimeSlot"
    p:pageSize="${200}"
    scope="step"/>

<util:map id="datesParameters" key-type="or.joda.time.DateTime" scope="step">
  <entry key="yesterday" 
    value="#{jobExecutionContext['EXTRACTION_START_DATE']}"/>
  <entry key="today" 
    value="#{jobExecutionContext['TODAY_DATE']}"/>
  <entry key="first_day_of_the_month" 
    value="#{jobExecutionContext['FIRST_DAY_OF_THE_MONTH_DATE']}"/>
  <entry key="first_day_of_the_previous_month" 
    value="#{jobExecutionContext['FIRST_DAY_OF_THE_PREVIOUS_MONTH_DATE']}"/>
</util:map>

The previous example makes use of a few different things:

  • sqlSessionFactory: あなた自身の sessionFactory を reader に指定することができます。複数のデータベースから読み取る場合は有用かも知れません。
  • queryId: レコード取得時に実行されるクエリの ID を指定します。異なるネームスペースに属するクエリを指定する場合はネームスペースの指定を忘れないようにしてください。
  • parameterValues: クエリ実行時に使用する追加のパラメーターを Map 形式で渡すことができます。上の例では Spring がjobExecutionContext から SpEL 式を使って取得した値をもとに構築した Map を指定しています。 MyBatis の Mapper ファイルでは Map のキーをパラメーター名として指定します(例: yesterday を指定する場合は #{yesterday,jdbcType=TIMESTAMP} のように指定します)。 jobExecutionContext と Spring EL 式を利用するため、Map および reader はどちらも step スコープ内で構築されているという点に注意してください。 また、MyBatis の Type Handler が正しく設定されていれば、この例のように JodaTime のようなカスタムのインスタンスを引数として渡すこともできます。
  • pageSize: バッチ処理のチャンクサイズを指定します。

MyBatisBatchItemWriter

SqlSessionTemplate のバッチ機能を使って渡された一連のアイテムを処理する ItemWriter です。 SqlSessionFactoryExecutorType.BATCH を使って設定する必要があります。

write() の呼び出し時に実行するステートメントの ID を指定しておく必要があります。 write() はトランザクション内で呼び出されることを前提としています。

設定例:

<bean id="writer" class="org.mybatis.spring.batch.MyBatisBatchItemWriter">
  <property name="sqlSessionFactory" ref="sqlSessionFactory" />
  <property name="statementId" value="updateEmployee" />
</bean>

Composite Writer を使って複数のテーブルに書き込む(注意事項あり)

このテクニックを使うには MyBatis 3.2 以降が必要です。それ以前のバージョンには 問題 があるため、Writer が期待通りに動作しません。

まず、Interaction と1対1の関係にある InteractionMetadata と、これらとは独立した VisitorInteraction および CustomerInteraction を保持する Item クラスを用意します。

public class InteractionRecordToWriteInMultipleTables {
  private final VisitorInteraction visitorInteraction;
  private final CustomerInteraction customerInteraction;
  private final Interaction interaction;
  // ...
}

public class Interaction {
  private final InteractionMetadata interactionMetadata;
}

CompositeItemWriter の設定では、それぞれのオブジェクトの writer を順番に呼び出すように設定します。 この例では Interaction をアップデートするためのキーを取得するため、InteractionMetadata を先に書き込む必要があります。

<bean id="interactionsItemWriter" class="org.springframework.batch.item.support.CompositeItemWriter">
  <property name="delegates">
    <list>
      <ref bean="visitorInteractionsWriter"/>
      <ref bean="customerInteractionsWriter"/>

      <!-- Order is important -->
      <ref bean="interactionMetadataWriter"/>
      <ref bean="interactionWriter"/>
    </list>
  </property>
</bean>

それぞれの writer を必要に応じて設定します。例えば InteractionInteractionMetadata の設定は次のようになります。

<bean id="interactionMetadataWriter"
  class="org.mybatis.spring.batch.MyBatisBatchItemWriter"
  p:sqlSessionTemplate-ref="batchSessionTemplate"
  p:statementId="com.my.name.space.batch.InteractionRecordToWriteInMultipleTablesMapper.insertInteractionMetadata"/>
<bean id="interactionWriter"
  class="org.mybatis.spring.batch.MyBatisBatchItemWriter"
  p:sqlSessionTemplate-ref="batchSessionTemplate"
  p:statementId="com.my.name.space.batch.InteractionRecordToWriteInMultipleTablesMapper.insertInteraction"/>

reader の場合と同様、 statementId はネームスペースを含むステートメント ID です。

Mapper ファイルにステートメントを定義します。

<insert id="insertInteractionMetadata"
  parameterType="com.my.batch.interactions.item.InteractionRecordToWriteInMultipleTables"
  useGeneratedKeys="true"
  keyProperty="interaction.interactionMetadata.id"
  keyColumn="id">
  <!-- the insert statement using #{interaction.interactionMetadata.property,jdbcType=...} -->
</insert>
<insert id="insertInteraction"
  parameterType="com.my.batch.interactions.item.InteractionRecordToWriteInMultipleTables"
  useGeneratedKeys="true"
  keyProperty="interaction.id"
  keyColumn="id">
  <!--
   the insert statement using #{interaction.property,jdbcType=...} for regular properties
   and #{interaction.interactionMetadata.property,jdbcType=...} for the InteractionMetadata property
  -->
</insert>

はじめに insertInteractionMetadata が呼ばれ、その際に取得した主キーを使って親となる InteractioninsertInteraction を使って書き込むことができます。

JDBC ドライバによって動作が異なるので注意が必要です。例えば MySQL の JDBC ドライバは作成された全ての行の ID を返しますが、H2 バージョン 1.3.168 ではバッチモードでも最後に作成された行の ID のみが返されます。