<?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="hpresnall@gmail.com">Hunter Presnall</author>
    <author email="eduardo.macarron@gmail.com">Eduardo Macarron</author>
  </properties>

  <body>
    <section name="Spring Batch">
      <p>
        Desde la versión 1.1.0 MyBatis-Spring proporciona dos beans para construir aplicaciones Spring Batch: <code>MyBatisPagingItemReader</code> 
        y <code>MyBatisCursorItemReader</code> y <code>MyBatisBatchItemWriter</code>.
        Also, As of version 2.0.0 provides three builder classes for supporting the Java Configuration: the <code>MyBatisPagingItemReaderBuilder</code>,
        the <code>MyBatisCursorItemReaderBuilder</code> and the <code>MyBatisBatchItemWriterBuilder</code>.
      </p>
      
      <p>
        <span class="label important">NOTA</span> 
        Esta sección se refiere a <a href="http://static.springsource.org/spring-batch/">Spring Batch</a> y no a 
        sesiones batch de MyBatis. Para obtener información sobre las sesiones batch ve a la sección
        <a href="sqlsession.html">Usnado un SqlSession</a>.
      </p>
      
      <subsection name="MyBatisPagingItemReader">
        <p>
          Este bean es un <code>ItemReader</code> que lee registros de una base de datos usando paginación.
        </p>
        
        <p>
          Ejecuta la sentencia especificada mediante la propiedad <code>setQueryId</code> para obtener los datos.
          La sentencia se ejecuta usando peticiones paginadas del tamaño indicando en la propiedad <code>setPageSize</code>.
          Al llamar al método <code>read()</code> éste devuelve el objeto que corresponde a la posición actual 
          y solicita más páginas si es necesario.

          El reader recibe algunos parametros estándar y la SQL deberá hacer uso de algunos de ellos para construir un resultset 
          del tamaño requerido. Los parametros son:
        </p>

        <ul>
          <li><code>_page</code>: el número de página a leer (comenzando en 0)</li>
          <li><code>_pagesize</code>: el tamaño de la página, es decir, el número de filas a devolver</li>
          <li><code>_skiprows</code>: el producto de <code>_page</code> por <code>_pagesize</code></li>
        </ul>

        <p>Se pueden mapear en un statement de tipo select de la siguiente forma:</p>
        <source><![CDATA[<select id="getEmployee" resultMap="employeeBatchResult">
  SELECT id, name, job FROM employees ORDER BY id ASC LIMIT #{_skiprows}, #{_pagesize}
</select>]]></source>

        <p>A continuación se muestra un ejemplo de configuración:</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>Veamos un ejemplo más complejo:</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>
          El ejemplo anterior hace uso de tres cosas distintas:
        </p>
        
        <ul>
          <li><code>sqlSessionFactory</code>: Puedes tu propio sessionFactory, podría ser útil si quires leer de 
            varias bases de datos.</li>
          <li><code>queryId</code>: Si el código accede a varias tablas, y tienes distintas sentencias de consulta,
             puede ser interesante usar ficheros de mapeo distintos con namespaces distintos.
             En este caso, al referirte a la query, no olvides incluir el namespace correspondiente.</li>
          <li><code>parameterValues</code>: Puedes pasar parametros adicionales en este mapa, el ejemplo de arriba
            usa un mapa que se construye usando una expresion SpEL y obteniendo valores del <code>jobExecutionContext</code>.
            Las claves del mapa puede usarse en el fichero mapper de MyBatis (por ejemplo:
            <em>yesterday</em> se puede usar como <code>#{yesterday,jdbcType=TIMESTAMP}</code>).
            Observa que el mapa y el reader se consutruyen en un solo <code>step</code> para que sea posible usar la expresión 
            SpEL con el <code>jobExecutionContext</code>. Adicionalmente si los type handlers de MyBatis
            están configurados correctamente puedes pasar instancias personalizadas como los parametros del ejemplo que son
            fechas JodaTime.</li>
          <li><code>pageSize</code>: Si le flujo batch está configurado con un tamaño de bloque (chunk size), 
            es importante pasar esta información al reader, y eso se hace mediante esta propiedad.</li>
        </ul>

      </subsection>

      <subsection name="MyBatisCursorItemReader">
        <p>
          Este bean es un <code>ItemReader</code> que lee registros de la base de datos usando un cursor.
        </p>

        <p>
          <span class="label important">NOTA</span> Para usar este bean necesitas al menos MyBatis 3.4.0 o superior.
        </p>
        
        <p>
          Ejecuta la sentencia especificada mediante la propiedad <code>setQueryId</code> para obtener los datos
          usando el método <code>selectCursor()</code>.
          Al llamar al método <code>read()</code> se devolverá el siguiente elemento del cursor 
          hasta que no quede ninguno por devolver.
        </p>
        
        <p>
          El reader usa una conexión separada para que la sentencia no participe en ninguna transacción creada como parte
          del proceso del step.
        </p>

        <p>Cuando se usar un cursor puedes usar una sentencia convencional:</p>
        <source><![CDATA[<select id="getEmployee" resultMap="employeeBatchResult">
  SELECT id, name, job FROM employees ORDER BY id ASC
</select>]]></source>

        <p>A continuación se muestra un ejemplo de configuración:</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>
          Es un <code>ItemWriter</code> que usa las capacidades de batch de <code>SqlSessionTemplate</code> para 
          ejecutar sentencias batch para todos los elementos (items) proporcionados. 
          El <code>SqlSessionFactory</code> debe configurarse con un executor de tipo <code>BATCH</code>.
        </p>

        <p>
          Ejecuta la sentencia indicada en la propiedad <code>statementId</code> cuando se invoca a <code>write()</code>.
          Se supone que <code>write()</code> se invoca dentro de una transacción.<br/>
        </p>
 
        <p>A continuación se muestra un ejemplo de configuración:</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>Converting a item that read using ItemReader to an any parameter object:</strong></p>

        <p>
          By default behavior, the <code>MyBatisBatchItemWriter</code> passes a item that read using <code>ItemReader</code>
          (or convert by <code>ItemProcessor</code>) to the MyBatis(<code>SqlSession#update()</code>) as the parameter object.
          If you want to customize a parameter object that passes to the MyBatis, you can realize to use the <code>itemToParameterConverter</code> option.
          For example using <code>itemToParameterConverter</code> option, you can passes any objects other than the item object to the MyBatis.
          Follows below a sample:
        </p>

        <p>
          At first, you create a custom converter class (or factory method). The following sample uses a factory method.
        </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>
          At next, you write a sql mapping.
        </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>
          At last, you configure the <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>Escribiendo en distintas tablas usando composite writers (con algunos condicionantes):</strong></p>

        <p>Esta técnica sólo puede usarse con MyBatis 3.2+, por que había un 
          <a href="http://code.google.com/p/mybatis/issues/detail?id=741">error</a> 
          en las versiones anteriores que hacían que el writer funcionara de forma incorrecta.
        </p>

        <p>Si el batch necesita escribir datos complejos, como registros con asociaciones, o en distintas bases de datos,
          entonces es necesario sortear el problema de que los insert statements solo pueden escribir en una tabla.
          Para conseguir esto debes preparar un <em>Item</em> para que sea escrito por el writer. Sin embargo, 
          dependiendo de las circunstancias puede ser interesante usar la siguiente técnica.
          El truco siguiente funciona con items con asociaciones simples o con tablas no relacionadas.
        </p>

        <p>
          Elabora el <code>item</code> de forma que <em>contenta</em> todos los resgistros distintos.
          Supon que para cada <code>item</code> hay una <em>Interaction</em> que tiene una asociación
          <em>InteractionMetadata</em> y dos filas no asociadas <em>VisitorInteraction</em> and
          <em>CustomerInteraction</em>. El objeto contenedor será de la siguiente forma:
        </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>
          Entonces en la configuración de spring habrá un <code>CompositeItemWriter</code> que usará writers
          delegados configurados especificamente para cada tipo de registro. Fijate que el <em>InteractionMetadata</em> 
          es una asociacióin en el ejemplo por lo que debe ser escrita antes para que la Interaction pueda recibir la clave
          generada.
        </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>Cada writer delegados se configura como sea necesario, por ejemplo para <em>Interaction</em> y
          <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>Al igual que con el reader el <code>statementId</code> puede hacer referencia al statement con un namespace como prefijo.</p>

        <p>Ahora es debe elaborarse el fichero de mapeo para cada tipo de registro, de la siguiente forma:</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>
          Lo que sucede es que primeramente se llamará a <code>insertInteractionMetadata</code>, y la sentencia de update 
          está configurada para devolver las claves autogeneradas (<code>keyProperty</code> y <code>keyColumn</code>).
          Una vez que el <code>InteractionMetadata</code> se ha almacenado por esta sentencia se puede ejecutar la siguiente para
          escribir el objeto padre <code>Interaction</code> mediante <code>insertInteraction</code>.
        </p>

        <p><strong><em>
          Sin embargo, ten en cuenta que los drivers JDBC se comportan distinto en este aspecto. A la fecha en la que se escribe esto
          el driver H2 1.3.168 solo devuelve el último ID incluso en modo BATCH (see org.h2.jdbc.JdbcStatement#getGeneratedKeys),
          mientras que el driver JDBC de MySQL se comporta como es de esperar y devuelve todos los IDs.</em></strong>
        </p>
      </subsection>
    </section>
  </body>
</document>