0% ont trouvé ce document utile (0 vote)
14 vues98 pages

Spring Batch Lab

Cet atelier guide la création d'une application Spring Batch pour générer des rapports de facturation pour une société de téléphonie mobile. L'application utilise Spring Boot et PostgreSQL, et comprend des étapes pour préparer un fichier, ingérer des données dans une base de données, et générer un rapport pour les clients ayant dépensé plus de 150 USD. Les participants apprendront à configurer un Job, gérer les erreurs, et tester le Job avec JUnit 5.

Transféré par

Ahmed Lahmami 2020
Copyright
© © All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats PDF, TXT ou lisez en ligne sur Scribd
0% ont trouvé ce document utile (0 vote)
14 vues98 pages

Spring Batch Lab

Cet atelier guide la création d'une application Spring Batch pour générer des rapports de facturation pour une société de téléphonie mobile. L'application utilise Spring Boot et PostgreSQL, et comprend des étapes pour préparer un fichier, ingérer des données dans une base de données, et générer un rapport pour les clients ayant dépensé plus de 150 USD. Les participants apprendront à configurer un Job, gérer les erreurs, et tester le Job avec JUnit 5.

Transféré par

Ahmed Lahmami 2020
Copyright
© © All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats PDF, TXT ou lisez en ligne sur Scribd

Spring Batch Lab

Dans cet atelier, vous allez créer une application de Spring Batch qui génère des
rapports de facturation pour une société de téléphonie mobile imaginaire.
L'application stocke les informations de facturation dans une base de données
relationnelle et génère un rapport de facturation. Cette application est basée sur
Spring Boot et utilise les fonctionnalités de Spring Batch pour créer un système
de traitement par lots robuste, re-démarrable et tolérant aux pannes.

Vous allez implémenter une Job nommée BillingJob qui est conçue comme suit :

Le Job de facturation est structuré selon les étapes suivantes :

• Étape de préparation du fichier : copie le fichier contenant l'utilisation


mensuelle des clients d'un serveur de fichiers vers une zone de
préparation.
• Étape d'ingestion de fichier : ingère le fichier dans une table de base de
données relationnelle qui contient les données utilisées pour générer le
rapport de facturation.
• Étape de génération de rapport : traite les informations de facturation de
la table de base de données et génère un fichier plat contenant des
données pour les clients qui ont dépensé plus de 150,00 USD.
Créer un projet avec Spring
Dans cet atelier, nous nous concentrons sur la création d'un projet Spring Batch
de base basé sur Spring Boot.

1. Suivez les étapes suivantes pour utiliser Spring Boot afin de configurer
l'application « Demo » de traitement par lots.
a. Ouvrez l'onglet du tableau de bord intitulé Spring Initializr.
b. Sélectionnez les options suivantes :

c. Cliquez sur Next


d. Ajoutez les dépendances suivantes :
e. Cliquez sur Create

Dans ce cours, nous utilisons PostgreSQL comme base de données pour le


JobRepository. Vous devrez s’assurer qu’un un serveur PostgreSQL est toujours
opérationnel dans un conteneur Docker.

Préparer le projet pour le Spring Batch


Dans cette section, nous préparons le projet pour utiliser Spring Batch.
Configuration Java

Pour un projet Spring Batch correctement structuré, nous ne devons mettre


aucune configuration de Spring Batch dans la classe principale de l'application.
La configuration de Spring Batch doit être définie dans une classe distincte.

2. Créez un nouveau fichier nommé JobConfiguration.java avec le contenu


suivant :

Cette classe est un espace réservé pour les beans liés à Spring Batch (Jobs, Steps,
etc.).

Configuration de la base de données

3. Préparez la base de données avec les tables de métadonnées de Spring


Batch
Dans cette section, vous allez démarrer le serveur Postgre et créer les
tables de métadonnées de Spring Batch dans la base de données. C’est la
seule fois où vous devrez le faire à des fins d’apprentissage. Ces tableaux
seront préchargés dans la base de données dans tous les étables
ultérieures du cours.
a. Démarrer Docker Desktop
b. Ouvrez un onglet de terminal et exécutez la commande suivante :

Ici aucun conteneur n’est en cours d’exécution

Vous devriez voir un certain nombre d'informations sur le conteneur Docker


exécutant PostgreSQL.

Nous allons maintenant nous connecter à la base de données et créer les tables.
Pour cela, utilisez la commande suivante :

Vous êtes maintenant connecté à la base de données avec l'outil de ligne de


commande psql. Cet outil est l'interface de ligne de commande officielle de
PostgreSQL. Il est maintenant temps de créer les tables de métadonnées.
c. Copiez le script SQL suivant, collez-le dans le terminal et appuyez sur
Entrée :
d. Enfin, vérifiez que toutes les tables ont été correctement créées à
l'aide de la commande \d :
4. Configurer les propriétés de la base de données dans Spring Boot
a. Ouvrez le fichier application.properties et ajoutez les propriétés
suivantes :

Une fois ces propriétés mise en place, Spring Boot configurera automatiquement
un bean JDBC DataSource dans le contexte d'application que nous pouvons
utiliser dans notre application Spring Batch.

Vérifier la configuration du projet

Maintenant que la structure de l'application est en place, vérifions si l'application


est correctement configurée et fonctionne comme prévu.

5. Démarrez votre projet et vérifiez qu’il se termine sans exception


Mettez en œuvre votre premier Job
Un Job par lots est une entité qui encapsule l'intégralité d'un processus par lots.
Dans cette partie, vous apprendrez à implémenter, exécuter et tester un Job par
lots dans une application basée sur Spring Batch et Spring Boot.

Nous allons créer une implémentation de Job qui imprime les informations de
facturation du traitement sur la console. Vous n'avez presque jamais besoin
d'implémenter l'interface Job vous-même, car Spring Batch fournit quelques
implémentations prêtes à l'emploi.

Plus loin dans le tutorial, nous créons toutes les étapes qui définissent notre Job
en utilisant l'une de ces implémentations. Pour l'instant, afin de comprendre les
responsabilités d'un Spring Batch Job, nous implémentons l'interface du Job
comme suit :

Créez la classe BillingJob.

6. Créez un nouveau fichier nommé BillingJob.java et mettez à jour son


contenu avec le code suivant :
Nous venons de créer une implémentation vide de l'interface du Job que nous
devons mettre à jour avec la logique métier du Job.

Nommez le Job.

7. Donnez un nom au Job en mettant à jour la méthode getName() comme


suit :

Implémenter exécuter.

8. Implémentons maintenant la méthode d'exécution en imprimant un


message sur la sortie standard :

Super! Nous avons terminé notre implémentation du premier Job.

Configurer le Job Bean

Maintenant que nous avons implémenté notre premier Job, déclarons-le comme
bean dans le contexte de l'application afin que Spring Boot le détecte et l'exécute
au démarrage de notre application.

Spring utilise une classe pour la configuration basée sur Java en ajoutant
l'annotation @Configuration. Cette classe est utilisée comme source de
définitions de beans. Dans cette section, nous créons une classe de configuration
Spring qui contient notre définition de Job bean.
9. Mettez à jour le contenu du fichier JobConfiguration.java avec le code
suivant :

Nous sommes maintenant prêts à exécuter notre Job, alors exécutons-le et


voyons ce qui se passe !

Exécuter le Job

L'une des fonctionnalités de Spring Boot lorsqu'il s'agit de prendre en charge


Spring Batch est l'exécution automatique de tout Job bean défini dans le contexte
de l'application au démarrage de l'application. Ainsi, pour lancer le Job, il vous
suffit de démarrer l’application Spring Boot.

Spring Boot recherche notre Job dans le contexte de l'application et l'exécute à


l'aide du JobLauncher, qui est autoconfiguré et prêt à être utilisé.

10.Exécutez l'application.
Le Job s'est exécuté correctement et s'est terminé avec succès car nous voyons
le message de traitement des informations de facturation dans la sortie standard
comme prévu.

Comment pouvons-nous vérifier que le Job s'est exécuté avec succès ?

Faisons-le ensuite !

Vérifiez le Job

Vérifions l'état du Job dans la base de données.

11.Dans l'onglet Terminal, exécutez la commande suivante :

Spring Batch a correctement enregistré l'exécution du Job dans la base de


données, mais le statut du Job est STARTING et son code de sortie est
UNKNOWN. Comment est-ce possible alors que notre Job a été exécuté et
terminé avec succès ?

C'est là qu'intervient la responsabilité du Job de signaler son statut et son code


de sortie au JobRepository. Réparons ça.

Définissez les statuts et utilisez le JobRepository.

12.Ouvrez le fichier BillingJob.java et mettez à jour son contenu comme suit :


Dans cette étape, nous avons d'abord transmis une référence JobRepository en
tant que paramètre constructeur à notre BillingJob. Nous utiliserons le
JobRepository pour enregistrer des informations importantes sur notre Job.

Ensuite, nous avons mis à jour la méthode d'exécution pour définir le statut
d'exécution du Job ainsi que son statut de sortie :
Enfin, nous avons émis un JobRepository.update(execution) pour mettre à jour
l'exécution du Job dans la base de données.

Comprenez les mises à jour.

Ce que vous devez comprendre ici, c'est qu'il est de la responsabilité de


l'implémentation du Job de signaler son statut au JobRepository.

• L'état du Job batch indique l'état de l'exécution.


Par exemple, si le Job est en cours d'exécution, l'état du lot est
BatchStatus.STARTED. S'il échoue, c'est BatchStatus.FAILED, et s'il se
termine avec succès, c'est BatchStatus.COMPLETED.
• Puisque notre Job a été terminé avec succès, nous définirons également le
statut d'exécution sur BatchStatus.COMPLETED et le statut de sortie sur
ExitStatus.COMPLETED.

Maintenant que nous avons mis à jour notre implémentation de Job pour utiliser
un JobRepository pour signaler son statut, nous devons ajouter une référence au
JobRepository dans notre définition de Job bean.

Fournissez le JobRepository au Job.

13.Ouvrez le fichier JobConfiguration.java et mettez à jour son contenu


comme suit :
Grâce à Spring Boot, un JobRepository a été autoconfiguré avec la source de
données configurée pour notre base de données PostgreSQL.

Ce JobRepository est prêt à être utilisé en le connectant automatiquement dans


notre Job bean.

Nettoyez et réexécutez le Job.

Essayons maintenant de réexécuter le Job et de vérifier son statut dans la base


de données.

Mais avant de réexécuter le Job, nettoyons la base de données pour réduire le


bruit de l'exécution précédente.

14.Dans l'onglet Terminal, exécutez les commandes suivantes :


a. Connectez-vous à l’instance Postgres
b. Supprimer les tables en utilisant les instructions du fichier « Schema
Drop postgresql.sql »
c. Creez les tables ene utilisant les instructions du fichier « Schema
postgresql.sql »

Réexécutez maintenant le Job comme indiqué précédemment et vérifiez la base


de données. Le statut d'exécution ainsi que le statut de sortie devraient
maintenant être COMPLETED.

Si le statut du Job est COMPLETED, alors félicitations ! Vous avez créé, configuré
et exécuté avec succès votre premier Job Spring Batch !

Maintenant, la question est : que se passe-t-il si une erreur se produit lors de


l’exécution de la logique métier ?

Apprenons cela ensuite.


