# Nested Transaction, JDBC und Sybase



## fritzr (2. Mrz 2008)

Hallo,
ich habe ein prinzipielles Problem zu lösen. In unserem Projekt haben wir eine Sybasedatenbank, eine Middleware und ein Java Frontend. Die Middleware bietet verschiedene Methoden an z.B. putObject(), removeObject() etc. an. In diesen Methoden werden die komplexeren Datenbank Zugriffe transaktional angesteuert. Das folgende Listing gibt eine Beispielmethode an...



```
public void putUser(User u) throws RepositoryException, SecurityException {
        Connection conn = null;
        RepositoryException exception = null;
        CallableStatement putUserStatement = null;
        CallableStatement changeMappingStatement = null;
        CallableStatement updateUssStatement = null;
        Statement stat = null;
        try {
            conn = getConnection();
            stat = conn.createStatement();
            stat.execute("BEGIN TRANSACTION");
...
...
...
            stat.execute("COMMIT");
        }
        catch (Exception e) {
            exception = new RepositoryException(e);
            try {
                if (stat != null) {
                    stat.execute("ROLLBACK");
                   }
            }
            catch (SQLException sqle) {
                sqle.printStackTrace();
            }
        }
...
    }
```

Diese Methode fügt einen einen neuen Benutzer hinzu, wenn irgendwas schief läuft, wird die Transaktion zurückgerollt. Nun möchte in einer weiteren Ebene die Möglichkeit haben, mehrere Benutzer zu schreiben, wobei diese gesamte Prozedur am bessten auch in einer eigenen Transaktion laufen sollte. Sobald ein Nutzer nicht angelegt werden konnte, sollen alle anderen ebenfalls zurückgerollt werden.

Also etwas in der Art wie:

Starte Transaktion
putUser(user1);
putUser(user2);
putUser(user3);
Commite Transaktion

Leider finde ich hierzu keinen Weg zur Lösung. Wie kann ich also mehrere Transaktionen in einanderschachteln (Nested Transactions)? Sybase unterstützt dies ja wohl...

Ich möchte jedoch keine neue Middleware-Methode schreiben, die parallel zu der ersten Methode steht.

Grüße


----------



## maki (2. Mrz 2008)

> Wie kann ich also mehrere Transaktionen in einanderschachteln (Nested Transactions)? Sybase unterstützt dies ja wohl...


Hab schon Persistenz Frameworks erlebt die das nicht können.

Das ist ein Architektur Problem, kein Sybase Problem oder sonstetwas.

Hier wäre spätestens der Zeitpunkt darüber nachzudenken, ob man Transaktionen nicht doch von den einzelnen JDBC aufrufen trennen möchte.


----------



## fritzr (2. Mrz 2008)

Also ein wirkliches Framework ( a la Spring) nutzen wir ja in unserem Fall gar nicht. Am liebsten wäre mir zwar der Springansatz, aber so richtig will unsere Middleware da nicht reinpassen. Ich frage mich noch, wie man die Transaktionen davon trennen kann? Die bisherigen Methoden funktionieren ja sehr gut, doch nutzt man mehrere dann fehlt mir hier noch völlig der richtige Ansatz. Wie könnte man vorgehen?

Du sagst die Transaktionen vom JDBC trenne, wo sollte man sie denn dann absetzen?


----------



## maki (2. Mrz 2008)

Problem: Ein "use case"/Transaktion ändert mehrere Tabellen/Entitäten, entweder alle oder keine Änderungen sollen übernommen werden.

Dieses Problem ist nicht selten, verbreitete Frameworks versuchen das auf ihre Weise zu meistern.

Selbst wenn du EJBs verwendest, kommst du irgendwann zu diesem Punkt, würdest wahrscheinlich das ApplicationService Pattern einsetzen.

Eine Gemeinsamtkeit ist, dass man den gesamten Vorgang als eine Transaktion sieht, wenn man mehrere "Entitäten" (oder Tabellen) in einem use Case ändert, nicht nur einen Einzelschritt und damit eine einzelne Datenänderung.

Grundprinzip ist das man nicht nach jeder Datenänderung ein "commit" macht, sondern  dieses "commit" explizit aus der Aufrufenden Funktionen macht.


----------



## fritzr (2. Mrz 2008)

So langsam sehe ich was gemeint sein könnte...

Das Problem ist also, dass ich in der Methode putUser() direkt die Transaktion in der JDBC-Connection starte und beende und andere Methoden von dieser Connection gar nichts wissen...

Sehe ich es richtig, dass man folglich einen Transaktionsmanager wie z.B. Aomikos benutzen könnte, über den die einzelnen Methoden stets ihre Connection beziehen? Das sollte man doch eigentlich recht gut aus dem Code herausziehen können...


----------



## maki (2. Mrz 2008)

Ich kenne Aomikos nicht,

aber du hast richtig verstanden.

So etwas selbst zu implementieren kann ein bisschen hakelig sein, musst diesselbe Connection verwenden für alle Änderungen verwenden die als eine Transaktion laufen sollen.

Wenn es nur ein Thread ist der eine Transaktion durchführt, wäre es zB eine Möglichkeit diese in ThreadLocal abzulegen und nach dem Durchlauf von allen Änderungen zu committen, oder eben auch nicht (rollback) 

