En este post examinaremos las bondades que nos ofrece el producto de spring framework, spring batch.
Spring batch es un marco de trabajo ligero para el desarrollo de aplicaciones batch robustas que ayudan en las operaciones diarias de aplicaciones empresariales. Por lo general se usa para llevar a cabo operaciones que son muy largas y muy pesadas, como por ejemplo: procesar un “batch” de información para generar un reporte, o transformar esta información a otro formato.
Para utilizar spring batch es necesario tener en cuenta que las operaciones que realiza no deben requerir intervención humana, y por lo general tienen 3 fases: leer, procesar, escribir.
Como siempre comenzamos por definir las dependencias de nuestro proyecto con maven:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.ricardogeek</groupId> <artifactId>ejemplo-spring-batch</artifactId> <packaging>jar</packaging> <version>1.0-SNAPSHOT</version> <name>ejemplo-spring-batch</name> <url>http://maven.apache.org</url> <properties> <jdk.version>1.6</jdk.version> <spring.version>3.2.2.RELEASE</spring.version> <spring.batch.version>2.2.0.RELEASE</spring.batch.version> <mysql.driver.version>5.1.25</mysql.driver.version> </properties> <dependencies> <!-- Spring Core --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${spring.version}</version> </dependency> <!-- Spring jdbc, for database --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>${spring.version}</version> </dependency> <!-- Spring Batch dependencies --> <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> <!-- MySQL database driver --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${mysql.driver.version}</version> </dependency> </dependencies> <build> <finalName>spring-batch</finalName> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-eclipse-plugin</artifactId> <version>2.9</version> <configuration> <downloadSources>true</downloadSources> <downloadJavadocs>false</downloadJavadocs> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>2.3.2</version> <configuration> <source>${jdk.version}</source> <target>${jdk.version}</target> </configuration> </plugin> </plugins> </build> </project>
Ahora vamos a definir un archivo de valores separados por coma (CSV), que será la información que vamos a importar a nuestra base de datos.
Fecha,Impresiones,Clicks,Ganancias 6/1/13,"139,237",37,227.21 6/2/13,"149,582",55,234.71 6/3/13,"457,425",132,211.48 6/4/13,"466,870",141,298.40 6/5/13,"472,385",194,281.35
Luego tenemos que definir un @Bean que actuará como dataSource para conectarse a la base de datos, en nuestra estructura de archivos del proyecto creamos este archivo en: resources/spring/batch/config/database.xml
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.2.xsd"> <!-- conectarse a la base de datos MySQL --> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://localhost:3306/test" /> <property name="username" value="root" /> <property name="password" value="r00tS3cur3" /> </bean> <bean id="transactionManager" class="org.springframework.batch.support.transaction.ResourcelessTransactionManager" /> <!-- crear un job-meta automaticamente --> <jdbc:initialize-database data-source="dataSource"> <jdbc:script location="org/springframework/batch/core/schema-drop-mysql.sql" /> <jdbc:script location="org/springframework/batch/core/schema-mysql.sql" /> </jdbc:initialize-database> </beans>
Luego vamos a configurar nuestro jobRepository y nuestro jobLauncher creando el archivo: resources/spring/batch/config/context.xml
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd"> <bean id="jobRepository" class="org.springframework.batch.core.repository.support.JobRepositoryFactoryBean"> <property name="dataSource" ref="dataSource" /> <property name="transactionManager" ref="transactionManager" /> <property name="databaseType" value="mysql" /> </bean> <bean id="jobLauncher" class="org.springframework.batch.core.launch.support.SimpleJobLauncher"> <property name="jobRepository" ref="jobRepository" /> </bean> </beans>
Ahora vamos a crear un xml más para mapear los campos del archivo CSV a los campos en la tabla de la base de datos que queremos, para eso creamos un archivo en: resources/spring/batch/jobs/job-report.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:batch="http://www.springframework.org/schema/batch" xmlns:task="http://www.springframework.org/schema/task" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/batch http://www.springframework.org/schema/batch/spring-batch-2.2.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd"> <bean id="report" class="com.mkyong.model.Report" scope="prototype" /> <batch:job id="reportJob"> <batch:step id="step1"> <batch:tasklet> <batch:chunk reader="cvsFileItemReader" writer="mysqlItemWriter" commit-interval="2" /> </batch:tasklet> </batch:step> </batch:job> <bean id="cvsFileItemReader" class="org.springframework.batch.item.file.FlatFileItemReader"> <!-- Read a csv file --> <property name="resource" value="classpath:cvs/report.csv" /> <property name="lineMapper"> <bean class="org.springframework.batch.item.file.mapping.DefaultLineMapper"> <!-- split it --> <property name="lineTokenizer"> <bean class="org.springframework.batch.item.file.transform.DelimitedLineTokenizer"> <property name="names" value="fecha,impresiones,clicks,ganancias" /> </bean> </property> <property name="fieldSetMapper"> <!-- return back to reader, rather than a mapped object. --> <!-- <bean class="org.springframework.batch.item.file.mapping.PassThroughFieldSetMapper" /> --> <!-- map to an object --> <bean class="org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper"> <property name="prototypeBeanName" value="report" /> </bean> </property> </bean> </property> </bean> <bean id="mysqlItemWriter" class="org.springframework.batch.item.database.JdbcBatchItemWriter"> <property name="dataSource" ref="dataSource" /> <property name="sql"> <value><![CDATA[insert into RAW_REPORT(FECHA,IMPRESIONES,CLICKS,GANANCIAS) values (:fecha, :impresiones, :clicks, :ganancias)]]></value> </property> <!-- It will take care matching between object property and sql name parameter --> <property name="itemSqlParameterSourceProvider"> <bean class="org.springframework.batch.item.database.BeanPropertyItemSqlParameterSourceProvider" /> </property> </bean> </beans>
Ahora creamos el modelo de datos para nuestra tabla, recuerden que java es un lenguaje orientado a objetos y todo debe ser mapeado primero a un objeto.
package com.ricardogeek.model; public class Reporte { private String fecha; private String impresiones; private String clicks; private String ganancias; public String getFecha() { return fecha; } public void setFecha(String fecha) { this.fecha = fecha; } public String getImpresiones() { return impresiones; } public void setImpresiones(String impresiones) { this.impresiones = impresiones; } public String getClicks() { return clicks; } public void setClicks(String clicks) { this.clicks = clicks; } public String getGanancias() { return ganancias; } public void setGanancias(String ganancias) { this.ganancias = ganancias; } }
Ahora solo hace falta correr el job con todas las configuraciones anteriormente hechas, en una clase principal que convenientemente llamaremos “App”
package com.ricardogeek; import org.springframework.batch.core.Job; import org.springframework.batch.core.JobExecution; import org.springframework.batch.core.JobParameters; import org.springframework.batch.core.launch.JobLauncher; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class App { public static void main(String[] args) { String[] springConfig = { "spring/batch/config/database.xml", "spring/batch/config/context.xml", "spring/batch/jobs/job-report.xml" }; ApplicationContext context = new ClassPathXmlApplicationContext(springConfig); JobLauncher jobLauncher = (JobLauncher) context.getBean("jobLauncher"); Job job = (Job) context.getBean("reportJob"); try { JobExecution execution = jobLauncher.run(job, new JobParameters()); } catch (Exception e) { e.printStackTrace(); } System.out.println("Terminado"); } }