Gestion des erreurs

Comme pour le chemin de réussite, il est de la responsabilité du Job de gérer les


exceptions d'exécution et de signaler son échec au JobRepository.

Comme exercice supplémentaire, essayez de simuler une exception dans la


méthode d'exécution et de signaler un statut FAILED au référentiel.

Notez que la méthode d'exécution n'est pas censée lever des exceptions, et il est
de la responsabilité de l'implémentation du Job de les gérer et de les ajouter à
l'objet JobExecution pour une inspection ultérieure :

15.A vous de jouer

Solution de l’exercice

16.Essayez ceci :

17.Nettoyez la base de données comme la dernière fois et récréez les tables


de nouveau.
18.Réexécutez le Job avec la logique métier ayant échoué intentionnellement.

En plus du statut COMPLETED, vous devez également un exist_code du statut


FAILED dans la base de données ainsi que le message d'erreur dans la colonne
exist_message.
Remarque : assurez-vous de rétablir la méthode d'exécution à l'implémentation
réussie avant de continuer.

Testez le Job

Maintenant que la mise en œuvre de notre Job est terminée, nous pouvons écrire
un test pour celui-ci. Spring Batch fournit plusieurs utilitaires de test pour
simplifier le test des composants Batch. Nous les aborderons plus tard dans ce
tutoriel.

Pour cette partie, nous voyons comment tester un Job Spring Batch en utilisant
JUnit 5 et les utilitaires de test fournis par Spring Boot.

Mettez à jour les BillingJobApplicationTests.

19.Mettez à jour le contenu du fichier DemoApplicationTests.java avec le code


suivant :
Comprendre la structure des classes de test

Tout d’abord, nous annotons la classe de test avec @SpringBootTest. Cela permet
de tester les fonctionnalités de Spring Boot, qui comprennent le chargement du
contexte de l'application Spring, la préparation du contexte de test, etc.

Après cela, et dans le but de tester notre Job qui écrit la sortie sur la console,
nous utilisons OutputCaptureExtension fournie par Spring Boot pour capturer
toute sortie écrite sur la sortie standard et la sortie d'erreur.

Dans notre cas, nous avons besoin de cette capture de sortie pour vérifier si le
Job imprime correctement le message d'informations de facturation de
traitement sur la console.

Nous pouvons ensuite binder automatiquement le Job testé ainsi que


JobLauncher à partir du contexte de test.
Comprendre le cas de test

Maintenant que la classe de test est configurée, nous pouvons écrire la méthode
de test testJobExecution, conçue pour :

• Lancez le Job batch avec un ensemble de paramètres.


• Les paramètres sont vides pour l’instant, mais cela convient aux besoins
de cet atelier. Nous utiliserons les paramètres du Job plus tard dans le
cours.

• Vérifiez que la sortie du Job contient le message attendu et que son statut
a été correctement mis à jour.

C'est tout ce dont nous avons besoin pour tester notre Job de facturation.

Exécutez le test.

20.Pour exécuter le test, cliquez sur l’icone

.
Le test devrait réussir, ce qui signifie que notre Job fait ce qu'il est censé faire.

Toutes nos félicitations ! Vous avez créé avec succès votre première tâche Spring
Batch avec une application basée sur Spring Boot.

Dans cette partie du tutoriel, le contenu a été soigneusement conçu afin que
vous puissiez implémenter l'interface Job directement à des fins d'apprentissage.

Vous n'aurez presque jamais à implémenter cette interface directement, car


Spring Batch fournit des implémentations prêtes à l'emploi telles que SimpleJob
pour les tâches simples basées sur des étapes séquentielles et FlowJob pour les
tâches qui nécessitent des flux d'exécution d'étapes complexes.

Dans la suite, vous utiliserez ces cours et apprendrez à structurer le flux de travail
de votre Job avec des étapes.
Utiliser les paramètres du Job
Dans cette partie du Lab, vous apprendrez à transmettre des paramètres à un Job
afin de créer des JobInstances.

Notre BillingJob devrait traiter les données de facturation mensuelles à partir


d’un fichier plat. Le fichier d'entrée sera utilisé comme paramètre d'identification
du Job nommé input.file. Par conséquent, nous aurions une JobInstance distincte
par mois.

Pour cela nous allons alimenter le dossier src/main/resources par deux fichiers
plats, billing-2023-01.csv et billing-2023-02.csv contenant respectivement les
données de facturation de janvier 2023 et février 2023. Vous pouvez explorer les
données de ces fichiers, mais le contenu n'est pas pertinent pour le moment.
Nous expliquerons le format de ces fichiers et quelles données ils représentent
plus tard. Pour l'instant, tout ce que vous devez comprendre, c'est que nous
allons transmettre ces fichiers en tant que paramètres à notre BillingJob pour
créer des JobInstances distinctes.

21.Copiez les deux fichiers dans le répertoire indiqué ci-dessous


22.Modifiez l'implémentation BillingJob pour obtenir le fichier d'entrée de
JobParameters.
Accédez au fichier BillingJob.java et mettez à jour la méthode d'exécution
avec le code suivant :

Vous devez également ajouter l'instruction d'importation suivante :


import org.springframework.batch.core.JobParameters;

Dans cette section, nous accédons à JobParameters à partir de la référence


JobExecution et extrayons le paramètre input.file. Nous avons ensuite mis à jour
le message que nous imprimons sur la sortie standard pour indiquer quel fichier
l'exécution en cours de traitement.

23.Lancez le BillingJob et transmettez le fichier d'entrée des données de


facturation en tant que JobParameter.
Dans l'onglet Terminal, exécutez la commande suivante pour créer le
projet

24.Ensuite, lancez le Job et passez le fichier d'entrée en paramètre avec la


commande suivante :

Vous devriez voir le message suivant dans la console :

Super! Cela signifie que notre Job est désormais capable d'obtenir le fichier
d'entrée de JobParameters et de traiter les données comme prévu. Vérifions
maintenant la base de données pour inspecter les premiers détails de
JobInstance.

25.Inspectez les métadonnées Batch dans la base de données.


Dans l'onglet Terminal, exécutez la commande suivante :
Vous devriez voir quelque chose comme le résultat suivant :

Nous avons une première JobInstance avec l'ID 1 pour le Job nommé BilingJob.
La colonne version est une colonne technique utilisée par Spring Batch pour le
verrouillage optimiste, et son utilisation sort du cadre de ce laboratoire. Le
job_key est un hachage des JobParameters d'identification calculés par Spring
Batch pour identifier les JobInstances.

26.Vérifions maintenant le JobExecution correspondant pour cette première


JobInstance. Pour cela, utilisez la commande suivante :

Vous devriez voir quelque chose comme le résultat suivant :

Comme nous le voyons, nous avons une première exécution qui correspond à la
première instance (via le job_instance_id) qui s'est terminée avec succès
(status=COMPLETED).

27.Enfin, vérifions le contenu du BATCH_JOB_EXECUTION_PARAMS avec la


commande suivante :

Vous devriez voir quelque chose comme le résultat suivant :


Comme prévu, la première exécution a reçu le paramètre de travail input.file avec
la valeur src/main/resources/billing-2023-01.csv.

Tout cela fonctionne à merveille ! Mais que se passe-t-il si nous réexécutons la


même JobInstance même si elle se termine avec succès ? Essayons-le et
découvrons-le.

réexécution d'une instance de Job

Comme nous l'avons vu dans le simple message que nous imprimons sur la
console, la première JobInstance correspondant au traitement du fichier billing-
2023-01.csv s'est terminée avec succès et le rapport de facturation a été généré
correctement.

Exécuter à nouveau la même JobInstance serait un gaspillage de ressources. Mais


que se passe-t-il si nous réexécutons accidentellement le même fichier ?

Comment cela pourrait-il arriver ? Parfois, la réexécution du même travail est due
à une erreur humaine. D'autres fois, un problème technique ou une limitation de
la plate-forme peut déclencher une nouvelle exécution. Quelle que soit la raison,
une JobInstance ne devrait pas entraîner de conséquences désastreuses si elle
est exécutée plus d'une fois. Personne ne veut être facturé deux fois pour son
utilisation de téléphone !

Alors essayons cela et voyons ce qui se passe !

28.Dans l'onglet Terminal initial, exécutez la commande suivante :


Vous devriez voir le message suivant dans la console :

Comme vous pouvez le constater, sans autre configuration, Spring Batch a


empêché la même JobInstance de s'exécuter une seconde fois. Ce choix de
conception par défaut résout les erreurs humaines et les limitations de la plate-
forme mentionnées précédemment.

Après cet échec de relance d'une JobInstance réussie, BATCH_JOB_EXECUTION


ne doit pas contenir une autre exécution, ce que vous pouvez vérifier avec la
commande suivante :

29.Dans l'onglet Terminal initial, exécutez la commande suivante :

Maintenant que nous sommes protégés contre la réexécution d'une JobInstance,


voyons ce qui se passe lorsque nous exécutons une deuxième JobInstance
différente.

Lancer une deuxième instance de Job

Dans cette section, nous lançons une deuxième JobInstance pour traiter
l'ensemble de données de facturation de février 2023, qui se trouve dans le
fichier src/main/resources/billing-2023-02.csv.

30.Lancez le BillingJob et transmettez le fichier billing-2023-02.csv en tant que


JobParameter.
Dans l'onglet Terminal, exécutez la commande suivante :
Vous devriez voir le message suivant dans la console :

Cela signifie que notre BillingJob a correctement traité les données de février
2023 et généré le rapport.

Vérifions maintenant la base de données pour inspecter les détails du deuxième


JobInstance.

31.Inspectez les métadonnées Batch dans la base de données.


Dans l'onglet Terminal, exécutez la commande suivante :

Vous devriez voir le message suivant dans la console :

Nous avons maintenant une deuxième JobInstance avec l'ID 2 pour le même
BilingJob. Notez à quel point le job_key est différent en raison des différents
JobParameter d'identification.

32.Vérifions maintenant le JobExecution correspondant pour cette deuxième


JobInstance. Pour cela, utilisez la commande suivante :

Vous devriez voir quelque chose comme le résultat suivant :


Comme prévu, un deuxième JobExecution pour ce deuxième JobInstance est
désormais présent dans le tableau.

33.Enfin, vérifions le contenu du BATCH_JOB_EXECUTION_PARAMS avec la


commande suivante :

Vous devriez voir quelque chose comme le résultat suivant :

Comme vous pouvez le constater, chaque exécution possède son propre


paramètre enregistré dans la table BATCH_JOB_EXECUTION_PARAMS.

Mettre à jour le test

Dans la section précédente, vous avez vu comment transmettre JobParameters


de manière déclarative sur l'interface de ligne de commande avec des paires
clé/valeur telles que input.file=src/main/resources/billing-2023-02.csv. Dans
cette section, nous mettrons à jour le test de notre BillingJob pour vous montrer
comment transmettre JobParameters par programme via les API fournies par
Spring Batch.

Par rapport à la version précédente de notre BillingJob, dans cet atelier, nous
avons mis à jour la logique de notre Job pour extraire le fichier d'entrée de
l'ensemble JobParameters et imprimer un message sur la console en
conséquence. Nous devrions mettre à jour la logique de test en conséquence.

34.Mettez à jour DemoApplicationTests pour tester la nouvelle logique.