Ein leichtgewichtiges Framework das JDBC kapselt und so etwas unterstützt ist zB iBatis, Spring unterstützt so etwas auch, genauso wie Hibernate und EJBs.


----------



## fritzr (2. Mrz 2008)

Ich spiele gerade mit dem Atomikos Framework herum und stelle nun testweise unsere Middleware entsprechend um.
Ich habe den Zugriff auf die Connection vom alten Verfahren umgestellt und verwende nun immer XAConnections.

Nachdem ich die SybaseDB entsprechend umgestellt habe (enable dtm) und sie nun XAConnections zulässt, erhalte ich den folgenden Fehler:

JZ0SF: No Parameters expected. Has query been sent?

Der Ablauf ist folgender:
1) Hole XAConnection
2) Aus der XAConnection erzeuge eine neue Connection
3) Erzeugen eines neuen Aufrufs mit checkEntryStatement = conn.getConnection().prepareCall("{?=call check_path ?,?,?,?,?,?}");
4) checkEntryStatement.execute();
5) wenn ich nun später darauf zurückgreife, kommt der oben beschriebene Fehler. Es scheint als hätte er den Call überhaupt nicht ausgeführt...

Reichen die Informationen aus, um das Problem zu erkennen?

Hier ein Auszug mit merkwürdigen Eigenschaften:


```
int tmp = checkEntryStatement.getInt(3);
listStatement = conn.getConnection().prepareCall("{?=call list_directories ?,?}");
tmp = checkEntryStatement.getInt(3);
```

Währen die erste Zeile ein Resultat liefert, wirft die dritte Zeile den Fehler aus. Es scheint also, als würde ein zweiter Call das erste Statement zu beeinflussen.


----------



## fritzr (2. Mrz 2008)

gut ich habe rausgefunden wie man diesen Fehler umgehen kann. Ich muss mir hierzu aus der XAConnection mit getConnection() ein neues Connection Objekt holen und dies dann in meiner Methode konsequent benutzen. Seltsam ist das aber schon.


----------



## fritzr (3. Mrz 2008)

maki hat gesagt.:
			
		

> Wenn es nur ein Thread ist der eine Transaktion durchführt, wäre es zB eine Möglichkeit diese in ThreadLocal abzulegen und nach dem Durchlauf von allen Änderungen zu committen, oder eben auch nicht (rollback)



Hallo Maki, den Tipp würde ich gerne genauer verstehen. Ich denke, dass unsere Anwendung nicht so komplex ist, dass man wirklich mit XAConnections arbeiten muss. Kannst du mir ein kleines Beispiel geben, wie du das mit dem ThreadLocal meinst?

Nochmal zusammengefasst:
Wir haben mehrere Methoden getXYZ die eigentlich nicht in einer Transaktion laufen müssen. Dann gibt es z.B. eine removeObject Methode, die selbst auf mehrere SQL-Statements ausführt und so auf jeden Fall in einer Transaktion laufen sollte. 

In unserer Applikation möchte ich nun Methoden ermöglichen, die z.B. 3 Objekte löscht. Dabei sollte die gesamte Arbeit jedoch in einer einzelnen Transaktion laufen, sodass entweder alle oder keines gelöscht werden kann.

Dies führt dazu, dass die removeObject Methode nur einmal aufgerufen wird, dann sollte sie selbst die Transaktion schließen. In anderen Fällen sollte man jedoch die Möglichkeit haben, mehrere Aufrufe zu tätigen, sodass erst am Ende die Transaktion abgeschlossen wird. Die Möglichkeit einer Methodenüberladung besteht natürlich, sodass ich für letztere durchaus Parameter übergeben kann. Ich weiß blos nicht was man da am besten nutzt. Transaktionen? XARessources? Connections?

Da es sich um eine JNDI-Client-Server Architektur handelt, würde ich ungerne, ich glaub es geht nicht mal, die Connectionobjekte nach ausßen reichen. 

Danke im Voraus für die Mühe...


----------



## Gast (5. Mrz 2008)

Fritzr,

Du kannst die Atomikos SimpleDataSourceBean verwenden:

1-starte JTA Transaktion
2-hole Connection Object
3-schicke deine SQL
4-schliesse Connection
5-wiederholen 2 - 4 falls notwendig
6-commit JTA Transaktion

Dies sollte funktionieren und auch der Aufwand des XAConnection vermeiden.

Guy


----------



## fritzr (5. Mrz 2008)

Vielen Dank für die Infos,
nach meinen neusten Tests scheint es so, als würde mir Atomikos in unserem Fall nicht weiterhelfen. Wir haben eine 3-Schichten-Architektur wobei die Middleware in einer, die einzelnen Java-Clients natürlich in einer anderen JVM laufen.

Ich habe zum Testen einen Transaktionsmanager in der Middleware erzeugen lassen und ihn per RMI auch den Clients zur Verfügung gestellt. Es scheint jedoch nicht zu funktionieren, dass die Clients nun Transaktionen starten und commiten können. Ich hörte, hierzu sei die professionelle und kostenpflichtige Variante von Atomikos nötig.

Gibt es alternative Frameworks?


----------

