Für eine ERP-System-Anwendung ist der Persistenzdienst die Schnittstelle, um Daten von der Datenbank zu laden und zu speichern. Ein Anwendungsentwickler benutzt davon hauptsächlich den Transaktionsmanager und den Objektmanager.
Der Persistenzdienst kümmert sich um die Anbindung der Datenbank an das System. Er führt die von der Anwendung angeforderten Datenbankzugriffe aus und kapselt dabei die spezifischen Eigenheiten des verwendeten DBMS. Der Objektmanager des Persistenzdienstes bietet einer Anwendung zwei Möglichkeiten des Datenbankzugriffes. Zum einem stellt er Methoden zum Lesen, Anlegen, Ändern und Löschen auf Ebene der Business Objects zur Verfügung, zum anderen kann die Anwendung über den Persistenzdienst ein beliebiges OQL-Statement ausführen, um Daten zu laden oder zu ändern.
Der Persistenzdienst bildet das in einer Anwendung benutzte objektorientierte Datenmodell in das relationale Modell der Datenbank ab. Aus den OQL-Statements werden die benötigten SQL-Befehle generiert. Das alles passiert für die Anwendung transparent. Dabei erfolgen alle Datenbankzugriffe in den Anwendungen im Rahmen von Transaktionen, die vom Transaktionsmanager verwaltet werden.
Weitere wesentliche Aufgaben des Persistenzdienstes sind:
- Die Verwaltung von Sperren auf Business Objects (Lock-Service).
- Die Cache-Verwaltung zur Minimierung der Datenbankzugriffe (Caching).
- Verwaltung der offen gehaltenen Verbindungen zur Datenbank (Connection-Pooling).
4.1 Transaktionsmanager
Der Transaktionsmanager verwaltet die Transaktionen in einer Session. Er kann eine neue Top-Level- oder Subtransaktion starten und eine Transaktion bestätigen oder abbrechen.
4.1.1 ERP-System-Transaktionen
Eine Transaktion ist eine Klammer für eine schreibende fachliche Datenbankoperation. Sie besteht aus ein oder mehreren Aktionen auf der Datenbank, die alle oder gar nicht ausgeführt werden (Atomarität). Sie werden explizit geöffnet, geschlossen bzw. abgebrochen. Die Änderungen sind erst nacherfolgreichem Abschluss der Transaktion nach außen hin sichtbar. Ein Abbruch macht alle vorigen Änderungen rückgängig. Eine ERP-System-Transaktion entspricht dem Transaktionsverständnis bei relationalen Datenbanksystemen, erfüllt also die ACID-Eigenschaften.
Das ERP-System unterstützt geschlossen geschachtelte Transaktionen. Das bedeutet, dass eine Transaktion im Prinzip Subtransaktionen beliebiger Schachtelungstiefe haben kann (zzt. ist die Schachtelungstiefe auf 20 Ebenen begrenzt). Die äußerste Transaktion heißt Top-Level-Transaktion. Änderungen innerhalb einer Subtransaktion werden nur nach erfolgreichem Ende auch in der jeweiligen Parent-Transaktion sichtbar und beim erfolgreichen Abschluss der Top-Level-Transaktion persistent.
Geschachtelte oder Subtransaktionen sind also Transaktionen, die innerhalb einer bestehenden Transaktion, der Parent-Transaktion, begonnen und beendet werden. Subtransaktionen sind hier nicht-vital, d. h. der Abbruch einer Subtransaktion erzwingt nicht den Abbruch der Parent-Transaktion.
In der Regel werden Softwarekomponenten so ausgelegt, dass sie eine bestimmte, abgeschlossene Leistung erbringen. Das muss oft transaktionssicher geschehen, entweder kann die Komponente die Leistung voll erbringen oder eben nicht. Die Komponente wird also eine Transaktion beginnen und ihre Operationen innerhalb dieser Transaktion ausführen. Bei geschachtelten Komponentenaufrufen ergeben sich so automatisch geschachtelte Transaktionen.
Eine Top-Level-Transaktion und alle ihre Subtransaktionen dürfen nur auf genau eine Datenbank lesend und schreibend zugreifen. Der Transaktionsmanager hat zu einer Zeit immer genau eine aktuelle Transaktion einer Anwendung. Wurde nicht explizit eine Transaktion begonnen, ist implizit eine Dummy-Transaktion auf der OLTP-Datenbank des Mandanten geöffnet. Mit dieser ist nur ein lesender Datenbankzugriff möglich, sie kann keine Subtransaktionen haben.
Transaktionssteuerung in der Anwendung
Der Transaktionsmanager stellt der Anwendung die notwendigen Funktionen zur Verfügung, um Transaktionen zu starten, abzubrechen und zu beenden.
Transaktionen können entweder mit „begin“ oder mit „create“ geöffnet werden. Beim Öffnen einer Transaktion mit „create“ entsteht ein Transaktionsobjekt, das die Schnittstelle „AutoCloseable“ implementiert. Wenn Sie neue Anwendungen und Funktionen entwickeln, sollten Sie „create“ anstatt „begin“ verwenden. Viele ältere Anwendungen verwenden „begin“.
beginNew() oder createNew()
Mit beginNew() und mit createNew() werden neue Top-Level-Transaktionen in der Anwendung erzeugt. Es kann ein Datenbankalias oder eine Datenbank-GUID als Parameter übergeben werden. Wird keine Datenbank übergeben, so wir die aktive OLTP-Datenbank der Session des Benutzers verwendet. Der Transaktionsmanager stellt die verfügbaren Datenbankaliases als Konstanten zur Verfügung:
- OLTP bezeichnet die aktive OLTP-Datenbank der Session.
- OLAP bezeichnet die aktive OLAP-Datenbank der Session.
Wenn Sie eine Transaktion mit createNew() erzeugt haben, dürfen Sie diese Transaktion nicht mit dem Transaktionsmanager schließen, sondern Sie müssen die Methoden an der Transaktion verwenden.
begin() oder create()
Mit begin() und mit create() werden neue Transaktionen erzeugt. Es kann ein Datenbankalias oder eine Datenbank-GUID als Parameter übergeben werden. Existiert bereits ein Transaktionskontext von einer offenen Transaktion, wird eine Subtransaktion angelegt. Stimmt die angegebene Datenbank nicht mit der Datenbank der offenen Transaktion überein wird eine Exception geworfen. Existiert keine offene Transkation wird eine Top-Level-Transaktion auf der angegebenen oder, wenn kein Datenbankalias übergeben wurde, auf der OLTP-Datenbank der Session erzeugt. Allerdings sollte man eine Top-Level-Transaktion immer dann explizit mit beginNew() oder mit createNew() statt mit begin() erzeugen, wenn sicherzustellen ist, dass die Änderungen bei commit() auch auf die Datenbank geschrieben werden.
Existiert schon ein offener Transaktionskontext und man benutzt begin() bzw. create(), wird eine Subtransaktion erzeugt, die zwar mit commit() erfolgreich beendet wird, aber die Parent-Transaktion kann abgebrochen werden. Damit gehen die Änderungen der Subtransaktionen verloren.
Wenn Sie eine Transaktion mit create() erzeugt haben, dürfen Sie diese Transaktion nicht mit dem Transaktionsmanager schließen, sondern Sie müssen die Methoden an der Transaktion verwenden.
Subtransaktionen sind unabhängig davon, ob die übergeordnete Transaktion mit „create“ oder „begin“ geöffnet wurde. Das bedeutet, Sie können innerhalb einer mit begin() oder beginNew() geöffneten Transaktion Subtransaktionen mit create() öffnen oder innerhalb einer mit create() oder createNew() geöffneten Transaktion Subtransaktionen mit begin() öffnen.
commit()
Mit commit() wird die aktuelle Transaktion erfolgreich beendet. Handelt es sich um eine Top-Level-Transaktion, so werden die registrierten Objekte in die Datenbank geschrieben oder gelöscht. Bei einer Subtransaktion werden die registrierten Objekte noch nicht in die Datenbank geschrieben, sondern an die Parent-Transaktion vererbt, d.h. in deren transaktionslokalen Kontext eingefügt, und sind somit für Brudertransaktionen sichtbar. Erst wenn die zugehörige Top-Level-Transaktion mit commit() beendet wird, werden diese Objekte in der Datenbank persistent gemacht. Die gehaltenen Sperren einer Subtransaktion werden an die Parent-Transaktion vererbt, die Sperren einer Top-Level-Transaktion werden aufgehoben.
Wenn Sie eine Transaktion mit create() oder createNew() erzeugt haben, schließen Sie die Transaktion mit commit() an der Transaktion. Verwenden Sie nicht das commit() am Transaktionsmanager. Nach dem commit() muss unbedingt auch die Methode close() an der Transaktion aufgerufen werden.
rollback()
Ein rollback() bricht die aktuelle Transaktion ab. Das bedeutet, dass alle Änderungen, die dem Persistenzdienst während dieser Transaktion oder einer ihrer Subtransaktionen mitgeteilt wurden (z.B. mithilfe von putObject oder deleteObject), verworfen werden. Dabei ist es unerheblich, ob die abgebrochene Transaktion eine Top-Level- oder eine Subtransaktion ist. Wurde eine Subtransaktion abgebrochen, so bleibt der transaktionslokale Kontext der Parent-Transaktion unverändert, also in demselben Zustand wie beim Start der Subtransaktion. Die Parent-Transaktion wird nach dem Abbruch wieder zur aktuellen Transaktion. Wurde eine Top-Level-Transaktion abgebrochen, wird die immer vorhandene Dummy-Transaktion zur aktuellen. Alle von der abgebrochenen Transaktion gehaltenen Sperren werden aufgehoben.
Die folgende Abbildung stellt schematisch eine Top-Level-Transaktioen mit einer Subtransaktion da, welche ebenfalls eine Subtransaktion hat.
Erst beim commit() der Top-Level-Transaktion werden die Änderungen auch auf der Datenbank persistent. Zur Isolation der transaktionslokalen Kontexte werden die State-Objekte und der Transaction-Cache verwendet.Transaktionsabhängigkeit.
Alle vom Objektmanager gelieferten Business-Object-Instanzen gehören zu genau der Transaktion, in der sie geladen bzw. erzeugt wurden. Sie dürfen nur in dieser Transaktion verwendet werden. Wenn die Transaktion, in der sie geladen wurden, geschlossen wird, so sind die Business-Object-Instanzen ungültig.
Um Business Objects bzw. ihre Attribute transaktionsübergreifend zu verwenden, ist es notwendig, ein transaktionsloses, transientes Business Object aus ihnen zu erzeugen (getTransientCopy). Der Inhalt eines transienten Business Objects kann dann in andere, transaktionsbezogene Business Objects (copyTo) kopiert werden.
Wenn Sie eine Transaktion mit create() oder createNew() erzeugt haben, schließen Sie die Transaktion mit close() an der Transaktion. Sie dürfen die Transaktion nicht mit rollback() am Transaktionsmanager schließen.
4.1.2 Sichtbarkeit von Änderungen in geschachtelten Transaktionen
Die Sichtbarkeit von Änderungen an den Business-Object-Instanzen, damit ist die Isolationseigenschaft von Transaktionen gemeint, wird auf drei Ebenen gesteuert.
Aktuelle Transaktion mit Subtransaktionen
Auf der Ebene der aktuellen Transaktion und ihren Subtransaktionen sind Attributänderungen nur in der jeweiligen Business-Object-Instanz bekannt, erst nach einem putObject() werden sie für die aktuelle Transaktion und für alle folgenden Subtransaktionen sichtbar, d.h. ein erneutes getObject() liefert die geänderte Business-Object-Instanz. Bereits geladene Instanzen des geänderten Business Object haben noch den alten Wert, bis sie erneut mit getObject() gelesen werden.
Parent-Transaktion der ändernden Transaktion
Attributänderungen sind in der Parent-Transaktion der ändernden Transaktion nur sichtbar, wenn die Business-Object-Instanz in der ändernden Transaktion mit putObject gespeichert wurde, diese Transaktion erfolgreich mit commit() beendet worden ist und die Business-Object-Instanz mit getObject() neu gelesen wird. Das gilt entsprechend auch für tiefere Schachtelungsebenen.
Änderung des Datenbankinhaltes
Der Inhalt der Datenbank reflektiert Änderungen (Insert, Update, Delete) erst dann, wenn die Top-Level-Transaktion erfolgreich mit commit() beendet wurde. Erst zu diesem Zeitpunkt werden alle Änderungen einer Top-Level-Transaktion und ihrer erfolgreich beendeten Subtransaktionen in die Datenbank geschrieben.
Sie sind danach global sichtbar. Es ist ein erneutes Lesen notwendig, da vor der ändernden Transaktion gelesene Business-Object-Instanzen noch die alten Werte enthalten. Update-Statements arbeiten immer auf den tatsächlich persistenten Daten, d.h. vorgenommene Änderungen an Business-Object-Instanzen in der gleichen Transaktion sind für OQL-Abfragen noch nicht sichtbar.
4.2 Cache-Verwaltung
Der Persistenzdienst benutzt zwei Caches, die unterschiedliche Aufgaben erfüllen.
4.2.1 Shared Cache
Der Shared Cache dient der Zwischenspeicherung von früher geladenen Business-Object-Instanzen im Hauptspeicher, um Datenbankzugriffe zu vermeiden.
Er trägt damit in ganz erheblichem Maße zur hohen Leistung des ERP-Systems bei. Will eine Anwendung ein Business Object von der Datenbank lesen, so wird zuerst nachgesehen, ob das Business Object im Shared Cache vorhanden ist, ansonsten wird es von der Datenbank gelesen. Der Shared Cache ist in jedem Application-Server, der Datenbankzugriffe ausführt, genau einmal vorhanden (Singleton) und spiegelt in der Regel die wichtigsten bzw. die zuletzt verwendeten Datenbankinhalte wider.
In einer verteilten Umgebung mit mehr als einem Application-Server würde es einen nicht gerechtfertigten hohen Aufwand bedeuten, wenn die Objekte in allen Shared Caches zu jeder Zeit den gleichen Zustand haben wie in der Datenbank. In der Regel reicht es, wenn in einem bestimmten Zeitintervall (30 Sekunden) die Objekte im Shared-Cache aktualisiert werden, bzw. können Änderungen an Customizing- oder Stammdaten ohne Gefahr in anderen Application-Servern erst 30 Sekunden später wirksam werden.
4.2.2 Transaction Cache
Der Transaction-Cache wird verwendet, um einen transaktionslokalen Kontext herzustellen. Jede Top-Level-Transaktion hat ihren eigenen Transaction-Cache. Er entsteht beim Erzeugen einer neuen Top-Level-Transaktion und wird für Schreibzugriffe (Anlegen, Ändern, Löschen) benötigt.
Um eine Business-Object-Instanz zu ändern, wird sie zuerst zum Ändern mit getObject geladen. Der Object Manager sperrt die Instanz, so dass sie während dieser Zeit in keinen anderen Transaction Cache aufgenommen werden kann.
Wird innerhalb einer anderen Transaktion versucht, dieselbe Business-Object-Instanz zu laden, wird eine bestimmte Zeit auf Freigabe gewartet, bis es zu einem Time Out kommt. Solange eine Transaktion nicht mit commit abgeschlossen wurde, werden die Objekte, die innerhalb dieser Transaktion mit putObject oder deleteObject zum Speichern registriert wurden, nur im Transaction-Cache gehalten.
Der Object-Manager sucht mit getObject angeforderte Objekte zuerst im jeweiligen Transaction-Cache. Dadurch wird sichergestellt, dass er vorher zum Speichern registrierte Objekte in dem Zustand zurückgeliefert werden, in dem sie registriert wurden, und nicht wie sie im Shared-Cache bzw. in der Datenbank stehen (Isolation).
Der Transaction Cache wird beim commit()/rollback() der Top-Level-Transaktion wieder aufgelöst:
- Beim commit() werden die Änderungen in die Datenbank übernommen. Standardmäßig werden Sie auch in den Shared Cache übernommen. Die gehaltenen Sperren werden aufgehoben.
- Beim rollback() werden sämtliche Änderungen verworfen, sie werden also weder in die Datenbank noch in den Shared Cache übernommen.
Die folgende Abbildung zeigt das Zusammenspiel von Transaction Cache, Shared Cache und Datenbank.
Kann ein Objekt im Transaction Cache nicht gefunden werden, laufen lesende Zugriffe auf die Datenbank generell über den Shared Cache. Wird die Business-Object-Instanz nicht im Shared Cache gefunden, wird sie von der Datenbank geladen, im Shared Cache abgelegt und im Transaction Cache gespeichert. Subtransaktionen haben jeweils ihren eigenen Bereich im Transaction Cache, in dem ihre geladenen und zum Speichern registrierten Business-Object-Instanzen liegen. Wird eine Subtransaktion mit commit beendet, werden die zum Speichern registrierten Business-Object-Instanzen in den Transaction-Cache der Parent-Transaktion übernommen. Beim commit der Top-Level-Transaktion werden die zum Speichern registrierten Business-Object-Instanzen in die Datenbank gespeichert und im Shared Cache aktualisiert. Danach wird der Transaction-Cache der beendeten Transaktion aufgelöst.
Der Shared Cache kann komplett umgangen werden, indem ein entsprechender Zugriffsmodus (IGNORE_SHARED_CACHE , BYPASS_CACHE) verwendet wird. Dann wird die Business-Object-Instanz – soweit vorhanden – direkt aus der Datenbank geladen, ohne die Cache-Inhalte zu beachten bzw. diese zu aktualisieren. Das ist sinnvoll für Tests, die Datenbankinhalte überprüfen und wird bei der Synchronisation des Shared-Cache mit den Datenbankinhalten verwendet. Aus Anwendungssicht kann das sinnvoll sein, um die Performance zu verbessern, wenn feststeht, dass die zu lesenden bzw. zu schreibenden Business Objects nicht „gecached“ werden sollen (z. B. bei temporären Tabellen).
Da der Hauptspeicher, der einem Application-Server zur Verfügung steht, begrenzt ist, können nicht alle Objekte gleichzeitig im Cache gehalten werden. Deshalb wird je Business-Object-Definition eine Cache-Priorität vergeben. Anhand dieser Priorität wird bei Konflikten entschieden, welches Objekt im Cache verbleibt und welches nicht. Die Prioritäten reichen von „nicht cachen” (z.B. für Logs) bis „immer im Cache halten” (z.B. Metadaten) Bei Anlage eines neuen Business Objects ist es wichtig, das Caching-Verhalten richtig einzustellen.
4.3 Objektmanager
Der Objektmanager ist die Schnittstelle für Anwendungen, mit der:
- Business Objects geladen bzw. erzeugt werden können (Methode getObject()),
- Business-Object-Iteratoren erzeugt werden können (Methode getObjectIterator ()),
- beliebige lesende OQL-Anweisungen (SELECT) ausgeführt werden können (Methode getResultSet()),
- Business Objects an der aktuellen Transaktion zum Speichern registriert werden können (Methode putObject()),
- Business Objects an der aktuellen Transaktion zum Löschen registriert werden können(Methode deleteObject()),
- beliebige schreibende OQL-Anweisungen (UPDATE/INSERT/DELETE) ausgeführt werden können (Methode getUpdateStatement()).
Alle im ERP-System verwendeten Business Objects werden mit Hilfe des Objektmanagers geladen, erzeugt, gespeichert oder gelöscht. Der Zugriff auf ein Business-Object-Instanz erfolgt mit einem technischen Schlüssel, der aus der GUID der Datenbank, der GUID der Business-Object-Klasse und den die Instanz identifizierenden Schlüssel (z.B. Primärschlüssel/Business Key) gebildet wird.
Die zum Business-Object generierte Java-Klasse besitzt Methoden, um den technischen Schlüssel zu erzeugen (build…Key()) und abzufragen (get…Key()). Technische Schlüssel sind als Byte-Array (byte[]) ausgeprägt. Die generierte Java-Klasse zu einem Business Object ist abgeleitet von der Klasse „CisObject”, um ein uniformes Verhalten sicherzustellen.
Implizit ist in einer Anwendung immer eine Dummy-Transaktion für den aktiven Mandanten geöffnet, mit der nur Lesezugriffe erlaubt sind. Schreibende Zugriffe (z.B. Speichern, Löschen) sind nur innerhalb einer explizit geöffneten Transaktion erlaubt. Die nicht transienten Business-Object-Instanzen gehören immer zu der Transaktion, in der sie geladen oder geändert wurden, bei Ende der Transaktion werden sie ungültig. Transaktionsunabhängige Objekte (transient) können mit der Methode getTransientCopy(), die alle Business Object-Klassen besitzen, erzeugt werden. Diese Objekte gehören zu keiner Transaktion.
4.3.1 Methode getObject()
Mit getObject() kann eine Business-Object-Instanz geladen werden, dabei wird der transaktionslokale Kontext beachtet. Als Parameter wird der technische Schlüssel und optional der Zugriffmodus erwartet. Zusätzlich kann die Inhaltssprache für das zu ladende Objekt vorgegeben werden, d.h. alle lokalisierbaren Attribute des Business Objects werden, soweit vorhanden, mit den Werten der Content-Language gefüllt. Liegt zu einem Attribut keine Übersetzung vor, so wird die Datenbanksprache für dieses Attribut genommen.
4.3.2 Methode getObjectIterator()
Mit der Methode getObjectIterator() kann eine Menge von Business-Object-Instanzen geladen werden. Dafür liefert die Methode einen Iterator über die Ergebnismenge der angegeben OQL-Anfrage zurück. Als Parameter wird das OQL-SELECT-Statement und optional der Zugriffmodus erwartet. Zusätzlich ist wie bei getObject() die Inhaltssprache für die zu ladenden Instanzen angebbar. Wird bei zeitabhängigen Business Objects weder validFrom noch validUntil in dem OQL-Statement benutzt, werden nur die aktuell gültigen Instanzen zurückgegeben. Wenn validFrom oder validUntil in irgendeinem Teil des OQL-Statements benutzt wird, werden alle verfügbaren Versionen, auf die die Bedingung des OQL-Statements zutrifft, zurückgegeben. Da der Objekt-Iterator „AutoClosable“ ist, wird der Objekt-Iterator, wenn dieser im try zugewiesen wurde, auch ohne „finally“ automatisch beim Verlassen des try-Blocks geschlossen.
Beispiel für die Verwendung des Object-Iterators ohne Zuweisung im try:
CisObjectIterator iter<Book>= om.getObjectIterator(„select from
com.cisag.app.edu.obj.Book o where o:title like ?“);
om.setString(1, „%Ringe%“);
try {
while (om.hasNext()) {
Book book= iter.next();
…
}
} finally {
iter.close();
}
Der Objekt-Iterator wird geschlossen, wenn das letzte Element des Iterators gelesen wurde oder wenn close() aufgerufen wurde.
Beispiel für die Verwendung des Object-Iterators mit create() und try:
try (CisObjectIterator<Book> iter= om.getObjectIterator(“select from com.cisag.app.edu.obj.Book o where o:title like ?”)) {
om.setString(1, “%Ringe%”);
while (om.hasNext()) {
Book book= iter.next();
…
}
}
Die Klasse „CisObjectIterator“ ist ein „AutoClosable“ und kann daher mit try verwendet werden. Der Iterator wird am Ende des try-Blocks automatisch mit close() geschlossen.
4.3.3 Methode getResultSet()
Mit der Methode getResultSet() können Attribute von Business Objects abgefragt werden. Dafür liefert die Methode ein Result-Set, welches das Ergebnis einer OQL-Anfrage enthält. OQL entspricht weitestgehend Standard-SQL. Die Abweichungen werden im Folgenden aufgezeigt:
- Als Tabellenname wird der vollständige Business-Object-Name benutzt (mit Namensraum), für den ein Tabellen-Aliasname vergeben werden muss.
- Als Spaltenamen werden die vollständigen Attributpfade benutzt, z.B. „holdQuantity[0].amount”.
- Jeder Spaltenname muss mit einem Tabellen-Alias qualifiziert werden. Der Tabellen-Alias wird mit “:“ von dem Spaltennamen getrennt.
Das als Parameter übergebene OQL-Statement kann Platzhalter für Werte enthalten, die wie bei SQL mit “?“ gekennzeichnet werden. Es werden weder Zeitabhängigkeit noch lokalisierte Attribute beachtet. Die Anfrage ist an die aktuelle Transaktion gebunden.
Das Ergebnis der Anfrage ist ein Result-Set. An diesem Result-Set müssen die im OQL-String vorgesehenen freien Parameter gesetzt werden. Die Auswertung der Anfrage erfolgt bei der ersten Abfrage des Ergebnisses mithilfe von next(). Das Result-Set kann nach Aufruf der Methode close() mit neuen Parametern wieder verwendet werden.
Solange das Result-Set nicht geschlossen wurde, wird exklusiv eine Datenbankverbindung belegt. Deshalb ist es wichtig, dass das Result-Set unter allen Umständen mit geschlossen wird. Die Verwendung eines Result-Sets sollte immer innerhalb eines try/catch -Blockes erfolgen. Im finally-Block ist es, wenn es noch offen ist, mit close() zu schließen. Da das Result-Set „AutoClosable“ ist, wird das Result-Set, wenn dieses im try zugewiesen wurde, auch ohne „finally“ automatisch beim Verlassen des try-Blocks geschlossen. Ein rollback()- bzw. commit()-Kommando oder das close() an der Transaktion schließt implizit alle Result-Sets, die in der Transaktion geöffnet wurden.
Beispiel für die Verwendung eines Result-Sets ohne Zuweisung im try:
…
CisResultSet rs= om.getResultSet(„select o:guid, o:number from
com.cisag.app.edu.obj.Book o where o:title like ?“);
try {
rs.setString(1, „%Ringe%“);
while (rs.next()) {
byte[] guid= rs.getGuid(1);
String number= rs.getString(2);
…
}
}
finally {
if (rs != null && !rs.isClosed()) {
rs.close();
}
}
…
Beispiel für die Verwendung eines Result-Sets mit Zuweisung im try:
try ( CisResultSet rs= om.getResultSet(
“select o:guid, o:number from
com.cisag.app.edu.obj.Book o where
o:title like ?”)) {
rs.setString(1, “%Ringe%”);
while (rs.next()) {
byte[] guid= rs.getGuid(1);
String number= rs.getString(2);
…
}
}
…
4.3.4 Methode putObject()
Mit der Methode putObject() wird eine Business-Object-Instanz in der Transaktion zum Speichern registriert. Die vorgenommenen Änderungen werden im aktuellen transaktionslokalen Kontext sichtbar. Beim Commit der Transaktion wird die Registrierung in den Transaktionskontext der Parent-Transaktion übernommen und ist dort auch für folgende Subtransaktionen sichtbar. Wenn das Business Object Update-Informationen (Attribut UpdateInformation) enthält, werden bei diesem Aufruf Zeitpunkt und User der letzten Änderung aktualisiert.
4.3.5 Methode deleteObject()
Mit der Methode deleteObject() wird eine Business Object-I nstanz an der Transaktion zum Löschen registriert und aus dem aktuellen transaktionslokalen Kontext entfernt. Das Sichtbarkeitsverhalten der Löschung entspricht im Wesentlichen der Methode putObject().
4.3.6 Zusammenspiel von Objektmanager und Caches
Wird eine Business-Object-Instanz in einer Anwendung mit getObject() geladen, sucht der Objektmanager die Instanz zuerst im Transaction-Cache der aktuellen Transaktion. Wurde es dort nicht gefunden, wird als zweites versucht, die Instanz aus dem Shared Cache zu laden. Konnte es dort ebenfalls nicht gefunden werden, erfolgt als drittes der Lesezugriff auf der Datenbank. Beim Schreiben mit putObject() wird die Business-Object-Instanz im Transaction-Cache der aktuellen Transaktion der Anwendung abgelegt. Beim Beenden der Top-Level-Transaktion mit commit() wird die Instanz im Shared Cache abgelegt und auf der Datenbank persistent gemacht. Die nachstehende Abbildung skizziert den Zusammenhang zwischen Objektmanager und Caches.
4.3.7 Update-Statements
Über OQL UPDATE, INSERT oder DELETE-Statements ist möglich, Statements auszuführen. Damit kann man mehrere Business-Object-Instanzen mit einem Befehl ändern, einfügen oder löschen.
Ein Update-Statement wird erst beim Commit der Top-Level-Transaktion ausgeführt. Vorher hat das Update-Statement auf die in der Transaktion zu ladenden Daten keinen Effekt. Beim Commit der Top-Level-Transaktion werden die Update-Statements immer zuerst ausgeführt. Diese dürfen sich nicht auf dieselben Business Objects beziehen, die innerhalb derselben Transaktion mit putObject()/deleteObject() zum Ändern/Löschen registriert wurden.
Ein Update-Statement sperrt die gesamte Tabelle auf der das Statement arbeitet, keine andere Transaktion kann eine Business-Object-Instanz dieses Typs laden. Die Performance z.B. beim Löschen von vielen Datensätzen ist mit einem OQL-Delete deutlich höher als über den Persistenzdienst.
Leider hat die Benutzung von Update-Statements ernste Einflüsse auf die Gesamtperformance des Systems. Nach der Benutzung eines Update-Statements werden alle Instanzen dieses Business Objects auf dieser Datenbank aus allen Shared Caches im gesamten System entfernt. Hängen andere Anwendungen davon ab, dass die Instanzen zu diesem Business Object im Cache gehalten werden, so vermindert sich deutlich die Performance. Update-Statements eignen sich also primär für die folgenden Anwendungsfälle:
- Für das Modifizieren/Löschen nicht im Cache gehaltener Business Objects.
- Für das Löschen temporärer Daten.
Beispiel mit beginNew:
tm.beginNew();
try {
CisUpdateStatement s = om.getUpdateStatement(“DELETE FROM com.cisag.app.edu.obj.Book o”);
om.putUpdateStatement(s);
tm.commit();
}
catch (Exception e) {
tm.rollback();
}
Beispiel mit createNew:
Try (CisTransaction txn=tm.createNew()) {
CisUpdateStatement s = om.getUpdateState-ment(“DELETE FROM com.cisag.app.edu.obj.Book o”);
om.putUpdateStatement(s);
txn.commit();
}
4.3.8 Zugriffsmodus
Der Zugriff auf Business-Object-Instanzen wird über den Zugriffmodus gesteuert. Dazu kann beim Aufruf der entsprechenden Methode ein Flag, das den Zugriffsmodus angibt, übergeben werden. Um Business Objects anlegen, ändern und löschen zu können, muss der richtige Zugriffsmodus verwendet werden. Ist kein Zugriffmodus gesetzt, wird standardmäßig der Modus READ verwendet.
Der Zugriffsmodus READ liefert Business-Object-Instanzen nur zum Lesen, sie können nicht geändert werden.
- Der Zugriffsmodus READ_UPDATE liefert Business-Object-Instanzen, die beliebig geändert oder gelöscht werden dürfen.
- Der Zugriffsmodus READ_WRITE verhält sich wie READ_UPDATE. Aber: Existiert die Instanz, auf welche zugegriffen werden soll nicht, wird eine neue Instanz zurückgegeben. Enthält die neue Instanz das Attribut UpdateInformation, wird der Zeitpunkt der Erstellung und User in der UpdateInformation
- Der Zugriffsmodus CACHE_ONLY veranlasst, dass die Business-Object-Instanzen nur im Cache gesucht werden. Sind sie dort nicht vorhanden, wird, selbst wenn sie in der Datenbank vorhanden sind, null zurückgeliefert.
- Der Zugriffsmodus BYPASS_CACHE veranlasst, dass die Business-Object-Instanzen direkt aus der Datenbank gelesen werden. Sowohl der Shared Cache als auch der Transaktion-Cache werden hierbei ignoriert.
- Der Zugriffsmodus IGNORE_SHARED_CACHE bewirkt dasselbe wie BYPASS_CACHE, ausser, dass die Business-Object-Instanzen nach dem Lesen nicht im Shared Cache abgelegt werden. Damit wird verhindert, dass die gelesenen Objekte wichtigere Objekte im Cache verdrängen.
- Der Zugriffsmodus READ_PARALLEL ist nützlich, wenn man aus einer anderen Transaktion heraus die Dummy-Transaktion zum Lesen benutzen möchte. Damit ist möglich, die Gültigkeit von Objektiteratoren und ResultSets an die Dummy-Transaktion zu binden und nicht an die aktuelle Transkation. Auf der Datenbank muss die Dummy-Transaktion offen sein. Deshalb funktioniert diese Option nur auf dem Mandanten, der dem Benutzer zugeordnet ist.
- Der Zugriffsmodus READ_REPEATABLE setzt einen Read-Lock auf das Business Object und stellt so sicher, dass es nicht von anderen Transaktionen geändert werden kann. Damit liefert dieser Zugriffsmodus garantiert konsistente Daten, auch wenn mehrere Application Server auf der OLTP-DB arbeiten. Beim normalen READ-Flag ist das nicht so, da der Cache-Abgleich zwischen den Servern nur alle 30s erfolgt und damit ein Business Object mit veralteten Attributwerten im Cache stehen kann.
- Der Zugriffsmodus INSERT schaltet die Existenzprüfung ab und erzeugt unabhängig vom Datenbankinhalt ein neues nicht persistentes Objekt.
Wenn Business Objects neu angelegt werden und durch die Programmlogik bekannt ist, dass diese Objekte auf der Datenbank noch nicht existieren, ist es unnötig, dass der Persistenzdienst deren Existenz auf der Datenbank prüft. Falls entgegen dieser Annahme das Business Object dennoch auf der Datenbank existiert, gibt es einen Fehler beim Beenden der Top-Level-Transaktion mit commit(). - Der Zugriffsmodus NEW_VERSION veranlasst, dass immer eine neue leere Instanz eines Business Objects angelegt wird, ohne den Cache oder die Datenbank zu berücksichtigen. Diese Option ist nur im Insert-Only-Modus bei zeitabhängigen Objekten sinnvoll.
4.3.9 Inhaltssprache (Content-Language)
Mit der Inhaltssprache wird gesteuert, in welcher Sprache die lokalisierbaren Attribute (NLS-Attribute) geladen werden. Wenn keine Sprache bei einer Abfrage angegeben wird, so wird die Inhaltssprache genutzt, die der angemeldete Benutzer in seiner Session eingestellt hat. Der Default für die Session ist die Inhaltssprache des Benutzers aus der Konfiguration.
4.3.10 Änderungsinformationen/„Fachliches“ Löschen
Wenn das Flag maintainUpdateInfo in den Metadaten eines Business Objects gesetzt ist, so wird das Attribut UpdateInformation zum Business Object hinzugefügt, welches die Zeitpunkte und den Benutzer vermerkt, der das Objekt erstellt, als letztes verändert oder „fachlich“ gelöscht hat. Die „fachliche“ Löschmarkierung wird vom Persistenzdienst nicht ausgewertet, es obliegt den Anwendungen das zu tun. Der Kernel speichert, wenn das Flag maintainUpdateInformation an der entsprechenden Business-Object-Definition gesetzt ist, die Erstellungs-, Änderungs- und Löschzeitpunkte und den jeweiligen Benutzer zu jeder Business-Object-Instanz. Änderungsoperationen an den Dependents eines Business Entitys werden bei dem zugeordneten Business Entity eingetragen. Der Löschzeitpunkt ist in diesem Fall nur ein Flag, d. h. ein Business Object mit Löschzeitpunkt wird nicht physikalisch gelöscht, sondern nur als gelöscht markiert, sodass die Anwendung diese Information berücksichtigen kann und muss.
4.3.11 Zeitabhängigkeit
Alle Leseoperationen des Objektmanagers arbeiten auf dem jeweils aktuell gültigen Datensatz eines Business Objects. Bei zeitabhängigen Business Objects ist die Instanz aktuell gültig, in dessen Gültigkeitsintervall (validFrom, validUntil) der aktuelle Zeitpunkt liegt. Dabei gehört der Zeitpunkt von validFrom zum Gültigkeitszeitraum, der von validUntil angegebene Zeitpunkt nicht. Der Persistenzdienst geht davon aus, dass die Gültigkeitsintervalle aller gespeicherten Business-Object-Instanzen mit dem gleichen Primärschlüssel lü-ckenlos und überlappungsfrei sind.
Zur Pflege von zeitabhängigen Daten kennt der Persistenzdienst den Insert-Only- und den Update-Modus. Instanzen zeitabhängiger Business Objects können genauso wie alle übrigen Business Objects über die Methoden putObject() und deleteObject() gespeichert bzw. gelöscht werden. Über die Belegung der Attribute validFrom bzw. validUntil der Instanz wird gesteuert, in welchem dieser Modi die Zeitabhängigkeit gehandhabt wird.
4.3.11.1 Insert-Only-Modus
Dieser Modus wird zur Anlage einer neuen Version benutzt. Dazu muss mit dem Flag NEW_VERSION eine neue Instanz über den Persistenzdienst erzeugt werden. Die Attribute validFrom und validUntil müssen auf null gesetzt sein, bevor die Instanz mit putObject gespeichert wird, da dann diese Attribute vom Persistenzdienst selbst berechnet werden. Eine so erzeugte Instanz ist ab dem aktuellen Zeitpunkt (validFrom) bis zum Maximaldatum gültig. Das validUntil-Datum der bisher letzten Version wird auf das validFrom-Datum der neuen Version gesetzt, wenn es nach diesem liegt, damit keine Lücke entsteht. Liegt es vor dem validFrom-Datum der neuen Version, wird es nicht abgeändert und es entsteht eine Lücke.
Die folgende Abbbildung veranschaulicht die Funktionsweise:
4.3.11.2 Update-Modus
Der Update-Modus dient zum Einfügen einer neuen Version zwischen existierende Versionen einer Business-Object-Instanz. Der Beginn des Gültigkeitszeitraum (validFrom) der einzufügenden Version wird vorgegeben, dessen Attribut validUntil ist auf null zu setzen. Der Kernel beschränkt in diesem Fall automatisch den maximalen Gültigkeitszeitpunkt (validUntil) der zu diesem Zeitpunkt gültigen Version und setzt das Attribut validUntil der einzufügenden Version auf den entsprechenden Wert. Ist keine Version zu diesem Zeitpunkt gültig, entsteht ein Lücke.
Bei der Verwendung des Update-Modus sollte die Instanz mit dem Flag READ_WRITE und dem Time-Dependent-Key geladen werden, damit eine ggf. existierende Version der Business-Object-Instanz berücksichtigt wird.
Die folgende Abbbildung veranschaulicht die Funktionsweise:
Wenn bei einem Business Object validFrom und validUntil gesetzt sind, d. h. nicht null, führt der Persistenzdienst keine weiteren Operationen aus, um die Konsistenz der Gültigkeitsintervalle zu warten. In diesem Fall wird also genau die Operation ausgeführt, die die Anwendung vorgibt ohne Rücksicht auf mögliche inkonsistente Zustände (Überlappung von Zeitintervallen, Lücken) als Ergebnis dieser Operation.
4.4 Sperrverwaltung
Die Sperrverwaltung dient der Synchronisation schreibender Transaktionen auf Business-Object-Instanzen. Auf einer Instanz wird eine exklusive Sperre für die aktuelle Transaktion gehalten, wenn es mit getObject() (Flags READ_WRITE, READ_UPDATE) gelesen wurde. Das Ändern oder Löschen ist nur in der aktuellen Transaktion möglich, eine andere Transaktion kann das Business Object mit dessen alten Wert nur lesen. Versucht eine andere Transaktion ein so gesperrtes Business Object zum Schreiben zu laden, kommt es zu einem Timeout nach einer Wartezeit von 60 Sekunden. Als Folge wird eine Exception geworfen, die der Programmierer geeignet behandeln sollte. Die Standardvorgehensweise ist, die gefangene Exception an die Message Queue der Anwendung zu senden (mm.sendMessage()), wo an zentraler Stelle bestimmte Exception-Typen generisch in entsprechende Meldungen umgewandelt werden. In diesem Falle würde eine Meldung der Form „Objekt ist gesperrt von …“ erzeugt. Sperren von Subtransaktionen werden an die Parent-Transaktion vererbt, unabhängig von commit() oder rollback(). Die Aufhebung der Sperren erfolgt erst beim commit() oder rollback() der Top-Level-Transaktion.
Das Sperren erfolgt über den Primärschlüssel. Dieser kann daher – im Gegensatz zum Business Key – niemals nachträglich direkt geändert werden (nur über Löschen und Neuanlage).
Anwendungen sollten ihre DB-Operationen blockweise abwickeln mit dem Ziel so wenig wie möglich und nur so lange wie nötig auf die Datenbank zuzugreifen. Das heißt:
- Während einer längeren Bearbeitung (z. B. in einer GUI-Anwendung) wird keine Transaktion offen gehalten und somit keine Business-Object-Instanz für andere Anwendungen gesperrt.
- Die Pflege der Daten erfolgt in einer geeigneten Datenstruktur z. B. über eine transiente (transaktionsunabhängiges) Instanz als Kopie der geladenen Business-Object-Instanz.
- Transaktionen sollten möglichst kurz offen gehalten werden. Deshab sollte nur unbedingt benötigter Programm-Code innerhalb einer Transaktion ausgeführt werden (Transaktion öffnen, Objekt mit Schreibflags lesen, Änderungen übernehmen, Transaktion bestätigen).