Accédez au fichier DemoApplicationTests.java et mettez à jour la méthode
de test testJobExecution avec le code suivant :

Vous devez également ajouter l'instruction d'importation suivante :

import org.springframework.batch.core.JobParametersBuilder;

JobParametersBuilder est la principale API fournie par Spring Batch pour créer
un ensemble de JobParameters. Dans ce test, nous utilisons ce générateur pour
créer un paramètre de type String nommé input.file ayant la valeur
/some/input/file.

La valeur du paramètre n'a pas vraiment d'importance pour les besoins du test,
il suffit de vérifier que le Job reçoit la bonne valeur de paramètre et imprime le
message comme prévu. C’est ce que nous avons fait dans l’assertion suivante :

35.Exécutez le test.
Pour exécuter le test, ouvrez l'onglet Terminal et exécutez la commande
suivante :
Le test devrait réussir, ce qui signifie que notre Job par lots fait ce qu'il est censé
faire.

relancez les tests

Si vous exécutez le test une deuxième fois, il échouera avec une erreur similaire
à celle que nous avons vue pendant la leçon lors de l'exécution à nouveau de la
même JobInstance terminée :

En effet, la même base de données est utilisée pour toutes les exécutions de
tests. Bien que cela soit prévu en production, cela pourrait poser problème lors
des tests, car les versions de projet ne seront pas idempotentes.

Pour cette raison, Spring Batch fournit une classe utilitaire appelée
JobRepositoryTestUtils qui vous permet de nettoyer la base de données avant ou
après chaque test. C’est ce dont nous discuterons dans la suite.

Pour l'instant, vous devrez réinitialiser la base de données si vous rencontrez


cette erreur.

36.Aller plus loin avec JobParamters.

En plus de la possibilité de fournir le nom, le type et la valeur de JobParameters,


l'API JobParametersBuilder permet également de spécifier si le paramètre est
identifiant ou non via le troisième paramètre booléen de la méthode addString.
Voici un exemple :
Avec cet extrait, nous ajoutons un deuxième paramètre nommé file.format de
type String avec la valeur csv et qui n'est pas identifiant (en raison du troisième
paramètre de méthode false).

Notez que nous n'avons pas explicitement transmis true comme troisième
paramètre pour input.file car il s'agit de la valeur par défaut pour JobParameters
dans Spring Batch.

Comme exercice supplémentaire, vous pouvez essayer de lancer le BillingJob


avec un mélange de JobParameters identifiants et non identifiants et voir
comment cela impacte ou non la définition et l'identification des JobInstances.
Tester vos jobs
Importer les utilitaires de test dans la classe de test

L'annotation @SpringBatchTest enregistre les JobLauncherTestUtils et


JobRepositoryTestUtils en tant que beans Spring dans le contexte de test, afin
que nous puissions les liées automatiquement dans la classe de test et les utiliser
selon nos besoins.

37.Supprimer et recréer les tables de la base de données


38.Ajoutez l'annotation @SpringBatchTest.
Ouvrez le fichier DemoApplicationTests.java et ajoutez l'annotation
@SpringBatchTest sur la classe de test :

Vous devez également ajouter l'instruction d'importation correspondante :

import org.springframework.batch.test.context.SpringBatchTest ;

39.Binder automatique des utilitaires de test.


Mettez à jour la classe de test et ajoutez les utilitaires de test :
Vous devez également ajouter les instructions d'importation suivantes :

import org.springframework.batch.test.JobLauncherTestUtils ;

import org.springframework.batch.test.JobRepositoryTestUtils ;

Une fois ces utilitaires en place, nous pouvons les utiliser dans notre test Job.

Lancer le Job sous test

L'une des fonctionnalités de JobLauncherTestUtils est qu'il détecte


automatiquement le Job testé dans le contexte de l'application s'il est unique.
C'est le cas dans notre Lab, nous n'avons qu'un seul job qui est défini qui est le
BillingJob. Pour cette raison, nous pouvons supprimer auto-binding du job testé
de la classe de test.

Utilisez l'utilitaire pour lancer le travail.

Au lieu de binder automatiquement le Job testé et le JobLauncher pour le tester,


nous utilisons la méthode jobLauncherTestUtils.launchJob qui a le même effet.

40.Modifiez les lignes suivantes dans la classe test :


41.Supprimez les variables inutilisées.
Puisque nous venons de supprimer la seule utilisation de job et
jobLauncher, nous pouvons également supprimer leurs déclarations.

42.Exécutez le test.
Faites un clic droit sur DemoApplicationTests.java et sélectionnez
"Exécuter Java".

Le test devrait réussir, ce qui signifie que nous avons le même résultat qu’avant,
mais avec moins de code !
Maintenant, si vous exécutez à nouveau le test, il devrait échouer avec l'erreur «
L'instance de travail existe déjà et est terminée ». Ceci est normal puisque nous
utilisons la même base de données partagée et que nous avons déjà une instance
de travail qui a été terminée lorsque nous exécutons le test pour la première fois.
Corrigeons ce problème en effaçant toutes les métadonnées de travail avant
chaque test.

Nettoyer l'environnement de test avant chaque test

Comme nous venons de le voir, nous rencontrons l'erreur « L'instance du job


existe déjà et est terminée » si nous exécutons nos tests plus d'une fois.

Corrigeons ce problème en utilisant la classe JobRepositoryTestUtils.

43.Nettoyez les métadonnées avant l'exécution des tests.


Mettez à jour la classe de test et ajoutez la méthode d'initialisation
@BeforeEach suivante :

Vous devez également ajouter l'instruction d'importation suivante :

import org.junit.jupiter.api.BeforeEach ;

Cette méthode utilise JobRepositoryTestUtils pour effacer toutes les exécutions


du job avant chaque exécution de test, de sorte que chaque exécution aura un
nouveau schéma et ne sera pas affectée par les métadonnées des autres tests.

44.Réexécutez le test plusieurs fois.

Désormais, si vous exécutez le test plusieurs fois, le test devrait réussir sans
l'erreur « L'instance de job existe déjà et est terminée ». Quel soulagement!
Examinons ensuite un moyen alternatif pour permettre plusieurs exécutions de
notre test.

Utilisation de paramètres uniques du Job

Dans la partie précédente, nous avons appris que JobLauncherTestUtils fournit


des méthodes d'assistance qui créent des paramètres aléatoires du job pour
nous aider à éviter la redoutable erreur « L'instance de tâche existe déjà et est
terminée ».

Explorons maintenant cet utilitaire.

45.Utilisez des paramètres uniques du job.


Mettez à jour notre méthode de test et changez la façon dont nous
construisons les JobParameters.

Notez que nous avons uniquement modifié le nouveau JobParametersBuilder()


en this.jobLauncherTestUtils.getUniqueJobParametersBuilder().

46.Exécutez le test.

Lorsque vous exécuterez le test, vous verrez qu'il réussit exactement comme lors
de la dernière étape du laboratoire.

Mais que se passerait-il si nous ne nettoyions pas les métadonnées de la base de


données ?
47.Désactivez RemoveJobExecutions().
Commentez la ligne qui a nettoyé la base de données :

C'était très problématique dans le passé. Que se passera-t-il maintenant lorsque


nous relancerons les tests ?

48.Refaites les tests.


49.Relancez le test et constatez qu'il réussit !

En fait, réexécutez le test plusieurs fois : 2 fois, 10 fois – autant de fois que vous
le souhaitez. Le test continue de réussir et n'échoue pas avec "L'instance de
travail existe déjà et est terminée".

Mais comment est-ce possible ?

Examinons les métadonnées pour le découvrir.

Inspectez la base de données.

Nous avons vu dans les ateliers précédents que les jobs enregistraient les
métadonnées dans la base de données.

Examinons certaines de ces métadonnées maintenant que nous utilisons


jobLauncherTestUtils.getUniqueJobParametersBuilder() mais pas
jobRepositoryTestUtils.removeJobExecutions();

50.Exécutez la requête suivante dans le terminal :

Votre résultat devrait ressembler à ce qui suit :


Regardez toutes ces métadonnées !

Notez que l'inclusion automatique du paramètre random, qui garde nos


JobExecutions uniques.

Il est clair que de plus en plus de métadonnées seront ajoutées à la base de


données au fil du temps. Devons-nous laisser ces métadonnées dans la base de
données puisqu'elles ne semblent pas avoir d'impact sur nos tests ? Est-ce un "on
s'en fiche ?" situation?

Tester la pollution

Oui, nous devrions rétablir jobRepositoryTestUtils.removeJobExecutions() et


nettoyer la base de données avant chaque exécution de test. Mais pourquoi est-
ce important si les tests réussissent quand même ?

Même si ces tests particuliers réussissent actuellement sans nettoyer la base de


données, nous avons quand même introduit une pollution de test non atténuée
dans l'équation. Qu’est-ce que la pollution test et pourquoi est-ce un problème ?

La pollution des tests se produit lorsque les tests laissent derrière eux des
artefacts susceptibles d’avoir un impact sur d’autres tests ou des tests futurs. En
omettant RemoveJobExecutions(), les tests devront peut-être prendre en compte
les métadonnées laissées par d'autres tests. Imaginez des centaines, voire des
milliers de tests qui sont tous nécessaires pour rendre compte d'une manière ou
d'une autre des artefacts restants de tous les autres tests !
Notre test actuel réussit même en présence de pollution de test, mais d'autres
tests qui pourraient être écrits à l'avenir pourraient ne pas avoir autant de
chance. Peut-être devons-nous compter les exécutions de tests, ou vérifier la
sortie exacte des métadonnées, ou tout autre scénario nécessitant un contrôle
précis sur la sortie de nos tests.

Pour ces raisons et bien d’autres, il est recommandé de nettoyer les artefacts de
test avant ou après une exécution de test.

51.Rétablissez RemoveJobExecutions().
Mettez à jour le test pour effectuer un nouveau nettoyage avant chaque
exécution de test :

52.Réexécutez les tests et inspectez la base de données.


Réexécutez les tests plusieurs fois et notez que la base de données ne
contient que les données du test précédent :
La première Step
Implémenter le FilePreparationTasklet

53.Créez un répertoire « staging » au même niveau du répertoire « src »


54.Créez un nouveau fichier nommé FilePreparationTasklet.java dans le sous
package « demo » et mettez à jour son contenu comme suit :

Dans cette classe, nous implémentons l'interface Tasklet pour obtenir le fichier
d'entrée à partir des paramètres du Job et le copier dans le répertoire
intermédiaire.

Notez comment nous avons utilisé l'option


StandardCopyOption.REPLACE_EXISTING lors de la copie du fichier afin de
remplacer tout fichier existant, le cas échéant. Ceci est utile dans le cas où l'étape
est réexécutée et que nous souhaitons qu'elle réussisse au lieu d'échouer car le
fichier existe déjà dans le répertoire.
Définir l’étape de préparation du fichier

55.Ouvrez le fichier JobConfiguration.java et ajoutez la définition de bean


suivante :
Une fois cela en place, nous sommes maintenant prêts à définir le
TaskletStep.

Vous devez également ajouter les instructions d'importation suivantes :

import org.springframework.batch.core.Step;

import org.springframework.batch.core.step.builder.StepBuilder;

import org.springframework.jdbc.support.JdbcTransactionManager;

Dans cet extrait, nous définissons un bean nommé step1 de type Step.

