11.07.2015 Aufrufe

2 Grundlagen Persistenzframeworks

2 Grundlagen Persistenzframeworks

2 Grundlagen Persistenzframeworks

MEHR ANZEIGEN
WENIGER ANZEIGEN

Sie wollen auch ein ePaper? Erhöhen Sie die Reichweite Ihrer Titel.

YUMPU macht aus Druck-PDFs automatisch weboptimierte ePaper, die Google liebt.

Westfälische Wilhelms-Universität MünsterAusarbeitung<strong>Persistenzframeworks</strong>im Rahmen des Software Engineering-SeminarsGeorg SenftThemensteller: Prof. Dr. Herbert KuchenBetreuer: Dipl.-Wirt.-Inf. Christian HermannsInstitut für WirtschaftsinformatikPraktische Informatik in der Wirtschaft


Inhaltsverzeichnis1 Einleitung ................................................................................................................... 12 <strong>Grundlagen</strong> <strong>Persistenzframeworks</strong> ............................................................................ 22.1 Definition und Einordnung ................................................................................ 22.2 Objektrelationales Mapping ............................................................................... 32.3 Besondere Aspekte für <strong>Persistenzframeworks</strong> ................................................... 73 LINQ .......................................................................................................................... 93.1 Überblick und Architektur ................................................................................. 93.2 Spracherweiterungen für LINQ ........................................................................ 103.3 Objektrelationales Mapping und Abfragen mit LINQ to SQL ........................ 123.4 LINQ to Entities und ADO.NET 3.0 ............................................................... 173.5 LINQ to XML .................................................................................................. 184 Hibernate und weitere Alternativen ......................................................................... 194.1 Hibernate und JPA ........................................................................................... 194.2 NHibernate für .NET und Quarae für Java ...................................................... 215 Fazit und Ausblick ................................................................................................... 22A Quellcode zu den Abfragen im LINQ to SQL-Beispiel .......................................... 23Literaturverzeichnis ........................................................................................................ 25II


Kapitel 1: Einleitung1 EinleitungDer Umgang mit Datenbeständen spielt beim Großteil aller Anwendungssysteme einewesentliche Rolle. Seien es in einem großen ERP-System die Ergebnisse einzelnerVerarbeitungsschritte abgebildeter Geschäftsprozesse oder der Abruf vonAdressinformationen aus einer simplen Anwendung zur Kontaktverwaltung; dasErzeugen, Einlesen, Verarbeiten und Speichern von Datenstrukturen stellt einenzentralen Vorgang dar, auf dessen Grundlage die meisten Anwendungsfälle erstermöglicht werden. Infolgedessen nehmen bei der Entwicklung von Anwendungen dieEntwurfsentscheidungen, die sich auf den Umgang mit Datenbeständen beziehen,ebenfalls einen bedeutsamem Stellenwert ein. Zudem bilden bei Entwicklungsprojektendie Implementierungsschritte zu diesem Teilaspekt häufig eine zeitraubende, da oft auchfehleranfällige Aktivität. Daher sind diesbezügliche Erleichterungen gefragt, auch umeine Fokussierung der Entwicklungsaktivitäten auf die Anwendungslogik zu bewirken.Der Einsatz von <strong>Persistenzframeworks</strong> zielt auf eine derartige Erleichterung ab. DieseAusarbeitung befasst sich mit der Untersuchung ausgewählter Frameworks, um einenEindruck zu vermitteln, mit welchen Mechanismen und welchem Grad der Problem-Abdeckung Erleichterungspotenziale durch sie bereitgestellt werden. Das Augenmerkliegt dabei auf den Lösungen zum Zusammenspiel mit relationalen Datenbanken aus derPerspektive der objektorientierten Programmiersprachen C# und Java. Hervorgehobenwird überdies die Untersuchung von Persistenzlösungen im Bereich der MicrosoftLINQ-Technologie, weil es sich hierbei um einen vielversprechenden, dasprachintegrierten und zugleich flexiblen Ansatz handelt.Der Gang der Untersuchung beginnt mit der Erklärung und Einordnung des Begriffs des<strong>Persistenzframeworks</strong> und behandelt das Problemfeld des objektrelationalen Mappings,typischer Lösungsansätze sowie weiterer Aspekte, für die ein Erleichterungspotenzialfür die datenbezogene Anwendungsentwicklung wünschenswert ist. Dem schließt sichim Hauptteil die ausführliche Betrachtung von LINQ an. Sowohl die dabei zu Grundegelegten Spracherweiterungen als auch die Persistenzlösung für Microsoft SQL-Datenbanken stehen dabei im Vordergrund. Zum Vergleich erfolgt eine Untersuchungdes für Java entwickelten <strong>Persistenzframeworks</strong> Hibernate, dessen Ablegers für .NETNHibernate und eine kurze Betrachtung des Java-Projektes Quarae.1


Kapitel 2: <strong>Grundlagen</strong> <strong>Persistenzframeworks</strong>2 <strong>Grundlagen</strong> <strong>Persistenzframeworks</strong>2.1 Definition und EinordnungDer Begriff Framework steht im Allgemeinen für eine Menge von Objekten, die einegenerische Lösung für eine Reihe verwandter Probleme implementieren [BBE95]. Aufden Bereich der Softwaretechnik bezogen liefert ein Framework einewiederverwendbare, für gewöhnlich nicht domänen- sondern konzeptspezifische[SGM02] Menge an Klassen mit Bezug auf eine bestimmte Aufgabe, deren Rollen undZusammenspiel geregelt sind, und legt auf diese Weise Entwurfsentscheidungen fest.Im Vergleich zu einer Klassenbibliothek werden Stellen zur Erweiterung undAnpassung der Funktionalität der Klassen bereitgehalten und spezifiziert. Frameworksfür die objektorientierte Software-Entwicklung bedienen sich dabei insbesondere demPrinzip der Vererbung, wobei der Entwickler eine abstrakte Framework-Klasse füreinen konkreten Anwendungsfall erweitert, sie folglich mit spezifisch implementiertenMethoden ausstattet. Hierbei ist die Kenntnis des Zusammenspiels der Framework-Elemente und somit der Einblick in dessen Funktionsweise erforderlich, weswegendieser Ansatz als White-Box-Wiederverwendung bezeichnet wird und sich von derBlack-Box-Wiederverwendung abgrenzt, bei der direkt instanziierbare, uneinsehbareKlassen als Wiederverwendungs-Komponenten zum Einsatz kommen. Häufig werdenauch Mischformen beider Verwendungsansätze verfolgt. Das Ausmaß, in dem einFramework Lösungen für Probleme eines Anwendungsfalls abdeckt, richtet sich nachdem Grad der Standardisierbarkeit der Probleme und dem Umfang, in demimplementierte Lösungen vorliegen [BBE95].Persistenz bezeichnet die Fähigkeit einer Anwendung, Daten im Sinne von Objekten,deren Zustände und Verbindungen aus dem aktuellen Programmablauf in einen nichtflüchtigenSpeicher zu bewegen, mit dem Ziel, sie dort langfristig zu sichern und zueinem späteren Zeitpunkt eine adäquate Wiederherstellung vorzunehmen [BaHel99,Kap. 12]. Im Wesentlichen kommen zur langfristigen Datenhaltung zwei Verfahreninfrage: Die Speicherung der Anwendungsdaten in Dateien oder in Datenbanken.Dateien liegen im Dateisystem des Betriebssystems und beinhalten eine Repräsentationder Daten in einem anwendungsspezifischen oder allgemeinen Dateiformat,beispielsweise in Form einer Bytefolge als Ergebnis einer Objektserialisierung oder alsXML-Daten. Die Speicherung der Anwendungsdaten in einer Datenbank ist als2


