Comment gérer des batchs en Java avec Apache TomEE et Jakarta?
Introduction
Gérer des batchs en Java, soit pour accéder à des volumes de données importants, effectuer des calculs longs ou simplement gérer une file d’exécution, peut vite devenir une galère. Regardons ce que propose le projet Apache Jakarta pour une telle gestion.
Contexte
Ici je vais présenter un exemple complet prévu pour fonctionner avec Java 17 (minimum), Apache TomEE et une application REST. Le combo est complété avec Maven pour obtenir un projet classique.
Mise en place
Configuration TomEE
Il faut passer deux paramètres à TomEE:
- tomee.mp.scan=all
- openejb.cxf.CxfContainerClassLoader=false
Dans mon cas, je les passe à travers la ligne de commande avec le flag “-D”: -Dtomee.mp.scan=all -Dopenejb.cxf.CxfContainerClassLoader=false.
Configuration microprofile
Il faut ajouter un fichier vide nommé microprofile-config.properties dans le dossier resources/META-INF/.
Configuration des batchs
Pour configurer les batchs, il faut impérativement ajouter un fichier batchee.properties dans le dossier resources/.
Ce fichier contient les lignes suivantes:
- La première ligne indique qu’il ne faut pas connecter BatchEE avec JMX.
- La seconde ligne indique quelle classe de gestion de threads utiliser.
Configuration du batch de test
Dans le dossier resources/, il faut ajouter un fichier job-test.xml contenant le code suivant:
A noter que l’id du job doit être identique au nom de fichier.
Code
Classe GtgApplication
Cette classe sert de point d’entrée à notre application REST.
Classe GtgBatchManager
C’est cette classe qui va être chargée de piloter le démarrage des batchs. Il y a un point à noter ici: le nommage des batchs: il faut indiquer l’id du job tel qu’indiqué dans le fichier XML du batch.
1package ch.gobothegeek.pro.jeebatch;
2
3import jakarta.annotation.PostConstruct;
4import jakarta.batch.operations.JobOperator;
5import jakarta.batch.runtime.BatchRuntime;
6import jakarta.ejb.Singleton;
7import jakarta.ejb.Startup;
8import java.util.Properties;
9
10@Startup
11@Singleton
12public class BatchManager {
13 private JobOperator jobMgr;
14
15 @PostConstruct
16 private void init() {
17 this.jobMgr = BatchRuntime.getJobOperator();
18 }
19
20 public long runJob(String job, Properties params) throws InterruptedException {
21 long batchID = this.jobMgr.start(job, params);
22 return batchID;
23 }
24}
Classe GtgBatchThreadService
Ici on crée un ThreadExecutor qui est autorisé à exécuter un thread à la fois. L’objectif est de créer une file d’attente limitant l’accès à une ressource unique.
1package ch.gobothegeek.pro.jeebatch;
2
3import org.apache.batchee.container.services.executor.BatcheeThreadFactory;
4import org.apache.batchee.container.services.executor.BoundedThreadPoolService;
5import java.util.Properties;
6import java.util.concurrent.ExecutorService;
7import java.util.concurrent.LinkedBlockingQueue;
8import java.util.concurrent.ThreadPoolExecutor;
9import java.util.concurrent.TimeUnit;
10
11public class GtgBatchThreadService extends BoundedThreadPoolService {
12 @Override
13 protected ExecutorService newExecutorService(Properties batchConfig) {
14 return new ThreadPoolExecutor(1, 1, 600, TimeUnit.SECONDS,
15 new LinkedBlockingQueue<Runnable>(10),
16 BatcheeThreadFactory.INSTANCE);
17 }
18}
Classe JobTest
Finalement, le code du batch a exécuter. On simule un traitement long avec Thread.wait (parfaitement optionnel dans la vraie vie). Le batch retourne imméditement “COMPLETED” mais se place en file d’attente dû au fait que le ThreadExecutor n’autorise pas plusieurs traitements en parallèle.
1package ch.gobothegeek.pro.jeebatch;
2
3import jakarta.batch.api.Batchlet;
4import jakarta.batch.runtime.BatchRuntime;
5import jakarta.batch.runtime.context.JobContext;
6import jakarta.batch.runtime.context.StepContext;
7import jakarta.inject.Inject;
8import jakarta.inject.Named;
9
10@Named
11public class JobTest implements Batchlet {
12 @Inject JobContext jobContext;
13 @Inject StepContext stepContext;
14
15 @Override
16 public String process() throws Exception {
17 long wait;
18 System.out.println("Param statique = " + this.stepContext.getProperties().getProperty("param1"));
19 System.out.println("Param dynamique = " + BatchRuntime.getJobOperator().getJobExecution(this.jobContext.getExecutionId()).getJobParameters().getProperty("param"));
20 wait = Long.parseLong(BatchRuntime.getJobOperator().getJobExecution(this.jobContext.getExecutionId()).getJobParameters().getProperty("timeout"));
21 Thread.sleep(wait);
22 return "COMPLETED";
23 }
24}
Conclusion
En quelques lignes de code, on peut mettre en oeuvre la gestion de batchs via la JSR-352. Bien sûr, cet article ne couvre pas l’intégralité du pilotage. Il manque:
- la gestion des statuts
- La gestion des résultats
- Eventuellement, l’arrêt des batchs