Nous transmettons un JobRepository pour l'étape et un JdbcTransactionManager


pour la tasklet. Ce gestionnaire de transactions est auto-configuré par Spring
Boot et nous pouvons l'utiliser ici pour définir le TaskletStep.

N'oubliez pas qu'un TaskletStep nécessite un gestionnaire de transactions pour


gérer la transaction autour de chaque itération du Tasklet. Le TaskletStep est
défini par l'appel à StepBuilder.tasklet auquel nous transmettons une instance de
FilePreparationTasklet et le gestionnaire de transactions pour piloter les
transactions.
Mettre à jour la configuration du travail

56.Ouvrez le fichier JobConfiguration.java et remplacez le job bean comme


suit :

Vous devez également ajouter les instructions d'importation suivantes :

import org.springframework.batch.core.job.builder.JobBuilder;

Dans cet extrait, nous avons remplacé la création d'une instance BillingJob par
l'utilisation de l'API JobBuilder pour créer le Job.

Nous transmettons le nom du travail et une référence à un JobRepository.

Après cela, nous appelons la méthode JobBuilder.start qui crée un flux de Job
séquentiel et attend la première étape de la séquence.

Dans notre cas, la première étape est l’étape filePreparation que nous avons
définie comme step1 dans la section précédente.

Supprimer la tâche de facturation

Maintenant que nous avons créé le Job sous la forme d'une séquence d'étapes,
nous pouvons nous débarrasser de l'implémentation initiale, la classe BillingJob.

57.Allez-y et supprimez le fichier BillingJob.java.

Il est maintenant temps d'exécuter l'application et de voir la nouvelle


implémentation du travail en action !
Exécutez l’application

58.Dans l'onglet Terminal, exécutez la commande suivante pour créer le


projet :

59.Ensuite, lancez le job et passez le fichier d'entrée en paramètre avec la


commande suivante :

Vous devriez voir le fichier billing-2023-01.csv copié dans le répertoire staging,


ce qui signifie que notre travail s'exécute comme prévu.

60.Vérifions maintenant les métadonnées sur la première étape enregistrée


par Spring Batch dans la table BATCH_STEP_EXECUTION. Ouvrez un
terminal et exécutez la commande suivante :

Examinez la sortie pour vérifier l’état de l’étape filePreparation, son heure de


début et son heure de fin, etc.

Mettre à jour le test d'emploi

61.Ouvrez la classe de test JobApplicationTests.java et mettez à jour le test


testJobExecution comme suit :
Vous devez également ajouter les instructions d'importation suivantes :

import java.nio.file.Files;

import java.nio.file.Paths;

Dans ce test, nous transmettons le fichier d'entrée en tant que paramètre de


travail et nous nous attendons à ce que le fichier soit présent dans le répertoire
intermédiaire après l'exécution du travail. Comme vous le voyez, nous ne
cherchons plus la présence de la phrase "traitement des informations de
facturation depuis le fichier /some/input/file" dans la sortie standard, vous
pouvez donc supprimer en toute sécurité la déclaration
@ExtendWith(OutputCaptureExtension.class) et les instructions d'importation
correspondantes.

62.Ensuite, désactivons la configuration de lancement de Job par défaut de


Spring Boot Batch en définissant spring.batch.job.enabled=false dans
notre application.properties.

La propriété -Dspring.batch.job.enabled=false désactive l'exécution automatique


du Job par Spring Boot. Sans cette propriété, le job sera exécuté deux fois : une
première fois au démarrage de l'application, et une seconde fois lors du test. Ceci
n'est évidemment pas souhaité lors de l'exécution de tests, d'où l'utilisation de
cette propriété.
Sans cette option le job par défaut sera lancé en premier avec un paramètre vide,
ce qui induira à une exception lors de la phase d’exécution même si en fin de
compte le teste se terminera avec succès pour notre test.

Exécutez les tests

63.Lançons maintenant ce test.


Dans le Terminal, exécutez la commande suivante :

Le test doit réussir, ce qui signifie que l’étape fait ce qu’elle est censée faire.

Exercices

Comme exercice supplémentaire, essayez les scénarios suivants :

Omettre le fichier d'entrée

Dans FilePreparationTasklet, que se passe-t-il si le paramètre de travail input.file


n'est pas transmis au travail comme prévu ? Vous pouvez mettre à jour
l'implémentation pour vérifier l'existence de ce paramètre obligatoire et lever
une exception pour faire échouer la tasklet (et l'étape et la job qui l'entourent).

Vous pouvez également définir un JobParametersValidator sur le job pour valider


l'existence du paramètre input.file. N'hésitez pas à consulter
JobParametersValidator dans la documentation.

Forcer une exception

Dans FilePreparationTasklet, l’opération Files.copy lèvera une exception si le


fichier d’entrée n’est pas présent.

Essayez de renommer ou de supprimer le src/main/resources/billing-2023-


01.csv, puis exécutez le travail et voyez le résultat.
Lecture et écriture de données
Dans cette partie, vous allez implémenter une étape orientée fragments qui lit
les données de facturation à partir du fichier d'entrée et les écrit dans une table
de base de données relationnelle. N'oubliez pas que la deuxième étape de notre
billingJob est conçue pour enregistrer les données de facturation dans la base de
données pour une utilisation ultérieure.

Avant de passer à la définition de l'étape, commençons par comprendre la


spécification des données de facturation qui doivent être analysées à partir du
fichier d'entrée.

Comprendre la spécification des données de facturation

Le fichier d'entrée dans billing-2023-01.csv a le format suivant :

Colonne Type Description


Year int L'année au cours de laquelle les données ont été
capturées pour le client
Month int Le mois au cours duquel les données ont été
capturées pour le client
Account int L'identifiant de compte associé au client
Identifier
Phone Number String Le numéro de téléphone auquel l'utilisation est
associée.
Data Usage float La somme des données utilisées pour le mois en Mo
Call Duration int La somme des appels utilisés pour le mois en minutes
SMS Count int Le nombre de SMS envoyés au cours du mois
Voici un exemple du fichier d'entrée, que vous pouvez également vérifier en
ouvrant le fichier billing-2023-01.csv dans l'éditeur :

Year Month Account Number Phone Number Data Usage Call Duration
2023 1 100 404-555-1234 12.5 0
2023 1 101 404-555-5678 7.8 5
2023 1 102 404-555-9101 45.2 2
2023 1 104 404-555-2345 22.1 0
2023 1 105 404-555-6789 8.9 10
2023 1 106 404-555-1112 3.2 3

Maintenant que nous comprenons le format du fichier plat que nous devons
analyser, nous pouvons définir un FlatFileItemReader pour lire les données de ce
fichier.

Configurer le reader d'articles

Puisque nous avons affaire à un fichier plat en entrée, le reader le plus pratique
à utiliser est le FlatFileItemReader<T>. Ce reader est conçu pour lire les données
de n'importe quel format de fichier plat comme les fichiers délimités (CSV, TSC,
etc.) ou les fichiers de longueur fixe.

Maintenant, la question est : quel type d’éléments T ce reader doit-il retourner ?

Bien que nous puissions configurer le reader pour qu'il renvoie des chaînes ou
des tableaux d'octets à partir du fichier, cela ne serait pas pratique à utiliser lors
du traitement des données. En tant que développeur Spring Batch, nous
définissons généralement un type de domaine qui représente les données en
cours de traitement. Dans le cas, nous définirons un type nommé BillingData qui
représente une ligne du fichier d'entrée.
64.Créez le type de domaine.
Dans l'éditeur, créez un Record nommé BillingData.java avec le contenu
suivant :

Ce type est un enregistrement Java qui représente un enregistrement (ou une


ligne) du fichier d'entrée.

Les champs du type BillingData correspondent aux colonnes du fichier d'entrée.


L'ordre de déclaration des colonnes n'a pas d'importance ici, nous le définirons
plus tard lors de la configuration du reader.

Maintenant que nous avons défini le type d’éléments, nous pouvons procéder à
la configuration du itemReader.

65.Créez billingDataFileReader.
Dans l'éditeur, ouvrez le fichier JobConfiguration.java et ajoutez la
définition de bean suivante :

Vous devez également ajouter les instructions d'importation suivantes :

import org.springframework.batch.item.file.FlatFileItemReader;

import org.springframework.batch.item.file.builder.FlatFileItemReaderBuilder;

import org.springframework.core.io.FileSystemResource;

Dans cet extrait, nous définissons un bean nommé billingDataFileReader de type


FlatFileItemReader<BillingData>. Nous utilisons FlatFileItemReaderBuilder pour
créer le reader et nous spécifions quelques propriétés telles que le nom du
reader et le fichier d'entrée staging/billing-2023-01.csv.

Nous indiquons également au reader que le fichier d'entrée doit être délimité (à
l'aide de la méthode .delimited()) et que les colonnes doivent être définies dans
un ordre spécifique, qui correspond à la liste des champs (.names(... )) dans le
type de cible BillingData.

Une fois cela en place, le itemReader lira les lignes une par une et créera une
nouvelle instance de BillingData pour chaque ligne.

Notez comment, avec seulement quelques lignes de code de configuration,


Spring Batch nous a en fait économisé beaucoup de code passe-partout pour lire
le fichier d'entrée (ouverture/fermeture du fichier), lire le fichier ligne par ligne,
analyser les champs et mapper les données aux types correspondants dans
Données de facturation. Il s'agit d'une énorme quantité de travail fastidieux et
sujet aux erreurs que Spring Batch nous a évité de faire !

Maintenant que nous avons défini le itemReader, passons à la définition du


itemWriter.

Configurer le itemWriter

Les données de facturation doivent être enregistrées dans la base de données


pour une utilisation ultérieure afin de générer le rapport de facturation. Pour
cela, nous devons créer une table dans la base de données pour stocker les
données de facturation.

66.Examinez la structure de la base de données.


Voici la définition du tableau où seront stockées les données de
facturation :

create table BILLING_DATA


(
DATA_YEAR INTEGER,
DATA_MONTH INTEGER,
ACCOUNT_ID INTEGER,
PHONE_NUMBER VARCHAR(12),
DATA_USAGE FLOAT,
CALL_DURATION INTEGER,
SMS_COUNT INTEGER
);
Vous devrez créer cette table dans la base de données. Créons cela :
Ouvrez un terminal, exécutez la commande suivante et notez les résultats :

Puis créer la table


On peut maintenant vérifier la création de la table en interrogant le nombre
d'enregistrements dans cette table.

La table devrait être vide à ce stade et nous nous attendons à y voir des
enregistrements plus tard lorsque nous exécuterons le travail. Maintenant que la
table cible est en place, il est temps de configurer le itemWriter.

67.Créez le billingDataTableWriter.
Puisque nous écrivons des données dans une source de données JDBC, le
itemWriter le plus pratique à utiliser est JdbcBatchItemWriter<T>. Cet
éditeur d'éléments est conçu pour écrire des éléments dans une base de
données à l'aide de l'API JDBC. Configurons un tel writer.
Dans l'éditeur, ouvrez le fichier JobConfiguration.java et ajoutez la
définition de bean suivante :

Vous devez également ajouter les instructions d'importation suivantes :

import javax.sql.DataSource;

import org.springframework.batch.item.database.JdbcBatchItemWriter;

