Intentaremos hacer una introducción a Spring Batch, uno de los componentes de Spring mas desconocidos hoy en día. No será esta una guía completa del framework, aunque pretemdemos facilitar el primer contacto con este componente y nombrar algunas de sus características. Aunque no nos resulte el trabajo mas excitante el escribir programas batch, siempre es necesario en los trabajos tener claro como podríamos utilizar un framework para tal fin.

Ya hace unas semanas que jugamos un poco con el framework -sabrán que fue mas Leo que yo no...- aunque les cuento que leí casi toda la funcionalidad que ofrecía el componente y llegue a entender el ejemplo que Leo me pasó y todo. En esas semanas pensabamos que teníamos un proyecto 'justo' para este framework, que por razones de tiempo -y a veces de NO esfuerzo- no pudimos implementar en producción.

Antes de comenzar
Les cuento que soy una de esas personas que de los últimos diez años ha pasado casi seis haciendo programas batch -por suerte no fueron los últimos seis, sino los primeros- y despues de estos últimos cuatro años sin hacer batch, la verdad que se me ponen los pelos de punta cuando me hablan de los mismos. Programas en C, PRO*C monolíticos con miles de líneas de código, con un montón de código que se dejo de usar y da miedo sacar, con nada de documentación y si MILES de líneas de código (5000, 7000)... o sea, difíciles de entender, de mantener, eso que ahora me molesta mas, antes estaba acostumbrado.

Cuando empecé a leer la documentación, el primer pensamiento que se me vino a la mente fue 'Ufff, estos de Spring se pasaron de la raya con la ingeniería de la librería, o con la sobreingeniería'.

Entonces con la lectura de la documentación hecha y con el ejemplo de Leo en mis narices, comencé a entender un poco mas que los de Spring no habían pecado de sobreingeniería, y podemos decir entonces que Spring Batch no es un framework sino un esqueleto para desarrollar progamas Batch de una manera simple, entendible y mantenible, y además tiene algunas implementaciones del esqueleto muy buenas.

Vamos entonces con Spring Batch en 2 palabras

Afortunadamente, el modelo de objetos que tiene Spring Batch lo explican los nombres por sí mismo. Vamos a tratar de enumerar los más importantes y para vincularlos entre sí:

Un Job (Trabajo) se compone de uno o más Step's (Pasos). Un JobInstance representa un determinado Job, parametrizado con un conjunto de propiedades llamados JobParameters. Cada ejecución de una JobInstance es una JobExecution. Imagine un trabajo de lectura de entradas de una base de datos y la generación de un XML que represente la misma y, a continuación, haciendo algo para limpiarla.

Por ejemplo, pensemos un Job compuesto de 2 Step's: lectura / escritura y la limpieza. Si parametriza este Job por la fecha de los datos generados, entonces nuestro Job del Viernes 13 es una JobInstance. Cada vez que ejecute este ejemplo (si se produce un error, para la instancia) es una JobExecution. Este modelo ofrece una gran flexibilidad respecto a cómo los Job se inician y se ponen en marcha. Naturalmente, esto nos lleva a iniciar sus trabajos con los parámetros de empleo, que es responsabilidad del JobLauncher. Por último, diversos objetos en el framework requieren un JobRepository para almacenar en tiempo de ejecución información relacionada con la ejecución batch. De hecho, el modelo de dominio de Spring Batch es mucho más elaborado pero esto será suficiente por ahora.

Writers, Readers o Transformers

Spring Batch viene por defecto con diferentes tipos de Readers para leer ficheros planos, XML o datos de base de datos. Estos Readers se configuran con una especie de mapeo entre registros y objetos.

Por ejemplo, un archivo de planetas separado por comas, como el siguiente:

10,Mercurio,4879,rocoso,mensajero de los dioses
20,Venus,12103,telurico,diosa del amor y de la belleza
30,Tierra,12875,telurico,madre de todos los dioses
40,Marte,6794,telurico,dios de la guerra

Podría ser interpretado en objetos como:

