<?xml version="1.0" encoding="UTF-8"?>
<!--

    Copyright ${license.git.copyrightYears} the original author or authors.

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.

-->
<document xmlns="http://maven.apache.org/XDOC/2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/XDOC/2.0 http://maven.apache.org/xsd/xdoc-2.0.xsd">

  <properties>
    <title>MyBatis-Spring | Spring Batch</title>
    <author email="eduardo.macarron@gmail.com">Eduardo Macarron</author>
    <author email="brice.dutheil@gmail.com">Brice Dutheil</author>
    <author email="harawata@gmail.com">Iwao AVE!</author>
  </properties>

  <body>
    <section name="Spring Batch">
      <p>
        MyBatis-Spring 1.1.0 以降では、 Spring Batch を構築するための Bean として <code>MyBatisPagingItemReader</code> 、 <code>MyBatisCursorItemReader</code> 、 <code>MyBatisBatchItemWriter</code> が用意されています。
        また、2.0.0 以降では、Java Configuration をサポートするための Builder クラスとして <code>MyBatisPagingItemReaderBuilder</code> 、
        <code>MyBatisCursorItemReaderBuilder</code> 、 <code>MyBatisBatchItemWriterBuilder</code> が用意されています。
      </p>
      
      <p>
        <span class="label important">NOTE</span> ここで扱うのは <a href="http://static.springsource.org/spring-batch/">Spring Batch</a> を使ったバッチ処理で、MyBatis の <a href="sqlsession.html">SqlSession</a> を利用したバッチ処理ではありません。
      </p>
      
      <subsection name="MyBatisPagingItemReader">
        <p>
          この Bean は、MyBatis を利用してデータベースからページ単位でレコードを読み出す <code>ItemReader</code> です。
        </p>
        
        <p>
          結果を取得する際は <code>setQueryId</code> プロパティで指定したクエリが実行されます。
          1ページあたりの件数は <code>setPageSize</code> プロパティで指定することができます。
          <code>read()</code> メソッドが呼び出されると、必要に応じて追加のページを取得するクエリが実行されます。
          実行されるクエリでは、Reader によって提供されるページング処理を行う際に必要となるパラメーターを使って期待される結果を返す SQL 文を記述することになります（実際の SQL 文は方言依存です）。
          提供されるパラメーターは次の通りです。
        </p>

         <ul>
         <li><code>_page</code>: 取得対象のページ番号（最初のページは０）</li>
         <li><code>_pagesize</code>: １ページあたりの件数</li>
         <li><code>_skiprows</code>: <code>_page</code> と <code>_pagesize</code> の積</li>
         </ul>

        <p>これらのパラメーターは、例えば次のように SELECT 文中で指定することができます。</p>
        <source><![CDATA[<select id="getEmployee" resultMap="employeeBatchResult">
  SELECT id, name, job FROM employees ORDER BY id ASC LIMIT #{_skiprows}, #{_pagesize}
</select>]]></source>

        <p>設定例：</p>
        
        <source><![CDATA[<bean id="reader" class="org.mybatis.spring.batch.MyBatisPagingItemReader">
  <property name="sqlSessionFactory" ref="sqlSessionFactory" />
  <property name="queryId" value="com.my.name.space.batch.EmployeeMapper.getEmployee" />
</bean>]]></source>

        <source><![CDATA[
@Bean
public MyBatisPagingItemReader<Employee> reader() {
  return new MyBatisPagingItemReaderBuilder<Employee>()
      .sqlSessionFactory(sqlSessionFactory())
      .queryId("com.my.name.space.batch.EmployeeMapper.getEmployee")
      .build();
}]]></source>

        <p><strong>さらに複雑な例：</strong></p>

        <source><![CDATA[
<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" 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>]]></source>

        <source><![CDATA[
@StepScope
@Bean
public MyBatisPagingItemReader<User> dateBasedCriteriaReader(
    @Value("#{@datesParameters}") Map<String, Object> datesParameters) throws Exception {
  return new MyBatisPagingItemReaderBuilder<User>()
      .sqlSessionFactory(batchReadingSessionFactory())
      .queryId("com.my.name.space.batch.ExampleMapper.queryUserInteractionsOnSpecificTimeSlot")
      .parameterValues(datesParameters)
      .pageSize(200)
      .build();
}

@StepScope
@Bean
public Map<String, Object> datesParameters(
    @Value("#{jobExecutionContext['EXTRACTION_START_DATE']}") LocalDate yesterday,
    @Value("#{jobExecutionContext['TODAY_DATE']}") LocalDate today,
    @Value("#{jobExecutionContext['FIRST_DAY_OF_THE_MONTH_DATE']}") LocalDate firstDayOfTheMonth,
    @Value("#{jobExecutionContext['FIRST_DAY_OF_THE_PREVIOUS_MONTH_DATE']}") LocalDate firstDayOfThePreviousMonth) {
  Map<String, Object> map = new HashMap<>();
  map.put("yesterday", yesterday);
  map.put("today", today);
  map.put("first_day_of_the_month", firstDayOfTheMonth);
  map.put("first_day_of_the_previous_month", firstDayOfThePreviousMonth);
  return map;
}]]></source>

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

      </subsection>

      <subsection name="MyBatisCursorItemReader">
        <p>
          This bean is an <code>ItemReader</code> that reads records from a database using a cursor.
        </p>

        <p>
          <span class="label important">NOTE</span> To use this bean you need at least MyBatis 3.4.0 or a newer version.
        </p>
        
        <p>
          It executes the query specified as the <code>setQueryId</code> property to retrieve requested data
          by using the method <code>selectCursor()</code>.
          Each time a <code>read()</code> method is called it will return the next element of the cursor until no more
          elements are left.
        </p>
        
        <p>
          The reader will use a separate connection so the select statement does no participate in any transactions created 
          as part of the step processing.
        </p>

        <p>When using the cursor you can just execute a regular query:</p>
        <source><![CDATA[<select id="getEmployee" resultMap="employeeBatchResult">
  SELECT id, name, job FROM employees ORDER BY id ASC
</select>]]></source>

        <p>Follows below a sample configuration snippet:</p>
        
        <source><![CDATA[<bean id="reader" class="org.mybatis.spring.batch.MyBatisCursorItemReader">
  <property name="sqlSessionFactory" ref="sqlSessionFactory" />
  <property name="queryId" value="com.my.name.space.batch.EmployeeMapper.getEmployee" />
</bean>]]></source>

        <source><![CDATA[
@Bean
public MyBatisCursorItemReader<Employee> reader() {
  return new MyBatisCursorItemReaderBuilder<Employee>()
      .sqlSessionFactory(sqlSessionFactory())
      .queryId("com.my.name.space.batch.EmployeeMapper.getEmployee")
      .build();
}]]></source>

      </subsection>

      <subsection name="MyBatisBatchItemWriter">
      
      <p>
        <code>SqlSessionTemplate</code> のバッチ機能を使って渡された一連のアイテムを処理する <code>ItemWriter</code> です。
        <code>SqlSessionFactory</code> は <code>ExecutorType.BATCH</code> を使って設定する必要があります。
      </p>
 
      <p>
        <code>write()</code> の呼び出し時に実行するステートメントの ID を指定しておく必要があります。
        <code>write()</code> はトランザクション内で呼び出されることを前提としています。
      </p>
 
        <p>設定例：</p>
        
        <source><![CDATA[<bean id="writer" class="org.mybatis.spring.batch.MyBatisBatchItemWriter">
  <property name="sqlSessionFactory" ref="sqlSessionFactory" />
  <property name="statementId" value="com.my.name.space.batch.EmployeeMapper.updateEmployee" />
</bean>]]></source>

        <source><![CDATA[
@Bean
public MyBatisBatchItemWriter<User> writer() {
  return new MyBatisBatchItemWriterBuilder<User>()
      .sqlSessionFactory(sqlSessionFactory())
      .statementId("com.my.name.space.batch.EmployeeMapper.updateEmployee")
      .build();
}]]></source>


        <p><strong>ItemReaderを使用して読み込んだアイテムを任意のパラメータオブジェクトへ変換する</strong></p>

        <p>
          デフォルトの動作では、<code>MyBatisBatchItemWriter</code> は <code>ItemReader</code> を使用して読みこんだアイテム
          (または <code>ItemProcessor</code> によって変換したアイテム)を、
          そのままMyBatis(<code>SqlSession</code> の <code>update</code> メソッド)のパラメーターオブジェクトとして渡します。
          もしMyBatisへ渡すパラメーターオブジェクトをカスタマイズしたい場合は、
          <code>itemToParameterConverter</code> オプションを利用することで実現するすることができます。
          たとえば、<code>itemToParameterConverter</code> オプションを使用すると、 アイテムオブジェクト以外のオブジェクトをMyBatisへ渡すことができます。
          以下にサンプルを示します。
        </p>

        <p>
          まず、任意のパラメータオブジェクトに変換するためのコンバータクラス(またはファクトリメソッド)を作成します。以下のサンプルではファクトリメソッドを使用します。
        </p>

        <source><![CDATA[
public static <T> Converter<T, Map<String, Object>> createItemToParameterMapConverter(String operationBy, LocalDateTime operationAt) {
  return item -> {
    Map<String, Object> parameter = new HashMap<>();
    parameter.put("item", item);
    parameter.put("operationBy", operationBy);
    parameter.put("operationAt", operationAt);
    return parameter;
  };
}]]></source>

        <p>
          つぎに, SQLマッピングを書きます。
        </p>

        <source><![CDATA[
<select id="createPerson" resultType="org.mybatis.spring.sample.domain.Person">
    insert into persons (first_name, last_name, operation_by, operation_at)
           values(#{item.firstName}, #{item.lastName}, #{operationBy}, #{operationAt})
</select>]]></source>

        <p>
          さいごに, <code>MyBatisBatchItemWriter</code>の設定を行います。
        </p>

        <source><![CDATA[
// Sample for Java Config
@Bean
public MyBatisBatchItemWriter<Person> writer() throws Exception {
  return new MyBatisBatchItemWriterBuilder<Person>()
      .sqlSessionFactory(sqlSessionFactory())
      .statementId("org.mybatis.spring.sample.mapper.PersonMapper.createPerson")
      .itemToParameterConverter(createItemToParameterMapConverter("batch_java_config_user", LocalDateTime.now()))
      .build();
}]]></source>

        <source><![CDATA[
<!-- Sample for XML Config -->
<bean id="writer" class="org.mybatis.spring.batch.MyBatisBatchItemWriter">
  <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
  <property name="statementId" value="org.mybatis.spring.sample.mapper.PersonMapper.createPerson"/>
  <property name="itemToParameterConverter">
    <bean class="org.mybatis.spring.sample.config.SampleJobConfig" factory-method="createItemToParameterMapConverter">
      <constructor-arg type="java.lang.String" value="batch_xml_config_user"/>
      <constructor-arg type="java.time.LocalDateTime" value="#{T(java.time.LocalDateTime).now()}"/>
    </bean>
  </property>
</bean>
]]></source>


        <p><strong>Composite Writer を使って複数のテーブルに書き込む（注意事項あり）</strong></p>

        <p>このテクニックを使うには MyBatis 3.2 以降が必要です。それ以前のバージョンには <a href="http://code.google.com/p/mybatis/issues/detail?id=741">問題</a> があるため、Writer が期待通りに動作しません。
        </p>

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

        <source><![CDATA[public class InteractionRecordToWriteInMultipleTables {
  private final VisitorInteraction visitorInteraction;
  private final CustomerInteraction customerInteraction;
  private final Interaction interaction;
  // ...
}

public class Interaction {
  private final InteractionMetadata interactionMetadata;
}]]></source>

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

        <source><![CDATA[<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>]]></source>

        <source><![CDATA[
@Bean
public CompositeItemWriter<?> interactionsItemWriter() {
  CompositeItemWriter compositeItemWriter = new CompositeItemWriter();
  List<ItemWriter<?>> writers = new ArrayList<>(4);
  writers.add(visitorInteractionsWriter());
  writers.add(customerInteractionsWriter());
  writers.add(interactionMetadataWriter());
  writers.add(interactionWriter());
  compositeItemWriter.setDelegates(writers);
  return compositeItemWriter;
}]]></source>

        <p>それぞれの writer を必要に応じて設定します。例えば <em>Interaction</em> と <em>InteractionMetadata</em> の設定は次のようになります。
        </p>

        <source><![CDATA[<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"/>]]></source>

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

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

        <source><![CDATA[<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>]]></source>

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

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