Kapitel 2: <strong>Grundlagen</strong> <strong>Persistenzframeworks</strong>Übergabe an ein zumeist relationales Datenbank-Management-System aufzufassen, dasdie Datenhaltung übernimmt und der Anwendung unter anderem Schnittstellen zumLesen und Schreiben der Daten anbietet. Beide Verfahren erfordern jeweils spezielleMechanismen, die Dateien im entsprechenden Format zu schreiben und zu lesenbeziehungsweise die Interaktion mit dem Datenbank-System abzuwickeln, welche vonder Anwendung realisiert werden müssen. Ein Persistenzframework stellt demAnwendungsentwickler derartige Mechanismen bereit, wobei dies durch eineeinheitliche Schnittstelle erfolgen kann und auf diese Weise eine Kapselung mit derdamit verbundenen Entkoppelung der Anwendung vom spezifischen Datenhaltungs-Verfahren angestrebt werden kann.Für die Entwicklung von Geschäftsanwendungen wird in der Praxis häufig der Ansatzeiner Drei-Schichten-Architektur verfolgt [DH03, S. 17 ff.], bei der drei Software-Schichten nach technischen und logischen Aufgabenbereichen getrennt hierarchischangeordnet sind [BaHei99, Kap. 10]: Die GUI-Schicht mit der Aufgabe, dieBenutzerinteraktion und die Präsentation von Daten zu realisieren, die Fachkonzept-Schicht, welche die Abbildung von Geschäftsobjekten sowie -prozessen und damit denfunktionalen Kern der Anwendung beherbergt, und die Datenhaltungs-Schicht zurRealisierung der Persistenz. Dabei werden für die Datenhaltung vonGeschäftsanwendungen derzeit zumeist relationale Datenbank-Management-Systemeeingesetzt, die durch ihre Eignung für verteilt und parallel zugreifende Anwendungen,Mehrbenutzerumgebungen und den effizienten Umgang mit großen Datenmengen dendateibasierten Methoden weitaus überlegen sind. In dem beschriebenen Szenario kannein Persistenzframework bei der Implementierung eingesetzt werden, um die Aufgabender Datenhaltungs-Schicht sowohl durch die Bereitstellung von Funktionalität als auchdurch festgelegte Entwurfsmuster zu unterstützen und im Idealfall gar weitestgehend zuübernehmen.2.2 Objektrelationales MappingRelationale Datenbanken, basierend auf dem Modell von [Co70], speichern Daten inzweidimensionalen Tabellen, auch Relationen genannt. Die Spalten jener Tabellenwerden als Attribute bezeichnet, die Zeilen als Datensatz oder auch Tupel. Attributeliegen dabei typisiert vor und können die Funktion eines Primärschlüssels zureindeutigen Identifizierung eines Datensatzes innerhalb der Tabelle erfüllen oder auch3


Kapitel 2: <strong>Grundlagen</strong> <strong>Persistenzframeworks</strong>durch Aufnahme des Primärschlüsselwertes eines Datensatzes einer anderen oderderselben Tabelle eine Fremdschlüssel-Beziehung abbilden. Jede Tabelle muss mitmindestens einer Primärschlüssel-Spalte ausgestattet sein. Der Entwurf derTabellenstruktur folgt dabei den Regeln der Normalisierung, um wohlgeformteRelationen zu entwickeln und Dateninkonsistenzen und -redundanzen zu vermeiden[Ke83]. Als Datenbanksprache hat sich SQL, die Structured Query Language, derzeit inVersion 2006, durchgesetzt, die Kommandos zur Datenmanipulation und -abfrage wieSELECT, INSERT, UPDATE und DELETE sowie Befehle zur Definition derTabellenstrukturen und zur Zugriffskontrolle vorsieht.Indes wird in objektorientierten Programmiersprachen mit vererbbaren Klassen,Methoden und Attributen sowie daraus instanziierten Objekten und Verweisengearbeitet; einem Paradigma, das sich vehement von dem der normalisierten Relationenunterscheidet und, allein auf die Struktur und die Verbindung von Objekten bezogen,einen deutlich größeren Spielraum bietet. So sind beispielsweise die Prinzipien derVererbung und Polymorphie im relationalen Modell nicht vorgesehen. UmObjektgraphen datenbankbasiert zu persistieren, bieten sich verschiedene Möglichkeitenan: Neben objektorientierten Datenbanken, die in der Praxis nur spärlich zum Einsatzkommen [Le00], kann man objektrelationale Datenbanken, deren relationaler Ansatzum benutzerdefinierte Datentypen erweitert wurde, oder XML-Datenbanken, die XML-Daten als Attributtyp vorsehen, verwenden. Unter der Prämisse, ein rein relationalesDatenbanksystem zu verwenden, bestehen jedoch nur die zwei Vorgehensweisen,entweder Objektserialisierungs-Ergebnisse als einzelnen Attributwert abzulegen oderein speziell für die Aufnahme der Objektklassen modelliertes Datenbankschema, dasKlassen, Attribute und Verweise adäquat abbildet, zu verwenden. Nur die letzteMethode, die auch als objektrelationales Mapping bezeichnet wird, nutzt effektiv dieFähigkeiten des relationalen Datenbankmodells und ermöglicht beispielsweise dieeffiziente, wertbasierte Suche.Beim objektrelationalen Mapping gilt es, den Impedanz-Unterschied [Am03, Kap. 7] zuüberwinden, der sich aus der Verschiedenheit des objektorientierten und desrelationalen Modells ergibt. Zwar lassen sich für objektorientiert organisierte Strukturenzum Zwecke der Persistenz relationale Datenstrukturen erstellen, die eine vollständigeAbbildung der Daten aufnehmen können, jedoch sind hierfür vielfältige Mechanismenund Konstrukte notwendig, da eine direkte Abbildung in dem Sinne, dass beispielsweise4


Kapitel 2: <strong>Grundlagen</strong> <strong>Persistenzframeworks</strong>eine Klasse ihr vollständiges Pendant in einer Tabelle findet, nur in sehr einfachenFällen möglich ist. Im Übrigen gelten vergleichbare Probleme auch für die Abbildungvon objektorientierten Datenstrukturen in hierarchische XML-Dokumente, deren semistrukturierterAufbau zwar dem von Objekten näher kommt, jedoch ebensoMechanismen zur Abbildung in Elemente, Attribute und Dokumente erfordert. ImFolgenden werden die Lösungsansätze des objektrelationalen Mappings anhand derAusführungen von [BHRS07], [Me08] und [Ke97] beleuchtet:Im Kern folgt das objektrelationale Mapping der Strategie, dass eine Klasse als eineTabelle modelliert wird. Die zu speichernden Attribute bzw. Eigenschaften der Klasse,die in Form von primitiven Datentypen vorliegen, finden sich als Attribute der Tabelle,also Spalten, wieder. Eine gespeicherte Objektinstanz entspricht dabei einem Datensatz.Attribut-Werte in primitiven Datentypen müssen in entsprechende Datentypen deskonkret eingesetzten, relationalen Datenbankmanagementsystems überführt werden, daes sowohl bezüglich der Typensysteme von Java oder .NET/CLR im Vergleich zu SQLals auch beim Vergleich der SQL-Dialekte verschiedener Datenbank-Hersteller keinevollständige Übereinstimmung gibt.Liegt ein zu speicherndes Attribut nicht in Form eines primitiven Datentypes, also alsVerweis auf die Objekt-Instanz einer anderen oder derselben Klasse, vor, so hat fürdiese Klasse, falls noch nicht geschehen, ebenfalls eine Modellierung als Tabelle zuerfolgen. Über eine Fremdschlüsselrelation wird der Bezug hergestellt. Sowohl zudiesem Zweck, als auch zur Beschreibung der eindeutigen Identität von Objekt-Datensätzen bei Lese- und Schreiboperationen müssen die Tabellen mit einemPrimärschlüssel ausgestattet sein, der wiederum in die abgebildete Klasse alszusätzliches Attribut aufgenommen werden muss. Per se ist dies bei objektorientiertenProgrammen nicht notwendig, bei relationalen Datenbanken jedoch Pflicht. Weiterhinstellt sich die Frage, welchen Wert ein Primärschlüssel für ein abgebildetes Objektannimmt, damit unter Einhaltung der referenziellen Integrität eine Eindeutigkeit in derTabelle und über alle Objekte der Klasse gewährleistet ist. Zwei Ansätze stehen zurWahl des Identitätswertes zur Verfügung: Der erste Ansatz sieht die Generierung durchdas Datenbanksystem vor, wobei dies dort beispielsweise unter Verwendung einerfortlaufenden Nummerierung zuverlässig erfolgt, aber nach Erzeugung des Datensatzeseine zusätzliche Leseoperation zur Ermittlung dieses Wertes vonseiten der Anwendungstattfinden muss. Der zweite Ansatz sieht die Generierung eines pseudo-eindeutigen5