public class Planeta {
    private int codigo;
    private String nombre;
    private long diametro;
    private String tipo;
    private String significado;
    ....
}
 

Esos datos podrían por ejemplo ser transformados con cadenas de transformadores en base a diferentes reglas y posteriormente almacenados utilizando un Writer a base de datos con su correspondiente mapeo entre los diferentes campos y las columnas de una tabla que recoja esos datos.

Un Hola Mundo con Spring Batch

Vamos entonces a realizar el clásico "Hola, Mundo" con Spring Batch. Básicamente crearemos un Job que contendrá 3 Steps:
  1. imprimir "Hola, "
  2. imprimir "Mundo"
  3. imprimir "!!!"
En código deberemos programar dos archivos:
  • spring-batch-demo.xml, el contexto de Spring donde configuraremos Spring Batch, los Jobs y demás beans.
  • ImprimirTasklet.java, que será la clase con la lógica para imprimir por consola un mensaje cualquiera.

Configuración básica

Por cada Job, vamos a utilizar un bean de Spring separado que lo representa. Hay también una serie de objetos comunes que vamos a necesitar usualmente. Vamos a ir a través de estos objetos comunes:

JobLauncher

Los JobLaunchers son responsables de iniciar un trabajo con determinados parámetros. Existe una implementación prevista, SimpleJobLauncher, que se basa en una TaskExecutor para poner en marcha los trabajos. Si no específico TaskExecutor, se setea entonces un SyncTaskExecutor para utilizarlo.

JobRepository

Un JobRepository es el encargado de almacenar información sobre la corrida de los Jobs.

Vamos a utilizar la implementación MapJobRepositoryFactoryBean que guarda la información de las ejecuciones en memoria.

En una implementación real, donde se quiere guardar en forma persistente esta información, se puede usar la implementación JobRepositoryFactoryBean la cual utiliza una base de datos para almacenar toda la corrida. Spring Batch utiliza un modelo de datos con tablas propias para este fin.

TransactionManager

No es un bean propio de Spring Batch, pero lo utiliza el JobRepository para manejar las transacciones. En este ejemplo, como no accederemos a ningún medio transaccional, usaremos una implementación "dummy" del transaction manager ya provista por Spring Batch, llamada ResourcelessTransactionManager.

Aquí está nuestro spring-batch-demo.xml

 
<beans>
  <bean id="transactionManager" 
        class="org.springframework.batch.
               support.transaction.ResourcelessTransactionManager"/>
 
  <bean id="jobRepository" 
        class="org.springframework.batch.
               core.repository.support.MapJobRepositoryFactoryBean">
        <property name="transactionManager" ref="transactionManager"/>
  </bean>
 
  <bean id="jobLauncher" 
        class="org.springframework.batch.
               core.launch.support.SimpleJobLauncher">
      <property name="jobRepository" ref="jobRepository"/>
  </bean>
</beans>
 

Los tasklets del Hola Mundo

Un Tasklet es un objeto que contiene cualquier lógica que será ejecutada como parte de un trabajo. Los Tasklets se construyen mediante la implementación de la interfaz Tasklet. Los Tasklet son la forma más simple en Spring Batch para ejecuar código.

Vamos a aplicar una tasklet que simplemente imprime un mensaje por consola:

 
public class ImprimirTasklet implements Tasklet {
    private String mensaje;
 
    public String getMensaje() {
        return mensaje;
    }
 
    public void setMensaje(String mensaje) {
        this.mensaje = mensaje;
    }
 
    public ExitStatus execute() throws Exception {
        System.out.print(mensaje);
        return ExitStatus.FINISHED;
    }
}
 

Tengan en cuenta que al ejecutar el método devuelve un ExitStatus para indicar el estado de la ejecución de la Tasklet.

Vamos a definir nuestro primer Job ahora en el XML de la aplicación. Usaremos la implementación SimpleJob que ejecuta todos los pasos de sequencialmente. Con el fin de conectar un tasklet a un Job, necesitamos un TaskletStep.

Es decir, a continuación agregaremos a nuestra configuración anterior:

  • un SimpleJob
  • tres TaskletStep, que referencian a nuestros Tasklet
  • tres Tasklet, configurados para imprimir distintos mensajes

 