import org.springframework.batch.item.database.builder.JdbcBatchItemWriterBuilder;
Le JdbcBatchItemWriter a besoin de beaucoup d'informations pour écrire
correctement nos données de facturation dans la base de données. Examinons
JdbcBatchItemWriter plus en détail pour savoir comment cela est accompli.

Comprenez le JdbcBatchItemWriter.

Le JdbcBatchItemWriter doit connaître la base de données cible dans laquelle


écrire des données et l'instruction SQL à exécuter.

Dans l'extrait précédent, nous définissons un bean nommé


billingDataTableWriter de type JdbcBatchItemWriter<BillingData>. Nous
transmettons la source de données en tant que paramètre à la méthode, qui
représente la base de données cible dans laquelle les données doivent être
stockées.

Nous définissons également l'instruction d'insertion SQL que le writer doit


appeler pour insérer des éléments. Cette instruction spécifie la table cible
BILLING_DATA que nous avons créée précédemment, ainsi que la liste des
colonnes à insérer. Notez comment la liste des colonnes (:dataYear, :dataMonth,
etc.) correspond aux noms des champs du type BillingData.

Mais comment le itemwriter lierait-il les données des objets BillingData créés par
le lecteur aux colonnes de la table en utilisant la syntaxe :fieldName ?

C'est là qu'intervient l'appel à la méthode beanMaped(). Cette méthode


demande au writer d'utiliser l'API Java Reflection pour appeler des méthodes
getter afin d'obtenir la valeur de chaque champ portant le même nom que la
colonne de la base de données. Par exemple, pour lier la colonne :dataYear dans
la requête SQL, le writer appellera dataYear() sur l'instance BillingData de
l'élément actuel.
Configurer l'étape d'ingestion de fichiers

Maintenant que le itemReader et le itemWriter ont été configurés, nous pouvons


définir l'étape fileIngestion.

68.Définissez la deuxième étape.


Dans l'éditeur, ouvrez le fichier JobConfiguration.java et ajoutez la
définition de bean suivante :

Assurez-vous d'ajouter les deux importations supplémentaires :

import org.springframework.batch.item.ItemReader;

import org.springframework.batch.item.ItemWriter;

Dans cette partie, nous définissons un bean nommé step2 de type Step, et il se
passe beaucoup de choses.

Comprendre la définition de l’étape d’ingestion de fichiers.

Comme nous l'avons vu précédemment, tous les types d'étapes nécessitent au


moins le référentiel de job et le nom de l'étape. Dans ce cas, le référentiel de job
est transmis en tant que paramètre à la méthode de définition du bean et le nom
de l'étape est fileIngestion.

Maintenant que nous créons une étape orientée fragments, qui est un
TaskletStep, nous devons également transmettre une référence à un gestionnaire
de transactions et spécifier la taille du fragment, qui est de 100 dans ce cas. Cela
se fait en appelant la méthode chunk(...).

La syntaxe <BillingData,BillingData>chunk(...) est utilisée pour indiquer à Spring


Batch que l'entrée et la sortie de l'étape sont de type BillingData, ce qui signifie
que le reader renverra des éléments de type BillingData et que le writer écrira
des éléments de type également BillingData.

Autrement dit, cette étape ne change pas le type des éléments lors de son
exécution. Il est possible de changer le type des éléments si les données doivent
être transformées lors du traitement. Nous aborderons cela plus tard.

Attention : la valeur de la taille du morceau dépend fortement du cas d'utilisation


et doit être définie de manière empirique. La valeur 100 est généralement un
bon point de départ, mais ce n'est pas toujours le cas.

Enfin, nous définissons le itemReader et le itemWriter en utilisant


respectivement les méthodes reader() et writer(). le itemReader et le itemWriter
sont passés en paramètres à la méthode de définition du bean, ce qui signifie
qu'ils seront automatiquement binder par Spring à partir des définitions du bean
que nous avons configurées dans les sections précédentes.

Maintenant que tout est correctement configuré, nous sommes prêts à exécuter
cette nouvelle version de notre job !

Exécuter le job

Maintenant que la deuxième étape est définie, ajoutons-la à la séquence


d'étapes du flux de job.

69.Ajoutez la nouvelle étape au travail.


Ouvrez le fichier JobConfiguration.java et mettez à jour la définition du
bean du BillingJob comme suit :
Par rapport à la version précédente, nous avons ajouté le step2 au flux séquentiel
à l'aide de l'appel de méthode next(step2). Avec cela, Spring Batch exécutera
l'étape fileIngestion après l'étape filePreparation. Exécutons le travail et vérifions
cela.

70.Exécutez le travail.
Dans l'onglet Terminal, exécutez la commande suivante pour créer le
projet :

Ensuite, lancez le job et passez le fichier d'entrée en paramètre avec la


commande suivante :

Vous devriez voir quelque chose comme ceci :

Cela signifie que le travail a été terminé avec succès. Mais il est toujours bon de
vérifier que les résultats sont conformes aux attentes.

71.Vérifiez que le travail a réussi.


Vérifions le contenu de la table BILLING_DATA. Dans le terminal, exécutez
la commande suivante :

Puis tapez ;

Vous devriez voir que la table contient 1 000 enregistrements, ce qui correspond
au nombre de lignes dans le fichier d'entrée.

Mettre à jour le test

Maintenant que le job comporte une deuxième étape qui insère les données
dans la table BILLING_DATA, nous devons mettre à jour le test pour affirmer que
cette table contient les données du fichier d'entrée.

72.Mettez à jour le test.


Ouvrez le fichier DemoApplicationTests.java et ajoutez les assertions
suivantes à la fin du test testJobExecution :

Cette assertion utilise JdbcTestUtils de Spring Framework pour compter le


nombre de lignes dans une table donnée. Dans notre cas, le fichier d'entrée
contient 1 000 enregistrements, nous devons donc nous assurer que la table
contient 1 000 enregistrements une fois le job exécuté. Notez que
countRowsInTable nécessite un JdbcTemplate pour interroger la table. Le
JdbcTemplate est autoconfiguré par Spring Boot et peut être automatiquement
binder dans la classe de test. Alors ajoutons-le :

@SpringBatchTest

Vous devez également ajouter les instructions d'importation suivantes :

import org.springframework.jdbc.core.JdbcTemplate;

import org.springframework.test.jdbc.JdbcTestUtils;

73.Exécutez le test.
Il est maintenant temps de lancer le test. Selon vous, quels seront les
résultats ?
Dans le Terminal, exécutez la commande suivante :

Quand on fait le test, on voit que les résultats sont intéressants ! En fait, nous
avons trop de disques. Pourquoi est-ce?

Nous avons trop d'enregistrements dans la base de données car nous utilisons la
même base de données que celle utilisée à l'étape précédente. Lorsque nous
avons exécuté le job, nous avons déjà inséré des données !

Nettoyons automatiquement la base de données avant d'exécuter des tests.

74.Configurez les tests pour une exécution propre.


Enfin, comme pour le nettoyage des métadonnées avant chaque test à
l'aide de JobRepositoryTestUtils, nous devons également nettoyer nos
données commerciales avant chaque test.
Dans notre cas, nous devons supprimer toutes les lignes de la table
BILLING_DATA avant chaque test afin que l'assertion sur le nombre
d'enregistrements réussisse toujours.
Ajoutez l'instruction suivante dans la méthode setUp :

Refaites les tests maintenant et vous verrez qu'ils réussissent !


Traitement des données
Dans ce Lab, vous allez implémenter la dernière étape du BillingJob, qui consiste
à générer le rapport de facturation.

Pour simplifier les choses, le rapport de facturation est en fait un sous-ensemble


du fichier d'entrée avec les clients qui ont dépensé plus de 150 $ par mois. Le
format du fichier de sortie est le même que celui du fichier d'entrée, avec une
colonne supplémentaire représentant le montant total de facturation pour le
mois.

Pour cette dernière étape, vous devez :

• Lire les données de la table BILLING_DATA, qui a été renseignée


précédemment par l'étape fileIngestion
• Calculer les dépenses mensuelles de chaque client
• Filtrez les clients qui ont dépensé moins de 150 $
• Et enfin générer un fichier plat avec les clients restants

Lecture des données de facturation de la base de données

Puisque nous devons lire les données d’une source de données JDBC, le Reader
le plus pratique fourni par Spring Batch est JdbcCursorItemReader.

Ce Reader ouvre un curseur JDBC sur une table donnée et renvoie les éléments
du ResultSet JDBC renvoyé par le curseur.

75.Créez le billingDataTableReader.
Ouvrez la classe JobConfiguration.java et ajoutez la définition de bean
suivante :
Vous devez également ajouter les instructions d'importation suivantes :

import org.springframework.batch.item.database.JdbcCursorItemReader;

import org.springframework.batch.item.database.builder.JdbcCursorItemReaderBuilder;

import org.springframework.jdbc.core.DataClassRowMapper;

Comprendre le nouveau lecteur d'éléments.

Dans cette méthode de définition de bean, nous utilisons


JdbcCursorItemReaderBuilder pour créer un JdbcCursorItemReader. Les
principales propriétés de configuration de ce Reader sont la source de données,
la requête SQL pour récupérer les données et le type d'élément cible auquel les
données doivent être mappées.

Dans notre cas, le type d'élément cible est BillingData, qui est un enregistrement
Java.

Maintenant, la question est : comment mapper les valeurs des colonnes des
lignes de la base de données aux champs du type BillingData ? Pour cela, nous
pouvons utiliser le DataClassRowMapper de Spring Framework.

La classe DataClassRowMapper utilise la même technique de réflexion dans


FlatFileItemReaderBuilder.beanMapped que celle que nous avons vue plus tôt
dans ce cours pour mapper les données des colonnes de la base de données aux
champs du type cible BillingData. Nous utilisons une instance de
DataClassRowMapper en appelant la méthode rowMapper(new
DataClassRowMapper<>(BillingData.class)).

Maintenant que nous disposons d'un nouveau itemReader, passons à la


définition de la logique de traitement de notre étape.

Calcul des données de reporting

Le calcul des dépenses mensuelles et le filtrage des clients peuvent être effectués
dans un processeur d'articles.

Pour calculer les dépenses mensuelles, nous avons besoin de la tarification de


l'utilisation des données, des appels et des SMS. Dans cet atelier, et pour
simplifier les choses, nous supposons que la tarification des données est la
suivante :

• Tarif des appels : 0,50 $ par minute


• Tarifs texte/SMS : 0,10 $ chacun
• Tarif des données : 0,01 $ par Mo

Cette configuration de tarification peut varier et doit être externalisée dans les
propriétés de configuration. Pour cette raison, nous les implémenterons en tant
que propriétés de configuration Spring avec les détails de tarification précédents
comme valeurs par défaut. Enfin, nous allons créer un nouveau type qui
représente les données de reporting, qui inclut tous les détails de BillingData
avec un nouveau champ pour le montant total de facturation.

76.Créez le type ReportingData.


Dans l'éditeur, créez un nouveau fichier nommé ReportingData.java avec
le contenu suivant :
Il s'agit d'un enregistrement Java contenant les informations de facturation des
clients ainsi qu'un nouveau champ nommé billingTotal qui représente les
dépenses mensuelles de chaque client. Nous calculerons et remplirons ce champ
dans un processeur d'articles.