Kapitel 2: <strong>Grundlagen</strong> <strong>Persistenzframeworks</strong>Wertes durch die Laufzeitumgebung der Anwendung vor, denkbar in Java 5 als UUIDund in .NET als Guid oder andere Verfahren zur Gewinnung von Zufallszahlen, wobeiKollisionen jedoch nur äußerst unwahrscheinlich sind. Auf die beschriebene Weiselassen sich 1:1- und n:1-Beziehungen realisieren.Über die genannten Fälle der Objekt-Attribute in Form von primitiven oder komplexenDatentypen hinaus verwendet man in objektorientierten Sprachen mengenwertigeAttribute wie Listen oder Kollektionen. Stellt deren Verwendung eine 1:n-Beziehungdar, so erfolgt die Abbildung in der Datenbank in umgekehrter Richtung derAbhängigkeitsbeziehung. Insofern nimmt die Tabelle der verwiesenen Klasse einFremdschlüssel-Attribut auf, welcher verweisenden Objekte ihre Objekte zugeordnetwerden. Auch dies wirkt sich wiederum auf die verwiesene Klasse auf, die dieseszusätzliche Attribut führen muss, was einen aus der objektorientierten Perspektiveunnötigen, zyklischen Verweis ergibt. Durch die Umkehrung der Verweisrichtung ist eserforderlich, beim Laden der verweisenden Klasse außerdem passende Einträge aus derTabelle der Verwiesenen abzufragen. Darüber hinaus ist es bei der Verwendungmengenwertiger Attribute ohne Weiteres denkbar, dass ein Objekt der verwiesenenKlasse in mehreren Instanzen des mehrwertigen Attributes der verweisenden Klasseauftauchen kann. In diesem Fall handelt es sich um eine n:m-Beziehung, derenrelationale Abbildung nicht ohne eine Zwischentabelle mit Fremdschlüsseln aufverweisende und verwiesene Datensätze erfolgen kann, mit dem Resultat aufwendiger,mehrstufiger Abfragen und Mechanismen zur Sicherstellung der referenziellenIntegrität. Da sich im objektorientierten Quelltext die 1:n-Verwendung einesmehrwertigen Attributs nicht von der n:m-Verwendung unterscheiden lässt, kann eineFallunterscheidung nicht ohne Zusatzinformationen erfolgen.Ferner ist das Prinzip der Vererbung der Attribute bzw. Eigenschaften und Methodenvon Oberklassen an Unterklassen in objektorientierten Sprachen von zentralerBedeutung – Mehrfachvererbung sei hier nicht betrachtet. In relationalen Datenbankenexistiert dieses Prinzip nicht; es ist keine Struktur-Ebene zur derartigen Verknüpfungvon Tabellen vorhanden. Zur Abbildung von Klassenhierarchien beim objektrelationalenMapping gibt es drei Ansätze: Beim ersten Ansatz wird je Klasse eineTabelle verwendet, wobei diese die jeweiligen, individuellen Attribute der Ober- undUnterklassen aufnehmen. Die Tabellen der erweiternden Unterklassen nehmen dabeinur die zusätzlichen Attribute auf. Jede Tabelle verfügt ferner über einen6


Kapitel 2: <strong>Grundlagen</strong> <strong>Persistenzframeworks</strong>Primärschlüssel für den eindeutigen Identitätswert eines Objektes. Für ein Unterobjekttaucht dieser Wert somit in jeder Tabelle entlang seines Vererbungspfades auf. AlsResultat werden Unterklassen bei diesem Ansatz auf mehrere Tabellen verteilt, wasZugriffsgeschwindigkeit und Speicher kostet. Beim zweiten Ansatz wird je konkreterUnterklasse eine eigene, vollständige Tabelle verwendet, die alle Ober- undUnterklassen-Attribute zugleich aufnimmt. Insofern wird die Vererbungsrelation derKlassen im Datenbankschema vollständig ignoriert. Ein konkretes Objekt befindet sichstets in einer einzigen Tabelle, was die Zugriffsleistung bei Objekten, solange derenKlasse bekannt ist, im Vergleich zum ersten Ansatz verbessert, jedoch bei polymorphenAbfragen die Untersuchung sämtlicher, infrage kommender Klassen-Tabellen erfordert.Der dritte Ansatz verwendet zur Abbildung der gesamten Klassenhierarchie eine einzigeTabelle und vereint darin unter Ergänzung von Typ-Informationen alle Ober- undUnterklassen mit allen Attributen. Als Resultat kann eine Tabelle mit vielenungenutzten Feldern vorliegen. Ferner ist die Eindeutigkeit der Attributnamen inVerbindung mit Attributtypen über alle Unterklassen zu gewährleisten.Das objektrelationale Mapping kann manuell durch Entwurf und Implementierung derzu speichernden Objektklassen, des Datenbankschemas und der Transfer-Mechanismenerfolgen, jedoch soll dies durch den Einsatz eines <strong>Persistenzframeworks</strong> erleichtert odergar automatisiert werden. Es handelt sich hierbei um die Kernaufgabe eines<strong>Persistenzframeworks</strong> für den Umgang mit relationalen Datenbanken.2.3 Besondere Aspekte für <strong>Persistenzframeworks</strong>Neben den genannten Aspekten des objektrelationalen Mappings, wie mitPrimärschlüsseln, mehrwertigen Attributen oder Vererbungshierarchien umgegangenwird,oder wie flexibel und komfortabel der Einsatz erfolgen kann, sind bei derUntersuchung von <strong>Persistenzframeworks</strong> weitere Aspekte einzubeziehen:Im Falle neuer Anwendungsentwicklungen interessiert die Frage, ob eine Unterstützungdes Entwicklers seitens des Frameworks geboten wird, aus einer vorgegebenenKlassenstruktur entsprechende Tabellenstrukturen (Top-Down-Perspektive des ORM)automatisch in einer ursprünglich leeren Datenbank zu erzeugen. Für bestehendeProjekte stellen sich die Fragen, wie das objektrelationale Mapping für bereitsvorliegende Datenbankschemata eingesetzt werden kann, ob also aus bestehendenStrukturen gemappte Klassen erzeugbar oder zumindest verwendbar sind (Bottom-Up-7


Kapitel 2: <strong>Grundlagen</strong> <strong>Persistenzframeworks</strong>Perspektive), wie mit bestehenden Daten umgegangen wird, wie sich Änderungen anabgebildeten Klassen darauf auswirken und ob bestehende Datenstrukturen, Abfragenund SQL-Funktionen weiterhin nutzbar bleiben. Darüber hinaus ist zu untersuchen,welche Möglichkeiten und Einschränkungen im Umgang mit Datenbank-Transaktionenbereitstehen, die bei verteilten Geschäftsanwendungen von hoher Bedeutung sind, damitdie Konsistenz innerhalb eines relationalen Datenbankmanagementsystemsgewährleistet bleibt, und ob zur Sicherung der referenziellen Integrität Gebrauch vonTriggern und Löschweitergaben gemacht wird. Ferner sind Performance-Aspekte zueruieren, ob ein Connection-Pooling oder zumindest ein zwischenzeitigesAufrechterhalten von Verbindungen zum Datenbanksystem stattfindet, da ein ständigesNeuaufbauen von Verbindungen die Leistung einer Anwendung massiv negativbeeinträchtigen kann. Dazu zählen auch die Aspekte, ob und wie Caching-Mechanismen für schnelle Wertänderungen an gemappten Objekten vorgesehen undumgesetzt sind sowie zu welcher Tiefe und zu welchem Zeitpunkt Objektgraphen beimLaden aufgelöst und vorgehalten werden, das sogenannte Fetching [BHRS07].8


