Botte de printemps avec lot de printemps

1. Vue d'ensemble

Spring Batch est un framework puissant pour développer des applications batch robustes. Dans notre didacticiel précédent, nous avons présenté Spring Batch.

Dans ce didacticiel, nous allons développer le précédent et apprendre à configurer et à créer une application de base basée sur les lots à l'aide de Spring Boot.

2. Dépendances de Maven

Tout d'abord, ajoutons le spring-boot-starter-batch à notre pom.xml :

 org.springframework.boot spring-boot-starter-batch 2.4.0.RELEASE 

Nous ajouterons également la dépendance org.hsqldb , qui est également disponible depuis Maven Central:

 org.hsqldb hsqldb 2.5.1 runtime 

3. Définition d'une tâche Spring Batch simple

Nous allons créer un travail qui importe une liste de café à partir d'un fichier CSV, la transforme à l'aide d'un processeur personnalisé et stocke les résultats finaux dans une base de données en mémoire .

3.1. Commencer

Commençons par définir notre point d'entrée d'application:

@SpringBootApplication public class SpringBootBatchProcessingApplication { public static void main(String[] args) { SpringApplication.run(SpringBootBatchProcessingApplication.class, args); } }

Comme nous pouvons le voir, il s'agit d'une application Spring Boot standard. Comme nous voulons utiliser les valeurs de configuration par défaut lorsque cela est possible, nous allons utiliser un ensemble très léger de propriétés de configuration d'application.

Nous définirons ces propriétés dans notre fichier src / main / resources / application.properties :

file.input=coffee-list.csv

Cette propriété contient l'emplacement de notre liste de cafés d'entrée. Chaque ligne contient la marque, l'origine et certaines caractéristiques de notre café:

Blue Mountain,Jamaica,Fruity Lavazza,Colombia,Strong Folgers,America,Smokey

Comme nous allons le voir, il s'agit d'un fichier CSV plat, ce qui signifie que Spring peut le gérer sans aucune personnalisation particulière.

Ensuite, nous ajouterons un script SQL schema-all.sql pour créer notre table basse pour stocker les données:

DROP TABLE coffee IF EXISTS; CREATE TABLE coffee ( coffee_id BIGINT IDENTITY NOT NULL PRIMARY KEY, brand VARCHAR(20), origin VARCHAR(20), characteristics VARCHAR(30) );

De manière pratique, Spring Boot exécutera ce script automatiquement au démarrage .

3.2. Classe du domaine du café

Par la suite, nous aurons besoin d'une classe de domaine simple pour contenir nos articles de café:

public class Coffee { private String brand; private String origin; private String characteristics; public Coffee(String brand, String origin, String characteristics) { this.brand = brand; this.origin = origin; this.characteristics = characteristics; } // getters and setters }

Comme mentionné précédemment, notre objet Coffee contient trois propriétés:

  • Une marque
  • Une origine
  • Quelques caractéristiques supplémentaires

4. Configuration du travail

Passons maintenant au composant clé, notre configuration de travail. Nous allons procéder étape par étape, construire notre configuration et expliquer chaque partie en cours de route:

@Configuration @EnableBatchProcessing public class BatchConfiguration { @Autowired public JobBuilderFactory jobBuilderFactory; @Autowired public StepBuilderFactory stepBuilderFactory; @Value("${file.input}") private String fileInput; // ... }

Tout d'abord, nous commençons avec une classe Spring @Configuration standard . Ensuite, nous ajoutons une annotation @EnableBatchProcessing à notre classe. Notamment, cela nous donne accès à de nombreux beans utiles qui prennent en charge les tâches et nous épargnera beaucoup de travail.

De plus, l'utilisation de cette annotation nous donne également accès à deux usines utiles que nous utiliserons plus tard lors de la création de la configuration de notre travail et des étapes des travaux.

Pour la dernière partie de notre configuration initiale, nous incluons une référence à la propriété file.input que nous avons déclarée précédemment.

4.1. Un lecteur et un écrivain pour notre travail

Maintenant, nous pouvons continuer et définir un bean lecteur dans notre configuration:

@Bean public FlatFileItemReader reader() { return new FlatFileItemReaderBuilder().name("coffeeItemReader") .resource(new ClassPathResource(fileInput)) .delimited() .names(new String[] { "brand", "origin", "characteristics" }) .fieldSetMapper(new BeanWrapperFieldSetMapper() {{ setTargetType(Coffee.class); }}) .build(); }

En bref, notre bean lecteur défini ci-dessus recherche un fichier appelé coffee-list.csv et analyse chaque élément de ligne dans un objet Coffee .

De même, nous définissons un bean écrivain:

@Bean public JdbcBatchItemWriter writer(DataSource dataSource) { return new JdbcBatchItemWriterBuilder() .itemSqlParameterSourceProvider(new BeanPropertyItemSqlParameterSourceProvider()) .sql("INSERT INTO coffee (brand, origin, characteristics) VALUES (:brand, :origin, :characteristics)") .dataSource(dataSource) .build(); }

Cette fois-ci, nous incluons l'instruction SQL nécessaire pour insérer un seul élément de café dans notre base de données, en fonction des propriétés de grain Java de notre objet Coffee . Handily, le dataSource est automatiquement créé par l' annotation @EnableBatchProcessing .

4.2. Mettre notre travail ensemble

Enfin, nous devons ajouter les étapes et la configuration réelles du travail:

@Bean public Job importUserJob(JobCompletionNotificationListener listener, Step step1) { return jobBuilderFactory.get("importUserJob") .incrementer(new RunIdIncrementer()) .listener(listener) .flow(step1) .end() .build(); } @Bean public Step step1(JdbcBatchItemWriter writer) { return stepBuilderFactory.get("step1") . chunk(10) .reader(reader()) .processor(processor()) .writer(writer) .build(); } @Bean public CoffeeItemProcessor processor() { return new CoffeeItemProcessor(); }

Comme nous pouvons le voir, notre travail est relativement simple et consiste en une étape définie dans la méthode step1 .

Jetons un coup d'œil à ce que fait cette étape:

