Skip to content

Commit

Permalink
Added use case
Browse files Browse the repository at this point in the history
  • Loading branch information
armirzoya committed Jan 26, 2024
1 parent bed59e3 commit bd2a98a
Show file tree
Hide file tree
Showing 15 changed files with 748 additions and 141 deletions.
42 changes: 35 additions & 7 deletions docs/appendix/springbatch/clean.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,27 @@
Ya tenemos todo configurado de los pasos anteriores asi que proseguimos con el último ejemplo.


## Código
## Caso de Uso

Este es un caso de uso nuevo para poner en práctica el uso de `Tasklet`.

### ¿Qué vamos a hacer?

Vamos a implementar un batch que limpie de ficheros un determinado directorio. Esta vez y dado que no necesitamos realizar ningún tipo de lectura ni trasformación ni escritura y queremos hacerlo todo al mismo tiempo, es buen momento para utilizar un `Tasklet`.


### ¿Cómo lo vamos a hacer?

A diferencia de los casos anteriores seguiremos el esquema de funcionamiento de tasklet de un proceso batch que hemos visto en la parte de introducción:

Vamos a implementar un batch que limpie de ficheros un determinado directorio. Esta vez y dado que no necesitamos realizar ningún tipo de lectura ni trasformación ni escritura y queremos hacerlo todo al mismo tiempo, es buen momento para utilizar un `Tasklet`.
![clean](../../assets/images/clean.png)

* **Tasklet**: Eliminará todos los ficheros del directorio.
* **Step**: El paso que contiene el tasklet que van a realizar la funcionalidad.
* **Job**: La tarea que contiene los pasos definidos.


## Código

### Tasklet

