Machine Learning? Ja gerne! Aber was und wie? Eine Kurzanleitung für den erfo...
Processing files as 2 phase xa transactional resources using java
1. www.javaspektrum.de 57
DER PRAKTIKER
Wenn‘s denn sein muss
Transaktionen auf dem
Dateisystem
Torsten Michelmann, Tapan Maheshwari
Wenn die Rede auf Dateimanipulation im Kontext von Transaktionen
kommt, dann ist die weit verbreitete Meinung: lieber nicht mit Java.
Warum eigentlich nicht? Es gibt Bibliotheken, die suggerieren, dass es
geht. Was spricht also dagegen? Verschiedene Lösungsvarianten für Ja-
va SE und Java EE sind denkbar und werden vorgestellt.
Das Grundproblem
Die meisten Dateisysteme unterstützen von Haus aus kei-
ne Transaktionen, und da Java den Gedanken der Platt-
formunabhängigkeit tief in seinen Genen trägt, unterstützt das
java.io-Package dementsprechend auch keine Transaktionen
auf Dateisystemebene.
Da die Erstellung von eigenem Code zur Implementierung
von transaktionssicheren Operationen auf dem Dateisystem
einen nicht unerheblichen Aufwand nach sich ziehen würde,
sorgt ein Griff in die Open-Source-Kiste für Erleichterung: Die
bewährte und bekannte Bibliothek Transaction aus den Apa-
che Commons liefert die Basisfunktionalität für transaktionales
Dateimanagement.
DieACID (atomicity, consistency, isolation, durability)Anfor-
derungen an Transaktionen werden in dieser Bibliothek durch
die Anwendung der pessimistischen Sperrung (pessimistic lo-
cking) implementiert. Das bedeutet, dass eine Datei nur von
genau einem Prozess bearbeitet werden darf. Weitere Anfragen
nach dieser Datei werden abgewiesen. Das garantiert, dass kei-
ne konkurrierenden Änderungen auf der Datei gemacht wer-
den, fordert aber den Preis, dass keine parallele Bearbeitung an
verschiedenen, unabhängigen Teilen der Datei durchgeführt
werden dürfen. Sicher ist das Aufsplitten einer Datei in meh-
rere Dateien in einem solchen Fall ein möglicher Workaround,
aber dieser ist mit Aufwand verbunden und möglicherweise
nicht immer einfach möglich (wenn z. B. das Dateiformat ein
Binärformat ist).
Die aktuelle Version von Commons Transaction ist 1.2, und
diese ist immerhin schon drei Jahre alt, Kontinuität und Fehler-
freiheit sind aber sicher kein Makel in jenen Kreisen, die Wert
auf Transaktionen legen. Ein entscheidender Makel der Bib-
liothek ist aber, dass verteilte Transaktionen nicht unterstützt
werden. Wie dieses Problem gelöst werden
kann, wird im Folgenden gezeigt.
Anhand zweier Anforderungsszenarien
und Design-Einschränkungen werden im
Folgenden drei Lösungsvarianten des Prob-
lems der transaktionalen Verarbeitung von
Daten, die in einer Datei vorliegen, beschrie-
ben.
Beispiel-Anwendungsfall 1:
Transaktionale Verarbeitung
einer einzelnen Datei
Eine einzelne Datei mit einer sehr großen An-
zahl von Geschäftsdatensätzen muss gelesen
und satzweise verarbeitet werden. Das Verarbeiten des Daten-
satzes besteht aus den Schritten
Datenbankeintrag anlegen,
markieren des Datensatzes in der Datei als „prozessiert“, da-
mit ein problemloses Wiederaufsetzen ohne Doppelverarbei-
tungen möglich ist.
Der Lösungsansatz sieht die folgenden Aktivitäten vor:
Start einer verteilten (XA-) Transaktion,
Datensatz aus der Datei lesen und als „prozessiert“ markie-
ren,
einfügen des Datensatzes in die Datenbank,
Commit der Transaktion.
Beispiel-Anwendungsfall 2: Transaktionale
Verarbeitung mit mehreren Dateien
Eine Quelldatei muss gelesen, verarbeitet und transformiert
werden, danach muss pro Satz ein Eintrag in eine Datenbank
geschrieben werden sowie eine neue Datei erzeugt werden.
Am Ende der Verarbeitung muss die Quelldatei gelöscht wer-
den. Der Lösungsansatz sieht die folgenden Aktivitäten vor:
Start einer verteilten (XA-) Transaktion,
einlesen der Quelldatei,
durchführen der Transformation und Erstellung der neuen
Datei sowie einfügen der Daten in eine Datenbank,
löschen der Quelldatei,
Commit der Transaktion.
Im Falle eines Fehlers während der Verarbeitung werden die
mittleren drei Schritte innerhalb einer Transaktion ausgeführt,
wodurch sicher gestellt ist, dass die Quelldatei erst gelöscht
wird, wenn die Zieldatei erstellt wurde und die Daten in die
Datenbank geschrieben wurden.
Abb. 1: Verwendung der Kernkomponenten der JTA (nach [JTA1.1])
2. JavaSPEKTRUM 2/2010
58
DER PRAKTIKER
Commons Transaction fit für verteilte
Transaktionen
Wie gesagt, unterstützt die Commons-Bibliothek Transaction
keine Two-Phase-Commit-Zugriffe. Dieser Makel verhindert die
direkte Verwendung der API zur Lösung unseres Anwendungs-
falls, gilt es doch, darin eine verteilte Transaktion unter Einbe-
ziehung einer Datei und einer Datenbank zu implementieren.
Es kann aber Abhilfe geschaffen werden: Die Java Transac-
tion API (JTA) liefert alle Interfaces, die benötigt und gemein-
hin von JDBC- und JMS-Treibern implementiert werden, um
die Einbindung in verteilte Transaktionen zu erlauben. Abbil-
dung 1 zeigt die Grundidee der JTA: Mittels einer zentralen
TransactionManager-Klasse werden XAResource-Manager-Klassen
koordiniert, damit die verteile Transaktion durchgeführt wer-
den kann. Abbildung 2 zeigt die notwendigen Methoden, die
implementiert werden wollen.
Tabelle 1 zeigt die verfügbaren Zustände, die in XAResource
Anwendung finden. Abbildung 3 zeigt nun, wie man JTA und
Commons Transaction verheiraten kann: Die Klasse FileXARe-
source stellt den ResourceManager dar und
implementiert folglich XAResource (vgl. Abb. 1),
welcher den Zugriff auf Dateien mittels der
Commons-Transaction-Klasse FileResourceMan-
ager implementiert.
Die Klasse FileXAResource.java (s. Auszüge in
Listing 1) enthält hierbei die wesentlichen Tei-
le der Implementierung. Die Aufrufreihenfol-
ge erschließt sich aus dem Sequenzdiagramm
in Abbildung 4.
Diese Variante der Implementierung hat
einen entscheidenden Nachteil: Sie ist nicht
anwendbar in Enterprise-JavaBeans (EJB), da
die Spezifikation die Verwendung von java.io
verbietet: „An enterprise bean must not use the
java.io package to attempt to access files and directories in the file
system.“ (Quelle: [JavaEJB3.0], Kapitel 20.1.2 Programming Re-
strictions).
Abb. 2: Die wichtigsten Interfaces der JTA
Konstantenname Beschreibung
XA_OK Die Vorbereitungen zur Transaktion
sind normal beendet worden
TMSUCCESS Transaktion wurde erfolgreich beendet,
der Aufrufer kann vom Transaktions-
kontext der Ressource entfernt werden
TMFAIL Beenden der Verbindung und markieren
der Transaktion als rollback-only
TMSUSPEND Aufrufer unterbricht (aber beendet nicht)
seine Verbindung mit der Transaktion
TMRESUME Aufrufer stellt die Verbindung zu einer
unterbrochenen Transaktion her
XA_RDONLY Die Transaktion ist committed und nur
noch read-only
TMJOIN Der Aufrufer verbindet sich zu einem
existierenden Transaktionskontext
Tabelle 1: Die Bedeutung der wichtigsten Konstanten der Klasse XAResource
Abb. 3: Beispiel-Implementierung
Abb. 4: Sequenzdiagramm zur Verwendung der Klasse FileXAResource
3. www.javaspektrum.de 59
DER PRAKTIKER
Datenbank-Loader: die mit EJB kompatible Lösung
Welche Muster stehen für die Verarbeitung von Dateien inner-
halb eines Java EE-Containers also zur Verfügung? Wenn die
Eingangsdaten in einer Datenbank vorlägen, wäre das Problem
schon gelöst, denn normale Datenbanken sind transaktionsori-
entiert und erlauben auch verteilte Transaktionen. Die Produk-
te sind etabliert und man vertraut ihnen, dass eine Transaktion
unter Einhaltung der ACID-Regeln durchgeführt wird.
Im Vergleich zu der Lösung mit Commons Transaction hat
diese Lösung durchaus ih-
ren Charme:
Ein mit der Datenbank
mitgelieferter Loader ist
hochgradig bzgl. Perfor-
mance optimiert.
Alle möglichen Fehler-
situationen sind bereits
dokumentiert und im-
plementiert.
Als Nachteil ist sicher zu
nennen, dass man ein we-
nig mehr Arbeit hat, da
man temporäre Tabellen
anlegen muss, um die Da-
ten dort zu speichern, be-
vor sie dann innerhalb der
Session-Beans verarbeitet
werden können. Aber al-
le Anwendungsfälle, die
oben beschrieben wurden,
lassen sich auch mit die-
sem datenbankzentrierten
Ansatz implementieren:
...
public class FileXAResource implements XAResource {
...
public void start(Xid xid, int flags) throws XAException {
this.storedXid = xid;
if ((flags != TMJOIN) && (flags != TMNOFLAGS)) {
//flag other than TMJOIN & TMNOFLAGS are not relevant
//to start() method, TMRESUME is not implemented
throw new XAException(XAException.XAER_INVAL);
}
checkXid(xid);
int txState = 0;
try {
txState =
fileResourceManager.getTransactionState(xidToString(xid));
} catch (ResourceManagerException e) {
//Convert ResourceManagerException to appropriate XAException
throwXAException(XAException.XAER_RMERR, e);
}
if (flags == TMJOIN &&
txState == FileResourceManager.STATUS_NO_TRANSACTION) {
//can‘t join, No existing transaction with this xid
throw new XAException(XAException.XAER_NOTA);
}
if (flags == TMNOFLAGS) {
if ((txState != FileResourceManager.STATUS_NO_TRANSACTION) ||
(txState == FileResourceManager.STATUS_ACTIVE)) {
//transaction already running with this xid
throw new XAException(XAException.XAER_DUPID);
}
try {
fileResourceManager.startTransaction(xidToString(xid));
} catch (ResourceManagerException e) {
//transaction with this xid could not be started,
//convert ResourceManagerException to appropriate XAException
throwXAException(XAException.XAER_RMERR, e);
}
}
}
...
public int prepare(Xid xid) throws XAException {
checkXid(xid);
int isPrepared = ResourceManager.PREPARE_FAILURE;
try {
//check if FileResourceManager is ready for prepare
isPrepared =
fileResourceManager.prepareTransaction(xidToString(xid));
} catch (ResourceManagerException e) {
//prepare transaction activity failed,
// convert ResourceManagerException to appropriate XAException
throwXAException(XAException.XAER_RMERR, e);
}
if (isPrepared == FileResourceManager.PREPARE_FAILURE) {
//could not prepare transaction with given xid,
//hence rolling back
throw new XAException(XAException.XA_RBROLLBACK);
}
if (isPrepared == FileResourceManager.PREPARE_SUCCESS_READONLY) {
return XA_RDONLY;
}
//return OK flag when FileResourceManager is ready
// to commit the transaction
return XA_OK;
}
...
public void commit(Xid xid, boolean onePhase) throws XAException {
checkXid(xid);
try {
fileResourceManager.commitTransaction(xidToString(xid));
} catch (ResourceManagerException e) {
//if we are here then transaction commit failed.
if (onePhase) {
//If case of one-phase commit we can directly
//roll-back the transaction
try {
//transaction identified by xid to be rolled back
fileResourceManager.rollbackTransaction(xidToString(xid));
} catch (ResourceManagerException e1) {
//exception occurred while rolling back the transaction
throwXAException(XAException.XAER_RMERR,e1);
}
throw new XAException(XAException.XA_RBROLLBACK);
//raise XAException to signal a failed commit operation
}else{
//if not onephase & commit failed, raise XAException
//to notify TX Manager
throw new XAException(XAException.XA_RBROLLBACK);
}
}
}
...
public void end(Xid xid, int flags) throws XAException {
if ((flags != TMFAIL) && (flags != TMSUCCESS)) {
//irrelevant flag found, throw an exception
throw new XAException(XAException.XAER_INVAL);
}
checkXid(xid);
if (flags == TMFAIL) {
try {
fileResourceManager.markTransactionForRollback(
xidToString(xid));
} catch (ResourceManagerException e) {
//Transaction could not be marked for rollback,
//convert ResourceManagerException to appropriate XAException
throwXAException(XAException.XAER_RMERR, e);
}
}
}
...
}
Listing 1: Die Klasse FileXAResource.java
Abb. 5: Anwendungsfall 1
mit Datenbank-Loader
4. JavaSPEKTRUM 2/2010
60
DER PRAKTIKER
In Abbildung 5 ist der
prinzipielle Ablauf unter
Verwendung des Daten-
banktoolings zum Laden
von Dateien gezeigt. In Ab-
bildung 6 sieht man, dass
auch die Erstellung von Da-
teien (wie in Anwendungs-
fall 2 gefordert) mit einem
sehr ähnlichen Ansatz
durchgeführt werden kann.
Der entscheidende Punkt
ist, dass man zum Erzeugen
der Datei ebenfalls auf Da-
tenbankwerkzeuge zurück-
greift. Viele Datenbanken
bieten einen Export von
Tabelleninhalten in Dateien
(zum Teil auch schon di-
rekt mit Transformation in
andere Zeichensätze, was
sehr nützlich sein kann)
mithilfe von zur Datenbank
gehörenden Tools an. Aber
selbst, wenn das nicht der
Fall ist, lässt sich mittels
einer Stored Procedure das
gewünschte Ergebnis erzielen.
Zugegeben, dieser Ansatz implementiert nicht zu 100 % die
Schritte, die im Anwendungsfall 2 beschrieben wurden, aber
das Ziel wird auf alle Fälle erreicht: Die Quelldatei wird mit
Garantie erst dann gelöscht, wenn die Daten in der Datenbank
angekommen sind und die Zieldatei erzeugt wurde.
Extract, Transform, Load (ETL)
Eine weitere mit EJB kompatible Lösungsvariante ist die Ver-
wendung eines ETL-Tools. Diese Tools erlauben zusätzlich die
Definition von Transformations- und Mappingregeln mittels
einer grafischen Oberfläche und abstrahieren auch von den da-
runter liegenden Datenbanken. Dafür erfordern sie einen we-
sentlich höheren Einarbeitungsaufwand und Mitarbeiter mit
einem neuen Skillset im Vergleich zu der auf Java basierenden
Lösung.
Erkenntnis
Die Entscheidung zwischen den Alternativen Commons Trans-
action, Datenbank- und ETL-Tools will gut überlegt sein. Jede
Lösung hat ihre Vorteile:
Wenn kein Java EE-Server vorhanden ist, dann ist die Verwen-
dung von Commons Transaction eine gute Alternative, solange
nicht Dutzende von verschiedenen Dateien bearbeitet werden
müssen, denn dann würde ein ETL-Tool seine Überlegenheit
bei der Implementierung ausspielen.
Wenn die Strategie eine Implementierung auf Basis von Java
EE EJBs verlangt, dann gibt es kaum einen Weg, der an einer
Implementierung mittels Datenbank-Tools zum Laden und
Entladen von Dateien vorbeiführt.
Wenn ein Team sehr viel Erfahrung mit einem ETL-Tool hat
und auch die notwendigen Lizenzen existieren, dann ist die
Wahl der ETL-Variante sicherlich nicht verkehrt.
Die Architekturentscheidung für eine der drei Alternativen
muss die im Projekt (und den nicht-funktionalen Anforderun-
gen) definierten Rahmenbedingungen berücksichtigen. Einen
goldenen Hammer zur Lösung des Problems der transaktiona-
len Verarbeitung von Dateien gibt es nicht.
Links
[ApacheTransaction] Transaction Overview,
http://commons.apache.org/transaction/
[JavaEJB3.0] Java Specification Request 220: Enterprise
JavaBeans 3.0, http://jcp.org/en/jsr/detail?id=220
[JavaTricksBlog2010] Transactional File Access in Java,
http://myjavatricks.com/jtfs.aspx
[JTA1.1] Java Transaction API (JTA), Version 1.1,
Release: 14 February 2007
[Olson07] J. Olson, Enhance Your Apps With File System Trans-
actions, http://msdn.microsoft.com/en-us/magazine/cc163388.aspx
[XA] X/Open XA (kurz „XA“), Standard für verteilte
Transaktionen, http://de.wikipedia.org/wiki/X/Open_XA
Abb. 6: Anwendungsfall 2 unter Verwen-
dung der Datenbank-Unload-Funktionalität
Torsten Michelmann arbeitet als Master Certified
IT Architect mit den Schwerpunkten Java EE und
Middleware für IBM Global Business Services. Er be-
fasst sich intensiv mit SOA, Open-Source-Bibliotheken
und Vorgehensmodellen zur Softwareentwicklung.
E-Mail: torsten.michelmann@de.ibm.com.
Tapan Maheshwari hat mehr als 10 Jahre
Erfahrung in der Architektur und dem Design von
E-Business-Lösung mittels Java EE. Er ist ein Certified
Enterprise Architect (SCEA) und IBM Certified SOA
Solution Designer. Er arbeitet für IBM Indien als Senior
Systems Analyst.