Showing posts with label Spring Batch. Show all posts
Showing posts with label Spring Batch. Show all posts

Monday, March 30, 2009

Tutorial Spring Batch - File Converter, Parte 2

Ya un rato sin poner algo en el blog. Y dado que tengo pendiente la parte 2 del tutorial de Spring Batch, pues empecemos...

La primera parte de este tutorial se enfoco mas a la teor铆a y los conceptos b谩sicos que maneja Spring Batch. Esta segunda parte sera mas practica y nos enfocaremos a crear el miniproyecto tutorial que demuestre un poco las funciones de este framework. Solo espero no extenderme tanto y no hacer una segunda parte de esta segunda parte :P.

Para este ejercicio vamos a suponer que tenemos que hacer un sistema que toma archivos de texto con informaci贸n de contactos donde los datos vienen separados por comas y que se tiene que transformar a otro archivo de texto donde la informaci贸n vendr谩 en registros de tama帽o fijo. Cada linea del archivo separado por comas representa la informaci贸n de un contacto en el siguiente estructura:
  1. Nombre
  2. Apellido
  3. e-mail personal
  4. e-mail del trabajo
  5. telefono
  6. pagina web
Empezaremos creando la plantilla del proyecto con maven 2 y el plugin archetype ejecutando el siguiente comando:
$> mvn archetype:create -DgroupId=<aqui-va-el-groupId> -DartifactId=<aqui-va-el-artifactId>
En este caso use groupId=com.jabaddon.tutorials.springbatch.fileconverter y artifactId=file-converter. Para mas informaci贸n de maven 2 y el uso del plugin archetype consulten la referencia que dejo al final.

Una vez ejecutado el comando tendremos una plantilla de proyecto simple en maven 2. Para poder hacer uso de Spring Batch tendremos que indicarle a nuestro proyecto en maven donde encontrar las librer铆as necesarias de Spring Batch para empezar a codificar. Como usaremos la versi贸n 2.0.0.RC2 hay que agregar a nuestro pom.xml el repositorio de spring-batch:
12:    <repositories>
13: <repository>
14: <id>spring-s3</id>
15: <name>Spring Maven MILESTONE Repository</name>
16: <url>http://s3.amazonaws.com/maven.springframework.org/milestone</url>
17: </repository>
18: </repositories>
Y las librer铆as que usaremos son:
20:    <dependencies>
21: <!-- Para las pruebas unitarias -->
22: <dependency>
23: <groupId>junit</groupId>
24: <artifactId>junit</artifactId>
25: <version>3.8.1</version>
26: <scope>test</scope>
27: </dependency>
28:
29: <!-- Para las pruebas unitarias con Spring -->
30: <dependency>
31: <groupId>org.springframework</groupId>
32: <artifactId>spring-test</artifactId>
33: <version>2.5.5</version>
34: <scope>test</scope>
35: </dependency>
36:
37: <!-- Para el logging -->
38: <dependency>
39: <groupId>log4j</groupId>
40: <artifactId>log4j</artifactId>
41: <version>1.2.8</version>
42: </dependency>
43:
44: <!-- Spring batch -->
45: <dependency>
46: <groupId>org.springframework.batch</groupId>
47: <artifactId>spring-batch-core</artifactId>
48: <version>2.0.0.RC2</version>
49: </dependency>
50:
51: <!-- Spring batch -->
52: <dependency>
53: <groupId>org.springframework.batch</groupId>
54: <artifactId>spring-batch-execution</artifactId>
55: <version>1.0.0.m4</version>
56: </dependency>
57:
58: <!-- Para la conexion a la base de datos -->
59: <dependency>
60: <groupId>commons-dbcp</groupId>
61: <artifactId>commons-dbcp</artifactId>
62: <version>1.2</version>
63: </dependency>
64:
65: <!-- El driver jdbc para mysql -->
66: <dependency>
67: <groupId>mysql</groupId>
68: <artifactId>mysql-connector-java</artifactId>
69: <version>5.1.6</version>
70: </dependency>
71:
72: </dependencies>
Ahora, yo tuve problemas a la hora de compilar el proyecto ya que intentaba el compilador hacerlo usando la versi贸n 1.3 de java, por lo que tuve que agregarle lo siguiente al pom.xml para obligarlo a compilar con java 1.5:
74:    <build>
75: <plugins>
76: <plugin>
77: <groupId>org.apache.maven.plugins</groupId>
78: <artifactId>maven-compiler-plugin</artifactId>
79: <configuration>
80: <source>1.5</source>
81: <target>1.5</target>
82: <archive>
83: <manifest>
84: <addClasspath>true</addClasspath>
85: </manifest>
86: </archive>
87: </configuration>
88: </plugin>
89: </plugins>
90: </build>
Job

