Spring Batch est une brique essentielle du framework Spring qui facilite la gestion des traitements par lots. Il permet de définir des processus en plusieurs étapes pour le traitement de grandes quantités de données.
Dans cet article, nous allons voir comment implémenter un traitement par lot avec Spring Batch pour un cas concret : marquer comme "inactifs" les utilisateurs qui ne se sont pas connectés depuis un certain temps à une application.
Pré requis
Avoir lu et suivit les différentes étapes de l'article suivant
Ajouter les dépendances
Avant d’entrer dans le détail de l'implémentation du batch, il est important de bien comprendre les dépendances nécessaires et leur rôle. Voici les dépendances à ajouter dans le fichier pom.xml
et une explication pour chacune d'elles.
spring-boot-starter-batch
- Pourquoi l'ajouter ? Ce starter est essentiel pour activer Spring Batch, un module de Spring conçu pour traiter des tâches par lot (batch processing). Il fournit les outils pour gérer de manière efficace les opérations répétitives, comme le traitement de grandes quantités de données. Il contient des classes essentielles comme
Job
,Step
,ItemReader
,ItemProcessor
, etItemWriter
que nous utiliserons pour définir notre traitement batch. - Quand l'utiliser ? Dès que vous avez des tâches à exécuter en arrière-plan, de façon automatisée ou planifiée, et que ces tâches nécessitent un traitement en masse de données, Spring Batch est une solution robuste.
spring-boot-starter-data-jpa
- Pourquoi l'ajouter ? Spring Data JPA est un autre module de Spring qui simplifie l'accès et la gestion des données relationnelles dans les applications Java. Nous l'utilisons ici pour interagir avec notre base de données et récupérer les utilisateurs. Grâce à JPA, il devient simple d'effectuer des requêtes SQL complexes via des interfaces en Java, sans avoir à écrire du SQL manuel.
- Quand l'utiliser ? Chaque fois que votre application interagit avec une base de données relationnelle, JPA vous permet de manipuler les entités en tant qu'objets Java sans vous soucier des détails de bas niveau liés à la gestion des connexions à la base de données.
com.h2database:h2
- Pourquoi l'ajouter ? H2 est une base de données embarquée légère qui peut être utilisée pour le développement et les tests. Spring Batch utilise une base de données pour stocker l'état des jobs, comme les métadonnées, les étapes de jobs, l'avancement des traitements, et pour faciliter les reprises en cas d'échec.
- Quand l'utiliser ? Vous pouvez utiliser H2 en phase de développement et de test, car elle est simple à configurer et s'exécute entièrement en mémoire, sans avoir besoin de configuration externe. En production, vous pourriez utiliser une base de données plus robuste comme MySQL ou PostgreSQL.
ℹ️ Version antérieur de Spring Boot
Avant Spring Boot 3 une autre dépendance aurait du être importé pour planifier les job : spring-boot-starter-scheduling
Maintenant le module Spring Batch gère désormais directement les tâches planifiées via l'annotation @Scheduled
ce qui fait que nous pouvons nous passer de cette dernière dépendance.
Configuration de la base de données
Spring Batch utilise une base de données pour stocker l'historique d'exécution des jobs et des étapes. Nous allons configurer H2, une base de données en mémoire, pour cet exemple.
Voici la configuration dans le fichier application.properties
:
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.jpa.hibernate.ddl-auto=update
spring.batch.initialize-schema=always
Cette configuration permet à Spring de créer automatiquement les tables nécessaires au bon fonctionnement de Spring Batch et JPA.
Nous allons aussi créer un fichier schema-all.sql
qui sera placé dans les resources du projet
ℹ️ Spring Boot exécute schema-@@platform@@. sql automatiquement au démarrage. -all est le paramètre par défaut pour toutes les plateformes.
Définition de l'entité User
Maintenant que notre base de données est prête à être utilisée, il nous faut une entité JPA pour représenter les utilisateurs dans la base de données.
Création du Repository
Il nous faut également un repository pour interagir avec la base de données.
Configuration du Job Spring Batch
Tout le code suivant sera dans une classe BatchConfiguration
Définition du Job
@Bean
public Job inactivateUsersJob(JobRepository jobRepository, Step step1) {
return new JobBuilder("inactivateUsersJob", jobRepository)
.incrementer(new RunIdIncrementer())
.start(step1)
.build();
}
- Ce code définit un bean
Job
. Le job est nommé"inactivateUsersJob"
, et il est configuré pour démarrer par lestep1
défini plus bas. RunIdIncrementer
permet d'ajouter un identifiant unique à chaque exécution du job, ce qui est utile pour exécuter plusieurs fois le même job sans conflit.- Un job est composé d'une ou plusieurs étapes (steps). Ici, nous commençons avec une seule étape.
Définition du Step
@Bean
public Step step1(JobRepository jobRepository, PlatformTransactionManager transactionManager,
ItemReader<User> reader, ItemProcessor<User, User> processor,
ItemWriter<User> writer) {
log.info("inactivateUsersJob - step1 ");
return new StepBuilder("step1", jobRepository)
.<User, User>chunk(10, transactionManager)
.reader(reader)
.processor(processor)
.writer(writer)
.build();
}
- Ici, nous définissons une étape du job (
step1
), qui se compose de trois parties essentielles : un lecteur (reader
), un processeur (processor
), et un écrivain (writer
). - Le type de traitement se fait par blocs (chunks). Cela signifie que Spring Batch lira 10 enregistrements à la fois (grâce à
chunk(10)
), les traitera via le processeur, et les écrira ensuite. StepBuilder
permet de construire cette étape avec une gestion transactionnelle grâce àPlatformTransactionManager
.
Définition du Reader
@Bean
public ItemReader<User> reader(UserRepository userRepository) {
log.info("inactivateUsersJob - search users");
return new ItemReader<User>() {
List<User> activeUsers = userRepository.findByStatus("active");
@Override
public User read() {
if (!activeUsers.isEmpty()) {
return activeUsers.removeFirst();
}
return null;
}
};
}
- Le
reader
est responsable de récupérer les données d'entrée. Ici, il interroge la base de données pour récupérer les utilisateurs ayant le statut"active"
. - Le lecteur fonctionne en parcourant une liste d'utilisateurs actifs et retourne un utilisateur à chaque appel de la méthode
read()
. Quand la liste est vide, il renvoienull
, ce qui indique à Spring Batch qu'il n'y a plus de données à traiter. - Le
UserRepository
est utilisé pour interroger les utilisateurs actifs dans la base de données.
Définition du Processor
@Bean
public ItemProcessor<User, User> processor() {
return user -> {
if (user.getLastLoginDate().isBefore(LocalDateTime.now().minusMonths(6))) {
log.info("user {} is inactive because last login date is {}", user.getUsername(), user.getLastLoginDate());
user.setStatus("inactive");
}
return user;
};
}
- Le
processor
est chargé de la logique métier. Il vérifie si un utilisateur ne s'est pas connecté depuis plus de six mois, et si c'est le cas, il modifie le statut de l'utilisateur à"inactive"
. - Chaque utilisateur traité est passé par le processeur avant d'être envoyé au
writer
. Ce mécanisme permet d'appliquer des transformations ou des validations sur les données.
Définition du Writer
@Bean
public ItemWriter<User> writer(UserRepository userRepository) {
log.info("inactivateUsersJob - save users");
return userRepository::saveAll;
}
- Le
writer
est responsable de l'enregistrement des données traitées. Ici, il utilise leUserRepository
pour sauvegarder tous les utilisateurs dans la base de données. - Spring Batch accumule les utilisateurs traités dans des blocs de 10 (comme spécifié dans le
chunk
) et appelle ensuite lewriter
pour les persister.
Configuration du Scheduler
Maintenant que notre job est configuré et prêt à l'usage, il faut maintenant configurer à quelle fréquence il sera exécuter, et pour ce faire il nous faut un Scheduler
.
@Component
public class BatchScheduler {
private final JobLauncher jobLauncher;
private final Job inactivateUsersJob;
public BatchScheduler(JobLauncher jobLauncher, Job inactivateUsersJob) {
this.jobLauncher = jobLauncher;
this.inactivateUsersJob = inactivateUsersJob;
}
@Scheduled(cron = "*/10 * * * * ?")
public void runJob() throws Exception {
JobParameters jobParameters = new JobParametersBuilder()
.addLong("time", System.currentTimeMillis()) // Paramètre unique pour chaque exécution
.toJobParameters();
jobLauncher.run(inactivateUsersJob, jobParameters);
}
}
Planification avec @Scheduled
L'annotation @Scheduled(cron = "*/10 * * * * ?")
indique que la méthode runJob()
sera exécutée toutes les 10 secondes. Le format cron utilisé ici signifie :
*/10
: Toutes les 10 secondes (dans un vrai contexte métier, jouer ce job toute les 10 secondes n'aurait pas grand intérêt, il faudrait plutôt le jouer 1 fois par jour)* * * * ?
: Chaque minute, chaque heure, chaque jour, chaque mois, indépendamment du jour de la semaine.
Il faut aussi penser à ajouter @EnableScheduling dans votre classe main.
Il ne nous reste plus qu'a démarrer notre application et voir ce que nous avons en console.
2024-10-01T18:36:06.587+02:00 INFO 31998 --- [demo] [ main] o.s.b.c.l.support.SimpleJobLauncher : Job: [SimpleJob: [name=inactivateUsersJob]] launched with the following parameters: [{'run.id':'{value=1, type=class java.lang.Long, identifying=true}'}]
2024-10-01T18:36:06.613+02:00 INFO 31998 --- [demo] [ main] o.s.batch.core.job.SimpleStepHandler : Executing step: [step1]
2024-10-01T18:36:06.629+02:00 INFO 31998 --- [demo] [ main] c.e.demo.config.BatchConfiguration : user eve is inactive because last login date is 2024-02-25T00:00
2024-10-01T18:36:06.629+02:00 INFO 31998 --- [demo] [ main] c.e.demo.config.BatchConfiguration : user grace is inactive because last login date is 2023-11-30T00:00
2024-10-01T18:36:06.673+02:00 INFO 31998 --- [demo] [ main] o.s.batch.core.step.AbstractStep : Step: [step1] executed in 59ms
2024-10-01T18:36:06.681+02:00 INFO 31998 --- [demo] [ main] o.s.b.c.l.support.SimpleJobLauncher : Job: [SimpleJob: [name=inactivateUsersJob]] completed with the following parameters: [{'run.id':'{value=1, type=class java.lang.Long, identifying=true}'}] and the following status: [COMPLETED] in 77ms
Conclusion
Spring Batch offre une solution pour gérer les traitements par lots, qu'il s'agisse de petites ou de grandes quantités de données.
Dans cet article, nous avons montré comment configurer un job batch pour marquer les utilisateurs inactifs en fonction de leur dernière connexion.
En combinant les fonctionnalités de Spring Boot, Spring Batch, et JPA, nous avons pu orchestrer facilement un traitement en plusieurs étapes, tout en bénéficiant d'une gestion simplifiée de la base de données et d'une planification automatique des jobs.
Ce tutoriel constitue une base solide pour implémenter des processus batch plus complexes, que ce soit pour le traitement de données en masse, des tâches répétitives ou l'automatisation de certains workflows en entreprise.
Spring Batch permet non seulement de gérer les traitements lourds, mais également d'assurer une fiabilité accrue grâce à sa gestion intégrée des transactions, des erreurs et des reprises. Vous êtes maintenant prêts à explorer davantage de scénarios où ce puissant framework pourrait simplifier vos développements.
Tout le code relatif à cet article est trouvable ici
Vous trouverez ici, toute la documentation liée à Spring Batch
Et si jamais vous avez du mal à trouver vos starter spring boot