In diesem Teil bauen wir das Fundament: ein Spring-Batch-Setup ohne Spring Boot. Das wirkt zunächst nach mehr Arbeit, ist aber absichtlich so. Spring Boot nimmt dir viel Konfiguration ab, hier siehst du die Bausteine bewusst explizit. Du erkennst, welche Infrastruktur Spring Batch wirklich braucht. Genau diese Bausteine sind später für Restartability, Monitoring und Recovery entscheidend.
Zielbild
Nach diesem Kapitel hast du eine minimale, aber vollständige Batch-Infrastruktur. Du nutzt ein persistentes JobRepository auf H2, startest Jobs zuverlässig per JobLauncher und hast ein nachvollziehbares Gerüst, das du Schritt für Schritt erweitern kannst.
Pflicht-Beans
Diese Komponenten sind nicht optional. Die DataSource ist die Datenbankverbindung (inklusive Pool) und speichert Job-Metadaten. Der PlatformTransactionManager steuert Transaktionsgrenzen, also wann committet oder zurückgerollt wird. Das JobRepository ist das Logbuch für Instanzen, Executions und Zähler. Der JobLauncher startet Jobs mit JobParameters.
Sinnvolle Ergänzungen sind JobExplorer für lesenden Zugriff auf Metadaten (etwa der letzte Lauf) und JobRegistry für dynamisches Starten von Jobs (Job X per Namen starten).
Metadaten-Schema: warum das zuerst kommt
Spring Batch speichert alles, was du für Restartability brauchst, in Tabellen.
Ohne diese Tabellen gibt es keine zuverlässigen Re-Runs. Für H2 nutzt du
das mitgelieferte Schema /org/springframework/batch/core/schema-h2.sql.
Das Schema erzeugt Tabellen für JobInstance, JobExecution,
StepExecution und deren ExecutionContexts. Diese Tabellen sind dein
Fahrtenbuch: welcher Job wann lief, wie weit er kam und mit welchem Ergebnis.
Diese Daten sind später die Basis für Monitoring und Recovery.
Schema-Management in der Praxis
Behandle das Batch-Schema wie jede andere produktive Datenbankstruktur: versioniert, migrationsfähig und nachvollziehbar. Spring Batch-Versionen können Schema-Änderungen mitbringen. Wenn du es einfach neu anlegst, verlierst du Historie und Restartability.
Empfehlung: Lege das Schema in dein Migrationssystem (z. B. Flyway/Liquibase) und upgrade es kontrolliert. Für Entwicklung kannst du das Schema per Script anlegen, für Produktion sollte es kein Auto-Create geben.
Minimal-Config-Template (Java 17, H2)
Das folgende Template ist bewusst klein, aber vollständig. Es verzichtet auf Boot-Autokonfiguration und zeigt dir explizit, welche Beans gebraucht werden.
package com.example.batch;
import javax.sql.DataSource;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.launch.support.TaskExecutorJobLauncher;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.repository.support.JobRepositoryFactoryBean;
import org.springframework.batch.core.explore.JobExplorer;
import org.springframework.batch.core.explore.support.JobExplorerFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.SyncTaskExecutor;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.jdbc.support.JdbcTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
@Configuration
public class BatchInfrastructureConfig {
@Bean
public DataSource batchDataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.addScript("/org/springframework/batch/core/schema-h2.sql")
.build();
}
@Bean
public PlatformTransactionManager batchTransactionManager(
DataSource batchDataSource) {
return new JdbcTransactionManager(batchDataSource);
}
@Bean
public JobRepository jobRepository(DataSource batchDataSource,
PlatformTransactionManager batchTransactionManager) throws Exception {
JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean();
factory.setDataSource(batchDataSource);
factory.setTransactionManager(batchTransactionManager);
factory.afterPropertiesSet();
return factory.getObject();
}
@Bean
public JobExplorer jobExplorer(DataSource batchDataSource) throws Exception {
JobExplorerFactoryBean factory = new JobExplorerFactoryBean();
factory.setDataSource(batchDataSource);
factory.afterPropertiesSet();
return factory.getObject();
}
@Bean
public JobLauncher jobLauncher(JobRepository jobRepository) throws Exception {
TaskExecutorJobLauncher launcher = new TaskExecutorJobLauncher();
launcher.setJobRepository(jobRepository);
launcher.setTaskExecutor(new SyncTaskExecutor());
launcher.afterPropertiesSet();
return launcher;
}
}
Wichtig: SyncTaskExecutor startet Jobs synchron und ist gut für Einsteiger. H2 ist nur für Entwicklung, in Produktion brauchst du eine persistente Datenbank. Das JobRepository nutzt die Metadaten-Tabellen, ohne die scheitert der Start.
Meta-DB vs. Business-DB trennen
Im echten Betrieb lohnt sich oft eine Trennung: eine DataSource für JobRepository-Metadaten und eine für die fachlichen Daten. Das verhindert, dass lange Business-Transaktionen die Metadaten blockieren. Außerdem kannst du die Pools unterschiedlich dimensionieren (Meta: klein und stabil, Business: größer und auf Durchsatz optimiert).
Für kleine Systeme ist eine gemeinsame DB okay, aber du solltest die Entscheidung bewusst treffen.
Isolation-Level und parallele Starts
Wenn mehrere Jobs gleichzeitig starten, konkurrieren sie beim Erzeugen der JobInstance und JobExecution. Das JobRepository braucht daher ein Isolation-Level, das doppelte Instanzen verhindert. Bei höherer Parallelität solltest du diesen Punkt testen, statt nur die Defaults zu übernehmen.
Typische Erweiterungen (später relevant)
Typische Erweiterungen sind eine eigene DataSource für Business-Daten und ein angepasstes Isolation-Level bei hoher Parallelität. Dazu kommen ein TaskExecutor für parallele Steps oder Partitioning sowie ein JobOperator für Start, Stop und Restart aus Betriebs-Tools.
Wir bleiben in dieser Serie zunächst minimal und bauen die Erweiterungen kapitelweise aus.
Minimal-Runbook (für den Betrieb)
Auch ohne Boot kannst du dir ein kleines Runbook anlegen:
- Jobstart mit festen Parametersätzen (z. B.
businessDate=...) - Status prüfen über JobExplorer oder JobRepository-Tabellen
- Re-Run: gleiche identifying Parameter, sonst neue Instanz
- Exit-Codes und Fehlermeldungen dokumentieren
Das spart dir später Zeit in der Fehlersuche.
Anti-Patterns
Ein In-memory Repository verhindert Restart, Audits und verlässliche Metriken. Eine DataSource für alles ohne Plan lässt Metadaten und Business-Tabellen um Ressourcen konkurrieren. Ein asynchroner Launcher ohne Monitoring lässt Jobs laufen, ohne dass jemand weiß, wann und wie sie fertig werden. Ein nicht versioniertes Schema macht spätere Änderungen zum Betriebsrisiko.
Kurzfazit
Ohne JobRepository gibt es keinen professionellen Batch-Betrieb. Das Grundgerüst ist klein, aber trägt die gesamte Serie. Ab jetzt können wir Jobs sauber definieren und starten.
Im nächsten Teil bauen wir den ersten Chunk-orientierten Step: Reader, Processor, Writer und die Frage, wie groß ein Chunk wirklich sein sollte.
Alle Teile der Serie: Serie: Spring Batch