El Job de este proyecto solo consistira de un paso, el cual se encargara de leer la informacion de un archivo para escribirlo en otro. El bean en el xml de spring de este Job lo definiremos como:
66:    <job id="process">
67: <step id="loadNWriteFile">
68: <tasklet reader="reader" writer="writer" commit-interval="1"/>
69: </step>
70: </job>
El Step esta definido por un tasklet que se compone de un reader y un writer. El bean del reader lo definimos como:
56:    <beans:bean id="reader" scope="step" class="org.springframework.batch.item.file.FlatFileItemReader">
57: <beans:property name="resource" value="#{jobParameters[input.file.name]}" />
58: <beans:property name="lineMapper" ref="lineMapper" />
59: </beans:bean>
Lectura (reader)

El 'reader' tiene dos propiedades. La propiedad 'resource' indica la url del archivo que leera y la propiedad 'lineMapper' define un bean que mapea cada linea del archivo a un POJO (que nosotros definiremos como la clase Contacto).

Como se logra notar el valor de la propiedad 'resource' del bean 'reader' tiene el valor de '#{jobParameters[input.file.name]}', esto es asi porque de esta forma le indicamos a Spring Batch que el valor lo tome del parametro que recibe el job con el nombre 'input.file.name'.

El lineMapper que usa el reader esta definido de la siguiente forma:
51:    <beans:bean id="lineMapper" class="org.springframework.batch.item.file.mapping.DefaultLineMapper">
52: <beans:property name="lineTokenizer"><beans:ref bean="tokenizer" /></beans:property>
53: <beans:property name="fieldSetMapper"><beans:ref bean="fieldSetMapper" /></beans:property>
54: </beans:bean>
Un LineMapper en Spring Batch es el encargado de (con ayuda de un Tokenizer) leer las lineas de una archivo para estos datos mapearlos a un POJO via el FieldSetMapper.

Como nuestro archivo esta separado por comas el Tokenizer que definimos es uno al cual se le puede especificar el caracter que divide los datos de una linea del archivo:
41:    <beans:bean id="tokenizer"
42: class="org.springframework.batch.item.file.transform.DelimitedLineTokenizer">
43: <beans:property name="delimiter"><beans:value>,</beans:value></beans:property>
44: <beans:property name="names" value="nombre,apellido,mailPersonal,mailTrabajo,telefono,paginaWeb" />
45: </beans:bean>
Este tokenizer tiene dos propiedades. La propiedad 'delimiter' indica el caracter por el cual vendra separada la informacion que en nuestro caso es una coma ','. La propiedad 'names' indica el nombre que le pondremos a cada uno de los valores en la linea de informacion en el archivo.

El fieldSetMapper es una clase que nosotros escribimos implementando de la clase de Spring Batch org.springframework.batch.item.file.mapping.FieldSetMapper y sobreescribiendo el metodo mapFieldSet, mismo que recibe como argumento un objeto de la clase org.springframework.batch.item.file.transform.FieldSet con la cual tenemos acceso a los campos definos en el tokenizer. Por ejemplo, la implementacion del metodo mapFieldSet en el codigo del tutorial luce asi:
 8:    public Contacto mapFieldSet(FieldSet fieldset) {
9: Contacto newContacto = new Contacto();
10:
11: newContacto.setNombre(fieldset.readString("nombre"));
12: newContacto.setApellido(fieldset.readString("apellido"));
13: newContacto.setMailPersonal(fieldset.readString("mailPersonal"));
14: newContacto.setMailTrabajo(fieldset.readString("mailTrabajo"));
15: newContacto.setTelefono(fieldset.readString("telefono"));
16: newContacto.setPaginaWeb(fieldset.readString("paginaWeb"));
17:
18: return newContacto;
19: }
20:
Como se logra ver la clase FieldSet contiene varios metodos readXXX con la cual se puede leer cada uno de los valores definidos en el tokenizer.

Escritura (writer)