77.Créez le BillingDataProcessor.
Dans l'éditeur, créez un nouveau fichier nommé BillingDataProcessor.java
avec le contenu suivant :

C'est plus de code que ce que nous ajoutons habituellement ! Plongeons-y.


Comprendre comment fonctionne le processeur d'articles.

Notre nouvelle classe de processeur implémente l'interface


ItemProcessor<BillingData,ReportingData>.

Dans cette classe, nous calculons les dépenses mensuelles du client actuel et
filtrons les clients qui ont dépensé moins de 150 $ (en renvoyant null dans ce
cas).

Les détails de tarification sont déclarés en tant que propriétés Spring sous
l'espace de noms spring.cellular.* et peuvent être configurés en externe. Ces
propriétés ont des valeurs par défaut que nous allons utiliser dans cet atelier.

Une fois le calcul et le filtrage effectués, nous créons un objet ReportingData avec
les informations de facturation ainsi que les dépenses mensuelles.

Ce processeur d'éléments est une combinaison de différents types de


traitement : il enrichit non seulement l'élément actuel avec de nouvelles
données, mais filtre également les éléments qui ne sont pas nécessaires aux
besoins commerciaux actuels.

78.Déclarez le BillingDataProcessor en tant que bean.


Ouvrez le fichier JobConfiguration.java et ajoutez la définition de bean
suivante :

Il est nécessaire de déclarer le BillingDataProcessor comme bean pour qu'il soit


géré par Spring et configuré avec les propriétés que nous y avons déclarées.
Avec la mise en œuvre de notre nouveau processeur et la définition du bean,
seuls les clients ayant dépensé plus de 150 $ seront transmis au IteWriter et
inclus dans le rapport final. Alors allons-y et définissons le Writer.

Génération du rapport de facturation

Comme mentionné précédemment, le rapport de facturation est un fichier plat


ayant le même format que le fichier d'entrée mais avec seulement un sous-
ensemble de clients et une colonne supplémentaire pour le montant total de
facturation.

Pour écrire des données dans un fichier plat, Spring Batch fournit le
FlatFileItemWriter, que nous utiliserons dans ce laboratoire.

79. Créer le Writer FlatFileItemWriter


Ouvrez le fichier JobConfiguration.java et ajoutez la définition de bean
suivante :

Vous devez également ajouter les instructions d'importation suivantes :

import org.springframework.batch.item.file.FlatFileItemWriter;

import org.springframework.batch.item.file.builder.FlatFileItemWriterBuilder;

Le FlatFileItemWriter doit être configuré avec le fichier cible, qui est


staging/billing-report-2023-01.csv. Nous devons également préciser le format du
fichier (qui est délimité dans notre cas) et les champs que nous souhaitons y
exporter. Cela se fait respectivement en utilisant les méthodes delimited() et
names().
Ce itemWriter accepte les éléments de type ReportingData qui ont été
transformés à partir de BillingData dans le processeur d'éléments. La liste des
champs provenant du ReportingData.billingData imbriqué est préfixée par
billingData.

Le dernier champ, billingTotal, ne nécessite pas ce préfixe puisqu'il est défini


directement dans le type ReportingData.

C'est tout pour le itemWriter, nous pouvons maintenant passer à la définition de


l'étape finale.

Définir l'étape de génération du rapport

L'étape de génération de rapport est une étape orientée fragments, similaire à la


première étape de notre travail. La principale différence ici est que nous ajoutons
un processeur d’articles, le billingDataProcessor.

80.Définir l'étape de génération du rapport.


Ouvrez le fichier JobConfiguration.java et ajoutez la définition de bean
suivante :

Vous devez également ajouter l'instruction d'importation suivante :

import org.springframework.batch.item.ItemProcessor ;
Cela fait beaucoup de paramètres et d’appels de méthodes ! Assurons-nous de
comprendre exactement ce qui se passe.

Comprendre l'étape de génération du rapport.

Nous avons déclaré un bean nommé step3 de type Step, qui représente l'étape
finale, reportGeneration.

Semblable à l'étape précédente orientée fragments, stpe2 nommée


fileIngestion, nous devons transmettre une référence au référentiel de job et au
gestionnaire de transactions. La taille du morceau est également de 100, ce qui
constitue une bonne valeur de départ.

Le itemReader, le itemProcessor et le itemWriter sont également transmis en


tant que paramètres à la méthode de définition du bean afin qu'ils soient
automatiquement binder par Spring à partir des définitions de bean
précédentes.

Assurons-nous maintenant de comprendre ce que fait chacun de ces trois beans


auto-binder et dans quelle phase ils sont utilisés.

• reader(billingDataTableReader)

Cette phase utilise notre bean JdbcCursorItemReader pour lire et mapper les
données déjà insérées dans la base de données à l'étape 2.

• processor(billingDataProcessor)

Il s'agit de notre nouveau bean ItemProcessor sophistiqué qui non seulement


filtre les factures inférieures à 150 $, mais enrichit également les données avec
le montant réel des dépenses.

Cette phase constitue une grande différence par rapport à la fileIngestion de


l'étape 2, qui n'avait pas de phase de processeur. Bien que l'étape fileIngestion
n'ait pas modifié le type d'élément (puisqu'elle n'a pas utilisé de ItemProcessor),
remarquez comment le type d'élément change ici : <BillingData,
ReportingData>chunk(...). En fait, cette dernière étape change le type d'élément
avec un ItemProcessor, d'où cette notation.

• writer(billingDataFileWriter)

Enfin, nous utilisons un FlatFileItemWriter pour écrire les données filtrées et


enrichies dans un fichier à des fins de reporting.

81.Ajoutez la troisième étape au flux de job.


Dans le fichier JobConfiguration.java, mettez à jour la définition du job
bean comme suit :

En ajoutant l'étape 3 après l'étape 2 dans JobBuilder, l'étape reportGeneration


ne sera exécutée qu'une fois l'étape fileIngestion terminée avec succès.

Nous avons tout mis en place. Il est temps pour nous d’exécuter le job !

Exécuter le travail

Nous sommes maintenant enfin prêts à exécuter l’intégralité du job et à vérifier


le rapport de facturation généré !

82.Packagez et exécutez.
Dans l'onglet Terminal exécutez la commande suivante pour créer le
projet :
Ensuite, lancez le job et passez le fichier d'entrée en paramètre avec la
commande suivante :

Vous devriez voir quelque chose comme le journal suivant dans la console :

Notez comment les trois étapes ont été complétées avec succès. Cela signifie que
nous devrions trouver le rapport généré dans le répertoire intermédiaire.
Regardons ça!

83.Consultez le rapport.
Dans l'éditeur, ouvrez le répertoire staging et inspectez son contenu. Un
nouveau fichier nommé billing-report-2023-01.csv doit être généré dans
ce dossier.

Ce fichier contient uniquement les clients ayant dépensé plus de 150 $ par mois.
Ce fichier contient également une colonne supplémentaire à la fin de chaque
ligne avec les dépenses mensuelles de chaque client. Toutes les valeurs ici
doivent être supérieures à 150 $.
Le nombre de clients dans ce fichier devrait être exactement de 781. Mettons à
jour le test avec ces informations.

Mettre à jour le test

En plus des vérifications précédentes, nous devons ajouter deux nouvelles


assertions :

• Nous devons affirmer que le fichier du rapport de facturation existe dans


le répertoire intermédiaire.
• Et que le rapport contient exactement 781 lignes
84.Mettons à jour le test en conséquence.
Mettez à jour les tests. Ouvrez le fichier DemoApplicationTests.java et
ajoutez les lignes suivantes à la fin de la méthode de test
testJobExecution :
Vous devez également ajouter l'instruction d'importation suivante :

import java.nio.file.Path ;

Ces assertions mettent en œuvre les vérifications que nous avons décrites ci-
dessus. Il est maintenant temps d'exécuter le test et de vérifier le résultat. Ouvrez
un terminal et exécutez la commande suivante :

Le test doit réussir, ce qui signifie que la troisième étape et que l'ensemble du
travail fait ce qu'il est censé faire. Toutes nos félicitations!
La portée des composants
Notre tâche de facturation présente un défaut : elle est codée en dur pour traiter
un seul fichier d'entrée de données. Alors que nous pouvons mettre à jour notre
job pour qu'il soit configuré dynamiquement avec un fichier d'entrée au moment
de l'exécution.

85.Examinez le fichier d'entrée codé en dur.


Ouvrez le fichier JobConfiguration.java et jetez un œil à la définition du
bean billingDataFileReader :

fichier d'entrée codé en dur !

Comme vous pouvez le constater, staging/billing-2023-01.csv sera le seul fichier


d'entrée toujours traité. Rendons cela plus flexible !

86.Mettez à jour le lecteur de fichiers.


Nos Reader de fichiers doivent être un bean à porté Step (@StepScope) et
la lecture doit être définie le fichier input.file au moment de l'exécution.
Mettez à jour la définition du bean billingDataFileReader comme suit :
Vous devez également ajouter les instructions d'importation suivantes :

import org.springframework.batch.core.configuration.annotation.StepScope ;

import org.springframework.beans.factory.annotation.Value ;

Dans cet extrait, nous avons défini la portée de l'étape billingDataFileReader afin
de le configurer avec le paramètre de job input.file. La valeur n’est plus codée en
dur ! Nous pouvons maintenant transmettre n'importe quel fichier comme
paramètre de job et il sera récupéré par ce Reader.

Nous avons corrigé le Reader des fichiers. Ensuite, corrigeons la façon dont les
données sont extraites de la base de données.

Portée du Reader de tables

Nous sommes maintenant confrontés à un autre défi : si nous ingérons des


données de deux fichiers de facturation pour des périodes différentes (disons
billing-2023-01.csv puis billing-2023-02.csv), l'étape d'ingestion de fichier
chargera toutes ces données dans la Table BILLING_DATA. Cependant,
billingDataTableReader est configuré pour lire toutes les données de cette table.
Ce n'est pas bon!

87.Passez en revue le code SQL codé en dur.


Ouvrez JobConfiguration.java et jetez un œil à la définition du bean
billingDataTableReader.

Pas de filtre !
Le problème est que notre bean billingDataTableReader, qui fait partie de l'étape
de génération du rapport, est actuellement configuré pour lire toutes les
données de cette table sans filtrer la période en cours de la base de données.

Nous devons mettre à jour cette requête SQL pour filtrer les données de la
période en cours que nous traitons, afin de générer correctement le rapport
mensuel. Alors réparons ça !

88.Mettez à jour le lecteur de table.


Mettez à jour la définition du bean billingDataTableReader, comme suit :

Par rapport à la version précédente, nous avons marqué ce Reader comme étant
à portée de Step en ajoutant l'annotation @StepScope.

Nous avons également ajouté deux nouveaux paramètres de Job : data.year et


data.month de type Integer. Ces paramètres seront utilisés pour spécifier la
période que nous prévoyons de traiter lors du lancement de l'instance de Job.

Notez comment la requête SQL est désormais définie dynamiquement pour


sélectionner uniquement les enregistrements de la période en cours.

Remarque : Vous vous demandez peut-être pourquoi nous n'avons pas extrait
l'année et le mois du nom du fichier. Bien que nous puissions le faire, cela serait
risqué, car le nom du fichier pourrait ne pas contenir ces informations ou les
fournir dans une notation différente. Pour cette raison, il est plus sûr de
transmettre explicitement l’année et le mois comme paramètres du Job.

