Descripción del problema
En esta oportunidad explicaré cómo leer un fichero de texto de 'n' cantidad de registros y dividirlo en varios ficheros, según un criterio en particular, usando la herramienta Spring-Batch. No voy a entrar en mucho detalles, simplemente a modo general para ver la solución.Spring Batch es un modulo perteneciente al Framework de "Spring" diseñado para dar soporte a las operaciones por lotes (batch) de nuestras aplicaciones.
Solución del problema
Trabajaremos básicamente con los siguientes elementos:- ItemReader: Elemento responsable de leer datos de una fuente de datos, en nuestro caso un fichero
- ItemWriter: Elemento responsable guardar la información leída por el reader o tratada por el processor y almacenarla en una fuente de datos, en nuestro caso dos ficheros.
- ClassifierCompositeItemWriter: Elemento responsable de enrutar la salida (el writer) dependiendo de un clasificador.
Necesitamos crear dos clases, adicionales:
- Item: Sera el elemento que almacene una linea de nuestro fichero.
- ItemClassifer : Elemento responsable de indicar cual es el elemento que clasificaremos
Clases Adicionales
Se necesita una clase de manera de indicar que campo de una linea del registro queremos que discrimine la salida hacia un fichero o hacia otro.
public class Item implements Serializable { /** type to classify **/ private String type; private String inittext; private String endtext; public String getType() { return type; } public void setType(String type) { this.type = type; } public String getInittext() { return inittext; } public void setInittext(String inittext) { this.inittext = inittext; } public String getEndtext() { return endtext; } public void setEndtext(String endtext) { this.endtext = endtext; } public String getText() { return inittext + type + endtext; } }
import org.springframework.classify.annotation.Classifier; public class ItemClassifer { @Classifier public String classify(Item item) { return item.getType(); } }
Ahora configuramos el spring-batch. Debemos tener en cuenta los siguientes elementos:
- batch:streams: Indicará que necesitamos dos escritores: FileItemWriter1 y FileItemWriter2. Si este elemento no existe, dará un error al intentar escribir el fichero de salida.
- FormatterLineAggregator: Indicará el formato en que se escribirá la linea en cada fichero
- ClassifierCompositeItemWriter: Indicará cómo se encargará de clasificar la salida.
- property name="matcherMap: Será quien discrimine el elemento clasificatorio según el patrón.
Ejemplo
Observamos que el fichero file1.dat vienen varios registros (lineas) con varias marcas por ejemplo: FORDSIERRA, PSA208, PSA308, etc. Adicionalmente, para dar un poco de complejidad, queremos que los PSA se almacenen en el mismo fichero. Para ello, usamos * dado que el campo de clasificación devolverá PSA2, PSA3.
MADRID-ES-FORDSIERRA 8090121231 ASADA MADRID-ES-PSA208 113141128912 ASADA MADRID-ES-PSA308 102131341213 ASADA MADRID-ES-FORDSIERRA 7654121231 ASADA
Fichero file2.dat
MADRID-ES-FORDSIERRA 8090121231 ASADA MADRID-ES-FORDSIERRA 7654121231 ASADA
Fichero file3.dat
MADRID-ES-PSA208 113141128912 ASADA MADRID-ES-PSA308 102131341213 ASADA
Configuración del spring-batch
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 | < beans xmlns:batch = "http://www.springframework.org/schema/batch" xmlns:seed = "http://seed.inetpsa.com/schema/spring-support" xmlns:task = "http://www.springframework.org/schema/task" xmlns:util = "http://www.springframework.org/schema/util" xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance" xmlns = "http://www.springframework.org/schema/beans" 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 http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.2.xsd" > < import resource = "classpath:META-INF/spring/generique/global-definition.xml" > < batch:job id = "splitFilesJob" > < batch:step id = "readWrite" > < tasklet transaction-manager = "transactionManager" > < batch:chunk commit-interval = "1" reader = "splitFileReader" writer = "customFilesWriting" > < batch:streams > < batch:stream ref = "FileItemWriter1" > < batch:stream ref = "FileItemWriter2" > </ batch:stream ></ batch:stream ></ batch:streams > </ batch:chunk > </ tasklet > </ batch:step > </ batch:job > <!-- File Reader to split --> < bean class = "org.springframework.batch.item.file.FlatFileItemReader" id = "splitFileReader" scope = "step" > < property name = "resource" value = "#{ jobParameters[ inputFile ] }" > < property name = "lineMapper" > < bean class = "org.springframework.batch.item.file.mapping.DefaultLineMapper" > < property name = "lineTokenizer" > < bean class = "org.springframework.batch.item.file.transform.FixedLengthTokenizer" > < property name = "strict" value = "false" > < property name = "names" value = "inittext,type,endtext" > < property name = "columns" value = "1-31,32-35,36" > </ property ></ property ></ property ></ bean > </ property > < property name = "fieldSetMapper" > < bean class = "org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper" > < property name = "prototypeBeanName" value = "itemSplitFile" > </ property ></ bean > </ property > </ bean > </ property > </ property ></ bean > <!-- Item File - one record (line) of the file --> < bean class = "org.ejemplo.Item" id = "itemSplitFile" scope = "prototype" > <!-- File Writer: Routing data to multiple files in item writer based on item's property as criteria --> < bean class = "org.springframework.batch.item.support.ClassifierCompositeItemWriter" id = "customFilesWriting" > < property name = "classifier" > < bean class = "org.springframework.classify.BackToBackPatternClassifier" > < property name = "routerDelegate" > < bean class = "org.ejemplo.ItemClassifer" > </ bean ></ property > < property name = "matcherMap" > < map > < entry key = "PROG" value-ref = "FileItemWriter1" > < entry key = "PTF*" value-ref = "FileItemWriter2" > </ entry ></ entry ></ map > </ property > </ bean > </ property > </ bean > < bean class = "org.springframework.batch.item.file.transform.FormatterLineAggregator" id = "itemSplitFileAggregator" > < property name = "fieldExtractor" > < bean class = "org.springframework.batch.item.file.transform.BeanWrapperFieldExtractor" > < property name = "names" value = "text" > </ property ></ bean > </ property > < property name = "format" value = "%s" > </ property ></ bean > < bean class = "org.springframework.batch.item.file.FlatFileItemWriter" id = "FileItemWriter1" scope = "step" > < property name = "lineAggregator" ref = "itemSplitFileAggregator" > < property name = "resource" value = "#{ jobParameters[ outputFile1 ] }" > < property name = "shouldDeleteIfExists" value = "true" > </ property ></ property ></ property ></ bean > < bean class = "org.springframework.batch.item.file.FlatFileItemWriter" id = "FileItemWriter2" scope = "step" > < property name = "lineAggregator" ref = "itemSplitFileAggregator" > < property name = "resource" value = "#{ jobParameters[ outputFile2 ] }" > < property name = "shouldDeleteIfExists" value = "true" > </ property ></ property ></ property ></ bean > </ bean ></ import ></ beans > |