La informacion que lee un reader es pasada a un writer para que este haga lo que tenga que hacer con esa informacion. En nuestro caso lo que vamos a hacer con esa informacion es escribirla en otro formato a otro archivo. El bean del writer lo definimos como:
61:    <beans:bean id="writer" scope="step"
62: class="com.jabaddon.tutorials.springbatch.fileconverter.ContactoItemWriter">
63: <beans:property name="archivoSalida" value="#{jobParameters[output.file.name]}" />
64: </beans:bean>
Este writer es una clase que nosotros escribimos implementando la clase org.springframework.batch.item.ItemWriter e implementado el metodo write(List items). A esta clase se le agrego una propiedad en la que le mandamos como parametro del job la ruta y nombre del archivo en el cual se va a escribir '#{jobParameters[output.file.name]}'. El metodo write de la clase que escribimos luce asi:
26:    public void write(List<? extends Contacto> items) throws Exception {
27: LOGGER.debug("### -> write() ###");
28: BufferedWriter out = null;
29: try {
30: new File(archivoSalida).createNewFile();
31: out = new BufferedWriter(new FileWriter(archivoSalida, true));
32: LOGGER.info("-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*");
33: for (Contacto item : items) {
34: LOGGER.info("Escribiendo item : " + item);
35: out.write(item.toString());
36: out.write("\n");
37: }
38: LOGGER.info("-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*");
39: }
40: finally {
41: if (out != null) {
42: out.close();
43: }
44: }
45: LOGGER.debug("### <- write() ###");
46: }
En esta clase es donde esta la logica de escribir el otro archivo.

JobRepository

Para que Spring Batch trabaje y guarde bitacora de los procesos que corre y persista valores es necesario crear una base de datos en la cual guarda todos estos valores. Actualmente tiene soporte para las bases de datos: db2, derby, hsqldb, mysql, oracle10 y postgresql. En el caso de este tutorial se uso mysql.

El JobRepository es la interfaz con la cual un Job interactua con la base de datos para realizar la persistencia y busqueda de datos. El bean del Job, aunque no lo tiene especificado, apunta por default a otro bean con nombre 'jobRepository' el cual lo definimos asi:
32:    <beans:bean id="jobRepository"
33: class="org.springframework.batch.core.repository.support.JobRepositoryFactoryBean"
34: p:dataSource-ref="dataSource" p:transactionManager-ref="transactionManager" />
35:
De esta forma el bean dataSource y transactionManager estan definidos asi:
13:    <beans:bean id="dataSource"
14: class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
15: <beans:property name="driverClassName" value="com.mysql.jdbc.Driver"/>
16: <beans:property name="url" value="jdbc:mysql://localhost/springbatch_tutorial"/>
17: <beans:property name="username" value="root"/>
18: <beans:property name="password" value="admin"/>
19: <beans:property name="maxActive" value="5"/>
20: <beans:property name="initialSize" value="1"/>
21: </beans:bean>
22:
23: <beans:bean id="transactionManager"
24: class="org.springframework.jdbc.datasource.DataSourceTransactionManager" lazy-init="true">
25: <beans:property name="dataSource" ref="dataSource" />
26: </beans:bean>
Los diversos scripts para crear el esquema de la base de datos lo trae el zip de la distribucion de Spring Batch.

JobLauncher

Ya creado todo para poder usar Spring Batch necesitamos definir un ultimo bean el cual nos ayudara a levantar y correr los Jobs. Este bean es el jobLauncher y lo definimos asi:
32:    <beans:bean id="jobLauncher"
33: class="org.springframework.batch.core.launch.support.SimpleJobLauncher">
34: <beans:property name="jobRepository" ref="jobRepository" />
35: </beans:bean>
Este bean como lo mencione antes con el cual vamos a poder iniciar Jobs mandandole los parametros adecuados para que corran. En el caso de este tutorial se definio una clase de prueba unitaria la cual tiene el metodo siguiente:
51:    public void testJob() throws Exception {
52: LOGGER.debug("### -> testJob() ###");
53: String userdir = System.getProperty("user.dir");
54: String inputFileName = "file:///" + userdir + "/src/test/files/datos-contactos.csv";
55: String outputFileName = userdir + "/target/datos-contactos.txt";
56: JobLauncher jobLauncher = (JobLauncher) this.getApplicationContext().getBean("jobLauncher");
57: JobParametersBuilder paramsBuilder = new JobParametersBuilder();
58: paramsBuilder.addLong("date.miliseconds", System.currentTimeMillis());
59: paramsBuilder.addString("input.file.name", inputFileName);
60: paramsBuilder.addString("output.file.name", outputFileName);
61: JobExecution jobExecution =
62: jobLauncher.run((Job) this.getApplicationContext().getBean("process"),
63: paramsBuilder.toJobParameters());
64:
65: LOGGER.debug("### <- testJob() ###");
66: }
En este metodo se definen las rutas de los archivos a leer y a escribir y se crean los parametros que se le enviaran al Job y con ayuda del bean JobLauncher se ejecuta el Job.

