Spring Batch I: Introducción

Inicio una serie de entradas sobre una herramienta para la definición de procesos batch con nombre Spring Batch. En las próximas publicaciones, realizaré una definición de los conceptos básicos de Spring Batch y, además, realizaré ejemplos prácticos para aumentar la comprensión de los conceptos definidos. En esta primera primera entrada, Spring Batch I: Introducción, realizaré una breve introducción de la tecnología así como la creación de un ejemplo básico.

Hasta la fecha, cuando tenía que realizar un tratamiento por lotes, realizaba, en líneas generales, el desarrollo de una clase con la cual realizaba la lectura de unos recursos, procesaba lo leído y generaba la salida del proceso con un resultado. Todo este proceso conlleva la definición de los conectores a los recursos, apertura de conexiones a base de datos y una serie de actividades de infraestructura con los tratamientos específicos para cada operación.

Con Spring Batch, podemos definir procesos que realicen lecturas a ficheros, ejecución de consultas a base de datos, procesadores, y escrituras de resultados a ficheros y base de datos; todo ello, de una forma sencilla y, además, ocultando operaciones de infraestructura. Es decir, con Spring podemos definir y desarrollar procesos batch de una forma sencilla y declarativa.

Definición de la arquitectura y conceptos

La definición de la arquitectura de un proceso es la representada en la siguiente imagen obtenida de la documentación oficial de Srping.

Los conceptos básicos de Spring Batch son los siguientes:

  • JobLauncher.-  Definimos JobLauncher como aquella entidad que lanza los procesos batch.
  • Job.- Definimos job como un proceso batch formado de unidades secuencias o step.
  • JobParameter.- Definimos jobParameter como un parámetro de un proceso
  • Step.- Definimos step como una unidad independiente de un proceso.
  • ExecutionContext.- Definimos ExecutionContext como el contexto de ejecución de un determinado proceso batch o job.
  • JobRepository.- Definimos JobRepository como el repositorio de la ejecución de un proceso.
  • ItemReader.- Definimos ItemReader como aquella unidad funcional encargada de realizar las lecturas de las fuentes de datos.
  • ItemProcessor.- Definimos ItemProcessor como aquella entidad encargada del procesamiento de una determinado tarea.
  • ItemWriter.- Definimos ItemWriter como aquella unidad funcional encargada de realizar la escritura del resultado de una fase de ejecución del proceso.
  • Mapper.- Definimos un Mapper como aquella entidad encargada de mapear los datos a leer/escribir en objetos.

Definición conceptual del ejemplo

La funcionalidad del proceso que definiremos será la lectura de un fichero con extensión txt, el procesamiento de cada línea; y, como salida, la escritura de un fichero txt. El contenido de cada línea son valores numéricos y alfanuméricos.

Configuración Maven

Para poder utilizar Spring Batch necesitamos tres ficheros: spring-batch-core, spring-batch-infrastructure y spring-batch-test. En maven se definen como se muestra en el siguiente snippet:

<dependency>
 <groupId>org.springframework.batch</groupId>
 <artifactId>spring-batch-core</artifactId>
 <version>${spring.batch.version}</version>
</dependency>
<dependency>
 <groupId>org.springframework.batch</groupId>
 <artifactId>spring-batch-infrastructure</artifactId>
 <version>${spring.batch.version}</version>
</dependency>
<dependency>
 <groupId>org.springframework.batch</groupId>
 <artifactId>spring-batch-test</artifactId>
 <version>${spring.batch.version}</version>
</dependency>

Para poder operar con Spring necesitamos a su vez las siguientes dependencias: spring-core, spring-jdbc y spring-oxm. En maven se definen como se muestra en el siguiente snippet:

<dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring-core</artifactId>
 <version>${spring.version}</version>
</dependency>
<dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring-jdbc</artifactId>
 <version>${spring.version}</version>
</dependency>
<dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring-oxm</artifactId>
 <version>${spring.version}</version>
</dependency>

Definición de un proceso

En el presente apartado, definiremos la forma de definir un proceso. Un proceso se define con la etiqueta job y se identifica mediante un literal en el atributo id. Un proceso está compuesto de pasos mediante el tag step; estos, se identifican con un literal en el atributo id. Los pasos se componen de tasklet que son las tareas de cada paso; estos a su vez, si la funcionalidad del paso lo requiere, se componen de elementos con la etiqueta chunk que representan una lectura, un procesamiento y una escritura. El chunk tiene los siguientes atributos: reader, para identificar al lector; processor, para identificar la entidad que procesa cada fila leída; writer, para identificar la entidad que escribe en el destino;y, para finalizar, el atributo commit-interval, para definir el número de filas que se leen antes de escribir en el destino. Un ejemplo de definición de una proceso es el siguiente:

<batch:job id="HolaMundoJob">
   <batch:step id="step1" >
      <batch:tasklet>
         <batch:chunk reader="txtFileItemReader" writer="txtItemWriter"
              processor="holaMundoProcessor" commit-interval="1">
         </batch:chunk>
      </batch:tasklet>
   </batch:step>
 </batch:job>