Kapitel 3: LINQ3 LINQ3.1 Überblick und ArchitekturDas im Folgenden untersuchte Persistenzframework, das auch gleichzeitig den Fokusdieser Ausarbeitung setzt, stellt die Datenzugriffsmöglichkeiten dar, die im Rahmen derLanguage Integrated Query-Technologie von Microsoft bereitgestellt werden. LINQsteht dabei für eine Methode zur Vereinfachung und Vereinheitlichung derImplementierung jedweder Art des Zugriffs auf Datenmengen [PR07], also nicht nurzum Zweck der Persistenz, wobei diese Perspektive vordergründig betrachtet werdensoll. Die ursprüngliche Motivation Microsofts zur Entwicklung von LINQ war dieÜberwindung von Problemen beim Datenzugriff auf relationale Datenbanken in denbisherigen .NET-Versionen und die Schaffung einer Lösung zum objektrelationalenMapping [MEW08], die es vor .NET 3.5 von Microsoft nicht als fertiges Produktgegeben hat.Als zentraler Aspekt von LINQ gilt der Ansatz, für beliebige Objekttypen ausbeliebigen Datenquellen eine vereinheitlichte Syntax und ein gemeinsames Konzeptzum Umgang mit Datenmengen und deren Elementen zu liefern. Dies erfolgt durch dieIntegration von Abfragen auf Mengen als natives Konstrukt in den .NET/CLR-Programmiersprachen, unter anderem C# 3.0 und VB.NET 9.0. Dabei wird intensiv vonden Erweiterungen, die jene Sprachen im Vergleich zu ihren Vorgängerversionenerhalten haben, Gebrauch gemacht. Als Resultat der Sprachintegration ist beiAbfrageausdrücken in LINQ die Typsicherheit gewährleistet; ferner erfolgt eineUnterstützung des Entwicklers durch die Verfügbarkeit der Abfrageelemente bei derCodevervollständigung im Entwicklungs- und Debugmodus von Visual Studio 2008sowie die Überprüfung der Ausdrücke zur Kompilierung, nicht erst zur Laufzeit.Darüber hinaus werden beim .NET-Framework 3.5 LINQ-Provider mitgeliefert,einheitliche Komponenten, die Schnittstellen zu spezifischen Datenquellenimplementieren, wobei auch Möglichkeiten zu deren Erweiterung vorgesehen sind. Zunennen sind: LINQ to Objetcs zum Zugriff auf nicht-persistierte Kollektionen,LINQ to SQL für die relationalen Datenbanken Microsoft SQL Server und CompactEdition, LINQ to XML und LINQ to Entities für den Datenzugriff per ADO.NET. Zubeachten ist, dass die Provider eine Top-Down-Perspektive verfolgen, den Zugriff auf9


Kapitel 3: LINQbestehende Strukturen zu ermöglichen, sodass für die Provider zu SQL und Entitieszwar ein umfangreiches objektrelationales Mapping vorgesehen ist, aber keineFunktionalität zur automatisierten Erstellung neuer Datenbankschemata auf Grundlageobjektorientierter Klassen vorliegt. Dies hat im Datenbankmanagementsystem stetsselbst zu erfolgen.ProgrammiersprachenC#Visual BasicLINQ‐BausteineExpression‐Bäume Abfrage‐Ausdrücke Abfrage‐OperationenLINQ‐ProviderLINQ to ObjectsLINQ to SQL LINQ to XML LINQ to EntitiesDatenquellenKlasseAKlasseBint : ZahlKlasseB : myBint : ZahlBbool : fertigfoo()bar()flexnet()indapta()ObjekteSQLServerXMLEntity FrameworkAbbildung 1: Architektur der LINQ-Technologien, nach [MEW08, S. 30].3.2 Spracherweiterungen für LINQDie neuen Sprachkonstrukte C# 3.0 und VB.NET 9.0, von denen LINQ intensivGebrauch macht und dadurch erst ermöglicht wird, werden im Folgenden genauer fürC# betrachtet. Es handelt sich um: Objektinitialisierer, Erweiterungsmethoden, implizittypisierte Variablen, anonyme Typen und Lambda-Ausdrücke.Objektinitialisierer dienen als verkürzte Schreibweise für die gemeinsameInitialisierung eines Objektes und das Setzen dessen öffentlicher Attribute. AnstelleCl o = new Cl(4); o.Attr1 = 1; o.Attr2 = 2;kann man dies durch einen Objektinitialisierer mit einer Anweisung bewirken:Cl o = new Cl(4) { Attr1 = 1, Attr2 = 2 };Der Compiler setzt den Objektinitialisierer in dieselben Kommandos um, wie es nachtraditioneller Schreibweise erfolgt wäre. Erweiterungsmethoden ermöglichen es demEntwickler, bestehende Klassen mit zusätzlichen Methoden auszustatten, ohneVererbung zu verwenden. Insofern ist dies auch für sealed-Klassen oder primitiveTypen möglich.internal static class ExtendInteger{internal static void add(this int a, int b){a += b;}}10


Kapitel 3: LINQDas Beispiel stattet den primitiven Datentyp int mit der Methode add aus, die sich wiefolgt verwenden lässt:int c=20; c.add(22); //c == 42Erweiterungsmethoden, die den erweiterten Typen über ihren ersten Parameterannehmen, was durch das Schlüsselwort this gekennzeichnet wird, haben in einerstatischen Klasse deklariert zu werden und sind selbst ebenfalls static. Implizittypisierte Variablen stehen dem Entwickler nur innerhalb eines Methodenkörpers zurVerfügung.var fourtytwo = 42;Über das Schlüsselwort var wird eine derartige Variable deklariert, wobei dies nur beiunmittelbarer Initialisierung anwendbar ist und der Typ der Variable durch denInitialisierungswert bestimmt wird. Anders als beim variant-Typ in Visual Basic isteine derartige Variable streng typisiert, was auch durch Visual Studio und den Compilergewährleistet und geprüft wird. Anonyme Typen kombinieren implizit typisierteVariablen mit Objektinitialisierern und ermöglichen es, innerhalb einer Methodekomplexe Objektinstanzen zu verwenden, ohne dass dafür eine Klasse zu erstellen ist:var blauesAuto = new {Marke="BMW", hatWinterreifen=true};var grauesAuto = new {Marke="Audi", hatWinterreifen=false};var rotesMoped = new {Marke="Puch"};Für die Objekte blauesAuto und grauesAuto wird implizit durch den Compiler eineKlasse mit den Attributen string Marke und bool hatWinterreifen erstellt undverwendet. Dabei richtet sich der Compiler nach den hier stets implizit typisiertenObjektinitialisierer-Parametern und auch deren Reihenfolge. Für das ObjektrotesMoped wird insofern ein anderer anonymer Typ angelegt und verwendet.Zur Veranschaulichung des Sprachkonstrukts der Lambda-Ausdrücke sei zunächst dasPrinzip der Methodenzeiger, der delegates, gezeigt, die bereits in früherenSprachversionen zum Einsatz gekommen sind:static bool istKlein(int i) { return (i < 42); }void test(){int[] Zahlenliste = { 32, 73, 99, 79, 17, 13, 18 };int[] kleineZahlen = Array.FindAll(Zahlenliste, new Predicate(istKlein));}Die Methode Array.FindAll, die vom Framework bereitgestellt wird, durchläuft alleElemente der Zahlenliste, wendet auf sie istKlein an und nimmt daraufhin diejeweils wahren Ergebnisse in die Liste kleineZahlen auf. Der Delegat-Typ11