Maintenant que nous avons corrigé tous les itemReader, passons aux itemWriter.

Portée des itemReaders

Le dernier composant que nous devons couvrir est le billingDataFileWriter. Il


s'agit du writer qui génère le rapport de facturation dans le cadre de l'étape
reportGeneration.

De la même manière que le fichier d'entrée est codé en dur dans notre reader
de fichiers, le fichier de sortie dans la méthode de définition du bean de cet
writer est codé en dur.

89.Examinez le fichier de sortie codé en dur.


Encore une fois, ouvrez JobConfiguration.java et cette fois jetez un œil à la
définition du bean billingDataFileWriter.

valeur codée en dur!


Comme le fichier d'entrée, le fichier de sortie doit être configurable pour plus de
flexibilité si l'on décide de changer son nom ou son emplacement. Alors réparons
ça.

90.Introduisez le paramètre de Jobk output.file.


Mettez à jour la définition du bean billingDataFileWriter comme suit :
Dans cet extrait, nous avons mis à jour billingDataFileWriter pour qu'il soit un
bean à portée de Step en ajoutant l'annotation @StepScope.

Cela nous permet de lier tardivement le fichier de sortie à partir d'un nouveau
paramètre de Job appelé output.file.

Une fois cela en place, nous pouvons désormais spécifier le fichier de sortie de
manière dynamique via un paramètre de Job.

C'est tout ce dont nous avons besoin en tant que bean à portée Step. Mettons à
jour la façon dont nous lançons le Job avec les nouveaux paramètres.

Exécuter le Job

Maintenant que notre Job peut prendre des valeurs d'entrée au moment de
l'exécution, générons des rapports de facturation pour nos deux fichiers d'entrée
de données, billing-2023-01.csv et billing-2023-02.csv.

91.Générez le rapport 2023-01.


Dans l'onglet Terminal, exécutez la commande suivante pour créer le
projet :
Vous pouvez maintenant exécuter la tâche avec la commande suivante pour
billing-2023-01.csv :

Notez que vous transmettez maintenant 4 paramètres :

• input.file
• output.file
• data.year
• data.month

Que se passerait-il si vous omettez l’un de ces éléments ? Gardez cela à l’esprit
et nous l’essayerons plus tard !

Pour l'instant, vous devriez voir le même résultat que dans l'atelier précédent :
les fichiers d'entrée et de sortie pour 2023-01 dans le répertoire staging :

staging/

− billing-2023-01.csv
− billing-report-2023-01.csv

Remarque : nous devrons déplacer les fichiers d'entrée du répertoire


src/main/resources vers un autre répertoire « input ». Les ressources sous
src/main/resources sont incluses par défaut par Maven dans le jar final, et nous
ne voulons évidemment pas que les fichiers d'entrée soient inclus dans le jar de
notre Job.

Mais que devrait-il se passer si nous exécutons le Job et fournissons billing-2023-


02.csv comme données d'entrée ? Découvrons-le!

92.Générez le rapport 2023-02.


Dans l'onglet Terminal, exécutez la Job, mais fournissez les valeurs d'entrée
et de sortie pour les données 2023-02.
Quel sera le résultat ?

En regardant dans le répertoire staging, vous pouvez voir que nous avons
maintenant les deux ensembles de données d’entrée et de sortie : 2023-01 et
2023-02 !

staging/

− billing-2023-01.csv
− billing-2023-02.csv
− billing-report-2023-01.csv
− billing-report-2023-02.csv

Paramètres manquants

Semblable à ce que nous avons mentionné dans l’étape « Implémentation de


l'étape de préparation des fichiers », si les paramètres de la job ne sont pas
spécifiés, la configuration des beans à portée de Step échouera, ce qui à son tour
fera échouer le Job. Essaie. Dans le terminal, essayez d'omettre le paramètre
output.file et observez l'erreur Path must not be null :

Assurez-vous toujours de transmettre les paramètres de Job comme prévu ou de


valider cela en enregistrant un JobParametersValidator dans la définition du Job.
Mettre à jour le test

La dernière chose que nous devons faire est de mettre à jour le test de notre Job
pour accepter les nouveaux paramètres du Job.

93.Ajoutez des paramètres au test.


Ouvrez DemoApplicationTests.java et mettez à jour la partie « donnée » de
la méthode testJobExecution comme suit :

Dans cette mise à jour, nous avons ajouté les trois nouveaux paramètres de
Job : output.file, data.year et data.month. L'exécution des tests et les assertions
ne sont pas affectées par ce changement. Vérifions le test. Dans le Terminal,
exécutez la commande suivante :

94.Exécutez le test.

Le test devrait réussir, ce qui signifie que nos composants à portée de Step
sont correctement configurés dynamiquement au moment de l'exécution
avec les nouveaux paramètres de Job !
Redémarrage des Jobs ayant échoué
Dans les étapes précédentes, vous avez lancé BillingJob avec deux fichiers de
facturation différents : staging/billing-2023-01.csv et staging/billing-2023-02.csv.
Tout s'est déroulé comme prévu et les rapports de facturation ont été générés
sans aucun problème.

Dans cet atelier, vous devrez traiter un troisième fichier, staging/billing-2023-


03.csv, et les choses ne passeront pas comme prévu.

Découvrons et apprenons comment faire face à de telles situations.

Lancer le Job sur les données de facturation du mois de mars

input/billing-2023-03.csv contient les données de facturation pour mars 2023. Ce


fichier contient 500 lignes de données de facturation, similaires aux fichiers
précédents. N'hésitez pas à ouvrir ce fichier dans l'éditeur et à l'inspecter.

Exécutons notre Job de facturation pour traiter ce fichier et générer le rapport


de facturation du mois de mars.

95.Construisez le projet.
Ouvrez un terminal et exécutez la commande suivante pour créer le
projet :

96.Exécutez le travail.
Ensuite, lancez BillingJob avec les paramètres suivants pour traiter billing-
2023-03.csv :

Vous devriez voir l'état suivant du processus dans la sortie :


Le travail a échoué ! Voyons pourquoi.

97.Analysez l'échec.
Inspectons la trace de pile de l'erreur :

...

...

Il semble que le itemReader ne soit pas en mesure d'analyser la ligne 226, qui
contient une valeur non valide pour le champ d'utilisation des données.

98.Ouvrez input/billing-2023-03.csv et inspectez la ligne 226 :

2023,03,325,404-555-1225,92-94,375,544

En effet, la valeur 92-94 qui correspond au champ d'utilisation des données


devrait être numérique, mais ce n'est pas le cas.

Réparons ça !

Corriger les données

Souvent, la réparation manuelle du fichier d'entrée d'un job ayant échoué


constitue la solution la plus pragmatique.

99.Modifiez le fichier d'entrée.


Dans l'éditeur, ouvrez input/billing-2023-03.csv et modifiez la valeur 92-94
à la ligne 226 par 92.94.
Eh bien, c'était facile !
100. Réexécutez le travail.

Maintenant que nous avons corrigé les données, réexécutons la tâche avec la
commande suivante :

Alors que s'est-il passé cette fois-ci ?

Le travail a encore échoué ! Est-ce la même erreur ?

101. Analysez le nouvel échec.


Inspectons la trace de pile de l'erreur :

...

...

L'erreur n'est pas la même qu'avant, mais elle est similaire : il semble que la ligne
408 contienne une autre valeur non valide pour le champ d'utilisation des
données, qui est 36-07.

Nous avons encore une autre erreur. Répétons le processus et corrigeons-le.


Corrigez les données... encore une fois !

Il n'est pas rare que les données d'entrée comportent plusieurs erreurs. Alors,
réparons à nouveau le fichier !

102. Modifiez le fichier d'entrée... encore une fois !


Dans l'éditeur, ouvrez input/billing-2023-03.csv et regardez la ligne
408 :

2023,03,507,404-555-1407,36-07,507,216

Il semble que nous ayons la même erreur de format de données.

Corrigez l'erreur en mettant à jour 36-07 vers 36.07.

Maintenant que les données sont corrigées, exécutez à nouveau le travail avec la
même commande.

103. Relancez le travail... encore une fois !

Lorsque vous réexécutez le travail, celui-ci devrait réussir. Super!

Ensuite, inspectons les métadonnées de Spring Batch pour comprendre ce qui


s'est passé avec toutes nos exécutions de Jobs.

Inspecter les métadonnées des exécutions de Jobs

Pour mieux comprendre comment Spring Batch assure le suivi des succès et des
échecs de notre Job, nous devons examiner les métadonnées de Spring Batch
dans la base de données.

Plus précisément, examinons les données dont nous disposons pour les
exécutions de Jobs et d'étapes.
104. Inspectez les métadonnées des exécutions de Jobs.
Ouvrez un terminal et exécutez la requête suivante pour inspecter la
table BATCH_JOB_EXECUTION :

Vous devriez voir quelque chose comme ce qui suit :

Comme vous le voyez, il y a 3 exécutions de job (32, 33, 34) pour la même
instance de job avec job_instance_id égal à 27. Ceux-ci correspondent à
l'ensemble de paramètres que nous avons transmis sur la ligne de commande.

Les deux premières exécutions du Job ont échoué, tandis que la dernière a réussi.
C'est la fonctionnalité de redémarrage de Spring Batch en action !

En effet, il était possible de réparer les données et de relancer le Job jusqu'à ce


qu'il aboutisse.

Vérifions ensuite les métadonnées des exécutions d'étapes.

105. Inspectez les métadonnées des exécutions d’étapes.


Ouvrez un terminal et exécutez la requête suivante pour inspecter la
table BATCH_STEP_EXECUTION :

Vous devriez voir le résultat suivant :


Cette sortie montre plusieurs points clés :

• L’étape filePreparation n’a été exécutée qu’une seule fois. En fait, il a réussi
lors de la première exécution du Job (32) et Spring Batch ne l'a pas
réexécuté.
• L'étape fileIngestion a échoué deux fois et a réussi une troisième fois, ce
qui correspond au résultat que nous avons rencontré lors des tentatives
de redémarrage de notre Job.
Notez comment les nombres de reader et de writer sont également
corrélés aux lignes que nous avons corrigées dans le fichier de données.
• Enfin, l'étape reportGeneration n'a été exécutée qu'une seule fois lors de
la troisième et dernière tentative, une fois toutes les données corrigées et
correctement ingérées dans la table BILLING_DATA.

Ces données peuvent être très précieuses, en particulier si vous devez déboguer
comment et où les échecs se produisent dans vos tâches.
Ignorer les enregistrements invalides
Dans l’étape précédent, vous avez constaté que chaque fois qu'une ligne non
valide est rencontrée dans le fichier d'entrée, le job échoue immédiatement. Par
conséquent, vous deviez corriger les données et redémarrer le job encore et
encore jusqu'à la fin.

Dans cette partie, vous apprendrez à configurer Spring Batch pour ignorer les
éléments non valides et les écrire dans un fichier séparé pour une analyse
ultérieure.

Nous utiliserons le même fichier problématique, input/billing-data-2023-03.csv,


comme entrée pour le job. N'oubliez pas que ce fichier contient deux lignes
invalides : 226 et 408.