En los siguientes apartados definiremos cada uno de los elementos que forman parte del proceso definido.

Definición de un lector

El tipo de fuentes de datos de los procesos pueden ser: ficheros de texto, csv,  xml, o bien, una base de datos. En el ejemplo, realizaremos la definición de un lector para un fichero txt. El nombre del lector queda definido en el atributo id; la fuente de datos, es decir, el fichero origen, se define en el atributo resource , en este caso, es un fichero txt; el limitador de las línea del fichero origen, se define en la clase DefaultLineMapper al cual se le define la estructura dde la línea del fichero leído, es decir,  el nombre de los campos y el orden en el origen de datos; y, para finalizar, se define el mapeador que convierte los datos leídos de una línea en un objeto.

El snippet de definición del lector es el siguiente:

<bean id="txtFileItemReader" class="org.springframework.batch.item.file.FlatFileItemReader">
 <property name="resource" value="classpath:/entrada/datos-entrada.txt" />
 <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,nombre,cantidad" />
        </bean>
      </property>
      <property name="fieldSetMapper">
         <bean class="es.directoandroid.mapper.HolaMundoFieldSetMapper" />
      </property>
   </bean>
 </property>
</bean>

La clase HolaMundoFieldSetMapper realiza la transformación de la línea leída del fichero de entrada en un bean. El snippet del método principal de la clase es el siguiente:

public Registro mapFieldSet(FieldSet fieldSet) throws BindException {
 Registro report = new Registro();
 report.setId(fieldSet.readInt(0));
 report.setNombre(fieldSet.readString(1));
 report.setCantidad(fieldSet.readInt(2));
 logger.debug("Report =" + report);
 return report;
}

Definición de un procesador

El procesador es aquella entidad que implementa el interfaz ItemProcessor. La definición debe  de definir las clases parametrizadas de entrada y salida. En nuestro caso, la clase de entrada es la misma que la clase de salida cuyo nombre es Registro. En esta clase es en donde se puede inyectar aquellos servicios Spring para su tratamiento. En el ejemplo, el procesamiento que realizaremos es la modificación del campo nombre.

La clase procesadora es la siguiente:

@Component("holaMundoProcessor")
 public class HolaMundoProcessor implements ItemProcessor<Registro, Registro> {
   private final Logger logger = LoggerFactory
      .getLogger(HolaMundoProcessor.class);
   public Registro process(Registro item) throws Exception {
      logger.debug("Procesando un elemento, valor=" + item);
      item.setNombre(item.getNombre() + " PROCESADO");
      return item;
   }
 }

Definición de un escritor

El escritor tiene una definición parecida al lector pero a diferencia que crea un fichero de salida. En nuestro caso, realizamos la creación de un fichero txt con nombre salida.txt. Además, debemos de especificar la entidad que agrega al fichero de salida la línea procesada; esta entidad es la clase HolaMundoLineAgregator la cual define el interfa LineAgregator<T> con una clase parametrizada con los datos a escribir.

El snippet de la definición de la entidad de salida es la siguiente:

<bean id="txtItemWriter" class="org.springframework.batch.item.file.FlatFileItemWriter">
 <property name="resource" value="file:txt/salida/salida.txt" />
 <property name="lineAggregator">
    <bean class="es.directoandroid.aggregator.HolaMundoLineAggregator" />
 </property>
</bean>

La clase agregadora HolaMundoLineAgregator es la siguiente:

public class HolaMundoLineAggregator<T> implements LineAggregator<T> {
   public String aggregate(T item) {
     return item.toString();
   }
}

La clase Registro es una clase bean que define mediante anotaciones los nodos del fichero xml.

Ejecución del proceso

La ejecución del proceso se realiza mediante una clase java en la cual se carga el contexto Spring; del contexto, obtenemos la referencia al lanzador, la referencia del proceso y ejecutamos el proceso con el método run de la clase JobLauncher al cual se le pasa el proceso y sus parámetros; como resultado, obtenemos un objeto de la clase JobExecution con los datos del resultado del procesamiento empleando, entre otros, los método getStatus y getExitStatus.

El snippet de la aplicación es el siguiente:

public class App {
  public static void main(String[] args) {
     String[] springConfig = { "/META-INF/spring/ejem1-job-application-context.xml" };
     ApplicationContext context = new ClassPathXmlApplicationContext(
       springConfig);
     JobLauncher jobLauncher = (JobLauncher) context.getBean("jobLauncher");
     Job job = (Job) context.getBean("HolaMundoJob");
     try {
       JobExecution execution = jobLauncher.run(job, new JobParameters());
       System.out.println("Estado de salida : " + execution.getStatus());
       System.out.println("getExitStatus : " + execution.getExitStatus());
       System.out.println("execution : " + execution);
     } catch (Exception e) {
       e.printStackTrace();
     }
     System.out.println("Fin");
  }
}

Si se desea el código ejemplo se puede acceder aquí.

En la siguiente entrada, Spring Batch II: Lectores (Reader), realizaré una descripción de los posibles lectores que podemos tener.

Anuncios

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión /  Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión /  Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión /  Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión /  Cambiar )

Conectando a %s