Die klassische Batch- bzw. Stapelverarbeitung von Daten bzw. Aufgaben war lange Zeit ein typisches Anwendungsszenario von Mainframe-Systemen. Schaut man sich aber auch mal kleinere Serversysteme und Serversoftware an, stellt man fest, dass auch hier oftmals ähnliche Anwendungsfälle auftreten, die aber meist proprietär umgesetzt werden. Ein Beispiel könnte das zeitgesteuerte, periodische Einlesen einer CSV-Datei in eine Datenbank sein. Oder das tägliche Generieren von Rechnungen anhand des aktuellen Datenstandes.
Hat man in seinem System gleich mehrere solcher Anwendungsfälle ausfindig machen können, lohnt sich ein Blick auf Spring Batch [1]. Spring Batch ist ein sehr flexibles Framework für eben genau diese Batchverarbeitung von Daten. Es bietet eine simple Domainlanguage zur deklarativen Formulierung von Jobs und deren Steps, liefert fertige Zugriffsklassen für Datenbank- und Dateianbindung und Verwaltungs- und Monitoringfunktionen für die Jobs und deren ausführbare Instanzen. Das Einlesen einer kommagetrennten CSV-Datei, das Mapping der Spalten zu Attributen einer Fachklasse und das Schreiben der Fachklasse in wieder andere Spalten einer Datenbank ist somit ohne viel selbstgeschriebenen Code in kurzer Zeit erledigt. Weitere Details zu Spring Batch sind im genannten Link [1] zu finden, bzw. in den sehr zu empfehlenden Büchern [2] und [3].
Das einzige was Spring Batch von Haus aus nicht liefert, ist ein Scheduler zum zeitgesteuerten Ausführen der Batch-Jobs. Hier beruft man sich darauf, dass es am Markt etliche gute Scheduler gibt, die man in Spring Batch integrieren kann. Explizit genannt und zur Integration empfohlen sind der TaskScheduler aus dem Spring Framwork [4], oder der bekannte Quartz-Scheduler [5]. Aber auch andere Produkte (wie z.B. die EJB-Timer) lassen sich als Scheduler nutzen.
Spring Batch bietet auch hier fertige Klassen, die z.B. Quartz-Jobs nahtlos mit Batch-Jobs verknüpfen. Auch hier kommt man sehr schnell zu ersten lauffähigen Ergebnissen, wenn man statische Ausführungszeitpunkte zur Entwicklungszeit deklariert. Möchte man mehr Flexibilität, d.h. zur Laufzeit dynamisch Trigger zu Jobs verwalten, dann lohnt es sich, Quartz wieder etwas aus Spring Batch heraus zu lösen, um die komplette Kontrolle darüber zu bekommen.
Eine mögliche Vorgehensweise ist, lediglich die Batch-Jobs im Spring ApplicationContext zu deklarieren und die Quartz-Trigger selbst zu verwalten. Einziges Bindeglied zwischen Quartz und Spring-Batch ist dann ein generischer Quartz-Job, der beliebige Batch-Jobs startet. Im Folgenden wird das näher betrachtet.
...
Zuerst wird die gewünschte Funktionalität als Batch-Job implementiert. Dies ist nichts besonderes und losgelöst vom Scheduler.
Das Instantiieren eines Quarz-Schedulers übernimmt Spring Batch, was den Vorteil hat, dass im Context des Schedulers der ApplicationContext von Spring verfügbar ist. Nur dann können wir aus dem Quarz-Job heraus auf den Batch-Job zugreifen.
public final class JobStarter implements org.quartz.Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
try {
Scheduler jobScheduler = context.getScheduler();
ApplicationContext appContext = (ApplicationContext) jobScheduler.getContext().get("applicationContext");
JobLauncher jobLauncher = (JobLauncher) appContext.getBean(JobLauncher.class);
JobRegistry jobRegistry = (JobRegistry) appContext.getBean(JobRegistry.class);
JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
Job job = jobRegistry.getJob(jobDataMap.getString("jobName"));
jobLauncher.run(job, getJobParameters(jobDataMap));
} catch (Exception exception) {
// ...
}
}
private static JobParameters getJobParameters(JobDataMap jobDataMap) {
// Map Quartz-Job-Data to Batch-Job-Parameters
}
}
Der generische Batch-JobStarter Quartz-Job. Dieser wird durch die definierten Trigger gestartet und die execute-Methode aufgerufen. Hier wird der Spring ApplicationContext geholt und daraus die nötigen Beans zur Batch-Job-Verwaltung. Den Namen des zu startenden Batch-Jobs, genauso wie dessen Batch-JobParameter, werden aus der Quartz-JobDataMap geladen und gemapped.
JobDetail jobDetail = new JobDetail("hourlyCsvImport", "defaultGroup", JobStarter.class);
jobDetail.getJobDataMap().put("jobName", "csvImportJob");
// Put additional Batch-JobParameter here
CronTrigger jobTrigger = new CronTrigger("hourlyTrigger", "defaultGroup", "0 0 0/1 * * ? *");
jobScheduler.scheduleJob(jobDetail, jobTrigger);
Zu guter letzt legen wir einen konkreten Quartz-Job an, der unseren Batch-JobStarter aufruft und den übergebenen Batch-Job startet. Für diesen Quartz-Job sind dann beliebige Trigger definierbar und das alles zur Laufzeit verwaltbar.
[1] Spring Batch Webseite
[2] Buch: “Spring Batch in Action”
[3] Buch: “Pro Spring Batch”
[4] Spring Framework TaskScheduler
[5] Quartz Scheduler Webseite