Expand Down Expand Up @@ -62,8 +80,10 @@ Posteriormente, como en el caso anterior, emplazamos la configuración junto al
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.job.builder.JobBuilder;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.step.builder.StepBuilder;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.FileSystemResource;
Expand All @@ -73,7 +93,7 @@ Posteriormente, como en el caso anterior, emplazamos la configuración junto al
public class CleanBatchConfiguration {

@Bean
public CleanTasklet cleanTasklet() {
public Tasklet taskletClean() {
CleanTasklet tasklet = new CleanTasklet();

tasklet.setDirectoryResource(new FileSystemResource("target/test-outputs"));
Expand All @@ -82,23 +102,24 @@ Posteriormente, como en el caso anterior, emplazamos la configuración junto al
}

@Bean
public Step step1Clean(JobRepository jobRepository, PlatformTransactionManager transactionManager) {
public Step step1Clean(JobRepository jobRepository, PlatformTransactionManager transactionManager, Tasklet taskletClean) {
return new StepBuilder("step1Clean", jobRepository)
.tasklet(cleanTasklet(), transactionManager)
.tasklet(taskletClean, transactionManager)
.build();
}

@Bean
public Job jobClean(JobRepository jobRepository, Step step1Clean) {
return new JobBuilder("jobClean", jobRepository)
.incrementer(new RunIdIncrementer())
.start(step1Clean)
.build();
}

}
```

* **CleanTasklet**: El bean del `Tasklet` que hemos creado anteriormente.
* **Tasklet**: El bean del `Tasklet` que hemos creado anteriormente.
* **Step**: La creación del `Step` se realiza mediante él `StepBuilder` al que únicamente le añadimos el `Tasklet` que se va a ejecutar de forma atómica.
* **Job**: Finalmente, debemos definir él `Job` que será lo que se ejecute al lanzar nuestro proceso. La creación se hace mediante el builder correspondiente como en los casos anteriores.

Expand All @@ -112,4 +133,11 @@ Como en el caso anterior pasamos como `VM option` la siguiente propiedad en el a
-Dspring.batch.job.name=jobClean
```

Hecho esto y ejecutado el batch, podremos ver la traza de la ejecución en nuestro `log` y que el fichero generado en el `target` del proyecto de la ejecución del batch de autores ya no está.
Hecho esto y ejecutado el batch, podremos ver la traza de la ejecución en nuestro `log` y que el fichero generado en el `target` del proyecto de la ejecución del batch de autores ya no está.

```
Job: [SimpleJob: [name=jobClean]] launched with the following parameters: [{'run.id':'{value=1, type=class java.lang.Long, identifying=true}'}]
Executing step: [step1Clean]
Step: [step1Clean] executed in 9ms
Job: [SimpleJob: [name=jobClean]] completed with the following parameters: [{'run.id':'{value=1, type=class java.lang.Long, identifying=true}'}] and the following status: [COMPLETED] in 23ms
```
130 changes: 112 additions & 18 deletions docs/appendix/springbatch/filetodb.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,30 @@ Esto nos generará un proyecto que ya vendrá configurado con Spring Batch y H2
Esta parte de tutorial es una ampliación de la parte de backend con Spring Boot, por tanto, no se ve a enfocar en las partes básicas aprendidas previamente, sino que se va a explicar el funcionamiento de los procesos batch.


## Casos de Uso
## Caso de Uso

En este ejemplo no podemos seguir los mismos casos de uso que de los ejemplos anteriores, ya que sus requisitos no son válidos para implementarse como un proceso batch por lo que vamos a mantener las mismas entidades pero imaginar casos de uso diferentes.
En este ejemplo no podemos seguir los mismos casos de uso que de los ejemplos del tutorial de `Spring Boot`, ya que sus requisitos no son válidos para implementarse como un proceso batch por lo que vamos a mantener las mismas entidades pero imaginar casos de uso diferentes.


## Código
### ¿Qué vamos a hacer?

Vamos a implementar un batch para leer un fichero de `Categorias` e insertar los registros leídos en Base de Datos.


### ¿Cómo lo vamos a hacer?

Seguiremos el esquema de funcionamiento habitual de un proceso batch que hemos visto en la parte de introducción:

![filetodb](../../assets/images/filetodb.png)

* **ItemReader**: Se va a leer de un fichero y convertir los registros leídos al modelo de `Category`.
* **ItemProcessor**: Va a procesar todos los registros convirtiendo los textos a mayúsculas.
* **ItemWriter**: Va a insertar los registros en la BBDD.
* **Step**: El paso que contiene los elementos que van a realizar la funcionalidad.
* **Job**: La tarea que contiene los pasos definidos.


Vamos a implementar un batch para leer un fichero de `Categorias` e insertar los registros leídos en Base de Datos.
## Código

### Modelo

Expand Down Expand Up @@ -86,6 +102,38 @@ En primer lugar, vamos a crear el modelo dentro del package `com.ccsw.tutorialba
}
```


### Reader

Ahora, emplazamos él `Reader` en la clase donde posteriormente añadiremos la configuración junto al resto de beans, dentro del package `com.ccsw.tutorialbatch.config`.

=== "CategoryBatchConfiguration.java"
``` Java
package com.ccsw.tutorialbatch.config;

...

@Configuration
public class CategoryBatchConfiguration {

@Bean
public ItemReader<Category> readerCategory() {
return new FlatFileItemReaderBuilder<Category>().name("categoryItemReader")
.resource(new ClassPathResource("category-list.csv"))
.delimited()
.names(new String[] { "name", "type", "characteristics" })
.fieldSetMapper(new BeanWrapperFieldSetMapper<>() {{
setTargetType(Category.class);
}})
.build();
}

}
```

Para la ingesta de datos vamos a hacer uso de `FlatFileItemReader` que nos proporciona Spring Batch. Como se puede observar se le proporciona el fichero a leer y el mapeo a la clase que deseamos. [Aquí](https://docs.spring.io/spring-batch/reference/readers-and-writers/item-reader-writer-implementations.html) el catálogo de Readers que proporciona `Spring Batch`.


### Processor

Posteriormente, emplazamos él `Processor` dentro del package `com.ccsw.tutorialbatch.processor`.
Expand Down Expand Up @@ -122,9 +170,40 @@ Hemos implementado un `Processor` personalizado, esta clase implementa `ItemProc
En nuestro caso, va a ser de `Category` a `Category` donde únicamente vamos a realizar una trasformación de pasar los datos leídos a mayúsculas, ya que el `Reader` que veremos más adelante ya nos habrá trasformado los datos del fichero al modelo deseado. Las trasformaciones en sí se especifican sobreescribiendo el método `process`.


### Reader, Writer, Step y Job
### Writer

Posteriormente, emplazamos la configuración junto al resto de beans dentro del package `com.ccsw.tutorialbatch.config`.
Posteriormente, añadimos el writer a la clase de configuración `CategoryBatchConfiguration` donde ya habíamos añadido `Reader`.

=== "CategoryBatchConfiguration.java"
``` Java
package com.ccsw.tutorialbatch.config;

...

@Configuration
public class CategoryBatchConfiguration {

...

@Bean
public ItemWriter<Category> writerCategory(DataSource dataSource) {
return new JdbcBatchItemWriterBuilder<Category>()
.itemSqlParameterSourceProvider(new BeanPropertyItemSqlParameterSourceProvider<>())
.sql("INSERT INTO category (name, type, characteristics) VALUES (:name, :type, :characteristics)")
.dataSource(dataSource)
.build();
}

}
```


Para la parte de escritura usaremos `JdbcBatchItemWriter` que nos ayuda a lanzar inserciones en la base de datos de forma sencilla. Él `DataSource` se inicializa automáticamente con la instancia de H2 que se carga al arrancar el Batch. [Aquí](https://docs.spring.io/spring-batch/reference/readers-and-writers/item-reader-writer-implementations.html) el catálogo de Writers que proporciona `Spring Batch`.


### Step y Job

Ahora ya podemos añadir la configuración del `Step` y del `Job` dentro de la clase de configuración. La clase completa debería quedar de esta forma:

=== "CategoryBatchConfiguration.java"
``` Java
Expand All @@ -140,10 +219,11 @@ Posteriormente, emplazamos la configuración junto al resto de beans dentro del
import org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.step.builder.StepBuilder;
import org.springframework.batch.item.ItemProcessor;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.item.database.BeanPropertyItemSqlParameterSourceProvider;
import org.springframework.batch.item.database.JdbcBatchItemWriter;
import org.springframework.batch.item.database.builder.JdbcBatchItemWriterBuilder;
import org.springframework.batch.item.file.FlatFileItemReader;
import org.springframework.batch.item.file.builder.FlatFileItemReaderBuilder;
import org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper;
import org.springframework.context.annotation.Bean;
Expand All @@ -157,7 +237,7 @@ Posteriormente, emplazamos la configuración junto al resto de beans dentro del
public class CategoryBatchConfiguration {

@Bean
public FlatFileItemReader<Category> readerCategory() {
public ItemReader<Category> readerCategory() {
return new FlatFileItemReaderBuilder<Category>().name("categoryItemReader")
.resource(new ClassPathResource("category-list.csv"))
.delimited()
Expand All @@ -169,13 +249,13 @@ Posteriormente, emplazamos la configuración junto al resto de beans dentro del
}

@Bean
public CategoryItemProcessor processorCategory() {
public ItemProcessor<Category, Category> processorCategory() {

return new CategoryItemProcessor();
}

@Bean
public JdbcBatchItemWriter<Category> writerCategory(DataSource dataSource) {
public ItemWriter<Category> writerCategory(DataSource dataSource) {
return new JdbcBatchItemWriterBuilder<Category>()
.itemSqlParameterSourceProvider(new BeanPropertyItemSqlParameterSourceProvider<>())
.sql("INSERT INTO category (name, type, characteristics) VALUES (:name, :type, :characteristics)")
Expand All @@ -184,11 +264,11 @@ Posteriormente, emplazamos la configuración junto al resto de beans dentro del
}

@Bean
public Step step1Category(JobRepository jobRepository, PlatformTransactionManager transactionManager, JdbcBatchItemWriter<Category> writerCategory) {
public Step step1Category(JobRepository jobRepository, PlatformTransactionManager transactionManager, ItemReader<Category> readerCategory, ItemProcessor<Category, Category> processorCategory, ItemWriter<Category> writerCategory) {
return new StepBuilder("step1Category", jobRepository)
.<Category, Category> chunk(10, transactionManager)
.reader(readerCategory())
.processor(processorCategory())
.reader(readerCategory)
.processor(processorCategory)
.writer(writerCategory)
.build();
}
Expand All @@ -206,9 +286,9 @@ Posteriormente, emplazamos la configuración junto al resto de beans dentro del
}
```

* **FlatFileItemReader**: Para la ingesta de datos vamos a hacer uso de este `Reader` que nos proporciona Spring Batch. Como se puede observar se le proporciona el fichero a leer y el mapeo a la clase que deseamos. [Aquí](https://docs.spring.io/spring-batch/reference/readers-and-writers/item-reader-writer-implementations.html) el catálogo de Readers que proporciona `Spring Batch`.
* **CategoryItemProcessor**: El bean del `Processor` que hemos creado anteriormente.
* **JdbcBatchItemWriter**: Este `Writer` nos ayuda a lanzar inserciones en la base de datos de forma sencilla. Él `DataSource` se inicializa automáticamente con la instancia de H2 que se carga al arrancar el Batch. [Aquí](https://docs.spring.io/spring-batch/reference/readers-and-writers/item-reader-writer-implementations.html) el catálogo de Writers que proporciona `Spring Batch`.
* **ItemReader**: El bean del `Reader` que hemos creado anteriormente.
* **ItemProcessor**: El bean del `Processor` que hemos creado anteriormente.
* **ItemWriter**: El bean del `Writer` que hemos creado anteriormente.
* **Step**: La creación del `Step` se realiza mediante él `StepBuilder` al que le definimos el tamaño del `chunk` que es el número de elementos procesados por lote y le asignamos los tres beans creados previamente. En este caso solo vamos a tener un único `Step` pero podríamos tener todos los que quisiéramos.
* **Job**: Finalmente, debemos definir él `Job` que será lo que se ejecute al lanzar nuestro proceso. La creación se hace mediante el builder correspondiente como en el caso anterior. Se asigna el identificador de `Job`, el conjunto de steps, en este caso solo tenemos uno y finalmente el listener que es opcional y se crea en el siguiente paso.

Expand Down Expand Up @@ -285,4 +365,18 @@ Finalmente, debemos crear el fichero de inicialización de base de datos con la

### Pruebas

Ahora si arrancamos la aplicación como cualquier aplicación `Spring Boot`, podremos observar la traza de la ejecución en nuestro `log` y comprobar que la ejecución ha sido correcta y los registros se han insertado.
Ahora si arrancamos la aplicación como cualquier aplicación `Spring Boot`, podremos observar la traza de la ejecución en nuestro `log` y comprobar que la ejecución ha sido correcta y los registros se han insertado.

```
Job: [FlowJob: [name=jobCategory]] launched with the following parameters: [{'run.id':'{value=1, type=class java.lang.Long, identifying=true}'}]
Executing step: [step1Category]
Converting ( Category [name=Eurogames, type=Mechanics, characteristics=Hard] ) into ( Category [name=EUROGAMES, type=MECHANICS, characteristics=HARD] )
Converting ( Category [name=Ameritrash, type=Thematic, characteristics=Mid] ) into ( Category [name=AMERITRASH, type=THEMATIC, characteristics=MID] )
Converting ( Category [name=Familiar, type=Fillers, characteristics=Easy] ) into ( Category [name=FAMILIAR, type=FILLERS, characteristics=EASY] )
Step: [step1Category] executed in 55ms
!!! JOB FINISHED! Time to verify the results
Found < Category [name=EUROGAMES, type=MECHANICS, characteristics=HARD] > in the database.
Found < Category [name=AMERITRASH, type=THEMATIC, characteristics=MID] > in the database.
Found < Category [name=FAMILIAR, type=FILLERS, characteristics=EASY] > in the database.
Job: [FlowJob: [name=jobCategory]] completed with the following parameters: [{'run.id':'{value=1, type=class java.lang.Long, identifying=true}'}] and the following status: [COMPLETED] in 73ms
```
Loading

0 comments on commit bd2a98a

Please sign in to comment.