3-939084-03-4_db4o_v01.book Seite 1 Mittwoch, 4. Oktober 2006 2:32 14
db4o
3-939084-03-4_db4o_v01.book Seite 2 Mittwoch, 4. Oktober 2006 2:32 14
3-939084-03-4_db4o_v01.book Seite 3 Mittwoch, 4. Oktober 2006 2:32 14
Larysa Visengeriyeva, Patrick Römer
db4o
schnell + kompakt
3-939084-03-4_db4o_v01.book Seite 4 Mittwoch, 4. Oktober 2006 2:32 14
Larysa Visengeriyeva, Patrick Römer db4o schnell + kompakt ISBN-10: 3-939084-03-4 ISBN-13: 978-3-939084-03-7
© 2007 entwickler.press, ein Imprint der Software & Support Verlag GmbH
http://www.entwickler-press.de/ http://www.software-support.biz/ Ihr Kontakt zum Verlag und Lektorat:
[email protected] Bibliografische Information Der Deutschen Bibliothek Die Deutsche Bibliothek verzeichnet diese Publikation in der Deutschen Nationalbibliografie; detaillierte bibliografische Daten sind im Internet über http://dnb.ddb.de abrufbar.
Korrektorat: Petra Kienle Satz: text & form GbR, Carsten Kienle Umschlaggestaltung: Melanie Hahn Belichtung, Druck und Bindung: M.P. Media-Print Informationstechnologie GmbH, Paderborn. Alle Rechte, auch für Übersetzungen, sind vorbehalten. Reproduktion jeglicher Art (Fotokopie, Nachdruck, Mikrofilm, Erfassung auf elektronischen Datenträgern oder andere Verfahren) nur mit schriftlicher Genehmigung des Verlags. Jegliche Haftung für die Richtigkeit des gesamten Werks kann, trotz sorgfältiger Prüfung durch Autor und Verlag, nicht übernommen werden. Die im Buch genannten Produkte, Warenzeichen und Firmennamen sind in der Regel durch deren Inhaber geschützt.
3-939084-03-4_db4o_v01.book Seite 5 Mittwoch, 4. Oktober 2006 2:32 14
Inhaltsverzeichnis Vorwort
7
Kapitel 1: Einführung
9
Kapitel 2: Ein-Schritt-Installation
13
Kapitel 3: db4o im SOLO-Modus
17
Kapitel 4: Abfragekonzepte unter db4o 4.1 Query By Example 4.2 S.O.D.A. 4.3 Native Queries 4.4 Auswahl des Abfragekonzepts
25 26 30 40 46
Kapitel 5: Tiefe Objektgraphen
49
Kapitel 6: Ändern und Löschen von Objekten
55
Kapitel 7: Objektidentität
61
Kapitel 8: Transaktionen
69
Kapitel 9: db4o im Client-Server-Modus
75
Kapitel 10: Objektorientierte Modellierung 10.1 Abhängigkeiten zu db4o minimieren 10.2 Ausnahmen 10.3 Queries und Geheimnisprinzip
81 81 83 84
Kapitel 11: Konfiguration 11.1 Container 11.2 Objekt 11.3 Client/Server
85 86 92 94
schnell + kompakt
5
3-939084-03-4_db4o_v01.book Seite 6 Mittwoch, 4. Oktober 2006 2:32 14
Inhaltsverzeichnis
Kapitel 12: db4o-Replikation
95
Kapitel 13: db4o-Tools
101
Kapitel 14: Object Manager
105
Kapitel 15: db4o API
109
Kapitel 16: Weiterführende Informationen
111
Literaturverzeichnis
113
Stichwortverzeichnis
115
6
3-939084-03-4_db4o_v01.book Seite 7 Mittwoch, 4. Oktober 2006 2:32 14
Vorwort Von Carl Rosenberger, Chief Software Architect db4objects Inc.
Mit db4o hat sich für mich ein Traum verwirklicht. Ich hatte das große Glück, ein großes Software-Projekt aus einer Idee heraus beginnen zu können, es wachsen und gedeihen zu sehen und über Jahre hinweg zu seinem Erfolg beitragen zu können. Ich hatte das große Glück, mehr zufällig als gezielt einen Bereich in der Softwareentwicklung zu finden, der aus einem mir unbegreiflichen Grund von anderen Produkten und Firmen im Vergleich zu seiner Wichtigkeit nur ungenügend abgedeckt war. Wer objektorientiert arbeitet, möchte doch einfach nur Objekte abspeichern können, oder? Genau das war im Jahr 2000, als die erste Zeile für db4o geschrieben wurde, noch nicht mit einfachen und erschwinglichen Mitteln möglich. Ich hatte das Glück, zur richtigen Zeit mit dem richtigen Projekt anzufangen. Später kamen die richtigen Menschen zu diesem Traum hinzu: ein Geschäftsmann, der das Finanzielle in die richtigen Bahnen gelenkt hat und ein Team aus sehr fähigen Entwicklern, die vieles viel besser können als ich und von denen ich ständig neu dazu lerne. Diese Menschen sind heute Bestandteil des Traums. Larysa Visengeriyeva und Patrick Römer, die Autoren dieses Buches, gehören dazu. Die ersten Zeilen für db4o wurden in Deutschland geschrieben, in einem Schwimmbad (keiner Garage), südlich von München auf dem Land. Viele der Ideen für db4o stammen aus dem deutschen Sprachraum, aus der Deutschen Java Newsgroup de.comp.lang.java. Da ist es nur konsequent, dass dieses zweite
schnell + kompakt
7
3-939084-03-4_db4o_v01.book Seite 8 Mittwoch, 4. Oktober 2006 2:32 14
Vorwort
Buch über db4o auf Deutsch erscheint. Ich freue mich sehr über dieses Buch und möchte mich bei den Autoren ganz herzlich bedanken. Mit db4o wird vor allem ein Ziel verfolgt: Entwicklung soll so einfach und produktiv wie möglich sein. Das Leben ist zu kurz, um die Hälfte der Entwicklungszeit für objektrelationales Mapping zu verschwenden, wenn es auch einfacher geht. Ich wünsche mir, dass dieses Buch dazu beiträgt, neue Anwender für db4o zu begeistern. Carl Rosenberger
8
3-939084-03-4_db4o_v01.book Seite 9 Mittwoch, 4. Oktober 2006 2:32 14
KAPITEL 1 Einführung
db4o – eine objektorientierte Datenbank db4o ist eine objektorientierte Datenbank, die auf den Plattformen Java und Microsoft .NET zur Verfügung steht und bei db4objects Inc. entwickelt wird. db4o bietet eine generische Persistenzschicht für die Applikationsentwicklung mit Java oder .NET auf PDAs, mobilen Geräten, Desktop- oder Serverrechnern. Objektorientierte Datenbanken orientieren sich am Datenmodell der Applikationssprache; der berüchtigte „objektrelationale Mismatch“ entfällt. db4o zeichnet sich aus durch schnelle Persistenz von komplexen Objekten, einfache Installation und Einbettung in die OO-Anwendung, leichte und effiziente Abfragesprachen, minimalen Administrationsaufwand und vielfältige Möglichkeiten, die eigene Datenbank durch Konfigurationseinstellungen an den spezifischen Bedarf anzupassen. Der Einsatz von db4o lässt sich durch die einfache API in kürzester Zeit erlernen und produktiv anwenden. Die zu persistierenden Objekte müssen hierbei keinerlei Voraussetzungen erfüllen – die Applikationslogik bleibt frei von Referenzen auf die Persistenzschicht. Die Objekte werden üblicherweise in einer
schnell + kompakt
9
3-939084-03-4_db4o_v01.book Seite 10 Mittwoch, 4. Oktober 2006 2:32 14
1 – Einführung
Datei – Default-Endung .yap – gespeichert, der Benutzer kann beliebig viele Dateien parallel geöffnet halten. Eine einzelne Datei kann hierbei maximal 2 GB adressieren. Für die Abfrage der gespeicherten Objekte bietet db4o drei Konzepte: 쐌 Native Queries (NQ) 쐌 S.O.D.A. 쐌 QueryByExample (QbE) QbE ist wohl die intuitivste Variante, die sich insbesondere für Einsteiger und relativ einfache Anfragen eignet. Hierzu erstellt man ein Prototyp-Objekt und db4o liefert alle Objekte aus der Datenbank mit identischen Feldwerten zurück. Defaultwerte (null für Referenztypen und 0 für primitive Typen) fungieren als Blankowerte und werden für die Anfrage ignoriert. In S.O.D.A. artikuliert man die Anfrage nicht anhand „lebendiger“ Objekte, sondern man erstellt einen Constraintgraph, der die relevanten Teile des gewünschten Objektgraphen spezifiziert. S.O.D.A. ist deutlich mächtiger und flexibler als QbE: Seiteneffekte durch die Erzeugung von QbE-Objekten können nicht auftreten, Defaultwerte nehmen keine Sonderstellung ein, die Nutzung von Indizes kann die Suche erheblich beschleunigen. Außerdem bietet die API über QbE hinausgehende Funktionalität, wie etwa Vergleichsoperatoren, boolesche Verknüpfungen etc. Die Hauptanfragesprache für db4o sind Native Queries (NQ). Die Charakteristika von NQ: 쐌 쐌 쐌 쐌 쐌
Objektorientiert Typsicher Refakturierbar Produktiv Hohe Performance
10
3-939084-03-4_db4o_v01.book Seite 11 Mittwoch, 4. Oktober 2006 2:32 14
Einführung
NQ basieren vollständig auf der Programmiersprache, in der die Anwendung geschrieben ist. Dadurch sind Anfragen, anders als in gängigen stringbasierten Sprachen, der Typprüfung durch den Compiler oder die Refactoring-Features der IDE zugänglich. Zur Erzielung optimaler Performance analysiert db4o NQs vor der Ausführung und versucht, diese in äquivalente S.O.D.A.Ausdrücke umzuwandeln. Dieses Konzept ist unseres Wissens derzeit einzigartig. Die Anfrage nach „allen Personen, die jünger als 33 Jahre sind“ lässt sich mit Native Queries beispielsweise wie folgt ausführen: List
persons = db.query( new Predicate() { public boolean match(Person person) { return person.getAge()<=33; } } );
Die Abfrage wird als Subklasse von com.db4o.Predicate implementiert, in der die Methode match(TargetType t) passend überschrieben wird. NQs erweisen sich als 100% typsicher und refaktorierbar. Anhand des Open Source Benchmark „PolePos“ lassen sich die Vorteile von db4o im direkten Vergleich zu reinen JDBC/SQLLösungen oder O/R-Mappern wie Hibernate nachvollziehen: Exzellente Performance und Zero-Administration sind Hauptkriterien insbesondere für den Markt der embedded Persistenzlösungen und mobilen Geräte. Der Client-Server-Modus macht db4o auch für den Bereich J2EE und Web-Applikationen attraktiv. Clients können sowohl embedded in derselben Virtual Machine (VM) wie der Server als
schnell + kompakt
11
3-939084-03-4_db4o_v01.book Seite 12 Mittwoch, 4. Oktober 2006 2:32 14
1 – Einführung
auch remote über Netzwerk verbunden laufen. db4o bietet ACID-Transaktionen sowie einen Replikationsmechanismus, der konsistente Datenhaltung sowohl zwischen mehreren db4o-Instanzen wie auch zwischen db4o und einer relationalen ERMDatenbank erlaubt. Dieses Feature ist besonders interessant für „partially connected“ Clients, wie etwa Laptops/PDAs von Mitarbeitern im Außendienst. Hauptanwendungsgebiet für db4o ist die Einbettung in Softwarekomponenten, unsichtbar für den Endbenutzer. Die Einbindung ist denkbar leicht; es reicht, eine einzige jar/dll-Datei hinzuzufügen: Keinerlei Installation, Konfiguration oder Wartung ist vonnöten. db4o wird als .jar/.dll-File zum Download angeboten und ist etwa 500 KB groß. Die Distribution enthält außerdem Utilities, die Testsuite, API-Dokumentation sowie ein interaktives Tutorial. Weitere Komponenten wie ObjectManager (ein db4o Browser) oder dRS (der Replikationsservice) können separat heruntergeladen werden. db4o – eine Open-Source-Datenbank Ein weiterer wichtiger Aspekt ist das Lizenzmodell: db4o wird unter einer dualen Lizenz angeboten und kann entweder unter den Bedingungen der GPL oder aber unter den erweiterten Möglichkeiten einer kommerziellen Lizenz verwendet werden. Beide Varianten unterscheiden sich nur in lizenzrechtlicher Hinsicht, das Produkt ist identisch.
12
3-939084-03-4_db4o_v01.book Seite 13 Mittwoch, 4. Oktober 2006 2:32 14
KAPITEL 2 Ein-Schritt-Installation
Db4o ist unter www.db4o.com zum freien Download verfügbar. Die Java-Version wird in einem einzigen .zip-File ausgeliefert. db4o-Verzeichnisbaum Nach dem Downloaden und Entpacken der db4o-*-*-java.zip-Datei erhält man folgenden Verzeichnisbaum: \db4o-5.2-java\db4o-5.2\doc
Im doc-Ordner findet man die gesamte Dokumentation sowie die Java-API-Docs. Dazu gehört auch ein ausführliches, interaktives Tutorial (auf Englisch). \db4o-5.2-java\db4o-5.2\lib
In diesem Verzeichnis befinden sich die eigentlichen Jar-Bibliotheken, die den db4o-Datenbankkern für unterschiedliche JavaVersionen von JDK1.1 bin JDK5/1.6 beinhalten.
schnell + kompakt
13
3-939084-03-4_db4o_v01.book Seite 14 Mittwoch, 4. Oktober 2006 2:32 14
2 – Ein-Schritt-Installation
\db4o-5.2-java\db4o-5.2\src
Als Open-Source-Datenbank bringt db4o selbstverständlich den vollständigen Java-Quellcode im Lieferumfang mit. Die Quellcodedateien, Ant-Buildscripte inklusive, befinden sich im Unterordner src. Installation db4o ist sofort installiert, nachdem der Pfad zur .jar-Bibliothek dem Klassenpfad des Java-Projekts hinzugefügt wurde. Die Installation erfolgt in nur wenigen Schritten: 1. Download der letzten Version von db4o von www.db4o.com 2. Entpacken Sie db4o-*-*-java.zip. 3. Fügen Sie db4o-*-*.jar aus dem Ordner src dem Klassenpfad hinzu.
Abb. 2.1: db4o in der Java-Anwendung
Der dritte Schritt in der IDE Eclipse: 1. Wählen Sie aus dem Ordner src die der verwendeten JavaVersion entsprechende .jar-Datei und kopieren Sie sie in den eigenen Projektordner (Refresh des Projekts nicht vergessen, falls dies außerhalb von Eclipse geschieht).
14
3-939084-03-4_db4o_v01.book Seite 15 Mittwoch, 4. Oktober 2006 2:32 14
Ein-Schritt-Installation
2. Wählen Sie im Menü PROJECT | PROPERTIES | JAVA BUILD PATH | ADD JAR... die gerade importierte .jar-Datei aus und bestätigen Sie mit OK.
schnell + kompakt
15
3-939084-03-4_db4o_v01.book Seite 16 Mittwoch, 4. Oktober 2006 2:32 14
3-939084-03-4_db4o_v01.book Seite 17 Mittwoch, 4. Oktober 2006 2:32 14
KAPITEL 3 db4o im SOLO-Modus
db4o in der Anwendung In diesem Kapitel wollen wir anhand eines simplen Beispiels die grundlegenden Prinzipien der Arbeit mit db4o vermitteln, bevor wir in den folgenden Kapiteln detailliert auf einzelne Aspekte eingehen. Die Anwendungsobjekte werden mittels einfacher API-Aufrufe in einer db4o-Datenbank gespeichert (Abbildung 3.1) und können von dort jederzeit abgefragt werden. Treiberinstallation, Erstellung von Mappingfiles etc. sind nicht notwendig, lediglich db4o.jar muss im Klassenpfad vorhanden sein. Grundsätzlich unterscheidet sich die Arbeitsweise mit db4o nicht von der Arbeitsweise mit anderen Datenbanken. Man hält sich an die übliche Reihenfolge: 쐌 Datenbank erzeugen bzw. öffnen 쐌 Objekte speichern, abfragen, verändern, modifizieren oder löschen 쐌 Datenbank schließen
schnell + kompakt
17
3-939084-03-4_db4o_v01.book Seite 18 Mittwoch, 4. Oktober 2006 2:32 14
3 – db4o im SOLO-Modus
Abb. 3.1: db4o-Modell
Dieses grundlegende Muster ist im folgenden Listing dargestellt: ObjectContainer db=Db4o.openFile("db.yap"); try { // Objekte speichern, abfragen, verändern // oder löschen } finally { db.close(); }
Beispielanwendung Die Arbeitsweise mit db4o wird anhand eines durchgehenden Beispiels gezeigt, dabei wird ein vereinfachtes Modell für das Registrierungssystem an einer Universität dargestellt. Es sind die
18
3-939084-03-4_db4o_v01.book Seite 19 Mittwoch, 4. Oktober 2006 2:32 14
db4o im SOLO-Modus
Informationen über Dozenten, Studierende und angebotene Kurse zu erfassen. Ein Kurs (die Klasse Course) wird mit seinem Titel und der Liste der Anforderungen beschrieben. public class Course implements Comparable{ private String courseTitle; private Set prerequisites; … }
Ein Dozent (die Klasse Lecturer) wird als eine Person mit Namen und Vornamen dargestellt. Zur Identifizierung dient seine Personalnummer. Ein Dozent bietet meistens mehrere Kurse an. public class Person { private String firstName; private String lastName; … } public class Lecturer extends Person { private int personnelNumber; private List instructs; … }
Ein Student (die Klasse Student) ist ebenfall eine Person und wird durch seine Matrikelnummer beschrieben. Ein Student hat einen Stundenplan (die Klasse Schedule) mit maximal sechs Kursen.
schnell + kompakt
19
3-939084-03-4_db4o_v01.book Seite 20 Mittwoch, 4. Oktober 2006 2:32 14
3 – db4o im SOLO-Modus
public class Student extends Person { private int matriculationNumber; private Schedule schedule; … } public class Schedule { private static final int MAXCOURSES = 6; List schedule; … }
In der Abbildung 3.2 wird das oben beschriebene Registrierungssystem als ein vereinfachtes UML-Diagramm dargestellt.
Abb. 3.2: UML-Darstellung des Beispiels
20
3-939084-03-4_db4o_v01.book Seite 21 Mittwoch, 4. Oktober 2006 2:32 14
db4o im SOLO-Modus
Datenbank erzeugen bzw. öffnen Um eine db4o-Datenbank zu erzeugen oder eine bereits existierende zu öffnen, genügt eine einzelne Codezeile: ObjectContainer db=Db4o.openFile("../db.yap");
Mit dem Aufruf Db4o.openFile("../db.yap") erhält man eine Instanz db vom Typ ObjectContainer. Dieses Interface repräsentiert die grundlegende Schnittstelle zur Datenbank, die als Datei „db.yap“ auf dem physischen Speichermedium angelegt wurde. Das File „db4o.jar“ muss im Klassenpfad vorhanden sein, damit die benötigten Klassendefinitionen importiert werden können: import com.db4o.Db4o; import com.db4o.ObjectContainer;
Objekte speichern Grundsätzlich lassen sich nahezu beliebige Objekte in db4o speichern. Es ist nicht erforderlich, dass zu persistierende Objekte ein bestimmtes Interface implementieren oder von einer bestimmten Klasse abgeleitet sind. Auch ein Defaultkonstruktor oder Getter/ Setter für persistente Felder wird nicht vorausgesetzt. Mit dem einfachen Aufruf der Methode set() können beliebige Objekte in der Datenbank gespeichert werden. Im folgenden Beispiel erzeugen wir zwei Objekte vom Typ Person und legen sie in unserer Datenbank db ab: // Objekte erzeugen Course math1 = new Course("mathematics1"); Course ethic = new Course("ethic");
schnell + kompakt
21
3-939084-03-4_db4o_v01.book Seite 22 Mittwoch, 4. Oktober 2006 2:32 14
3 – db4o im SOLO-Modus
Student student1 = new Student(8306, "Bogdan", "Valint"); Schedule schedule1 = new Schedule(); schedule1.addCourseToSchedule(math1); schedule1.addCourseToSchedule(ethic); student1.setSchedule(schedule1); Lecturer lecturer1 = new Lecturer(15572, "Taylor", "Brada"); lecturer1.addCourse(math1); ObjectContainer db=Db4o.openFile("db.yap"); try { //Objekte in der Datenbank speichern db.set(lecturer1); db.set(student1); } finally { db.close(); }
Man beachte, dass nur die Objekte vom Typ Student und Lecturer explizit in der Datenbank abgelegt wurden. Diese besitzen aber Referenzen auf Objekte vom Typ Course und Schedule, die implizit mitgespeichert werden – es wird also der gesamte Objektgraph persistiert (Abbildung 3.3). Insbesondere respektiert db4o das Konzept der Objektidentität: In diesem Beispiel referenzieren Lecturer und Schedule in der Datenbank wie in-memory tatsächlich dasselbe Course-Objekt und nicht lediglich unterschiedliche Kopien mit gleichen Attributwerten.
22
3-939084-03-4_db4o_v01.book Seite 23 Mittwoch, 4. Oktober 2006 2:32 14
db4o im SOLO-Modus
Abb. 3.3: Persistenter Objektgraph
Nachdem wir eine Datenbank angelegt und einige Objekte gespeichert haben, können wir diese abfragen. Wir benutzen hier eine einfache Abfrage, die uns sämtliche Objekte des Typs Student liefert. ObjectContainer db=Db4o.openFile("db.yap"); try { // Objekte abfragen ObjectSet<Student> result = db.get(Student.class); System.out.println(result); } finally { db.close(); }
Das Ergebnis einer db4o-Query ist stets ein Exemplar des Typs ObjectSet, neben ObjectContainer das wichtigste Interface der db4o-API. ObjectSet implementiert das Interface java.util.List (ebensowie das Interface java.util.Iterator), fügt sich also nahtlos in
schnell + kompakt
23
3-939084-03-4_db4o_v01.book Seite 24 Mittwoch, 4. Oktober 2006 2:32 14
3 – db4o im SOLO-Modus
die gewohnte Verwendung der Collection-API ein. In diesem Beispiel nutzen wir nur die toString()-Darstellung von java.util.List. [console] [Bogdan Valint < matriculation number: 8306>]
Datenbank schließen Die Datenbank sollte stets wieder sauber geschlossen werden. Dafür sorgt die Anweisung: db.close();
Schließt man die Datenbank nicht explizit, wird bei Beendigung der VM automatisch versucht, sie zu schließen. Das „Versäumnis“ wird von db4o allerdings mit einer Warnung quittiert: [db4o 5.2.002 2006-03-08 17:11:29] `C:/db.yap` closed by ShutdownHook
In diesem Kapitel haben wir die Grundlagen der Arbeit mit db4o aus der „Vogelperspektive“ behandelt. Im nächsten Kapitel werden wir uns zunächst genauer mit den verschiedenen Möglichkeiten beschäftigen, Abfragen in db4o zu formulieren.
24
3-939084-03-4_db4o_v01.book Seite 25 Mittwoch, 4. Oktober 2006 2:32 14
KAPITEL 4 Abfragekonzepte unter db4o 4.1
Query By Example
26
4.2
S.O.D.A.
30
4.3
Native Queries
40
4.4
Auswahl des Abfragekonzepts
46
Die Abfragesprache ist ein essenzieller Bestandteil von Datenbanksystemen. Dieses Kapitel soll einen grundsätzlichen Überblick über die unterschiedlichen von db4o unterstützten Konzepte vermitteln, die sich naturgemäß von den „gewohnten“ relationalen Abfragesprachen unterscheiden. db4o bietet drei unterschiedliche Abfrageschnittstellen: 쐌 Query By Example (QbE) 쐌 Simple Object Database Access (S.O.D.A.) 쐌 Native Queries (NQ)
Abb. 4.1: Abfragesprachen in db4o
schnell + kompakt
25
3-939084-03-4_db4o_v01.book Seite 26 Mittwoch, 4. Oktober 2006 2:32 14
4 – Abfragekonzepte unter db4o
Abbildung 4.1 zeigt die konzeptuellen Verbindungen zwischen den Ansätzen. 1. QbE ist die einfachste (und ausdrucksschwächste) Variante: Eine Anfrage besteht aus einem Beispielobjekt; zurückgeliefert werden Objekte, die dieser Spezifikation „ähneln“. 2. S.O.D.A. ist deutlich mächtiger und erlaubt komplexere Anfragen mittels logischer Operatoren, Schachtelung etc. QbEAbfragen können in S.O.D.A.-Abfragen integriert werden. 3. Evaluations können ebenfalls in S.O.D.A.-Abfragen eingebunden werden und bieten die Möglichkeit, beliebigen, benutzerdefinierten Java-Code gegen persistente Objekte laufen zu lassen. 4. NQ erweitern das Konzept der Evaluations um erhöhte Typsicherheit und die Möglichkeit, Abfragen per Bytecodeanalyse in (performantere) äquivalente S.O.D.A.-Ausdrücke zu überführen. Dies ist die Hauptabfragesprache für db4o.
4.1 Query By Example Musterobjekte Eine QbE-Abfrage besteht aus einem Musterobjekt. Alle Felder, für die man einen gewünschten Wert vorgeben möchte, werden dabei entsprechend gesetzt. Felder, die mit dem Defaultwert (null für Objektreferenzen, 0 für primitive Typen) belegt sind, werden für die Anfrage ignoriert. Listing 4.1: Einfaches QbE
ObjectContainer db=Db4o.openFile("db.yap"); try { Student template= new Student(0,null,”Valint”);
26
3-939084-03-4_db4o_v01.book Seite 27 Mittwoch, 4. Oktober 2006 2:32 14
Query By Example Listing 4.1: Einfaches QbE (Forts.)
ObjectSet<Student> result=db.get(template); while (result.hasNext()) { Student student=result.next(); System.out.println(student); } } finally { db.close(); }
In unserem Beispiel (siehe Listing 4.1) suchen wir alle Studenten mit Nachnamen „Valint“. Hierzu erstellen wir ein Musterobjekt: Student template = new Student(0,null,”Valint”);
Da wir Matrikelnummer und Vornamen nicht kennen und nicht spezifizieren wollen, setzen wir diese Parameter des Musterobjekts auf die entsprechenden Defaultwerte. ObjectSet<Student> result = db.get(template);
Die Ausführung einer QbE-Abfrage erfolgt durch Übergabe des Musterobjekts an die Methode get() in ObjectContainer. Das Ergebnis vom Typ ObjectSet kann nun entweder als List oder, wie im Beispiel, als Iterator weiterverwendet werden. while (result.hasNext()) { Student student=result.next(); System.out.println(student); }
schnell + kompakt
27
3-939084-03-4_db4o_v01.book Seite 28 Mittwoch, 4. Oktober 2006 2:32 14
4 – Abfragekonzepte unter db4o
Hinweis Im vorherigen Kapitel haben wir bereits einige Objekte von Typ Student in der Datenbank db.yap abgelegt. Das Ergebnis der Query ist: [console] Bogdan Valint < matriculation number: 8306>
Musterobjekte vom Typ java.lang.Class Im vorherigen Kapitel haben wir bereits einen QbE-Spezialfall kennen gelernt: Exemplare von java.lang.Class werden als Musterobjekte gesondert behandelt und spezifizieren ausschließlich den erwarteten Objekttyp – man erhält alle gespeicherten Exemplare der gewünschten Klasse, den „Extent“ dieser Klasse. ObjectSet<Student> result = db.get(Student.class);
Für eine äquivalente Formulierung in „reinem“ QbE würde man alle Felder des Musterobjekts auf ihre Defaultwerte setzen. ObjectSet<Student> result = db.get(new Student(0,null,null));
Abfrage aller Objekte mit null Um alle gespeicherten Objekte aus der Datenbank abzufragen, übergibt man null als Musterobjekt. Jedoch erhält man dann wirklich alle Objekte, die in der Datenbank enthalten sind – nicht nur Student und Lecturer, sondern auch „Utilityklassen“ wie ArrayList etc. ObjectSet result = db.get(null);
28
3-939084-03-4_db4o_v01.book Seite 29 Mittwoch, 4. Oktober 2006 2:32 14
Query By Example
Hinweis Ein leerer String "" sollte nicht mit dem Wert null verwechselt werden. Defaultwerte im Sinne von QbE sind ausschließlich null, 0 und 0.0. Arrays als Musterobjekte Eine weitere offensichtliche Frage ist, wie man Arrays mit Musterobjekten spezifiziert. Course courseproto= new Course(null, new Course[]{new Course("mathematics1")}); ObjectSet result = db.get(courseproto); while (result.hasNext()) { System.out.println(result.next());}
Man bildet ein Array vom gewünschten Typ und füllt es mit den Elementen, die enthalten sein sollen. Mit anderen Worten: Es ist irrelevant, wie groß das Array tatsächlich ist und an welchen exakten Positionen die geforderten Elemente auftreten – die Query spezifiziert also eine geordnete Teilmenge des Kandidatenarrays. In unserem Fall erhält man alle Objekte vom Typ Course, für die der Kurs „mathematics1“ Voraussetzung ist. [console] philosophy mathematics2
QbE Fazit QbE ist einfach und elegant, bringt aber einige gravierende Einschränkungen mit sich: Abfragen nach Defaultwerten sind nicht
schnell + kompakt
29
3-939084-03-4_db4o_v01.book Seite 30 Mittwoch, 4. Oktober 2006 2:32 14
4 – Abfragekonzepte unter db4o
möglich, Seiteneffekte bei der Erzeugung der Musterobjekte können den Einsatz von QbE unmöglich machen, komplexere Abfragen, etwa unter Verwendung logischer Verknüpfungen, können nicht formuliert werden. Damit eignet sich QbE hauptsächlich für den Einstieg in db4o oder für kleinere Experimente; für den Produktiveinsatz stellen S.O.D.A. und Native Queries deutlich flexiblere und mächtigere Konzepte zur Verfügung.
4.2 S.O.D.A. S.O.D.A. steht für Simple Object Database Access1. Das Basiskonzept einer S.O.D.A.-Anfrage ist der Querygraph. Ein solcher Graph ähnelt in gewisser Hinsicht dem Musterobjekt im QbEAnsatz, nur ist dieses Muster hier kein „lebendiges“ Anwendungsobjekt, sondern eine abstrakte Spezifikation. constrain() Wir beginnen mit einem inzwischen bekannten Beispiel: Es sollen alle Studierenden aus unserer Datenbank abgefragt werden. Query query = db.query(); query.constrain(Student.class); ObjectSet result = query.execute();
Der Kern der S.O.D.A.-API ist das Interface Query. Mittels db.query() wird eine Instanz von Query erstellt; diese repräsentiert die „Wurzel“ unseres Querygraphen, der sukzessive die Suchkriterien hinzugefügt werden können. Mit dem Aufruf von constrain(Student.class) wird der Extent spezifiziert, die Suche also auf Objekte vom Typ Student eingeschränkt. Der Aufruf von execute() auf dem derart konfigurierten 1. Die S.O.D.A.-Spezifikation steht unabhängig von db4o unter http://sf.net/projects/ sodaquery zum Download zur Verfügung.
30
3-939084-03-4_db4o_v01.book Seite 31 Mittwoch, 4. Oktober 2006 2:32 14
S.O.D.A.
Query-Objekt liefert uns das Ergebnis der Suche in Form unseres bekannten ObjectSet. Um das Ergebnis zu veranschaulichen, verwenden wir in Abbildung 4.2 die folgende Graphnotation:
Abb. 4.2: Querygraph in S.O.D.A.
Hierbei stellt der Kreis einen Query-Knoten dar, das Rechteck einen Constraint und das Fünfeck unser „Handle“ auf den Graphen. Der Query-Knoten spezifiziert unsere Anfrage auf Objekte vom Typ Student. Ein Knoten in einem Querygraph kann den Extent einer Klasse, mehrerer Klassen oder auch nur ein bestimmtes Attribut repräsentieren. Die Spezifikation eines Extents hat keine Sonderstellung, sie ist optional und gleichberechtigt gegenüber anderen Constraints. Das Ergebnis der Query ist wie erwartet: [console] Richard Schmidt<matriculation number: 8309> Bratislav Kramperg<matriculation number: 8308> Anne Visconto < matriculation number: 8307> Bogdan Valint < matriculation number: 8306>
descend() Um Feldwerte des vorgegebenen Extents zu spezifizieren, verwendet man die Methode descend(), die, ausgehend vom momentanen Query-Knoten, einen untergeordneten Query-Knoten zu-
schnell + kompakt
31
3-939084-03-4_db4o_v01.book Seite 32 Mittwoch, 4. Oktober 2006 2:32 14
4 – Abfragekonzepte unter db4o
rückgibt. Die nächste Anfrage soll alle Studenten mit dem Vornamen Bogdan zurückliefern: Query query = db.query(); query.constrain(Student.class); query.descend("firstName") .constrain("Bogdan"); ObjectSet result = query.execute();
Grafisch lässt sich diese Query wie in Abbildung 4.3 darstellen.
Abb. 4.3: S.O.D.A.-Query mit descend
Wird der Studierende mit der Matrikelnummer 8307 gesucht, setzt man analog den Parameter von descend auf „matriculationNumber“ und den Parameter von constrain auf den gewünschten Wert. query.descend("matriculationNumber") .constrain(8307);
Hinweis constrain() erwartet eine Objektreferenz auch dann, wenn das Zielfeld tatsächlich einen primitiven Typ besitzt – eigentlich wäre hier also ein Integer anstelle eines int gefragt. Ab Java 5 nimmt uns Autoboxing diese Unterscheidung ab.
32
3-939084-03-4_db4o_v01.book Seite 33 Mittwoch, 4. Oktober 2006 2:32 14
S.O.D.A.
Logische Verknüpfungen Die bisher vorgestellten Anfragen lassen sich ebensogut in QbE realisieren. Kommen wir nun zu den S.O.D.A.-Features, die über die Möglichkeiten von QbE hinausgehen. Hier sind zunächst boolesche (logische) Verknüpfungen und Vergleichsoperatoren zu erwähnen. Die Anfrage nach „allen Studierenden mit der Matrikelnummer 8306 und dem Vornamen Bogdan“ lässt sich in S.O.D.A. wie folgt formulieren: Query query = db.query(); query.constrain(Student.class); query.descend("firstName") .constrain("Bogdan") .and( query.descend("matriculationNumber") .constrain(8306));
Abb. 4.4: Zusammengesetzte Abfrage in S.O.D.A.
Man formuliert also zwei separate Anfragen nach Vornamen und Matrikelnummer und verknüpft diese per and(). Damit stellt sich allerdings die Frage, warum die Beschränkung auf Objekte vom Typ Student nicht auch mit den anderen Bedingungen verknüpft werden muss. Die einfache Antwort: db4o nimmt bei „unzusam-
schnell + kompakt
33
3-939084-03-4_db4o_v01.book Seite 34 Mittwoch, 4. Oktober 2006 2:32 14
4 – Abfragekonzepte unter db4o
menhängenden“ Constrains auf demselben Query-Knoten per default eine and()-Verknüpfung an. Das Beispiel lässt sich also vereinfachen: Query query = db.query(); query.constrain(Student.class); query.descend("firstName") .constrain("Bogdan"); query.descend("matriculationNumber") .constrain(8306);
Die logischen Operatoren or() und not() sind analog zu verwenden. Die daraus entstehenden Constrains können wiederum beliebig geschachtelt werden. Vergleichsoperatoren Die Standardoperation für S.O.D.A.-Vergleiche ist equal(). Dieser Vergleich ist nur für Basistypen (primitive Typen/Wrapper, Strings, Date) identisch mit dem Aufruf der Java-Standardmethode equals(). Für komplexere Objekte wird ein Vergleich im QBE-Modus vorgenommen. Mit anderen Worten: QBE-Anfragen lassen sich in S.O.D.A.-Anfragen einbetten. Alternative Vergleichsmodi sind der Vergleich auf Objektidentität (identity()), relative Vergleiche (greater(), smaller()) sowie spezielle Stringvergleiche (startsWith(), endsWith(), contains(), like()). Im Beispiel suchen wir alle Studenten, deren Nachname mit „V“ beginnt.
34
3-939084-03-4_db4o_v01.book Seite 35 Mittwoch, 4. Oktober 2006 2:32 14
S.O.D.A.
Query query = db.query(); query.constrain(Student.class); query.descend("lastName") .constrain("V") .startsWith(true); ObjectSet result = query.execute(); [console] Anne Visconto < matriculation number: 8307> Bogdan Valint < matriculation number: 8306>
Hinweis Einige Modifikatoren lassen sich kombinieren: greater().equal() steht beispielsweise für „>=“ („größer gleich“). Absteigende und aufsteigende Sortierung S.O.D.A. bietet schließlich auch die Möglichkeit, das Ergebnis aufsteigend oder absteigend nach dem Wert eines vorgegebenen Felds (das nicht unbedingt auch als Anfragekriterium verwendet werden muss) sortieren zu lassen. Dieses Beispiel liefert alle Studenten, aufsteigend sortiert nach Nachnamen. Query query = db.query(); query.constrain(Student.class); query.descend("lastName").orderAscending(); ObjectSet result = query.execute();
schnell + kompakt
35
3-939084-03-4_db4o_v01.book Seite 36 Mittwoch, 4. Oktober 2006 2:32 14
4 – Abfragekonzepte unter db4o
Die Tabellen 4.1 und 4.2 verschaffen einen schnellen Überblick über die wichtigsten Methoden der S.O.D.A.-Basisinterfaces. Tabelle 4.1: Wichtige Methoden des Interface Query
Query-Methoden
Beschreibung
constrain
Fügt eine Einschränkung hinzu
descend
Erzeugt eine Subquery für das Feld mit dem gegebenen Namen
execute
Führt die Anfrage aus
orderAscending
Aufsteigende Sortierung
orderDescending
Absteigende Sortierung
Tabelle 4.2: Wichtige Methoden des Interface Constraint
ConstraintMethoden
Beschreibung
and
Logisches UND
or
Logisches ODER
not
Logische Negation
equal
Vergleich per equals() oder QbE
greater
„>“ / größer
smaller
„<“ / kleiner
identity
Vergleich auf Objektidentität
contains
Analog zu String#contains()
36
3-939084-03-4_db4o_v01.book Seite 37 Mittwoch, 4. Oktober 2006 2:32 14
S.O.D.A. Tabelle 4.2: Wichtige Methoden des Interface Constraint (Forts.)
ConstraintMethoden
Beschreibung
like
Wie contains(), aber ohne Berücksichtigung von Groß-/Kleinschreibung
startsWith
Analog zu String#startsWith()
endsWith
Analog zu String#endsWith()
Collection und Arrays Ein sehr wichtiges Merkmal ist, dass Collections und Array für S.O.D.A.-Queries transparent sind. Das bedeutet, dass die spezifizierten Felder in der constrain()-Methode auch ein CollectionObjekt sein können. Sucht man nach einem bestimmten FelderWert, so ist der als Parameter der descend()-Methode einzugeben. Die Suche erfolgt danach automatisch. Query q1 = db.query(); q1.constrain(Course.class); q1.descend("prerequisites").descend("courseTitle") .constrain("ethic"); ObjectSet result1 = q1.execute(); System.out.println(result1);
Das Attribut „prerequisites“ ist eine Collection, die Objekte vom Typ Course enthält. db4o geht durch jedes Element aus dieser Collection und vergleicht, ob das Attribut „courseTitle“ dem Namen „ethic“ entspricht. Das Ergebnis ist wie erwartet: Der Kurs „philosophy“ hat die Voraussetzung „ethic“.
schnell + kompakt
37
3-939084-03-4_db4o_v01.book Seite 38 Mittwoch, 4. Oktober 2006 2:32 14
4 – Abfragekonzepte unter db4o
[console] [philosophy]
Weitere Beispiele sind analog aufgebaut: Query q2 = db.query(); q2.constrain(Student.class); q2.descend("schedule").descend("schedule").descend("course Title") .constrain("ethic"); ObjectSet result2 = q2.execute(); System.out.println(result2);
Dabei werden alle Studierenden, die den Kurs „ethic“ besuchen, zurückgegeben. Query q3 = db.query(); q3.constrain(Lecturer.class); q3.descend("instructs").descend("courseTitle").constrain(" ethic"); ObjectSet result3 = q3.execute(); System.out.println(result3);
Mit dieser Query fragt man nach allen Dozenten, die den Kurs „ethic“ betreuen. Das Attribut „instructs“ ist eine java.util.ArrayList. Evaluation Evaluation bietet einen Mechanismus, beliebigen Code in die S.O.D.A.-Anfrage einzubetten. Diesen Ansatz kann man verwenden, wenn sich die normale S.O.D.A.-Query nicht formulieren lässt.
38
3-939084-03-4_db4o_v01.book Seite 39 Mittwoch, 4. Oktober 2006 2:32 14
S.O.D.A.
Den gewünschten Code implementiert man in der evaluate(Candidate candidate)-Methode in einer Klasse vom Interface Evaluation. import com.db4o.query.Candidate; import com.db4o.query.Evaluation; import de.db4o.book.Course; public class SODAEvaluation implements Evaluation { public void evaluate(Candidate candidate) { Course course = (Course) candidate .getObject(); candidate.include(course.getRequirements() .isEmpty()); }}
Das Candidate-Interface enthält die Methoden getObject() und include(). Das aktuelle Objekt wird anhand der Methode getObject() zurückgegeben. Die include()-Methode mit dem booleschen Parameter bestimmt, ob dieses Objekt berücksichtigt werden kann oder nicht. Danach integriert man eine neue SODAEvaluation-Instanz in die S.O.D.A.-Query: Query q = db.query(); q.constrain(Course.class); q.constrain(new SODAEvaluation()); ObjectSet result = q.execute(); System.out.println(result);
schnell + kompakt
39
3-939084-03-4_db4o_v01.book Seite 40 Mittwoch, 4. Oktober 2006 2:32 14
4 – Abfragekonzepte unter db4o
Das Ergebnis dieser Anfrage sind alle Kurse, die keine Voraussetzung haben. [console] [ethic, mathematics1]
Der Nachteil dieses Ansatzes ist, dass die Evaluation sehr langsam ist. Null-Werte In S.O.D.A. besteht die Möglichkeit, die Objekte mit den NullWerten abzufragen. Im Gegensatz zu QbE ist null ein gültiger Wert für S.O.D.A.
4.3 Native Queries Die zukünftige Hauptanfragesprache für db4o sind Native Queries (NQ), ein recht neuartiger Ansatz, dessen Implementierung in db4o als pragmatischer „proof of concept“ der Arbeiten von William Cook1 betrachtet werden kann. NQ-Abfragen werden vollständig in Java bzw. C# formuliert. Typ- und Feldnamen werden nicht in Strings „versteckt“, daher sind NQs den Toolsets moderner IDEs zugänglich; sie sind typsicher und refakturierbar. NQs steht die gesamte Bandbreite der zugrunde liegenden Programmiersprache zur Verfügung. Intern wird der Bytecode eines NQ-Prädikats optional analysiert und in eine äquivalente S.O.D.A.-Darstellung überführt. Diese Umwandlung erlaubt es, NQ gegen Feldindizes auszuführen. Dadurch wird eine höhere Performance der Abfrage erzielt.
1. Cook/Rosenberger, Native Queries for Persistent Objects, A Design White Paper. Cook/Rai, Safe Query Objects: Statically Typed Objects as Remotely Executable Queries (2005).
40
3-939084-03-4_db4o_v01.book Seite 41 Mittwoch, 4. Oktober 2006 2:32 14
Native Queries
Hinweis Die Bytecodeanalyse und -instrumentierung erfordert es, dass bloat.jar und db4o-*.*-nqopt.jar im Klassenpfad vorhanden sind. Beide Bibliotheken sind Bestandteil der db4o-Distribution. Hauptelemente einer Native Query Wir beginnen die Vorstellung der Native Queries mit unserem Standardbeispiel, der „Liste aller Studierenden“. ObjectSet<Student> list = db.query( new Predicate<Student>() { public boolean match(Student candidate) { return true; } });
Hier sind bereits alle Kernelemente einer NQ sichtbar: Eine Anfrage besteht aus einer Implementierung der Klasse Predicate. Der erwartete Extenttyp wird als generischer Parameter deklariert; die eigentliche Anfrage wird in der (in Predicate abstrakt deklarierten) Methode match() spezifiziert. Alle Objekte eines gegebenen Extent erhalten wir, indem wir in match() jedes Kandidatenobjekt bedingungslos akzeptieren, also durch Rückgabe von true. Beschränken wir unsere Abfrage auf „alle Studierenden mit Vornamen Anne“, ... ObjectSet<Student> list = db.query( new Predicate<Student>() { public boolean match(Student candidate) { return candidate.getFirstName() .equalsIgnoreCase("Anne"); } });
schnell + kompakt
41
3-939084-03-4_db4o_v01.book Seite 42 Mittwoch, 4. Oktober 2006 2:32 14
4 – Abfragekonzepte unter db4o
... alle Dozenten mit Personalnummer 15572 oder Nachnamen „Aristokro“ ... ObjectSet result = db.query( new Predicate() { public boolean match(Lecturer candidate) { return candidate.getPersonnelNumber() == 15572 || candidate.getLastname() .equalsIgnoreCase("Aristokro"); } });
... oder alle Kurse, die eine vorherige Teilnahme am Kurs „Ethik“ voraussetzen. // get the “Ethics” course ObjectSet ethicCourses = db.query(new Predicate() { public boolean match(Course candidate) { return candidate.getCourseTitle() .equalsIgnoreCase("ethic"); } }); final Course ethics = (Course) ethicCourses.next(); // get courses requiring “Ethics” ObjectSet resultCourses = db.query(new Predicate(){ public boolean match(Course candidate) { return candidate.getRequirements() .contains(ethics); } });
42
3-939084-03-4_db4o_v01.book Seite 43 Mittwoch, 4. Oktober 2006 2:32 14
Native Queries
Dies ist nur eine mögliche Variante, diese Anfrage auszudrücken. Welche „Formulierung“ jeweils zu bevorzugen ist, hängt stark vom konkreten Objektmodell ab (und natürlich davon, ob diese spezielle Formulierung nach S.O.D.A. optimiert werden kann). Zum Vergleich stellen wir die letzte Anfrage auch in S.O.D.A. Query q=db.query(); q.constrain(Course.class); q.descend("courseTitle").constrain("ethic"); ObjectSet result= q.execute(); Course ethic=(Course)result.next(); Query q1=db.query(); q1.constrain(Course.class); q1.descend("requirements").constrain(ethic); ObjectSet courses= q1.execute();
Auf den ersten Blick sieht die NQ-Version komplizierter aus. Das liegt am Code, der in Java notwendig ist, um ein Methodenobjekt zu emulieren. Moderne IDEs können die Tipparbeit größtenteils automatisieren, und bei komplexeren Anfragen relativiert sich dieser Nachteil schnell. Außerdem besteht inzwischen vage Hoffnung, dass die nächste Java-Version echte Closures mit einer vereinfachten Syntax bietet, wie es sie in .NET schon gibt. Sortierte Native Queries Die gewünschte Sortierung der Ergebnisliste einer NQ lässt sich beim Aufruf von query() über einen optionalen Parameter vom Typ java.util.Comparable spezifizieren.
schnell + kompakt
43
3-939084-03-4_db4o_v01.book Seite 44 Mittwoch, 4. Oktober 2006 2:32 14
4 – Abfragekonzepte unter db4o
Comparator courseCmp= new Comparator() { public int compare(Course o1, Course o2) { return o1.getCourseTitle() .compareTo(o2.getCourseTitle()); } }; ObjectSet result = db.query(new Predicate() { public boolean match(Course candidate) { return true; } }, courseCmp); System.out.println(result); [console] [ ethic, mathematics1, philosophy]
mathematics2,
mathematics3,
Zum Abschluss betrachten wir noch ein komplexeres Beispiel: „Liste alle Kurse auf, die der Student mit der Matrikelnummer 8308 belegt hat, inklusive aller Dozenten, die den jeweiligen Kurs betreuen“. ObjectSet<Student> result = db.query(new Predicate<Student>() { public boolean match(Student candidate) { return candidate .getMatriculationNumber() == 8308; } });
44
3-939084-03-4_db4o_v01.book Seite 45 Mittwoch, 4. Oktober 2006 2:32 14
Native Queries
Student stud = result.next(); ArrayList schedule = stud.getSchedule().getSchedule(); System.out.println(stud.toString() + " has schedule: "+ schedule.toString()); for (int i = 0; i < schedule.size(); i++) { final Course course = schedule.get(i); ObjectSet result2 = db.query(new Predicate() { public boolean match(Lecturer candidate) { return candidate.includesCourse(course); } }); while (result2.hasNext()) { result2.next().getLecturerByCourse(course); } }
Zunächst wird der Studierende mit der Matrikelnummer 8306 abgefragt, um dann über alle Kurse in dessen Studienplan zu iterieren und per NQ nach dem zugehörigen Dozenten zu suchen. [console] Bratislav Kramperg < matriculation number: 8308> has schedule: [ mathematics2] mathematics2 is instructed by Taylor Brada < personnel number: 15572>
schnell + kompakt
45
3-939084-03-4_db4o_v01.book Seite 46 Mittwoch, 4. Oktober 2006 2:32 14
4 – Abfragekonzepte unter db4o
Vorteile von Native Queries Wenn auch die Vorteile des NQ-Konzepts nicht sofort erkennbar sind, so sind sie gerade unter dem Gesichtspunkt der Wartbarkeit bemerkenswert: 쐌 NQs integrieren sich mit einer minimalen API in die zugrunde liegende Programmiersprache. Es ist keine Abfragesprache und keine komplexe API zu erlernen, dem Entwickler bleiben ständige „Kontextwechsel“ bei der Arbeit erspart. 쐌 Beliebig komplexe Queries sind schnell und intuitiv geschrieben, ohne von den Beschränkungen einer speziellen Abfragesprache behindert zu werden. Optimierungen können sich auf die „Hotspots“ konzentrieren. 쐌 NQs sind statischer Typüberprüfung zugänglich: Ein Tippfehler in „courseTitle“ oder „requirements“ würde im S.O.D.A.Beispiel erst zur Laufzeit auffallen, bei NQs würde er schon von IDE oder Compiler abgefangen. 쐌 Aus denselben Gründen sind NQs stabil gegenüber (manuellen oder IDE-gestützten) Refactorings wie Umbenennungen von Feldern und verwandten Code-Transformationen.
4.4 Auswahl des Abfragekonzepts Bleibt noch die Frage, unter welchen Umständen welches Abfragekonzept zu bevorzugen ist. Die Antwort ist recht einfach: 쐌 NQs sind die Hauptabfragesprache für db4o. Sie erscheinen uns deutlich bequemer und besser wartbar als andere Abfragekonzepte (nicht nur im Kontext von db4o). Mit der Weiterentwicklung des Optimierers wird die Anzahl der Fälle, die aus Performancegründen eine Formulierung in S.O.D.A. nötig machen, weiter sinken. 쐌 S.O.D.A. sollte zur Optimierung von NQ-Hotspots eingesetzt werden sowie in Fällen, in denen das Objektmodell die ge-
46
3-939084-03-4_db4o_v01.book Seite 47 Mittwoch, 4. Oktober 2006 2:32 14
Auswahl des Abfragekonzepts
wünschte Query in NQ-Form (also: „in Java“) nicht zulässt – z.B. wenn die Query auf private Felder zugreifen muss, die auch über Getter etc. nicht zugänglich sind oder wenn Referenzen „in Gegenrichtung“ navigiert werden müssen. 쐌 QbE ist nicht für den Produktivbetrieb gedacht, sondern für kleine Experimente, ad-hoc-Abfragen und für den „Einstieg“ in db4o. Ausnahmen bestätigen die Regel, so kann es (in seltenen Fällen) sinnvoll sein, QbE-Anfragen in S.O.D.A.-Anfragen einzubetten.
schnell + kompakt
47
3-939084-03-4_db4o_v01.book Seite 48 Mittwoch, 4. Oktober 2006 2:32 14
3-939084-03-4_db4o_v01.book Seite 49 Mittwoch, 4. Oktober 2006 2:32 14
KAPITEL 5 Tiefe Objektgraphen
Bisher wurden nur relativ einfache und „flache“ Objekte betrachtet. Wie aber behandelt db4o komplexere, rekursiv definierte Strukturen, wie etwa Bäume, so dass man mit einer Referenz auf einen einzelnen Knoten nicht immer gleich die ganze Datenbank im Speicher hat? Eine denkbare Lösung wäre ein transparentes System, das über lazy loading, Caches und Garbage Collection-artige Mechanismen den Eindruck eines „unendlichen“ Speichers erzeugt. Dies würde allerdings Komplexität und Ressourcenansprüche erhöhen. Da db4o ursprünglich besonders auf mobile Geräte mit begrenzten Ressourcen zielte, wurde hier ein anderer Ansatz gewählt, der dem Entwickler abverlangt, das „partielle Laden“ von Objektgraphen mit einzubeziehen, ihm andererseits aber auch entsprechend mehr Kontrolle über das Laufzeitverhalten lässt. Aktivierungstiefe Der Vorgang des „rekursiven“ Nachladens eines Feldwerts eines persistenten Objekts nennt sich Aktivierung. Der Wert eines nicht aktivierten Felds ist null im Falle eines Referenztypen und der
schnell + kompakt
49
3-939084-03-4_db4o_v01.book Seite 50 Mittwoch, 4. Oktober 2006 2:32 14
5 – Tiefe Objektgraphen
entsprechende numerische Nullwert im Falle eines primitiven Typen. Hinweis Die Aktivierungstiefe eines Objekts beschreibt, welche Bestandteile des mit diesem Objekt verknüpften Graphen in den Speicher geladen wurden – in wie vielen Schritten rekursiv vom Wurzelobjekt ausgehend in Felder abgestiegen wurde. (Aus dieser Definition ergibt sich schon, dass Objekte von db4o immer gleichförmig aktiviert werden: Es kann nicht geschehen, dass ein Feld eines Objekts aktiviert ist und ein anderes nicht.) Zur Illustration soll eine einfache verkettete Liste dienen. public class Node { private Node next; private int id; private String name; }
Angenommen, eine solche Liste mit drei Einträgen würde in db4o gespeichert.
Bei einer Aktivierungstiefe 0 besteht dieses Objekt ausschließlich aus der Identität des Wurzelknotens – alle Felder sind mit Nullwerten belegt.
50
3-939084-03-4_db4o_v01.book Seite 51 Mittwoch, 4. Oktober 2006 2:32 14
Tiefe Objektgraphen
Bei Aktivierungstiefe 1 sind die primitiven Felder mit den korrekten Werten belegt und der Folgeknoten ist auf Tiefe 0 aktiviert, besitzt also lediglich seine Identität.
Aktivierungstiefe 2 liefert den Folgeknoten auf Tiefe 1 aktiviert ...
... und Aktivierungstiefe 3 lädt schließlich den vollständigen Graphen in den Speicher. Wenn ein Objekt in db4o gespeichert wird, wird der vollständige Objektgraph persistiert. Wenn ein Objekt geladen wird, das sich schon im Speicher befindet, behält es seinen Aktivierungszustand. Andernfalls wird es mit der dynamisch oder per Konfiguration festgelegten Aktivierungstiefe geladen.
schnell + kompakt
51
3-939084-03-4_db4o_v01.book Seite 52 Mittwoch, 4. Oktober 2006 2:32 14
5 – Tiefe Objektgraphen
Konfiguration der Aktivierungstiefe Die Aktivierungstiefe kann global festgelegt werden. Der Defaultwert ist 5. Der Wert der Objekttiefe sollte möglichst klein angesetzt werden, um den Speicherverbrauch gering zu halten. Für bestimmte Klassen kann weiterhin festgelegt werden, dass bei Aktivierung eines ihrer Exemplare dessen Felder automatisch mitgeladen werden sollen – dass diese also stets mit Tiefe 1 aktiviert werden. Db4o.configure().activationDepth(1); Db4o.configure() .objectClass(Student.class) .cascadeOnActivate(true);
Bei rekursiven Strukturen, wie im Beispiel der verketteten Liste, führt diese Konfiguration dazu, dass die Objektgraphen immer vollständig geladen werden, da jedes Elternobjekt rekursiv ein Kaskadieren seiner Kindobjekte auf Stufe 1 auslöst. Dynamische Aktivierung In vielen Situationen dürften diese Konfigurationsmöglichkeiten deutlich zu grob sein – üblicherweise wird man von Fall zu Fall entscheiden wollen, wie tief man aktivieren möchte. Hier ist die dynamische Aktivierung die empfohlene Vorgehensweise, die jedoch die Konfiguration nicht gänzlich obsolet macht – diese kann immer noch den Standardrahmen vorgeben. Db4o.configure().activationDepth(0); ObjectContainer db=Db4o.openFile(FILENAME); Student student= (Student)db.query(Student.class).next(); // activation depth (student) == 0 objectContainer.activate(student,2); // activation depth (student) == 2
52
3-939084-03-4_db4o_v01.book Seite 53 Mittwoch, 4. Oktober 2006 2:32 14
Tiefe Objektgraphen
Deaktivierung Um nach der Nutzung von tiefen Objektgraphen Speicherplatz wieder freizugeben, ist oft der zur Aktivierung entgegengesetzte Vorgang hilfreich: die Deaktivierung, bei der alle Referenzen mit einer bestimmten Entfernung vom Wurzelobjekt auf Nullwerte gesetzt werden, ohne dass die Datenbank dies als tatsächliche Änderung des Objektmodells begreift. db.ext().deactivate(student,1); // activation depth (student) == 1
Transparent Activation Ein transparentes System für den Umgang mit Objektgraphen wie eingangs des Kapitels beschrieben wäre wünschenswert – nicht nur, um dem Anwendungsprogrammierer Arbeit zu ersparen. Die Notwendigkeit, die Struktur des Objektgraphen für eine sachgerechte Konfiguration der Aktivierungstiefe zu kennen, widerspricht schlicht objektorientierten Prinzipien und ist in manchen Fällen unerfüllbar bzw. muss experimentell ermittelt werden. Es ist zu erwarten, dass db4o bald eine integrierte Lösung anbietet – allerdings auch, dass diese nicht 100% aller Anwendungsfälle abdecken wird, da eine solche vermutlich auf der Instrumentierung oder dem Austausch der zu persistierenden Klassen basieren wird. Tipps und Strategien 쐌 Dynamische Aktivierung ist zu bevorzugen. Die globale Aktivierungstiefe sollte so gering wie möglich sein. Kaskadieren sollte vermieden werden. 쐌 Das Objektmodell lässt sich an die „nichtfunktionalen“ Speicher-/Aktivierungsanforderungen anpassen, indem speicher-
schnell + kompakt
53
3-939084-03-4_db4o_v01.book Seite 54 Mittwoch, 4. Oktober 2006 2:32 14
5 – Tiefe Objektgraphen
lastige und seltener gebrauchte Teile einer Klasse in eine eigenständige Referenz (die nicht immer aktiviert sein muss) ausgelagert werden. public class Email { private String subject; private String body; }
wird … public class Body { private String text; } public class Email { private String subject; private Body body; }
쐌 Eventuelle NullPointerExceptions können auf zu geringe Aktivierungstiefe hindeuten. 쐌 ObjectSets sind nach Erzeugung völlig deaktiviert – die enthaltenen Objekte werden erst per Zugriff mit next(), get() etc. aktiviert. Jenseits der Aktivierung Analoge Konzepte (Tiefe, Kaskadierung etc.) greifen bei der Behandlung von Updates und Löschvorgängen.
54
3-939084-03-4_db4o_v01.book Seite 55 Mittwoch, 4. Oktober 2006 2:32 14
KAPITEL 6 Ändern und Löschen von Objekten
Nach dem Speichern von Objekten und den Abfragemechanismen fehlt noch die Möglichkeit, gespeicherte Elemente zu modifizieren und zu löschen, um die Kernaufgaben einer Datenbank zu vervollständigen. Updates db4o bietet keine eigenständige API für das Update von Objekten – Änderungen erfolgen wie das Speichern neuer Objekte über den Aufruf von ObjectContainer#set(Object). Dieser Code ändert beispielsweise den Nachnamen des Studenten mit der Matrikelnummer 8308: ObjectSet<Student> result = db.query(new Predicate<Student>() { public boolean match(Student candidate) { return candidate .getMatriculationNumber()==8308; } }); Person student = (Student) result.next(); student.setLastName("Till"); db.set(student);
schnell + kompakt
55
3-939084-03-4_db4o_v01.book Seite 56 Mittwoch, 4. Oktober 2006 2:32 14
6 – Ändern und Löschen von Objekten
Hier wird der Student mit dem Attribut „Matrikelnummer 8308“ abgefragt, sein Nachname auf „Till“ gesetzt und letztendlich das modifizierte Objekt per set() wieder in der Datenbank abgespeichert. Identität Die Entscheidung zwischen Speicherung eines neuen Objekts oder Update eines bestehenden macht sich für db4o an der Objektidentität fest. Dieses Konzept wird im Kapitel „Objektidentität“ näher betrachtet. Vereinfacht gesagt, wird db4o ein Update vornehmen, wenn das übergebene Objekt bekannt ist. Ein Objekt ist einem ObjectContainer bekannt, wenn 쐌 es während der laufenden Session diesem ObjectContainer neu hinzugefügt wurde, 쐌 es während der laufenden Session von diesem ObjectContainer abgefragt wurde. Im obigen Beispiel ist der Student bekannt, weil er vorher abgefragt wurde, das Objekt wird also modifiziert. Wäre die Datenbank zwischen der Abfrage und dem Aufruf von set() geschlossen und wieder geöffnet geworden, wäre auch die Identität verloren und der Aufruf würde als Speicherung eines neuen, zweiten Studenten interpretiert. Update-Tiefe Fast alles, was im Kapitel über „Tiefe Objektgraphen“ über die Aktivierungstiefe gesagt wurde, lässt sich 1:1 auf die Update-Tiefe übertragen. db4o merkt sich nicht, welche Objekte modifiziert wurden („dirty“ sind), sondern speichert Objektgraphen beim Update strikt bis zur vorgegebenen Update-Tiefe (während beim Neuspeichern stets der komplette Graph geschrieben wird).
56
3-939084-03-4_db4o_v01.book Seite 57 Mittwoch, 4. Oktober 2006 2:32 14
Ändern und Löschen von Objekten
Ausgehend von der persistenten verketteten Liste aus dem Aktivierungsbeispiel,
die in-memory derart modifiziert worden sein könnte:
... würde sich bei einer Update-Tiefe von 0 gar nichts ändern. Bei Update-Tiefe 1 würde zwar die Referenz von A auf C „umgeschrieben“, die Referenz von C auf B bliebe aber unberücksichtigt:
Das zuvor dritte Kettenglied B verbliebe (evtl. unreferenziert) weiter in der Datenbank.
schnell + kompakt
57
3-939084-03-4_db4o_v01.book Seite 58 Mittwoch, 4. Oktober 2006 2:32 14
6 – Ändern und Löschen von Objekten
Bei einer Update-Tiefe 2 würde die Verbindung von B nach C neu geschrieben, die Verbindung von C nach B bliebe aber in der Datenbank bestehen:
Bei Update-Tiefe 3 würde der In-Memory-Zustand komplett persistiert – was wohl in nahezu allen Fällen das gewünschte sein dürfte. Die Konfigurationsmöglichkeiten sind analog zur Aktivierung; der globale Defaultwert ist 1. Dynamische Kontrolle über die Update-Tiefe erhält man, indem man die entsprechende set()-Variante verwendet. db.ext().set(student,2);
Auch hier gilt, dass üblicherweise die dynamische Kontrolle der statischen Konfiguration vorzuziehen ist. Mittel- bis langfristig ist wohl auch hier eine optionale, transparente Lösung zu erwarten. Delete Das Löschen eines – bekannten (!) – Objekts aus der Datenbank übernimmt die Methode delete().
58
3-939084-03-4_db4o_v01.book Seite 59 Mittwoch, 4. Oktober 2006 2:32 14
Ändern und Löschen von Objekten
ObjectSet<Student> result = db.query(new Predicate<Student>() { public boolean match(Student candidate) { return candidate.getMatriculationNumber() == 8308; } }); Student student = (Student) result.next(); db.delete(student);
Ohne weitere Konfiguration wird hier ausschließlich und ohne jede Prüfung das Studentenobjekt gelöscht. Persistente Referenzen auf dieses Objekt verweisen ab sofort auf null. Von diesem Objekt referenzierte Objekte werden auch dann nicht gelöscht, wenn sie von nirgendwo anders mehr referenziert werden. Dieses Objekt wird auch dann gelöscht, wenn noch Referenzen darauf bestehen – mit anderen Worten: db4o sichert für delete() keine referentielle Integrität zu! Kaskadierendes Löschen db4o bietet zum rekursiven Löschen die Konfigurationsmethode cascadeOnDelete() an. Sie ist mit ähnlicher Vorsicht zu verwenden wie der Befehl killall auf UNIX-Betriebssystemen. Bevor die Datenbank geöffnet wird, werden folgende Konfigurationseinstellungen vorgenommen: Db4o.configure().objectClass(Student.class) .cascadeOnDelete(true);
schnell + kompakt
59
3-939084-03-4_db4o_v01.book Seite 60 Mittwoch, 4. Oktober 2006 2:32 14
6 – Ändern und Löschen von Objekten
Hierdurch werden alle Objekte, die das zu löschende Objekt direkt referenziert, ebenfalls gelöscht. In unserem Fall wird mit dem Objekt Student auch das zugehörige Schedule-Exemplar aus der Datenbank entfernt – nicht aber vom Schedule referenzierte Objekte. Es muss nochmals betont werden, dass delete(), und ganz besonders cascadeOnDelete(), mit größter Vorsicht zu genießen sind. Man sollte stets sicherstellen, dass die zu löschenden Objekte nicht von anderen Objekten in der Datenbank referenziert werden. Hinweis db4o bietet interne Collections (Db4oList, Db4oMap) an, die Updates automatisch kaskadieren und performanter sind als Standard-Collections – aber damit natürlich Klassen, die sie nutzen, an die db4o-API binden.
60
3-939084-03-4_db4o_v01.book Seite 61 Mittwoch, 4. Oktober 2006 2:32 14
KAPITEL 7 Objektidentität
In der Literatur wird ein Objekt üblicherweise durch drei Attribute beschrieben: 쐌 Zustand 쐌 Verhalten 쐌 Identität Während Zustand und Verhalten im Kontext einer Objektdatenbank recht intuitiv erfassbar sind und durchgängig dieselbe Bedeutung haben, wie in der Sprache/Plattform Java selber, verdient das Konzept der Objektidentität nähere Betrachtung. Objektidentität innerhalb der VM Die erste Begegnung mit dem Konzept der Objektidentität erfährt der Java-Neuling meistens, wenn er sich mit dem identitätsbasierten Vergleichsoperator „==“ für Objektreferenzen und der equals()-Methode auseinander setzt, deren Default-Implementierung in java.lang.Object ebenfalls auf Identität basiert: Ausschließlich bei einem Vergleich eines Objekts mit sich selber wird auf Gleichheit erkannt; ein anderes Objekt des exakt selben Typs und mit exakt denselben Feldbelegungen wird als ungleich
schnell + kompakt
61
3-939084-03-4_db4o_v01.book Seite 62 Mittwoch, 4. Oktober 2006 2:32 14
7 – Objektidentität
angesehen. Die Identität wird einem Objekt bei der Erzeugung von der VM „mitgegeben“ und ändert sich während der gesamten Lebenszeit des Objekts nicht. Üblicherweise (aber nicht zwingend) korrespondiert diese Identität mit der Position der Objektreferenz im Speicher. db4o respektiert das Identitätskonzept der zugrunde liegenden Plattform – bildlich gesprochen kann es Objekte „wiedererkennen“. In der API gibt es keine unterschiedlichen Methoden für das Speichern eines neuen Objekts und das Update eines bestehenden – für beides wird die Methode ObjectContainer#set() verwendet. Falls das übergebene Objekt bereits bekannt ist, wird ein Update durchgeführt, andernfalls wird das Objekt als neu gespeichert. Student original= new Student(12345,”John”,”Doe”); db.set(original); Student copy= new Student(12345,”John”,”Doe”); // speichert einen zweiten Studenten mit // identischen Feldbelegungen db.set(copy); original.setLastName(“Doe-Dalton”); // modifiziert den bereits bekannten ersten // Studenten innerhalb der Datenbank db.set(original);
Intern nutzt db4o zum Caching der bekannten Objekte bei Verwendung neuerer Java-Versionen den Weak-Reference-Mechanismus, bei älteren Versionen wird dieser Mechanismus mittels „harter“ Referenzen (und entsprechend höherem Speicherverbrauch) emuliert.
62
3-939084-03-4_db4o_v01.book Seite 63 Mittwoch, 4. Oktober 2006 2:32 14
Objektidentität
Identität und Sessions Das beschriebene Verhalten ist im Verlauf einer allein stehenden db4o-Session recht intuitiv und problemlos. Komplizierter wird es, wenn man eine db4o-Datenbank schließt und wieder öffnet, während persistierte Objekte „lebendig“ in der laufenden Applikation verbleiben. Das „Wiedererkennen“ der Objekte beschränkt sich jeweils auf die aktuelle Session; mit dem erneuten Öffnen der Datenbank sind die aus der vorhergehenden Sitzung bekannten Identitäten vergessen. Student student= new Student(12345,”John”,”Doe”); ObjectContainer db= Db4o.openFile(„reopen.yap“); db.set(student); db.close(); db=Db4o.openFile(„reopen.yap“); student.setLastName(“Doe-Dalton”); // Das Objekt ist zwar vom Standpunkt der VM // identisch zum bereits gespeicherten – da // es sich aber um eine neue Session handelt, // wird ein “zweiter” Student gespeichert. db.set(student); db.close();
Der einfachste Weg, derartige „Verdoppelungen“ zu vermeiden, ist, stets dafür Sorge zu tragen, dass ein Objekt, das zwecks Update an ObjectContainer#set() übergeben wird, in der aktuellen Session bereits bekannt ist – dass es also innerhalb dieser Session gespeichert oder per Query abgefragt wurde.
schnell + kompakt
63
3-939084-03-4_db4o_v01.book Seite 64 Mittwoch, 4. Oktober 2006 2:32 14
7 – Objektidentität
db=Db4o.openFile(„reopen.yap“); ObjectSet<Student> result= db.query(new Predicate<Student>() { public boolean match(Student student) { return student.getMatriculationNumber() ==12345; } }); Student student=result.next(); student.setLastName(“Doe-Dalton”); // Nach der Abfrage ist der Student in der // aktuellen Session bekannt; es wird also // ein Update vorgenommen. db.set(student); db.close();
Explizite IDs In manchen Umgebungen ist dieses empfohlene Vorgehen nicht möglich oder angemessen. In diesen Fällen ist es notwendig, die „lebendigen“ Objekte und ihre Repräsentation innerhalb von db4o aufeinander zu mappen. db4o bietet hierfür Zugriff auf die internen IDs, anhand derer Objekte innerhalb einer Datenbank eindeutig identifiziert werden können. Die entsprechende API findet sich in com.db4o.ExtObjectContainer; die wichtigsten Methoden sind getID() und getByID(). Im obigen Beispiel wurde die Existenz eines „natürlichen primary key“ (nämlich der Matrikelnummer) ausgenutzt. Mithilfe von IDs lässt sich dieser Ansatz generischer gestalten.
64
3-939084-03-4_db4o_v01.book Seite 65 Mittwoch, 4. Oktober 2006 2:32 14
Objektidentität
Student student= new Student(12345,”John”,”Doe”); ObjectContainer db= Db4o.openFile(„reopen.yap“); db.set(student); long studentID=db.ext().getID(student); db.close(); db=Db4o.openFile(„reopen.yap“); student=(Student)db.ext().getByID(studentID); student.setLastName(“Doe-Dalton”); // Bekannt nach Retrieval anhand ID -> Update db.set(student); db.close();
Dieses Vorgehen vereinfacht das Nachschlagen bestimmter Objekte. Es bleibt aber bei der Inkonsistenz von Objektidentität aus Sicht der VM und aus Sicht von db4o: Aus Sicht der VM wird eine (nicht identische!) Kopie des bereits bestehenden Studenten erzeugt. In manchen Fällen ist dies nicht akzeptabel – wünschenswert wäre vielmehr, db4o zu „zwingen“, die Sichtweise der VM anzuerkennen. Hierzu existiert die Methode ExtObjectContainer#bind(). Student student= new Student(12345,”John”,”Doe”); ObjectContainer db= Db4o.openFile(„reopen.yap“); db.set(student); long studentID=db.ext().getID(student); db.close();
schnell + kompakt
65
3-939084-03-4_db4o_v01.book Seite 66 Mittwoch, 4. Oktober 2006 2:32 14
7 – Objektidentität
db=Db4o.openFile(„reopen.yap“); // zwingt db4o, den „lebendigen“ Studenten // mit der durch die ID gegebenen Identität // zu assoziieren db.ext().bind(student,studentID); student.setLastName(“Doe-Dalton”); // Erzwungenermassen bekannt -> Update db.set(student); db.close();
Dieses Vorgehen ist aus diversen Gründen kritisch und sollte wirklich nur als letzte Möglichkeit in Betracht gezogen werden, wenn die anderen beschriebenen Vorgehensweisen nicht in Frage kommen. Profitipp Die beste Strategie dürfte stets sein, das Schließen und Wiederöffnen einer Datenbank nach Möglichkeit zu vermeiden und mit dem standardmäßigen Zusammenspiel zwischen VMund db4o-Identitäten zu programmieren, statt dagegen „anzuarbeiten“. Eine andere Variante der „Vervielfachung“ von Objektidentitäten sind die unterschiedlichen Versionen eines Objekts, die in verschiedenen Transaktionen zur Verfügung stehen. Auf diese Problematik wird im Kapitel über Transaktionen näher eingegangen. Globale IDs Manche Szenarien erfordern die eindeutige Identifizierung von Objekten über VM-Grenzen und mehrere Datenbanken hinweg. Die im vorigen Kapitel verwendeten IDs sind hierfür ungeeignet – sie sind fest an eine db4o-Datenbank gebunden, nur in deren
66
3-939084-03-4_db4o_v01.book Seite 67 Mittwoch, 4. Oktober 2006 2:32 14
Objektidentität
Kontext zu verwenden und auch nicht garantiert unveränderlich – eine Defragmentierung der Datenbank führt etwa zu einer Neuzuordnung der IDs. Eine eindeutige globale Identität für Objekte erhält man durch Verwendung von Unique Universal Identifiern (UUID), die neben einer datenbankinternen Identität des Objekts auch die Identität der Datenbank spezifizieren. Die Verwendung von UUIDs muss vor dem Öffnen der Datenbank expliziert konfiguriert werden; Zugriff erhält man dann über die Methoden getObjectInfo() und getByUUID() in ExtObjectContainer. // UUIDs für alle Klassen, s. API-Docs Db4o.configure() .generateUUIDs(Integer.MAX_VALUE); ObjectContainer db= Db4o.openFile(„uuid.yap“); db.set(student); Db4oUUID uuid= db.ext().getObjectInfo(student).getUUID(); student=(Student)db.ext().getByUUID(uuid); db.close();
Das Hauptanwendungsgebiet für UUIDs ist die Replikation von Objekten zwischen db4o-Datenbanken. Diesem Thema ist ein eigenes Kapitel gewidmet. Benutzerdefinierte IDs Ein weiteres mögliches Anwendungsszenario ist die „Injektion“ extern vorgegebener IDs. db4o bietet hierfür keine spezielle API. Eine gängige Herangehensweise ist aber, ein entsprechendes Mapping extern vorzuhalten, spezielle, transiente Felder in den Benutzerklassen einzurichten und diese mithilfe von Callbacks zu verwalten. Ein komplettes Beispiel für diesen Anwendungs-
schnell + kompakt
67
3-939084-03-4_db4o_v01.book Seite 68 Mittwoch, 4. Oktober 2006 2:32 14
7 – Objektidentität
fall würde den Rahmen dieses Kapitels sprengen; die db4o-Distribution beinhaltet aber entsprechenden Code als Beispielanwendung für External Callbacks.
68
3-939084-03-4_db4o_v01.book Seite 69 Mittwoch, 4. Oktober 2006 2:32 14
KAPITEL 8 Transaktionen
Ein klassisches Feature einer Datenbank ist die Möglichkeit, eine Abfolge von (Lese- und/oder Schreib-)Operationen in eine Transaktion zu kapseln, die, weitgehend unabhängig von parallel ablaufenden Transaktionen, atomar entweder ganz oder gar nicht ausgeführt wird. Das bekannteste Kriterium für Transaktionalität ist wohl das ACID-Prinzip (Atomicity, Consistency, Isolation, Durability). Transaktionale Systeme werden kategorisiert nach der Strenge, mit der die Unabhängigkeit der Transaktionen gewährleistet wird (Isolationsgrad). Wir setzen diese Grundlagen hier als bekannt voraus und konzentrieren uns auf die praktische Anwendung in db4o. Transaktionen in db4o: Theorie ... Da die „lebendigen“ Objekte innerhalb der VM und ihre persistente Repräsentation in db4o eng miteinander verbunden sind, müssen die „lebendigen“ Objekte mit in das Transaktionskonzept einbezogen werden: Jede Transaktion muss über ihre eigene Version der bearbeiteten Objekte verfügen. Andernfalls wären alle Änderungen am Objekt sofort in allen anderen Transaktionen sichtbar, die Zugriff auf dasselbe Objekt haben. Das bringt
schnell + kompakt
69
3-939084-03-4_db4o_v01.book Seite 70 Mittwoch, 4. Oktober 2006 2:32 14
8 – Transaktionen
gewisse Anforderungen an das Design der transaktionalen Clients mit sich, die denjenigen ähneln, die bereits im Kapitel über Objektidentität angesprochen wurden: Der Client muss dafür Sorge tragen, dass die von ihm bearbeiteten Objekte der Transaktion „bekannt“ sind. db4o bietet ACID-Transaktionen des Isolationsgrads READ COMMITTED. Das bedeutet, dass Änderungen einer Transaktion sofort mit ihrem erfolgreichen Abschluss (COMMIT) für andere Transaktionen sichtbar werden; die Transaktionen sind also nicht vollständig unabhängig voneinander. Darüber hinaus ist zu berücksichtigen, dass db4o bereits im Speicher vorhandene Objekte nicht automatisch von der Festplatte nachlädt – um von anderen Transaktionen vorgenommene Änderungen tatsächlich wahrzunehmen, ist also sicherzustellen, dass die der aktuellen Transaktion bekannte Objektversion auf dem neuesten Stand ist. Das mag etwas abschreckend klingen, ist für das Gros der Anwendungsfälle aber absolut ausreichend. Zahlreiche relationale Systeme können sehr gut mit dem Isolationsgrad READ COMMITTED leben, und die Probleme, die durch Inkonsistenzen zwischen persistenter und „lebendiger“ Objektversion auftreten können, minimieren sich, wenn die Grundregeln, die wir im Kapitel „Objektorientierte Modellierung“ vorstellen, beachtet werden. ... und Praxis Die Praxis gestaltet sich ebenfalls weitaus einfacher, als die lange Vorrede vielleicht vermuten ließ. Tatsächlich arbeiten wir schon seit dem ersten Kapitel durchgehend transaktional: Jede Operation in db4o findet im Kontext einer Transaktion statt; im bisher betrachteten Solo-Modus wird diese implizit mit dem Öffnen der Datenbank gestartet und beim Schließen mit einem COMMIT be-
70
3-939084-03-4_db4o_v01.book Seite 71 Mittwoch, 4. Oktober 2006 2:32 14
Transaktionen
endet. Diese implizite Transaktion lässt sich selbstverständlich auch explizit kontrollieren. Student student= new Student(12345,“John“,“Doe“); ObjectContainer db=Db4o.openFile(„trans.yap“); db.set(student); db.commit(); // Student ist gespeichert student.setLastName(„Doe-Dalton“); db.set(student); db.rollback(); // Keine Änderung in der DB, „lebendiges“ und // persistentes Objekt sind inkonsistent! db.set(student); db.commit(); // Versionen sind wieder konsistent
Mehrere Transaktionen Um vom Solo- zum Embedded-Modus überzugehen, also mit mehreren Transaktionen zu arbeiten, müssen wir zunächst einen Server (die „eigentliche“ Datenbank) erzeugen und darauf über (transaktionale) Clients zugreifen. Die Clients implementieren das wohlbekannte ObjectContainer-Interface, nach dem Erstellen der Transaktion bleibt auf API-Ebene also alles beim Alten. Hinweis Eine db4o-Datei sollte nie von mehreren File- oder ServerHandlern gleichzeitig geöffnet werden! Mehrere Referenzen auf eine db4o-Datei sollten stets als transaktionale Clients realisiert werden.
schnell + kompakt
71
3-939084-03-4_db4o_v01.book Seite 72 Mittwoch, 4. Oktober 2006 2:32 14
8 – Transaktionen
Abb. 8.1: db4o Embedded Server
Im folgenden Beispiel entsprechen die Zahlen im Kommentar der Anzahl der Studenten, die sich aus Sicht der jeweiligen Transaktion zurzeit in der Datenbank befinden (dem Ergebnis von clientX.query(Student.class).size()). Student student1= new Student(12345,“John“,“Doe“); Student student2= new Student(12346,“Jill“,“Doe“); ObjectServer server= Db4o.openServer(„trans.yap“,0); ObjectContainer client1=server.openClient(); ObjectContainer client2=server.openClient(); client1.set(student1); // client1:1/client2:0 client2.set(student2); // 1/1 client1.commit(); // 1/2 client2.rollback(); // 1/1 client2.set(student2); // 1/2 client2.commit(); // 2/2
72
3-939084-03-4_db4o_v01.book Seite 73 Mittwoch, 4. Oktober 2006 2:32 14
Transaktionen
Die mysteriöse „0“ als Parameter des openServer()-Aufrufs ist ein Platzhalter für einen TCP-Port und legt fest, dass dieser Server ausschließlich im Embedded-Modus betrieben wird. Die Angabe einer gültigen Portnummer liefert einen Server im NetworkingModus, der im nächsten Kapitel näher betrachtet werden soll. Die Zugriffe könnten selbstverständlich auch genauso aus verschiedenen Threads heraus erfolgen.
schnell + kompakt
73
3-939084-03-4_db4o_v01.book Seite 74 Mittwoch, 4. Oktober 2006 2:32 14
3-939084-03-4_db4o_v01.book Seite 75 Mittwoch, 4. Oktober 2006 2:32 14
KAPITEL 9 db4o im Client-Server-Modus
Bisher haben wir mit mehreren transaktionalen Clients in mehreren Threads gearbeitet, sind aber stets innerhalb der Grenzen einer einzigen VM verblieben – wenn wir aber schon mit ClientServer-Abstraktionen operieren, liegt der Sprung übers Netzwerk schon nahe. db4o bietet im Networking-Modus über Sockets größtenteils dieselbe API wie im Solo- und Embedded-Modus. Out-Of-Band Signalling erlaubt darüber hinaus beliebig auf die eigene Anwendung zugeschnittene Kommunikation zwischen Server und Clients. Grundlagen der Client-Server-Programmierung in Java setzen wir in diesem Kapitel als bekannt voraus. Networking-Modus Die Kommunikation zwischen Client und Server läuft im Networking-Modus über Java-TCP/IP-Sockets.
schnell + kompakt
75
3-939084-03-4_db4o_v01.book Seite 76 Mittwoch, 4. Oktober 2006 2:32 14
9 – db4o im Client-Server-Modus
Abb. 9.1: Client/Server im Netzwerkmodus
Das folgende Codefragment zeigt die Grundlagen des Networking-Modus: ObjectServer server= Db4o.openServer(“db.yap”,0xdb40); server.grantAccess(“user”, “password”); try { ObjectContainer client= Db4o.openClient( "localhost",0xdb40, “user”, “password”); // mit DB arbeiten client.close(); } finally { server.close(); }
Der Server wird erzeugt, wie bereits aus dem Embedded-Modus bekannt. In diesem Fall wird ihm allerdings anstelle des Platzhalters 0 eine echte TCP/IP-Portnummer zugewiesen. Zusätzlich werden die Benutzername/Passwort-Paare registriert, mit denen Netzwerkclients sich verbinden können. Ein anonymer/freier Login ist derzeit nicht vorgesehen, es wird also mindestens ein grantAccess()-Aufruf benötigt. Zu einem Server im NetworkingModus können sich via ObjectServer#openClient() auch lokale Em-
76
3-939084-03-4_db4o_v01.book Seite 77 Mittwoch, 4. Oktober 2006 2:32 14
db4o im Client-Server-Modus
bedded-Clients verbinden; hierzu werden keine Login-Parameter benötigt. Nach dem Start des db4o-Servers, der stets in einem eigenen Thread läuft, können Clients erstellt und mit dem Server verbunden werden. Dies geschieht über einen Aufruf von Db4o#openClient(), hierbei müssen Hostname, TCP-Port, Benutzername und Passwort angegeben werden. Der so erzeugte Client implementiert wiederum das ObjectContainer-Interface, die eigentliche Interaktion mit db4o unterscheidet sich also nicht vom Solo- und Embedded-Modus. Hinweis Sowohl Server als auch die Clients müssen nach getaner Arbeit mit einem Aufruf der jeweiligen close()-Methode geschlossen werden. Out-of-band Signalling Oft ist es wünschenswert, beliebige Nachrichten zwischen Clients und Server auszutauschen. Ein klassisches Beispiel wäre eine Shutdown-Nachricht, die ein besonders privilegierter Client an den Server absetzen kann. Derartige Interaktionen werden durch die Messaging-API ermöglicht.
Abb. 9.2: Messaging
Das nachfolgende einfache, aber vollständige Beispiel soll eine Anwendung des Out-Of-Band Signalling veranschaulichen:
schnell + kompakt
77
3-939084-03-4_db4o_v01.book Seite 78 Mittwoch, 4. Oktober 2006 2:32 14
9 – db4o im Client-Server-Modus
import import import import import import
com.db4o.Db4o; com.db4o.ObjectContainer; com.db4o.ObjectServer; com.db4o.messaging.MessageRecipient; com.db4o.messaging.MessageSender; de.db4o.book.Student;
public class Messaging implements MessageRecipient { void main() throws Exception { // (1) ObjectServer server = Db4o.openServer("dbCS.yap", 0xdb40); server.grantAccess("user", "password"); // (2) ObjectContainer client = Db4o.openClient( "localhost", 0xdb40, "user", "password");
// (3) server.ext().configure(). setMessageRecipient(new Messaging()); // (4) MessageSender sender = client.ext().configure() .getMessageSender(); // (5) sender.send(new Messaging()); client.close(); server.close(); }
78
3-939084-03-4_db4o_v01.book Seite 79 Mittwoch, 4. Oktober 2006 2:32 14
db4o im Client-Server-Modus
// (6) public void processMessage( ObjectContainer db, Object message) { System.out.println(message ); } }
(1) Es wird ein ObjectServer erzeugt und gestartet, der auf Port 56128 (hexadezimal: db40) lauscht. Ein gültiger Login für den Benutzernamen user und das Passwort password wird registriert. (2) Beim Verbinden eines Clients sind Hostname (oder die IPAdresse als String), Port, Benutzername und Password als Parameter der Methode Db4o#openClient() zu übergeben. Das Ergebnis ist ein Exemplar von ObjectContainer. (3) Es wird serverseitig ein Nachrichtenempfänger registriert. Dieses Objekt implementiert das Interface MessageRecipient. Der Server wird ab sofort alle eingehenden Nachrichten an dessen processMessage()-Methode weiterleiten. (4) Der Client stellt einen Sender zur Verfügung, der beliebige Objekte als „Nachrichten“ an den Server übermitteln kann. (Beliebig mit einer Einschränkung: Es sollten First-Class-Objekte im Sinne von db4o sein, also etwa keine Strings oder Date-Exemplare.) (5) Der Client sendet ein Messaging-Objekt an den Server. (6) Der Server leitet das empfangene Objekt an die processMessage()-Methode seines Empfängers weiter – das Messaging-Objekt wird auf die Konsole ausgegeben.
schnell + kompakt
79
3-939084-03-4_db4o_v01.book Seite 80 Mittwoch, 4. Oktober 2006 2:32 14
3-939084-03-4_db4o_v01.book Seite 81 Mittwoch, 4. Oktober 2006 2:32 14
KAPITEL 10 Objektorientierte Modellierung 10.1 Abhängigkeiten zu db4o minimieren
81
10.2 Ausnahmen
83
10.3 Queries und Geheimnisprinzip
84
db4o versteht sich als objektorientierte Persistenzlösung. Diese Bezeichnung impliziert, dass db4o sich umso leichter in eine Anwendung integrieren lässt, je mehr deren Objektmodell sich an gängigen OO-Prinzipien orientiert. Dieses Kapitel soll zum einen die Gültigkeit dieser Regel gerade im Vergleich zu anderen Persistenzmechanismen vor Augen führen, zum anderen auf die offenbaren Ausnahmen von dieser Regel hinweisen.
10.1 Abhängigkeiten zu db4o minimieren Eine bewährte Grundregel im Umgang mit db4o lautet, nach Möglichkeit so zu programmieren, als sei überhaupt keine Persistenzschicht vorhanden – als sei db4o eine einfache Collection, die rein zufällig auch persistiert werden kann. Die Überschrift dieser Rubrik bezieht sich also nicht nur auf Abhängigkeiten im klassischen Sinn, sondern auf alle Eigenschaften des Objektmodells, die explizit zur Unterstützung der Persistenzschicht eingeführt wurden.
schnell + kompakt
81
3-939084-03-4_db4o_v01.book Seite 82 Mittwoch, 4. Oktober 2006 2:32 14
10 – Objektorientierte Modellierung
Kein eigenes Meta-Objekt-Protokoll db4o stellt keine Anforderungen an das Objektmodell. Die persistenten Anwendungsklassen müssen keine Interfaces implementieren oder von db4o-Klassen erben etc. Im Normalfall sollte man es auch tatsächlich so halten. Eine Superklasse, die ihren Subklassen Persistenzfunktionalität zur Verfügung stellt, ist oft mehr Problem als Lösung. Navigation statt Queries Abfragen sollten nur dort eingesetzt werden, wo das Objektmodell keine gleichwertige Funktionalität bietet. Falls die Navigation über Referenzen des Objektmodells möglich ist, sollte sie bevorzugt werden. Im Idealfall würden Abfragen nur eingesetzt, um „Wurzelobjekte“ zu erhalten, deren Objektgraph in Folge traversiert werden kann. Keine künstlichen Primärschlüssel Gerade Anwender, die den Umgang mit RDBMS gewohnt sind, tendieren oft dazu, ihre Applikationsklassen mit künstlichen IDAttributen zu versehen. Zumeist ist ein explizites Identitätskonzept überhaupt nicht notwendig und die übliche Identitäts- und Referenzsemantik der Java-Plattform ist völlig ausreichend. Falls es doch erforderlich wird (etwa in Webapplikationen oder VMübergreifenden Anwendungen), sollten die von db4o zur Verfügung gestellten IDs oder UUIDs verwendet werden. Im Kapitel „Objektidentität“ wird näher auf dieses Thema eingegangen. Explizite Persistenzschicht Wie von RDBMS-basierten Applikationen gewohnt, ist es auch beim Umgang mit db4o hilfreich, jegliche db4o-spezifische Funktionalität in einem eigenen Modul anzusiedeln und auf möglichst hohem Abstraktionsniveau vom Rest der Applikation zu kapseln (Data Access Object Pattern). Die „Schlankheit“ der
82
3-939084-03-4_db4o_v01.book Seite 83 Mittwoch, 4. Oktober 2006 2:32 14
Ausnahmen
db4o-API verleitet oft dazu, sie einfach dort in der Applikation aufzurufen, wo sie gerade gebraucht wird. Während es tatsächlich Anwendungsfälle gibt, in denen dieses Vorgehen sinnvoll ist (z.B. CRUD1-Applikationen mit direktem GUI/db4o-Databinding), erleichtert eine derartige Kapselung im Normalfall Wartung und Weiterentwicklung des Codes enorm, auch wenn man garantiert nicht beabsichtigt, die Implementierung der Persistenzschicht auszutauschen (eine Begründung, die unserer Erfahrung nach sowieso eher im Reich der Fabeln anzusiedeln ist).
10.2 Ausnahmen 쐌 Bis db4o 5.6 mussten Callback-Methoden zur Benachrichtigung bei Datenbankoperationen in den Applikationsklassen selbst residieren. Ab db4o 5.6 sollten externe Callbacks bevorzugt werden. 쐌 Selbstverständlich kann db4o mit den in der Standard-API zur Verfügung gestellten Collections umgehen. db4o bietet aber auch eigene Collections, die das List- bzw. Map-Interface implementieren, deutlich performanter gelesen und geschrieben werden können und ihren Aktivierungszustand selbsttätig kontrollieren. Diese Vorteile erkauft man sich allerdings mit einer engen Bindung der Applikationsklassen an db4o. 쐌 In Webapplikationen oder VM-übergreifenden Applikationen kann es notwendig sein, die (Datenbank-)Identität von Objekten explizit mitzuführen. Dies kann z.B. geschehen, indem die Applikationsklassen ein transientes Feld für die ID oder UUID deklarieren, das in Callbacks geeignet gesetzt und ausgewertet wird. 쐌 Auf subtilere Art können Performance-Erwägungen Einfluss auf die Gestaltung des Objektmodells nehmen. So legt db4o z.B. für jede Klasse (also auch abstrakte Superklassen) einen ei1. Create, Read, Update, Delete
schnell + kompakt
83
3-939084-03-4_db4o_v01.book Seite 84 Mittwoch, 4. Oktober 2006 2:32 14
10 – Objektorientierte Modellierung
genständigen Index an, dessen Verwaltung einen gewissen Overhead mit sich bringt. Queries werden umso schneller abgearbeitet, je spezifischer der Typ des abgefragten Attributs ist, usw. Prinzipiell sollte db4o also gegen „flache“ Klassen mit sehr konkret getypten Attributen (etwa ArrayList statt List) bessere Performance bieten. Doch sollten hier, bevor man ein sauberes Design aus derartigen Gründen verschlechtert, wie stets die beiden Grundregeln fürs Performance-Tuning gelten: Don't do it! und, nur für Experten, Don't do it now!
10.3 Queries und Geheimnisprinzip S.O.D.A.-Queries brechen das Geheimnisprinzip, indem sie sich explizit auf die interne Struktur der persistenten Objekte beziehen. Somit sind sie auch fragil gegenüber Änderungen der Struktur der Applikationsklassen. Der beste Weg, diesem Problem vollständig aus dem Weg zu gehen, ist, auf S.O.D.A.-Queries zu verzichten und stattdessen Native Queries zu verwenden. Diese sind vollständig objektorientiert, den Refakturierungsmechanismen moderner IDEs zugänglich und können zumindest teilweise zur Laufzeit in S.O.D.A.-Queries optimiert werden. S.O.D.A.-Queries sollten nur verwendet werden, wenn Native Queries zu kompliziert wären, um vom Optimierer umgesetzt werden zu können, oder wenn man tatsächlich in der Abfrage explizit gegen objektorientierte Prinzipien verstoßen will, etwa durch direkten Zugriff auf private Felder oder Navigation von Referenzen in Gegenrichtung.
84
3-939084-03-4_db4o_v01.book Seite 85 Mittwoch, 4. Oktober 2006 2:32 14
KAPITEL 11 Konfiguration 11.1 Container
86
11.2 Objekt
92
11.3 Client/Server
94
In diesem Kapitel werden wir auf einige der häufiger verwendeten Konfigurationseinstellungen von db4o eingehen. Zur besseren Übersicht haben wir die besprochenen Optionen grob in drei Gruppen eingeteilt: 쐌 Container: Einstellungen, die global für eine Datenbank gültig sind 쐌 Objekt: Einstellungen, die gezielt für eine bestimmte Klasse/ Interface oder für ein bestimmtes Feld vorgenommen werden 쐌 Client/Server: Einstellungen für den (embedded oder networking) Client-Server-Modus Um db4o zu konfigurieren, werden entsprechende Methoden des globalen Configuration-Objekts aufgerufen: Configuration config= Db4o.configure();
Die meisten Einstellungen müssen vorgenommen werden, bevor ein ObjectContainer geöffnet wird. Einmal konfiguriert, gelten diese Einstellungen für alle in Folge geöffneten Datenbanken – jede Datenbank erhält beim Öffnen eine Kopie dieses globalen
schnell + kompakt
85
3-939084-03-4_db4o_v01.book Seite 86 Mittwoch, 4. Oktober 2006 2:32 14
11 – Konfiguration
Konfigurationsobjekts. Ein klassisches Szenario sieht wie folgt aus: Configuration config= Db4o.configure(); config.generateUUIDs(Integer.MAX_VALUE); // weitere Einstellungen ObjectContainer db = Db4o.openFile("db.yap"); try { // DB-Operationen } finally { db.close(); }
Lokale Konfigurationseinstellungen sind über ExtObjectContainer.configure() und ExtObjectServer.configure() zugänglich. Spezifische Konfigurationen für Klassen und Felder (die „Objekt“-Kategorie) sind über das globale Konfigurationsobjekt via Configuration#objectClass() erreichbar.
11.1 Container activationDepth(int d) Legt die Aktivierungstiefe des Objekts in der Datenbank fest. Siehe dazu das Kapitel „Tiefe Objektgraphen“. Der Defaultwert ist 5. addAlias(Alias a) Ermöglicht die Registrierung von Aliasnamen für Klassen. Das Hauptanwendungsgebiet ist das Mapping von Java- und .NETKlassennamen. (In .NET ist der Assembly-Name Bestandteil des Klassennamens.)
86
3-939084-03-4_db4o_v01.book Seite 87 Mittwoch, 4. Oktober 2006 2:32 14
Container
automaticShutDown(boolean b) Gibt an, ob Datenbanken, die nicht explizit geschlossen wurden, beim Herunterfahren der VM per Shutdown-Hook oder Finalizer automatisch geschlossen werden sollen. Der empfohlene Defaultwert ist true. blockSize(int s) Diese Einstellung setzt die Blockgröße. Erlaubte Werte liegen zwischen 1 und 127, die Standardeinstellung ist 1. Die hierdurch vorgegebene Maximalgröße einer Yap-Datei ist 2 GB. Größere Werte erlauben um einen entsprechenden Faktor größere Dateien, können aber zu erhöhtem Platzverbrauch innerhalb der Datei führen. bTreeNodeSize(int s) db4o nutzt für Indizes eine B*-Tree-ähnliche Datenstruktur. Mit dieser Einstellung lässt sich die Anzahl der Objektreferenzen pro Knoten regulieren. Der Defaultwert ist 100. bTreeCacheHeight (int s) Konfiguriert die Höhe des Cache für Btree-Knoten, ausgehend von der Wurzel. Standardmäßig ist dieser Wert 0. callbacks(boolean b) Schaltet Callbacks ein und aus. Falls die Applikationsobjekte keine Callbacks benötigen, sollte diese Option abgeschaltet werden, um eine bessere Performance zu erzielen. Die Defaulteinstellung ist true.
schnell + kompakt
87
3-939084-03-4_db4o_v01.book Seite 88 Mittwoch, 4. Oktober 2006 2:32 14
11 – Konfiguration
Hinweis Callback-Methoden werden in den Applikationsobjekten implementiert. Sie können z.B. für kaskadierende Aktivierung, Löschen oder Update von Objekten bestimmter Typen verwendet werden. Die komplette Liste der Callback-Methoden ist dem Interface com.db4o.ext.ObjectCallbacks zu entnehmen. Applikationsobjekte müssen dieses Interface allerdings nicht implementieren – es reicht, Methoden mit der entsprechenden Signatur zu implementieren. Ab Version 5.6 bietet db4o außerdem die Option externer Callbacks. callConstructors(boolean b) Legt fest, ob Konstruktoren oder plattform/JDK-abhängige Mechanismen zur Instantiierung von Objekten verwendet werden sollen. Wegen möglicher Seiteneffekte sollten Konstruktoraufrufe möglichst vermieden werden. Die Defaulteinstellung ist false. detectSchemaChanges(boolean b) Wenn diese Option angeschaltet ist, wird db4o beim Hochfahren einer Datenbank Änderungen in den Klassendefinitionen bereits gespeicherter Objekte, etwa hinzugefügte oder gelöschte Felder, erkennen. Die Defaulteinstellung ist true. freespace() Dient der Konfiguration des Freespace-Systems, das für die Wiederverwendung ungenutzten Speicherplatzes innerhalb der Datenbankdatei zuständig ist. freespace().discardSmallerThan(int b);
Alle Slots der Größe b (oder kleiner) sind der Wiederverwendung nicht mehr zugänglich. Je größer dieser Wert ist, umso per-
88
3-939084-03-4_db4o_v01.book Seite 89 Mittwoch, 4. Oktober 2006 2:32 14
Container
formanter wird db4o werden, da der Overhead für die Freespace-Verwaltung sich verringert. Dieser Geschwindigkeitsvorteil wird durch ein entsprechend höheres Wachstum der Dateigröße erkauft. freespace().useIndexSystem()
Triggert die Verwendung des indexbasierten Freespace-Systems. Diese Einstellung gewährleistet einen niedrigen Speicherverbrauch und transaktionale Sicherheit des Freespace-Systems. freespace().useRamSystem()
Diese Einstellung bietet erhöhte Geschwindigkeit bei höherem Speicherverbrauch. Beim unkontrollierten Herunterfahren kann ungenutzter Freespace (bis zum nächsten Defragmentierungsvorgang) verloren gehen. exceptionsOnNotStorable(boolean b) Wenn ein Objekt nicht persistiert werden kann (z.B. weil es einen ObjectContainer referenziert), wird eine ObjectNotStorableException geworfen. Der Defaultwert ist false, nicht persistierbare Objekte werden stillschweigend ignoriert. generateUUIDs(int s) Generierung eines universellen Objektidentifikators für alle Objekte. Siehe dazu das Kapitel „Replikation“. generateVersionNumbers(int s) Generierung einer Versionsnummer eines Objekts. Gültige Werte sind: -1 (keine Generierung), 1 (spezifische Konfiguration pro Klasse, s.u.) und Integer.MAX_VALUE (für alle Objekte). Siehe dazu das Kapitel „Replikation“.
schnell + kompakt
89
3-939084-03-4_db4o_v01.book Seite 90 Mittwoch, 4. Oktober 2006 2:32 14
11 – Konfiguration
io(IoAdapter a) Definiert die I/O-Schicht, über die db4o rohe Binärdaten liest und schreibt. Per Default ist dies ein RandomAccessFileAdapter. Ein Beispiel für die Verwendung dieser Einstellung wäre der XteaEncryptionFileAdapter-Decorator (siehe com.db4o.io.crypt), der die Datenbankdatei blockweise verschlüsselt: Db4o.configure(). io(new XTeaEncryptionFileAdapter(password));
Ein weiteres Beispiel ist der MemoryIoAdapter (siehe com.db4o.io), der eine temporäre Datenbank im In-Memory-Mode erzeugt: Db4o.configure().io(new MemoryIoAdapter());
messageLevel(int l) Legt den db4o-Nachrichtenlevel fest. Es gibt insgesamt vier Levels, die unterschiedliche Ausgaben auf den konfigurierten PrintStream erzeugen. Mögliche Werte sind: 0 – keine Nachrichten 1 – Nachrichten beim Öffnen und Schließen der Datenbank 2 – Nachrichten bei Objekterzeugung, Update und Delete 3 – Nachrichten bei Aktivierung und Deaktivierung optimizeNativeQueries(boolean b) Legt fest, ob db4o versuchen soll, Native Queries nach S.O.D.A. zu optimieren. Der Defaultwert ist true. Damit diese Einstellung greift, müssen die entsprechenden Zusatzbibliotheken (db4onqopt und bloat) im Classpath vorhanden sein.
90
3-939084-03-4_db4o_v01.book Seite 91 Mittwoch, 4. Oktober 2006 2:32 14
Container
readOnly(boolean b) Falls true, wird die Datenbank im Lesemodus geöffnet. In diesem Fall wird die Datenbankdatei nicht gegen konkurrierende Zugriffe gesperrt, Modifikationsversuche (ObjectContainer#set() etc.) führen zu einer Exception. reflectWith(Reflector r) Legt den Mechanismus fest, den die Datenbank zur Objektintrospektion verwenden soll. Hauptanwendungsgebiet ist das Setzen des abstrakten Classpath. Db4o.configure().reflectWith( new com.db4o.reflect.jdk.JdkReflector( classLoader));
reserveStorageSpace(long s) Reserviert die angegebene Anzahl von Bytes innerhalb der Datenbankdatei. Diese Einstellung kann für Batch-Inserts/Updates sinnvoll sein, da sie die Wahrscheinlichkeit erhöht, dass der Speicherplatz auf dem Medium zusammenhängend allokiert wird. setBlobPath(String p) Setzt den Dateisystempfad, unterhalb dessen BLOBs (Binary Large Objects) gespeichert werden sollen. unicode(boolean b) Definiert, ob Strings im Unicode-Format gespeichert werden. Der Default ist true.
schnell + kompakt
91
3-939084-03-4_db4o_v01.book Seite 92 Mittwoch, 4. Oktober 2006 2:32 14
11 – Konfiguration
updateDepth(int d) Legt die Update-Tiefe des Objekts bei einem Update in der db4oDatenbank fest. Siehe dazu die Kapitel „Tiefe Objektgraphen“ und „Ändern und Löschen von Objekten“. weakReferences(boolean b) db4o nutzt zum Caching der bekannten Objekte weak references. Wird diese Option auf false gesetzt, werden „harte“ Referenzen verwendet. Dies erhöht sowohl Performance als auch Speicherverbrauch. (Letzteres kann durch den gezielten Einsatz von ExtObjectContainer#purge() abgemildert werden.) Defaultwert: true.
11.2 Objekt cascadeOnActivate(boolean b) Falls true, werden bei Aktivierung eines Objekts dieses Typs auch dessen direkte Member aktiviert. Siehe dazu das Kapitel „Tiefe Objektgraphen“. cascadeOnDelete(boolean b) Falls true, werden beim Löschen eines Objekts dieses Typs auch dessen direkte Member gelöscht. Siehe dazu die Kapitel „Tiefe Objektgraphen“ und „Ändern und Löschen von Objekten“. cascadeOnUpdate(boolean b) Falls true, werden beim Update eines Objekts dieses Typs auch Änderungen an dessen Feldern persistiert. Siehe dazu die Kapitel „Tiefe Objektgraphen“ und „Ändern und Löschen von Objekten“. generateUUIDs(boolean b) Generierung eines universellen Identifikators für Objekte dieses Typs. Siehe dazu das Kapitel „Replikation“.
92
3-939084-03-4_db4o_v01.book Seite 93 Mittwoch, 4. Oktober 2006 2:32 14
Objekt
generateVersionNumbers(boolean b) Generierung einer Versionsnummer für Objekte dieses Typs. Siehe dazu das Kapitel „Replikation“. maximumActivationDepth(int d) Maximale Aktivierungstiefe für Objekte dieses Typs. minimumActivationDepth(int d) Minimale Aktivierungstiefe für Objekte dieses Typs. objectField(String name).indexed(boolean b) Schaltet Indizierung für das gegebene Feld ein oder aus. Abfragen gegen indizierte Felder sollten um Größenordnungen schneller sein, als bei nichtindizierten Feldern. Dem gegenüber steht der Overhead für die Verwaltung des Index. Ab Java 5 besteht die Möglichkeit, diese Konfiguration gleich in der Applikationsklasse als Annotation festzulegen. Man markiert das gewünschte Feld mit @Indexed: public class Course{ @Indexed String courseTitle; Set prerequisites;
storeTransientFields(boolean b) Felder, die als transient markiert sind, können mit dieser Konfiguration persistiert werden. Der Defaultwert ist false. updateDepth(int d) Bestimmt die Update-Tiefe eines Objekts dieses Typs bei einem Update.
schnell + kompakt
93
3-939084-03-4_db4o_v01.book Seite 94 Mittwoch, 4. Oktober 2006 2:32 14
11 – Konfiguration
11.3 Client/Server getMessageSender() Der Client stellt einen Sender zur Verfügung, der beliebige Objekte (nicht aber primitive Werte aus db4o-Sicht, inklusive Strings und Dates) als „Nachrichten“ an den Server sendet. setMessageRecipient(MessageRecipient m) Registrierung eines Nachrichtenempfängers. Der Server wird alle eingehenden Nachrichten an dessen processMessage()-Methode weiterleiten. timeoutClientSocket(int s) Setzt die Zeit in Millisekunden, die der Client maximal auf Reaktionen des Servers warten soll. Standardmäßig beträgt diese Zeitspanne fünf Minuten. timeoutServerSocket(int s) Setzt die Zeit in Millisekunden, nach der serverseitige Threads periodisch das Bestehen der Verbindung überprüfen sollen. Standardmäßig beträgt diese Zeitspanne fünf Sekunden. timeoutPingClients(int s) Bestimmt das Zeitintervall (in Millisekunden), nach dem der Server periodisch die Verbindung zu jedem Client überprüfen soll. Die Defaulteinstellung beträgt drei Minuten.
94
3-939084-03-4_db4o_v01.book Seite 95 Mittwoch, 4. Oktober 2006 2:32 14
KAPITEL 12 db4o-Replikation
Der Begriff „Replikation“ Die Replikation ist ein Synchronisationsmechanismus eines Objektzustands zwischen mehreren Kopien dieses Objekts. Durch Replikation werden alle Änderungen, die an einem (primären) Objekt vorgenommen wurden, in allen Replikaten (Kopien dieses Objekts) aktualisiert.
Abb. 12.1: Replikation
In Bezug auf Datenbanken lässt sich Replikation wie folgt verstehen: Ein Objekt aus der Datenbank „A“ wird in die Datenbank „B“ dupliziert. Bei eventuellen Änderungen wird das Replikat auf den neuesten Stand gebracht. Dieser Mechanismus kann auch bidirektional angewandt werden, so dass auch Änderungen in Datenbank „B“ nach „A“ repliziert werden. Die Unterscheidung zwischen „primärem“ Objekt und Replikat kann in diesem Fall nicht mehr sinnvoll vorgenommen werden.
schnell + kompakt
95
3-939084-03-4_db4o_v01.book Seite 96 Mittwoch, 4. Oktober 2006 2:32 14
12 – db4o-Replikation
Anforderungen an ein Replikationssystem Ein Replikationsmechanismus für Datenbanken setzt einige Grundbedingungen voraus: 쐌 Jedes Objekt, das in der Datenbank abgespeichert wurde, muss einen eindeutigen und zugleich universellen Identifikator erhalten. 쐌 Insbesondere bei verteilten Datenbanken müssen die Objekte versioniert werden, damit das Replikationssystem entscheiden kann, welches Objekt den neuesten Stand hat. 쐌 Das Replikationssystem muss einen Konfliktauflösungsmechanismus anbieten. Konflikte entstehen, wenn auf beiden Seiten seit dem letzten Replikationsvorgang Änderungen stattgefunden haben, die Versionierung also nicht mehr in der Lage ist, den „neuesten Stand“ zu bestimmen. Der eingebaute db4o-Replikationsmechanismus db4o bietet einen eingebauten Replikationsmechanismus, der nach Bedarf aktiviert werden kann. Wir betrachten die db4o-Replikation wie immer anhand eines Beispiels mit begleitenden Kommentaren. In der Datenbank werden Objekte vom Typ Student abgelegt, definiert über Namen, Matrikelnummer und Studienleistungen. Die Methode addScore() fügt eine Benotung hinzu: public class Student { private String name; private int matrNum; private ArrayList<Score> attestation; … public void addScore(int score, String courseTitle) {
96
3-939084-03-4_db4o_v01.book Seite 97 Mittwoch, 4. Oktober 2006 2:32 14
db4o-Replikation
attestation.add( new Score(score, courseTitle)); } }
Die Klasse Score definiert eine Benotung: public class Score { private int score; private String courseTitle; … }
Um die Replikation zu ermöglichen, müssen UUID-Erzeugung und Versionierung in der globalen Konfiguration aktiviert werden. Db4o.configure().generateUUIDs( Integer.MAX_VALUE); Db4o.configure().generateVersionNumbers( Integer.MAX_VALUE);
Die Methode generateUUIDs() mit einem Parameter von Integer.MAX_VALUE schaltet die Erzeugung des eindeutigen Identifikators für alle persistierten Objekte an. Dieser Identifikator besteht aus einer Objekt-ID relativ zur Datenbank und einer Datenbanksignatur. Die Erzeugung der Versionsnummern wird über die Konfigurationseinstellung generateVersionNumbers() eingeschaltet. Eine Versionsnummer entspricht einer Transaktionsnummer – jede Modifikation des Objekts führt zur Inkrementierung der Versionsnummer. Dadurch erkennt das db4o-Replikationssystem, dass an dem Objekt Änderungen vorgenommen wurden.
schnell + kompakt
97
3-939084-03-4_db4o_v01.book Seite 98 Mittwoch, 4. Oktober 2006 2:32 14
12 – db4o-Replikation
ObjectContainer dbA =Db4o.openFile("dbA.yap"); Student student = new Student("John Smith", 2162); student.addScore(1.0, "Ethic"); dbA.set(student); dbA.commit();
In einer Datenbank dbA wird ein Student abgelegt und die Transaktion wird festgeschrieben. ObjectContainer dbB =Db4o.openFile("dbB.yap");
Eine zweite Datenbank dbB wird geöffnet. ReplicationProcess replication = dbA.ext().replicationBegin(dbB, new ReplicationConflictHandler() { public Object resolveConflict( ReplicationProcess rProcess, Object studentA, Object studentB) { return studentA; } });
Danach erzeugt man ein ReplicationProcess-Exemplar, indem man die Methode ExtObjectContainer#replicationBegin() auf unserer Quelldatenbank aufruft. Diese Methode hat zwei Parameter: eine Zieldatenbank und eine Implementierung eines ReplicationConflictHandler, die in unserem Fall stets das Objekt aus der Quelldatenbank als den neuesten Stand betrachtet. replication.setDirection(dbA, dbB);
98
3-939084-03-4_db4o_v01.book Seite 99 Mittwoch, 4. Oktober 2006 2:32 14
db4o-Replikation
Per Default ist die Replikation zwischen db4o-Instanzen bidirektional, man kann aber optional die Replikationsrichtung mit der Methode setDirection() einstellen: Im Beispiel wird ausschließlich von der Quelle dbA in das Ziel dbB repliziert. ObjectSet<Student> result = dbA.query(new Predicate<Student>() { public boolean match(Student candidate) { return true; } });
Diese Query soll die gegebenenfalls zu replizierenden Objekte bestimmen. Hier werden alle Student-Objekte ausgewählt. while (result.hasNext()) { replication.replicate(result.next()); }
Die eigentliche Replikation erfolgt dann über einen Aufruf der Methode ReplicationProcess#replicate(). Als Parameter nimmt diese Methode das zu replizierende Objekt. replication.commit(); dbA.close(); dbB.close();
Um den Replikationsvorgang abzuschließen, wird die Transaktion per commit() festgeschrieben und die Datenbanken werden, wie immer, geschlossen.
schnell + kompakt
99
3-939084-03-4_db4o_v01.book Seite 100 Mittwoch, 4. Oktober 2006 2:32 14
12 – db4o-Replikation
Hinweis Um den Replikationsvorgang ausschließlich auf seit der letzten Replikation modifizierte Objekte zu beschränken, kann die Methode ReplicationProcess#whereModified() verwendet werden, die als Parameter ein Query-Objekt erwartet und diese Abfrage auf modifizierte Objekte eingrenzt: replication.whereModified(dbA.query());
100
3-939084-03-4_db4o_v01.book Seite 101 Mittwoch, 4. Oktober 2006 2:32 14
KAPITEL 13 db4o-Tools
db4o bietet eine Reihe von Tools an, die separat von der eigentlichen Kernbibliothek geliefert werden. Nach dem Entpacken der db4o-Distribution befinden sie sich im Ordner \db4o-5.3\src\tools\src\com\db4o\tools
Die Dateien Defragment.java, Logger.java und Statistics.java sind zur Standalone-Verwendung oder zum Aufruf aus der ClientApplikation gedacht. Um diese Tools nutzen zu können, kopiert man die jeweilige Datei ins Projekt. Defragment Defragmentiert die db4o-Datenbank, d.h., der durch das Löschen oder Modifizieren von Objekten entstandene freie Speicherplatz innerhalb der Datei wird bereinigt. Außerdem erstellt Defragment eine Sicherungskopie der db4o-Datenbank mit der zusätzlichen Endung .bak. Defragmentierung gehört zum Datenbanktuning. Zur Optimierung der Performance und Reduzierung der Dateigröße sollte der Defragmentierungsprozess regelmäßig ausgeführt werden.
schnell + kompakt
101
3-939084-03-4_db4o_v01.book Seite 102 Mittwoch, 4. Oktober 2006 2:32 14
13 – db4o-Tools
ObjectContainer db=Db4o.openFile(“../db.yap”); try { // arbeiten mit db } finally { db.close(); } new Defragment().run(“db.yap”, true);
Der zweite Parameter der run()-Methode gibt an, ob eine eventuell bereits existierende .bak-Datei gelöscht werden soll. Während der Defragmentierung werden auch alle unerreichbaren Klassen oder Klassenexemplare, Klassenindizes und IDs von entfernten Objekten vollständig gelöscht. Logger Die Klasse Logger enthält statische Methoden, die Protokollierungsaufgaben erfüllen: Logger.log(ObjectContainer db, Object obj);
Analysiert das Objekt obj in der Datenbank db. Logger.logAll(ObjectContainer db);
Erstellt einen Bericht über die Struktur aller persistenten Objekte in der Datenbank db.
102
3-939084-03-4_db4o_v01.book Seite 103 Mittwoch, 4. Oktober 2006 2:32 14
db4o-Tools
Beispiel de.db4o.book.Student .Person.firstName:Richard .Person.lastName:Schmidt .Student.matriculationNumber:8309 .Student.schedule: .Schedule.MAXCOURSES:6 .Schedule.schedule: .AbstractList.modCount:3 .ArrayList.serialVersionUID: 8683452581122892189 […] Statistics Gibt statistische Information über die gesamte db4o-Datenbank auf der Konsole aus: new Statistics().run("db.yap");
schnell + kompakt
103
3-939084-03-4_db4o_v01.book Seite 104 Mittwoch, 4. Oktober 2006 2:32 14
3-939084-03-4_db4o_v01.book Seite 105 Mittwoch, 4. Oktober 2006 2:32 14
KAPITEL 14 Object Manager
Object Manager ist ein vom db4o-Kern unabhängiges Tool zum Browsen, Abfragen oder Ändern von Objekten in einer db4oDatenbank, das im Download Center unter www.db4o.com zur Verfügung steht. Starten von Object Manager Object Manager wird unter Windows über eine Batch-Datei objectmanager.bat gestartet, unter Linux: objectmanager.sh. Es ist keinerlei Installation nötig. Nach dem Öffnen einer Datenbank erhält man eine Baumansicht sämtlicher gespeicherten Klassen und Objekte (Abbildung 14.1). File öffnen FILE | OPEN FILE… Es erscheint der Standarddialog für das Öffnen der db4o-Datenbank. Falls die Datei beim Anlegen verschlüsselt wurde, ist das Passwort einzugeben. Die Objekte werden in einer Baumstruktur repräsentiert. Beim Aufklappen eines Objektknotens sieht man alle referenzierten Objekte als Kindknoten.
schnell + kompakt
105
3-939084-03-4_db4o_v01.book Seite 106 Mittwoch, 4. Oktober 2006 2:32 14
14 – Object Manager
Abb. 14.1: Object Manager
Auf der rechten Seite des OM-Fensters erscheint das angeklickte Objekt oder die Gruppe von Objekten, die zu derselben Klasse gehören. Wenn die Datenbank nicht auf „read only“ gesetzt wurde, kann man die Objektfelder editieren oder die Objekte selbst gleich löschen. Zum Server verbinden FILE | CONNECT TO SERVER… Beim Verbinden zum db4o-Server wird zuerst nach Hostnamen, Portnummer, Benutzernamen und Passwort gefragt. db4o-Datenbank nach XML exportieren FILE | EXPORT TO XML… Die baumartige Struktur des Objektgraphen kann als menschenlesbare XML-Repräsentation in eine Datei geschrieben werden.
106
3-939084-03-4_db4o_v01.book Seite 107 Mittwoch, 4. Oktober 2006 2:32 14
Object Manager
Beim Exportieren der geladenen db4o-Datenbank nach XML sind lediglich der Ordner und der Dateiname einzugeben. Queries mit Object Manager FILE | QUERY… Object Manager bietet die Möglichkeit, grafische Abfragen gemäß dem Query-By-Example-Konzept zu formulieren. Zunächst begrenzt man im Dialogfenster Query a type den Typ der Abfrage. Alternativ wechselt man über einen Doppelklick auf einen Klassenknoten in der Baumdarstellung in den QueryModus. In beiden Fällen öffnet sich der Query-Editor (Abbildung 14.2).
Abb. 14.2: Query mit Object Manager
Die Suchkriterien sind oben links einzugeben, die Ergebnisse erscheinen in der unteren Hälfte des Fensters.
schnell + kompakt
107
3-939084-03-4_db4o_v01.book Seite 108 Mittwoch, 4. Oktober 2006 2:32 14
14 – Object Manager
Object-Manager-Einstellungen FILE | PREFERENCES… Im Dialogfenster „Preferences“ kann man verschiedene Einstellungen für die betrachtete db4o-Datenbank vornehmen. OBJECT ACTIVATION Aktivierungstiefe des Objektgraphen; dies gehört zu den globalen db4o-Konfigurationseinstellungen (siehe Kapitel „Konfiguration“). CLASSPATH Beim Öffnen der db4o-Datenbank kennt Object Manager wahrscheinlich den Klassenpfad zu den konkreten Klassen der persistierten Objekte nicht und verwendet den Generic Reflector. Dessen Verwendung wird durch ein den Objektknoten vorangestelltes (G) signalisiert. Um die Klassendefinitionen bekannt zu machen und Introspektion über den Java-Reflektionsmechanismus vornehmen zu können, gibt man den Pfad des Projektordners an. CONSTRUCTOR CALLING Dies ist eine globale Einstellung. Nach dem Ändern dieser Konfiguration werden alle im Object Manager geöffneten Datenbanken geschlossen. Im Dialogfenster kann man eine Liste von Klassen angeben, für die db4o-Konstruktoraufrufe verwenden soll. Dies entspricht der db4o-Konfigurationseinstellung: Db4o.configure().objectClass(Yourclass.class) .callConstructor(true);
Mehr dazu im Kapitel „Konfiguration“.
108
3-939084-03-4_db4o_v01.book Seite 109 Mittwoch, 4. Oktober 2006 2:32 14
KAPITEL 15 db4o API
db4o befindet sich in ständiger Entwicklung und ständig wird neue Funktionalität hinzugefügt. Daher lohnt es sich immer, einen Blick auf die aktuellste API-Dokumentation zu werfen. Nach dem Entpacken der db4o-Distribution ist die API-Dokumentation im Ordner db4o-*\doc\api\
zu finden. In diesem Kapitel werden die wichtigsten Packages kurz vorgestellt. com.db4o Enthält die Hauptfunktionalität der db4o-Datenbank, etwa die bekannte Interfaces ObjectContainer, ObjectServer und ObjectSet. Hier liegt auch die Singleton-Klasse Db4o, über deren statische Methoden Datenbanken geöffnet oder Konfigurationseinstellungen vorgenommen werden können.
schnell + kompakt
109
3-939084-03-4_db4o_v01.book Seite 110 Mittwoch, 4. Oktober 2006 2:32 14
15 – db4o API
com.db4o.config Enthält die Schnittstellen und die Implementierung des Konfigurationsmechanismus, erreichbar über Db4o.configure(). com.db4o.ext Bietet Schnittstellen für eine erweiterte Funktionalität von ObjectContainer, ObjectServer und ObjectSet, erreichbar über Aufrufe von #ext() auf den entsprechenden Basisinterfaces. com.db4o.query Enthält Schnittstellen zur Abfragefunktionalität, u.a. die Basis der Native Queries, die Predicate-Klasse sowie das Interface Query, das S.O.D.A-Querygraphen repräsentiert. com.db4o.io Neben der Basisklasse des I/O-File-Adapters für die db4o-Datenbank IoAdapter ist in diesem Package auch MemoryIoAdapter für In-Memory-Operationen zu finden.
110
3-939084-03-4_db4o_v01.book Seite 111 Mittwoch, 4. Oktober 2006 2:32 14
KAPITEL 16 Weiterführende Informationen
Wir konnten in diesem Buch nur einen Bruchteil aller Aspekte der Arbeit mit db4o ansprechen, und auch diese mit oft nur geringer Detailschärfe. Hinzu kommt, dass die Entwicklung von db4o sich in schnellem Fluss befindet, so dass einige der hier niedergelegten Informationen schon bald überholt sein dürften. Wir möchten daher zum Abschluss auf die im Lieferumfang enthaltene Dokumentation und die vielfältigen Informationsquellen unter www.db4o.com hinweisen, insbesondere aber auf Folgende: db4o-Tutorial Stellt eine komplette Einführung in die Arbeit mit db4o dar. Das Tutorial ist derzeit nur in englischer Sprache verfasst, aber in jedem Download von db4o kostenfrei enthalten. Knowledge Base Sie hält kurze Artikel zu häufig auftretenden Fragen und Problemen bereit. Die Knowledge Base ist zusammen mit anderen, weiterführenden Ressourcen unter http://docs.db4o.com zu finden.
schnell + kompakt
111
3-939084-03-4_db4o_v01.book Seite 112 Mittwoch, 4. Oktober 2006 2:32 14
16 – Weiterführende Informationen
Foren Neben den englischsprachigen Support- und Entwicklerforen existiert auch ein deutschsprachiges Forum, in dem fortgeschrittene Benutzer und db4o-Entwickler selbst aktiv sind: http://developer.db4o.com/forums/16/ShowForum.aspx db4o-Blogs Neuigkeiten rund um das Produkt sind am schnellsten aus einer Reihe von Blogs zu erfahren, unter anderem in deutscher Sprache: http://blogs.db4o.com/deutsch Newsletter Der db4o-Newsletter informiert (auf Englisch) über alles Wissenswerte mit einer einzigen, übersichtlichen Mail pro Monat. Den Newsletter kann man beziehen durch Anmeldung unter: http://developer.db4o.com/user/createuser.aspx Kommerzielle Unterstützung Für eine Übersicht über deutschsprachige Materialien und für Fragen zur kommerziellen Lizenzierung und Support durch db4objects, den Hauptsponsor des db4o-Projekts, wenden Sie sich bitte an: http://www.db4o.com/deutsch E-Mail: [email protected]
112
3-939084-03-4_db4o_v01.book Seite 113 Mittwoch, 4. Oktober 2006 2:32 14
Literaturverzeichnis
db4o 5.3-Tutorial. Edlich, Stefan; Kunert, Patrick: Tapestry. Webanwendungen mit dem Apache Framework, Frankfurt, Software & Support Verlag, 2004. Elmasri, Ramez; Navathe, Shamkant B.: Grundlagen von Datenbanksystemen, München, Pearson Studium, 2002. Grehan, Rick: Inside the db4o Database, Dr.Dobb´s Journal, May 16, 2006. Heuer, Andreas: Objektorientierte Datenbanken. Konzepte, Modelle, Standards und Systeme, Bonn, Addison-Wesley-Longman, 1997. Kemper, Alfons; Eickler, André: Datenbanksysteme. Eine Einführung, München, Oldenbourg Wissenschaftsverlag, 2004. Paterson, Jim et al: The Definitive Guide to db4o, Apress, 2006.
schnell + kompakt
113
3-939084-03-4_db4o_v01.book Seite 114 Mittwoch, 4. Oktober 2006 2:32 14
3-939084-03-4_db4o_v01.book Seite 115 Mittwoch, 4. Oktober 2006 2:32 14
Stichwortverzeichnis A Abfragesprache 25 ACID-Prinzip 69 activationDepth() 86 addAlias() 86 Aktivierung 49 and() 33 API 109 Autoboxing 32 automaticShutDown() 87
delete() 58 descend() 31 detectSchemaChanges() 88
E Embedded-Modus 71 equal() 34 evaluate() 39 Evaluation 38 exceptionOnNotStorable() 89 execute() 30
B bind() 65 blockSize() 87 bTreeCacheHeight() 87 bTreeNodeSize() 87
F Foren 112 freespace() 88
G C callbacks() 87 callConstructors() 88 cascadeOnActivate() 92 cascadeOnDelete() 59, 92 cascadeOnUpdate() 92 Classpath 108 constrain() 30
generateUUIDs() 89, 92 generateVersionNumbers() 89, 93 Generic Reflector 108 getByID() 64 getID() 64 getMessageSender() 94 grantAccess() 76 Graphnotation 31 greater() 34
D Datenbank öffnen 21 Datenbank schließen 24 db4o-Blogs 112 db4o-Collections 60 db4o-Lizenzmodell 12 db4o-Tools 101 db4o-Tutorial 111 Deaktivierung 53 Defragment 101
schnell + kompakt
I indexed() 93 Installation 14 interne IDs 64 io() 90
K Knowledge Base 111 Konfigurationseinstellung 85
115
3-939084-03-4_db4o_v01.book Seite 116 Mittwoch, 4. Oktober 2006 2:32 14
Stichwortverzeichnis
L
R
Logger 102 logische Verknüpfungen 33
readOnly() 91 reflectWith() 91 replicate() 99 replicationBegin() 98 ReplicationConflictHandler 98 Replikation 95 reserveStorageSpace() 91
M match() 41 maximumActivationDepth() 93 messageLevel() 90 minimumActivationDepth() 93 Musterobjekt 26
N Native Queries 10, 40 Networking Modus 75 Newsletter 112 not() 34 null Werte 40
S S.O.D.A. 10, 30 set() 21 setBlobPath() 91 setDirection() 99 setMessageRecipient() 94 smaller() 34 Statistics 103 storeTransientFields() 93
O ObjectContainer 23 ObjectManager 105 ObjectSet 23 Objektidentität 61 Objektorientierte Datenbank 9 Objektorientierte Persistenzlösung 81 openServer() 73 optimizeNativeQueries() 90 or() 34 Out-Of-Band Signalling 75
T timeoutClientSocket() 94 timeoutPingClients() 94 timeoutServerSocket() 94 Transaktion 70
U unicode() 91 Update 55 updateDepth() 92, 93
V P
Vergleichsoperatoren 34
Predicate 41 processMessage() 79
W
Q query() 30 QueryByExample 10, 26
116
Weak Reference 62 weakReferences() 92 whereModified() 100