Oracle Java: Stapelverarbeitung mit Spring Batch

Die Stapelverarbeitung von Daten wurde mit dem JSR 352 in die Oracle Java Version 1.7 implementiert.
Grundlage dieses JSR (Java Specification Request) ist die Spring Batch implementation. Welche ich hier gerne, etwas genauer beschreiben möchte.

Vorbedingungen

Es wird vorrausgesetzt das folgende Software installiert und konfiguriert ist:

Einfaches Projekt zum verarbeiten einer CSV Datei

Ziel des Projektes

Jedes Projekt benötigt ein Ziel. Dieses Projekt soll das Ziel haben eine CSV Datei mit Daten zu lesen
und in eine andere CSV Datei zu schreiben. Später soll der CSVWriter gegen einen PDFWriter ersetzt werden. (Was deutlich mehr sinn ergibt!)

Ablage

Dieses Projekt habe ich online, im GitHub unter https://github.com/StefanDraeger/BatchWorker abgelegt.
In den ersten Commits musste ich das Projekt auf ein Level bringen damit ich dieses in diesem Tutorial verwenden kann.

CSV Datenformat

Die Daten einer CSV Datei können mit folgendem Trenner separiert sein:

  • Komma,
  • Semikolon,
  • Leerzeichen,
  • oder andere Zeichen

Eine sehr gute erläuterung zum CSV Format ist unter https://de.wikipedia.org/wiki/CSV_(Dateiformat) zu finden.

Die Rohdaten der CSV:

  • erste Spalte ist die ID (fortlaufende Nummer)
  • zweite Spalte ist ein Beschreibungstext
  • dritte Spalte ist ein Double Wert
  • vierte Spalte ist ein Timestamp

Apache Maven

Nachdem wir ein einfaches Maven Projekt erstellt haben müssen wir nun noch die benötigten Abhängigkeiten und Plugins für das spätere compilieren und bauen in das Projekt bringen.

Dependencies

Plugins

Nachdem die Dependencies geladen wurden (eventuell mit einem mvn install nachhelfen). Kann nun mit der implementierung begonnen werden.

Viele Wege führen nach Rom!

Frei nach dem Sprichwort „Viele Wege führen nach Rom!“ möchte ich auch die implementierung von Spring Batch vornehmen.
Es gibt nämlich zwei Möglichkeiten dieses Framework zu verwenden. Einmal rein programmatisch und einmal mit XML Konfiguration beide haben Ihre Vor- und Nachteile.

Im ersten Schritt möchte ich die Implementierung mit Java Quellcode Erläutern, da danach die XML Konfiguration besser verstanden wird.

Was wird für die Stapelverarbeitung benötigt?

Da wir ein FatJar am Ende bauen werden, benötigen wir eine Main Klasse welche später in der Maven POM.XML konfiguriert wird.

Application.java

Die Klasse Application erhält die Annotation @SpringBootApplication und führt in der main Methode sich selber aus, damit wird der SpringBootApplication prozess gestartet und eine Klasse mit der Anotation @Configuration gesucht.

BatchConfiguration.java

Die Klasse BatchConfiguration enthält wie der Name es vermuten läßt die Konfiguration (Steps usw.) für unsere Stapelverarbeitung. Durch die zuvor erwähnte Annotation @Configuration wird diese als Konfiguration herangezogen.
Die zusätzliche Annotation @EnableBatchProcessing ermöglicht es nun die Features von Spring Batch in einer Klasse zu verwenden welche zuvor mit der Annotation @Configuration benannt wurde.

In Zeile 19 bis 31 wird der Reader für die Daten beschrieben, da die Verarbeitung einer CSV Datenstruktur bereits standartmäßig implementiert ist muss hier nur die interne Struktur benannt werden, dazu wird die Klasse CSVSensorTokenizer wie folgt angelegt:

Wichtig! Die Groß- undKleinschreibung der String Werte sind nicht wichtig. Es muss jedoch auf die korrekte Schreibweise geachtet werden. Die Spring Configuration durchsucht die Entity nach dem Wert zbsp. ID. Findet diese eine nach der EJB konformität implementierte Variable (getter & setter, CamelCase usw.) so wird diese verwendet. Was jedoch beim testen aufgefallen ist, dass wenn der Wert ID123 lautet trotzdem die korrekte Variable (ID) gefunden wurde.

Es muss dann noch ein Mapping geschehen da wir für die Felder auch eine Entity benötignen:

Die Methode

kann um beliebig viele Steps erweitert werden.

Methode mit „nur“ einem Step

Methode mit  zwei aufeinanderfolgenden Steps

SensortEntity

Die Klasse SensorEntity enthält nun die Werte für unsere CSV Datenstruktur:

Der CSVWriter

Da ich zuerst eine einfache Verarbeitung der CSV Daten beschreiben möchte UND es dem Tutorial nichts bringt gleich auf die Frameworks iText oder Apache POI zu wechseln, implementiere ich einen einfachen CSVWriter.

Dieser Writer öffnet eine Datei im Format „export_<<TIMESTAMP>>.csv“ wenn diese Datei nicht vorhanden ist wird diese neu erstellt. Wenn jedoch die Datei vorhanden ist wird diese um die Einträge aus der Liste „sensorValues“ erweitert. Dieses vorgehen ist bei der Chunk Orientierten Stapelverarbeitung wichtig. (Dazu aber später mehr.)

Der Weg über XML Konfiguration