  • Tout d'abord, nous configurons notre étape pour qu'elle écrive jusqu'à dix enregistrements à la fois en utilisant la déclaration chunk (10)
  • Then, we read in the coffee data using our reader bean, which we set using the reader method
  • Next, we pass each of our coffee items to a custom processor where we apply some custom business logic
  • Finally, we write each coffee item to the database using the writer we saw previously

On the other hand, our importUserJob contains our job definition, which contains an id using the build-in RunIdIncrementer class. We also set a JobCompletionNotificationListener, which we use to get notified when the job completes.

To complete our job configuration, we list each step (though this job has only one step). We now have a perfectly configured job!

5. A Custom Coffee Processor

Let's take a look in detail at the custom processor we defined previously in our job configuration:

public class CoffeeItemProcessor implements ItemProcessor { private static final Logger LOGGER = LoggerFactory.getLogger(CoffeeItemProcessor.class); @Override public Coffee process(final Coffee coffee) throws Exception { String brand = coffee.getBrand().toUpperCase(); String origin = coffee.getOrigin().toUpperCase(); String chracteristics = coffee.getCharacteristics().toUpperCase(); Coffee transformedCoffee = new Coffee(brand, origin, chracteristics); LOGGER.info("Converting ( {} ) into ( {} )", coffee, transformedCoffee); return transformedCoffee; } }

Of particular interest, the ItemProcessor interface provides us with a mechanism to apply some specific business logic during our job execution.

To keep things simple, we define our CoffeeItemProcessor, which takes an input Coffee object and transforms each of the properties to uppercase.

6. Job Completion

Additionally, we're also going to write a JobCompletionNotificationListener to provide some feedback when our job finishes:

@Override public void afterJob(JobExecution jobExecution) { if (jobExecution.getStatus() == BatchStatus.COMPLETED) { LOGGER.info("!!! JOB FINISHED! Time to verify the results"); String query = "SELECT brand, origin, characteristics FROM coffee"; jdbcTemplate.query(query, (rs, row) -> new Coffee(rs.getString(1), rs.getString(2), rs.getString(3))) .forEach(coffee -> LOGGER.info("Found  in the database.", coffee)); } }

In the above example, we override the afterJob method and check the job completed successfully. Moreover, we run a trivial query to check that each coffee item was stored in the database successfully.

7. Running Our Job

Now that we have everything in place to run our job, here comes the fun part. Let's go ahead and run our job:

... 17:41:16.336 [main] INFO c.b.b.JobCompletionNotificationListener - !!! JOB FINISHED! Time to verify the results 17:41:16.336 [main] INFO c.b.b.JobCompletionNotificationListener - Found  in the database. 17:41:16.337 [main] INFO c.b.b.JobCompletionNotificationListener - Found  in the database. 17:41:16.337 [main] INFO c.b.b.JobCompletionNotificationListener - Found  in the database. ... 

As we can see, our job ran successfully, and each coffee item was stored in the database as expected.

8. Conclusion

In this article, we've learned how to create a simple Spring Batch job using Spring Boot. First, we started by defining some basic configuration.

Then, we saw how to add a file reader and database writer. Finally, we took a look at how to apply some custom processing and check our job was executed successfully.

Comme toujours, le code source complet de l'article est disponible à l'adresse over sur GitHub.