Referencias

Friday, March 27, 2009

Tutorial Spring Batch - File Converter, Parte 1 (Introducci贸n)

Dicen que la mejor forma de aprender algo es ense帽谩ndolo. Por esta raz贸n he decidido escribir un peque帽o tutorial del uso de Spring Batch 2.0.0.RC2 haciendo un peque帽o proyecto para un conversor de archivos de un formato a otro.

Est谩n muy de moda el desarrollo de sistemas basados en servicios, SOA, SaaS, ESB, BPM, etc. Sin embargo, aun existe una gran necesidad en las empresas por aplicaciones de procesamiento masivo de informaci贸n en ambientes de misi贸n critica. Generalmente este tipo de aplicaciones de procesamiento se desarrollan usando COBOL o C/C++, pero ahora con Spring Batch podemos empezar a pensar en usar Java para este tipo de aplicaciones.

Hay toda una serie de conceptos involucrados en lo que es Spring Batch, de los cuales algunos de los mas importantes para entender esto son:
  • Job: Un Job en Spring Batch es una entidad para encapsular todo un proceso batch. Un Job esta compuesto de una serie de pasos (Steps) que tienen una relaci贸n l贸gica y en conjunto la ejecucion de estos pasos realizan el procesamiento deseado.
  • JobInstance: As铆 como una Clase es a un Objeto, un Job es a un JobInstance. Un JobInstance es la ejecuci贸n de un Job. Por ejemplo, si tenemos definido un Job que debe correr una vez al final del d铆a, la ejecuci贸n del Job el d铆a 25 de Marzo del 2009 sera un JobInstance, al igual que la ejecuci贸n del Job el d铆a 26 de Marzo del 2009. Sin embargo, si la ejecuci贸n del Job el d铆a 26 de Marzo fallo por alguna raz贸n y se intenta re-ejecutar este mismo Job del 26 de Marzo al siguiente d铆a (que seria 27) sigue siendo el mismo JobInstance. Cada JobInstance puede tener m煤ltiples intentos de ejecuci贸n (JobExecution), el JobInstance se considera terminado cuando alguno de sus intentos de ejecuci贸n tuvo 茅xito.
  • JoParameters: Son los par谩metros que un Job recibe para poder realizar su trabajo. Un JobInstance se distingue de otro por los par谩metros que recibe. El JobInstance que se ejecuta el 25 de Marzo se distingue del 26 de Marzo porque el primero recibi贸 como par谩metro que su d铆a de ejecuci贸n es 25/03/2009 y el otro recibi贸 como par谩metro 26/03/2009. Los par谩metros pueden ser usados para identificar Jobs o para poder hacer el filtro del conjunto de datos que procesar谩 el Job.
  • JobExecution: Es el intento de ejecuci贸n de un JobInstance, terminado con exito o no. Como se menciono antes el JobInstace solo se considera terminado cuando alguno de sus JobExecution ha terminado con 茅xito. Un JobExecution es el mecanismo primario de almacenamiento de lo que ha pasado durante la ejecuci贸n. Un JobExecution tiene las siguientes propiedades:
  1. status: Indica el estatus de la ejecucion. Es un objeto BatchStatus.
  2. startTime: Una fecha java.util.Date que representa el momento en que se inicio la ejecuci贸n.
  3. endTime: Una fecha java.util.Date que representa el momento en que termino la ejecuci贸n.
  4. createTime: Una fecha java.util.Date que representa el momento en que el JobExecution fue por primera vez persistido.
  5. lastUpdate: Una fecha java.util.Date que representa el momento en que el JobExecution fue por ultima vez persistido.
  6. executionContext: Guarda los valores que mete el usuario para que sean persistidos entre las ejecuciones.
  7. failureExceptions: Una lista de excepciones que ocurrieron durante la ejecucion de un Job. De esta forma se mantiene registro de las diversas excepciones que ocurrieron al ejecutar un Job.
  • Step: Un Job esta compuesto de uno o mas Step. Un Step es un objeto de dominio que encapsula de manera independiente una de toda la secuencia de fases de un Job. Un Step contiene toda la informaci贸n necesaria que define y controla el actual procesamiento de un proceso batch. Un Step podr铆a tomar la informaci贸n de un archivo y dispersarla en la base de datos o podr铆a tener l贸gica mas compleja relacionada al negocio en cuanto a validaci贸n de datos, etc.
  • StepExecution: De la misma forma que un JobInstance tiene JobExecution un Step tiene StepExecution. Un StepExecution representa un intento de ejecutar un Step. Una nueva instancia de StepExecution es creada cada que se intenta ejecutar un Step, sin embargo, a diferencia de un JobExecution si un Step falla porque un Step anterior fallo no se persistira su ejecucion. Un StepExecution tiene las siguientes propiedades:
  1. status: Indica el estatus de la ejecucion. Es un objeto BatchStatus.
  2. startTime: Una fecha java.util.Date que representa el momento en que se inicio la ejecuci贸n.
  3. endTime: Una fecha java.util.Date que representa el momento en que termino la ejecuci贸n.
  4. exitStatus: Un valor ExitStatus indicando el resultado de la ejecucion.
  5. executionContext: Guarda los valores que mete el usuario para que sean persistidos entre las ejecuciones.
  6. readCount: Un contador indicando el numero de elementos que fueron exitosamente leidos.
  7. writeCount: Un contador indicando el numero de elementos que fueron exitosamente escritos.
  8. commitCount: Un contador indicando el numero de transacciones que fueron exitosas.
  9. rollbackCount: Un contador indicando el numero de veces que las transacciones controladas por el Step fueron desechas.
  10. readSkipCount: Numero de veces que la lectura fallo resultando en un elemento ignorado.
  11. processSkipCount: Numero de veces que el proceso fallo resultando en un elemento ignorado.
  12. filterCount: Numero de elementos que fueron filtrados por el ItemProcessor.
  13. writeSkipCount: Numero de veces que la escritura fallo, resultando en un elemento ignorado.
  • ExecutionContext: Un ExecutionContext es una coleccion de elementos llave/valor que son persistidos y controlados por el propio framework que permiten al desarrollador almacenar valores que son persistidos y que estan dentro del alcance de un StepExecution o un JobExecution. El mejor uso de un ExecutionContext es almacenar valores que permiten el reinicio de procesos desde un punto en particular.
  • JobRepository: El JobRepository es el mecanismo de persistencia que usan los ExecutionContext para los StepExecution y JobExecution. Contiene operaciones CRUD (Create, Read, Update, Delete; por sus siglas en ingles) que son utilizadas por las implementaciones de JobLauncher, Job y Step.
  • JobLauncher: Un JobLauncher representa el mecanismo para ejecutar un Job con ciertos JobParameters.
  • ItemReader: Un ItemReader es una abstracci贸n que representa el obtener datos de entrada para un Step, un elemento (item) a la vez. Cuando el ItemReader a acabado de obtener los elementos indica que ya no hay mas elementos que tomar regresando null.
  • ItemWriter: Un ItemWriter es una abstracci贸n que representa la salida de un Step, un elemento a la vez.
  • ItemProcessor: Es una abstracci贸n que representa el procesamiento de un elemento. Mientras el ItemReader lee un elemento y el ItemWriter lo escribe, el ItemProcessor aplica transformaciones u otras reglas. De esta forma, si el ItemProcessor determina que un elemento no es valido regresa null para indicar que el elemento no debe ser escrito.
Relaci贸n entre Job, Step y sus elementos.


Creo que por ahora esto sera suficiente como introduccion a los conceptos de Spring Batch, en futuras entradas de este blog ire hablando mas del tutorial del File Converter.

Este tutorial y los conceptos que aqui se explican se basaron en la guia de usuario de Spring Batch que la encuentras en esta pagina:

http://static.springframework.org/spring-batch/

Les recomiendo que bajen el archivo zip de la liberacion de Spring Batch para que vean los ejemplos:

http://static.springframework.org/spring-batch/downloads.html