Wie am Anfang erläutert wurde, gibt es min. 2 Wege wie man Spring Batch verwenden kann.
In diesem Abschnitt möchte ich also nun die XML Konfiguration erläutern.
Als Basis für diesen Abschnitt nutze ich das fertige Projekt welches unter folgenden Link geladen werden kann:

https://github.com/StefanDraeger/BatchWorker/releases

Erstellen der XML Konfiguration

Es wird im Pfad src\main\resources\spring\batch folgende Dateien angelegt:

config\context.xml

jobs\job-convertCSVtoPDF.xml

 

Erweitern der Klasse CSVWriter.java

Für das erstellen der Bean CSVWriter muss ein parameterloser Konstruktor existent sein. Da wir aber im ersten Schritt einen Konstruktor erstellt haben (mit der Übergabe eines Timestamps) so ist der Default Konstruktor nicht mehr existen und muss nun eingepflegt werden.

 

Nachdem wir nun die XML Konfiguration erstellt haben können wir obsolete Klassen entfernen, das Projekt wird nun deutlich schlanker.

Projektstruktur mit XML Konfiguratuion
Projektstruktur mit XML Konfiguratuion

 

 

Chunk Orientierte Stapelverarbeitung

Ein Chunk ist ein Ergebnis aus dem Lesen eines Datensatzes (ItemReader) und der Verarbeitung (ItemProcessor), dabei wird eine Liste mit diesen verarbeiteten / aufbearbeiteten Ergebnissen gesammelt. Beim Erreichen einer maximalen Anzahl werden diese Chunks dem ItemWriter übergeben und dort persistiert. Dieses kann eine Datenbank eine Datei oder sonst welche Schnittstellen sein. Das minimum der Chunksize ist die positive 0 und das die positive Integer.MAX_VALUE Zahl.

 Erstellen des FatJars für die Ausführung

Damit das Projekt ohne IDE lauffähig ist wird nun ein FatJar gebaut, dazu wird auf der Konsole folgender Befehl ausgeführt:

Nachdem der Build erfolgreich durchgelaufen ist. Dieses ist erkennbar an:

Befindet sich im .\target\ Verzeichnis nun das Java Archiv „BatchWorker-0.0.1-SNAPSHOT.jar“ dieses wiederum läßt sich auf der Konsole mit dem Java Befehl

ausführen.

Jetzt haben wir aber leider nur eine Datei welche abgearbeitet werden kann und diese ist im Java Archiv gepackt. Im nächsten Schritt müssen wir also die Datei „data.csv“ nicht im Classpath suchen sondern im Dateisystem oder viel besser als Parameter dem Job übergeben.

Übergabe des Dateinamens an den Job

Pfad relativ zum Classpath

Im ersten Schritt haben wir die Resource an den org.springframework.batch.item.file.FlatFileItemReader statisch mit

übergeben. In diesem Fall sucht der Job die Datei im Pfad „src\main\resources“.

Absoluter Pfad

Nun möchten wir aber außerhalb des Projektes die Datei(en) ablegen, dazu könnte man nun, wie in diesem Fall auf dem Laufwerk „C“ folgende Struktur anlegen „C:\csv\daten.csv“.

Nun sucht die Job Konfiguration in diesem Ordner und verarbeitet die Datei. Das ist schon deutlich dynamischer als im ersten Schritt, jedoch ist man noch auf den eindeutigen Dateinamen angewiesen.

Definition über eine Propertiesdatei

Den Dateinamen kann man auch bequem über eine Propertiesdatei konfigurieren, dazu legt man eine einfache Datei an zbsp. „converter.properties“ mit folgendem Wert:

Diese Datei muss unter „src/main/resources“ abgelegt werden und wird wiefolgt in der Job Konfiguration, konfiguriert:

Nun kann in jedem Step wo diese Datei benötigt wird mit der EL „${filename}“ auf den Wert der Propertie zugriffen werden.

Es ist aber keinesfalls dynamisch denn der Dateiname steht hier nun fest in der Propertiesdatei und kann von außen nicht so einfach geändert werden.

Übergabe als Argument

Wie man von einer Java Anwendung kennt, kann man auch hier einen (oder mehrere) Parameter übergeben.
In Eclipse kann dieses zur Entwicklungszeit über „Run -> Run Configurations….“ erledigt werden.

Run Configuration der Anwendung BatchWorker zum Tutorial "Stapelverarbeitung mit Spring Batch"
Run Configuration der Anwendung BatchWorker

Zur Laufzeit über der Konsole wird dieses als Parameter hinter der JAR Datei angehangen:

Jedoch alleine durch das vorhanden sein des Parameters wird dieser nicht verarbeitet (soviel Magie steckt nicht in Spring).

Argumente entgegennehmen

Damit wir nun den Parameter „filename“ verarbeiten können müssen wir unsere Startklasse „Application.java“ wiefolgt ändern:

Es wird hier mit dem JobParametersBuilder

  • die Argumente entgegen genommen (Methode addString(key, value)), und
  • das Objekt JobParameters erzeugt (Methode toJobParameters())

diese Kette kann beliebigt fortgesetzt werden, wichtig ist nur das am Ende die Methode toJobParameters() aufgerufen wird. (siehe Builder Pattern)

Durch den Schlüsselwert „filename“ kann nun, wie auch schon bei der Propertiesdatei mit der Expressionlanguage (EL) der Parameter geladen werden

Es ist natürlich sinnvoll eine Prüfung zu implementieren ob das Argument übergeben wurde UND ob die Datei existiert.

Das Projekt mit einer XML Konfiguration kann bequem über GitHub geladen werden.

https://github.com/StefanDraeger/BatchWorker/releases/tag/0.2

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.