Cependant, décidons que le job ne doit plus échouer et que les lignes invalides
doivent être écrites dans un nouveau fichier nommé staging/billing-data-skip-
2023-03.psv afin que nous puissions auditer les enregistrements
problématiques. Ce fichier est un fichier de valeurs séparées par des colonnes et
doit contenir des lignes non valides au format suivant :

226|2023,03 325 404-555-1225,92-94 375 544

408|2023,03 507 404-555-1407,36-07 507 216

Ce format simple commence par le numéro de ligne ignorée suivi de la valeur


brute de la ligne du fichier d'origine.

Définir un listener SkipListener

Tout d'abord, implémentons un SkipListener qui écrit les lignes ignorées dans un
fichier donné.

106. Créez le BillingDataSkipListener.


Créez un nouveau fichier nommé BillingDataSkipListener.java avec le
contenu suivant :

107. Comprenez le BillingDataSkipListener.

Soulignons certains des aspects les plus importants de BillingDataSkipListener.

• Cette classe implémente l'interface SkipListener.


• Les types génériques, <BillingData,BillingData>, correspondent au type
d'éléments d'entrée et de sortie de l'étape dans laquelle ce listener sera
enregistré (l'étape fileIngestion dans notre cas).
N'oubliez pas que cette étape ne modifie pas le type d'élément, BillingData
- d'où la notation <BillingData,BillingData>.
• BillingDataSkipListener nécessite le chemin d'accès au fichier dans lequel
les éléments ignorés doivent être écrits.
Ici, nous transmettons le chemin d'accès à ce fichier au listener au moment
de la construction : skippedItemsFile. Le chemin sera spécifié comme
paramètre du Job plus tard lorsque nous démarrerons le job.
• Enfin, nous implémentons la méthode onSkipInRead. Cette méthode sera
invoquée selon la politique du skip, que nous définirons à l'étape de la
section suivante.
L'exception qui nous intéresse est FlatFileParseException. Cette exception
donne une référence à la ligne brute qui a été lue dans le fichier ainsi qu'au
numéro de ligne.
Dans notre implémentation, nous retirons ces deux détails de l'exception
et les ajoutons au fichier skip conformément à la spécification Pipe
Separated Values.

C'est tout pour l'implémentation de SkipListener.

Définir le listener du skip comme un bean

Comme nous l'avons mentionné, le chemin d'accès au fichier dans le listener skip
sera configuré avec un paramètre du job. Par conséquent, nous déclarerons le
listener du skip comme un bean de portée Step (step-scope) et utiliserons une
expression SpEL pour lier la valeur du paramètre job.

108. Ouvrez le fichier JobConfiguration.java et ajoutez la définition de


bean suivante :
Cette méthode définit BillingDataSkipListener comme un bean à portée d'étape
et le configure avec un chemin d'accès à un fichier spécifié par le paramètre du
job skip.file. Ce paramètre de job sera transmis ultérieurement lors du lancement
de l'application.h

Maintenant que nous avons défini le listener du skip, passons à la politique du


skip.

Définir la politique du skip

L'étape d'ingestion de fichier est l'étape où les données de facturation sont


analysées à partir du fichier d'entrée et où l'exception FlatFileParseException est
susceptible de se produire. C’est donc l’étape où nous définirons la politique du
skip.

109. Ajoutez la stratégie du skip à l’étape d’ingestion de fichiers.


Nous avons ajouté de nouvelles classes et beans, mais ils ne sont
utilisés nulle part. Mettons-les à l’usage.
Dans l'éditeur, ouvrez JobConfiguration.java et mettez à jour l'étape
d'ingestion de fichier, étape 2, comme suit :

Vous devez également ajouter l'instruction d'importation suivante :

import org.springframework.batch.item.file.FlatFileParseException;
110. Comprenez la mise à jour.

Voici les impacts de nos changements :

• Nous avons marqué l'étape fileIngestion comme étape tolérante aux


pannes avec l'appel à la méthode .faultTolerant().
• Après cela, nous avons spécifié FlatFileParseException comme exception
ignorable (skippable), avec une limite de skip de 10 éléments au
maximum.
• Enfin, nous avons enregistré BillingDataSkipListener en tant que listener
dans l'étape.
• Ce listener est passé en paramètre à la méthode de définition d'étape, ce
qui signifie qu'il sera automatiquement binder par Spring à partir de la
méthode de définition du bean skipListener définie précédemment.

C'est tout ce qu'il faut pour rendre l'étape d'ingestion de fichiers tolérante aux
pannes ! Exécutons le travail et vérifions les résultats.

Exécuter le job

Voyons si notre job tolérant aux pannes l’est vraiment !

111. Packagez et exécutez.


Ouvrez le terminal et exécutez la commande suivante pour créer le
projet :

Ensuite, exécutez le job pour traiter le fichier billing-report-2023-03.csv :

g
Notez le nouveau paramètre skip.file=staging/billing-data-skip-2023-03.psv qui
correspond au fichier des éléments ignorés.

Le job devrait réussir et un nouveau fichier nommé billing-data-skip-2023-03.psv


doit être généré dans le répertoire staging.

Regardons ça!

112. Inspectez les éléments ignorés.


Dans l’éditeur, ouvrez le fichier staging/billing-data-skip-2023-
03.psv et vérifiez son contenu. Il doit contenir les lignes suivantes :

226|2023,03 325 404-555-1225,92-94 375 544

408|2023,03 507 404-555-1407,36-07 507 216

Cela signifie que notre job a correctement ignorer les lignes incorrectes sans
échouer à chaque fois ! C'est formidable par rapport à la manière précédente de
corriger les données ligne par ligne et de redémarrer le job encore et encore
jusqu'à la fin.

Avec ce nouveau fichier contenant des lignes ignorées, nous pouvons décider
comment procéder. Nous pouvons soit corriger les lignes en même temps et
ingérer ce fichier séparément, soit simplement les ignorer si elles contiennent
des données de facturation incorrectes qui ne peuvent pas être utilisées.
Nouvelle tentative de traitement des erreurs
Dans cette section, vous mettrez en œuvre une stratégie de nouvelle tentative
lors de l'étape de génération de rapport.

N'oubliez pas que cette étape utilise un processeur d'articles pour calculer les
dépenses totales de chaque client en fonction des données de tarification.

Dans ce Lab, les données de tarification seront fournies par un service distinct,
PricingService, qui n'est pas fiable ! Apprenons comment gérer de tels services.

Découvrir le service de tarification

Pour cette section, nous avons préparé une classe PricingService.java qui fournit
des informations sur les prix. Les données de tarification sont disponibles via des
getters pour les données, les appels et les SMS.

113. S'il vous plaît, Créer cette classe et jetez-y un œil.


Concentrons-nous sur la méthode getDataPricing() en particulier
Pour simuler des échecs, getDataPricing() échouera de manière aléatoire en
lançant une PricingException.

Sur un projet réel, ce service aurait pu être un service Web instable qui pourrait
échouer en raison d'erreurs réseau, mais pour simplifier cet atelier, nous avons
rendu ce service local. Dans les deux cas, le résultat aurait été le même.

114. Pour votre commodité, nous avons également déclaré ce service


comme bean dans la classe JobConfiguration :

Cela est nécessaire pour pouvoir configurer les données de tarification via les
propriétés Spring, comme nous l'avons fait précédemment pour
BillingDataProcessor.
Mettre à jour le BillingDataProcessor

Nous devons apporter quelques mises à jour à notre Job de facturation pour
utiliser le nouveau PricingService. Apportons ces changements maintenant.

115. Mettez à jour le BillingDataProcessor.


Dans l'éditeur, ouvrez le fichier BillingDataProcessor.java et mettez à
jour son contenu comme suit :

Dans ce changement, nous avons obligé le processeur d’articles à utiliser


PricingService. La logique de calcul et de filtrage est la même, nous avons
simplement externalisé la manière d'obtenir les données de tarification en
utilisant le PricingService, ce qui pourrait échouer !
Essayons cela, mais avant cela, nous devons mettre à jour la définition du bean
du processeur d'articles en fournissant le service de tarification.

116. Mettez à jour le bean BillingDataProcessor.


Ouvrez le fichier JobConfiguration.java et mettez à jour la méthode
billingDataProcessor comme suit :

Dans cet extrait, nous injectons le service de tarification dans le bean


BillingDataProcessor.

C'est tout! Exécutons le travail.

Générer et exécuter le travail

Comme nous l'avons fait à plusieurs reprises dans les sections précédents,
créons, exécutons et étudions les résultats.

117. Créez et exécutez le travail.


Tout d'abord, ouvrez un terminal et créez la nouvelle version du Job :

Maintenant, exécutons le Job sur le fichier input/billing-2023-04.csv.

Il s'agit d'un nouveau fichier qui représente les données de facturation d'avril
2023. Il contient 200 lignes d'informations de facturation. Toutes les lignes sont
correctement formatées cette fois.

118. Dans le Terminal, exécutez la commande suivante :


Le travail a échoué ! Voyons pourquoi.

119. Enquêtez sur l'échec.

L'erreur devrait ressembler à ce qui suit :

Le processeur d'articles n'a pas réussi à calculer les dépenses totales en raison
d'une exception levée par le service de tarification.

Que devons-nous faire maintenant? Si vous essayez de réexécuter le Job, il


échoue à nouveau !

Comme vous pouvez le constater, il n’y a aucune garantie que le Job réussisse à
chaque tentative, et essayer encore et encore n’est certainement pas la voie à
suivre !

Il semble que nous ayons besoin d’une stratégie de nouvelle tentative.

Implémenter la fonctionnalité de nouvelle tentative

Les exceptions de prix sont transitoires. En fait, une nouvelle tentative


d’opération de tarification ayant échoué peut réussir lors d’une tentative
ultérieure. Par conséquent, PricingException est un bon candidat à déclarer
comme exception réessayable. Faisons cela.

120. Ajoutez la fonctionnalité de nouvelle tentative.


Ouvrez le fichier JobConfiguration.java et mettez à jour la définition
de l'étape 3 comme suit :
Cela fait beaucoup de code ! Comprenons les changements :

• Tout d’abord, nous avons rendu l’étape de génération du rapport tolérante


aux pannes en appelant la méthode StepBuilder.faultTolerant().
• Après cela, nous avons déclaré PricingException comme exception
réessayable à l'aide de l'appel .retry(PricingException.class).
• Enfin, nous avons défini une limite de tentatives de 100

Avec cette configuration, toute opération de processus qui lève une


PricingException sera réessayée au maximum 100 fois, après quoi l'étape sera
marquée comme ayant échoué.

Essayons à nouveau le Job.

121. Générez et exécutez à nouveau le Job.


Tout d’abord, nous devons construire la nouvelle version du Job.
Ouvrez un terminal et exécutez la commande suivante :

Maintenant, réexécutez le travail :

Le travail devrait maintenant réussir !


Les opérations ayant échoué seront réessayées sans échouer l’ensemble du Job.

C'est génial par rapport à la version précédente où nous devions réessayer le Job
manuellement encore et encore.

Autres exercices

Comme exercice supplémentaire, essayez d'ajouter des messages de


journalisation dans le service de tarification pour vérifier le résultat et voir
comment les choses fonctionnent en coulisses.

Vous pouvez également implémenter un RetryListener pour compter le nombre


de tentatives pour chaque élément et enregistrer ce listener à l'étape de
génération du rapport.

Vous aimerez peut-être aussi