<bean id="trabajoBatch" class="org.springframework.batch.core.job.SimpleJob">
    <property name="steps">
        <list>
            <bean id="primerPaso" 
                  class="org.springframework.batch.core.step.tasklet.TaskletStep">
                <property name="jobRepository" ref="jobRepository"/>
                <property name="tasklet" ref="imprimirHola" />
            </bean>                
 
            <bean id="segundoPaso" 
                  class="org.springframework.batch.core.step.tasklet.TaskletStep">
                <property name="jobRepository" ref="jobRepository"/>
                <property name="tasklet" ref="imprimirMundo"/>
            </bean>                
 
            <bean id="tercerPaso" 
                  class="org.springframework.batch.core.step.tasklet.TaskletStep">
                <property name="jobRepository" ref="jobRepository"/>
                <property name="tasklet" ref="imprimirExclamacion"/>
            </bean>                
        </list>
    </property>
    <property name="restartable" value="true" />     
    <property name="jobRepository" ref="jobRepository" />
</bean>
 
<bean id="imprimirHola" class="com.dosideas.springbatch.demo0.ImprimirTasklet">
    <property name="mensaje" value="Hola, " />
</bean>
 
<bean id="imprimirMundo" class="com.dosideas.springbatch.demo0.ImprimirTasklet">
    <property name="mensaje" value="Mundo" />
</bean>
 
<bean id="imprimirExclamacion" class="com.dosideas.springbatch.demo0.ImprimirTasklet">
    <property name="mensaje" value="!!!" />
</bean>
 

Ejecutando el Job

Ahora tenemos algo para poner en marcha la ejecución de nuestros Trabajos. Para esto crearemos un test JUnit que obtenga una instancia del JobLauncher, e inicie una corrida del Job.

 
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {
    "classpath:/com/dosideas/springbatch/demo0/spring-batch-demo.xml"
})
public class ImprimirHolaMundoTest {
 
    @Autowired
    private SimpleJobLauncher launcher;
 
    @Autowired
    private SimpleJob job;
 
    @Test
    public void iniciarJob() throws Exception {
        JobParametersBuilder builder = new JobParametersBuilder();
        builder.addDate("Ejecucion", new Date());
        builder.addString("jobName", "Imprimir hola mundo por consola");
        JobParameters parameters = builder.toJobParameters();
 
        launcher.run(job, parameters);
    }
}
 

Y listo! Este test ejecutará la tarea, y veremos por consola el mensaje "Hola, Mundo!!!".

Spring Batch también ofrece una clase conveniente para ejecutarse desde la línea de comandos: CommandLineJobRunner. En su forma más simple esta clase tiene de 2 argumentos: el XML de contexto de la aplicación que contiene el Job para poner en marcha y el id de ese Job. Naturalmente, requiere un JobLauncher que es configurado en el mismo XML.

A continuación se muestra cómo iniciar el trabajo desde la linea de comandos (necesita especificar el classpath):

java org.springframework.batch.core.launch.support.CommandLineJobRunner
     spring-batch-demo.xml trabajoBatch

Curso de Spring Batch y descarga de un proyecto de ejemplo

Para ver más detalles pueden descargar el proyecto de ejemplo de Spring Batch. Esta descarga contiene el ejemplo aquí comentado en forma completa, junto a varios ejemplos más que demuestran distintos aspectos de Spring Batch, y todas las librerias necesarias para su funcionamiento.

Además, publicamos el curso Procesamiento con Spring Batch, donde repasamos las distintas características de este framework y explicamos el proyecto de ejemplo en detalle.

Adaptado libremente de Spring Batch Hello World.

Inspiración.

"Si tú tienes una manzana y yo tengo una manzana e intercambiamos las manzanas, entonces tanto tú como yo seguiremos teniendo una manzana cada uno. Pero si tú tienes una idea y yo tengo una idea, e intercambiamos las ideas, entonces ambos tendremos dos ideas"

Bernard Shaw