6 Anwendungsschichten Klassische Anwendungen sowohl aus der ABAP- als auch aus der Java-/J2EE-Welt lassen sich mit einem dreischichtigen Modell beschreiben. So enthält jede Applikation gewisse Anteile an Datenhandling, Businesslogik und Präsentationslogik. In diesem Kapitel betrachten wir alle Schichten für beide Seiten. 6.1 Beschaffungslogik und Persistenz Die Schicht der Datenbeschaffungslogik und Persistenz hat die Aufgabe, der nachfolgenden Businesslogik, die mit Daten umgehen und diese prozessieren muss, dieselben zur Verfügung zu stellen. Ziel dabei ist es, mit der Beschaffungslogik gegenüber der Businesslogik eine gewisse Abstraktion gegenüber den unzähligen Ressourcen, auf die zugegriffen werden kann – Datenbanken, Dateien, Remote-Aufrufe und Services –, zu etablieren und den Zugriff im Optimalfall vollständig zu standardisieren, im Regelfall aber zumindest stark zu vereinfachen und die technischen Details, die gegenüber der Businesslogik nicht relevant sind, auszublenden. Die folgenden Unterkapitel stellen die Möglichkeiten der beiden Plattformen vor, mit verschiedenartigsten Ressourcen umzugehen. 6.1.1 ABAP Open SQL SQL (Structured Query Language) ist eine strukturierte Abfragesprache für relationale Datenbanken. Sie steht für nahezu jedes RDBMS (Relationales Datenbank-Management-System) zur Verfügung, allerdings in herstellerabhängigen Varianten. Die SQL-Standards von ANSI (American National Standards Institute) und ISO (International Standards Organization) dienen meist nur als Richtlinien, an die sich die Datenbankhersteller mehr oder weniger halten. SQL erlaubt nicht nur die Abfrage von Daten aus der Datenbank, sondern auch die Änderung von Tabelleninhalten, Modifikation von Strukturen, Einrichtung von Benutzerberechtigungen und Einstellungen für die Systemsicherheit. Dabei untergliedert man SQL in DML (Data Manipulation Language) zum Lesen und Ändern von Daten, DDL (Data Definition Lan- Anwendungsschichten 283 guage) zum Anlegen und Verwalten von Tabellen auf der Datenbank und DCL (Data Control Language) für Berechtigungsprüfungen und zur Überprüfung der Konsistenz. Eine Teilmenge der SQL-Anweisungen, genannt Open SQL, ist in allen Datenbanken bekannter Software-Häuser implementiert und steht in ABAP vollständig zur Verfügung. Dies ermöglicht einen einheitlichen Zugriff auf alle von SAP unterstützten Datenbanken und damit eine weitgehende Unabhängigkeit der ABAP-Entwicklungen von Datenbankprodukten, sofern man sich auf Open SQL beschränkt. Native SQL Open SQL enthält allerdings lediglich DML-Befehle, die bereits ausführlich in Abschnitt 4.1.4 erläutert wurden. Für den Fall, dass diese für eine spezielle Anforderung nicht ausreichend sein sollten, ist es in ABAP auch möglich, datenbankspezifische Kommandos abzusetzen. Dazu wird die Native-SQL-Anweisung zwischen die ABAP-Befehle EXEC SQL und ENDEXEC gestellt: EXEC SQL. CREATE TABLE BUILD_COMP ( CLIENT CHAR(3) NOT NULL, BUILD CHAR(9) NOT NULL, COMP1 CHAR(6) NOT NULL, COMP2 CHAR(6) NOT NULL, PRIMARY KEY (CLIENT, BUILD) ) ENDEXEC. Listing 6.1 Beispiel einer in ABAP eingebetteten Native-SQL-Anweisung Die so in ABAP eingebetteten Befehle werden direkt an das Datenbanksytem durchgereicht. So erlaubt Native SQL die Nutzung der gesamten Funktionalität, die die datenbankseitige Schnittstelle bietet. Auf ABAP-Seite enthält jeder Workprozess auf einem Applikationsserver eine Datenbankschnittstelle mit einer herstellerabhängigen Schicht, über die die gesamte Information zwischen ABAP-Seite und Datenbank fließt. Bei Verwendung von Native-SQL-Befehlen in ABAP-Programmen kann bei einem Wechsel auf eine andere Datenbank ein erheblicher Mehraufwand entstehen, da sich die Kommandos der Datenbanken im Allgemeinen unterscheiden und die entsprechenden Codezeilen gefunden und angepasst werden müssen. Ohnehin sollten in Anwendungsprogrammen keine DDL-Operationen ausgeführt werden; das Anlegen und Verwalten 284 Anwendungsschichten von Tabellen sollte im ABAP Dictionary ausgeführt werden. Außerdem führt das SAP-System für datenbankspezifische Befehle keine weiteren Prüfungen durch. Aus diesen Gründen sollte Native SQL in ABAP so weit wie möglich vermieden werden. Logische Datenbanken Bei einer logischen Datenbank handelt es sich um nichts anderes als ein ABAP-Programm. Jedoch sind logische Datenbanken spezielle Programme, die einem Anwendungsprogramm Daten zur Verarbeitung zur Verfügung stellen können. Die häufigste Verwendung logischer Datenbanken ist das Lesen von Daten aus Datenbanktabellen und die Verknüpfung mit einem ausführbaren Programm. Darüber hinaus können logische Datenbanken über den Funktionsbaustein LDB_PROCESS aufgerufen werden. Dies ermöglicht es, mehrere logische Datenbanken innerhalb eines ausführbaren Programms aufzurufen. Diese können dann entsprechend komplex geschachtelt werden. Mit der logischen Datenbank werden die Datenbankzugriffe durch OpenSQL-Zugriffe außerhalb des Anwendungsprogramms realisiert. Die logische Datenbank liest aus der Datenbank zeilenweise und stellt sie dem ausführbaren Programm zur Laufzeit zur Verfügung. Logische Datenbanken sind hierarchisch organisiert, da viele Tabellen über Fremdschlüssel in Beziehung stehen. Folgende Aufgaben können von logischen Datenbanken übernommen werden: Aufgaben 왘 Sie können in mehreren ausführbaren Programmen genutzt werden. 왘 Einheitliches Selektionsbild für alle Programme, die eine logische Datenbank nutzen. 왘 Die Berechtigungsprüfung ist zentral in der logischen Datenbank abge- legt. 왘 Änderungen zur Verbesserung der Performance greifen in allen Anwendungsprogrammen, die die logische DB nutzen. Grundsätzlich kann man die logische Datenbank in drei Objekte unterteilen: Die Strukturdefinition legt die Datensicht der logischen Datenbank fest, die Selektion definiert die Benutzeroberfläche des ausführbaren Programms und im Datenbankprogramm erfolgen schließlich die Anweisungen für das Lesen der Daten und die Übergabe an die Aufrufer der logischen Datenbank. Der Aufruf hat folgende Struktur: Beschaffungslogik und Persistenz Bestandteile 285 GET <Tabellenkopf>. ... GET <Tabellenposition>. ... Listing 6.2 Prinzipieller Aufruf in einem ausführbaren Programm Persistente Objekte Die persistenten Objekte gehören zu den Object Services und stellen Anwendungen verschiedene zentrale Dienste zur Verfügung, die nicht direkt durch ABAP-Objects-Sprachelemente dargestellt werden. Derzeit werden von SAP zwei solcher Objektdienste zur Verfügung gestellt, die Persistenzdienste und die Transaktionsdienste. Im Rahmen dieses Abschnitts wird der Persistenzdienst kurz dargestellt. Ein Persistenzdienst unterstützt den ABAP-Entwickler bei der objektorientierten Arbeit mit Daten in relationalen Datenbanken. Transiente und persistente Daten Grundsätzlich können Daten in zwei verschiedene Kategorien unterschieden werden: in transiente und in persistente Daten. Vereinfacht kann man sagen, dass transiente Daten nur während der Laufzeit eines Programms, dagegen aber persistente Daten dauerhaft, z.B. auf der Datenbank, existieren. Darüber hinaus können persistente Daten auch als Inhalte auf der Applikationsebene oder auf der Präsentationsebene vorkommen. In objektorientierten Programmen werden Daten in der Regel als Attribute von Objekten dargestellt. Selbstverständlich werden in Methoden lokale Daten definiert und benutzt, diese werden jedoch hier nicht betrachtet. Ein Objekt in der objektorientierten Programmierung lebt nur zur Laufzeit eines Programms, zwischen der Erzeugung und der Löschung eines Modus des Programms. Um in Objekten mit persistenten Daten zu arbeiten, müssen innerhalb der Methoden der Klasse Zugriffe auf die Ablage programmiert werden. Der Sinn persistenter Objekte liegt deshalb darin, die Daten eines Objekts transparent für den Entwickler in der Datenbank abzuspeichern und sie während der Initialisierung des Objekts wieder zu beschaffen, damit ein Programm mit den gleichen Objekten weiterarbeiten kann, die ein anderes Programm in einem bestimmten Zustand hinterlassen hat. Die Aufgabe eines Persistenzdienstes besteht daher darin, die Möglichkeit zur Verfügung zu stellen, die Attribute eines Objekts persistent zu speichern und auf die richtige Klasse abzubilden. 286 Anwendungsschichten Um den Persistenzdienst für Objekte zu nutzen, müssen deren Klassen als so genannte persistente Klassen im Class Builder definiert werden. Diese Objekte und der Zustand des Objekts werden durch den Persistenzdienst verwaltet. Die Objekte einer solchen Klasse werden in einem ABAP-Programm nicht über die Anweisung CREATE OBJECT erzeugt, sondern mit einer Methode des Persistenzdienstes. Diese sorgt auch für die richtige Initialisierung. Die persistenten Klassen können neben der eindeutigen Identität auch Schlüsselattribute zur eindeutigen Identifizierung des Objekts enthalten. Der Persistenzdienst verwaltet die persistenten Objekte und sorgt für die Verbindung zwischen Objekt und Datenbank. Tutorium: Persistente Klasse In diesem Tutorium wird dargestellt, wie eine einfache persistente Klasse für eine Datenbanktabelle angelegt wird. Das Ziel besteht darin, dem Entwickler die Feinheiten bei der Entwicklung persistenter Klassen vor Augen zu führen, um einen Vergleich mit dem später ausführlich erläuterten Java-Äquivalent – JDO – durchführen zu können. Zielsetzung Sie sollten sich mit den Grundlagen der ABAP Workbench auskennen und darüber hinaus auch mit ABAP Objects in Berührung gekommen sein. Voraussetzungen 1. Im Class Builder SE24 oder im Object Builder SE80 wird die persistente Klasse angelegt. Ablauf 2. Geben Sie den Namen für die persistente Klasse mit ZCL_<dbtab>_ PERSISTENT an. 3. In den Eigenschaften für die Klasse wählen Sie unbedingt den Klassentyp Persistente Klasse aus. 4. Die angelegte Klasse implementiert die Methoden des Interfaces IF_ OS_STATE, die den Zustand des Objekts verwalten. 5. Daneben werden nun automatisch noch weitere Klassen zu der neuen persistenten Klasse generiert – die Klassen ZCB_<dbtab>_PERSISTENT und ZCA_<dbtab>_PERSISTENT. 6. Über die Persistenzabbildung wird der Klasse ZCL_<dbtab>_PERSISTENT die Datenbanktabelle <dbtab> zugeordnet. Die Persistenzabbildung erreicht man über das Menü Springen • Persistenzabbildung. 7. Hier kann das Mapping für die Datenbanktabelle DBTAB erfolgen. 8. Sichern und aktivieren Sie die persistente Klasse. 9. Das folgende Coding soll einen Eindruck vermitteln, wie eine solche persistente Klasse innerhalb eines Kontextes – z.B. eines Reports – verwendet wird. Über die Referenzvariable agent wird eine Referenz auf Beschaffungslogik und Persistenz 287 die persistente Klasse ZCL_<dbtab>_Persistent zugewiesen. Mit der Methode GET_PERSISTENT wird geprüft, ob ein Eintrag in der Datenbank existiert. Existiert kein Eintrag, wird eine Ausnahme ausgelöst. Innerhalb dieses CATCH-Blocks wird dann versucht, ein Objekt anzulegen. Erst nachdem das COMMIT WORK abgestetzt wurde, existiert auf der Datenbank ein entsprechender Eintrag. Wird der Commit Work nicht abgesetzt, besteht das erzeugte Objekt nur während der Laufzeit. DATA: connection TYPE REF TO zcl_<dbtab>_persistent, agent TYPE REF TO zca_<dbtab>_persistent. Agent = zca_<dbtab>_persistent=>agent. TRY. Connection = agent->get_persistent( i_key1 = wa_<dbtab>-key1 ... i_keyn = wa_<dbtab>-keyn ). CATCH cx_os_object_not_found. TRY. agent->create_persistent( i_key1 = wa_<dbtab>-key1 ... i_field1 = wa_<dbtab>-field1 ... ). CATCH cx_os_object_not_found. ... ENDTRY. ENDTRY. 6.1.2 Java Um nun auf der anderen Seite die komplexen Architekturdetails der Java Personality der Persistenzschicht des SAP Web Application Servers zu analysieren, sind zunächst noch einmal die Zusammenhänge der Datenhaltung innerhalb des SAP-Kontextes zu betrachten. Innerhalb der reinen ABAP-Welt, auf die Sie sich bisher konzentriert haben, werden alle Daten in einer mehr oder weniger zentralen Datenbank gespeichert. So setzte ein ABAP-Programm über einen der eben vorgestellen Mechanismen normalerweise direkt auf das zugrunde liegende Datenbanksystem auf. Warum sollte sich dies so nicht auch einfach in Java realisieren lassen? So würde man annehmen, dass man neue Tabellen im ABAP Dictionary anlegt, auf die man von irgendeiner Stelle im Java-Code zugreift. 288 Anwendungsschichten Während diese Vorgehensweise auf den ersten Blick sehr einfach und auch schlüssig erscheint, birgt sie doch einige Nachteile: Der herausragendste ist die Tatsache, dass dieser Ansatz so nicht den J2EE-Standards entspricht, denn er würde die Existenz einer ABAP-Instanz voraussetzen, was für eine reine J2EE-Umgebung – die sich auch außerhalb des SAPUmfelds großer Beliebtheit im Rahmen von Enterprise-Projekten erfreut – offensichtlich sehr unwahrscheinlich ist. Weiterhin würde das Zusammenlegen von ABAP- und Java-Tabellen dazu führen, dass Java-Entwickler den ABAP-Konventionen folgen müssten, beispielsweise bezüglich Sperrverwaltung oder Update-Aufträgen, die Datenkonsistenz sicherstellen. Nichtsdestotrotz bringt eine zentrale Datenbank der Java-Identität des Web AS enorme Vorteile. So setzt die zentrale Instanz des Web AS auf einer zentralen Datenbankinstanz auf, die sich ähnlich zu der korrespondierenden ABAP-Instanz verhält – Customizing- und Konfigurationsdaten werden zusätzlich zu den Anwendungstabellen gespeichert. Um dies zu ermöglichen, beinhalten die Design-Ziele der SAP folgende Maßgaben: Design-Ziele 왘 Strikte Trennung von ABAP- und Java-Persistenz Beide Personalities haben ihr eigenes abgegrenztes Datenbankschema, ausgeprägt durch zwei logisch – oder sogar physisch – getrennte Datenbanken. Keine Transaktion kann sich direkt über beide Schemata erstrecken, wobei sicherlich beispielsweise eine Java-Anwendung auf ABAP-Daten zugreifen kann, allerdings nicht auf Datenbankebene – sprich: Tabellenzugriffe zwischen ABAP- und Java-Stack sind nicht möglich. Dies erfolgt auf der Ebene der Geschäftslogik bzw. der diese kapselnden Middleware, beispielsweise über RFC mittels Java Connector (JCo). Die Kollaboration muss an dieser Stelle also auf Komponentenebene erfolgen. 왘 Minimierung von Datenbankadministrations-Aufwendungen Um trotz der notwendigen Trennung beider Datenbankschemata die Aufwendungen für Installation und Administrierung möglichst gering zu halten, ist es möglich, beide Schemata innerhalb einer einzigen Datenbank zu realisieren. Das bedeutet, eine ABAP-Transaktion greift auf das ABAP-Schema zu, eine Java-Transaktion auf das korrespondierende Java-Schema, jedoch in ein und derselben physikalischen Datenbank. 왘 Erweiterung der Java-Persistenz-Technologien Altbekannte Fähigkeiten und Konzepte aus der ABAP-Welt, beispielsweise das Cachen von Statements und Tabellenpufferunterstützung, wurden in die Java-Welt übertragen. Beschaffungslogik und Persistenz 289 Ein anderer Aspekt, der die Architektur der Persistenzschicht auf der JavaSeite maßgeblich beeinflusst hat, ist die Objektorientierung der Sprache Java. Während herkömmliche SAP-Anwendungen in der Regel noch auf relationaler Persistenz und prozeduralem Code basieren – wodurch sich Geschäftsdaten relativ einfach in Tabellen abbilden lassen –, ist man in Java eher gezwungen, in Objekten zu denken. Aus diesem Grund bietet SAP beide Möglichkeiten der Datenzugriffsarten an, die grundsätzlich voneinander zu unterscheiden sind: relationale und objekt-basierte Datenhaltung. Entsprechend unterschiedlich ist auch der Umgang für den Entwickler mit den Daten. Open SQL for Java Framework für einheitliche Datenzugriffsebene Ebenso wie Open SQL innerhalb einer ABAP-Umgebung einen einheitlichen Zugriff auf Datenbanken ermöglicht, stellt Open SQL for Java eine einheitliche Datenzugriffsebene für Java-Anwendungen zur Verfügung. Diese Schicht stellt zum einen leistungssteigernde Mechansismen wie Tabellenpuffer und Statement Pooling zur Verfügung und ermöglicht zugleich einen übertragbaren Zugriff auf verschiedenste Datenbanken wie Oracle, IBM DB2, Microsoft SQL Server, MaxDB und weitere. Anwendungen müssen auf diese Art nicht angepasst werden, denn das SQL Subset SQLJ gleicht die Unterschiede zwischen den Datenbanken aus. So können Anwendungen ohne Änderungen auf verschiedensten Datenbanken laufen. Alle Programmiermodelle, die SAP für die unterstützten Datenbanken anbietet, sind fester Bestandteil dieses Open SQL for Java Frameworks. Der Anwendungsentwickler hat die Option, über verschiedene Wege auf die Daten der Persistenzschicht zuzugreifen. Sämtliche Zugriffsmöglichkeiten innerhalb von Open SQL for Java basieren auf der untersten Instanz – auf dem bereits in Abschnitt 5.2 eingeführten JDCB API. Auf dieser Programmierschnittstelle setzt SAP nun verschiedene Abstraktionsschichten auf, die unabhängig voneinander in einer Anwendung koexistieren und genutzt werden können und auf jeweils einer eigenen Hierarchieebene verschiedene Funktionalitäten anbieten bzw. gewisse Vor- und Nachteile implizieren. JDBC Wie man Abbildung 6.1 entnehmen kann, repräsentiert JDBC das niedrigste Abstraktionsniveau – sämtliche höheren Schichten generieren letzten Endes JDBC-Aufrufe, die vom herstellerspezifischen JDBC-Datenbanktreiber im Sinne des Java JDBC API als SQL-Statements prozessiert 290 Anwendungsschichten und direkt an die Datenbank übermittelt werden. JDBC ist sehr populär, allein schon aufgrund der Fülle von Beispielcodes, die allgemein zugänglich sind. Relational Persistence (SQL) SQLJ JDBC (J2EE) Object Relational Persistence EJB CMP (J2EE) JDO »open« Open SQL Engine Table Buffer SQL Processor DB Access Layer Table Catalog »native« Statement Cache SQL Trace »vendor« Connection Pool Vendor-specific JDBC Driver Database Abbildung 6.1 Open SQL Framework – Datenbank-Zugriffsebenen Allerdings garantiert die Verwendung von nativem JDBC noch keine Datenbankunabhängigkeit. Es ist von der JDBC-Treiberimplementierung und der Semantik der letztlich zugrunde liegenden Datenbank abhängig, wie die JDBC-Aufrufe ausgeführt werden. Wenn für die Implementierung von Datenbankzugriffen natives SQL bzw. JDBC explizit verwendet werden, ist festzuhalten, dass das JDBC-API keinerlei Framework für die Überprüfung und Validierung von SQL-Anweisungen beinhaltet. Der Entwickler erhält also keine Sicherheit darüber, ob sein Anwendungscode wirklich auf einer anderen Datenbankplattform problemlos ausgeführt wird. Native JDBC Um diesen möglichen Fragen um Portabilität im Zusammenhang mit JDBC und SQL vorzubeugen, definiert SAP eine Untermenge (Subset) von SQL-Anweisungen, die für eine Datenbankunabhängigkeit zumindest zwischen allen von SAP unterstützten Datenbanken entworfen wurde. Open SQL for Java ist in diesem Zusammenhang somit als Pendant zum Beschaffungslogik und Persistenz 291 bekannten Open SQL auf der ABAP-Seite zu sehen und löst auf ähnliche Art verwandte Probleme, die mit fast jeder Programmiersprache einhergehen. Den Mittelpunkt des Open SQL for Java Frameworks bildet die Open SQL Engine, bestehend aus drei Schichten, die aufeinander aufbauend von unten nach oben mehr und mehr Funktionalitäten anbieten: Die unterste Schicht bildet der Connection Pool, darauf setzt die Datenbankzugriffsschicht auf, hierauf wiederum die oberste Schicht, nämlich die SQL-Prozessorebene (siehe Abbildung 6.2). Open SQL Empfohlen Verbindlich bei SAP-Projekten · SQLJ · Portabilität von SQL/JDBC garantiert · Java Dictionary nur für das »default« · Table Buffer Datenbankschema Native SQL · SQL Trace · SQL Statement Cache Empfohlen, wenn das standardmäßige Datenbankschema nicht genutzt werden kann, weil Datenbanktabellen bereits existieren Anbieter-spezifisches SQL Toleriert · Datenbankverbindungspool · Full JDBC standard API · Anbieter-spezifische SQL-Statements Abbildung 6.2 Open SQL Engine – Schichtenmodell Relationale und objektrelationale Persistenz 292 Wie bereits erwähnt, bietet SAP verschiedene Programmiermodelle an, um auf Daten zuzugreifen: Es lassen sich zum einen relationale und objektrelationale Persistenz gegeneinander abgrenzen, die sich wiederum ihrerseits durch verschiedene Ansätze realisieren lassen. Innerhalb des relationalen Modells werden die Persistenzszenarien SQLJ und JDBC unterschieden. Für den objektorientierten Persistenzansatz bietet Java die zwei Möglichkeiten der Realisierung über Enterprise Entity Beans oder über Java Data Objects. Die einzelnen Programmiermodelle werden später in diesem Kapitel behandelt, zunächst genügt es zu wissen, dass, abgesehen von SQLJ, das auf der obersten Schicht der Open SQL Engine aufsetzt, alle anderen Modelle jeweils auf jeder der drei Ebenen innerhalb des Open-SQL-Schichtenmodells angesetzt werden können, somit ist die Bindung an die unterste Schicht maßgeblich vorausgesetzt. Entsprechend untergliedert SAP in ihrer Terminologie diese drei Gruppen, nämlich »open«-, »native«- und »vendor«-basierte Anknüpfungsmodelle. Alle drei Ebenen der Open SQL Engine werden im Folgenden beschrieben. Anwendungsschichten Connection Pool – Vendor SQL Als unterste Schicht setzt direkt auf die Schicht des anbieterspezifischen JDBC-Treibers der Connection Pool auf. Das Aufbauen und Anbieten von Datenbankverbindungen ist bekanntermaßen aufwändig und aus Sicht der Systemressourcen somit teuer. Durch den Connection Pool werden Verbindungen zu gleichen Datenquellen in einem so genannten Pool gespeichert. Somit lassen sich Verbindungen ohne Zeitverzug aufbauen, zudem ermöglicht dieser Pool Zugriffe auf das Default-DatenbankSchema, das bereits vorkonfiguriert im Connection Pool vorhanden ist, und nicht angepasst werden muss. Connection Pools werden dabei zentral im J2EE-Server seitens der Administration angelegt und danach lediglich über einen logischen und eindeutigen Namen aus einem JNDI-Kontext referenziert. Sensible Daten wie Authentifikationsdaten oder Belastungsmaxima werden so von den Entwicklern fern gehalten und verlagern auf diese Weise typisch architektonische Parameter nicht unnötigerweise in die Applikationslogik. Anwendungen deklarieren Ressourcenreferencen zum Pool als Datenquelle. Sie erhalten die Verbindungen über den Pool und lösen die Verbindungen ebenso über ihn. Der Connection Pool wird sowohl zwischen verschiedenen Requests als auch zwischen verschiedenen Anwendungen gemeinsam genutzt. Neben den bereits erwähnten Performanceaspekten bietet sich hierdurch eine zentrale Stelle sowohl zur Konfiguration als auch für das Monitoring der Datenbankverbindungen und -zugriffe. Sie können Abbildung 6.3 entnehmen, dass jeder Anknüpfungsansatz mindestens auf dieser Schicht aufbaut, womit sämtliche Funktionalitäten dieses Layers grundsätzlich immer zur Verfügung stehen. Greifen Anwendungen direkt auf Daten in einer relationalen Datenbank zu, so kommt immer JDBC zum Einsatz. Durch die Verwendung von Abstraktionsmodellen wie Enterprise Entity Beans oder Java Data Objects bleibt dies dem Entwickler zwar bis zu einem gewissen Grad verborgen, doch bereits weiter vorne wurde darauf hingewiesen, dass auch diese objektorientierten Modelle ebenso auf JDBC zurückgreifen. Gleichwohl gilt: Setzt man auf dieser untersten Ebene auf, können zwar die proprietären Fähigkeiten einzelner Datenbanken genutzt werden, jedoch unter Verlust der Open-SQL-Mehrwehrte wie Portabilität, Tabellenpufferung und SQL Statement Cache, um nur einige zu nennen. Da im Prinzip an dieser Stelle nun mehr oder weniger direkt auf herstellerspezifischer Ebene der Datenbank gearbeitet wird, nennt SAP einen solchen Ansatz folglich »vendor-specific« bzw. »Vendor-SQL« oder auch »Vendor-JDBC«. Beschaffungslogik und Persistenz Vendor SQL bzw. JDBC 293 J2EE Applikation #1 J2EE Applikation #2 Verbindungspool · Konfigurationen · Monitoring Gemeinsame Nutzung von Anfragen und Applikationen Eine Verbindung aufbauen ist teuer! DB-Schema Abbildung 6.3 Connection Pool SAP gibt die Persistierung auf direkter Grundlage dieser dritten Schicht lediglich als »toleriert« an, somit sollte auf dieser Ebene grundsätzlich nur entwickelt werden, wenn Anwendungen nicht ohne jene benötigten proprietären Fähigkeiten auskommen können. DB Access Layer – Native SQL/JDBC Als zweite Schicht setzt die Datenbankzugriffsebene auf der Schicht des Connection Pools auf, der diese – getreu dem Gedanken von Schichtenmodellen – um gewisse Funktionalitäten erweitert. Ebenso wie auf der herstellerspezifischen Ebene bieten sich dem Entwickler sämtliche Funktionalitäten des zugrunde liegenden proprietären Datenbanksystems, jedoch ebenso mit Einbußen bei der Portabilität und Tabellenpufferung. Native SQL bzw. JDBC Alle Methodenaufrufe werden dem zugrunde liegenden JDBC-Treiber direkt und unverändert übermittelt. Die Implementierung des Native JDBC API entspricht grundsätzlich einem einfachen Wrapper um den herstellerspezifischen JDBC-Treiber, jedoch mit zwei entscheidenden Erweiterungen in Bezug auf Geschwindigkeit und Instandhaltungsaufwand der J2EE Engine: SQL Trace und Statement Pooling. SQL Trace SQL Trace bietet auf Abruf die Möglichkeit, sämtliche SQL-Statements, die gegen die Datenbank gestellt werden und über Methoden dieser Ebene oder der darüber liegenden Schicht der Open SQL Engine – die 294 Anwendungsschichten SQL-Prozessorschicht – ausgeführt werden, zu protokollieren. SQL Trace kann über den Visual Administrator dynamisch an- bzw. ausgeschaltet werden. Das Log-Format ist dabei datenbankunabhängig. Die Protokollierungseinträge enthalten neben den eigentlichen SQL-Statements Informationen über den Zeitpunkt der Anfrage, die Abfragedauer, die Eingabeparameter und gegebenenfalls die Abfrageergebnisse sowie Kontextinformationen. SQL Trace ist über eine Browseroberfläche innerhalb des SAP Web AS zugänglich und ist besonders nützlich für Performanceanalysen, denn es lassen sich mit SQL Trace schnell und einfach Fehlerquellen oder schlechte Persistenzentwürfe aufdecken, besonders wenn höherschichtige APIs verwendet werden, die eventuell eine zu große Menge von SQL-Statements erzeugen. So kann SQL Trace auch zur Entwicklungszeit eine große Hilfe sein, indem Entwickler lernen können, welcher SQL-Code letzten Endes aus ihren JDOs, JSPs, Servlets und Enterprise Beans erzeugt wird. Statement Pooling verbessert die Performance zur Laufzeit, indem SQLAnfragen, die häufiger verwendet werden, gecacht werden. Der Zwischenspeicher erlaubt es der Engine, festzustellen, ob eine Anfrage einige Zeit vorher bereits schon einmal gestellt wurde. Somit kann signifikant CPU-Zeit eingespart werden, denn häufig benutzte Anfragen müssen nur einmal vorbereitet werden (Prepare-Phase) und können wiederholt direkt ausgeführt werden. Dies reduziert die Gesamtanzahl an Parse-Routinen, die gegen die Datenbank gestellt werden müssen, erheblich. Statement Pooling PreparedStatement ps = con.prepareStatement("SELECT * FROM ZRM_RES_MASTER WHERE RESID = ?”); ps.setInt(1, 256); [...] ResultSet rs = ps.executeQuery(); [...] ps.close(); Listing 6.3 Lebenszyklus eines SQL-Statements Beachten Sie, dass dieses Quellcodefragment Teil eines Servlets sein könnte, das innerhalb der J2EE-Anwendung mehrfach ausgeführt wird, lediglich mit unterschiedlichen empl_id-Werten. Das daraus resultierende SQL-Statement müsste jedes Mal an die Datenbank übermittelt und vorbereitet werden, wenn das Servlet ausgeführt würde. Das Vorbereiten des SQL-Statements – was für die Datenbank bedeutet, es zu parsen und den optimalen Ausführungsplan zu erstellen – ist auf den meisten Beschaffungslogik und Persistenz 295 Systemen ein sehr kostenintensiver Vorgang und erzeugt infolgedessen langfristig einen Performance-Overhead. PreparedStatement-Object Statement Pooling ermöglicht nun der Anwendung das Wiederverwenden eines bereits vorbereiteten Statement-Objekts (PreparedStatement-Object) auf eine ähnliche Weise wie auch Datenbankverbindungen wieder verwendet werden können, wenn Connection Pooling aktiviert ist. Die Wiederverwendung ist der Anwendung gegenüber vollständig transparent. Aus Sicht der Anwendung ist es für die Verwendung eines PreparedStatement-Objekts gleichgültig, ob dieses am Statement Pooling partizipiert oder eben nicht. Änderungen im Code sind nicht notwendig. Wenn eine Anwendung ein PreparedStatement-Objekt schließt, kann sie es über die Methode Connection.prepareStatement() wieder verwenden. SAP Web AS SQL Wiederholte (teure!) SQL-Statements vermeiden Prepare SQL JDBC-Statements cachen SQL parsen und kompilieren SQL Ausführungsplan } einmal !!! Ein Cache pro physische Datenbankverbindung SQL ausführen Result Set Identifikation des Statements über seine textuelle Repräsentation Datenbank Abbildung 6.4 Statement Cache Eine Statement-Pool-Instanz ist mit einer physischen Datenbankverbindung assoziiert und cacht PreparedStatement- und CallableStatement-Objekte, die auf dieser Verbindung erstellt werden. Jedes Mal, wenn eine prepareStatement()- oder prepareCall()-Methode auf einer bestimmten Verbindung aufgerufen wird, durchsucht der native JDBC-Treiber automatisch den assoziierten Statement Pool nach einem passenden Statement. Dabei sind für den Entwickler folgende Kriterien relevant: 296 Anwendungsschichten 왘 Der Statement-Text muss mit dem im Cache exakt identisch sein, ins- besondere Groß- und Kleinschreibung sind hierbei zu beachten (casesensitiv). 왘 Der Aufruf-Typ muss derselbe sein (prepared oder callable). 왘 Der Scrollable-Type des Result Sets, das aufgrund des Aufrufs erzeugt wird, muss derselbe sein (forward-only oder scrollable). Wird ein passendes Statement im Pool gefunden, wird ein neues PreparedStatement-Objekt erzeugt und an den Aufrufer übergeben. Ansonsten wird der Prepare-Aufruf zunächst geparst, um ein neues Objekt zu erzeugen. Jedes neue dieser Objekte wird gepoolt, wenn die close()Methode auf ihm aufgerufen wird. SQL-Prozessor – Open SQL/JDBC Die dritte und zugleich oberste Ebene der Open SQL Engine bildet die SQL-Prozessorschicht. In dieser wird mit dem Tabellenpuffer ein weiterer Baustein zur Effizienzsteigerung eingeführt. Ziel ist es, einzelne Teile der Datenbanktabellen nach erstmaligem Zugriff im Application Server vorzuhalten, um mehrfache Zugriffe auf dieselben Datenbestände innerhalb der Datenbank zu vermeiden. So werden die Datenbankbelastung und die Netzwerkkommunikation reduziert. Es existiert jeweils ein Puffer pro Datenbankschema und Web AS-Instanz, jedoch kann ein Puffer für mehrere Verbindungen gleichzeitig arbeiten. Das Puffern kann für Tabellen individuell konfiguriert werden, weiterhin kann die Granularität der Pufferung so definiert werden, dass entweder nur Teile des Inhalts einer Tabelle oder sogar die gesamte Tabelle zwischengespeichert wird. Das Puffern ist der Anwendung gegenüber transparent gehalten, der erste Pufferzugriff lädt implizit die Daten in den Zwischenspeicher, so dass Folgezugriffe die Daten direkt von ihm erhalten und nicht bis auf die Datenbank durchgreifen müssen. Innerhalb des Visual Administrator besteht die Möglichkeit, Statistiken über die Verwendung des Tabellenpuffers abzurufen. Diese stehen im Reiter für die Monitoring Services zur Verfügung. Visual Administrator Während der native SQL/JDBC-Ansatz, aufbauend auf der zweiten Schicht der Open SQL Engine, nur für den Fall gewählt werden sollte, dass ein Standard-Datenbankschema nicht verwendet werden kann, da bereits Datentabellen existieren, ist der Open-SQL-Ansatz als generell erste Wahl anzusehen. Denn nach diesem Modell sind die Rollen der Entwicklungszeit klar strukturiert, es wird das Java Dictionary explizit mit in den Prozess mit einbezogen. Beschaffungslogik und Persistenz 297 SAP Web AS TabellenPuffer SQL Result Set Die selben Daten nicht mehrfach von der Datenbank lesen } einmal !!! Einen Puffer pro DB-Schema und Web AS-Instanz (ein Puffer wird für mehrere Verbindungen verwendet) SQL ausführen Datenbank Abbildung 6.5 Tabellenpuffer Java Dictionary Vollständig integriert in das SAP NetWeaver Developer Studio, wird das Java Dictionary verwendet, um den Lebenszyklus der Datenbankobjekte zu verwalten, also die Definition, Erstellung und Modifikation von Datenbankobjekten. Dies ist nur möglich, wenn die Entwicklung auf dieser obersten Schicht der SQL Engine stattfindet. Wie auch auf der ABAPSeite sollten DDL-Operationen nur innerhalb dieses Dictionary ausgeführt werden. Anfragen und DML-Ausdrücke werden über die Open SQL Engine abgewickelt, die somit die zweite Rolle in diesem Modell einnimmt. Datenbankverbindungen, SQL-Statement-Processing, Tabellenpuffer, Statement Pooling und SQL Trace werden über sie abgewickelt. Doch neben diesen weiteren Möglichkeiten der Performanceoptimierung vollendet die SQL-Prozessorebene das Konzept der Open SQL Engine dahingehend, dass sie durch den SQL-Prozessor selbst Funktionalitäten anbietet, die die Verwendung eines neuen Programmiermodells innerhalb der Java-Persistenz ermöglicht: Hierzu lässt sich festhalten, dass über sämtliche bereits vorgestellten Programmiermodelle (JDBC, EJB, JDO), egal, auf welcher Schicht sie innerhalb der Open SQL Engine basieren, letztlich über den JDBC-Treiber die endgültigen SQL-Statements generiert werden. Ebenso klar ist, dass das Open SQL Framework for Java grundsätzlich sehr große Ähnlichkeiten mit seinem ABAP-Gegenstück hat, jedoch mit einem entscheidenden Unterschied: Reine JDBC-Anfragen bzw. resultierende JDBC-Anfragen werden erst zur Laufzeit syntaktisch analysiert, wodurch 298 Anwendungsschichten Fehler entsprechend auch erst zur Laufzeit festzustellen sind – nicht bereits zur Designzeit, was den Entwicklungsprozess schwerfällig macht. Aus diesem Grund führt SAP nun ein weiteres Abstraktionslevel für Persistenz ein: SQLJ. SQLJ SQLJ definiert eine Syntax, um statische SQL-Ausdrücke in Java-Quellcode einzubetten, im Gegensatz zu JDBC, wo SQL-Statements als StringArgumente einer JDBC-Methode übergeben werden. Da der Java Compiler nicht mit diesen Ausdrücken umgehen kann, werden Quellcode-Dateien mit SQLJ-Elementen mit der Dateiendung *.sqlj gespeichert. In einem Präprozessor-Schritt werden diese Elemente von einem SQLJ-Übersetzer des Open-SQL-Prozessors durch Aufrufe auf die SQLJ-Laufzeitumgebung ersetzt. Erst der daraus resultierende Java-Quelltext ist nun kompilierbar. SQLJ wurde seinerzeit von Oracle initiiert und es gelang, ein SQLJ-Konsortium zu gründen, dem Oracle, IBM, Microsoft, Sun, Sybase, Tandem und Informix angehören. Die Referenzimplementierung wurde daraufhin von Oracle entwickelt und als ISO/IEC-Spezifikation standardisiert (ISO/IEC 9075–10). Hieraus leitet SAP nun das im Open SQL for Java Framework verwendete API ab. In dieser Implementierung der SAP wird die Syntax stets auf Konformität mit der Open-SQL-Grammatik überprüft, wodurch höchste Datenbankportabilität erlangt wird, da ja, wie bereits in früheren Kapiteln festgestellt, die Open-SQL-Syntax eine Untermenge der SQL-Syntax ist, die von allen führenden Datenbankherstellern unterstützt wird. Somit lassen sich über SQLJ keine datenbankspezifischen SQL-Aufrufe prozessieren. Die Open-SQL-Grammatik basiert auf Entry Level SQL, spezifiziert durch ISO/IEC 9075 (Third Edition, 01.11.1992), darüber hinaus werden folgende SQL-Konstrukte unterstützt: Open-SQLGrammatik 왘 Joined Tables 왘 Dynamic Parameter Specification Open SQL unterstützt folgende Teilmenge von SQL-Befehlssätzen: 왘 Abfragen 왘 Data Manipulation Language (DML) Beschaffungslogik und Persistenz 299 Syntax Die SQLJ-Syntax ist sehr einfach zu lesen: In SQLJ wird SQL-Ausdrücken die Direktive #sql vorangestellt. Der Präcompiler übergeht den JavaCode und bearbeitet direkt nur den SQL-Code, den er zunächst syntaktisch überprüft. Sind keine Fehler vorhanden, generiert der Übersetzer Java-Quellcode und wandelt die SQLJ-Ausdrücke in die benötigten JDBC-Aufrufe um. Der SQLJ-Übersetzer ist vollständig transparent in das SAP NetWeaver Developer Studio integriert. Wenn die SQLJ-Quelldateien gespeichert werden, werden automatisch die korrespondierenden Java-Klassen erzeugt. Zum Debuggen der Anwendung werden die ursprünglichen SQLJ-Quelldaten angezeigt und bearbeitet. Der Vorteil dieses im Gegensatz zu JDBC auf höherem Niveau angelegten APIs sollen einfachere, kompaktere und robustere Programme sein. Auf der einen Seite werden natürlich die Programme dahingehend robuster, dass Syntax, Semantik, Typenvalidität und Portabilität bereits während der Entwicklungszeit überprüft werden – und nicht erst zur Laufzeit nach dem Deployen –, jedoch kann nicht pauschal von einer deutlich verringerten Komplexität von SQL-Befehlen und Quellcode gesprochen werden. Die Testphase wird nicht zwangsläufig weniger zeitaufwändig, denn der zusätzliche Präcompilierzyklus verlagert diese vor den Deploy-Vorgang. SAP versucht diesem Manko zu begegnen, indem die Entwicklungsumgebung bereits das Auflösen von SQLJ unterstützt und man das Testen so schon während der Designzeit fahren kann. Zudem wird auch der Code nicht automatisch weniger komplex, vielmehr kann Quellcode nun verwirrend wirken, z.B. dadurch, dass Sprachen und Syntaxen gemischt werden. Beispielsweise werden die Variablen bei SQLJ über :varName angesprochen. Das Modell von SQLJ stellt insbesondere für langjährige ABAP-Entwickler sicherlich eine Erleichterung dar, ist es doch angelehnt an die Open-SQLEinbettung von SQL in ABAP-Code. Doch da die SQL-Statements hart in den Java-Quelltext codiert werden und die syntaktische Prüfung keine dynamisch generierten Statements unterstützt, lassen sich mit SQLJ nur statische SQL-Funktionalitäten nutzen, im Gegensatz zu Open SQL in ABAP, wo das dynamische Generieren von SQL-Anweisungen zunehmend – allerdings auch nur bis zu einem gewissen Maße – ermöglicht wird. 300 Anwendungsschichten SQLJ-Statements beginnen mit der Direktive #sql und enden mit einem Semikolon. Zusätzlich zu den in Java reservierten Schlüsselwörtern sind die Worte iterator, context und with innerhalb von SQLJ-Ausdrücken reserviert. Wie auch Java-Code selbst, sind auch SQLJ-Statements casesensitiv. SQLJ-Entwicklung Der eigentliche SQL-Aufruf ist innerhalb von geschweiften Klammern { und } enthalten und ist case-insensitiv. Host-Variablen erhalten als Präfix einen Doppelpunkt (:). #sql context Ctx with (dataSource = ”jdbc/SYS”); String var; #sql [ctx] { Select col into :var FROM tab }; Innerhalb einer SQLJ-Quelldatei können folgende Kommentare verwendet werden: 왘 Java-artige Kommentare (/* ... */ oder //) 왘 SQL-artige Kommentare (/* ... */ oder --) Die SQL-Kommentare sind nur für die SQL-Teile des Quellcodes zu verwenden, außerhalb des SQL-Fragments müssen Java-Kommentare verwendet werden: /* #sql context Ctx with (dataSource = "jdbc/SYS");*/ // String var; #sql [ctx] { --Select col into :var FROM tab }; Java-Host-Variablen werden dazu verwendet, um Daten zwischen Java (der Host-Sprache) und SQL (der eingebetteten Sprache) auszutauschen. Sie besitzen folgende Syntax: Host-Variablen <host expression> ::= (IN | OUT | INOUT)? ':'( <java variable> | '(' <java expression> ')' ). Host-Variablen und Expressions können überall in einem eingebetteten SQL-Statement verwendet werden, wo die Verwendung dynamischer Parameter durch die Open-SQL-Grammatik zugelassen ist. Um eine JavaVariable als Host-Variable zu benutzen, muss ihr das Präfix : vorangestellt werden, zudem müssen die Variablennamen innerhalb des Java-Teils gleichlautend mit den Namen derer im SQLJ-Teil einer Quelldatei sein, Groß- und Kleinschreibung ist hierbei zu beachten: Beschaffungslogik und Persistenz 301 String res_id = "1"; #sql [ctx] { DELETE FROM ZRM_RES_MASTER WHERE RESID = :res_id }; Host-Expressions Ebenso wie Variablen lassen sich auch komplexe Java-Ausdrücke als Host Expression in ein SQL-Statement einbetten. Dabei muss ein Host-Ausdruck von :( und ) eingeschlossen werden. Die Host-Ausdrücke werden dabei von links nach rechts entsprechend ihrem Auftreten innerhalb des Statements ausgewertet. Im folgenden Beispielcode werden zwei Java-Ausdrücke in ein SQL-Statement eingebettet. Der Ausdruck ref.getKey() ist ein IN-Parameter, der Ausdruck values[++i] ein OUT-Parameter. Beide Ausdrücke werden nach Ausführung des Statements ausgewertet. String[] values = new String[5]; MyClass ref = new MyClass(); int i = 3; #sql [ctx] { SELECT col INTO :(values[++i]) FROM dbtab WHERE key = :(ref.getKey()) }; Parameter-Modus Um den Parameter-Modus einer Host-Variablen oder -Expression zu bestimmen, können diese mit einem optionalen Parameter-Modus-Indikator »IN«, »OUT« oder »INOUT« (aus Sicht der Datenbank) gekennzeichnet werden. Dies hilft lediglich dem einfacheren Verständnis des Quellcodes, der letztliche Datenfluss wird automatisch erkannt und entsprechend vollzogen. Der IN-Parameter zeigt an, dass Daten von der Java-Variable in das SQL-Statement übergeben werden, während der OUT-Parameter anzeigt, dass das Ergebnis des SQL-Statements zurück an die Java-Anwendung übergeben wird. INOUT definiert hingegen einen Datenfluss in beide Richtungen. Der nachfolgende Quelltext zeigt dies beispielhaft: #sql [ctx] { SELECT col INTO :OUT var FROM dbtab WHERE key = :IN (ref.getKey()) }; Man sollte vorsichtig mit den Host-Expressions umgehen, da sie zu bestimmten Zeitpunkten ausgewertet werden: OUT-Ausdrücke werden nach der Ausführung des SQL-Statements, IN-Ausdrücke werden vorher ausgewertet. 302 Anwendungsschichten In SQLJ werden Datenbankverbindungen durch einen so genannten Connection Context identifiziert. Dieser spezifiziert die zu verwendende Datenbank, die Session und Transaktion. Alle SQLJ-Ausdrücke oder DMLStatements müssen einen expliziten Connection Context verwenden. Das bedeutet, dass diese Ausdrücke einen Bezeichner enthalten müssen, der ein Connection-Context-Objekt bestimmt, auf dem der Ausdruck ausgeführt wird. Vereinfacht beschrieben, repräsentiert das Conenction-Context-Objet eine Datenbankverbindung. DatenbankConnectionContext Der SQLJ-Übersetzer substituiert diese Connection-Context-Deklaration durch die Deklaration einer bestimmten Java-Connection-ContextKlasse, die das Interface sqlj.runtime.ConnectionContext implementiert. Da die generierte Klasse statische Variablen enthalten wird, darf ein Connection Context nur als globale oder als statische innere Klasse deklariert werden. Die Connection-Context-Klasse repräsentiert im Gegensatz zum Objekt keine Datenbankverbindung, sondern eine Datenquelle sowie einen logischen Katalog (zur Designzeit), auf den später in diesem Abschnitt eingegangen wird. Es werden zwei Varianten der Datenquellen-Verbindungskontexte unterschieden: Der URL Connection Context hat Konstruktoren, die es ermöglichen, einen neuen Verbindungskontext aufgrund einer URL zu instanziieren. Der Data Source Connection Context hingegen ermöglicht das Erstellen eines Objekts aufgrund einer Datenquelle. Die Deklaration eines Connection Contexts kann eine with-Klausel enthalten, die den Wert für die Datenquelle spezifiziert. Dann handelt es sich um einen Connection Context mit Datenquelle; solch ein Connection Context ist fest mit der Datenquelle verbunden, die unter dem angegebenen Namen im JNDI-Verzeichnis gefunden werden kann. Der Defaultkonstruktor erzeugt eine Instanz dieser Kontextklasse, die eine JDBC-Verbindung zur assoziierten Datenquelle beinhaltet. Ist die withKlausel nicht vorhanden, handelt es sich um einen URL Connection Context. Der folgende Code zeigt einen Connection Context mit Datenquelle: Connection Context mit Datenquelle #sql context SysCtx with (dataSource = "java:comp/env/jdbc/MyDB"); [...] SysCtx sysCtx = new SysCtx(); #sql [sysCtx] { DELETE FROM dbtab WHERE key = 17 }; Beschaffungslogik und Persistenz 303 [...] sysCtx.close(); SQLJ in der IDE Der gesamte Entwicklungsprozess der Persistenz einer Web AS-J2EEAnwendung wird über das SAP NetWeaver Developer Studio abgebildet. Java Dictionary Bei der Entwicklung eines neuen Projekts müssen zunächst sämtliche benötigten Tabellen im Java Dictionary angelegt worden sein. Das Java Dictionary ist in das NetWeaver Developer Studio fest integriert, beim Anlegen neuer Tabellen werden zunächst nur auf der Client-Seite (beim Entwickler) Metadaten über die Tabellen erzeugt, die erst beim DeployVorgang in der jeweils verwendeten Datenbank erzeugt werden. Die Vorgehensweise bei der Entwicklung mit dem Java Dictionary wurde bereits im entsprechenden Tutorium in Kapitel 5 ausführlich beschrieben, weshalb hier nur auf die wesentlichen SQLJ-Bereiche eingegangen wird. Zielsetzung Voraussetzungen Tutorium: SQLJ-Entwicklung Es müssen die SQLJ-Quelldateien angelegt werden. Hierzu haben Sie zum einen die Möglichkeit, vollständig neue Dateien anzulegen bzw bestehende reine Java-Quellcodes in SQLJ-Quellcodes zu konvertieren, um in diese SQL-Statements einzubetten. Wie bereits beschrieben, generiert der SQLJ-Übersetzer automatisch aus den SQLJ-Dateien Java-Klassen, sobald Sie Ihre Arbeit speichern. Arbeiten Sie aus diesem Grund immer auf SQLJ-Ebene, niemals jedoch auf den Java-Dateien, da ansonsten Inkonsistenzen entstehen können und die Java-Klassen generell beim Speichern der zugehörigen SQLJ-Dateien überschrieben werden. 왘 Sie befinden sich in der NetWeaver-Developer-Studio-Umgebung. 왘 Es ist bereits ein Projekt vorhanden. Ablauf Zum Anlegen neuer Dateien verwenden Sie den Wizard: 1. Wählen Sie File • New • Other … 2. Wählen Sie im linken Fensterbereich Persistence und daraufhin im rechten Bereich SQLJ Source. 3. Wählen Sie Next. 4. Geben Sie die erforlerlichen Informationen wie für eine Java-Datei an. Zum Konvertieren einer bestehenden Java-Quelldatei gehen Sie folgendermaßen vor: 304 Anwendungsschichten 1. Legen Sie eine Java-Quelldatei an, falls Sie dies noch nicht getan haben oder über keine verwendbare Datei verfügen. 2. Klicken Sie die Java-Quelldatei mit der rechten Maustaste an und wählen Sie im Kontextmenü Convert to SQLJ aus. Mit beiden Methoden erhalten Sie eine SQLJ-Datei und eine Java-Datei mit demselben Namen. Bearbeiten Sie niemals die Java-Datei, denn diese enthält den generierten Code. Ergebnis Der SQLJ-Checker ist transparent in SAP NetWeaver Developer Studio integriert. Beim Konvertieren der SQLJ-Dateien, was spätestens beim Speichern der Daten automatisch geschieht, werden über diesen Checker die eingebetteten SQL-Anweisungen geprüft. Zum einen wird die Konformität mit der Open-SQL-Grammatik validiert, zum andern wird das Schema gegen eine Offline-Katalogbeschreibung geprüft, die von .gdbtable-Dateien bereitgestellt wird. Diese Dateien entstammen aus der Phase, in der Sie das Java-Dictionary-Projekt angelegt haben, in ihnen sind sämtliche Metadaten enthalten, die auch für den Deploy-Vorgang des Java Dictionary verwendet werden. Validierung – SQLJ-Checker Damit diese Schemaprüfung durchgeführt werden kann, muss der SQLJKonverter den Pfad der .gdtable-Datei kennen, die den Offline-Katalog beschreibt, Sie müssen also jene Datei mit ihrem Projekt fest assoziieren: 1. Wählen Sie das Projekt. Vorgehensweise 2. Wählen Sie Properties im Kontextmenü. 3. Wählen Sie SQLJ Translator. 4. Wählen Sie XML Source und geben Sie den Pfad zur .gdtable-Datei ein. 5. Wählen Sie OK. Durch das Assoziieren der Offline-Katalogbeschreibung bietet das NetWeaver Developer Studio nun bereits während der Designzeit Unterstützung zum Ermitteln von SQL-Fehlern. Konvertierungs- und Java-Kompilierungsfehler werden direkt angezeigt und es wird die Option geboten, direkt zum relevanten Teil der SQLJ-Quelldatei zu navigieren, sowie Haltepunkte in der SQLJ-Quelldatei zu setzen. Grundsätzlich sollte Debugging nicht für den generierten reinen JavaCode verwendet werden, jedoch kann das Debugging von SQLJ-Quelldaten jederzeit aktiviert werden. Das Debugging selbst verhält sich allerdings ebenso wie das Debugging für Java-Quelldateien: Es werden innerhalb der Java-Quellen Haltepunkte gesetzt, der Quelltext wird Beschaffungslogik und Persistenz Debugging 305 schrittweise analysiert und die Werte werden überprüft. Sie können keine Haltepunkte für SQLJ-Anweisungen setzen. Vorgehensweise Um das SQLJ-Debugging zu aktivieren, gehen Sie folgendermaßen vor: 1. Wählen Sie Window • Customize Perspective … 2. Wählen Sie Other. 3. Wählen Sie SQLJ-Debugging. 4. Wählen Sie OK. 5. Daraufhin wird SQLJ-Debugging dem Run-Menü hinzugefügt. 6. Wählen Sie Run • SQLJ-Debugging Ergebnis SQLJ-Debugging ist nun für die aktuelle Sitzung aktiviert. Die Vorgehensweise für die Entwicklung der SQLJ-Quelltexte selbst verhält sich prinzipiell immer nach folgendem Schema: 1. Deklarieren Sie ein Datenbank-Connection-Context-Objekt, z.B.: #sql context SysCtx with (dataSource = "jdbc/myDB"); Dieses Objekt basiert auf der Connection Context Class. 2. Schaffen Sie eine Verbindung zur Datenbank, indem Sie das Objekt (Connection Context) instanziieren. 3. Nun arbeiten Sie mit dieser Verbindung, indem Sie SQL-Statements absetzen und die Ergebnisse verarbeiten können. 4. Schließen Sie die Connection. Kombination von SQLJ und JDBC Für die Entwicklung von dynamisch generierten Statements bietet sich aufseiten der relationalen Persistenz JDBC an, um dynamische SQL-Abfragen bzw. -Anweisungen zu implementieren. Da SAP für die Implementierung der Java-Persistenz die Verwendung von Open SQL, also SQLJ, empfiehlt und für interne Entwicklungen sogar explizit vorschreibt, stellt sich dem Entwickler natürlich die Frage, wie sich beide Modelle miteinander verbinden lassen. Um in einer Anwendung dynamische und statische Ausdrücke zu verwenden, lassen sich SQLJ und JDBC gemeinsam verwenden. JDBC Connections und SQLJ Connection Contexte sind gegenseitig konvertierbar, ebenso SQLJ-Iteratoren und JDBC Result Sets. 306 Anwendungsschichten Da Open SQL bzw. SQLJ zur Laufzeit über die Open SQL Engine in JDBCAnfragen umgewandelt werden, können beide dieselbe Datenbankverbindung und Transaktion nutzen. Hingegen können SQLJ und Native SQL/JDBC oder Vendor SQL/JDBC nicht dieselbe Verbindung oder Transaktion nutzen, da Letztere nicht den gesamten Stack der Open SQL Engine durchlaufen. Exchange Connections Alle Connection-Context-Klassen besitzen einen Konstruktor, der eine existierende JDBC-Verbindung als Argument beinhaltet. Eine über diesen Konstruktor erstellte SQLJ-Verbindung teilt die zugrunde liegende Datenbankverbindung mit der JDBC-Verbindung, aus der sie hervorgegangen ist. Wird der SQLJ-Verbindungskontext mit der close(boolean closeConnection)-Methode geschlossen, wird auch die zugrunde liegende JDBC-Verbindung beendet. Wird allerdings der boolsche Wert true, zur besseren Lesbarkeit auch in Form der Konstanten ConnectionContext.KEEP_CONNECTION, als Argument übergeben, wird beim Methodenaufruf close() lediglich das SQL Connection Context Object von der darunter liegenden JDBC-Verbindung losgelöst, diese wird also nicht geschlossen. JDBC-Verbindung mit SQLJ nutzen Im folgenden Codebeispiel wird ein SQLJ Connection Context ctx von der JDBC-Verbindung conn kreiert. Nun teilen ctx und conn dieselbe Datenbankverbindung. Die INSERT- und DELETE-Anweisungen werden beide auf dieser Verbindung ausgeführt und teilen sich dieselbe Transaktion. #sql context MyCtx; //... Connection conn = ... ; Statement stmt = conn.createStatemnt(); stmt.executeUpdate( "INSERT INTO ZRM_RES_MASTER (MANDT, RESID, RESTYPE, DESCRIPTION, INV_NUMBER, LOC_ADDRESS) VALUES (100, 1, 'R', 'Besprechungsraum 1.OG', null, '0000100100')"); MyCtx ctx = new MyCtx(conn); Beschaffungslogik und Persistenz 307 #sql [ctx] { DELETE FROM ZRM_RES_ MASTER WHERE RESID = 1 }; Listing 6.4 Connection-Sharing von JDBC nach SQLJ JDBC-Verbindung aus SQLJ-Context Die Methode getConnection() des Interfaces ConnectionContext erlaubt es, eine JDBC-Verbindung von einem zugrunde liegenden SQLJ Connection Context zu erhalten. Im folgenden Beispiel wird die dem SQLJ Connection Context ctx unterliegende JDBC-Verbindung außerhalb des SQLJ-Codes dem reinen Java-Code verfügbar gemacht. Nun teilen sich ctx und conn dieselbe Datenbankverbindung. Die INSERT- und DELETE-Anweisungen werden beide auf dieser Verbindung ausgeführt und teilen sich dieselbe Transaktion. #sql context DemoCtx with (dataSource = "jdbc/DEMO"); // ... DemoCtx ctx = new DemoCtx(); #sql [ctx] { INSERT INTO ZRM_RES_MASTER (MANDT, RESID, RESTYPE, DESCRIPTION, INV_NUMBER, LOC_ADDRESS) VALUES (100, 1, 'R', 'Besprechungsraum 1.OG', null, '0000100100') }; Connection conn = ctx.getConnection(); Statement stmt = conn.createStatemnt(); stmt.executeUpdate( " DELETE FROM ZRM_RES_MASTER WHERE RESID = 1"); Listing 6.5 Connection-Sharing von SQLJ nach JDBC Austauschen von Result Sets/ Iteratoren Ebenso wie Datenbankverbindungen gemeinsam genutzt werden können, lassen sich auch Result Sets und Iteratoren gemeinsam nutzen und können untereinander ausgetauscht werden. JDBC Result Set zu SQLJ Ein JDBC Result Set kann sehr einfach mit einem SQLJ-CAST-Statement in einen SQLJ-Iterator konvertiert werden. Innerhalb von Open SQL/SQLJ kann das CAST-Statement auf jeden Result-Set-Iterator innerhalb des derzeitigen Sichtbereichs angewandt werden. Um eine Kompatibilität mit SQLJ-Übersetzern anderer Hersteller zu gewährleisten, sollte das CASTStatement nur auf öffentlichen (public) Result-Set-Iteratoren angewendet 308 Anwendungsschichten werden. Ist das SQLJ-ResultSetIterator-Objekt einmal erstellt, sollten alle Operationen, um Daten zu beschaffen, über die Methoden dieses Objekts abgewickelt werden. Nachfolgend wird das JDBC Result Set rs in einen SQLJ Result Set Iterator über den CAST-Ausdruck umgewandelt. #sql iterator NamedIterator (String name); //... NamedIterator namIter; Connection conn = ... Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery("SELECT RESID FROM ZRM_RES_MASTER" ); #sql namIter = { CAST :rs }; while (namIter.next()) { System.out.println(namIter.name()); Listing 6.6 Umwandlung eines JDBC Result Sets in einen SQLJ Result Set Iterator Auf ähnliche Weise lassen sich SQLJ-Ergebnissätze innerhalb von JDBC verwenden. Dazu verfügt jedes ResultSetIterator-Objekt über die getResultSet()-Methode, um das letztlich zugrunde liegende JDBCResultSet-Objekt zu erhalten. Es zeigt sich auch an dieser Stelle ganz transparent, dass SQLJ letztlich die zugrunde liegende JDBC-Schicht lediglich vor dem Anwender maskiert. Iteratoren von SQLJ nach JDBC Ist im umgekehrten Falle das JDBC-ResultSet-Objekt einmal erstellt, sollte die Datenübernahme in das umgebende Java-Programm auch über diese spezielle Objektinstanz geschehen, anstatt durch die zusätzliche Instanziierung eines SQLJ-ResultSetIterator doppelten Aufwand zu betreiben. Im folgenden Beispiel wird auf dem SQLJ-ResultSetIterator-Objekt namIter die im vorletzten Absatz beschriebene Methode getResultSet() gerufen, die dieses als JDBC-ResultSet zurückgibt. Beschaffungslogik und Persistenz 309 #sql iterator NamedIterator (String name); //... NamedIterator namIter = null; #sql [ctx] namIter = { SELECT RESID FROM ZRM_RES_MASTER }; ResultSet rs = namIter.getResultSet(); while (rs.next()) { System.out.println(rs.getString(1)); } Listing 6.7 Übernahme eines SQLJ-Iterators durch JDBC Objektrelationale Persistenz Wir haben uns in den vergangenen Kapiteln bereits an mehreren Stellen mit der Eigenschaft der Objektorientierung der Sprache Java beschäftigt. Dabei wurde über eine Vielzahl an Details von Enterprise Beans großzügig hinweggegangen. In diesem Abschnitt werden nun die Entity Beans genauer betrachtet. Substantive von Geschäftsprozessen Entity Beans modellieren Geschäftskonzepte, die als Substantive ausgedrückt werden können. Diese grundsätzliche Regel hilft Entwicklern bei der Entscheidung, ob ein Geschäftskonzept ein Kandidat für die Implementierung als Entity Bean ist. Diese Art von Beans stellt im Gegensatz zu Session Beans keine Geschäftsprozesse dar, sondern Geschäftsobjekte bzw. fachliche Entitäten. Dabei beschreiben sie sowohl den Zustand als auch das Verhalten von Objekten der realen Welt und ermöglichen es dem Entwickler, die Daten und Geschäftsregeln, die zu bestimmten Konzepten gehören, einzukapseln. Somit repräsentieren diese Beans Daten in der Datenbank, weshalb Änderungen an ihnen auch Änderungen in der Datenbank nach sich ziehen. Es hat viele Vorteile, Entity Beans zu verwenden, anstatt direkt auf die Datenbank zuzugreifen. Die Daten werden in Objektform gebracht und stellen somit einen einfachen Mechanismus für den Zugriff auf dieselben und deren Veränderung dar, nämlich über die Methoden des jeweiligen Beans. Der Entwickler »spricht« quasi nicht mit der Datenbank, sondern über eine Methode – PersonObject.tellMeYourName() – mit Objekten. Bei entsprechender Verwendung wird die Implementierung erleichtert und der Code lässt sich einfacher verstehen. Bedenken Sie die 310 Anwendungsschichten Unmengen von – teilweise verschachtelten – SQL-Anweisungen. Zudem erhöht man so die Chancen, wieder verwendbare Software zu schreiben. Dabei ist jedoch zu beachten, dass ein Entity Bean sämtliche Funktionalitäten beinhaltet, um Datenkonsistenz und Einfachheit gegenüber dem Entwickler zu bieten. Wird ein neues Bean erzeugt, muss ein neuer Datensatz in der Datenbank eingefügt werden und eine Bean-Instanz mit diesen Daten verbunden werden. Wird das Bean verwendet und ändert sich sein Zustand, müssen diese Änderungen mit den Daten in der Datenbank synchronisiert werden: Einträge müssen hinzugefügt, geändert oder entfernt werden. Die Kommunikation zwischen Anwendung und Datenbank findet also weiterhin statt, ist jedoch dem Entwickler verborgen. Diesen Kommunikationsvorgang, die von einer Bean-Instanz repräsentierten Daten mit der Datenbank zu koordinieren, nennt man Persistenz. Man unterscheidet zwei Arten von Entity Beans, die nach verschiedenen Konzepten diese Persistenz realisieren. Container Managed Persistence sowie Bean Managed Persistence. Container Managed Persistence (CMP) Bei Container Managed Persistence wird die Persistenz automatisch vom EJB-Container verwaltet. Dieser weiß, wie die Instanzattributwerte des Beans auf die Datenbank- bzw. Tabellenfelder in der Datenbank abgebildet werden, und übernimmt das Einfügen, Ändern und Löschen der Daten, die in der Datenbank zu Entitäten gehören. Aus Sicht der Entwicklung sind CMP Entity Beans einfacher zu programmieren, da Sie sich auf die Implementierung der Geschäftslogik konzentrieren und die Verantwortlichkeit für Persistenz an den Container delegieren können. Bei Inbetriebnahme eines solchen Beans müssen Sie durch ein Mapping definieren, welche Felder vom Container verwaltet werden sollen und wie diese auf die Datenbank abgebildet werden. Ist diese Arbeit einmal getan, erzeugt der Container die notwendige Logik, um den Zustand der Bean-Instanz automatisch zu speichern. Entwicklersicht Felder, die auf die Datenbank abgebildet werden, heißen Container-verwaltete Felder. Sie können beliebige primitive Java-Typen oder serialisierte Objekte beinhalten. Der Vorteil der CMP liegt darin, dass das Bean unabhängig von der zugrunde liegenden Datenbank, die später seinen Zustand speichert, entwickelt werden kann. Container-verwaltete Beans können sowohl relationale als auch Objekt-Datenbanken verwenden. Beschaffungslogik und Persistenz 311 Der Zustand des Beans wird unabhängig definiert, was die Flexibilität und somit die Möglichkeit der Wiederverwendung erhöht. Nachteilig wirkt sich prinzipiell bei der Container-verwalteten Persistenz aus, dass Sie komplizierte Abbildungswerkzeuge benötigen, um zu definieren, wie die Felder auf die Datenbank abzubilden sind. In manchen Fällen genügt es allerdings auch, jedes Feld in dem Bean auf eine Spalte in der Datenbank abzubilden oder das Bean in eine Datei zu serialisieren. Oft wird es aber auch komplizierter, beispielsweise könnte der Zustand eines Beans anhand eines komplexen relationalen Datenbank-Joins definiert sein. Für die Definition des Mappings stehen jedoch zahlreiche Funktionen im SAP NetWeaver Developer Studio zur Verfügung. O/R Mapping Enterprise-BeanVorgaben Regeln für die CMP Felder Das Mapping wird von SAP als O/R Mapping (Object/Relational Mapping) bezeichnet. Dabei sind so genannte O/R-Mapping-Regeln (rules) zu beachten, die gewisse Mapping-Regeln und darüber hinaus definieren, welche Java-Datentypen auf welche JDBC-Typen abgebildet werden. Wird das O/R Mapping innerhalb des NetWeaver Developer Studios erstellt, werden diese Anforderungen erfüllt; wird jedoch von diesem Schema abgewichen, bietet das Developer Studio zusätzlich eine O/RMapping-Verifikation an. O/R-Mapping-Regeln Jede Entity-Bean-Klasse entspricht einer separaten Tabelle in der Datenbank. Um die Integrität der Daten zu gewährleisten, können verschiedene Bean-Klassen nicht in ein und derselben Tabelle abgebildet werden. Ein CMP-Feld, das ein atomares Attribut repräsentiert, wird auf eine einzelne Spalte gemappt. Folgende JDBC-Typen werden für die entsprechend korrespondierenden CMP-Felder akzeptiert: Java-Datentypen Mögliche JDBC-Datentypen Standard-JDBC-Datentyp java.lang.String VARCHAR, CHAR, LONGVARCHAR, CLOB VARCHAR byte[] VARBINARY, BINARY, LONGVARBINARY, BLOB VARBINARY java.lang.Byte[] VARBINARY, BINARY, LONGVARBINARY, BLOB VARBINARY Short SMALLINT SMALLINT Tabelle 6.1 Mapping-Regeln für CMP auf JDBC 312 Anwendungsschichten Java-Datentypen Mögliche JDBC-Datentypen Standard-JDBC-Datentyp java.lang.Short SMALLINT SMALLINT Int INTEGER INTEGER java.lang.Integer INTEGER INTEGER Long BIGINT BIGINT java.lang.Long BIGINT BIGINT Float REAL REAL java.lang.Float REAL REAL Double DOUBLE, FLOAT DOUBLE java.lang.Double DOUBLE, FLOAT DOUBLE java.math.BigDecimal DECIMAL, NUMERIC DECIMAL java.util.Date TIMESTAMP TIMESTAMP java.sql.Date DATE DATE java.sql.Time TIME TIME java.sql.Timestamp TIMESTAMP TIMESTAMP java.sql.Clob CLOB CLOB java.sql.Blob BLOB BLOB Boolean SMALLINT SMALLINT java.lang.Boolean SMALLINT SMALLINT Byte SMALLINT SMALLINT java.lang.Byte SMALLINT SMALLINT java.io.Reader VARCHAR VARCHAR java.io.InputStream VARBINARY VARBINARY Tabelle 6.1 Mapping-Regeln für CMP auf JDBC (Forts.) Beziehungen werden durch Referenzierung zwischen Primärschlüsselspalten und Fremdschlüsselspalten realisiert. Regeln für Referenzfelder Eine oder mehrere verschiedene Fremdschlüsselspalten werden pro Beziehung definiert. Gibt es n Verbindungen zwischen zwei Beans, müssen folglich n Mappings zwischen Primär- und Fremdschlüsselspalten existieren. Dabei muss der JDBC-Datentyp des Fremdschlüssels mit dem des Primärschlüssels übereinstimmen. Beschaffungslogik und Persistenz 313 Weiterhin darf eine Spalte vom Typ »unique key« nicht Teil eines Fremdschlüssels sein. Sie dürfen also eine Fremdschlüsselspalte nicht als »unique« oder Primärschlüssel – der somit dann auch den Status »unique« erhielte – definieren. 1-zu-1-Beziehung Bei der Realisierung einer 1-zu-1-Beziehung sind die Fremdschlüssel in einer der beiden Tabellen, die an dieser Beziehung teilhaben, enthalten. 1-zu-n-Beziehung In einer 1-zu-n-Beziehung sind die Fremdschlüssel in der Tabelle, die zu dem Bean gehört, das die n-Seite der Beziehung repräsentiert. n-zu-m-Beziehung Zur Realisierung einer n-zu-m-Beziehung ist es unabdingbar, dass Sie eine Zwischentabelle implementieren, die Fremdschlüssel zu beiden Primärschlüsseln der an der Beziehung beteiligten Objekte enthält. Die Spalten müssen vom selben JDBC-Typ wie auch die Primärschlüsselspalten sein. Einschränkungen Die Validierung des O/R Mapping ist nicht in der Lage, folgende Fehler zu erkennen und entsprechend zu behandeln: 왘 Eine Spalte ist als logischer Fremdschlüssel definiert, ist jedoch ein echter Fremdschlüssel. 왘 Eine Spalte ist ein Primärschlüssel, ist jedoch als Fremdschlüssel defi- niert. Die Container Managed Persistence wird auch oft deklarative Persistenz ganannt. Sie ist sehr einfach zu verwenden, wenn das Objektmodell der persistenten Daten kompliziert ist. Es müssen keine SQL-Abfragen programmiert werden – Sie können innerhalb der Entwicklungsumgbung das O/R Mapping, die entsprechenden Tabellen und SQL-Statements automatisch generieren lassen. Tutorium: Container-verwaltete Entity Bean erzeugen Dieses Tutorium beschreibt die Vorgehensweise zum Anlegen eines Entity Beans über den Wizard im SAP NetWeaver Developer Studio. Enterprise Beans können ebenso über die Kontextmenüs des relevanten Projekts angelegt werden. Voraussetzungen Ablauf Ein EJB-Module-Projekt existiert bereits. 1. Wählen Sie File • New • Other. 2. Auf der linken Seite der ersten Wizard-Seite wählen Sie J2EE • EJB, auf der rechten wählen Sie nun Enterprise Bean aus. 3. Klicken Sie Next. 314 Anwendungsschichten 4. Im Feld EJB-Name geben Sie einen Namen für das neue Entity Bean ein. 5. Wählen Sie im EJB-Projekt-Feld das Projekt aus, in dem das Bean enthalten sein soll. 6. Im Feld Bean Type wählen Sie Entity Bean. 7. Im Feld Default Package geben Sie ein Paket an, oder falls keines existiert, legen Sie hier ein neues an. 8. Wählen Sie Generate default interfaces oder spezifizieren Sie selbst, welche Interfaces generiert oder genutzt werden sollen, wie in Abbildung 6.6. Abbildung 6.6 Bean-Interfaces auswählen 9. Wählen Sie Next. 10. Wählen Sie nun den Persistenztyp – Container Managed Persistence oder Bean Managed Persistence, in unserem Fall nun Container Managed Persistence. Nun können Sie Persistenzfelder hinzufügen und entfernen, was später jedoch auch jederzeit noch möglich ist. 11. Wählen Sie Next. 12. Fügen Sie, wenn benötigt, Superklassen hinzu und klicken Sie auf Next. 13. Fügen Sie Methoden hinzu, auch dies ist später in der Entwicklungsphase jederzeit möglich. Für jede Methode wählen Sie den Typ der Methode und klicken auf Add. Geben Sie Namen und Rückgabetypen der Methoden an und spezifizieren Sie die Parameter. 14. Wählen Sie Finish. Beschaffungslogik und Persistenz 315 Ergebnis Im J2EE Explorer des SAP NetWeaver Developer Studios werden Sie nun ein Bild wie in Abbildung 6.7 vorfinden. Abbildung 6.7 Ergebnis im J2EE Explorer Sie können nun das Entity Bean im Quellcode editieren und die Felder für die Container-verwaltete Persistenz (CMP-Felder) erstellen. Ablauf 1. Wählen Sie im J2EE-Explorer-Fenster Ihr EJB-Projekt, dann ejb-jar.xml und schließlich dasjenige Enterprise Bean, dessen Felder Sie erstellen möchten. 2. Wählen Sie im Kontextmenü Open, im rechten Fenster werden die Bean-Eigenschaften dargestellt. 3. Wählen Sie den Tab Fields. 4. Wählen Sie Persistent Fields und klicken Sie auf Add. Es erscheint nun ein neues Persistenzfeld als Unterknoten innerhalb der Persistent Fields-Baumstruktur. 5. Selektieren Sie das entsprechende Feld und geben folgende Daten ein: 왘 Name: Der Name des Feldes. Das SAP NetWeaver Developer Stu- dio wird diesen Namen verwenden, um die korrespondierenden get- und set-Accessormethoden zu erstellen. Dabei wird in JavaManier der erste Buchstabe des Feldnamens großgeschrieben, mit entsprechend vorangestelltem set bzw. get. 왘 Fully Qualified Name: Der vollständig qualifizierte Name des Typs des Feldes, der auch den Packetnamen beinhalten muss. 왘 Array: Wählen Sie diese Option, um zu spezifizieren, dass das Per- sistenzfeld ein Array der spezifizierten Typen darstellt. Geben Sie die Dimension des Arrays in das Feld ein, das erscheint, wenn Sie Array option wählen. Die Werte dafür müssen zwischen 1 und 9 liegen. 316 Anwendungsschichten Die Persistenzfelder sind nun über die entsprechende ejb-jar.xml-Deployment-Deskriptor-Datei beschrieben. Ihr O/R Mapping wurde automatisch im SAP-J2EE-Engine-spezifischen Deployment-Deskriptor persitent.xml beschrieben. Diese Datei konfiguriert den EJB-Container, um die Container-verwaltete Persistenz zu übernehmen. In der persistent.xmlDatei sind dazu folgende Eigenschaften und Einstellungen enthalten: Ergebnis 왘 Datenquelle und Datenbankhersteller 왘 die Art und Weise der Sperrmechanismen für die Entity Beans 왘 das bereits behandelte O/R Mapping 왘 die Deployment-Eigenschaften der Finder- und Select-SQL-Methoden, die zur Performanceoptimierung der Entity Beans vom Container verwendet werden Beim Deployment-Prozess wird der gesamte benötigte Code nun aufgrund der Informationen in den Deployment-Deskriptoren vom EJBContainer generiert. Es lässt sich also feststellen, dass der Entwickler die Zugriffslogik nicht mehr implementieren muss, sondern lediglich Attribute und Beziehungen deklariert und konfiguriert. Bean Managed Persistence (BMP) Die Bean-verwaltete Persistenz ist wesentlich komplizierter als die Container-verwaltete Persistenz, weil Sie als Entwickler die Persistenzlogik explizit in der Bean-Klasse programmieren müssen. Sie müssen also die SQL-Abfragen selbst vollständig implementieren. Dadurch gestattet dieses Modell eine hohe Flexibilität, selbst festzulegen, wie der Zustand zwischen der Bean-Instanz und der Datenbank verwaltet wird. Entity Beans, die durch komplexe Joins, eine Kombination von verschiedenen Datenbanksystemen oder mit anderen Ressourcen wie Altsystemen definiert werden, profitieren in aller Regel von der Bean-verwalteten Persistenz. Auch wenn das O/R Mapping des abstrakten Schemas nicht den Anforderungen des Projekts entspricht, kann dieses Programmiermodell Unterstützung bieten. Mit der Container-verwalteten Persistenz lassen sich Objekte lediglich auf eine Tabelle abbilden und man ist dort doch relativ eingeschränkt, wenn es beispielsweise um echte verteilte Objekte geht, die eben nicht innerhalb einer Tabelle gehalten werden, sondern beispielsweise aus mehreren Attributen aus verschiedenen Datenquellen zusammengesetzt werden Beschaffungslogik und Persistenz 317 sollen. Hier bietet die Bean-verwaltete Persistenz deutlich mehr Möglichkeiten und Flexibilität. Der Nachteil der Bean-verwalteten Persistenz besteht darin, dass mehr Arbeit zur Definition des Beans notwendig ist. Sie müssen die Struktur der Datenbank verstehen und die Logik entwickeln, mit denen die mit einer Entität verbundenen Daten erzeugt, aktualisiert und entfernt werden. Dies erfordert höchste Sorgfalt im Umgang mit den generischen Bean-Methoden, insbesondere ejbLoad() und ejbStore(). Auch die im Home-Interface des Beans definierten Suchmethoden und das Mapping der Bean-Attribute auf die Datenbank müssen Sie explizit von Hand entwickeln. Eine Bean-verwaltete Anwendung ist nicht so datenbankunabhängig wie eine Container-verwaltete Entität, jedoch für den Umgang mit komplexen Daten eher geeignet. Sie können innerhalb eines Entity Beans sowohl reines herstellerspezifisches JDBC als auch die native Form sowie Open SQL, also SQLJ, verwenden, um eine größtmögliche Datenbankunabhängigkeit zu gewährleisten. Dennoch empfiehlt SAP innerhalb der objektrelationalen Entwicklung grundsätzlich die Verwendung von Container-verwalteter Persistenz bzw. Java Data Objects, auf die im Folgenden näher eingegangen wird. Aus diesem Grund wird an dieser Stelle nicht weiter auf die Bean-verwaltete Persistenz eingegangen, vielmehr empfiehlt sich für dieses Thema eine spezielle Java- bzw. J2EE-Lektüre. Java Data Objects (JDO) Java Data Objects sind der zweite von SAP präferierte Weg zur Realisierung objektrelationaler Persistenz. Der Java-Data-Objects-Standard ist eine viel versprechende Technologie für persistente Java-Objekte. JDO ist zwar einer der zahlreichen JavaStandards und wird meist in Verbindung mit J2EE genannt, ist jedoch weder Teil der J2EE-1.3- noch der 1.4-Spezifikationen. Doch aufgrund der Vorzüge, die JDO gegenüber dem EJB-Konzept teilweise bietet, ist JDO im SAP Web Application Server implementiert. Während EJB Entity Beans das Komponentenmodell der J2EE-Architektur zugrunde liegt, versucht der Java-Data-Objects-Standard, sich möglichst nahe an das Objektmodell der Programmiersprache Java zu halten. JDO erlaubt damit die direkte Persistierung fast jeder Java-Klasse, unabhängig von der Architekturebene, in der sich deren Objekte befinden. JDO erfor- 318 Anwendungsschichten dert also nicht das Container-Modell der J2EE-Umgebungen, sondern erweitert vielmehr direkt die Sprache Java um Persistenz. Zusätzlich ist es mit JDO möglich, auf Data Stores verschiedenster Typen zuzugreifen – relationale Datenbanken, objektrelationale Datenbanken oder dateibasierte Formate. JDO basiert auf einer Bytecodetransformation der zu persistierenden Klassen. Für jede Klasse, die persistent gespeichert werden soll, muss eine XML-basierte Mapping-Datei angelegt werden, die ähnlich wie bei CMP die Abbildung der Klassenattribute auf die Datenbanktabellen beschreibt. Der so genannte Bytecode Enhancer wird daraufhin die Zugriffsmethoden auf der Klasse überschreiben und durch die benötigten SQL-Statements ersetzen. JDO verfügt über einen Persistence Manager, der den Lebenszyklus, Transaktionen, Abfragen und Identitäten der Persistenzobjekte verwaltet. Die Abfragesprache ist JDOQL – Java Data Objects Query Language. JDO lässt sich innerhalb einer J2EE-Umgebung mit JSPs, Session Beans und Entity Beans (BMP) kombinieren, nicht jedoch mit Container-verwalteten Entity Beans (CMP). Tutorium: JDO-Entwicklungszyklus Die Entwicklung eines persistenten Objekts gestaltet sich für den Entwickler sehr strukturiert nach einem festen Schema. Die Entwicklungstools für JDO sind derzeit noch nicht in das Developer Studio integriert, weshalb sämtliche Schritte momentan noch manuell durchgeführt werden müssen. Dieses Tutorium stellt anhand eines Mitarbeiterobjekts einen Entwicklungszyklus dar, realisiert durch die Klasse Ressource: 1. Definition der Datenbanktabellen Zielsetzung Ablauf 2. Erstellen der zu persistierenden, reinen Java-Klassen Die Klassen, die später die Persistenz realisieren sollen, müssen zunächst, wie auch andere Java-Klassen im SAP NetWeaver Developer Studio, angelegt und implementiert werden. Jede dieser Klassen definiert letztlich Objekte, die in einer Datenbank gespeichert und aus der Datenbank bezogen werden können. Wählen Sie für das Anlegen der Klassen New der Klasse den Namen Ressource. • Java Class. Geben Sie Die neuen Java-Dateien öffnen sich automatisch und Sie können Code eingeben. Der folgende Codeausschnitt zeigt beispielhaft eine solche Beschaffungslogik und Persistenz 319 Klasse, wir beschränken uns an dieser Stelle auf drei Attribute des Ressourcenobjekts: public class Ressource { // Class attributes: the persistent fields // of a ressource. // Also defined inside the file Ressource.jdo private int resId; private String resType; private String description; // Required: a no-args constructor public Ressource() { this.resId = 1; this.resType = "INITIAL"; this.description = "INITIAL"; } // Constructor where the ID is set public Ressource(int resId) { this.resId = resId; this.resType = "INITIAL"; this.description = "INITIAL"; } // Implement the getter methods of the class public int getResId() { return resId; } public String getResType() { return resType; } public String getDescription() { return description; } // Implement the setter methods of the class public void setResType(String type) { 320 Anwendungsschichten resType = type; } public void setDescription(String desc) { description = desc; } } 3. Definieren der Objektidentität JDO sieht eine Identitätsklasse pro persistentem Objekt vor, die garantiert, dass eine einzelne JDO-Instanz mit einem Persistenzmanager assoziiert ist, der ein bestimmtes Datenspeicherobjekt – die Datenbank – repräsentiert. Während der JDO-Standard drei Typen der Identität beschreibt, nämlich Anwendungs- (Application), Datenspeicher- (Data Store) und nicht-dauerhafte Identität (Nondurable Identity), unterstützt die JDOImplementierung der SAP lediglich die Anwendungsidentität. In dieser Form wird die JDO-Identität durch die Anwendung verwaltet und im Datenspeicher gehalten. Die Instanzidentität entspricht meist dem Primärschlüssel. Zur Realisierung der Identität muss für jede persistente Klasse eine spezielle Objektidentitätsklasse erstellt werden. Diese wird als static public inner class ID der zugehörigen Persistenzklasse definiert. Für jedes Primärschlüsselfeld der Persistenzklasse erhält die Objektidentitätsklasse ein korrespondierendes public instance-Feld vom selben Namen und Datentyp. Die Objektidentitätsklasse hat einen Konstruktor ohne Argumente wie auch die Persistenzklasse. Weiterhin hat sie einen String-Konstruktor, der wie eine toString()-Methode eine Instanz als String zurückgibt. Zusätzlich muss sie eine hashCode()-Methode implementieren, die die Primärschlüssel zurückgibt, sowie eine equals()-Methode, die mit einem boolschen Rückgabewert zur Instanzüberprüfung bzw. zur Überprüfung der Instanzzugehörigkeit von Objekten dient. Der folgende Code zeigt beispielhaft die Identitätsklasse für das Ressourcenobjekt: import com.sap.jdo.SAPJDOHelper; static public class Id { // public field corresponding to the primary key of the Beschaffungslogik und Persistenz 321 // PC class public int resId; static { // establish the relation: Ressource$Id // class is the identity class for the PC // class Ressource. SAPJDOHelper.registerPCClass(Ressource.class); } public Id() { // required: a no-args constructor } public Id(int resId) { this.resId = resId; } public Id(String string) { // required: a string constructor // defined as the counterpart of toString() resId = Integer.parseInt(string); } public int hashCode() { // required: implement hashCode() return resId; } public String toString() { // required: toString() defined // as the counterpart of the string constructor return Integer.toString(resId); } public boolean equals(Object that) { // required: define equals() if (that == null || !(that instanceof Id)) return false; else return resId == ((Id) that).resId; } } 322 Anwendungsschichten 4. Definieren der JDO-Metadaten Nun können die XML-Metadaten für die JDO-Objekte definiert werden. Hierzu erhält jede Persistenzklasse eine zugehörige *.jdo-Datei, die im selben Verzeichnis liegen muss. 왘 Wählen Sie hierzu New • File. 왘 Geben Sie das richtige Verzeichnis an, in dem die Datei abgelegt werden soll, und geben Sie der Datei denselben Namen wie der entsprechenden Java-Klassen-Datei, mit der Endung .jdo. 왘 Sie können nun die Definitionen eingeben. Das folgende Listing zeigt beispielhaft den Code für die oben demonstrierte Java-Klasse: <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE jdo SYSTEM "jdo.dtd"> <jdo> <package name="temp.persistence.gettingstarted.jdo"> <class name="Ressource" identity-type="application" objectid-class="Ressource$Id"> <field name="resId" persistence-modifier="persistent" primary-key="true"/> <field name="resType" persistence-modifier="persistent"/> <field name="description" persistence-modifier="persistent"/> </class> </package> </jdo> 5. Definieren des O/R Mappings für die Persistenzklassen Nun kann das Mapping erzeugt werden, dies geschieht wiederum über eine entsprechende XML-Datei, die ebenfalls unter demselben Namen im selben Ordner erstellt werden muss, jedoch mit der Dateiendung *.map. Das Format dieser Klasse wird durch die JDO Mapping Metadata Document Type Definition (DTD) spezifiziert. Beschaffungslogik und Persistenz 323 Das Anlegen der Datei vollzieht sich ebenso wie das der *.jdo-Datei, nachfolgend wird das Mapping zu unserem Beispiel demonstriert: <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE map SYSTEM "map.dtd"> <map version="1.0"> <package name="temp.persistence.gettingstarted.jdo"> <class name="Ressource"> <field name="resId"> <column name="RESID" table="ZRM_RES_MASTER"/> </field> <field name="resType"> <column name="RESTYPE" table="ZRM_RES_MASTER"/> </field> <field name="description"> <column name="DESCRIPTION" table="ZRM_RES_MASTER"/> </field> </class> </package> </map> 6. Bytecode-Kompilierung durch das Enhancer-Tool Nachdem nun die entsprechenden Klassen und Deskriptoren erstellt sind, müssen nun durch den JDO Enhancer die entsprechenden Klassen erzeugt werden, mit denen letztlich die Geschäftslogik der Anwendung kommunizieren soll. Da die JDO-Enhancer- und Validierungstools noch nicht in das Developer Studio integriert sind, müssen diese Schritte manuell durchgeführt werden. Es empfiehlt sich die Verwendung des ANT-Build-Tools, das als Plug-In für das Developer Studio bereitsteht. 왘 Erstellen Sie zunächst über das Kontextmenü Ihres Projekts eine neue Datei mit dem Namen build.xml und speichern Sie diese im Hauptverzeichnis ihres Projekts. 왘 Fügen Sie – im Falle unseres Beispiels – folgenden Code ein: <project name="GettingStartedWithJDO" default="enhance" basedir=".."> <property name ="sourceproject.dir" value="GettingStartedJDOWeb"/> 324 Anwendungsschichten <property name ="dictproject.dir" value="GettingStartedPersistenceDic"/> <property name ="src.dir" value="${sourceproject.dir}/source"/> <property name ="bin.dir" value="${sourceproject.dir}/bin"/> <property name ="catalog.dir" value="${dictproject.dir}/gen_ddic /dbtables/"/> <property name ="enhancer" value="com.sap.jdo.enhancer.Main"/> <property name ="utility" value="com.sap.jdo.sql.util.JDO"/> <property name ="tssap" value="C:/Program Files/SAP/JDT/eclipse /plugins"/> <property name ="jdo" value="${tssap}/com.sap.ide.eclipse.ext .libs.jdo/lib/jdo.jar"/> <property name ="xml" value="${tssap}/com.tssap.sap.libs .xmltoolkit /lib/sapxmltoolkit.jar"/> <property name ="jdoutil" value="${tssap}/com.sap.jdo.utils /lib/sapjdoutil.jar"/> <property name ="dictionary" value="${tssap}/com.sap.dictionary.database /lib/jddi.jar"/> <property name ="logging" value="${tssap}/com.tssap.sap.libs.logging /lib/logging.jar"/> <property name ="catalogreader" value="${tssap}/com.sap.opensql /lib/opensqlapi.jar"/> <property name ="classpath" value="${jdo};${jdoutil};${xml}"/> Beschaffungslogik und Persistenz 325 <property name ="classpath.check" value="${classpath};${dictionary};${logging}; ${catalogreader};${bin.dir}"/> <target name="enhance"> <antcall target="enhance.Ressource"/> </target> <target name="check"> <antcall target="check.Ressource"/> </target> <target name="enhance.Ressource"> <java fork ="yes" failonerror="yes" classname ="${enhancer}" classpath ="${classpath}"> <arg line ="-f -d" /> <arg value="${bin.dir}"/> <arg value="${src.dir}/temp/persistence/ gettingstarted/jdo/Ressource.jdo"/> <arg value="${bin.dir}/temp/persistence/ gettingstarted/jdo/Ressource.class"/> </java> </target> <target name="check.Ressource"> <java fork ="yes" failonerror="yes" classname ="${utility}" classpath ="${classpath.check}"> <arg line ="-v -p" /> <arg value ="${sourceproject.dir} /checker.properties" /> <arg value ="-c"/> <arg value ="${catalog.dir}"/> <arg value ="check"/> <arg value ="temp/persistence/gettingstarted/jdo 326 Anwendungsschichten /Ressource.class"/> </java> </target> </project> 왘 Nun müssen Sie über das Kontextmenü des Projekts eine neue Datei namens checker.properties erstellen, die ebenfalls im Hauptordner des Projekts enthalten sein muss. Fügen Sie dort folgenden Code ein: com.sap.jdo.sql.mapping.useCatalog=true com.sap.jdo.sql.mapping.checkConsistency=true com.sap.jdo.sql.mapping.checkConsistencyDeep=true 7. In der Java-Perspektive des Developer Studios öffnen Sie nun das Kontextmenü der build.xml-Datei und wählen Run ANT… 8. Im Tab Targets wählen Sie zusätzlich zu enhance die Option check. 9. Wählen Sie Apply und daraufhin Run. Das Ergebnis dieses Prozesses wird über die Konsole des Developer Studios ausgegeben. 10. Nun implementieren die daraus generierten Klassen das Interface javax.jdo.spi.PersistenceCapable und können nun von der Geschäftslogik verwendet werden. Die Abbildung 6.8 fasst den Entwicklungsprozess der JDO-Persistierung nochmals zusammen. Ergebnis Obgleich die Vorgehensweise der Entwicklung mit JDO sehr strukturiert abläuft, ist es doch nachteilig, dass die entsprechenden Werkzeuge für den Umgang mit JDO noch nicht im Developer Studio integriert sind. Ein Großteil der Entwicklungszeit muss in Arbeit investiert werden, die eigentlich durch den Einsatz entsprechender Tools generisch abgearbeitet werden könnte. So kommt es insbesondere bei größeren Projekten hier schnell zu Fehlern, da man als Entwickler schnell die Übersicht über die manuell erstellten Dateien verlieren kann. Beschaffungslogik und Persistenz 327 Java-Klasse schreiben .java .jdo Klasse kompilieren Compiler Datenbanktabellen deklarieren .class Klasse um JDOMetadaten erweitern Persistente Eigenschaften deklarieren (XML) Enhancer Tool (IDE) .class .map Objektrelationales Mapping und Tabellen deklarieren (XML) Persistente Java-Klasse in der Applikation verwenden Abbildung 6.8 JDO-Entwicklungszyklus Das volle Potenzial des JDO-Ansatzes wird im SAP-Umfeld wohl erst ausgenutzt werden, wenn die entsprechenden Erweiterungen für das Developer Studio seitens der SAP nachgereicht werden. Wie bereits angesprochen, empfiehlt SAP im Bereich der objektorientierten Persistenz die Verwendung von Container-verwalteter Persistenz oder von JDO, Bean-verwaltete Persistenz wird derzeit völlig außer Acht gelassen. Die folgende Tabelle vergleicht nun die von SAP empfohlenen beiden Ansätze und gibt Vorschläge für den jeweiligen Einsatzbereich: JDO 1.0 Entity Beans (CMP) 2.0 Nicht Teil des J2EE-Standards, wird in den J2EE-Anwendungsservern also nur optional unterstützt. Teil des J2EE-Standards, die Unterstützung von CMP ist durch die J2EE-Spezifikationen gesichert. CMP bietet ein komplettes Programmiermodell. Bietet keine Unterstützung von direkten Remote-Calls; diese können jedoch unter Verwendung von Facade Session Beans realisiert werden. Direkte Remote-Aufrufe sind möglich, trotz allem wird empfohlen, Entity Beans nur lokal zu verwenden, während Remote-Kommunikation an Facade Session Beans delegiert werden sollte. Keine Sicherheitsfunktionalitäten implizit vorhanden. CMP-Spezifikationen enthalten Sicherheitsfeatures. Tabelle 6.2 Vergleich zwischen JDO und Entitiy Beans 328 Anwendungsschichten JDO 1.0 Entity Beans (CMP) 2.0 JDO ist sowohl in verwalteten Umgebun- Nur in verwalteten Umgebungen wie im gen als auch in offenen Java-UmgebunEJB-Container lauffähig. gen verfügbar. Unterstützt Vererbung und Interfaces, multiples Mapping und Mapping zu Legacy-Tabellen. Unterstützt keine Vererbung und Interfaces. Ein Bean kann nur auf einer einzigen Tabelle abgebildet werden. Eine separate Klasse für die Primärschlüs- Eine Primärschlüsselklasse ist nicht notsel ist notwendig, derzeit ist diese Imple- wendig. mentierung nicht automatisiert und entsprechend aufwändig. Nicht zentral verwaltete Beziehungen unter Objekten untereinander. Zentral verwaltete Beziehungen unter den Objekten untereinander. JDOQL erlaubt dynamische Abfragen, die immer Objekte zurückliefern. Allerdings hat JDOQL eine Java-ähnliche Syntax und ist weniger mächtig. Mit EJBQL können nur statische Abfragen ausgeführt werden, die unveränderliche Datensets zurückliefern. EJBQL hat eine mit OQL vergleichbare Syntax und ist mächtiger als JDOQL. Schnellere Deploymentzyklen. Die Deploymentvorgänge sind sehr komplex, daher auch entsprechend langsamer. Kenntnisse in der Sprache Java sind für die Implementierungen im Wesentlichen ausreichend. Der Umgang mit EJBs erfordert umfangreiches Wissen über Objektorientierung und verteilte Konzepte. Tabelle 6.2 Vergleich zwischen JDO und Entitiy Beans (Forts.) 6.2 Middleware: Konnektivität zwischen Applikationen Bereits mehrfach wurde nun angesprochen, dass eine Konnektivität zwischen ABAP- und Java Personality grundsätzlich auf Anwendungsebene erfolgen muss. Es ist einer Java-Anwendung normalerweise nicht möglich, auf direktem Wege – beispielsweise durch etwaige JDBC-Treiber – auf die Datenbank der ABAP-Instanz zuzugreifen. Dies gilt auch im umgekehrten Fall für ABAP-Anwendungen. Durchgriff auf ABAP Personality Aus Sicht der Persistenz sind derartige Verbindungen zur reinen Datenbeschaffung notwendig, aus Sicht der Geschäftslogik wird in vielen Projekten eine Kommunikation zwischen beiden Entitäten angestrebt, um Geschäftsprozesse auf einer der beiden Identitäten auszuführen. In beiden Fällen findet der Austausch von Informationen und Daten auf der Komponentenebene statt, basierend auf einer Middleware-Infrastruktur, Middleware: Konnektivität zwischen Applikationen 329