Kapitel 3: LINQPredicate, dessen Instanz hier auf die Methode istKlein zeigt, teilt FindAll mit,welche Methode für die Elemente jeweils aufzurufen ist. Durch die Verwendunganonymer Methoden kann die explizite Methode istKlein entfallen und derMethodenkörper direkt in den Aufruf von FindAll aufgenommen werden:…Array.FindAll(Zahlenliste,delegate(int i){return(i { return (i < 42); });oder analog mit einem Expression-Body:…Array.FindAll(Zahlenliste, i => (i < 42));Das links stehende i steht dabei für den Parameter, der in den rechts vom Lambda-Operator => stehenden Ausdruck eingeht. FindAll lässt auch hier jedes int-Elementder Zahlenliste als i in den Lambda-Ausdruck eingehen und erwartet ein bool-Resultat. Dabei ist wiederum vollständige Typsicherheit gegeben. Der Expression-Bodywird vom Compiler intern in die folgende Form umgewandelt, einen sog. Expression-Baum:ParameterExpression i = Expression.Parameter(typeof(int), "i");Expression istKlein =Expression.Lambda(Expression.LessThan(i, Expression.Constant(42, typeof(int))), new ParameterExpression[] { i });In der Expression-Klasse findet man zudem Methoden zu sämtlichen in einemLambda-Ausdruck verwendbaren Operatoren. Der Expression-Baum lässt sich zu einerFunc zusammenführen, die sich wie folgt für FindAll verwenden lässt:Func istKleinC = istKlein.Compile();…Array.FindAll(Zahlenliste, j => istKleinC.Invoke(j));3.3 Objektrelationales Mapping und Abfragen mit LINQ to SQLIm Folgenden wird demonstriert, wie eine Anwendung ausgehend von einerbestehenden Datenbank erstellt wird und unter Einsatz des LINQ to SQL-ProvidersLese- und Schreiboperationen mit gemappten Objekten durchgeführt werden, die aufder zu Grunde liegenden Datenquelle umgesetzt werden. Als Anschauungsobjekt dientdie Datenbank dbo.Fahrtenbuch mit den Tabellen Automobil, Person und Fahrt12


Kapitel 3: LINQinklusive der drei Fremdschlüssel-Relationen Automobil.HalterPerson,Fahrt.FahrerPerson und Fahrt.FahrzeugAutomobil. Die Datenbankliegt auf einem Microsoft SQL Server 2005 vor, wobei der Provider LINQ to SQL zuDatenquellen der Versionen ab 2000 und der SQL Server Compact Edition kompatibelist. Als Entwicklungsumgebung kommt Microsoft Visual Studio 2008 und alsProgrammiersprache C# in Verbindung mit .NET 3.5 zum Einsatz.Abbildung 2: Datenbank-Diagramm zu dbo.Fahrtenbuch.Um das objektrelationale Mapping vorzunehmen, wird in einem neuen Projekt eineneue LINQ to SQL-Klasse erstellt. Unter Zuhilfenahme des grafischen Design-Werkzeugs, wie es in einem praktischen Projekt sicherlich zweckmäßig ist, wird nachFestlegung einer Verbindungszeichenfolge und dem Verbindungsaufbau zurDatenquelle deren Schema geladen und importiert. Dies erfolgt durch Drag&Drop derTabellen aus dem Server Explorer in die entsprechende Design-Ansicht, wobei nebenTabellen auch gespeicherte Prozeduren, Ansichten und Funktionen auswählbar sind.Fremdschlüssel-Relationen zwischen Tabellen werden automatisch übernommen undangezeigt. Die Entwicklungsumgebung generiert im Hintergrund die gemappten,objektorientierten Klassen, stattet sie unter anderem mit allen wesentlichen Attributenaus und annotiert sie. Es sei angemerkt, dass ein erfolgreiches Mapping auch miteinfacheren Mustern und weniger Quelltext erfolgen kann, hier jedoch der ausführliche,automatisch generierte Code aufgrund seiner Vollständigkeit und der Verwendungzweckdienlicher Entwurfsmuster analysiert werden soll.Es werden vier Klassen generiert: Zum einen FahrtenbuchDataContext, dieSystem.Data.Linq.DataContext erweitert und für die drei Tabellen die Klassen13


Kapitel 3: LINQAutomobil, Person und Fahrt. Die Erstgenannte stellt dabei hauptsächlich einenAnsatzpunkt für benutzerdefinierte Erweiterungen der Funktionalität des DataContextdar, der unter anderem die Aufgabe des Verbindungsmanagements übernimmt und alsKernfunktionalität die Durchführung von Abfragen durch die Umsetzung von LINQ-Ausdrücken in spezifische SQL-Kommandos vornimmt. Sämtliche Konstruktoren sindmit dem Aufruf der Erweiterungsmethode OnCreated() ausgestattet. Insofern kannder Entwickler Programmschritte ergänzen, die beim Verbindungsaufbau, demErzeugen des Kontextes, verarbeitet werden sollen. Ferner sind dieErweiterungsmethoden Insert, Update und Delete für die drei Tabellen-Objektebeigefügt, die zu den jeweiligen Ereignissen aufgerufen werden, sowie typisierteEigenschaften zum komfortablen Holen der Tabellen als EntitySet generiertworden. Annotiert ist der FahrtenbuchDataContext lediglich mit demDatabaseAttribute(Name="Fahrtenbuch"), was den Namen der Datenbank aufdem Quellserver widerspiegelt.Die drei den Tabellen zugehörigen Klassen werden vom Designer so angelegt, dass siedie Schnittstellen INotifyPropertyChanging und -Changed implementieren, wasan sich nicht notwendig ist, jedoch wiederum eine komfortable Erweiterung darstellt,um Änderungsereignisse der Felder zu behandeln. Die dafür notwendigenErweiterungsmethoden liegen ebenfalls vor. Von Interesse ist die Wiedergabe dergemappten Attribute, die als öffentliche Eigenschaften mit privaten Feldern erstelltwurden. Jedes einfache, in keinem Fremdbezug stehende Attribut ist mit Columnannotiert. Dieses beschreibt per Storage den Namen des lokalen Feldes, das denDateninhalt aufnimmt, gegebenenfalls per Name die Bezeichnung der Tabellenspalte,den Typ des Feldinhaltes z. B. als DbType="NVarChar(50) NOT NULL" undexplizit, ob es sich um ein Pflichtfeld handelt CanBeNull=false. Felder, die leerbleiben können, werden als System.Nullable typisiert abgebildet.Primärschlüssel werden als solche durch IsPrimaryKey=true gekennzeichnet, wobeidie Generierungsstrategie des Primärschlüssel-Wertes festgelegt wird, hier als Vergabedurch das Datenbanksystem IsDbGenerated=true in Verbindung mit der Angabe,dass dieser Wert nach einem Einfügevorgang aus der Datenbank einzulesen ist,AutoSync=AutoSync.OnInsert. Fremdschlüsselattribute werden wie gewöhnlicheAttribute abgebildet, zusätzlich wird jedoch in die verweisende Klasse ein Attribut vomTyp der verwiesenen Klasse ergänzt, das die Relation zum Ausdruck bringt und14


Kapitel 3: LINQverwiesene Objekte typisiert und transparent erreichbar macht. So steht beispielsweisein der Klasse Automobil neben Nullable Halter auch die entsprechendePerson zur Verfügung. Annotiert wird das Zusatzattribut als Association unterAngabe, dass es Teil einer Fremdschlüssel-Relation ist, IsForeignKey=true, und derdaran beteiligten, hiesigen ThisKey="Halter" und verwiesenen SpaltenOtherKey="PersonID". Ferner stattet der Codegenerator die set-Methode desAssoziations-Attributs mit Operationen zur Aktualisierung aller an der Relationbeteiligten Objekte und Fremdschlüssel-Attributen aus. Auch in der Gegenrichtung wirddie verwiesene Klasse, im Beispiel Person, um eine typisierte KollektionEntitySet mit Association annotiert. Dies verdeutlicht, dass demEntwickler transparente Navigationsmöglichkeiten über beide Richtungen einerFremdschlüsselrelation geschaffen werden.Nachdem das objektrelationale Mapping vorgenommen ist, können Abfragen per LINQauf Grundlage der erstellten Klassen formuliert werden. Um in einer AnwendungZugriff auf die gemappten Datenelemente zu erhalten, ist die Instanziierung einesDataContext, in diesem Fall des FahrtenbuchDataContext, erforderlich, wasunter Angabe einer Verbindungszeichenfolge stattfindet, welche die dafür nötigenInformationen zur Datenquelle enthält. Zur Abfrage sämtlicher Automobile lässt mansich die Table vom Kontext-Objekt übergeben und führt folgendesLINQ-Statement damit aus:IQueryable qAutos = from auto in tAutos select auto;Die Resultat-Kollektion wird als IQueryable angegeben, was eine Erweiterung desIEnumerable darstellt und als Automobil typisiert wird, der gemappten Klasse. EineImplementierung von IQueryable kann dabei über die Methode zur Auswertung desAbfrageausdruckes entscheiden und kann Mengenoperationen wie Auswahl undFilterung damit direkt in der Datenquelle selbst stattfinden lassen. Auf diese Weise wirdim Falle von LINQ to SQL von den leistungsfähigen Abfragemechanismen einerSQL Server-Datenquelle Gebrauch gemacht. Der gezeigte Abfrageausdruck besteht auszwei Teilen: dem from/in-Abschnitt, der die Variable auto für die Elemente derSequenz tAutos vom Typ Table durchläuft, und dem selectAbschnitt, der die Rückgabe der Elemente auto bewirkt. Die LINQ-Syntax istkeinesfalls mit SQL zu verwechseln, obwohl sie bezüglich der verwendeten15


Kapitel 3: LINQSchlüsselwörter sehr ähnlich erscheint. Um bei einem Abfrageausdruck eine Filterunganzuwenden, genügt es einen where-Abschnitt anzufügen:IQueryable qAutos = from auto in tAutoswhere (auto.Typ.StartsWith("BMW")) select auto;Hervorzuheben ist die Verwendung der String-Methode StartsWith direkt auf demgemappten Attribut stringTyp, die einem wie alle weiteren String-Memberfunktionen nativ zur Verfügung stehen. Tiefer in die Tabellenstrukturvordringende Abfragen, die bei SQL die Verwendung von geschachtelten Abfragenoder JOIN-Verbunde erfordern, lassen sich auf ähnliche Weise vornehmen:IQueryable qAutos = from auto in tAutos where(auto.Person.Vorname.Equals("Georg")) select auto;Die missverständliche Bezeichnung Person steht dabei für das durch dieFremdschlüsselbeziehung Halter verwiesene Person-Objekt und wurde vomCodegenerator so gewählt. Im grafischen Designer lässt sie sich jedoch ohne Mühenbeispielsweise in HalterPerson ändern, wobei die Übernahme der Änderung in allerelevanten Codestellen automatisch erfolgt.Das Einfügen von Objekten sei an folgendem Auszug demonstriert:Person georg = (from person in tPersonen whereperson.Vorname.Equals("Georg") select person).First();Automobil georgsAuto = (from auto in tAutos where(auto.Person == georg) select auto).First();Fahrt neueFahrt = new Fahrt { Automobil = georgsAuto, Person =georg, geschaeftlich = false, Zeit = DateTime.Now,Zweck = "Winterreifen" };tFahrten.InsertOnSubmit(neueFahrt);ctx.SubmitChanges();Das neue Fahrt-Objekt wird instanziiert und seine Attribute gesetzt. Das Fahrzeug alsAutomobil und der Fahrer als Person werden hier im Datenbestand gesucht, wobeiIQueryable.First() zum Einsatz kommt, um das erste Element der Kollektion zuerhalten. Die neueFahrt wird zur zeitlich versetzten Speicherung an die TabelletFahrten per InsertOnSubmit() übergeben. Die Übertragung an dasDatenbanksystem erfolgt erst durch den Aufruf von SubmitChanges() desDatenkontextes. Löschen erfolgt über die Tabellenfunktion DeleteOnSubmit().Die Modellierung von Vererbungsstrukturen kann bei LINQ to SQL ebenfalls über diegrafische Design-Ansicht erfolgen. Ausgehend von einer bestehenden Tabellen-Strukturin der Datenbank, die bereits eine Vererbungshierarchie abbildet, lassen sich16


Kapitel 3: LINQentsprechende Klassen modellieren. Dabei kann zur Unterscheidung verschiedenerUnterklassen eine Diskriminator-Spalte mit Unterklassen-individuellen Ausprägungenfestgelegt werden.LINQ to SQL unterstützt zwar die Verwendung von Datenbanktransaktionen, jedochnicht komfortabler als dies bereits in .NET 2.0 der Fall war. Um beispielsweise dasgezeigte Speichern einer Fahrt innerhalb einer Transaktion stattfinden zu lassen, was inAnbetracht der datenbankseitigen Primärschlüsselvergabe ratsam erscheint, muss imFahrtenbuchDataContext über dessen Connection manuell eine Transaktiongestartet und zugewiesen werden:DbTransaction tr =ctx.Connection.BeginTransaction(IsolationLevel.ReadCommitted);ctx.Transaction = tr;try {ctx.SubmitChanges(); tr.Commit();} catch {tr.Rollback();}Eine integrierte Methode der Transaktionsverwaltung innerhalb der Datenkontext-Klasse, beispielsweise durch Setzen eines IsolationLevel für alle schreibendenÜbertragungsvorgänge, wäre förderlich gewesen, um diesbezügliche Fehlerauszuschließen. Der FahrtenbuchDataContext bietet indes genügend Ansatzpunkte,um eine derartige Funktionalität nachzurüsten.Das Laden von gemappten Objekten findet im Normalfall zu dem Zeitpunkt statt, zudem sie tatsächlich von der Anwendung benötigt werden [Ki09]. Dieses Verhalten wirdals Lazy Fetching bezeichnet. Für Situationen, in denen dieses Verhalten nichterwünscht ist, lassen sich dem DataContext sogenannte DataLoadOptionsmitteilen. Das Beispiel zeigt die Anweisung, beim Laden der Automobil-Tabelle diePerson-Tabelle ebenfalls zu übertragen:DataLoadOptions dlo = new DataLoadOptions();dlo.LoadWith(auto => auto.Person);ctx.LoadOptions = dlo;3.4 LINQ to Entities und ADO.NET 3.0Mit Veröffentlichung des Servicepack 1 für das .NET-Framework 3.5 und demServicepack 1 für das Visual Studio 2008 hat Microsoft im August dieses Jahres dieDatenzugriffskomponenten ADO.NET 3.0 und den LINQ to Entities-Providernachgeliefert. Den zentralen Unterschied zu LINQ to SQL stellt die Verwendung desADO.NET Entity Frameworks dar, das die Verwendung eines sogenannten Entity-17


Kapitel 3: LINQModells ermöglicht [MEW08]. Hierbei handelt es sich um ein konzeptionelles Schema,das der Entwickler mithilfe eines grafischen Modellierungswerkzeuges auf Grundlagedes internen Schemas einer Datenquelle erstellen kann. Es ist dabei möglich, eigeneEntities zu modellieren und diese mit einer Zuordnung auf verschiedene Tabellen-Elemente auch unter Einbezug logischer Bedingungen zu belegen. Vergleichbar miteiner Datenbank-Ansicht lassen sich somit Entities erzeugen, die anhand von Relationenauf zusammengeführten Daten arbeiten. Der LINQ to Entities-Provider übernimmt imUmgang mit zusammengeführten Entities deren Auflösung, um beispielsweiseAktualisierungsoperationen auf den zu Grunde gelegten, internen Tabellendurchzuführen [Ki09]. Durch die Trennungsschicht wird eine weitere Abstraktion, nunauch auf Strukturebene der gemappten Klassen, möglich, welche die Entkopplung vomkonkreten, internen Datenbankschema fördert.3.5 LINQ to XMLAnstelle einer relationalen Datenbank kann zum Speichern von Daten auch eine XML-Datei verwendet werden. Durch die Dokumentenorientierung eignet sich dasDateiformat darüber hinaus als Transferdatei zwischen zwei Systemen und spieltbeispielsweise im Bereich der Webservices eine große Rolle. Der LINQ to XML-Provider liefert als zentrale Klasse das XElement.XElement wurzel = new XElement("wurzelknoten",new XElement("unterknoten0", null),new XElement("unterknoten1", null));Unter Verwendung funktionaler Konstruktoren kann der Entwickler auf einfache WeiseXML-Bäume erzeugen, wobei für den Inhalt eine beliebige Zahl weiterer XElementeangegeben werden kann. Darüber hinaus stellt die Klasse die Methoden Load() undSave() bereit und kann somit auch ein vollständiges XML-Dokument repräsentieren[Ra07].Ein Mapping vergleichbar mit dem objektrelationalen bei LINQ to SQL oder der Java-Technologie JAXB von XML-Elementen auf Klassen existiert im Rahmen vonLINQ to XML nicht. Elementbezeichnungen werden konsequent vom Typ Stringgesetzt.18


Kapitel 4: Hibernate und weitere Alternativen4 Hibernate und weitere Alternativen4.1 Hibernate und JPADas Open Source Persistenzframework Hibernate für JDBC-kompatible, relationaleDBMS liegt derzeit in der dritten Generation vor. Seit Version 3.2 ist es kompatibelzum Java Persistence API-Standard (JPA), der seit der Java Platform Enterprise Edition5.0 Schnittstellen zu <strong>Persistenzframeworks</strong> spezifiziert. Hibernate und JPA sind sowohlfür den Einsatz in einem Enterprise Java Beans-Container als auch in der Java PlatformStandard Edition geeignet [MW08]. Die Betrachtungen im Folgenden beziehen sich aufdie charakteristischen, JPA-konformen Funktionalitäten von Hibernate.Die Spezifikation des objektrelationalen Mappings kann bei Hibernate über XML-Dateien oder aussagekräftiger über Annotationen in den gemappten Klassen erfolgen.Eine derartige Klasse ist mit der Annotation @Entity zu versehen und hat einenPrimärschlüssel zu besitzen, der mit @Id markiert ist. Die Strategie für dessenWertzuweisung legt @GeneratedValue(Strategy=GenerationType.IDENTITY)fest. Neben IDENTITY zur Generierung des Wertes über die Datenbank stehen laut JPATABLE, wobei der letzte Wert in einer gesonderten Tabelle abgespeichert wird, undSEQUENCE, das nicht für alle DBMS geeignet ist, zur Verfügung. Hibernate beherrschtzudem weitere Strategien, wie uuid.hex oder guid. Attribute, die als Spalten gemapptwerden sollen, werden mit @Column annotiert. Zu beachten ist, entweder alleAnnotationen inkl. @Id vor die Klassenattribute selbst oder konsequent vor derenGetter-Funktionen zu positionieren. Pflichtfelder erhalten dabei nullable=false undFelder, für die eine Eindeutigkeit gewährleistet werden soll, unique=true. Nebenweiteren @Column-Attributen ist vor allem für das Mappen von String-Feldern legthvon Interesse. Das Mapping primitiver Datentypen in korrespondierende Datenbank-Feldtypen wird implizit vorgenommen. Weitere spaltenbezogene Annotationen sindz. B. @Temporal für Zeitwerte und @Basic(fetch=FetchType.LAZY), das lautJPA ein spätes, bedarfsweises Laden des annotierten Attributes bewirken soll, jedochvon Hibernate nicht umgesetzt wird. Zur Abbildung von 1:1-Assoziationen steht dieAnnotation @OneToOne zur Verfügung, für 1:n-Beziehungen @OneToMany. Letzteressollte zu einem mengenwertigen Attribut stehen, wobei dafür eine Set-Kollektion wieHashSet empfehlenswert ist [He07, S. 191]. Alternativ kann man indizierte Map-19


Kapitel 4: Hibernate und weitere AlternativenKollektionen, wie HashMaps nutzen, deren Schlüsselspalte über die zusätzlicheAnnotation @MapKey(name="…") anzugeben ist. Darüber hinaus ist in der@OneToMany-Annotation per mappedBy die Angabe des Fremdschlüssel-Attributes derZielklasse und dort die Annotation @ManyToOne erforderlich. Erfolgt dies nicht, sowird für das Mapping eine Zwischentabelle verwendet. Ferner wird @JoinColumn zurKennzeichnung von Fremdschlüsseln angeboten. @ManyToMany kennzeichnet analogn:m-Beziehungen, deren beiderseitige Aktualität von einer Seite durchListenoperationen auf der anderen Seite zu gewährleisten ist. Bei den Assoziations-Annotationen kann ferner über fetch=fetchType.LAZY ein bedarfsweises Ladenerwirkt werden, das von Hibernate in diesem Fall umgesetzt wird, sowie die Weitergabevon u. a. Speicher- oder Lösch-Anforderungen durch cascade=CascadeType.ALLaktiviert werden, was jedoch nicht mit Weitergaben oder Triggern auf Datenbank-Ebenegleichzusetzen ist. Das Mapping einer Vererbungshierarchie aus @Entity-Klassenkann über die Klassen-Annotation @InheritanceStrategy(strategy=…) gesteuertwerden. Hier stehen TABLE_PER_CLASS mit geerbten Attributen in allen Untertabellen,JOINED mit vererbten Attributen in der Oberklassen-Tabelle und SINGLE_TABLE alsVorgabe zur Verfügung [He07]. Beim letztgenannten ist zur Unterscheidung derKlassen die Angabe einer @DiscriminatorColumn möglich.In Hibernate und JPA ist die Bereitstellung eines EntityManagers vorgesehen, deru. a. für gemappte Objekte die Methoden persist() zum Speichern, remove() zumLöschen und find() zum Suchen anhand des Primärschlüssels bietet. WeiterführendeAbfragen lassen sich in JPA nur per createNativeQuery() oder createQuery()erzeugen, wobei ersteres als String übergebene Anweisungen an das DBMSweiterleitet und das zweite Anweisungen in der JPA Query Language, einemvereinfachten SQL-Dialekt, erwartet. Die Abfragen sind parametrisierbar. Zusätzlichbietet Hibernate die Klassen Criteria und Example zur strukturierten odertypsicheren Modellierung von Abfragekriterien. Zur Transaktionssteuerung liefert derEntityManager Instanzen der Klasse EntityTransaction, die per begin()einzuleiten und per commit() abzuschließen sind. Ferner ist Connection Pooling überDrittanbieter-Bibliotheken wie C3PO möglich [BHRS07, S. 96]. Auf Grundlage einesvorliegenden Mappings kann durch hb2ddl.SchemaExport mit den Methodencreate() und drop() das vollständige, zugehörige Datenbankschema im DBMSangelegt oder entfernt werden.20


Kapitel 4: Hibernate und weitere Alternativen4.2 NHibernate für .NET und Quarae für JavaZum vorgestellten Hibernate für Java existiert mit NHibernate ein ebenfalls als OpenSource veröffentlichtes Pendant für .NET. Es liegt derzeit mit dem Versionsstand 2 vorund unterstützt offiziell die .NET-Versionen 1.1 und 2.0, lässt sich jedoch auch unter.NET 3.5 einsetzen. NHibernate ist aus einer Portierung von Hibernate 2.1 entstandenund wird seitdem eigenständig weiterentwickelt. Es verwendet zum DatenzugriffADO.NET und kann insofern mit diversen DBMS eingesetzt werden. Auch liegenWerkzeuge zur Generierung von .NET-Klassen aus XML-Mapping-Dateien vor. DieFunktionalität entspricht in weiten Teilen der von Hibernate, mit einer deutlichenEinschränkung: Die Spezifikation des objektrelationalen Mappings kann ausschließlichüber XML-Dateien erfolgen, nicht über Annotationen; obwohl Annotationen in .NETseit 1.0 als sogenannte Attribute möglich sind, wird davon kein Gebrauch gemacht.Zur Verwendung mit LINQ liegt ein entsprechender LINQ to NHibernate-Provider vor,der den Zugriff auf gemappte Klassen über IQueryable ermöglicht. Die wesentlichenLINQ-Kommandos für das Abfragen von Elementen sind dabei implementiert undwerden über Criteria umgesetzt.Bei Quarae handelt es sich um kein Persistenzframework, sondern um ein junges Java-Projekt mit dem Ziel, an LINQ angelehnte Ausdrücke auf Listen, Iterable- oderorg.quaere.Queryable-Datenstrukturen anzuwenden. Aufgrund der mangelndenSprach-Integration weicht die Schreibweise freilich von der LINQs ab:Iterable kleineZahlen =from("n").in(Zahlenliste).where(lt("n", 42)).select("n");Der dargestellte Ausdruck bildet eine Baumstruktur, die per Visitor-Entwurfsmusterausgewertet wird. Darüber hinaus kann Quarae über die genannte Queryable-Struktur,die den JPA-EntityManager einbezieht, Anfragen an ein Persistenzframeworkrichten.21


Kapitel 5: Fazit und Ausblick5 Fazit und AusblickDen Problemfeldern, die beim Mapping objektorientierter Klassenstrukturen mitrelationalen Datenbank-Schemata durch die Verschiedenheit der zu Grunde liegendenParadigmen als objektrelationaler Impedanz-Unterschied auftauchen, können vielfältigeLösungsansätze entgegengebracht werden. So lässt sich die Abbildung der Strukturobjektorientierter Klassen durch den Einsatz einer oder mehrerer relationaler Tabellenrealisieren, wobei auch komplexe und mengenwertige Attribute sowie Vererbungs-Hierarchien abgebildet werden können. Insofern ist die Modellierung relationalerSchemata zur adäquaten Aufnahme der Daten und Beziehungen komplexer Objekt-Graphen unter Zuhilfenahme spezifischer Mechanismen vollständig möglich.Die im Rahmen der LINQ-Technologie vorliegenden Methoden liefern dem Entwicklerumfassende und flexible Ansätze bei der Erstellung von Persistenzlösungen in .NET.Insbesondere der LINQ to SQL-Provider sorgt durch effektives objektrelationalesMapping für eine umfangreiche Unterstützung der Verwendung relationaler Daten alsObjektstrukturen. Die dabei verwendeten Muster gewährleisten die konsistenteKapselung und effiziente Abfrage von Objekten aus der Datenbank. Mit den LINQ-Ausdrücken wird zudem eine prägnante und typsichere Methode für Operationen auftransienten oder persistenten Kollektionen bereitgestellt. Integrierte Ansätze zurautomatisierten Erstellung eines Datenbank-Schemas aus gemappten Objekten sowieVerbesserungen im Umgang mit Transaktionen fehlen bislang. Das ebenfallsvorgestellte Persistenz-Framework Hibernate für Java, das sich ausschließlich für dieVerwendung mit relationalen, JDBC-kompatiblen Datenquellen eignet, bietet einenvergleichbaren Grad der Problem-Abdeckung. Es ist kompatibel zum allgemeinen JavaPersistence API und liefert darüber hinaus ein breites Repertoire ausgereifter Lösungenzum objektrelationalen Mapping. Als Pendant für .NET und ADO.NET-kompatibleDatenquellen ist NHibernate als gleichwertig zu betrachten, mit der Einschränkung,dass die Spezifikation des Mappings allein über XML-Dateien erfolgen kann.Aufgrund der Sprachintegration von Abfragen durch LINQ erscheint diese Lösung inkomplexen Szenarien gegenüber den traditionellen Methoden in Java und Hibernateüberlegen. Es bleibt abzuwarten, wann derartige Mechanismen, die überDemonstrationen wie Quarae hinaus gehen, in Java zur Verfügung stehen.22


Anhang A: Quellcode zu den Klassen im LINQ to SQL-BeispielA Quellcode zu den Abfragen im LINQ to SQL-BeispielÖffnen eines DataContext, Laden der Tabellen, Abfragen der Autos:FahrtenbuchDataContext ctx = newFahrtenbuchDataContext(Properties.Settings.Default.ConnectionString);Table tAutos = ctx.Automobils;Table tPersonen = ctx.Persons;Table tFahrten = ctx.Fahrts;IQueryable qAutos = from auto in tAutos select auto;Es wird folgender SQL-Ausdruck generiert:SELECT [t0].[Kennzeichen], [t0].[Typ], [t0].[Erstzulassung],[t0].[Halter] FROM [dbo].[Automobil] AS [t0]Abfrage aller Automobile, deren Typ mit "BMW" beginnt:IQueryable qAutos = from auto in tAutos where(auto.Typ.StartsWith("BMW")) select auto;Es wird folgender SQL-Ausdruck generiert:SELECT [t0].[Kennzeichen], [t0].[Typ], [t0].[Erstzulassung],[t0].[Halter] FROM [dbo].[Automobil] AS [t0]WHERE [t0].[Typ] LIKE BMW%Abfrage aller Automobile, deren Halter den Vornamen "Georg" trägt:IQueryable qAutos = from auto in tAutos where(auto.Person.Vorname.Equals("Georg")) select auto;Es wird folgender SQL-Ausdruck mit JOIN generiert:SELECT [t0].[Kennzeichen], [t0].[Typ], [t0].[Erstzulassung],[t0].[Halter] FROM [dbo].[Automobil] AS [t0] LEFT OUTER JOIN[dbo].[Person] AS [t1] ON [t1].[PersonID] = [t0].[Halter]WHERE [t1].[Vorname] = 'Georg'Abfrage einer Person mit dem Vornamen "Georg", dessen Automobils, Erstellung einerneuen Fahrt, Speichern der neuen Fahrt:Person georg = (from person in tPersonen whereperson.Vorname.Equals("Georg") select person).First();Automobil georgsAuto = (from auto in tAutos where(auto.Person == georg select auto).First();Fahrt neueFahrt = new Fahrt { Automobil = georgsAuto, Person = georg,geschaeftlich = false, Zeit = DateTime.Now, Zweck = "Winterreifen" };tFahrten.InsertOnSubmit(neueFahrt);ctx.SubmitChanges();23


Anhang A: Quellcode zu den Klassen im LINQ to SQL-BeispielAuf dem SQL-Server wird folgender Befehl beim Übermitteln der Änderungausgeführt:INSERT INTO [dbo].[Fahrt]([Zeit], [Fahrzeug], [Fahrer],[Entfernung], [geschaeftlich], [Zweck], [exportiert])VALUES (@p0, @p1, @p2, @p3, @p4, @p5, @p6)SELECT CONVERT(Int,SCOPE_IDENTITY()) AS [value]Mit den Parameter-Typen:@p0 datetime,@p1 nchar(12),@p2 int,@p3 float,@p4 bit,@p5 text,@p6 bitUnd den Parameter-Werten:@p0='2008-12-08 18:40:03:783',@p1=N'WAF IT 246 ',@p2=3,@p3=0,@p4=0,@p5='Winterreifen',@p6=024


Literaturverzeichnis[Am03] Scott Ambler: Agile Database Techniques, John Wiley & Sons, 2003.[BaHei99] Heide Balzert: Lehrbuch der Objektmodellierung, Spektrum AkademischerVerlag, 1999.[BaHel99] Helmut Balzert: Lehrbuch <strong>Grundlagen</strong> der Informatik, SpektrumAkademischer Verlag, 1999.[BHRS07] Robert F. Beeger, Arno Haase, Stefan Roock, Sebastian Sanitz: Hibernate –Persistenz in Java-Systemen mit Hibernate und der Java Persistence API, 2.Aufl., dpunkt.verlag, 2007.[BBE95][Co70][DH03][He07]Andi Birrer, Walter R. Bischofberger, Thomas Eggenschwiler:Wiederverwendung durch Frameworktechnik – vom Mythos zur Realität,in: Objektspektrum (1995) 5, S. 18-26.Edgar Frank Codd: A Relational Model of Data for Large Shared DataBanks, in: Communications of the ACM, 13 (1970) 6, S. 377–387.Jürgen Dunkel, Andreas Holitschke: Softwarearchitektur für die Praxis,Springer, 2003.Sebastian Hennebrüder: Hibernate – Das Praxisbuch für Entwickler, GalileoPress, 2007.[Ke97] Wolfgang Keller: Mapping Objects to Tables – A Pattern Language. 1997.[Ke83]William Kent: A Simple Guide to Five Normal Forms in RelationalDatabase Theory, in: Communications of the ACM, 26 (1983) 2, S. 120–125.[Ki09] Paul Kimmel: LINQ Unleashed for C#, Pearson Education, 2009.[Le00]Neal Leavitt: Whatever Happened to Object-Oriented Databases?, in:Computer, 33 (2000) 8, S. 16–19.[MEW08] Fabrice Marguerie, Steve Eichert, Jim Wooley: LINQ im Einsatz, CarlHanser Verlag, 2008.[Me08] Vijay P. Mehta: Pro LINQ Object Relational Mapping with C# 2008,Apress, 2008.[MW08][PR07][Ra07]Bernd Müller, Harald Wehr: Java-Persistence-API mit Hibernate,Standardisierte Persistenz, Addison-Wesley, 2008.Paolo Pialorsi, Marco Russo: Introducing Microsoft LINQ, Microsoft Press,2007.Joseph C. Rattz: Pro LINQ: Language Integrated Query in C# 2008, Apress,2007.


[SGM02] Clemens Szyperski, Dominik Gruntz, Stephan Murer: Component Software– Beyond Object-oriented Programming, Pearson Education, 2002.

Hurra! Ihre Datei wurde hochgeladen und ist bereit für die Veröffentlichung.

Erfolgreich gespeichert!

Leider ist etwas schief gelaufen!