Webentwicklung mit JSPs, Grails, Spring, Hibernate und db4o

Werbung
Webentwicklung mit JSPs, Grails,
Spring, Hibernate und db4o
von
Ina Brenner
Hinweis:
Alle Beispiele und Listings wurden von der Autorin sorgfältig überprüft, trotzdem kann es zu
Fehlern kommen. Die Autorin kann deshalb für etwaige Schäden keine Haftung übernehmen,
die im Zusammenhang mit der Verwendung des vorliegenden Buches und aufgrund fehlerhafter
Hinweise entstehen. Die Autorin ist allerdings für alle Hinweise auf Fehler dankbar.
c
Ina
Brenner
Das vorliegende Buch ist urheberrechtlich geschützt. Alle Rechte sind der Autorin vorbehalten.
Alle Software- und Firmenangaben, die in diesem Buch erwähnt wurden, können auch ohne
Kennzeichnung eingetragene Warenzeichen sein und unterliegen somit den hierfür vorgesehenen
gesetzlichen Bestimmungen des Markenschutzes.
Satz: Ina Brenner
Inhaltsverzeichnis
Vorwort zur ersten Auflage
vii
Danksagung
ix
1
Einleitung
1
2
Installation und Konfiguration
2.1 Das erste J2EE-Projekt in NetBeans . . . . . . . . . . . . . . . . . . . . . . . . .
2.2 Installation von db4o: Einbinden einer Bibliothek . . . . . . . . . . . . . . . . . .
2.2.1 Erste Datenbankabfragen in db4o . . . . . . . . . . . . . . . . . . . . . . .
2.3 Voraussetzungen für die Nutzung der Java Persistenz API mit Hibernate und MySQL
2.3.1 Installation von MySQL . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.3.2 Erstellen einer Datenbankverbindung mit JNDI . . . . . . . . . . . . . . .
2.3.3 Einrichten der MySQL-Datenbank . . . . . . . . . . . . . . . . . . . . . .
2.3.4 Einrichten der Java Persistence API mit Hibernate . . . . . . . . . . . . .
2.3.5 Erstes Beispiel in Hibernate . . . . . . . . . . . . . . . . . . . . . . . . . .
2.4 Konfiguration eines Projektes in Spring . . . . . . . . . . . . . . . . . . . . . . .
2.4.1 Ein Spring-Projekt in NetBeans . . . . . . . . . . . . . . . . . . . . . . . .
2.4.2 Erstes Beispiel in Spring . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.5 Grundlagen eines Grails-Projektes . . . . . . . . . . . . . . . . . . . . . . . . . .
2.5.1 Erstellen eines Grails-Projekts in NetBeans . . . . . . . . . . . . . . . . .
2.5.2 Einrichten von MySQL und Hibernate in Grails . . . . . . . . . . . . . . .
2.5.3 Erstes Beispiel in Grails . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3
3
5
6
15
15
17
18
21
25
27
27
30
33
33
34
36
3
Die Java Persistence API umgesetzt in db4o, Hibernate und Grails
3.1 Java Persistence API . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.2 Erste Überlegungen zu einem Datenbankentwurf: Unser erstes Objekt
3.2.1 Erstes Objekt in Grails . . . . . . . . . . . . . . . . . . . . . .
3.3 Theoretische Grundlagen . . . . . . . . . . . . . . . . . . . . . . . . .
3.3.1 Datenredundanz und Normalisierung . . . . . . . . . . . . . . .
3.3.2 Die EJB3 Java Persistence API . . . . . . . . . . . . . . . . . .
3.3.3 Referentielle Integrität . . . . . . . . . . . . . . . . . . . . . . .
3.3.4 Theoretische Datenbankgrundlagen und db4o . . . . . . . . . .
3.4 Beziehungen in der Java Persistence API . . . . . . . . . . . . . . . . .
3.4.1 Die 1:1-Beziehung: die has-a-Beziehung . . . . . . . . . . . . .
3.4.2 Vererbungsbeziehungen . . . . . . . . . . . . . . . . . . . . . .
3.4.3 1:n-Beziehung . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.4.4 m:n-Beziehung . . . . . . . . . . . . . . . . . . . . . . . . . . .
39
39
39
43
44
44
44
45
45
46
46
52
64
77
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
iii
Inhaltsverzeichnis
4
Einstieg in die Webentwicklung: Servlets und JSPs
4.1 Der Model-View-Controller: Klassen, Servlets und JSPs . . . . . . . .
4.2 Zusammenspiel zwischen Servlet und JSP . . . . . . . . . . . . . . . .
4.2.1 Versenden von Daten mit POST und GET . . . . . . . . . . .
4.3 Servlets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4.3.1 Allgemeines . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4.3.2 Methoden des Interface HttpServletRequest . . . . . . . . . . .
4.3.3 Die Methode sendRedirect() des Interface HttpServletResponse
4.3.4 Sessions und die zugehörigen Methoden . . . . . . . . . . . . .
4.3.5 Attribute, Parameter und Geltungsbereiche . . . . . . . . . . .
4.3.6 Ein praktisches Beispiel . . . . . . . . . . . . . . . . . . . . . .
4.4 JSP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4.4.1 Grundlagen . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4.4.2 Syntaxelemente in einem JSP . . . . . . . . . . . . . . . . . . .
4.5 Filter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4.6 Deployment Descriptor: web.xml . . . . . . . . . . . . . . . . . . . . .
4.7 Listener . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4.7.1 HttpSessionAttributeListener . . . . . . . . . . . . . . . . . . .
4.7.2 ServletContextListener . . . . . . . . . . . . . . . . . . . . . . .
4.7.3 HttpSessionListener . . . . . . . . . . . . . . . . . . . . . . . .
4.7.4 Weitere Listener . . . . . . . . . . . . . . . . . . . . . . . . . .
5
Webentwicklung mit Spring
6
Webentwicklung mit Grails
6.1 Model-View-Controller in Grails
7
Grundlagen der Datenbankabfragen mit JPA
7.1 Die 1:1-Beziehung . . . . . . . . . . . .
7.1.1 Die 1:1-Beziehung in db4o . . . .
7.1.2 Die 1:1-Beziehung in Hibernate .
7.1.3 Die 1:1-Beziehung in Spring . . .
7.1.4 Die 1:1-Beziehung in Grails . . .
7.2 Vererbungsbeziehungen . . . . . . . . .
7.2.1 Die Vererbungsbeziehung in db4o
7.2.2 Die 1:n-Beziehung . . . . . . . .
8
iv
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
83
83
84
90
90
90
92
92
93
94
94
98
98
108
123
128
131
131
133
133
134
137
139
. . . . . . . . . . . . . . . . . . . . . . . . . . . 139
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
Fortgeschrittenes Wissen über Abfragen in db4o
8.1 Native Queries . . . . . . . . . . . . . . . . . . . . . . .
8.1.1 Speichern und Auslesen von Objekten der Klasse
8.1.2 Bedingungen formulieren . . . . . . . . . . . . .
8.1.3 Sortieren . . . . . . . . . . . . . . . . . . . . . .
8.2 S.O.D.A. . . . . . . . . . . . . . . . . . . . . . . . . . . .
8.2.1 Bedingungen formulieren . . . . . . . . . . . . .
8.2.2 Sortieren . . . . . . . . . . . . . . . . . . . . . .
8.2.3 Direkter Zugriff auf Elemente einer ArrayList . .
8.3 Query-by-Example . . . . . . . . . . . . . . . . . . . . .
8.3.1 Zugriff auf Elemente einer ArrayList . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
141
141
141
150
154
157
161
161
167
. . . . .
WebSite
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
191
191
191
197
206
210
212
221
222
224
224
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
Inhaltsverzeichnis
9
Client-Server-Modus in db4o
227
9.1 Netzwerkmodus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 227
9.2 Embedded-Modus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 232
9.3 Out-of-Band-Signalling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 233
10 Transaktionen
10.1 Theoretische Grundlagen . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
10.1.1 ACID . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
10.1.2 Isolationsstufen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
10.2 Transaktionen in db4o . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
10.2.1 Transaktionen und die Methode refresh() . . . . . . . . . . . . . . . .
10.3 Transaktionen und Sessions in Hibernate . . . . . . . . . . . . . . . . . . . . .
10.3.1 JTA und JDBC-Transaktionen . . . . . . . . . . . . . . . . . . . . . .
10.3.2 Transaktionsgrenzen bei den JDBC-Transaktionen . . . . . . . . . . .
10.4 Persistenzlebenszyklus in Hibernate . . . . . . . . . . . . . . . . . . . . . . . .
10.4.1 Die Zustände transient, persistent, detached und removed . . . . . . .
10.5 Lockingprozesse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
10.5.1 Theoretische Grundlagen: Optimistisches und Pessimistisches Sperren
10.5.2 Lockingprozesse in db4o: Semaphores . . . . . . . . . . . . . . . . . .
10.5.3 Optimistisches Sperren in Hibernate . . . . . . . . . . . . . . . . . . .
10.5.4 Lockingprozesse in Projekten und Performance . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
237
237
237
237
238
244
245
245
245
249
249
250
250
251
256
262
11 Abfragen in Hibernate
11.1 Hibernate Query Language (HQL) . . . . . . . . . . . . . . . . . . .
11.1.1 Wichtige Methoden . . . . . . . . . . . . . . . . . . . . . . .
11.1.2 Verwendung von Aliasen . . . . . . . . . . . . . . . . . . . . .
11.1.3 Parameter-Binding . . . . . . . . . . . . . . . . . . . . . . . .
11.1.4 Exkurs: Kaskadierende Beziehungen . . . . . . . . . . . . . .
11.1.5 Ausdrücke und Operatoren in HQL: Bedingungen formulieren
11.2 Named Queries . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
11.2.1 Erstellen einer Named Query mit Annotations . . . . . . . .
11.2.2 Zugreifen auf eine NamedQuery . . . . . . . . . . . . . . . . .
11.3 Criteria Queries . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
11.4 Query by example . . . . . . . . . . . . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
265
265
265
265
266
267
268
275
275
276
276
276
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
12 Abfragen in Grails
13 LazyLoading in Hibernate und Aktivierungstiefe in db4o
13.1 Aktivierungstiefe, Tiefe Objektgraphen und Lazy Loading
13.2 Aktivierungstiefe in db4o . . . . . . . . . . . . . . . . . .
13.2.1 Aktivierungstiefe und die LinkedList . . . . . . . .
13.2.2 Update-Tiefe . . . . . . . . . . . . . . . . . . . . .
13.2.3 Transparente Aktivierung . . . . . . . . . . . . . .
13.3 Lazy Loading in Hibernate . . . . . . . . . . . . . . . . .
277
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
279
279
279
282
283
283
283
14 Besonderheiten bei Webprojekten mit db4o
297
14.1 Die Methode refresh() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 304
14.2 Update-Tiefe im Embedded Modus . . . . . . . . . . . . . . . . . . . . . . . . . . 305
v
Inhaltsverzeichnis
15 Anhang A: Threads und Multithreading
15.1 Erstellung eines Threads . . . . . . . . .
15.1.1 Mithilfe des Runnable Interfaces
15.1.2 Mithilfe der Klasse Thread . . .
15.2 Synchronisation . . . . . . . . . . . . . .
15.2.1 Synchronisierte Blöcke . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
313
313
313
314
315
315
16 Anhang B: Eine kurze Einführung in Groovy
16.1 Einführung . . . . . . . . . . . . . . . . . . . . . . . . .
16.1.1 Groovy-Klassen in NetBeans-Projekten . . . . .
16.1.2 Was kann in Groovy weggelassen werden? . . . .
16.2 Klassen . . . . . . . . . . . . . . . . . . . . . . . . . . .
16.3 Dynamische Typisierung . . . . . . . . . . . . . . . . . .
16.4 Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . .
16.5 Das Collections-Framework . . . . . . . . . . . . . . . .
16.5.1 Listen . . . . . . . . . . . . . . . . . . . . . . . .
16.5.2 Maps . . . . . . . . . . . . . . . . . . . . . . . .
16.6 Closures . . . . . . . . . . . . . . . . . . . . . . . . . . .
16.7 Reguläre Ausdrücke . . . . . . . . . . . . . . . . . . . .
16.8 Ein- und Ausgabe von Dateien . . . . . . . . . . . . . .
16.9 Groovy und XML . . . . . . . . . . . . . . . . . . . . . .
16.9.1 Eine einfache XML-Datei . . . . . . . . . . . . .
16.9.2 Auslesen von Werten aus einer XML-Datei . . .
16.9.3 XML-Schema . . . . . . . . . . . . . . . . . . . .
16.9.4 Reguläre Ausdrücke in einer XML-Schema-Datei
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
319
319
319
321
321
323
324
324
325
326
327
330
332
333
334
336
338
341
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
17 Anhang
347
17.1 Literaturverzeichnis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 347
17.2 Informative Quellen im Netz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 347
vi
Vorwort zur ersten Auflage
Echte Innovationen kommen erst einmal auf leisen Füßen daher. So auch db4o, die Datenbank,
die das Leben von Java und .NET Programmierern so viel einfacher macht. Die macht keine
großartige Werbung oder ist auf großen und teuren Messeständen vertreten, sondern vor allem
im Herzen der Entwickler, die die Datenbankmaschine kostenlos unter der GPL OpensourceLizenz heruntergeladen haben, ausprobiert haben, und nicht mehr zurückblicken wollen.
Denn in moderner, objektorientierter Softwareprogrammierung wird, je nach Anwendung, 10-50%
der Zeit und des Geldes darauf verwendet, Objekte in inkompatible, relationale Datenbanktabellen hineinzupressen, oder - noch schlimmer - hausgemachte Persistenzlösungen basierend auf
flachen, proprietären Dateien zu hacken. Das ist nicht nur verwunderlich, da Daten abspeichern ja
eigentlich eine ganz grundsätzliche und einfache Anforderung ist, sondern vor allem unglaublich
ineffizient.
Das Problem ist dass sich irgendwann einmal in unseren Köpfen festgesetzt hat, dass eine Datenbank nun einfach mal relational strukturiert sein muss. Das ist genauso wahr wie die Überzeugung, dass die Erde flach ist. Man glaubt nicht an die Kugel - bis man sie dann mal gesehen
hat.
R der Hersteller der führenden relationalen Datenbank gibt zu: „Das Herstellen
Selbst Oracle,
von Java-Anwendungen mit relationalen Datenbanken ist wahrscheinlich die am meisten unterschätzte Herausforderung in der heutigen Unternehmenssoftware-Entwicklung. Mehr und mehr
Projekte sind verspätet, haben fehlende Funktionen oder sind schwer zu warten wegen dieser
Fehleinschätzung. Das Problem liegt in der Anwendung grundsätzlich verschiedener Technologien. Die Objektwelt und die relationale Welt passen nicht zusammen.“ 1
Computer-Vordenkerin Esther Dyson hat das noch pointierter ausgedrückt: „Tabellen zu verwenden um Objekte abzuspeichern - das ist wie wenn Sie Ihr Auto jeden Abend auseinanderbauen
um es in die Garage zu bringen. Sie können es am nächsten morgen wieder zusammenschrauben,
aber irgendwann fragt man sich dann doch, ob das die effizienteste Art ist, ein Auto zu parken. “
Durch db4o ist mit diesem Unsinn Schluß: Objekte in der Applikation sind Objekte in der Datenbank. Ein einheitliches Schema ersetzt den freundlich „dual“ genannten Ansatz, der schlicht eine
massive Redundanz und riesige Synchronisationskosten bedeutet. Obwohl es sicher sinnvolle Anwendungen für relationale Datenbanken und den dualen Ansatz gibt, gibt es doch viele Gebiete,
wo sich die massiven Effizienzsteigerungen durch db4o direkt in Wettbewerbsfähigkeit, bessere
Produkte, weniger Kosten- und Terminverzögerung sowie besser wartbare und wiederverwendbarere Softwarekomponenten umsetzen lassen.
Ina Brenner hat sich in die Fangemeinde von db4o eingereiht, die nach 1 Millionen Downloads
schon weit über 20,000 registrierte Entwickler in ihrer Community (http:\\developer.db4o.com)
zählt, und jetzt ein neues, wunderbares Buch verfasst, das die ganz praktische Anwendung von
db4o, Schritt für Schritt, zeigt und so hoffentlich wieder viele neue Freunde fuer diese revolutionäre Technologie rekrutiert.
1
„Building Java applications that use relational databases is perhaps the single most underestimated challenge
in enterprise development today. More projects are delayed, under-featured and difficult to maintain because
of this underestimation. The problem lies with the use of fundamentally different technologies. The object
world and the relational world do not match up.“ , Oracle Corporation, An Oracle Whitepaper, September
2005,http:\\www.oracle.com\technology \products\ias\toplink\technical \TopLink_WP.pdf
vii
Vorwort zur ersten Auflage
Dass das Buch in deutsch verfasst ist, ist besonders bemerkenswert, da db4o in Deutschland
konzipiert wurde und dort besonders populär ist, auch wenn die dazugehörige Firma, db4objects,
Inc., im kalifornischen Silicon Valley zuhause ist und mit Kunden, Anwendern und Mitarbeitern
rund um den Globus zusammenarbeitet.
Wir wünschen dem Leser viel Spaß beim Entdecken, warnen aber schon vorab, dass db4o süchtig
macht: Sie werden nie mehr zurück gehen wollen in die Zeit, wo Sie sich den Kopf über das
Speichern von Objekten zerbrechen müssen, sondern sich einfach um Ihre tatsächliche Applikationsdomäne kümmern können!
Ihr
Christof Wittig CEO db4objects, Inc.
viii
Danksagung
Es ist mir ein Vergnügen, alle Personen zu würdigen, die in den verschiedenen Phasen des Schreibens mir zur Seite gestanden sind und mit Ihrem Engagement sehr zum Erfolg dieses Buches
beigetragen haben:
Meinen Dank gilt Herrn Burbiel, dessen Vorschläge mir von großem fachlichen Nutzen waren.
Sein Fachwissen hat zur Qualität des vorliegenden Buches beigetragen.
Außerdem bin Herrn Prof. Dr. Stefan Edlich und Majk Jablonski zu Dank verpflichtet, die mir
mit Verbesserungsvorschlägen zur Seite gestanden sind.
Besonders erwähnen möchte ich die Unterstützung durch db4o, insbesondere Herrn Christof
Wittig, der mit seinem Engagement reges Interesse an meinem Buch gezeigt hat und mich in
allen Phasen unterstützt hat.
Und last but not least, bedanke ich mich bei dem englischsprachigen db4o-Forum.
ix
1 Einleitung
Das folgende PDF ist der erste Entwurf für die zweite Auflage. Der Arbeitstitel lautet: Java
Persistence mit db4o und Hibernate.
1
2 Installation und Konfiguration
2.1 Das erste J2EE-Projekt in NetBeans
Beginnen wir mit dem ersten Schritt auf dem Weg zu unserem Content-Management-System.
Wir erstellen unser erstes Projekt in NetBeans, ein Web-Projekt, das das Grundgerüst darstellt
für all unsere Dateien. Genauere Informationen zu der Ordnerstruktur in Web-Projekte finden
Sie weiter hinten im Kapitel Einstieg in J2EE.
Gehen Sie bitte wie folgt vor:
1. Öffnen Sie NetBeans.
2. Klicken Sie im Menü auf File und dann auf New Project.
Abbildung 2.1: Neues Projekt
3. Wählen Sie Web und auf der rechten Seite Web Application aus und gehen Sie mit Next
weiter zum nächsten Schritt.
3
2 Installation und Konfiguration
Abbildung 2.2: 1.Schritt: Choose Project
4. Vergeben Sie im 2. Schritt einen Namen und legen Sie einen Ordner fest, in dem Ihr Projekt
abgespeichert werden soll.
5. Im 3. Schritt wählen Sie Tomcat als Server aus:
Abbildung 2.3: 3.Schritt: Server auswählen
6. Im nächsten Schritt besteht die Möglichkeit, Frameworks einzubinden, im Falle von Hibernate bliebe Ihnen die Erstllung der Hibernate.cfg.xml, die Sie weiter unten finden können,
erspart.
4
2.2 Installation von db4o: Einbinden einer Bibliothek
Abbildung 2.4: 3.Schritt: Auswahl Frameworks
7. Klicken Sie auf Finish.
NetBeans hat Ihnen auf diesem Weg bereits die gesamte Ordnerstruktur des Web-Projektes vorgegeben, die Sie jetzt nur noch mit Inhalt füllen müssen. Struts und Java Server Faces, sind
Frameworks, die zusätzliche Funktionalitäten zur Verfügung stellen, Web-Projekte zu vereinfachen und zu optimieren. Der im Kapitel Einstieg in J2EE vorgestellte Model-View-Controller
stellt die Basis von Struts dar.
Wollen Sie ein ganzes, bereits existierendes Projekt verwenden, müssen Sie im Menü File, Open
Project nehmen und nicht den Unterpunkt New Project.
2.2 Installation von db4o: Einbinden einer Bibliothek
Machen wir weiter mit dem nächsten Schritt: Wir installieren db4o. Schon hier wird der Unterschied zu einer relationalen Datenbank deutlich: Beim Installationsvorgang wird nur die db4oBibliothek zu einem Projekt hinzugefügt und schon können Sie alle Funktionalitäten von db4o
nutzen. Es wird nur - wie bei einem Framework - eine Bibliothek eingebunden.
Folgende Schritte sind notwendig, um die db4o-Bibliothek einzubinden:
1. Laden Sie die neueste Version db4o von der Website www.db4o.de herunter und entzippen
Sie sie in einem Verzeichnis Ihrer Wahl. Im Unterverzeichnis lib befinden sich die Bibliotheken für die jeweiligen Java-Versionen, wobei 5 sowohl 5 als auch 6 unterstützt. Wir nehmen
db4o-7.12.156.14667-all-java5, da dort alle Einzelbibliotheken zu einer zusammgefasst wurden.
2. Erstellen Sie im Ordner web/WEB-INF ein Verzeichnis lib, in das Sie die db4o-Bibliothek
kopieren.
3. Sie müssen zusätzlich die Bibliothek dem NetBeans-Projekt hinzufügen: Klicken Sie das
Projekt mit der rechten Maustaste an, wählen Sie anschließend Properties aus.
4. Bibliotheken in ein Projekt einbinden
5
2 Installation und Konfiguration
Abbildung 2.5: Properties
5. Sie gelangen zu einem Fenster, das Ihnen auf der linken Seite verschiedene Kategorien
vorgibt, klicken Sie Libraries an und dann den Button Add JAR/Folder.
Abbildung 2.6: Bibliotheken (Libraries)
War das nicht einfach? Eine Datenbankinstallation in 5 Minuten. Keine komplizierten Installationsschritte, einfach nur eine Bibliothek hinzufügen und schon können Sie loslegen. Haben
Sie alles richtig gemacht, erscheint die db4o-Bibliothek in dem Ordner Libraries in der Ansicht
Projects.
2.2.1 Erste Datenbankabfragen in db4o
Wichtige Klassen und Interfaces
Das Package com.db4o
Wo finden Sie die wichtigsten Methoden für unsere ersten Datenbankabfragen? Im Package
com.db4o in den Klassen ObjectContainer und Db4oEmbedded.
6
2.2 Installation von db4o: Einbinden einer Bibliothek
Im Moment wollen wir uns auf die Methoden des so genannten Solo-Modus konzentrieren und
uns erst später mit dem Client-Server-Modus beschäftigen. Das Interface ObjectContainer stellt
Ihnen die passenden Methoden für einfache Datenbankabfragen sowohl im Stand-Alone-Modus
als auch im Client-Server-Modus zur Verfügung. So enthält es z. B. folgende Methoden:
1. store(): speichert ein Objekt
2. queryByExample(): liest ein Objekt wieder aus
3. queryByExample() und store(): zuerst wird das zu verändernde Objekt ausgelesen und
anschließend wird es geändert
4. queryByExample() und delete(): liest Objekt aus und löscht es
5. close(): schließt die Datenbank wieder
In der Klasse Db4oEmbedded gibt es die Methode openFile(), die die Datenbank öffnet und der
Sie den Namen der Datenbank als Parameter übergeben.
Ein ObjectSet stellt eine Aufzählung aller Abfrageergebnisse dar, ähnlich einer ArrayList, das
mit den Methoden hasNext() und next() ausgelesen wird.
Das Interface ExtObjectContainer
Ein Subinterface des Interfaces ObjectContainer ist das Interface ExtObjectContainer, das sich
im Package com.db4o.ext befindet. Dieses Interface stellt ergänzende Methoden zum Interface
ObjectContainer zur Verfügung. So gibt es folgende wichtige Methode:
getID ( j a v a . l a n g . O b j e c t o b j )
die long als Rückgabetyp hat und den OID zurückgibt. Der OID ist eine interne Zahl, die es
innerhalb der Datenbank nur einmal gibt und die jedem Objekt zugewiesen wird.
Befinden Sie sich in einem ObjectContainer, müssen Sie zuerst einen Cast durchführen, der einen
ObjectContainer in einen ExtObjectContainer verwandelt. Dies geschieht mit der Methode ext().
Die gesamte Befehlszeile lautet wie folgt:
l o n g i d = o b j e c t C o n t a i n e r . e x t ( ) . getID ( j a v a . l a n g . O b j e c t o b j )
Datenbankabfragen
Speichern von Objekten
Wir erstellen ein Objekt FormatierungEinfach, das nur zwei Variablen enthält: einen Namen und
eine Id. Wie wird unser soeben erstelltes Objekt gespeichert? Zuerst muss ein ObjectContainer
und die zugehörige Datenbank geöffnet werden. Sollte die Datenbank noch nicht existieren, wird
automatisch eine erstellt. Die Datenbank erhält die Endung .yap, die für "yet another protocol"
steht. Der Name wurde nicht vergeben, weil er eine bestimmte Bedeutung besitzt, sondern weil es
ihn noch nicht gibt – soweit bekannt. Da die Datenbankverbindung unterbrochen werden kann,
solange Daten gespeichert werden, muss dieser Vorgang in einem try-catch-finally-Block stehen.
Dieser macht es möglich, Probleme aufzufangen, ohne dass das Programm abgebrochen wird.
Der try-catch-finally-Block besteht aus drei Teilen: Im try-Block steht die Methode, die ein
Objekt in der Datenbank speichert, nämlich store(); es wird also „versucht“ das Objekt in der
Datenbank zu speichern. Der catch-Block gibt im Falle eines Problems eine Fehlermeldung aus.
Ein eventueller Fehler wird also „aufgefangen“. Zum Schluss wird der finally-Block auf jeden Fall
durchgeführt und schließt die Datenbank wieder.
7
2 Installation und Konfiguration
package db4oFirstQueries;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import Datenbankentwurf.FormatierungEinfach;
import com.db4o.Db4oEmbedded;
import com.db4o.ObjectContainer;
public class FormatierungEinfachDatenSpeichern {
public void speichern(FormatierungEinfach formatierung){
//Oeffnen der Datenbank
ObjectContainer db = Db4oEmbedded.openFile
("C:/Datenbank/DasErsteProjekt/datenbank.yap");
/*Es wird versucht (try) der Datenbank das Objekt f
*hinzufuegen (store),*/
try {
db.store(formatierung);
/*und sollte es Probleme beim Oeffnen der Datenbank
oder beim Abspeichern des Objektes geben,
wird eine Exception (DatabaseFileLockedException)
geworfen; sie wird im catch-Block aufgefangen
und es kommt zur Ausgabe einer Fehlermeldung.*/
} catch (Exception e) {
e.printStackTrace();
/*Der finally-Block wird auf jeden Fall durchgefuehrt,
so wird die Datenbank wieder geschlossen (close).*/
} finally{
db.close();
}
}
}
Listing 2.1: Speichern eines Formatierungsobjektes
Kommen wir wieder zu SQL zurück und schauen uns an, wie Formatierungen in einer relationalen Datenbank gespeichert werden. Eine neue CSS-Formatierung wird mit dem INSERT-Befehl
gespeichert und er lautet:
INSERT INTO F o r m a t i e r u n g E i n f a c h VALUES( 1 , " f o n t 1 " )
Auslesen von Objekten mit Query-by-Example
Wie wird das gespeicherte Objekt wieder ausgelesen? Mit einem Beispielobjekt und der Methode queryByExample(). Dieser Vorgang nennt sich Query-by-Example und stellt eines der
Abfragekonzepte in db4o dar (vgl. Kapitel Abfragekonzepte). In unserem Falle wollen wir alle
8
2.2 Installation von db4o: Einbinden einer Bibliothek
FormatierungEinfach-Objekte aus der Datenbank auslesen: Wir erstellen ein Beispielobjekt, indem wir dem Konstruktor den Default-Wert (Standardwert) eines Objektes übergeben, nämlich
null. So lautet die entsprechende Befehlszeile:
F o r m a t i e r u n g E i n f a c h f o = new F o r m a t i e r u n g E i n f a c h ( n u l l ) ;
Würde die Klasse statt einem String einen primitiven Datentyp enthalten, wie z. B. eine Variable vom Datentyp int, ist der Default-Wert 0 und dieser müsste dem Beispielobjekt übergeben
werden. Bei den primitiven Datentypen float lautet der Standardwert 0.0F oder bei double 0.0D.
Wir erhalten als Ergebnis der Abfrage ein Objekt vom Typ ObjectSet zurück, das alle vorhandenen FormatierungEinfach-Objekte enthält. Wir legen das ObjectSet als ein generisches ObjectSet fest (ObjectSet<FormatierungEinfach>), so werden beim Auslesen FormatierungEinfachObjekte zurückgegeben und keine Objekte vom Typ Object. Die Methode hasNext() stellt fest,
ob das ObjectSet noch weitere Objekte enthält und die Methode next() liest die Objekte aus.
Alle FormatierungEinfach-Objekte werden anschließend einer generischen ArrayList hinzugefügt
und die ArrayList wird von der Methode auslesen() zurückgegeben.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package db4oFirstQueries;
import
import
import
import
import
Datenbankentwurf.FormatierungEinfach;
com.db4o.Db4oEmbedded;
com.db4o.ObjectContainer;
com.db4o.ObjectSet;
java.util.ArrayList;
public class FormatierungEinfachDatenAuslesen {
/*Die Methode auslesen() gibt eine generische ArrayList zurueck. */
public ArrayList<FormatierungEinfach> auslesen(){
ObjectContainer db = Db4oEmbedded.openFile
("C:/Datenbank/DasErsteProjekt/datenbank.yap");
/*Instanziieren der generischen ArrayList, die Objekte der Klasse
FormatierungEinfach ein- und ausliest.*/
ArrayList<FormatierungEinfach> formatierungList =
new ArrayList<FormatierungEinfach>();
try {
FormatierungEinfach formatierungAusgelesen = new FormatierungEinfach(null);
/*Die Methode get() liest die FormatierungEinfach-Objekte aus und gibt ein
ObjectSet zurueck, das alle FormatierungEinfach-Objekte enthaelt.*/
ObjectSet<FormatierungEinfach> result =
db.queryByExample(formatierungAusgelesen);
/*hasNext() stellt fest, ob das ObjectSet noch weitere Elemente enthaelt*/
while (result.hasNext()){
9
2 Installation und Konfiguration
/*next() gibt die FormatierungEinfach-Objekte aus*/
formatierungAusgelesen = result.next();
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/*Das FormatierungEinfach-Objekt, das aus der Datenbank
ausgelesen wurde, wird der generischen ArrayList hinzugefuegt.*/
formatierungList.add(formatierungAusgelesen);
}
} catch (Exception e) {
e.printStackTrace();
} finally{
db.close();
}
/*Die Methode auslesen() gibt die ArrayList f zurueck.*/
return formatierungList;
}
}
Listing 2.2: Auslesen von Formatierungsobjekten
Die Methode auslesen() gibt eine generische ArrayList zurück. Eine ArrayList ist eine Liste, die
mehrere Elemente beinhalten kann. Sie ist in der Regel die beste Implementierung aller Listen, da
sie einen sehr schnelle wahlfreien Zugriff ermöglicht und auf einem Array basiert, das dynamisch
schrumpft und wächst. Die Elemente einer Liste haben wie bei einem Array Felder und einen
Index. Die Reihenfolge der Ausgabe der Elemente ist identisch mit der Reihenfolge der Eingabe.
Generics wurden in Java mit 5.0 eingeführt und bedeuten eine sinnvolle Erweiterungen, z. B. der
Funktionen einer ArrayList. Generisch bedeutet, eine ArrayList, der nur FormatierungEinfachObjekt hinzugefügt werden können, gibt auch nur FormatierungEinfach-Objekte zurück und
keine Objekte der Klasse Object. Eine generische ArrayList erspart Ihnen also das nachträgliche
Casten von Objekten der Klasse Object in Objekte der Klasse FormatierungEinfach, da sie bereits
Objekte der Klasse FormatierungEinfach zurückgibt.
Ziehen wir wieder den direkten Vergleich zu SQL: Alle CSS-Formatierungen werden mit dem
SELECT-Befehl aus einer relationalen Datenbank ausgelesen:
SELECT ∗ FROM F o r m a t i e r u n g E i n f a c h
Es werden nicht immer alle FormatierungEinfach-Objekte benötigt, sondern nur Bestimmte.
Wie können Sie aus db4o ein genau definiertes Objekt auslesen? Instanziieren Sie ein Objekt mit
einem eindeutigen Wert: in unserem Fall mit der Variablen name. Wir erstellen also wieder ein
Beispielobjekt:
F o r m a t i e r u n g E i n f a c h p = new F o r m a t i e r u n g E i n f a c h ( name ) ;
Hier die dazu passende vollständige Methode und Klasse:
package db4oFirstQueries;
1
2
3
4
import Datenbankentwurf.FormatierungEinfach;
import com.db4o.Db4oEmbedded;
10
2.2 Installation von db4o: Einbinden einer Bibliothek
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import com.db4o.ObjectContainer;
import com.db4o.ObjectSet;
public class FormatierungEinfachDatenAuslesenEinzeln {
public FormatierungEinfach auslesenEinzeln(String name){
ObjectContainer db = Db4oEmbedded.openFile
("C:/Datenbank/DasErsteProjekt/datenbank.yap");
FormatierungEinfach formatierungAusgelesen = new FormatierungEinfach();
try {
FormatierungEinfach formatierung = new FormatierungEinfach(name);
ObjectSet<FormatierungEinfach> result = db.queryByExample(formatierung);
formatierungAusgelesen = result.next();
} catch (Exception e) {
e.printStackTrace();
} finally{
db.close();
}
return formatierungAusgelesen;
}
}
Listing 2.3: FormatierungEinfachDatenAuslesenEinzeln.java
In SQL ist dies natürlich auch möglich. Es wird eine bestimmte CSS-Formatierung, wie z. B.
font1, mit dem SQL-Befehl SELECT folgendermaßen ausgelesen:
SELECT ∗ FROM F o r m a t i e r u n g E i n f a c h WHERE name = " f o n t 1 "
Löschen von Objekten
Wir wollen die Objekte, die wir gespeichert haben, wieder löschen. Die entsprechende Methode
in db4o heißt delete(). Sie übergeben ihr das Objekt als Parameter, das Sie löschen wollen. Bevor
Sie ein Objekt löschen können, müssen Sie es aus der Datenbank auslesen.
1
2
3
4
5
6
7
8
9
package db4oFirstQueries;
import
import
import
import
Datenbankentwurf.FormatierungEinfach;
com.db4o.Db4oEmbedded;
com.db4o.ObjectContainer;
com.db4o.ObjectSet;
public class FormatierungEinfachDatenLoeschen {
11
2 Installation und Konfiguration
public void loeschen(FormatierungEinfach formatierung){
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
ObjectContainer db = Db4oEmbedded.openFile
("C:/Datenbank/DasErsteProjekt/datenbank.yap");
try {
/*Das zu loeschende Objekt muss zuerst aus der
Datenbank ausgelesen werden.*/
ObjectSet<FormatierungEinfach> result = db.queryByExample(formatierung);
FormatierungEinfach formatierungAusgelesen = result.next();
/*Die Methode delete() loescht Objekte wieder.*/
db.delete(formatierungAusgelesen);
} catch (Exception e) {
e.printStackTrace();
} finally{
db.close();
}
}
}
Listing 2.4: Löschen eines bestimmten Objektes
In SQL heißt der Befehl, der einen bestimmten Datensatz löscht, ebenfalls DELETE:
DELETE FROM F o r m a t i e r u n g E i n f a c h WHERE name = " f o n t 1 "
Ändern von Objekten
Last, but not least, ändern wir ein Objekt. Wie geschieht dies? Sie müssen zuerst das entsprechende Objekt auslesen und dann mit der Methode setName() den Namen des Objektes ändern
und anschließend müssen Sie es nochmals speichern. Wichtig: Diese beiden Vorgänge müssen
durchgeführt werden, solange die Datenbank offen ist. Wollen Sie ein Objekt ändern, so benötigen Sie zwei Methoden. Sie stellen eine Einheit dar. Sie dürfen nicht zuerst die oben stehenden
Methoden auslesenEinzeln() durchführen und dann die Methode speichern(), da dies dazu führt,
dass das FormatierungEinfach-Objekt ein weiteres Mal abgespeichert wird. Auf diese Art und
Weise wird nicht dieses bestimmte Objekt verändert, sondern ein Neues erstellt.
package db4oFirstQueries;
1
2
3
4
5
6
7
8
9
10
import
import
import
import
Datenbankentwurf.FormatierungEinfach;
com.db4o.Db4oEmbedded;
com.db4o.ObjectContainer;
com.db4o.ObjectSet;
public class FormatierungEinfachDatenAendern {
public void aendern
12
2.2 Installation von db4o: Einbinden einer Bibliothek
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
(FormatierungEinfach formatierung,
FormatierungEinfach formatierungNeu){
ObjectContainer db = Db4oEmbedded.openFile
("C:/Datenbank/DasErsteProjekt/datenbank.yap");
try {
/*Das zu veraendernde Objekt wird aus der Datenbank mit der
Methode get() ausgelesen.*/
ObjectSet<FormatierungEinfach> result = db.queryByExample(formatierung);
FormatierungEinfach formatierungAusgelesen = result.next();
String name = formatierungNeu.getName();
formatierungAusgelesen.setName(name);
/*Das geaenderte Objekt wird wieder mit der Methode set() gespeichert.*/
db.store(formatierungAusgelesen);
} catch (Exception e) {
e.printStackTrace();
} finally{
db.close();
}
}
}
Listing 2.5: Ändern eines bestimmten Objektes
Der entsprechende Befehl in SQL unterscheidet sich vom Prinzip her von der obigen Vorgehensweise. Die Methoden queryByExample() und store() entsprechen in SQL einem Befehl. Es wird
in einem Schritt, der zu verändernde Datensatz gesucht und verändert.
UPDATE F o r m a t i e r u n g E i n f a c h SET name = " f o n t 2 " WHERE name = " f o n t 1 "
Praktische Anwendungsbeispiele
Bis jetzt haben wir die entsprechenden Abfragen für unsere Klasse FormatierungEinfach kennen gelernt, lassen Sie uns diese ausprobieren. Zuerst speichern wir zwei FormatierungEinfachObjekte:
1
2
3
4
5
6
7
8
9
10
package db4oFirstQueries;
import Datenbankentwurf.FormatierungEinfach;
public class Einlesen {
public static void main(String[] args){
FormatierungEinfachDatenSpeichern fd = new FormatierungEinfachDatenSpeichern();
FormatierungEinfach f = new FormatierungEinfach("font1");
13
2 Installation und Konfiguration
FormatierungEinfach fo = new FormatierungEinfach("font2");
fd.speichern(f);
fd.speichern(fo);
11
12
13
14
15
16
}
}
Listing 2.6: Einlesen.java
Tipp: Eine Java-Klasse können Sie starten, indem Sie mit der rechten Maustaste in die JavaKlasse klicken und Run File auswählen. Der entsprechende Short-Cut lautet Umschalt + F6.
Die Datenbank dantenbank.yap wird hierbei im Verzeichnis C:Datenbank/DasErsteProjekt angelegt. Wenn Sie die Datenbank öffnen, werden Sie feststellen, dass Sie nicht allzu viel mit dem
Inhalt anfangen können, da die Objekte als binäre Daten gespeichert werden.
Abbildung 2.7: Unser Datenbank datenbank.yap
Anschließend lesen wir alle Objekte wieder aus der Datenbank aus. Werden tatsächlich beide
Elemente wieder ausgelesen? Ja, beide CSS-Formatierungen erscheinen auf der Konsole.
package db4oFirstQueries;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import Datenbankentwurf.FormatierungEinfach;
import java.util.ArrayList;
public class Auslesen {
public static void main(String[] args){
FormatierungEinfachDatenAuslesen fda = new FormatierungEinfachDatenAuslesen();
/*Die Methode auslesen() gibt eine generische ArrayList zurueck,*/
ArrayList<FormatierungEinfach> al = fda.auslesen();
/*aus der direkt Objekte der Klasse FormatierungEinfach
mit der erweiterten for-Schleife ausgelesen werden.*/
for(FormatierungEinfach fo: al){
System.out.println("Auslesen Formatierung: "+fo.getName());
}
}
}
14
2.3 Voraussetzungen für die Nutzung der Java Persistenz API mit Hibernate und MySQL
Listing 2.7: Auslesen.java
Ausgabe:
Auslesen Formatierung: font2
Auslesen Formatierung: font1
2.3 Voraussetzungen für die Nutzung der Java Persistenz API mit
Hibernate und MySQL
Sollten Sie z.B. mit db4o eine Laptopanwendung oder eine Anwendung für ein Mobiltelefon für
einen Außendienstmitarbeiter erstellen, die abends mit einer zentralen Anwendung synchronisiert
werden soll, so bietet sich das Replikationsmodul dRS an. Sie können Ihre Daten mit einer
relationalen Datenbank mithilfe von Hibernate und dRS abgleichen.
Ich werde Hibernate als Persistenzprovider für die Java Persistenz API verwenden. Die JPA ist
der Persistenzstandard und Hibernate ist ein Persistenzprovider, der zusätzliche Funktionalitäten zur Verfügung stellt, wie z. B. verschiedene Cache-Implementierungen, die die Performance
erhöhen. Die JPA gibt den Standard vor, wie Objekte auszusehen haben, die in einer relationalen
Datenbank gespeichert werden sollen. Die Java Persistenz API stellt aber nicht nur Regeln für
relationale Datenbanken auf, sondern auch für objektorientierte.
2.3.1 Installation von MySQL
Als Erstes laden Sie Folgendes von der Seite MySQL herunter:
1. MySQL Community Server: Der MySQL-Datenbankserver.
2. MySQL Workbench: Die Workbench ermöglicht es Ihnen Datenbanktabellen graphisch
darzustellen.
3. MySQL Connector/J: Datenbanktreiber für Java, der die entsprechenden Bibliotheken
für die Datenbankverbindung zu MySQL in Java zur Verfügung stellt.
Folgen Sie beim Server und der Workbench den Installationsanleitungen und legen Sie den MySQL Connector/J in ein Verzeichnis Ihrer Wahl.
Als Zweites legen wir in NetBeans eine Verbindung zu dem Datenbanktreiber MySQL Connector/J an und zu der Datenbank MySQL:
1. Klicken Sie links oben auf Services und dann auf Databases und Drivers.
Abbildung 2.8: Datenbanktreiber in NetBeans
15
2 Installation und Konfiguration
2. Klicken Sie mit der rechten Maustaste auf Drivers und anschließend auf New Driver.
Abbildung 2.9: Neuer Datenbanktreiber
3. Klicken Sie auf den Button Add und wählen Sie die Bibliothek mysql-connector-java-5.1.5bin.jar aus.
Abbildung 2.10: Neuer Datenbanktreiber
4. Und haben Sie jetzt alles richtig gemacht, erscheint jetzt links oben ein zusätzlicher Treiber.
Abbildung 2.11: Neuer Datenbanktreiber
5. Wir registrieren die Datenbank in NetBeans, indem wir mit der rechten Maustaste auf
Databases klicken:
16
2.3 Voraussetzungen für die Nutzung der Java Persistenz API mit Hibernate und MySQL
Abbildung 2.12: Registrieren von MySQL
6. Wir geben folgende Daten und als Passwort admin ein und klicken anschließend auf OK:
Abbildung 2.13: MySQL Server Properties
2.3.2 Erstellen einer Datenbankverbindung mit JNDI
Das Java Naming und Directory Interface, kurz JNDI genannt, ist ein Namens- und Verzeichnisservice, der es Ihnen ermöglicht z.B. eine Datenbank, an einer Stelle in der Webanwendung
einzurichten. So können Sie von überall in der Webanwendung darauf zugreifen. Sollte der Name
der Datenbank sich ändern, so müssen Sie diese Änderung nur noch an einer Stelle durchführen,
indem Sie die Konfiguration der url und den Namen der Datenbank ändern. Wie tun Sie das?
Ersetzen Sie den Namen nach dem letzten Schrägstrich (hier: book), durch den Namen einer
anderen Datenbank. Weiter unten werden wir sehen, dass Sie zum Aufrufen der Datenbankverbindung aus JNDI den InitialContext starten müssen.
In unten stehender context.xml wird der Name der Datenbankressource in JNDI mit "jdbc/book" festgelegt und die dazugehörigen Daten der Datenbankverbindung. Die context.xml muss
im Verzeichnis web/META-INF abgelegt werden. Die context.xml ist eine Datei, die von Tomcat
benötigt wird. Wozu benötigen wir Tomcat? Tomcat ermöglicht es uns Webseiten im Internet zu
veröffentlichen.
17
2 Installation und Konfiguration
<?xml version="1.0" encoding="UTF-8"?>
<Context path="/ErstesBeispielHibernate">
<Resource name="jdbc/book" auth="Container"
driverClassName="com.mysql.jdbc.Driver"
password="admin"
username="root"
type="javax.sql.DataSource"
url="jdbc:mysql://localhost:3306/book"/>
</Context>
1
2
3
4
5
6
7
8
9
10
Listing 2.8: context.xml
Die Datenbankressource muss ebenfalls in die web.xml eingetragen werden, die sich im Verzeichnis
web/WEB-INF befindet.
<resource-ref>
<res-ref-name>jdbc/book</res-ref-name>
<res-type>javax.sql.DataSource </res-type>
<res-auth>Container</res-auth>
</resource-ref>
1
2
3
4
5
Listing 2.9: Eintrag in die web.xml
2.3.3 Einrichten der MySQL-Datenbank
Wie machen wir eine Datenbank mit MySQL bekannt? Wir starten den MySQL QueryBrowser
und geben als Passwort admin ein:
Abbildung 2.14: Starten des MySQL QueryBrowsers
Wir nehmen folgendes einfaches Beispiel buch.sql einer Datenbank, die Bücher mit Titeln und
Autoren speichert:
18
2.3 Voraussetzungen für die Nutzung der Java Persistenz API mit Hibernate und MySQL
1
2
3
4
5
6
CREATE DATABASE book;
USE book;
CREATE TABLE Book(bookId INT AUTO_INCREMENT,
title varchar(50), author varchar(50), PRIMARY KEY(bookId));
Listing 2.10: book.sql
Wir öffnen die Datenbank buch.sql, indem wir auf Datei –> Skript öffnen ... klicken und die
entsprechende Datenbank auswählen und anschließend rechts oben auf den Button Ausführen
klicken.
Abbildung 2.15: Ausführen der Datenbank
MySQL hat für Sie ein neues Datenbankschema angelegt, das auf der rechten Seite zu den anderen
Schemata hinzugefügt wurde:
Abbildung 2.16: Datenbankschema
Wir haben ein neues Datenbankschema angelegt, das wir uns weiter unten noch genauer ansehen
werden.
Als Nächstes erstellen wir eine Verbindung zur Datenbank, um weiter unten eine Datenbankverbindung in der hibernate.cfg.xml anlegen zu können. Die Datenbank werden wir später mit
Inhalt füllen. Für diese Datenbank brauchen wir eine neue Connection zu MySQL:
1. Klicken Sie mit der rechten Maustaste auf Databases und wählen Sie New Connection aus:
19
2 Installation und Konfiguration
Abbildung 2.17: Erstellen einer neuen Verbindung zur Datenbank
2. Geben Sie folgende Einstellungen ein:
Abbildung 2.18: Grundeinstellungen für die Datenbankverbindung
Wir haben sowohl MySQL in NetBeans integriert als auch eine Verbindung zu buch.sql erstellt:
Wollen Sie MySQL starten oder die Verbindung zu einer bestimmten Datenbank herstellen,
können Sie dies jeweils tun, indem Sie die entsprechenden Symbole mit der rechten Maustaste
anklicken.
Wollen Sie allerdings eine leere Datenbank anlegen, können Sie dies wie folgt tun: Klicken Sie
in NetBeans mit der rechten Maustaste auf den MySQL-Server und anschließend auf Create
Database. Eine komplett leere Datenbank wird z.B. in Grails benötigt.
20
2.3 Voraussetzungen für die Nutzung der Java Persistenz API mit Hibernate und MySQL
Abbildung 2.19: Erstellen eines leeren Datenbankschemas
Für jedes neues Projekt, für das Sie MySQL benötigen, müssen Sie zusätzlich noch die Bibliothek
mysql-connector-java-5.1.5-bin hinzufügen.
2.3.4 Einrichten der Java Persistence API mit Hibernate
Um die Java Persistence API mit Hibernate 3.2.5 und Netbeans 6.9 verwenden zu können, erstellen wir - wie oben - ein Webprojekt und markieren im Schritt 4 das Framework Hibernate.
Außerdem wählen wir die soeben erstellte Database Connection zu der Datenbank bank.sql aus.
Abbildung 2.20: Auswahl des Frameworks Hibernate
Stand Juli 2010: Es muss für ein Hibernate-Projekt die Bibliothek asm.jar durch die asm-3.1.jar
ersetzt werden, da es ansonsten zu folgender Fehlermeldung kommen würde:
j a v a . l a n g . NoSuchMethodError :
o r g . o b j e c t w e b . asm . C l a s s W r i t e r .< i n i t >.
Erstellen Sie einen lib-Ordner im Verzeichnis web/WEB-INF und kopieren die neue Bibliothek
in diesen Ordner und fügen Sie anschließend die Bibliothek auch bei den Properties / Libraries
21
2 Installation und Konfiguration
hinzu. Die Bibliothek asm.jar muss aus den Properties / Libraries entfernt werden. Das gleiche
gilt für ein Spring-Projekt, wenn Sie Hibernate verwenden wollen.
Wir fügen eine weitere Bibliothek unserem Projekt hinzu, genauso wie wir es mit der db4oBibliothek getan haben. Es handelt sich um das log4j-Framework, das Ihnen bei Programmierfehlern ausführliche Hinweise über die Ursachen gibt. Das Framework ist Teil der Apache Software
Foundation und Sie können darüber nähere Informationen unter http://logging.apache.org/log4j/
finden. Der Logging-Vorgang funktioniert nur, wenn Sie Ihrem Projekt zusätzlich die log4j.propertiesDatei im Verzeichnis src/Java hinzufügen. Die Propertiesdatei finden Sie im Hibernate-CorePackage im Verzeichnis etc. Warum in das src/Java-Verzeichnis? Da dies standardmäßig in einem
NetBeans-Projekt als src-Verzeichnis für alle unkompilierten Java-Klassen festgelegt ist.
### direct log messages to stdout ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
### direct messages to file hibernate.log ###
#log4j.appender.file=org.apache.log4j.FileAppender
#log4j.appender.file.File=hibernate.log
#log4j.appender.file.layout=org.apache.log4j.PatternLayout
#log4j.appender.file.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
### set log levels - for more verbose logging change ’info’ to ’debug’ ###
log4j.rootLogger=warn, stdout
#log4j.logger.org.hibernate=info
log4j.logger.org.hibernate=debug
### log HQL query parser activity
#log4j.logger.org.hibernate.hql.ast.AST=debug
### log just the SQL
#log4j.logger.org.hibernate.SQL=debug
### log JDBC bind parameters ###
log4j.logger.org.hibernate.type=info
#log4j.logger.org.hibernate.type=debug
### log schema export/update ###
log4j.logger.org.hibernate.tool.hbm2ddl=debug
### log HQL parse trees
#log4j.logger.org.hibernate.hql=debug
### log cache activity ###
#log4j.logger.org.hibernate.cache=debug
22
2.3 Voraussetzungen für die Nutzung der Java Persistenz API mit Hibernate und MySQL
38
39
40
41
42
43
44
45
46
47
### log transaction activity
#log4j.logger.org.hibernate.transaction=debug
### log JDBC resource acquisition
#log4j.logger.org.hibernate.jdbc=debug
### enable the following line if you want to track down connection ###
### leakages when using DriverManagerConnectionProvider ###
#log4j.logger.org.hibernate.connection.DriverManagerConnectionProvider=trace
Listing 2.11: log4j.properties
Nachdem wir die Bibliotheken dem Projekt hinzugefügt haben, stellt sich folgende Frage: Wie teilen wir unserem Projekt mit, dass wir die Java Persistence API mit dem Persistenzprovider Hibernate verwenden wollen? Wir erstellen eine persistence.xml, die wir in das Verzeichnis META-INF
legen. Mit dem Element <provider> legen wir Hibernate als Persistenzprovider fest und mit dem
transaction-type="RESOURCE_LOCAL" legen wir eine JDBC-Transaktionsverwaltung fest. Den
Transaktionen sind weiter hinten ein separates Kapitel gewidmet.
1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
<persistence-unit name="ErstesBeispielHibernate" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<non-jta-data-source>jdbc/book</non-jta-data-source>
<properties/>
</persistence-unit>
</persistence>
Listing 2.12: persistence.xml
Jetzt müssen wir noch unsere Datenbank mit Hibernate bekannt machen und mit Hibernate eine
Datenbankverbindung herstellen. Da wir die Datenbank bereits als Ressource in JNDI angelegt
haben, müssen wir nur noch auf diese zugreifen. Dies wird in der so genannten hibernate.cfg.xml
konfiguriert, die in dem Verzeichnis src/Java abgelegt wird. Wir ergänzen die bereits existierende
hibernate.cfg.xml um einen Einträge. Wir legen ein Mapping für unser Beispielobjekt Buch an,
das wir später in der Datenbank speichern werden.
1
2
3
4
5
6
7
<?xml version=’1.0’ encoding=’utf-8’?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<property name="hibernate.dialect">
23
2 Installation und Konfiguration
org.hibernate.dialect.MySQLDialect
</property>
<property name="hibernate.connection.driver_class">
com.mysql.jdbc.Driver
</property>
<property name="hibernate.connection.url">
jdbc:mysql://localhost:3306/book
</property>
<property name="hibernate.connection.username">
root
</property>
<property name="hibernate.connection.password">
admin
</property>
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<!--Datenbankmapping fuer das Objekt Buch, das in der Datenbank
buch in der Tabelle Buch gespeichert werden soll-->
<mapping class="ErstesBeispielDomain.Book"/>
</session-factory>
</hibernate-configuration>
Listing 2.13: hibernate.cfg.xml
In unten stehender Klasse HibernateUtil.java, die ich der Hibernatedokumentation entnommen
habe, wird eine SessionFactory erstellt. Die SessionFactory öffnet eine Datenbankverbindung und
ermöglicht es Ihnen, Daten in die Datenbank ein- und wieder auszulesen. Der unten stehende
Code entspricht dem so genannten Singelton-Pattern, das zur Folge hat, dass es pro Anwendung
nur eine einzige SessionFactory gibt, die pro Request eine Session erzeugt.
package util;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import org.hibernate.*;
import org.hibernate.cfg.*;
public class HibernateUtil {
private static final SessionFactory sessionFactory;
static {
try {
// Create the SessionFactory from hibernate.cfg.xml
sessionFactory = new AnnotationConfiguration().configure("/hibernate.cfg.xml").
buildSessionFactory();
} catch (Throwable ex) {
// Make sure you log the exception, as it might be swallowed
24
2.3 Voraussetzungen für die Nutzung der Java Persistenz API mit Hibernate und MySQL
21
22
23
24
25
26
27
28
29
System.err.println("Initial SessionFactory creation failed." + ex);
throw new ExceptionInInitializerError(ex);
}
}
public static SessionFactory getSessionFactory() {
return sessionFactory;
}
}
Listing 2.14: HibernateUtil.java
2.3.5 Erstes Beispiel in Hibernate
Wir haben alle erforderlichen Konfigurationsdateien erstellt. Fahren wir mit einem ersten einführenden Beispiel fort. Wir erstellen unsere erste Entityklasse. Eine Klasse, die mit Hibernate
und JPA in der Datenbank gespeichert wird, muss mit @Entity gekennzeichnet werden. Der
Primärschlüssel erhält die Annotations @Id und @GeneratedValue. Mit der Strategie GenerationType.AUTO wird festgelegt, dass der Primärschlüssel automatisch hochgezählt wird.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package ErstesBeispielDomain;
import
import
import
import
import
java.io.Serializable;
javax.persistence.Entity;
javax.persistence.GeneratedValue;
javax.persistence.GenerationType;
javax.persistence.Id;
@Entity
public class Book implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer bookId;
private String title;
private String author;
public Integer getBookId() {
return bookId;
}
public void setBookId(Integer buchId) {
this.bookId = buchId;
}
public String getTitle() {
return title;
}
25
2 Installation und Konfiguration
29
30
31
32
33
34
35
36
37
38
39
40
41
public void setTitle(String title) {
this.title = title;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
}
Listing 2.15: Book.java
In der Klasse BookDAOHibernate.java öffnen wir eine SessionFactory (eine Datenbankverbindung) und eine Transaktion (mehrere zusammenhängende Datenbankabfragen). Dann speichern
wir unser Buch und schließen die Transaktion und die SessionFactory wieder. In Hibernate müssen Transaktionsgrenzen explizit gesetzt werden.
package ErstesBeispielDAO;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import
import
import
import
import
ErstesBeispielDomain.Book;
org.hibernate.Session;
org.hibernate.SessionFactory;
org.hibernate.Transaction;
util.HibernateUtil;
public class BookDAOHibernate {
public static void main (String[] args){
Book book = new Book();
book.setTitle("Neues Buch");
BookDAOHibernate bookDAO = new BookDAOHibernate();
bookDAO.insert(book);
}
public void insert(Book book) {
Session session = null;
/*Wir verschaffen uns Zugriff auf die SessionFactory von Hibernate.*/
SessionFactory sessionFactory = HibernateUtil.getSessionFactory();
/*Wir Oeffnen eine Session.*/
session = sessionFactory.openSession();
Transaction tx = null;
26
2.4 Konfiguration eines Projektes in Spring
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
/*Im try-Block wird versucht, eine Transaktion zu Oeffnen und der
Datenbank ein Element hinzuzufuegen.*/
try {
/*Wir beginnen die Transaktion.*/
tx = session.beginTransaction();
/*Wir speichern unser Objekt.*/
session.save(book);
/*Wir beenden die Transaktion und schreiben die Daten in
die Datenbank.*/
tx.commit();
/*Sollte es zu Problemen kommen, wird die Transaktion mit rollback()
rueckgaengig gemacht und es wird eine Fehlermeldung ausgegeben.*/
} catch (RuntimeException e) {
tx.rollback();
e.printStackTrace();
/*Alles was im finally-Block steht, wird auf jeden Fall durchgefuehrt,
so wird in jedem Fall die Session wieder geschlossen.*/
} finally {
if (session != null) {
/*Wir muessen die Session explizit beenden.*/
session.close();
}
}
}
}
Listing 2.16: BookDAOHibernate.java
Als Ergebnis ist das Buch in der Datenbank gespeichert
2.4 Konfiguration eines Projektes in Spring
2.4.1 Ein Spring-Projekt in NetBeans
Wie erstelle ich ein Spring-Projekt mit Spring 3.0.2 und Hibernate 3.2.5 in Netbeans 6.9? Sie
erstellen ein neues Projekt und Sie wählen im vierten Schritt sowohl Hibernate als auch Spring
als Framework aus:
27
2 Installation und Konfiguration
Abbildung 2.21: Auswahl der Frameworks Hibernate und Spring
Wie bereits weiter oben erwähnt, müssen wir für Hibernate die Bibliothek asm.jar durch asm3.1.jar ersetzen. Wir benötigen für ein Spring-Projekt zusätzlich die Bibliothek aopalliance.jar. In
Spring gibt es eine Konfigurationsdatei, die applicationContext.xml heißt. Die applikationContext.xml ist die zentrale Stelle für die Konfiguration einer Anwendung. Hier wird die Datenbank
MySQL, die SessionFactory, das Mapping für die Klasse Book und der Transaktionsmanager konfiguriert. In Spring wurden alle Konfigurationsdateien, die wir in Hibernate erstellen mussten, zu
einer Einzigen zusammengefasst.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!--In der Transaktionsverwaltung kann mithilfe von Annotations
vorgenommen werden.-->
<tx:annotation-driven />
<!--Dependency Injection ist mit Annotations moeglich -->
28
2.4 Konfiguration eines Projektes in Spring
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
<context:annotation-config/>
<!--Die Datenbankressource wird definiert:-->
<bean id="myDataSource"
class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/book"/>
<property name="username" value="root"/>
<property name="password" value="admin"/>
</bean>
<!--Die Hibernate SessionFactory wird festgelegt: -->
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<property name="dataSource" ref="myDataSource"/>
<!--Das Hibernate-Mapping fuer die Klasse Buch wird festgelegt: -->
<property name="annotatedClasses">
<list>
<value>ErstesBeispielDomain.Book</value>
</list>
</property>
<property name="hibernateProperties">
<props>
<!--Der entsprechende Hibernatedialekt fuer MySQL wird festgelegt: -->
<prop key="hibernate.dialect">
org.hibernate.dialect.MySQLInnoDBDialect
</prop>
<!--true bedeutet auf der Konsole sind die
SQL-Befehle sichtbar -->
<prop key="hibernate.show_sql">true</prop>
<!--update bedeutet es wird die Datenbank aktualisiert,
wohingegen create bedeutet, dass immer eine neue
Datenbank erzeugt wird.-->
<prop key="hibernate.hbm2ddl.auto">update</prop>
</props>
</property>
</bean>
<!--Die Java-Klasse, die die Datenbankzugriffsmethoden enthaelt, wird
hier registriert.-->
<bean id="bookDAO" class="ErstesBeispielDAO.BookDAOImpl" autowire="byName">
<!-<property name="sessionFactory" ref="sessionFactory"/>
-->
</bean>
29
2 Installation und Konfiguration
<!--Die Implementierung der Transaktionsverwaltung wird festgelegt. -->
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
</beans>
68
69
70
71
72
73
Listing 2.17: Konfigurationsdatei: applicationContext.xml
Wir verwenden als Klasse für die Datenbankverbindungen
o r g . apache . commons . dbcp . B a s i c D a t a S o u r c e
Diese ermöglicht es uns mehrere Datenbankverbindungen gleichzeitig als Pool zu öffnen und zu
verwalten. Es macht keinen Sinn pro User eine separate Connection auf- und wiederzuzumachen. Da dies viel Zeit verbraucht. So wird ein Pool angelegt, der bereits aus mehreren geöffneten Verbindungen besteht und diese werden zwischen den Benutzern aufgeteilt. Um diesen
Pool verwenden zu können, müssen wir 2 Bilbiotheken hinzufügen: commons-pool-1.5.4.jar und
commons-dbcp-1.4.jar.
2.4.2 Erstes Beispiel in Spring
Wir beziehen uns auf obiges Beispiel: die Klasse Book. Wir wollen Objekte dieser Klasse speichern und wieder auslesen. Wenn wir dies in Spring tun wollen, benötigen wir ein Konzept das
sich Dependency Injection oder Inversion of Control nennt. Mit dem Prinzip Dependency Injection werden Werte einer Variablen in einer XML-Datei festgelegt. Dies geschieht außerhalb der
jeweiligen Klasse. Die Werte einer Variablen werden weder über eine Setter-Methode zugewiesen
noch werden sie aus einer XML-Datei ausgelesen. Dies scheint auf den ersten Blick sehr kompliziert und ist sicherlich auch gewöhnungsbedürftig. Aber, wenn Sie sich ernsthaft mit Spring und
Grails auseinandersetzen wollen, stellt Dependency Injection sicherlich ein wesentlicher Baustein
dar. Im Anhang zum Thema Spring gehe ich näher darauf ein.
Gehen wir Schritt für Schritt vor: Wir benötigen ein Interface für unsere Klasse, die Methoden
enthält, mit denen man auf die Datenbank zugreifen kann. Dieses Interface enthält in unserem
Falle zwei leere Methoden, eine die Bücher einliest und eine, die sie wieder aus der Datenbank
ausliest. In der agilen Softwareentwicklung wird nicht das Interface besonders benannt, wie z.B.
IBookDAO, sondern die davon abgeleitete Klasse, die dann BookDAOImpl (Impl von implements) heißt und das Interface BookDAO.
package ErstesBeispielDAO;
1
2
3
4
5
6
7
8
9
import ErstesBeispielDomain.Book;
import java.util.List;
public interface BookDAO {
public void saveBook(Book book);
public List<Book> listBook();
}
Listing 2.18: Interface BookDAO.java
30
2.4 Konfiguration eines Projektes in Spring
Die leeren Methoden werden in der abgeleiteten Klasse mit Inhalt gefüllt.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package ErstesBeispielDAO;
import
import
import
import
import
import
import
import
ErstesBeispielDomain.Book;
java.util.ArrayList;
java.util.List;
org.hibernate.SessionFactory;
org.springframework.orm.hibernate3.HibernateTemplate;
org.springframework.stereotype.Repository;
org.springframework.transaction.annotation.Transactional;
org.springframework.beans.factory.annotation.Autowired;
@Transactional
public class BookDAOImpl implements BookDAO{
private SessionFactory sessionFactory;
//Autowired
public void setSessionFactory(SessionFactory sessionFactory){
this.sessionFactory = sessionFactory;
}
@Transactional
public void saveBook(Book book) {
sessionFactory.getCurrentSession().save(book);
}
@Transactional
public List<Book> listBook() {
return (ArrayList<Book>)sessionFactory.
getCurrentSession().createQuery("Select b from Book b").list();
}
}
Listing 2.19: Klasse BookDAOImpl.java enthält Datenbankabfragen
In Spring wird mit der Methode sessionFactory.getCurrentSession() auf eine kontextabhängige
Session zugegriffen. Dieses Konzept entspricht in etwa dem Konzept der Session pro Transaktion
in Hibernate. Mit der Annotation @Transactional vor der Methode entsprechen die Transaktionsgrenzen dem Anfang und Ende einer Methode, die Transaktionsgrenzen müssen für Hibernate
in Spring nicht mehr explizit gesetzt werden. Näheres zum Thema Transaktionen finden Sie im
entsprechenden Kapitel.
Wir verwenden in dieser Klasse das Prinzip Dependency Injection. Wo? Wir weisen der Variablen
sessionFactory, innerhalb der applicationContext.xml alle wichtigen Informationen zu. Wie tun
wir das? Es gibt mehrere Möglichkeiten, von denen ich Ihnen drei vorstellen möchte:
31
2 Installation und Konfiguration
1. Sie ergänzen die applicationContext.xml Bean bookDAO wie folgt:
<bean i d ="bookDAO" c l a s s ="E r s t e s B e i s p i e l D A O . BookDAOImpl"
a u t o w i r e="byName">
</bean>
Wobei autowire=“byName“ bedeutet, dass Spring alle Namen aller Beans mit den Namen
der Variablen innerhalb der Klasse BookDaoImpl vergleicht. So findet er jeweils eine sessionFactory. Er weist die Werte aus der applicationContext.xml der sessionFactory in der
KlasseBookDAOImpl zu. Wie durch Geisterhand.
2. Sollten Sie ein Anhänger von Annotations sein, können Sie @Autowired hinzufügen. Sie
brauchen es nur einzukommentieren, an der Stelle, an der es in oben stehender Klasse auskommentiert ist. Zusätzlich müssen Sie autowire=“byName“ in der applicationContext.xml
entfernen.
3. Wollen Sie allerdings die sessionFactory nicht automatisch zuweisen, gibt es noch die Möglichkeit sie mit Hilfe des Variablennamens explizit zuzuweissen. So müssen Sie die BeanDefinition wie folgt ändern:
<bean i d ="bookDAO" c l a s s ="E r s t e s B e i s p i e l D A O . BookDAOImpl">
<p r o p e r t y name=" s e s s i o n F a c t o r y " r e f =" s e s s i o n F a c t o r y "/>
</bean>
Wie verbinden wir unsere Klassen mit der applicationContext.xml? Folgendermaßen:
ApplicationContext context =
new F i l e S y s t e m X m l A p p l i c a t i o n C o n t e x t
( " web/web−i n f / a p p l i c a t i o n C o n t e x t . xml " ) ;
Mit der Klasse SaveList können Sie Objekte der Klasse Book ein- und wieder auslesen.
package SaveList;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import
import
import
import
import
ErstesBeispielDAO.BookDAO;
ErstesBeispielDomain.Book;
java.util.ArrayList;
org.springframework.context.ApplicationContext;
org.springframework.context.support.FileSystemXmlApplicationContext;
public class SaveList {
private BookDAO buchDAO;
public static void main(String[] args){
ApplicationContext context =
new FileSystemXmlApplicationContext
32
2.5 Grundlagen eines Grails-Projektes
16
17
18
19
20
21
22
23
24
25
26
("web/web-inf/applicationContext.xml");
Book book = new Book();
book.setTitle("First Book");
BookDAO bookDAO = (BookDAO)context.getBean("bookDAO");
bookDAO.saveBook(book);
ArrayList<Book> list = (ArrayList<Book>) bookDAO.listBook();
for(Book bookList: list){
System.out.println(bookList.getTitle());
}
}
}
Listing 2.20: Speichern und Auslesen von Daten der Klasse Book
Sollte jetzt auf der Konsole Erstes Buch ausgegeben werden, haben wir alles richtig gemacht.
2.5 Grundlagen eines Grails-Projektes
2.5.1 Erstellen eines Grails-Projekts in NetBeans
Wie richte ich ein Grails-Projekt in NetBeans ein? Analog zu einem normalen Webprojekt: File
–> New Project –> Groovy –> Grails Application. Beim ersten Grails-Projekt muss man auf
Configure Grails und anschließend auf das Register Groovy klicken und zu den Javadocs von
Groovy und zu dem Grails-Home-Verzeichnis verlinken. Das Grails-Home-Verzeichnis, ist das
Verzeichnis in das Sie Grails nach dem Downloaden entpacken. In diesem Verzeichnis befindet
sich auch ein lib-Ordner, der alle Bibliotheken enthält, die Sie zur Entwicklung eines GrailsProjektes brauchen.
33
2 Installation und Konfiguration
Abbildung 2.22: Erforderliche Grails- und Groovy-Links
2.5.2 Einrichten von MySQL und Hibernate in Grails
NetBeans hat Ihnen eine Projektstruktur erstellt, die dem Prinzip Convention over Configuration
entspricht. Dieses Prinzip aus dem Grails Framework bedeutet, dass nur wenige Konfigurationsdateien zu erstellen sind und darin nur außergewöhnliche Einstellungen festgehalten werden müssen. Dies bedeutet, es gibt nur eine einzige Konfigurationsdatei für die Datenbankverbindung mit
MySQL und Hibernate. Dies ist die DataSource.groovy im Verzeichnis grails-app/conf. Grundsätzlich befinden sich alle Konfigurationsdateien in Grails in dem Verzeichnis mit dem Namen
conf.
34
2.5 Grundlagen eines Grails-Projektes
Abbildung 2.23: Projektstruktur in Grails
In der DataSource.groovy wird in der dataSource der Datenbanktreiber und der MySQL-Dialekt
eingetragen. In hibernate werden standardmäßig Cacheeinstellungen festgelegt (siehe Kapitel
zum Thema Cache). Die Einstellung dbCreate legt fest, ob eine neue Datenbank erstellt wird
(create), ob sie gelöscht und gleichzeitig erstellt wird (create-drop) oder ob sie nur aktualisiert
(update) wird. Es können auch Entwicklungsumgebungen (environments) mit unterschiedlichen
Einstellungen festgelegt werden: development, test und production.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
dataSource {
pooled = true
dbCreate = "update"
driverClassName = "com.mysql.jdbc.Driver"
dialect = org.hibernate.dialect.MySQL5InnoDBDialect
username = "root"
password = "admin"
}
hibernate {
cache.use_second_level_cache = true
cache.use_query_cache = true
cache.provider_class = ’net.sf.ehcache.hibernate.EhCacheProvider’
}
// environment specific settings
environments {
development {
dataSource {
dbCreate = "update" // one of ’create’, ’create-drop’,’update’
url = "jdbc:mysql://localhost/myFirstDB"
35
2 Installation und Konfiguration
}
}
test {
dataSource {
dbCreate = "update"
url = "jdbc:mysql://localhost/myFirstDB"
}
}
production {
dataSource {
dbCreate = "update"
url = "jdbc:mysql://localhost/myFirstDB"
}
}
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
}
Listing 2.21: Datenbankkonfiguationsdatei: DataSource.groovy
Wo lege ich fest, in welcher Entwicklungsumgebung ich mich gerade befinde? Bei den Properties:
Klicken Sie mit der rechten Maustaste auf das Projekt und klicken Sie Properties und dann
General Settings an:
Abbildung 2.24: Festlegen der Entwicklungsumgebungen
Es gilt noch eine Besonderheit zu beachten: Die Bibliothek mysql-connector-java-5.1.5-bin.jar
(MySQL-Treiber für Java) muss in dem Verzeichnis lib zusätzlich vorhanden sein und unter den
Properties und Libraries hinzugefügt werden.
2.5.3 Erstes Beispiel in Grails
Für ein erstes Beispiel sind nur ganz wenige Schritte notwendig.
1. Wir richten die Klasse DataSource.groovy (s.o.) ein.
2. Wir erstellen eine leere Datenbank (s.o.).
36
2.5 Grundlagen eines Grails-Projektes
3. Bibliothek für den MySQL-Treiber hinzufügen (s.o.)
4. Wir erstellen im Verzeichnis eine Domainklasse im Verzeichnis grails-app/domain, indem
wir mit der rechten Maustaste auf domain und anschließend auf Grails Domain Class klicken.
Abbildung 2.25: Neue Domain-Klasse
Wir erstellen eine Klasse Buch, die zwei Variablen enthält:
1
2
3
4
5
6
7
8
package groovyforbeginners.Klassen
class Book {
String title
String name
}
Listing 2.22: Unsere erste Domain-Klasse
5. Wir klicken mit der rechten Maustaste auf diese Klasse und wählen generate-all aus und
dann starten wir die Applikation mit run:
Abbildung 2.26: generate-all
37
2 Installation und Konfiguration
Der Browser wird geöffnet, und uns stehen einige automatisch generierte Formulare zur
Verfügung:
Abbildung 2.27: generate-all
Dies war ein erster Einblick in die Funktionalitäten von Grails. Größere und komplexere Projekte
sind nicht ganz so einfach umzusetzen, aber ein kleines Projekt kann durchaus sehr schnell
realisiert werden.
38
3 Die Java Persistence API umgesetzt in db4o,
Hibernate und Grails
3.1 Java Persistence API
Die Java Persistence API ist einen Standard, der Regeln aufstellt, wie Objekte auszusehen haben, die in einer Datenbank gespeichert werden. Diese Richtlinien stellen für relationale und
objektorientierte Datenbanken den Rahmen für das Datenbankdesign dar. Wir werden uns in
diesem Kapitel ansehen, wie dies in db4o und Hibernate realisiert wird. db4o ist eine reine objektorientierte Datenbank. Hibernate ist ein objektrelationaler Mapper, der auf einer relationalen
Datenbank, z.B. MySQL, aufsetzt. Grails stellt zusätzlich ein eigenes objektrelationales Mapping
mit Namen GORM zur Verfügung. GORM benutzt im Hintergrund Hibernate.
3.2 Erste Überlegungen zu einem Datenbankentwurf: Unser erstes
Objekt
Wir beginnen mit einem einfachen Beispiel: Wir wollen alle CSS-Formatierungen unseres ContentManagement-Systems speichern. Für jede Schriftfarbe und Schriftgröße in unseren HTML-Seiten
wollen wir ein separates CSS-Format festlegen. Wie gehen Sie in relationalen Datenbanksystemen
vor? Sie erstellen eine Tabelle. Wie gehen Sie in einer objektorientierten Datenbank vor? Sie
erstellen eine Klasse.
Eine Klasse stellt in objektorientierten Programmiersprachen eine Art Prototyp dar. Ich mache
mir Gedanken welche Eigenschaften meine Klasse braucht und erstelle anschließend davon nur
noch Kopien. Die Kopien werden Objekte oder Instanzen genannt.
Wir erstellen eine Klasse für all unsere CSS-Formatierungen, die nur eine einzige Variable enthält,
nämlich die Variable name. Die Variable name deklarieren wir als private, deshalb kann von
außerhalb der Klasse nicht direkt auf sie zugegriffen werden. Wollen wir den Wert der Variablen
verändern oder wieder auslesen, müssen wir die Variablen mit Getter- und Setter-Methoden von
außen wieder zugänglich machen. Diese Art auf Variablen zuzugreifen, nennt man Kapselung.
Kapselung ist ein wichtiges Prinzip in der Objektorientierten Programmierung.
Des Weiteren besitzt unsere Klasse FormatierungEinfach zwei Konstruktoren: einen Standardkonstruktor und einen Konstruktor mit dem Parameter name. Welche Funktion hat ein Konstruktor? Er macht es Ihnen möglich eine Instanz einer Klasse zu erstellen. Er „konstruiert“ also
ein Objekt.
Die Klasse FormatierungEinfach.java ist der erste Baustein für unser Content-ManagementSystem:
1
2
3
4
5
6
package Kap03;
public class FormatierungEinfach {
private String name;
39
3 Die Java Persistence API umgesetzt in db4o, Hibernate und Grails
//Standardkonstruktor
public FormatierungEinfach(){
this.name = name;
}
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//Konstruktor mit Parameter name
public FormatierungEinfach(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Listing 3.1: Klasse für CSS-Formate
Ein Tipp: Getter- und Setter-Methoden können Sie in NetBeans erstellen, indem Sie folgende
Schritte ausführen:
1. Klicken Sie auf die Variable name mit der rechten Maustaste und wählen Sie Refactor und
Encapsulate Fields aus
Abbildung 3.1: Refactoring und Encapsulating Fields
40
3.2 Erste Überlegungen zu einem Datenbankentwurf: Unser erstes Objekt
2. und im nächsten Schritt klicken Sie auf Next
Abbildung 3.2: Button Next anklicken
3. und zum Schluß links unten auf Do Refactoring.
Abbildung 3.3: Do Refactoring anklicken
Lassen Sie uns einige erste Vergleiche mit relationalen Datenbanken ziehen: Unserer Klasse FormatierungEinfach entspricht unten stehender Tabelle FormatierungEinfach in einem relationalen
Datenbankentwurf, die mit folgendem CREATE-TABLE-Befehl, den Sie aus SQL kennen, erstellt
wird:
CREATE TABLE F o r m a t i e r u n g E i n f a c h ( ID i n t e g e r PRIMARY KEY, name
VARCHAR( 1 0 ) )
41
3 Die Java Persistence API umgesetzt in db4o, Hibernate und Grails
Sie stellen sich jetzt sicherlich die Frage: Wo ist in der Klasse FormatierungEinfach der Primärschlüssel geblieben? Der Primärschlüssel (PRIMARY KEY) erfüllt in relationalen Datenbanksystemen die wichtige Aufgabe, einzelne Datensätze eindeutig zu identifizieren. Jeder Datensatz
besitzt einen anderen Schlüssel und es gibt keinen Primärschlüssel doppelt. So gibt es jede Rechnung mit der Nummer 1 oder jede Formatierung mit der Nummer 5 nur einmal.
Die Datenbank db4o vergibt auch für jedes Objekt einen Schlüssel, der sich Unique Object
Identifier oder abgekürzt OID nennt, dieser ist aber für den Datenbankprogrammierer nicht
sichtbar. Im Gegensatz zu relationalen Datenbanken legen nicht Sie den Schlüssel fest, sondern die
Datenbank. Speichern Sie zwei Objekte der Klasse FormatierungEinfach mit gleichem Inhalt in
der Datenbank, gibt es in der Datenbank zwei Objekte mit gleichem Inhalt, aber unterschiedlichen
OIDs. Der Inhalt der Objekte ist gleich, aber nicht ihre OIDs. So gilt es immer sicherzustellen,
dass keine zwei Objekte mit gleichem Inhalt in der Datenbank existieren und Sie auch immer auf
das gleiche Objekt zugreifen.
Diese Fragestellung wird uns im Weiteren immer wieder beschäftigen. Ihnen wird es sicherlich am
Anfang schwerfallen, sich an den Gedanken zu gewöhnen, ohne sichtbaren Primärschlüssel und
ohne Tabellen zu arbeiten, aber am Ende dieses Buches wird es Ihnen aus objektorientierter Sicht
viel logischer und einfacher vorkommen. Es entspricht der objektorientierten Denkweise, Objekte direkt in der Datenbank zu speichern, und es stellt eine Arbeitserleichterung zu bisherigen
relationalen Vorgehensweisen dar.
Die Klasse FormatierungEinfach soll den Ausgangspunkt für weitere Überlegungen darstellen:
Welche zusätzlichen Elemente brauchen wir für eine Webseite? Was macht unsere Website zu
einer Website? Texte, Bilder, Hyperlinks und Verlinkungen zu PDFs. Also brauchen wir für jedes
genannte Element eine separate Klasse: eine Klasse Text, Bild, Link und PDF. Zusätzlich benötigen wir noch eine Klasse WebSite als zusammenfassende Klasse. Beginnen wir mit der Klasse
Bild, die aus dem Pfad und einem Namen besteht. Der Name erscheint in einer HTML-Seite
anstelle eines Bildes, wenn dieses nicht gefunden wird und der Pfad entspricht dem Speicherort
des Bildes.
package Datenbankentwurf;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Bild {
private String name;
private String pfad;
public Bild (){
}
public Bild(String name, String pfad) {
this.name = name;
this.pfad = pfad;
}
public String getPfad() {
return pfad;
}
public void setPfad(String pfad) {
42
3.2 Erste Überlegungen zu einem Datenbankentwurf: Unser erstes Objekt
22
23
24
25
26
27
28
29
30
31
32
33
this.pfad = pfad;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Listing 3.2: Bild.java
3.2.1 Erstes Objekt in Grails
Wie sehen unsere Objekte in Grails aus? Sie bestehen nur aus Variablen, Getter- und SetterMethoden sind nicht mehr notwendig. Konstruktoren, die die gleichen Funktionalitäten wie
Setter-Methoden haben, auch nicht mehr. Dies ist Groovy-Syntax, die Sie detaillierter im Anhang
nachlesen können.
1
2
3
4
5
package JavaPersistenceAPI
class FormatierungEinfach {
String name
}
Listing 3.3: Klasse für CSS-Formate
1
2
3
4
5
6
7
package Datenbankentwurf
class Bild {
String name
String pfad
}
Listing 3.4: Klasse für Bilder
Klassen, die in Grails als Datenbankmodell gelten, heißen in Grails Domain-Klassen und sie
werden im Verzeichnis grails-app/domain als Domänklasse abgelegt. Weder in Grails noch in db4o
wird ein expliziter Primärschlüssel bentötigt. In db4o wird ein interner Schlüssel (OID) vergeben
und in Grails wird der Primärschlüssel automatisch in der Datenbanktabelle hinzugefügt.
43
3 Die Java Persistence API umgesetzt in db4o, Hibernate und Grails
3.3 Theoretische Grundlagen
Über dreißig Jahre Erfahrung in der Entwicklung von relationalen Datenbanken haben viel Wissen und Theorie hervorgebracht. Diese Theorie wollen wir uns ansehen und dann im Folgenden
überlegen, wie diese Theorie auf objektorientierte Datenbanken übertragen werden kann oder
tatsächlich übertragen wird. In der Welt der objektorientierten Datenbanken wird also versucht,
Prinzipien aus der relationalen Datenbankwelt zu übertragen und zu erweitern. So erfordert das
Einbinden der objektorientierten Datenbank db4o in ein Web-Projekt auf der einen Seite analoges Vorgehen zu dem Einbinden von relationalen Datenbanken, aber auf der anderen Seite auch
kreatives Denken von neuen Lösungen. Ein hilfreiches Instrument stellt des Weiteren die Java
Persistence API dar, die Richtlinien festlegt, wie Objekte auszusehen haben, die in einer relationalen Datenbank gespeichert werden. Die dazugehörige Spezifikation finden Sie unter folgendem
Link: http://www.jcp.org/en/jsr/detail?id=220.
Hibernate ist eine Art Bindeglied zwischen objektorientierter und relationaler Welt. Es werden
so genannte Mappings erstellt. Objekte sehen in db4o mehr oder weniger genauso aus, wie in
Hibernate, da sie letztendlich beide auf der Java Persistence API beruhen. Bei Grails läuft im
Hintergrund auch Hibernate, wobei Grails die Hibernate-Syntax vereinfacht und modifiziert. Das
Prinzip bleibt das gleiche, aber die Syntax ändert sich.
3.3.1 Datenredundanz und Normalisierung
Was verstehen wir unter Redundanz? Redundante Daten sind Daten, die in einer Datenbank
mehrmals vorkommen können. Ähnlich wie bei relationalen Datenbanken, müssen redundante
Daten vermieden werden. So ist es nicht sinnvoll, jedes Mal den Artikel Computer anzulegen,
wenn Sie eine neue Rechnung schreiben wollen. Dies hätte drei Nachteile: Erstens würde dies die
Zeit, die Sie zur Dateneingabe benötigen, enorm vervielfachen. Zweitens würden die Fehlerquellen
zunehmen und das Risiko, statt einem Artikel zum Schluß jede Menge Artikel zu haben. Warum
mehrere Artikel? Da die Möglichkeit sich zu vertippen doch relativ groß ist und sich jeder Artikel
dann nur durch eine Kleinigkeit vom Anderen unterscheidet. Und drittens würde Ihre Datenbank
explodieren und langfristig viel zu groß werden.
Um redundante Daten zu verhindern, werden in relationalen Datenbanksystemen Tabellen normalisiert, d. h. die Daten werden solange auf mehrere Tabellen verteilt, bis sichergestellt wird,
dass alle Daten in einer Datenbank nur ein einziges Mal vorhanden sind. Anschließend werden die
Daten wieder mithilfe von Primär- und Fremdschlüsseln miteinander verbunden. Primärschlüssel
haben die Aufgabe, Daten eindeutig zu identifizieren, d. h. es gibt jede Formatierung mit der
Nummer 1 nur ein einziges Mal. So können Sie diesen Schlüssel in eine andere Tabelle eintragen
und schon gibt es eine Beziehung zwischen den beiden Tabellen. So hat der Primärschlüssel in
der andere Tabelle die Funktion eines Fremdschlüssels übernommen.
Gibt es Situationen, in denen es sinnig ist, die Normalisierung zum Teil wieder rückgängig zu
machen? Ja! Nehmen Sie folgenden Fall: Es greifen viele Personen auf Ihre Website zu und es
könnte zu Verzögerungen bei den Antwortzeiten kommen. Der Vorgang die Normalisierung ganz
oder teilweise wieder rückgängig zu machen, wird Denormalisierung genannt. Wir werden uns
weiter unten darüber noch Gedanken machen.
3.3.2 Die EJB3 Java Persistence API
Da es in objektorientierten Datenbanken keine Tabellen gibt, werden wir zur Beschreibung der
Datenbank die Java Persistence API zu Hilfe nehmen, um einen objektorientierten Datenbankentwurf ohne Redundanzen zu erhalten. Außerdem dient JPA als Standard für alle Objekte, die
44
3.3 Theoretische Grundlagen
in einer relationalen Datenbank abgespeichert werden müssen. Mit ihr soll das Problem gelöst
werden, das so genannte Impedance Mismatch, das entsteht, da Objekte und Tabellen in einer
Datenbank eine unterschiedliche Struktur und andere Regeln haben.
In der Java Persistence API gibt es verschiedene Beziehungen, die wir im nächsten Kapitel anhand
von konkreten Beispielen näher betrachten werden:
1. 1:1-Beziehung: Ein Objekt wird genau einem anderen Objektyp zugeordnet.
2. 1:n-Beziehung: Ein Objekt wird mehreren Objekten eines anderen Objekttyps zugeordnet.
3. m:n-Beziehung: Sowohl dem ersten Objekttyp werden mehrere Objekte des zweiten Objekttyps zugeordnet als auch umgekehrt.
4. Vererbungsbeziehung: Zwischen beiden Objekten existiert eine Vererbungsbeziehung im
objektorientierten Sinn.
3.3.3 Referentielle Integrität
Was versteht man unter Referentieller Integrität? Referentielle Integrität umfasst zwei Aspekte:
Der erste Aspekt beinhaltet, dass Sie in eine Rechnung keinen Artikel eingeben können, der in
der Datenbank nicht vorhanden ist. So müssen Sie also zuerst einen Artikel anlegen, bevor Sie
für diesen Artikel eine Rechnung schreiben können.
Der zweite Aspekt betrifft den Löschvorgang: So kann es unmöglich gemacht werden, z. B. eine
Rechnung zu löschen, die einen Artikel enthält, der noch gebraucht wird. Sie können außerdem
einen Artikel nur löschen, wenn er in keiner Rechnung mehr enthalten ist.
Erfahrene Datenbankexperten wissen sicherlich, wie in relationalen Datenbanksystemen referentielle Integrität sichergestellt werden kann: Mit Primärschlüssel, Fremdschlüssel, References und
einer Constraint-Klausel. In db4o müssen Sie dahingehend umdenken: Sie müssen referentielle
Integrität zum Großteil in der Anwendung selbst festlegen.
So können Sie im Projekt selbst folgendes sicherstellen: Es sollen nur Artikel in eine Rechnung
eingegeben werden, die bereits existieren. Wohingegen Sie beim Löschen von Rechnungen und
Artikel zusätzlich genaue Kenntnisse der Löschvorgänge in db4o benötigen. In db4o können Sie
von Fall zu Fall und von Ebene zu Ebene separat festlegen, welche Daten Sie löschen und welche
nicht.
3.3.4 Theoretische Datenbankgrundlagen und db4o
Im nächsten Kapitel ”Beziehungen in einem objektorientierten Datenbankentwurf” werde ich anhand unseres Content-Management-Systems darlegen, wie diese theoretischen Datenbankgrundlagen in db4o umgesetzt worden sind. Datenredundanz lässt sich verhindern, indem Sie die verschiedenen Beziehungen der Java Persistenz API umsetzen und einhalten.
Wohingegen Sie bei der referentiellen Integrität umdenken müssen. Sie müssen manche Probleme
in dem Projekt selbst lösen. So ist z.B. eine der Möglichkeiten Daten nicht zu löschen, die noch
gebraucht werden, eine if-Abfrage. Dies werden wir ausführlich am Beispiel der Formatierungen
sehen. Wir lernen, wie wir eine bestimmte Formatierung nur löschen, wenn keine Texte mehr
vorhanden sind, die zu dieser Formatierung gehören.
45
3 Die Java Persistence API umgesetzt in db4o, Hibernate und Grails
3.4 Beziehungen in der Java Persistence API
Wie werden Probleme mit der Datenredundanz in db4o gelöst? Wie können Objekte aussehen,
die sowohl in einer relationalen als auch in einer objektorientierten Datenbank gespeichert werden
können? Schritt für Schritt werden wir uns das objektorientierte und relationale Konzept anhand
der Java Persistence API erarbeiten. In diesem Kapitel wollen wir uns mit den verschiedenen
Beziehungen beschäftigen, die es in der Java Persistence API gibt. Es sind die Folgenden: die
1:1-Beziehung, die 1:n-Beziehung, die m:n-Beziehung und die Vererbungsbeziehungen.
3.4.1 Die 1:1-Beziehung: die has-a-Beziehung
Wir wollen uns die 1:1-Beziehung zwischen 2 Klassen ansehen, nämlich zwischen der Klasse
FormatierungEinfach und der Klasse TextEinfach. Für einen Text ist es unumgänglich Schrift
und Schriftgröße festzulegen. So ist eine CSS-Formatierung ein wichtiger Teil unserer Klasse
Text. Und wir geben der Klasse TextEinfach zwei Bestandteile: die Variable name und das
Objekt FormatierungEinfach. Die Beziehung zwischen Text und Formatierung nennt man has-aBeziehung, da die Klasse TextEinfach ein Objekt der Klasse FormatierungEinfach „hat“ .
Bei der 1:1-Beziehung wird in der Java Persistence API zusätzlich zwischen einer unidirektionalen
und einer bidirektionalen unterschieden, die wir uns in den nächsten Abschnitten näher ansehen
werden.
Die unidirektionale 1:1-Beziehung
Lassen Sie uns mit der unidirektionalen Beziehung anfangen und das soeben erwähnte Beispiel
TextEinfach und FormatierungEinfach näher unter die Lupe nehmen. Eine unidirektionale Beziehung geht davon aus, dass Sie immer nur die Texte mit den zugehörigen Formatierungen
auslesen wollen und nie die Formatierungen mit den Texten. Letzteres wäre gar nicht möglich,
da das Objekt FormatierungEinfach gar kein TextEinfach-Objekt enthält.
Wie setzen wir die unidirektionale 1:1-Beziehung um? In der Java Persistence API stellt eine Klasse eine Entität dar, die mit der Annotation @Entity gekennzeichnet werden muss und nur einmal
vorhanden sein darf. Mit @Id wird festgelegt, welche Variable in der Datenbank dem Primärschlüssel entspricht. Wollen Sie die Objekte nur in db4o speichern, können Sie den Primärschlüssel und die Annotations weglassen. Mit @GeneratedValue(strategy = GenerationType.AUTO)
wird vorgegeben, dass JPA in MySQL das automatische Hochzählen des Primärschlüssels (AUTO_INCREMENT) unterstützt. Die Klassen müssen das Serializable-Interface implementieren,
um in Hibernate verarbeitet werden zu können.
Wichtig!
Wollen Sie die Objekte nur in db4o speichern, können Sie den Primärschlüssel und die Annotations weglassen.
package Datenbankentwurf;
1
2
3
4
5
6
7
import
import
import
import
import
46
java.io.Serializable;
javax.persistence.Entity;
javax.persistence.GeneratedValue;
javax.persistence.GenerationType;
javax.persistence.Id;
3.4 Beziehungen in der Java Persistence API
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
@Entity
public class FormatierungEinfach implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer formatierungEinfachId;
private String name;
public FormatierungEinfach() {
this.formatierungEinfachId = formatierungEinfachId;
this.name = name;
}
public FormatierungEinfach(String name) {
this.name = name;
}
public FormatierungEinfach(String name, Integer formatierungEinfachId) {
this.formatierungEinfachId = formatierungEinfachId;
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getFormatierungEinfachId() {
return formatierungEinfachId;
}
public void setFormatierungEinfachId(Integer formatierungEinfachId) {
this.formatierungEinfachId = formatierungEinfachId;
}
}
Listing 3.5: FormatierungEinfach.java
Mit der Annotation @JoinColumn(name="formatierungEinfachId") legen Sie die Fremdschlüsselspalte in der Tabelle TextEinfach fest, die dem Objekt FormatierungEinfach entspricht und
mit @OneToOne wird die 1:1-Beziehung festgelegt. Mithilfe dieser Spalte bzw. diesem Objekt
werden beide Objekte miteinander verbunden.
1
2
package Datenbankentwurf;
47
3 Die Java Persistence API umgesetzt in db4o, Hibernate und Grails
import
import
import
import
import
import
import
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
java.io.Serializable;
javax.persistence.Entity;
javax.persistence.GeneratedValue;
javax.persistence.GenerationType;
javax.persistence.Id;
javax.persistence.JoinColumn;
javax.persistence.OneToOne;
@Entity
public class TextEinfach implements Serializable{
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer textEinfachId;
private String name;
@OneToOne
@JoinColumn(name="formatierungEinfachId")
private FormatierungEinfach formatierungEinfach;
public TextEinfach(){
this.name = name;
this.formatierungEinfach = formatierungEinfach;
}
public TextEinfach(String name) {
this.name = name;
this.formatierungEinfach = formatierungEinfach;
}
public TextEinfach(String name, FormatierungEinfach formatierungEinfach) {
this.name = name;
this.formatierungEinfach = formatierungEinfach;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public FormatierungEinfach getFormatierungEinfach() {
return formatierungEinfach;
}
public void setFormatierungEinfach(FormatierungEinfach formatierungEinfach) {
this.formatierungEinfach = formatierungEinfach;
48
3.4 Beziehungen in der Java Persistence API
52
53
54
55
56
57
58
59
60
61
}
public Integer getTextEinfachId() {
return textEinfachId;
}
public void setTextEinfachId(Integer textEinfachId) {
this.textEinfachId = textEinfachId;
}
}
Listing 3.6: TextEinfach.java
In der Datenbank werden jeweils beide Objekte als Tabellen abgebildet, die jeweils einen Primärschlüssel (PRIMARY KEY) haben und durch einen Fremdschlüssel (FOREIGN KEY) verbunden
werden. Die Tabelle TextEinfach ist durch den Fremdschlüssel formatierungEinfachId mit der Tabelle FormatierungEinfach und dessen Primärschlüssel verbunden.
FOREIGN KEY ( f o r m a t i e r u n g E i n f a c h I d )REFERENCES
FormatierungEinfach ( formatierungEinfachId )
Abbildung 3.4: Verbindungen der Tabellen mithilfe des Fremdschlüssels
Die Datenbank sieht dann folgendermaßen aus:
1
2
3
4
5
6
7
8
9
10
11
12
CREATE DATABASE einfach;
USE einfach;
CREATE TABLE FormatierungEinfach(formatierungEinfachId INT AUTO_INCREMENT,
name VARCHAR(30), PRIMARY KEY (formatierungEinfachId));
CREATE TABLE TextEinfach(textEinfachId INT AUTO_INCREMENT,
name VARCHAR(30), formatierungEinfachId INT,
FOREIGN KEY (formatierungEinfachId)REFERENCES
FormatierungEinfach(formatierungEinfachId),
PRIMARY KEY(textEinfachId));
Listing 3.7: einfach.sql
49
3 Die Java Persistence API umgesetzt in db4o, Hibernate und Grails
Die unidirektionale 1:1-Beziehung in Grails
Wie sehen die entsprechenden Domain-Klassen der unidirektionalen Beziehung in Grails aus? Wir
benötigen wie in db4o keinen Primärschlüssel. Grails fügt automatisch einen Primärschlüssel zu
den Datenbanktabellen hinzu. Da in der unidirektionalen Beziehung davon ausgegangen wird,
dass nur die Texte mit den dazugehörigen Formatierungen abgefragt werden, wird zu der Klasse
TextEinfach ein FormatierungEinfach-Objekt hinzugefügt. Da es sich um eine 1:1-Beziehung
handelt, muss ein Constraint Unique hinzugefügt werden: Es kann ja immer nur ein Text zu
einer Formatierung gehören.
package JavaPersistenceAPI
1
2
3
4
5
6
7
8
9
10
11
class TextEinfach {
String name
FormatierungEinfach formatierungEinfach
static constraints = {
formatierungEinfach unique: true
}
}
Listing 3.8: TextEinfach.groovy
package JavaPersistenceAPI
1
2
3
4
5
class FormatierungEinfach {
String name
}
Listing 3.9: FormatierungEinfach.groovy
Die bidirektionale 1:1-Beziehung
Wozu benötigen Sie eine bidirektionale Beziehung? Wenn Sie öfter nicht nur einen Text mit der
zugehörigen Formatierung auslesen wollen, sondern auch eine Formatierungen mit dem zughörigen Text, so benötigen Sie eine bidirektionale Beziehung. An unserem Textobjekt ändert sich
außer dem Namen nichts. Im Folgenden wurde der Einfachheit halber auf Getter- und SetterMethoden verzichtet.
1
2
3
4
5
6
7
8
package Datenbankentwurf;
import
import
import
import
import
50
java.io.Serializable;
javax.persistence.Entity;
javax.persistence.GeneratedValue;
javax.persistence.GenerationType;
javax.persistence.Id;
3.4 Beziehungen in der Java Persistence API
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;
@Entity
public class TextEinfachBidirektional implements Serializable{
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer textEinfachBidirektionalId;
private String name;
@OneToOne
@JoinColumn(name="formatierungEinfachId")
private FormatierungEinfachBidirektional formatierungEinfachBidirektional;
public TextEinfachBidirektional() {
}
}
Listing 3.10: TextEinfachBidirektional.java
Unsere Formatierung erhält ein zusätzliches Textobjekt, das sich auf das FormatierungEinfachBidirektional des TextEindachBidirektional bezieht. Der Fremdschlüssel befindet sich nach wie
vor in der Tabelle des Textes: an der Datenbank ändert sich nichts.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package Datenbankentwurf;
import
import
import
import
import
import
java.io.Serializable;
javax.persistence.Entity;
javax.persistence.GeneratedValue;
javax.persistence.GenerationType;
javax.persistence.Id;
javax.persistence.OneToOne;
@Entity
public class FormatierungEinfachBidirektional implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer formatierungEinfachBidirektionalId;
private String name;
@OneToOne(mappedBy="formatierungEinfachBidirektional")
private TextEinfachBidirektional textEinfachBidirektional;
public FormatierungEinfachBidirektional() {
}
51
3 Die Java Persistence API umgesetzt in db4o, Hibernate und Grails
24
25
26
}
Listing 3.11: FormatierungEinfachBidirektional.java
Das Element mappedBy="formatierungEinfachBidirektional" legt eine bidirektionale Verbindung zu dem FormatierungEinfachBidirektional an, das sich in der Klasse TextEinfachBirektional
befindet. Was hat diese Verbindung zur Folge? Formatierungen können jetzt mit den zugehörigen
Texten ausgelesen werden.
Die bidirektionale 1:1-Beziehung in Grails
Wie realisieren wir eine bidirektionale 1:1-Beziehung in Grails? Der Text enthält eine Formatierung:
package JavaPersistenceAPI
1
2
3
4
5
6
7
8
class TextEinfachBidirektional {
String name
FormatierungEinfachBidirektional formatierungEinfachBidirektional
}
Listing 3.12: TextEinfachBidirektional.groovy
Und die Formatierung wird mit einem Hinweis
s t a t i c belongsTo = [ t e x t E i n f a c h B i d i r e k t i o n a l : T e x t E i n f a c h B i d i r e k t i o n a l ]
versehen:
package JavaPersistenceAPI
1
2
3
4
5
6
7
8
class FormatierungEinfachBidirektional {
String name
static belongsTo = [textEinfachBidirektional:TextEinfachBidirektional]
}
Listing 3.13: FormatierungEinfachBidirektional.groovy
3.4.2 Vererbungsbeziehungen
Eine der wichtigsten Bausteine einer objektorientierten Programmiersprache sind Vererbungsbeziehungen. Wie können diese in auf Basis der JPA abgebildet werden? Welche Auswirkungen
haben Interfaces, Abstrakte Klassen und Super- und Subklassen auf Abfragen?
52
3.4 Beziehungen in der Java Persistence API
Super- und Subklassen
Was verbindet eine Superklasse mit einer Subklasse? Die Subklasse erbt von der Superklasse. Es
verhält sich ähnlich wie bei Vater und Kind: Das Kind erbt vom Vater. Es liegt eine sogenannte
is-a-Beziehung vor. Ein Kind erbt vom Vater und ist somit vom Typ her ein Vater.
Wo kommt dieses Prinzip in unserem Datenbankentwurf zum Einsatz? In unserem ContentManagement-System gibt es zwei Elemente, die noch in unserer Datenbank abgebildet werden
müssen: Hyperlinks und PDFs. Für diese zwei Klassen erstellen wir eine Vaterklasse Verlinkung,
die aus einer Variablen name und verlinkungId besteht:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package Datenbankentwurf;
import
import
import
import
import
java.io.Serializable;
javax.persistence.Entity;
javax.persistence.GeneratedValue;
javax.persistence.GenerationType;
javax.persistence.Id;
@Entity
public class Verlinkung implements Serializable{
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private Integer verlinkungId;
private String name;
public Verlinkung() {
}
public Verlinkung(String name){
this.verlinkungId = verlinkungId;
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getVerlinkungId() {
return verlinkungId;
}
public void setVerlinkungId(Integer verlinkungId) {
53
3 Die Java Persistence API umgesetzt in db4o, Hibernate und Grails
this.verlinkungId = verlinkungId;
41
42
43
44
}
}
Listing 3.14: Verlinkung.java
Unsere erste Kindklasse ist die Klasse Link, die durch eine extends-Beziehung zur Subklasse der
Klasse Verlinkung wird.
package Datenbankentwurf;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import javax.persistence.Entity;
@Entity
public class Link extends Verlinkung{
public Link(){
}
public Link(String name) {
super(name);
}
}
Listing 3.15: Link.java
Die zweite Kindklasse unserer Klasse Verlinkung ist unsere Klasse PDF, die zusätzlich eine
Variable Pfad beinhaltet. Der Pfad enthält das Verzeichnis, in dem sich das PDF auf dem Server
befindet.
package Datenbankentwurf;
1
2
3
4
5
6
7
8
9
10
11
12
import javax.persistence.Entity;
@Entity
public class PDF extends Verlinkung{
private String pfad;
public PDF() {
}
54
3.4 Beziehungen in der Java Persistence API
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public PDF(String name) {
super(name);
this.pfad = pfad;
}
public PDF(String name, String pfad) {
super(name);
this.pfad = pfad;
}
public String getPfad() {
return pfad;
}
public void setPfad(String pfad) {
this.pfad = pfad;
}
}
Listing 3.16: PDF.java
Die Klasse Link und PDF sind durch Verwandschaftsbeziehungen miteinander verbunden: Sie
sind beide Subklassen der Klasse Verlinkung. Somit steht in jedem Konstruktor der Klasse Link
und der Klasse PDF implizit ein Aufruf super(), der den Standardkonstruktor der Superklasse
aufruft, den ersetzen wir durch ein Aufruf super(name). Der Konstruktoraufruf super(name) ruft
den Konstruktor mit Parameter name der Vaterklasse auf. Somit stehen Ihnen in den Subklassen
alle Methoden der Superklasse zur Verfügung.
Darstellung der Vererbungsbeziehungen in der Java Persistence API
Die Java Persistence API sieht drei verschiedene Mapping-Strategien für Vererbungsbeziehungen
vor: Single_Table, Joined und Table per class. Wobei letztere in der EJB3-Spezifikation als
optional definiert wurde und der Idee von normalisierten Tabellen gänzlich widerspricht.
Single_Table-Mapping
Bei dem Single_Table-Mapping werden alle Objekte, die mit einer Vererbungsbeziehung verbunden sind, durch eine einzige Tabelle in der Datenbank dargestellt. Die Tabelle wird ergänzt durch
eine Spalte, die wir typ nennen und in die entweder Link oder PDF eingetragen wird. So kann in
der Tabelle unterschieden werden, ob es sich um einen Link oder um ein PDF handelt. Die Spalte typ legen wir in der Superklasse fest, indem wir eine sogenannte @DiscriminatorColumn mit
dem Namen typ festlegen. Außerdem muss zusätzlich der InheritanceType.SINGLE_TABLE als
Inheritance-Strategie bestimmt werden. Das Single_Table-Mapping ist auch unter dem Namen
Table-per-Hierarchy also Tabelle für eine Vererbungshierarchie bekannt.
55
3 Die Java Persistence API umgesetzt in db4o, Hibernate und Grails
package Datenbankentwurf;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import
import
import
import
import
import
import
import
java.io.Serializable;
javax.persistence.DiscriminatorColumn;
javax.persistence.Entity;
javax.persistence.GeneratedValue;
javax.persistence.GenerationType;
javax.persistence.Id;
javax.persistence.Inheritance;
javax.persistence.InheritanceType;
@Entity
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name="typ")
public class VerlinkungSingleTable implements Serializable{
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private Integer verlinkungId;
private String name;
public VerlinkungSingleTable() {
}
public VerlinkungSingleTable(String name){
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getVerlinkungId() {
return verlinkungId;
}
public void setVerlinkungId(Integer verlinkungId) {
this.verlinkungId = verlinkungId;
}
}
56
3.4 Beziehungen in der Java Persistence API
Listing 3.17: VerlinkungSingleTable.java
Der DiscriminatorValue in der Klasse PDFSingleTable.java gibt als Wert für die Spalte typ in
der Tabelle den Wert PDF vor. So können Sie in der Datenbank sehen, ob es sich um ein PDF
und nicht um einen Link handelt.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package Datenbankentwurf;
import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity;
@Entity
@DiscriminatorValue("PDF")
public class PDFSingleTable extends VerlinkungSingleTable{
private String pfad;
public PDFSingleTable() {
}
public PDFSingleTable(String name) {
super(name);
this.pfad = pfad;
}
public PDFSingleTable(String name, String pfad) {
super(name);
this.pfad = pfad;
}
public String getPfad() {
return pfad;
}
public void setPfad(String pfad) {
this.pfad = pfad;
}
}
Listing 3.18: PDFSingleTable.java
Analog weisen wir in der Klasse LinkSingleTable.java dem DiscriminatorValue den Wert Link
zu:
57
3 Die Java Persistence API umgesetzt in db4o, Hibernate und Grails
package Datenbankentwurf;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity;
@Entity
@DiscriminatorValue("Link")
public class LinkSingleTable extends VerlinkungSingleTable{
public LinkSingleTable(){
}
public LinkSingleTable(String name) {
super(name);
}
}
Listing 3.19: LinkSingleTable.java
So besteht unsere Datenbank nur aus einer Tabelle, der Tabelle Verlinkung, die vier Spalten
enthält, und zwar die Spalten verlinkungId, typ, name und pfad. Es wurden also drei Objekte
zu einer Tabelle zusammengefasst.
Abbildung 3.5: Unsere Datenbank mit einer Tabelle
Das SINGLE_TABLE-Mapping hat sowohl Vor- als auch Nachteile: Ein großer Nachteil hiervon
ist: Die Tabelle ist nicht voll normalisiert, da es die Spalte pfad gibt, die für alle Links null ist
und somit die Bedingung not null nicht für diese Spalte vergeben werden kann. Um dem Gebot
der Normalisierung besser genüge zu tun, muss die Tabelle erneut geteilt werden, was wir bei der
58
3.4 Beziehungen in der Java Persistence API
Inheritance-Strategie JOINED tun werden. Wohingen die einfache Handhabung und die bessere
Performance, als Vorteil zu Buche schlägt.
1
2
3
4
5
6
CREATE Database webhibernateSingleTable;
USE webhibernateSingleTable;
CREATE TABLE Verlinkung(verlinkungId INT AUTO_INCREMENT, typ VARCHAR(10),
name VARCHAR(50), pfad VARCHAR(50), PRIMARY KEY (verlinkungId));
Listing 3.20: webhibernateSingleTable.sql
JOINED-Mapping
Kommen wir zu der JOINED-Strategie, die ebenfalls unter dem Namen TABLE_PER_SUBCLASSStrategie bekannt ist: Sie legen in der Superklasse die strategy=InheritanceType.JOINED fest.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package Datenbankentwurf;
import
import
import
import
import
import
import
java.io.Serializable;
javax.persistence.Entity;
javax.persistence.GeneratedValue;
javax.persistence.GenerationType;
javax.persistence.Id;
javax.persistence.Inheritance;
javax.persistence.InheritanceType;
@Entity
@Inheritance(strategy=InheritanceType.JOINED)
public class Verlinkung implements Serializable{
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private Integer verlinkungId;
private String name;
public Verlinkung() {
}
public Verlinkung(String name){
this.verlinkungId = verlinkungId;
this.name = name;
}
public String getName() {
return name;
59
3 Die Java Persistence API umgesetzt in db4o, Hibernate und Grails
}
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public void setName(String name) {
this.name = name;
}
public Integer getVerlinkungId() {
return verlinkungId;
}
public void setVerlinkungId(Integer verlinkungId) {
this.verlinkungId = verlinkungId;
}
}
Listing 3.21: Verlinkung.java
Die beiden Subklassen PDF.java und
package Datenbankentwurf;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import javax.persistence.Entity;
@Entity
public class PDF extends Verlinkung{
private String pfad;
public PDF() {
}
public PDF(String name) {
super(name);
this.pfad = pfad;
}
public PDF(String name, String pfad) {
super(name);
this.pfad = pfad;
}
public String getPfad() {
return pfad;
60
3.4 Beziehungen in der Java Persistence API
30
31
32
33
34
35
}
public void setPfad(String pfad) {
this.pfad = pfad;
}
}
Listing 3.22: PDF.java
die Subklasse Link müssen dann nur noch als Entity festgelegt werden:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package Datenbankentwurf;
import javax.persistence.Entity;
@Entity
public class Link extends Verlinkung{
public Link(){
}
public Link(String name) {
super(name);
}
}
Listing 3.23: Link.java
In der Datenbank wird für jede Klasse eine separate Tabelle erstellt, die jeweils nur die Felder
ihrer eigenen Klasse enthält und mit der Tabelle Verlinkung verknüpft ist.
Abbildung 3.6: Unsere Vererbungshierarchie mit JOINED verteilt auf 3 Tabellen
61
3 Die Java Persistence API umgesetzt in db4o, Hibernate und Grails
Jede Tabelle, die eine Subklasse repräsentiert, ist über ihren Primärschlüssel, der als Fremdschlüssel dient, mit dem Primärschlüssel der Tabelle Verlinkung verbunden.
CREATE Database webhibernateJoined;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
USE webhibernateJoined;
CREATE TABLE Verlinkung(verlinkungId INT AUTO_INCREMENT,
name VARCHAR(50), PRIMARY KEY (verlinkungId));
CREATE Table Pdf(verlinkungId INT AUTO_INCREMENT, pfad VARCHAR(50),
PRIMARY KEY (verlinkungId),
FOREIGN KEY(verlinkungId)REFERENCES Verlinkung(verlinkungId) );
CREATE Table Link(verlinkungId INT AUTO_INCREMENT,
PRIMARY KEY (verlinkungId),
FOREIGN KEY(verlinkungId)REFERENCES Verlinkung(verlinkungId) );
Listing 3.24: webhibernateJoined.sql
Das JOINED-Mapping entspricht im vollen Umfang den Erfordernissen der Normalisierung, sie
hat allerdings den Nachteil langsam zu sein, da intern SQL UNION Abfragen notwendig sind.
Für unser Content-Management entscheiden wir uns für das JOINED-Mapping, da es im vollen
Umfang der Normalisierung entspricht.
Table-per-Concrete-Class-Mapping
Bei dieser Strategie erstellen wir für jede Klasse eine Tabelle, die jeweils alle Felder auch die der
Superklasse enthält. So würde in allen drei Tabellen die Spalte name vorkommen, was den Regeln
der Normalisierung widerspricht und in keinster Weise, die Vererbungshierarchie der Klassen
widerspiegelt. Außerdem wurde diese Strategie in der JPA-Spezifikation als optional deklariert,
so müssen es die Persistenceprovider nicht umsetzen und so kann es bei einem Providerwechsel
zu Problemen kommen. Wollen Sie das Table-per-Concrete-Class-Mapping trotzdem verwenden,
müssen Sie als Strategie
@ I n h e r i t a n c e ( s t r a t e g y=I n h e r i t a n c e T y p e .TABLE_PER_CLASS)
festlegen.
Darstellung der Vererbungsbeziehungen in Grails
Vererbungsbeziehungen haben in Groovy, die gleichen Funktionalitäten und die gleiche Syntax
wie in Java. Was gilt bei der Umsetzung von Vererbungsbeziehungen in Grails zu beachten? Wird
eine Vererbungsbeziehung in Grails definiert, wird standardmäßig das Single_Table-Mapping
(Table-per-Hierarchy) auf Datenbankebene verwendet.
package Datenbankentwurf
1
2
3
4
5
class Verlinkung {
String name
}
62
3.4 Beziehungen in der Java Persistence API
Listing 3.25: Verlinkung.groovy
1
2
3
4
5
6
package Datenbankentwurf
class Link extends Verlinkung{
}
Listing 3.26: Link.groovy
1
2
3
4
5
package Datenbankentwurf
class PDF extends Verlinkung {
String pfad
}
Listing 3.27: PDF.groovy
Und das Ergebnis in der Datenbank sieht dann wie unten stehend aus. Grails hat eine Spalte
class hinzugefügt. Diese entspricht unserer oben verwendeten Spalte mit Namen typ.
Abbildung 3.7: Eine Tabelle Verlinkung
Wollen Sie in Grails das Table-per-Subclass-Mapping verwenden, muss in der Klasse Verlinkung
das Table-per-Hierarchie-Mapping auf false gesetzt werden. Die Klasse Verlinkung verändern wir
wie folgt:
c l a s s Verlinkung {
S t r i n g name
s t a t i c mapping = {
tablePerHierarchy f a l s e
}
}
63
3 Die Java Persistence API umgesetzt in db4o, Hibernate und Grails
3.4.3 1:n-Beziehung
Kehren wir zu unseren Formatierungen und Texten zurück. Wir hatten zwei Klassen definiert:
die Klasse FormatierungEinfach.java und die Klasse TextEinfach.java. Diese zwei Klassen hatten
eine 1:1-Beziehung zueinander. Was machen wir aber, wenn wir viele verschiedene Texte haben,
die jeweils einer CSS-Formatierung zugewiesen werden? Dies lässt sich nur schlecht durch unsere
bisherigen Klassen abbilden. Wir brauchen also eine 1:n-Beziehung, wobei wir wieder zwischen
einer unidirektionalen und bidirektionalen Variante unterscheiden können.
Die bidirektionale 1:n-Beziehung
Es stellt sich die Frage: Wie können wir eine bidirektionale 1:n-Beziehung darstellen? Erstens
durch eine ArrayList. Eine ArrayList in der Klasse Formatierung kann mehrere Objekte der
Klasse Text aufnehmen. Und zweitens mithilfe einer Klasse Text, die ein Objekt Formatierung
enthält, da wir in der Regel Texte abrufen und gleichzeitig die dazugehörige Formatierung. In
einer bidirektionalen Beziehung ist es aber auch möglich eine Formatierung mit den zugehörigen
Texten auszulesen.
So wird für das Formatierungsobjekt eine @ManyToOne-Beziehung festgelegt und eine @JoinColumn, die eine Entsprechung in der relationalen Datenbank hat: Eine Spalte in der Tabelle
Text, die formatierungId heißt, und die als Fremdschlüssel mit der Spalte des Primärschlüssels
formartierungId in der Tabelle Formatierung verbunden ist.
@JoinColumn ( name=" f o r m a t i e r u n g I d " , referencedColumnName=" f o r m a t i e r u n g I d " )
Wie Sie sehen können, entspricht die Syntax vom Prinzip her der Syntax des entsprechenden
SQL-Befehls, der den Fremdschlüssel der Tabelle Text mit dem Primärschlüssel der Tabelle
Formatierung verbindet:
FOREIGN KEY( f o r m a t i e r u n g I d )REFERENCES Fo rmati erung ( f o r m a t i e r u n g I d )
package Datenbankentwurf;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import
import
import
import
import
import
import
java.io.Serializable;
javax.persistence.Entity;
javax.persistence.GeneratedValue;
javax.persistence.GenerationType;
javax.persistence.Id;
javax.persistence.JoinColumn;
javax.persistence.ManyToOne;
@Entity
public class Text implements Serializable{
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private Integer textId;
private String name;
/*name="formatierungId" ist der Name des Fremdschluessels der Tabelle Text, der
64
3.4 Beziehungen in der Java Persistence API
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
mit dem Primarschluessel formatierungId der Tabelle
Formatierung (referencedColumnName="formatierungId) verbunden ist"*/
@ManyToOne
@JoinColumn(name="formatierungId", referencedColumnName="formatierungId")
private Formatierung formatierung;
public Text(){}
public Text(String name) {
this.name = name;
}
public Text(String name, Formatierung formatierung) {
this.name = name;
this.formatierung = formatierung;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Formatierung getFormatierung() {
return formatierung;
}
public void setFormatierung(Formatierung formatierung) {
this.formatierung = formatierung;
}
public Integer getTextId() {
return textId;
}
public void setTextId(Integer textId) {
this.textId = textId;
}
}
Listing 3.28: Text.java
Wir haben jetzt eine geänderte Klasse Formatierung, die zusätzlich zu der Variablen name eine
ArrayList als Bestandteil hat, die wiederum eine Vielzahl von Textobjekten aufnehmen kann.
Viele Texte können also einer Formatierung zugewiesen werden. Jetzt haben wir also keine 1:1Beziehung mehr, sondern eine bidirektionale 1:n Beziehung. Mit mappedBy="formatierung"wird
die Verbindung zu dem Formatierungsobjekt formatierung in der Klasse Text festgelegt.
65
3 Die Java Persistence API umgesetzt in db4o, Hibernate und Grails
package Datenbankentwurf;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import
import
import
import
import
import
import
import
import
java.io.Serializable;
java.util.ArrayList;
java.util.List;
javax.persistence.CascadeType;
javax.persistence.Entity;
javax.persistence.GeneratedValue;
javax.persistence.GenerationType;
javax.persistence.Id;
javax.persistence.OneToMany;
@Entity
public class Formatierung implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer formatierungId;
private String name;
/* Die ArrayList<Text> text wird gemappt zu dem Objekt
Formatierung formatierung, das sich in der Klasse Text befindet.*/
@OneToMany(mappedBy = "formatierung")
private List<Text> text = new ArrayList<Text>();
public Formatierung() {
}
public Formatierung(String name) {
this.name = name;
this.text = text;
}
public Formatierung(String name, Integer formatierungId) {
this.name = name;
this.formatierungId = formatierungId;
}
public Formatierung(String name, List<Text> text) {
this.name = name;
this.text = text;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
66
3.4 Beziehungen in der Java Persistence API
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
}
public Integer getFormatierungId() {
return formatierungId;
}
public void setFormatierungId(Integer formatierungId) {
this.formatierungId = formatierungId;
}
public List<Text> getText() {
return text;
}
public void setText(List<Text> text) {
this.text = text;
}
}
Listing 3.29: Formatierung.java
Die zwei Tabellen sehen in der Datenbank immer noch so aus wie die Tabellen der 1:1-Beziehung.
Mit dieser Datenbank kann sowohl eine 1:1 als auch eine bidirektionale 1:n-Beziehung festgelegt
werden.
Abbildung 3.8: Bidirektionale 1:n-Beziehung
Hier sehen Sie die dazu passenden Tabellen in SQL:
1
2
3
4
5
6
7
8
9
10
11
CREATE Database oneToManyBidirektional;
USE oneToManyBidirektional;
CREATE TABLE Formatierung(formatierungId INT AUTO_INCREMENT,
name VARCHAR(30), PRIMARY KEY (formatierungId));
CREATE TABLE Text(textId INT AUTO_INCREMENT ,
name VARCHAR(50), formatierungId INT,
FOREIGN KEY(formatierungId)REFERENCES Formatierung(formatierungId),
PRIMARY KEY(textId));
67
3 Die Java Persistence API umgesetzt in db4o, Hibernate und Grails
Listing 3.30: oneToManyBidirektional.sql
Die bidirektionale 1:n-Beziehung in Grails
In Grails wird die bidirektionale 1:n-Beziehung folgendermaßen umgesetzt: Es wird eine hasManyBeziehung festgelegt, die standardmäßig vom Typ java.util.Set ist. Will man, aber wie in db4o
eine ArrayList festlegen, ist dies ohne Probleme möglich. Man muss explizit das Objekt Text als
Typ ArrayList festlegen und folgendes hinzufügen:
A r r a y L i s t Text
Die Klasse Formatierung, die eine Vielzahl von Texten enthält, sieht dann wie folgt aus:
package Datenbankentwurf
1
2
3
4
5
6
7
class Formatierung {
String name
ArrayList Text
static hasMany = [texte: Text]
}
Listing 3.31: Formatierung.groovy
Und in der Klasse Text befindet sich ein Objekt vom Typ Formatierung:
package Datenbankentwurf
1
2
3
4
5
6
class Text {
String name
Formatierung formatierung
}
Listing 3.32: Text.groovy
Wollen Sie außerdem, dass beim Löschen der Formatierung, die Texte mitgelöscht werden, müssen
Sie die Klasse Text wie folgt abändern und belongsTo hinzufügen:
c l a s s Text {
S t r i n g name
s t a t i c b e l o n g s T o = [ f o r m a t i e r u n g : Form atieru ng ]
}
Beim Speichern gilt der gleiche Mechanismus: Wird eine Formatierung gespeichert, wird der Text
mitgespeichert. Mit belongsTo wird eine sogenannte Eager-Loading-Strategie festgelegt. .
68
3.4 Beziehungen in der Java Persistence API
Die unidirektionale 1:n-Beziehung
Die Klasse WebSite
Wie geht es weiter? Wir haben bereits die folgenden Klassen: Verlinkung, Link, PDF, Bild, Text
und Formatierung. Welche Klasse fehlt uns noch? Die Klasse WebSite. Welche Bestandteile hat
normalerweise eine Webseite? Sie kann einen oder mehrere Texte enthalten, genauso wie Bilder
und PDFs, aber nur einen einzigen Hyperlink. So fügen wir der Klasse WebSite ein Objekt der
Klasse Link hinzu, da eine Webseite jeweils einen Link besitzt und zu einem Link eine sogenannte
has-a-Beziehung definiert. Weiterhin enthält sie jeweils eine ArrayList für Texte, Bilder und
PDFs, da die Webseite zu diesen Elementen jeweils eine 1:n-Beziehung besitzt.
Soweit stimmen Sie mir bestimmt zu. Was bedeuten aber die zwei Strings reihenfolge und ebene?
Mit dem String reihenfolge wird die Reihenfolge der Links festgelegt. So steht z. B. der Link Home
an erster Stelle und der Link Produkte an zweiter Stelle. So geben Sie bei Home die Reihenfolge
1 ein und bei Produkte die Reihenfolge 2. Gibt es innerhalb der Produkte wieder Unterpunkte,
wie z. B. Bücher und DVDs, dann können Sie bei Bücher auch wieder die Reihenfolge 2 eingeben
und bei Ebene dann 1, wohingen Sie bei DVDs die Reihenfolge 2 und die Ebene 2 zuweisen. So
erscheinen die beiden Links für Bücher und DVDs sobald Sie auf den Link Produkte klicken.
Abbildung 3.9: Hyperlinks oben und links
Da wir in unserem Beispiel immer nur die Daten der jeweiligen Website und ihre entsprechenden PDFs und Bilder benötigen, und nie wissen wollen welche PDFs pder Bilder zu welcher
Homepage gehören, brauchen wir eine unidirektionale 1:n-Beziehung. In einer unidirektionalen
1:n-Beziehung weiß z.B. die Klasse Bild nichts über die Beziehung zur Klasse WebSite:
1
2
3
4
5
package Datenbankentwurf;
import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
69
3 Die Java Persistence API umgesetzt in db4o, Hibernate und Grails
import javax.persistence.GenerationType;
import javax.persistence.Id;
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
@Entity
public class Bild implements Serializable{
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private Integer bildId;
private String name;
private String pfad;
public Bild (){
}
public Bild(String name, String pfad) {
this.name = name;
this.pfad = pfad;
}
public String getPfad() {
return pfad;
}
public void setPfad(String pfad) {
this.pfad = pfad;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getBildId() {
return bildId;
}
public void setBildId(Integer bildId) {
this.bildId = bildId;
}
}
Listing 3.33: Bild.java
Eine unidirektionale 1:n-Beziehung zwischen WebSite und Bild muss gemäß der Spezifikation mit
einer Assoziationstabelle abgebildet werden. Diese Tabelle wird mit der Annotation @JoinTable
70
3.4 Beziehungen in der Java Persistence API
erstellt und der Annotation @OneToMany versehen, die eine 1:n-Beziehung festlegt. Wir nennen
sie WebSiteBilder, sie enthält zwei Fremdschlüssel: Den Fremdschlüssel webSiteId, der mit dem
Primärschlüssel webSiteId der Tabelle WebSite verbunden ist, und dem Fremdschlüssel bildId,
der auf den Primärschlüssel bildId der Tabelle Bild zeigt.
@OneToMany
@JoinTable ( name = " W e b S i t e B i l d e r " ,
joinColumns={@JoinColumn ( name="w e b S i t e I d " ) } ,
i n v e r s e J o i n C o l u m n s={@JoinColumn ( name = " b i l d I d " ) } )
Die Annotation @OneToMany wird um einen CascadeType ergänzt. Mit All wird festgelegt, dass
die Bilder mitgelöscht werden, wenn ein Objekte der Klasse WebSite gelöscht wird.
@OneToMany( c a s c a d e = CascadeType . ALL)
Analog wird die entsprechende Tabelle in der Datenbank erstellt, wobei die Spalte bildId auf
UNIQUE gesetzt werden muss, da ein Bild jeweils nur einer einzigen Webseite hinzugefügt werden
kann, da es sich um eine 1:n-Beziehung handelt:
CREATE TABLE W e b S i t e B i l d e r ( w e b S i t e I d INT , b i l d I d INT UNIQUE,
FOREIGN KEY( w e b S i t e I d )REFERENCES WebSite ( w e b S i t e I d ) ,
FOREIGN KEY( b i l d I d )REFERENCES B i l d ( b i l d I d ) ) ;
Die gesamte Klasse WebSite sieht wie unten stehend aus. Sie besitzt jeweils zu den Klassen PDF,
Bild und Text eine unidirektionale 1:n-Beziehung und eine unidirektionale 1:1-Beziehung zu der
Klasse Link.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package Datenbankentwurf;
import
import
import
import
import
import
import
import
import
import
import
import
java.io.Serializable;
java.util.ArrayList;
javax.persistence.CascadeType;
javax.persistence.Entity;
javax.persistence.GeneratedValue;
javax.persistence.GenerationType;
javax.persistence.Id;
javax.persistence.JoinColumn;
javax.persistence.JoinTable;
javax.persistence.OneToMany;
javax.persistence.OneToOne;
javax.persistence.Version;
@Entity
public class WebSite implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer webSiteId;
71
3 Die Java Persistence API umgesetzt in db4o, Hibernate und Grails
@Version
private Integer version;
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name="linkId")
private Link link;
@OneToMany(cascade = CascadeType.ALL)
@JoinTable(name = "WebSiteTexts",
joinColumns = {@JoinColumn(name = "webSiteId")},
inverseJoinColumns={@JoinColumn(name = "textId")}
)
private ArrayList<Text> text = new ArrayList<Text>();
@OneToMany(cascade = CascadeType.ALL)
@JoinTable(name = "WebSiteBilder",
joinColumns = {@JoinColumn(name = "webSiteId")},
inverseJoinColumns={@JoinColumn(name = "bildId")}
)
private ArrayList<Bild> bild = new ArrayList<Bild>();
@OneToMany(cascade = CascadeType.ALL)
@JoinTable(name = "WebSitePdfs",
joinColumns = {@JoinColumn(name = "webSiteId")},
inverseJoinColumns={@JoinColumn(name = "verlinkungId")}
)
private ArrayList<PDF> pdf = new ArrayList<PDF>();
private String reihenfolge;
private String ebene;
public WebSite() {
}
public WebSite(Link link, ArrayList<Text> text,
ArrayList<Bild> bild, ArrayList<PDF> pdf,
String reihenfolge, String ebene) {
this.link = link;
this.text = text;
this.bild = bild;
this.pdf = pdf;
this.reihenfolge = reihenfolge;
this.ebene = ebene;
72
3.4 Beziehungen in der Java Persistence API
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
}
public Link getLink() {
return link;
}
public void setLink(Link link) {
this.link = link;
}
public String getReihenfolge() {
return reihenfolge;
}
public void setReihenfolge(String reihenfolge) {
this.reihenfolge = reihenfolge;
}
public String getEbene() {
return ebene;
}
public void setEbene(String ebene) {
this.ebene = ebene;
}
public ArrayList<Text> getText() {
return text;
}
public void setText(ArrayList<Text> text) {
this.text = text;
}
public ArrayList<Bild> getBild() {
return bild;
}
public void setBild(ArrayList<Bild> bild) {
this.bild = bild;
}
public ArrayList<PDF> getPdf() {
return pdf;
}
public void setPdf(ArrayList<PDF> pdf) {
this.pdf = pdf;
}
73
3 Die Java Persistence API umgesetzt in db4o, Hibernate und Grails
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
public Integer getWebSiteId() {
return webSiteId;
}
public void setWebSiteId(Integer webSiteId) {
this.webSiteId = webSiteId;
}
public Integer getVersion() {
return version;
}
public void setVersion(Integer version) {
this.version = version;
}
}
Listing 3.34: WebSite.java
An dieser Stelle sind wir bereits fertig mit der Datenbank, die wir für unser Projekt brauchen:
74
3.4 Beziehungen in der Java Persistence API
Abbildung 3.10: Gesamte Datenbank unseres Projektes
Die dazugehörigen SQL-Befehle sind in der folgenden Datei datenbank.sql zusammengefasst:
1
2
3
4
5
6
7
8
CREATE Database datenbank;
USE datenbank;
CREATE TABLE Formatierung(formatierungId INT AUTO_INCREMENT,
name VARCHAR(30), PRIMARY KEY (formatierungId));
CREATE TABLE Text(textId
INT AUTO_INCREMENT ,
75
3 Die Java Persistence API umgesetzt in db4o, Hibernate und Grails
name VARCHAR(50), formatierungId INT,
FOREIGN KEY(formatierungId)REFERENCES Formatierung(formatierungId),
PRIMARY KEY(textId));
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
CREATE TABLE Bild(bildId INT AUTO_INCREMENT,
name VARCHAR(50), pfad VARCHAR(100), PRIMARY KEY (bildId));
CREATE TABLE Verlinkung(verlinkungId INT AUTO_INCREMENT,
name VARCHAR(50), PRIMARY KEY (verlinkungId));
CREATE Table Pdf(verlinkungId INT AUTO_INCREMENT, pfad VARCHAR(50),
PRIMARY KEY (verlinkungId),
FOREIGN KEY(verlinkungId)REFERENCES Verlinkung(verlinkungId) );
CREATE Table Link(verlinkungId INT AUTO_INCREMENT,
PRIMARY KEY (verlinkungId),
FOREIGN KEY(verlinkungId)REFERENCES Verlinkung(verlinkungId) );
CREATE TABLE WebSite(webSiteId INT AUTO_INCREMENT,
linkId INT, reihenfolge VARCHAR(5), ebene VARCHAR(5),
FOREIGN KEY(linkId)REFERENCES Link(verlinkungId),
PRIMARY KEY(webSiteId));
CREATE TABLE WebSiteTexts(webSiteId INT, textId INT UNIQUE,
FOREIGN KEY(webSiteId)REFERENCES WebSite(webSiteId),
FOREIGN KEY(textId)REFERENCES Text(textId));
CREATE TABLE WebSiteBilder(webSiteId INT, bildId INT UNIQUE,
FOREIGN KEY(webSiteId)REFERENCES WebSite(webSiteId),
FOREIGN KEY(bildId)REFERENCES Bild(bildId));
CREATE TABLE WebSitePdfs(webSiteId INT, verlinkungId INT UNIQUE,
FOREIGN KEY(webSiteId)REFERENCES WebSite(webSiteId),
FOREIGN KEY(verlinkungId)REFERENCES Pdf(verlinkungId));
Listing 3.35: datenbank.sql
Im Kapitel Abfragekonzepte werden wir uns genauer anschauen, wie die entsprechenden Abfragen
lauten, die wir dafür brauchen.
Die unidirektionale 1:n-Beziehung in Grails
Betrachten wir die Klasse WebSite, wie sie in Grails aussieht. Wir haben eine hasMany-Beziehung
zu den drei Klassen Text, Bild und PDF. Es ist nur möglich ein hasMany-Element pro Klasse
zu definieren, so werden alle 1:n-Beziehungen innerhalb der eckigen Klammer aufgezählt. In den
Klassen Text, Bild und PDF befindet sich jeweils keinerlei Hinweis auf die Klasse WebSite, da
es sich um eine undirektionale Beziehung handelt.
package Datenbankentwurf
1
2
76
3.4 Beziehungen in der Java Persistence API
3
4
5
6
7
8
class WebSite {
Link link
static hasMany = [text:Text, bild:Bild, pfd:PDF]
String reihenfolge
String ebene
}
Listing 3.36: WebSite.groovy
3.4.4 m:n-Beziehung
In unserem Projekt gibt es keine m:n Beziehung, deswegen möchte ich einige andere Beispiele
anführen: So können Ärzte viele Patienten haben, aber auch Patienten viele Ärzte. Oder: Eine
Rechnung kann verschiedene Artikel beinhalten und Artikel können Teil vieler Rechnungen sein.
In der Java Persistence API gibt es sowohl eine bidirektionale als auch eine unidirektionale m:nBeziehung.
Die bidirektionale m:n-Beziehung
So könnte eine Klasse Artikel, eine ArrayList<Rechnung> enthalten und so mehrere Rechnungen
speichern. Diese wird mit der Klasse Rechnung verbunden, indem mit mappedBy=“artikel“ zu
der ArrayList<Artikel> artikel der Klasse Rechnung eine Beziehung erstellt wird.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package Datenbankentwurf;
import
import
import
import
import
import
import
java.util.ArrayList;
java.util.List;
javax.persistence.Entity;
javax.persistence.GeneratedValue;
javax.persistence.GenerationType;
javax.persistence.Id;
javax.persistence.ManyToMany;
@Entity
public class Artikel {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private Integer artikelId;
private String name;
/*Es besteht eine bidirektionale Beziehung zu dem Objekt artikel in der
Klasse Rechnung.*/
@ManyToMany(mappedBy="artikel")
private List<Rechnung> rechnung = new ArrayList<Rechnung>();
public Artikel() {
77
3 Die Java Persistence API umgesetzt in db4o, Hibernate und Grails
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
}
public Artikel(Integer artikelId, String name,
List<Rechnung> rechnung){
this.artikelId = artikelId;
this.name = name;
this.rechnung = rechnung;
}
public Integer getArtikelId() {
return artikelId;
}
public void setArtikelId(Integer artikelId) {
this.artikelId = artikelId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<Rechnung> getRechnung() {
return rechnung;
}
public void setRechnung(List<Rechnung> rechnung) {
this.rechnung = rechnung;
}
}
Listing 3.37: Artikel.java
Die passende Klasse Rechnung könnte die entsprechende ArrayList enthalten, die Artikel speichert, und der eine JoinTable zugewiesen ist, die als Bindeglied zwischen beiden Klassen dient.
package Datenbankentwurf;
1
2
3
4
5
import java.util.ArrayList;
import java.util.List;
import javax.persistence.Entity;
78
3.4 Beziehungen in der Java Persistence API
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
import
import
import
import
import
import
javax.persistence.GeneratedValue;
javax.persistence.GenerationType;
javax.persistence.Id;
javax.persistence.JoinColumn;
javax.persistence.JoinTable;
javax.persistence.ManyToMany;
@Entity
public class Rechnung {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private Integer rechnungId;
/*Dies ist eine bidirektionale m:n-Beziehung, die durch eine
Assoziationstabelle in der relationalen Datenbank abgebildet wird.*/
@ManyToMany
@JoinTable(name = "RechnungArtikel",
joinColumns = {@JoinColumn(name = "rechnungId")},
inverseJoinColumns={@JoinColumn(name = "artikelId")}
)
private List<Artikel> artikel = new ArrayList<Artikel>();
public Rechnung() {
}
public Rechnung(Integer rechnungId,
List<Artikel> artikel) {
this.artikel = artikel;
this.rechnungId = rechnungId;
}
public Integer getRechnungId() {
return rechnungId;
}
public void setRechnungId(Integer rechnungId) {
this.rechnungId = rechnungId;
}
public List<Artikel> getArtikel() {
return artikel;
}
public void setArtikel(List<Artikel> artikel) {
this.artikel = artikel;
79
3 Die Java Persistence API umgesetzt in db4o, Hibernate und Grails
}
55
56
57
}
Listing 3.38: Rechnung.java
Die Beziehungen sehen dann in der Datenbank als Graphik dargestellt wie folgt aus:
Abbildung 3.11: Bidirektionale m:n-Beziehung in der Datenbank
Die Assoziationstabelle verbindet die Tabellen Rechnung und Artikel miteinander und im Gegensatz zu der bidirektionalen 1:n-Beziehung wird kein Fremdschlüsselfeld als UNIQUE definiert.
CREATE Database rechnungMitArtikel;
1
2
3
4
5
6
7
8
9
10
11
12
13
USE rechnungMitArtikel;
CREATE TABLE Artikel(artikelId INT AUTO_INCREMENT,
name VARCHAR(50), PRIMARY KEY(artikelId));
CREATE TABLE Rechnung(rechnungId INT AUTO_INCREMENT,
PRIMARY KEY(rechnungId));
CREATE TABLE RechnungArtikel(rechnungId INT, artikelId INT,
FOREIGN KEY(rechnungId)REFERENCES Rechnung(rechnungId),
FOREIGN KEY(artikelId) REFERENCES Artikel(artikelId));
Listing 3.39: rechnungMitArtikel.sql
Bei der bidirektionalen m:n-Beziehung wollen wir sowohl regelmäßig Rechnungen mit den dazugehörigen Artikeln auslesen, als auch Artikel mit den entsprechenden Rechnungen.
Die bidirektionale m:n-Beziehung in Grails
In Grails wird eine bidirektionale m:n-Beziehung abgebildet, indem jeder Klasse ein hasManyElement hinzugefügt wird.
package Datenbankentwurf
1
2
3
4
5
6
class Artikel {
String name
static hasMany = [rechnungen: Rechnung]
}
80
3.4 Beziehungen in der Java Persistence API
Listing 3.40: Artikel.groovy
Einer Seite muss auf jeden Fall ein belongsTo-Element hinzugefügt werden. Wir fügen es der
Rechnung hinzu. Bei m:m-Beziehungen bezieht sich belongsTo nur auf das Speichern und nicht
auf das Löschen. Sie können keinen Artikel mit einer Rechnung löschen, da dieser Artikel noch
in einer anderen Rechnung vorhanden sein könnte.
1
2
3
4
5
6
package Datenbankentwurf
class Rechnung {
static belongsTo = [artikel: Artikel]
static hasMany = [artikel:Artikel]
}
Listing 3.41: Rechnung.groovy
Die unidirektionale m:n-Beziehung
Bei der unidirektionalen m:n-Beziehung gibt es im Gegensatz zur bidirektionalen m:n-Beziehung
keine ArrayList<Rechnung> und kein mappedBy-Element in der Klasse Artikel. Wir gehen
in diesem Fall davon aus, dass nie Artikel und die dazugehörigen Rechnungen abgefragt, sie
aber trotzdem in der Datenbank gespeichert werden. Es werden immer nur Rechnungen und die
dazugehörigen Artikel benötigt. So ändert sich in Bezug auf das Datenbankdesign nichts.
Die unidirektionale Beziehung m:n-Beziehung in Grails
Was müssen wir in Grails weglassen? Das hasMany-Element in der Klasse Artikel.
Datenbankentwurf
class Artikel {
S t r i n g name
}
Die Klasse Rechnungen braucht nicht verändert zu werden:
package Datenbankentwurf
c l a s s Rechnung {
s t a t i c belongsTo = [ a r t i k e l : A r t i k e l ]
s t a t i c hasMany = [ a r t i k e l : A r t i k e l ]
}
81
4 Einstieg in die Webentwicklung: Servlets und
JSPs
4.1 Der Model-View-Controller: Klassen, Servlets und JSPs
Wie ist ein J2EE-Projekt mit Datenbankanbindung gegliedert? Das Projekt wird in verschiedene
Bereiche eingeteilt, und zwar in Datenbankentwurf, Datenbankabfragen, Programmierlogik und
Darstellung im Internet. Dies hat mehrere Vorteile: Erstens ist eine Übersicht gewährleistet, die
einen schnellen Zugriff auf die einzelnen Elemente erleichtert und zweitens können so die einzelnen
Module wiederverwendet werden.
Wie heißen die unterschiedlichen Dateien und wie werden sie abgespeichert? Es gibt in einem
J2EE-Projekt Java-Klassen, Servlets und JSPs. Datenbankentwurf und Datenbankabfragen werden als Java-Klassen abgespeichert, Programmierlogik als Servlets und Filter und die Darstellung
im Internet als JSP.
In dem Designmodell Model-View-Controller haben wir ein Modell, das aus drei Teilen besteht:
dem Model, dem Controller und dem View. Datenbankenwurf und –abfragen werden unter dem
Oberbegriff Model zusammengefasst, der Controller enthält die Programmierlogik und das View
ist die Darstellung unserer Seite im Internet.
Abbildung 4.1: Model-View-Controller
Wie sieht die Struktur des J2EE-Projekts in NetBeans aus? Im Ordner DasErsteProjekt/src/Java
werden alle Java-Klassen, Servlets und Filter abgelegt; im Ordner web, auch webapp genannt,
alle JSP-Dateien, Bilder, PDFs und CSS-Dateien. Um Servlets und Filter von "normalen" JavaKlassen unterscheiden zu können, müssen diese in eine speziellen Datei eingetragen werden, der
so genannten web.xml, die sich im Ordner WEB-INF befindet. Sollten zusätzliche Bibliotheken
dem Projekt hinzugefügt werden, müssen Sie vorher im Ordner WEB-INF ein Verzeichnis lib
anlegen.
83
4 Einstieg in die Webentwicklung: Servlets und JSPs
Abbildung 4.2: Projektstruktur eines Projektes in NetBeans
4.2 Zusammenspiel zwischen Servlet und JSP
Wie wir weiter oben gesehen haben, enthalten Servlets die Programmierlogik und JSPs die Darstellung im Internet. Lassen Sie uns das Zusammenspiel dieser beiden Dateien anhand eines
einfachen Beispiels näher betrachten: Ein Servlet ist eine normale Java-Klasse, der spezielle
Funktionalitäten zugewiesen werden und die Methoden der Vaterklasse HttpServlet erbt und
überschreibt. Unser erstes Servlet überschreibt die Methoden doGet() und doPost(), wobei die
doGet()-Methode dazu da ist, Daten zu empfangen, die mit GET verschickt wurden und die
doPost()-Methode mit POST. Weiter unten werden wir sehen, was POST und GET von einander unterscheidet.
package Kap11;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import
import
import
import
import
java.io.IOException;
javax.servlet.http.HttpServlet;
javax.servlet.http.HttpServletRequest;
javax.servlet.http.HttpServletResponse;
javax.servlet.ServletException;
public class ErstesServlet extends HttpServlet {
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
}
protected void doPost(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
84
4.2 Zusammenspiel zwischen Servlet und JSP
22
23
}
Listing 4.1: Unser erstes Servlet
Da wir dieses Servlet im Internet veröffentlichen wollen, brauchen wir einen Webcontainer, nämlich Tomcat. In unserer IDE NetBeans ist Tomcat bereits eingebunden. Sie finden Tomcat in
NetBeans, indem Sie links oben auf Runtime klicken und dann auf Servers.
Abbildung 4.3: Tomcat in NetBeans
Ein Servlet wird in die web.xml eingetragen, so weiß Tomcat, in welchem Ordner danach gesucht
werden muss. Was ist die web.xml? Der Web Deployment Descriptor web.xml ist eine Datei,
die XML-konforme Befehle enthält, und in die in einem Webprojekt wichtige Informationen
eingetragen werden müssen. Diese Informationen sind wichtig für den Webcontainer Tomcat, der
die Aufgabe übernimmt z.B. ein Servlet mit bestimmten Ausprägungen im Internet darzustellen.
Wenn Sie Einträge in der web.xml erstellen, ist es nicht notwendig, tief greifende XML-Kenntnisse
zu besitzen, es genügen einige wenige Grundlagen. Jedes Element besteht aus zwei Teilen, einem
Anfangs-Tag und einem End-Tag. In jedem Element können sich wieder weitere Elemente befinden. Im Servlet-Element mit dem Anfangs-Tag <servlet> und dem End-Tag </servlet> gibt es
zwei weitere Elemente, hier legen Sie den Namen des Servlets fest und tragen ein, in welchem Ordner das Servlet abgelegt wurde. Im URL-Pattern-Element des Servlet-Mapping steht die Bezeichnung unter der Sie das Servlet erreichen können: Der Pfad ändert sich von Kap11/ErstesServlet
in die abgekürzte Form /ErstesServlet. Dies hat drei Vorteile: Erstens müssen Sie den Pfad nur
noch an einer Stelle verändern, zweitens müssen Sie nicht immer den kompletten Pfad eingeben,
und drittens wird die Ordnerstruktur für den User komplett unsichtbar.
1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<servlet>
<!-- Name des Servlets -->
<servlet-name>ErstesServlet</servlet-name>
85
4 Einstieg in die Webentwicklung: Servlets und JSPs
<!-- Ort, an dem das Servlet gespeichert wurde -->
<servlet-class>Kap11.ErstesServlet</servlet-class>
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
</servlet>
<servlet-mapping>
<!-- Name des Servlets -->
<servlet-name>ErstesServlet</servlet-name>
<!-- Aufruf des Servlets in der URL -->
<url-pattern>/ErstesServlet</url-pattern>
</servlet-mapping>
</web-app>
Listing 4.2: Servlet-Mapping (web.xml)
Kommen wir zu unserem ersten Formular, das Daten an unser Servlet sendet. Wie Sie sehen
können, besteht eine JSP-Datei hauptsächlich aus HTML; Java wird nur an bestimmten Stellen
eingebettet. So holt uns der Java-Befehl <%=request.getContextPath()%> den aktuellen Pfad
des Servlets aus der URL. Dieser kann unterschiedlich sein, je nachdem ob das Projekt sich lokal
auf Ihrem Rechner befindet oder im Internet.
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<html>
<head>
<title>Erstes Formular</title>
</head>
<body>
<!--Sendet Daten an das Servlet mit Namen ErstesServlet -->
<form name=""
action="<%=request.getContextPath()%>/ErstesServlet"
method="GET">
<input type="text" name="Formatierung" value=""/>
<input type="submit" name="Eingabe" value="Eingabe"/>
</form>
</body>
</html>
Listing 4.3: Unser erstes Formular(erstesJSP.jsp)
86
4.2 Zusammenspiel zwischen Servlet und JSP
Unten stehende Abbildung stellt die Beziehung zwischen JSP und Servlet dar. Es werden in
das Formular Daten eingegeben, die direkt mit GET an das Servlet und die Methode doGet()
gesendet werden.
Abbildung 4.4: Beziehung zwischen Servlet und JSP
Diese Arbeitsteilung stellt eine enorme Erleichterung für den Programmierer und den mit ihm
zusammenarbeitenden Webdesigner dar. Eine JSP-Datei, bei der noch klar die HTML-Struktur
vorhanden ist, kann jederzeit beinahe problemlos von einem Webdesigner verändert und ergänzt
werden. Deshalb sollten auch jegliche Javaprogrammierzeilen auf ein Minimum beschränkt sein.
Was passiert intern mit einem JSP? Es gibt noch eine andere Beziehung zwischen Servlet und
JSP, die bisher nicht ersichtlich war: Das JSP wird in ein Servlet umgewandelt, sobald das Projekt
von Tomcat für das Internet fertig "verpackt" wird. Wo befindet sich dieses Servlet? NetBeans
hat es geschickt vor Ihnen versteckt. Sie müssen in die Projects-Ansicht wechseln und mit der
rechten Maustaste das JSP anklicken und anschließend View Servlet.
87
4 Einstieg in die Webentwicklung: Servlets und JSPs
Abbildung 4.5: So gelangen Sie in die Servlet-Ansicht des JSP
So öffnet sich folgende Java-Klasse, die Ihnen zeigt wie viel Arbeit Ihnen durch den Einsatz der
Java Server Pages erspart bleibt. Weiter unten in den Abschnitten über Servlets und JSPs werden
wir auf diese Zusammenhänge näher eingehen.
package Kap11;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;
public final class erstesJSP_jsp
extends org.apache.jasper.runtime.HttpJspBase
implements org.apache.jasper.runtime.JspSourceDependent {
private static java.util.List _jspx_dependants;
public Object getDependants() {
return _jspx_dependants;
}
public void _jspService(HttpServletRequest request,
HttpServletResponse response)
throws java.io.IOException, ServletException {
JspFactory _jspxFactory = null;
PageContext pageContext = null;
HttpSession session = null;
ServletContext application = null;
ServletConfig config = null;
JspWriter out = null;
88
4.2 Zusammenspiel zwischen Servlet und JSP
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
Object page = this;
JspWriter _jspx_out = null;
PageContext _jspx_page_context = null;
try {
_jspxFactory = JspFactory.getDefaultFactory();
response.setContentType("text/html");
pageContext = _jspxFactory.getPageContext(this, request,
response,
null, true, 8192, true);
_jspx_page_context = pageContext;
application = pageContext.getServletContext();
config = pageContext.getServletConfig();
session = pageContext.getSession();
out = pageContext.getOut();
_jspx_out = out;
out.write("<!DOCTYPE HTML PUBLIC " +
"\"-//W3C//DTD HTML 4.01 Transitional//EN\"\n");
out.write("\"http://www.w3.org/TR/html4/loose.dtd\">\n");
out.write("\n");
out.write("<html>\n");
out.write("
<head>\n");
out.write("
<title>Erstes Formular</title>\n");
out.write("
</head>\n");
out.write("
<body>\n");
out.write("
<form name=\"\" action=\"");
out.print(request.getContextPath());
out.write("\n");
out.write("
/ErstesServlet\" method=\"GET\">
" +
" \n");
out.write("
<input type=\"text\" name=\"Formatierung" +
"\" value=\"\"/>\n");
out.write("
<input type=\"submit\" name=\"Eingabe\" " +
"value=\"Eingabe\"/>\n");
out.write("
</form>\n");
out.write("
</body>\n");
out.write("</html>\n");
} catch (Throwable t) {
if (!(t instanceof SkipPageException)){
out = _jspx_out;
if (out != null && out.getBufferSize() != 0)
out.clearBuffer();
if (_jspx_page_context != null) _jspx_page_context.
handlePageException(t);
}
} finally {
if (_jspxFactory != null) _jspxFactory.
releasePageContext(_jspx_page_context);
89
4 Einstieg in die Webentwicklung: Servlets und JSPs
}
76
77
78
}
}
Listing 4.4: JSP als Servlet
4.2.1 Versenden von Daten mit POST und GET
Daten werden im Internet gemäß des HTTP-Protokolls versendet, das eine einfache AnfrageAntwort-Beziehung zwischen dem Benutzer und dem Server darstellt. Der Benutzer füllt ein
Formular aus und sendet diese Formulardaten zum Server, der anschließend Daten als Antwort
zurücksendet. Der Benutzer fragt an, der Server antwortet. Der englische Fachbegriff für die
Anfrage des Client lautet Request und die Antwort des Servers Response. Jeder Request läuft in
einem separaten Thread.
Abbildung 4.6: Die Request-Response-Beziehung
Es gibt zwei Methoden, die es Ihnen möglich machen, Fragen und Antworten im Internet zu
versenden: POST und GET. Welche Unterschiede bestehen zwischen den beiden Methoden POST
und GET? Bei GET werden die Formulardaten an die URL mit einem Fragezeichen angehängt:
http://localhost:8084/DasErsteProjekt/eingabeFormatierung?name=font1&Eingabe=Eingabe
Dies hat vier Nachteile: Erstens sind die Daten für den Benutzer sichtbar; dies wäre beim Übersenden von geheimen Daten, wie z.B. Passwörter nicht sinnvoll. Zweitens können Sie zwar gemäß
HTTP-Protokoll beliebig viele Daten versenden, aber praktisch ist diese Datenmenge in vielen
Browsern begrenzt. Und zum Dritten speichert der Browser u.U. die Daten im URL-Cache. Viertens können Sie nur Textdaten versenden und keine binären Daten. Diese Nachteile fallen alle
weg, wenn Sie die Methode POST benutzen. POST versendet Daten im so genannten Request
Message Body, der für den Benutzer nicht sichtbar ist. Außerdem können Sie mit POST binäre
Daten versenden.
So lässt sich kurz zusammengefasst sagen: Große Datenmengen, die vom Benutzer nicht eingesehen werden sollen, versenden Sie am besten mit POST. Zu Übungszwecken werde ich im
folgenden alle Daten mit GET versenden, da es hier oft notwendig sein wird, die Daten, die
versendet werden, als Anhang der URL sehen zu können.
4.3 Servlets
4.3.1 Allgemeines
Ursprünglich standen den Programmierern in der Java-Webprogrammierung nur Servlets zur Verfügung. Vor Einführung der JSPs musste der Programmierer mithilfe eines Servlets die HTMLAusgabe im Browser programmieren. Weiter oben haben Sie bereits gesehen, wie ein JSP aussieht,
wenn es von Tomcat in ein Servlet umgewandelt wurde.
90
4.3 Servlets
Wie können Sie ein Servlet mit HTML-Ausgabe erstellen? Mit Hilfe eines PrintWriter-Objektes.
Der PrintWriter gehört zum java.io-Package und besitzt die Funktion, Text in eine Datei auslesen zu können. Hierbei handelt es sich um einen Zeichenstrom, der Text zeichenweise in einen
Speicher-Puffer und dann in eine Datei ausliest. Im Falle von Servlets wird dieser Text im Browser
ausgegeben. Das PrintWriter-Objekt erhalten Sie, wenn Sie auf dem HttpServletResponse-Objekt
die getWriter()-Methode ausführen. Mit der println()-Methode können Sie dann anschließend die
HTML-Seite erstellen. Wie Sie im HelloWorldServlet sehen können, ist dies eine sehr mühselige
Arbeit, die Ihnen heute durch JSPs abgenommen wird.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package Kap11;
import
import
import
import
import
import
java.io.IOException;
java.io.PrintWriter;
javax.servlet.ServletException;
javax.servlet.http.HttpServlet;
javax.servlet.http.HttpServletRequest;
javax.servlet.http.HttpServletResponse;
public class HelloWorldServlet extends HttpServlet {
public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("<html>");
out.println(" <head>");
out.println("
<title>HelloWorld</title>");
out.println(" </head>");
out.println(" <body>");
out.println("
Hello World");
out.println(" </body>");
out.println("</html>");
}
public void doPost(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
Listing 4.5: HelloWorldServlet.java
91
4 Einstieg in die Webentwicklung: Servlets und JSPs
Das Objekt der Klasse HttpServletResponse mit Namen response, das der Methode doGet() als
Parameter übergeben wird, stellt Ihnen alle Methoden zur Verfügung, die notwendig sind, Daten
im Browser auszugeben. Rufen Sie nun das Servlet auf, erscheint Hello World im Browser:
Abbildung 4.7: Ausgabe des HelloWorldServlets
4.3.2 Methoden des Interface HttpServletRequest
Das Interface HttpServletRequest erweitert das ServletRequest-Interface und stellt Methoden
zur Verfügung, die es Ihnen im Servlet möglich machen, Daten auszulesen, die vom JSP an das
Servlet gesendet worden sind. Sollten Sie jetzt nach diesem Interface in Ihrer Java-Dokumentation
(Javadocs / API Documentation) suchen, werden Sie feststellen, dass es dort kein entsprechendes
Interface gibt. Informationen zu J2EE-Klassen finden Sie in der entsprechenden J2EE-API, die
Sie von der Sun-Website herunterladen können.
Mit der Methode getParameter() lässt sich das Feld mit dem Namen Formatierung auslesen, das
vom Formular im JSP mit Namen erstesJSP.jsp an das Servlet mit Namen ErstesServlet.java
gesendet worden ist. Das Formularfeld Formatierung wird im Servlet als Parameter ausgelesen.
Ein Parameter kann nicht verändert werden; er ist nur lesbar. Die entsprechende Befehlszeile in
der doGet-Methode des Servlets ErstesServlet lautet dann wie folgt:
r e q u e s t . g e t P a r a m e t e r ( " Formatierung " ) ;
Normalerweise endet der Request an dieser Stelle, da es sich um eine Weiterleitung vom Client
zum Server handelt. Sollte Sie allerdings die Daten weiterschicken wollen, besteht die Möglichkeit
die Daten des Requests vom Server an den Client zurückzusenden. Dies macht es Ihnen z.B. möglich, Daten wieder parat zu haben, wenn in einem Adressformular ein Pflichtfeld nicht ausgefüllt
wurde und Sie nicht wollen, dass der Kunde alle Felder nochmals eingeben muss. Sie senden die
Daten des Request mit getRequestDispatcher() weiter, wobei Sie die Zielseite angeben müssen:
r e q u e s t . g e t R e q u e s t D i s p a t c h e r ( " / Kap11/ e r s t e s J S P . j s p " ) . f o r w a r d ( r e q u e s t ,
response ) ;
Zum Schluß noch die bereits erwähnte Methode getContextPath(), die den tatsächlichen Ort und
die Pfadangabe des Projektes wiedergibt:
r e q u e s t . ge tCon textP at h ( )
Das Interface HttpServletRequest besitzt noch mehrere Methoden bezüglich Sessions, die wir
weiter unten näher betrachten werden.
4.3.3 Die Methode sendRedirect() des Interface HttpServletResponse
Die Methode response.sendRedirect() unterscheidet sich von der bereits kennengelernten Methode request.getRequestDispatcher(), insofern, als sie nur auf eine andere Seite weiterleitet.
92
4.3 Servlets
Schickt der Benutzer z.B. Daten aus einer Bestellung zum Server und schickt der Server diese Daten mit der Methode response.sendRedirect() weiter, stehen diese Daten auf der Zielseite
nicht mehr zur Verfügung. Achtung: Hier müssen Sie der Methode zusätzlich mit der Methode
request.getContextPath() den Pfad des Projektes übergeben.
r e s p o n s e . s e n d R e d i r e c t ( r e q u e s t . getC ont ex t P a t h ( ) +
"/ Kap02/ e r s t e s J S P . j s p " ) ;
Leiten Sie mit der Methode sendRedirect() Ihre Seite weiter, gehen somit alle Informationen
verloren, die Sie vom Formular erhalten haben.
4.3.4 Sessions und die zugehörigen Methoden
Wir haben gesehen, dass die Informationen, die vom Formular an das Servlet gesendet werden,
nur während des Zeitraums der Anfrage/des Requests existieren. Nehmen wir an, Sie haben eine
Bestellung, die aus verschiedenen Seiten besteht, wie z.B. aus einem Warenkorb, Artikel- und
Kundenstammdaten, so benötigen Sie diese Formulardaten während des ganzen Bestellvorgangs.
Diese Funktion erfüllt eine Session.
Die Session ist Teil des Requests/der Anfrage und existiert ab dem Zeitpunkt, an dem Sie der
doGet()-Methode des Servlets das HttpServletRequest übergeben. Der Server beendet nach einer
bestimmten Zeit die Session automatisch, wenn der Benutzer über einen längeren Zeitraum keine
Daten mehr an den Server schickt. Wollen Sie selbst die Session löschen, geht dies mit invalidate(),
wobei Sie sich zuerst mit der Methode getSession() einen Zugriff auf die Session des Requests
verschaffen müssen:
request . getSession ( ) . invalidate ( ) ;
Können Sie auch den Zeitraum festlegen, nachdem die Session beendet wird? Ja, Sie legen einen
Timeout für Ihre Session in der web.xml fest, indem Sie in das Timeout-Element des SessionConfig-Elements eine Zeitangabe in Minuten schreiben. Was passiert, wenn Sie 0 oder eine negative Zahl eingeben? Bei 0 wird die Session sofort beendet und bei einer negativen wird sie nie
beendet.
<s e s s i o n −c o n f i g >
<s e s s i o n −timeout >30</ s e s s i o n −timeout>
</ s e s s i o n −c o n f i g >
Wie können Sie der Session Werte übergeben? Mit der Methode setAttribute(String name, Object
value). Diese Methode besitzt zwei Übergabewerte: Der Erste gibt dem Attribut seinen Namen
und der Zweite ist eine Variable oder ein Objekt. In unten stehendem Beispiel erstellen wir eine
Fehlermeldung, die wir als Attribut der Session übergeben:
S t r i n g f e h l e r m e l d u n g = new S t r i n g ( " S i e haben das F e ld n i c h t
ausgefüllt !";
request . g e t S e s s i o n ( ) . s e t A t t r i b u t e (" e r r o r " , fehlermeldung ) ;
Diesen String können sie im Servlet mit der Methode getAttribute() auslesen:
request . g e t S e s s i o n ( ) . getAttribute (" e r r o r " ) ;
Und sollten Sie das Attribut nicht mehr brauchen, können Sie es mit der Methode removeAttribute() wieder löschen:
request . g e t S e s s i o n ( ) . removeAttribute (" e r r o r " ) ;
93
4 Einstieg in die Webentwicklung: Servlets und JSPs
Achtung: Die Sessions funktionieren nur, wenn der Benutzer nicht die höchste Sicherheitsstufe bezüglich Cookies eingestellt hat. Es gibt zweierlei Lösungen für dieses Problem: Entweder
Sie können auf Sessions verzichten und nur Request-Attribute verwenden, die Sie immer mit
dem Request-Dispatcher weitersenden oder Sie machen es wie Yahoo, die Ihre Benutzer dazu
auffordern, die Cookies wieder einzuschalten.
4.3.5 Attribute, Parameter und Geltungsbereiche
Wie die Methoden setAttribute(), getAttribute() und removeAttribute() zeigen, können Attribute erstellt, ausgelesen und wieder gelöscht werden, wohingegen Parameter, die direkt aus dem
Request mit request.getParameter("Formatierung"); des Interfaces HttpServletRequest ausgelesen werden, nur lesbare (read-only) Informationen enthalten.
Gibt es Attribute nur für Sessions? Nein! Es gibt sie für folgende Geltungsbereiche: Sessions,
Requests und Applikationen/Contexte. Wodurch unterscheiden sich die Geltungsbereiche, die
auch Scope genannt werden? Der Request existiert während der Anfrage, sprich in der Zeit, in
der Daten vom Client zum Server gesendet werden. Die Session umfasst einen längeren Zeitraum,
der exakt festgelegt werden kann, und der in Regel so lange dauert, wie ein Benutzer sich auf
einer bestimmten Website befindet. Wohingegen der Context die Webapplikation selbst ist, auf
den von allen Bestandteilen der Webapplikation zugegriffen werden kann und die keine zeitliche
Begrenzung kennt.
Für den Request können Sie ein Attribut folgendermaßen erstellen:
request . s e t A t t r i b u t e (" e r r o r " , fehlermeldung ) ;
Beim Context lautet die entsprechende Befehlszeile wie folgt:
getServletContext ( ) . s e t A t t r i b u t e (" e r r o r " , fehlermeldung ) ;
Wie sieht es mit den Parametern aus? Gibt es sie auch für alle drei Geltungsbereiche? Es gibt sie
auch für den Context und für den Context des Servlets, aber nicht für Sessions. Für Context- und
Servlet-Context-Parameter gibt es keine Setter-Methode, da sie in der web.xml definiert werden.
Im entsprechenden Kapitel weiter unten werden wir besprechen, wie sie wieder ausgelesen werden
und welche Aufgabe sie haben.
4.3.6 Ein praktisches Beispiel
Lassen Sie uns anhand eines Beispiels das Wichtigste zusammenfassen. Wir beginnen wie folgt:
Erstens: Wir erstellen ein Servlet, nennen es ZweitesServlet, und tragen es in die web.xml ein.
Zweitens: Wir erstellen ein JSP mit Namen zweitesJSP.jsp und schicken von diesem JSP Daten
an das Servlet.
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
1
2
3
4
5
6
7
8
9
10
<html>
<head>
<title>ClientServer</title>
</head>
<body>
<form name=""
94
4.3 Servlets
11
12
13
14
15
16
17
18
19
20
action="<%=request.getContextPath()%>/ZweitesServlet"
method="GET">
<input type="text" name="Formatierung" value=""/>
<input type="submit" name="Eingabe" value="Eingabe"/>
</form>
</body>
</html>
Listing 4.6: Unser zweites JSP(zweitesJSP.jsp)
Anschließend lesen wir in untenstehendem Servlet die Daten des Formulars mit getParameter()
aus, speichern den Namen der Formatierung mit setAttribute() als Session-Attribut und leiten
das Servlet mit der Methode getRequestDispatcher() an ein JSP weiter.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package Kap11;
import
import
import
import
import
java.io.IOException;
javax.servlet.http.HttpServlet;
javax.servlet.http.HttpServletRequest;
javax.servlet.http.HttpServletResponse;
javax.servlet.ServletException;
public class ZweitesServlet extends HttpServlet {
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
/*Ist im zweitesJsp.jsp auf submit mit
Namen Eingabe geklickt worden,
ergibt unten stehender Ausdruck true*/
if(request.getParameter("Eingabe")!= null){
/*Es wird das Formularfeld Formatierung
aus dem Request ausgelesen*/
String formatierungName = request.getParameter
("Formatierung");
/*Der String formatierungName wird der
Session als Attribut übergeben*/
request.getSession().setAttribute
("Formatierung", formatierungName);
/*Der Request wird an das JSP weitergeleitet*/
request.getRequestDispatcher("/Kap02/drittesJSP.jsp").
forward(request, response);
95
4 Einstieg in die Webentwicklung: Servlets und JSPs
}
33
34
35
36
37
38
39
40
41
42
}
protected void doPost(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
Listing 4.7: Unser zweites Servlet
Wie auf Parameter des Requests und auf Attribute der Session in einem JSP zugegriffen wird,
werden wir uns im JSP namens drittesJSP.jsp im Unterkapitel JSPs näher ansehen.
Sie werden sich jetzt sicherlich fragen: Wie starte ich mein Projekt? Wählen Sie im Menü den
Menüpunkt Run und Run Main Project aus, oder wenn Ihr Projekt nicht das Main Project ist,
klicken Sie in der Ansicht Projects mit der rechten Maustaste auf Ihr Projekt und anschließend
auf Run Project.
Der eingebettete Web-Container Tomcat öffnet einen virtuellen Server, den so genannten Localhost. Dieser ist unter URL://localhost:8080 oder URL://localhost:8084 zu erreichen. Bei mir ist
er unter dem Port 8084 zu erreichen, da ich Tomcat zweimal installiert habe, einmal nur Tomcat
und einmal Tomcat mit NetBeans. Der Startbildschirm von Tomcat, auf Deutsch Kater, sieht
wie folgt aus:
Abbildung 4.8: Startbildschirm von Tomcat
Sobald sich Ihr Browser öffnet, geben Sie folgende Adresse ein:
http://localhost:8084/DasErsteProjekt/Kap02/zweitesJSP.jsp
96
4.3 Servlets
und Sie können die folgende Seite in Ihrem Browser sehen:
Abbildung 4.9: Unser zweites Formular
Dieser Vorgang nennt sich Deployment. Was passiert während dieses Vorgangs? Es werden alle
wichtigen Dateien in einer WAR-Datei zusammengefasst; so ist es möglich eine Applikation im
Internet zu veröffentlichen. Platzieren Sie die WAR-Datei im webapps-Verzeichnis von Tomcat
und der Inhalt wird automatisch entpackt und Ihre Webapplikation kann benutzt werden. Wie
setzt sich diese Datei zusammen? Es wird die Struktur des Ordners web, auch webapp genannt,
übernommen und es wird ein zusätzlicher Ordner classes für die kompilierten Java-Klassen erstellt. Dieser Ordner classes wird im Verzeichnis WEB-INF angelegt.
Die WAR-Datei finden Sie in NetBeans im Ordner dist:
Abbildung 4.10: War-Datei
So bleibt eine letzte Frage: Woher weiß Tomcat, wie unser Projekt heißt? Dies wird im Context
path in der context.xml festgelegt, die sich im Ordner META-INF befindet und die NetBeans
ganz am Anfang für Sie angelegt hat.
1
2
3
4
5
<?xml version="1.0" encoding="UTF-8"?>
<Context path="/ErstesBeispielHibernate">
<Resource name="jdbc/book" auth="Container"
driverClassName="com.mysql.jdbc.Driver"
password="admin"
97
4 Einstieg in die Webentwicklung: Servlets und JSPs
username="root"
type="javax.sql.DataSource"
url="jdbc:mysql://localhost:3306/book"/>
</Context>
6
7
8
9
10
Listing 4.8: context.xml
4.4 JSP
4.4.1 Grundlagen
Java Server Pages bestehen aus HTML und CSS, in das Java eingebettet wird. Da JSPs und
Servlets sich die Arbeit teilen, steht in einem JSP nur noch ein Minimum an Java und es werden in
der Regel nur noch Daten ein- oder ausgegeben, da sich die Programmierlogik im Servlet befindet.
Die HTML- und CSS-Struktur sollte immer klar erkennbar bleiben, so können Sie problemlos
mit Webdesignern zusammenarbeiten, die Ihnen das Design zur Verfügung stellen.
Beginnen wir mit den CSS-Formaten unseres Projektes, die sich alle in einer Datei stil.css befinden, die weiter unten komplett eingefügt wurde. Ich will hier an dieser Stelle nur einige wenige
CSS-Formate näher erläutern, da der Schwerpunkt des Buches auf Datenbankanbindung mit db4o
liegen soll und ich nur die Grundstruktur von JSP-Seiten kurz darstellen möchte. Unsere erste
JSP-Seite soll aus drei Teilen bestehen: oben, links und unten. Die Bereiche haben normalerweise
unterschiedliche Funktionen, so befindet sich im oberen Teil oft das Firmenlogo, im linken die
Menüleiste und im unteren der Text und die Bilder.
98
4.4 JSP
Abbildung 4.11: Grundstruktur einer JSP-Seite im Browser
In unten stehender JSP-Seite, die im Moment nur aus HTML und CSS besteht, werden drei CSSKlassen mit einem div-Tag zugewiesen. CSS-Klassen können sowohl mit einem div-Tag als auch
mit einem span-Tag zugewiesen werden, wobei der Unterschied darin besteht, dass beim div-Tag
zusätzlich ein Zeilenumbruch in die HTML-Seite eingefügt wird. Das CSS-Format boxOben legt
den oberen Teil fest, boxLinks den linken und boxRechts den rechten.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<!--hier wird festgelegt auf welche CSS-Datei
zugegriffen wird-->
<link rel="stylesheet" href="../Styles/stil.css" type="text/css">
<title>Erstes JSP mit CSS</title>
</head>
<body>
<div class="boxOben">
oben
</div>
<div class="boxLinks">
99
4 Einstieg in die Webentwicklung: Servlets und JSPs
links
</div>
20
21
22
23
24
25
26
27
28
<div class="boxRechts">
unten
</div>
</body>
</html>
Listing 4.9: Erstes JSP mit CSS-Formatierungen(jspUndStile.jsp)
In unten stehender Datei stil.css gibt es mehrere CSS-Klassen, die unterschiedliche Formatierungsaufgaben erfüllen:
1. Die CSS-Klassen, die unsere HTML-Seite in drei Bereiche einteilen: boxOben, boxLinks
und boxRechts.
2. Die Textformatierungen font1, font2, font3 und font4 ermöglichen es Ihnen, die Schrift,
Schriftfarbe, Schriftgröße und Ausrichtung von Texten zu variieren.
3. Das Format für Hyperlinks menu legt das Format für den div-Bereich eines Hyperlinks der
linken Menüleiste fest. Die dazugehörige CSS-Klasse menu a formatiert den Normalzustand
eines Hyperlinks und menu a:hover den Rollover-Effekt.
4. Mit menub wird der div-Bereich der Links für die PDFs formatiert, mit menub a der
Normalzustand des Hyperlinks und mit menu a:hover der dazugehörige Rollover-Effekt.
5. Die Klasse button formatiert die Farbe und Schriftfarbe eines Submit-Buttons im Formular.
6. Die Klasse list ul legt die CSS-Formate für die Aufzählung, die Klasse list li formatiert die
darin enthaltenen Aufzählungszeichen und list die dazugehörigen Hyperlinks. Diese Formatgruppe wird dazu benutzt in unserem Content-Management-System die obere Menüleiste
zu erstellen, da so sichergestellt wird, dass sich die Hyperlinks nebeneinander befinden und
nicht untereinander.
7. Die Klassen box1, variablebox, box2 und box3 teilt unsere Seite in vier Teile: Die variablebox ist dafür vorgesehen ist, die obere Menüleiste zu beinhalten, box1 stellt Platz für ein
Titelbild bereit, box2 dient der linken Menüleiste und box3 Text und Bildern als Rahmen.
/*Legt die Hintergrundfarbe des HTML-Dokumentes fest*/
body{
background-color:#4E799E;
}
1
2
3
4
5
6
7
8
9
10
/*legt das Format für den div-Bereich der linken Menüleiste fest*/
.menu {
font-family:Comic Sans MS;
color:#ffffcc;
font-size:10pt;
100
4.4 JSP
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
text-decoration:none;
text-align:center;
width:150px;
height: 25px;
padding:10px;
margin:2px;
}
/*legt das Format für den Normalzustand der linken Menüleiste fest*/
.menu a{
font-family:Comic Sans MS;
text-align:center;
font-size:10pt;
text-decoration:none;
width:150px;
height: 25px;
color:#ffffcc;
background-color:#2B3E5E;
display:block;
padding:2px;
margin:2px;}
/*legt das Format für den Rollover-Effekt der linken Menüleiste fest*/
.menu a:hover {
font-family: Comic Sans MS;
font-size:10pt;
text-align:center;
width:150px;
height: 25px;
text-decoration:none;
color:#336699;
display:block;
padding:2px;
margin:2px;
}
/*legt das Format für den div-Bereich der Hyperlinks
für die PDFs fest, die sich unten in der Mitte befinden */
.menub {
font-family:Comic Sans MS;
color:#ffffcc;
font-size:10pt;
text-decoration:none;
text-align:center;
width:150px;
height: 25px;
padding:10px;
margin:2px 120px 2px 105px;}
101
4 Einstieg in die Webentwicklung: Servlets und JSPs
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
102
/*legt den Normalzustand des Hyperlinks für das PDF fest*/
.menub a{
font-family:Comic Sans MS;
text-align:center;
font-size:10pt;
text-decoration:none;
width:150px;
height: 25px;
color:#ffffcc;
background-color:#2B3E5E;
display:block;
padding:2px;
margin:2px 120px 2px 105px;}
/*legt den Rollover-Effekt des Hyperlinks für das PDF fest*/
.menub a:hover {
font-family: Comic Sans MS;
font-size:10pt;
text-align:center;
width:150px;
height: 25px;
text-decoration:none;
color:#336699;
display:block;
padding:2px;
margin:2px 120px 2px 105px;
}
/*Hier werden vier verschiedene Schriftgrößen festgelegt*/
.font1 {
font-family:Comic Sans MS;
font-size:20pt;
color:#ffffcc;
padding:10px;
margin:10px;
}
.font2 {font-family:Comic Sans MS;
font-size:14pt;
color:#FFFFCC;
padding:10px;
margin:10px;
}
.font3 {
font-family:Comic Sans MS;
font-size:12pt;
color:#FFFFCC;
padding:10px;
4.4 JSP
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
margin:10px;}
.font4 {
font-family:Comic Sans MS;
font-size:20pt;
text-align: center;
color:#FFFFCC;
padding:10px;
margin:10px;}
/*Hier wird Farbe und Schriftfarbe für den Submit-Button festgelegt*/
.button{
font-family:Comic Sans MS;
color:#ffffcc;
background-color:#2B3E5E;
font-size:10pt;
text-decoration:none;
text-align:center;
width:170px;
border:0px;
margin:2px 2px 2px 2px;
}
/*Format für Aufzählung*/
.list ul{
margin: 0;
padding: 0px;
list-style: none;
background-color:#2B3E5E;
text-align:center;
}
/*Format für einzelne Aufzählungspunkte*/
.list li{
float:right;
width:150px;
list-style: none;
text-align:center;
margin: 0 0 0 10px;
}
/*legt das Format für den div-Bereich der Hyperlinks
für die Menüleiste fest, die sich rechts oben befindet */
.list {
font-family:Comic Sans MS;
color:#ffffcc;
font-size:10pt;
text-decoration:none;
text-align:center;
103
4 Einstieg in die Webentwicklung: Servlets und JSPs
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
104
width:100%;
height: 100%;
display: block;
padding:5px 0 5px 0;
}
/*legt das Format für den Normalzustand der Hyperlinks
für die Menüleiste fest, die sich rechts oben befindet */
.list a{
font-family:Comic Sans MS;
text-align:center;
font-size:10pt;
text-decoration:none;
color:#ffffcc;
background-color:#2B3E5E;
width:100%;
height: 100%;
display: block;
padding:5px 0 5px 0;
}
/*legt das Format für den div-Bereich der Hyperlinks
für die Menüleiste fest, die sich rechts oben befindet */
.list a:hover {
font-family: Comic Sans MS;
font-size:10pt;
text-align:center;
width:100%;
height: 100%;
text-decoration:none;
color:#336699;
display:block;
padding:5px 0 5px 0;
}
/*Kasten der Höhe 100 und der Breite 800, der sich ganz oben befindet*/
.box1{
position:absolute;
top: 0px;
left: 0px;
height: 100 px;
width: 800px;
}
/*Kasten der Höhe 50 und der Breite 800, der sich
100 px vom oberen Rand weg befindet*/
.variablebox{
position:absolute;
max-height: 50px;
4.4 JSP
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
top: 100px;
left: 0px;
height: 50px;
width: 800px;
text-align:center;
}
/*Kasten der Höhe 400 und der Breite 200, der sich
150 px vom oberen Rand weg befindet*/
.box2{
position:absolute;
top: 150px;
left: 0px;
float:left;
width: 200px;
height: 400px;
padding: 0px 0px 0px 0px;
margin: 0px;
}
/*Kasten der Höhe 400 und der Breite 600, der sich
150 px vom oberen und 200px vom linken Rand weg befindet*/
.box3{
position:absolute;
top: 150px;
left: 200px;
text-align:center;
width: 600px;
height: 400px;
color:#FFFFCC;
}
/*Kasten der Höhe 100 und der Breite 800 px, der sich direkt
am linken oberen Rand befindet*/
.boxOben{
position:absolute;
top: 0px;
left: 0px;
width: 800px;
height: 100px;
border:1px solid black;
}
/*Kasten der Höhe 400 und der Breite 200 px,
der sich 100 px weg vom oberen Rand befindet*/
.boxLinks{
position:absolute;
top: 100px;
left: 0px;
105
4 Einstieg in die Webentwicklung: Servlets und JSPs
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
/*float:left bedeutet, dass sich das Element links
befindet und von dem nächsten Element umflossen wird*/
float:left;
width: 200px;
height: 400px;
border:1px solid black;
}
/*Kasten der Höhe 400 und der Breite 600 px, der sich 100 px weg
vom oberen Rand und 200 px weg vom linken Rand befindet*/
.boxRechts{
position:absolute;
top: 100px;
left: 200px;
text-align:center;
width: 600px;
height: 400px;
border:1px solid black;
}
Listing 4.10: CSS-Formate(stil.css)
Wie sieht die Grundstruktur unseres Content-Management-Systems aus? Wir haben vier Bereiche: box1, variablebox, box2 und box3. Wir haben zwei Menüleisten eine linke und eine obere
und der restliche Platz steht uns für Text, Bilder und Hyperlinks für PDFs zur Verfügung.
106
4.4 JSP
Abbildung 4.12: Grundstruktur unseres Content-Management-Systems
In unserem JSP werden die CSS-Formate wieder sowohl mit span- als auch mit div-Tags zugewiesen. Unsere obere Menüleiste besteht aus dem HTML-Tag ul für Aufzählungen und dem li-Tag
für die darin enthaltenen Aufzählungspunkte. Diese Aufzählungspunkte enthalten wiederum die
Hyperlinks unserer Menüleiste, die auf diese Art und Weise nebeneinander aufgereiht werden und
nicht - wie bei der linken Menüleiste - untereinander.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<link rel="stylesheet" href="../Styles/stil.css" type="text/css">
<title>menues.jsp</title>
</head>
<body>
<div class="box1">
<!--Wir fügen das Bild für unser Content-Management-System ein,
das sich im Ordner web/bilder befindet-->
<img src="../Bilder/oben.jpg" width="800" height="100">
</div>
<div class="variablebox">
107
4 Einstieg in die Webentwicklung: Servlets und JSPs
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<!--Wir fügen eine Aufzählung ein und weisen ihr das
CSS-Format list zu -->
<ul class="list">
<!--Wir fügen einzelne Aufzählungspunkte hinzu, in die
wir Hyperlinks einfügen. Die Hyperlinks haben ebenfalls
das Format list, für das wir auch Formate für Hyperlinks
festgelegt haben-->
<li><a href="">Home</a></li>
<li><a href="">Kontakt</a></li>
</ul>
</div>
<div class="box2">
<!--Wir weisen den Hyperlinks das CSS-Format menu zu -->
<span class="menu">
<a href="">Impressum</a>
</span>
</div>
<div class="box3">
<span class="font4">
<br><br>
Willkommen!
</span>
</div>
</body>
</html>
Listing 4.11: menues.jsp
Soweit unser Ausflug in die Welt des HTML und der CSS-Formatierungen.
4.4.2 Syntaxelemente in einem JSP
Bis jetzt besteht unser JSP nur aus HTML und CSS, so stellt sich folgende Frage: Wie können Sie
in ein JSP Java einbetten? Wir wollen uns im Folgenden auf diese Syntaxelemente konzentrieren:
Direktiven, Deklarationen, Skriptlets und Expressions.
Direktiven
Es gibt drei Arten von Direktiven, und zwar Page, Include und Taglib. Mit Direktiven können
Sie in einem JSP allgemeine Informationen festlegen. Lassen Sie uns mit der Page-Direktive und
108
4.4 JSP
seinem Attribut Language beginnen. Mit unten stehendem Attribut Language teilen Sie Tomcat
mit, dass Sie im JSP Java als Programmiersprache verwenden.
<%@page l a n g u a g e="j a v a " %>
Weitere Attribute der Page-Direktive sind contentType und pageEncoding:
<%@page contentType=" t e x t / html"%>
<%@page pageEncoding="UTF−8"%>
Diese Direktiven ersetzen den HTML-Tag, der als Mime-Type einer HTML-Seite HTML und den
Zeichensatz UTF-8 festlegt.
<meta http−e q u i v="Content−Type" c o n t e n t=" t e x t / html ; c h a r s e t=UTF−8">
Es ist zu empfehlen die JSP-Direktiven anstelle des Meta-Tags zu nehmen, da es ansonsten zu
Problemen, z. B. mit Umlauten kommt, wenn Sie Daten mit POST und GET versenden.
Gibt es weitere Page-Attribute? Ja: import. Mit dem Attribut import können Sie in Ihr JSP
Javaklassen importieren.
<%@ page import="j a v a . u t i l . C o l l e c t i o n , K l a s s e n . WebSite " %>
Wollen Sie z.B. Hyperlinks in eine separate Datei namens menue.jsp auslagern und diese Datei
in Ihr JSP einfügen, so brauchen Sie die Direktive Include.
<%@ i n c l u d e f i l e ="menue . j s p " %>
Unsere letzte Direktive, die Taglib-Direktive, macht es Ihnen möglich in einem JSP sogenannte
Custom Tags zu verwenden, die wir uns weiter unten näher ansehen werden.
<%@ t a g l i b u r i ="h t t p : / / j a v a . sun . com/ j s p / j s t l / c o r e " p r e f i x ="c"%>
Expressions
In einem JSP wird der Befehl print(); ersetzt durch eine Expression. Eine Expression haben wir
bereits kennen gelernt, und zwar die, die den Pfad des Projektes zurückgibt:
<%=r e q u e s t . g etC onte xt Pa th ()%>
In unten stehendem JSP, mit Namen expression.jsp, habe ich Ihnen ein paar Beispiele zusammengestellt, die Ihnen verdeutlichen sollen, dass eine Expression genauso funktioniert wie die
print()-Methode und im Servlet auch in eine solche umgewandelt wird. Das Servlet können Sie
einsehen, indem Sie in der Project-Ansicht mit der rechten Maustaste das JSP anklicken und
View Servlet auswählen.
1
2
3
4
5
6
7
8
9
<%@page language="java" %>
<%@page contentType="text/html"%>
<%@page pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
109
4 Einstieg in die Webentwicklung: Servlets und JSPs
<title>Expressions</title>
</head>
<body>
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<!--wird im Servlet durch folgendes ersetzt
out.print("hallo" );-->
<%="hallo" %>
<br>
<!--wird im Servlet durch folgendes ersetzt:
out.print(request.getContextPath());-->
<%=request.getContextPath()%>
<br>
<!--wird im Servlet durch folgendes ersetzt:
out.print(new java.util.Date() );-->
<%=new java.util.Date() %>
<br>
<!--wird im Servlet durch folgendes ersetzt:
out.print(false );-->
<%=false %>
</body>
</html>
Listing 4.12: Beispiele für Expressions:expression.jsp
Nachdem Sie das Projekt gestartet haben, erhalten Sie die folgende Ausgabe im Browser:
Abbildung 4.13: Ausgabe von expression.jsp
Merke: Mithilfe einer Expression können Sie nur Inhalte von bereits existierenden Methoden und
Variablen im Browser ausgeben. Es können keine neue Methoden oder Variablen erstellt werden.
Eine Expression dient nur zur Ausgabe von Daten. Und dieser Inhalt landet beim Umwandeln
eines JSPs in ein Servlet innerhalb der Methode _jspService() des Servlets.
110
4.4 JSP
Skriptlets
Skriptlets machen es Ihnen möglich Java in Ihr JSP zu integrieren. So können Sie in einem JSP
for-Schleifen erstellen oder lokale Variablen deklarieren.
<% S t r i n g s t r = new S t r i n g ( " I c h b i n e i n n e u e s S k r i p t l e t ! " ) ; %>
Skriptlets werden genauso wie Expressions nach dem Umwandeln in ein Servlet Teil der _jspService()Methode des Servlets.
Deklarationen
Wollen Sie Methoden, Klassen- und Instanzvariablen deklarieren, benötigen Sie so genannte
Deklarationen, da diese im Gegensatz zu Skriptlets und Expressions Javacode außerhalb der
Methode _jspService() des Servlets erzeugen.
<%! S t r i n g s t r = new S t r i n g ( " I c h b i n e i n n e u e s S k r i p t l e t ! " ) ; %>
Ein zusammenfassendes Beispiel
In unten stehendem JSP mit Namen syntaxElemente.jsp werden alle vier Syntaxelemente nochmals mit einem Beispiel verdeutlicht.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<!--Mit
<%@page
<%@page
<%@page
Direktiven werden allgemeine Informationen im JSP festgelegt-->
language="java" %>
contentType="text/html"%>
pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>Syntaxelemente eines JSP</title>
</head>
<body>
<!--Mit einer Deklaration koennen Sie Klassen- und
Instanzvariablen erzeugen.-->
<%! String st = new String("Guten Tag");%>
<!--Mit einem Skriptlet koennen Sie lokale Variablen
deklarieren und der entsprechende Code landet in
der _jspService-Methode.-->
<% String s = new String("Hallo");%>
<!--Mit einer Expression koennen Sie Werte von Variablen und
Methoden im Browser ausgeben und der entsprechende Code
landet in der _jspService-Methode.-->
<%=st%>
<br>
111
4 Einstieg in die Webentwicklung: Servlets und JSPs
<%=s%>
29
30
31
32
</body>
</html>
Listing 4.13: syntaxElemente.jsp
Es werden die Werte von beiden Variablen im Browser ausgegeben:
Abbildung 4.14: Ausgabe von syntaxElemente.jsp im Browser
Oben stehendes JSP wird in folgendes Servlet umgewandelt:
package Kap11;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;
public final class syntaxElemente_jsp
extends org.apache.jasper.runtime.HttpJspBase
implements org.apache.jasper.runtime.JspSourceDependent {
String st = new String("Hallo");
private static java.util.List _jspx_dependants;
public Object getDependants() {
return _jspx_dependants;
}
public void _jspService(HttpServletRequest request,
HttpServletResponse response)
throws java.io.IOException, ServletException {
JspFactory _jspxFactory = null;
PageContext pageContext = null;
HttpSession session = null;
ServletContext application = null;
ServletConfig config = null;
112
4.4 JSP
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
JspWriter out = null;
Object page = this;
JspWriter _jspx_out = null;
PageContext _jspx_page_context = null;
try {
_jspxFactory = JspFactory.getDefaultFactory();
response.setContentType("text/html;charset=UTF-8");
pageContext = _jspxFactory.getPageContext(this, request,
response, null, true, 8192, true);
_jspx_page_context = pageContext;
application = pageContext.getServletContext();
config = pageContext.getServletConfig();
session = pageContext.getSession();
out = pageContext.getOut();
_jspx_out = out;
out.write("<!--Mit Direktiven werden allgemeine\n");
out.write("Informationen im JSP festgelegt-->\n");
out.write("\n");
out.write("\n");
out.write("\n");
out.write("\n");
out.write("<!DOCTYPE HTML PUBLIC " +
"\"-//W3C//DTD HTML 4.01 " +
"Transitional//EN\"\n");
out.write("\"http://www.w3.org/TR/html4/loose.dtd\">\n");
out.write("\n");
out.write("<html>\n");
out.write("
<head> \n");
out.write("
<title>Syntaxelemente eines JSP" +
"</title>\n");
out.write("
</head>\n");
out.write("
<body>\n");
out.write("
\n");
out.write("
<!--Mit einer Deklaration können" +
" Sie\n");
out.write("
Klassen- und Instanzvariablen " +
"erzeugen-->\n");
out.write("
");
out.write(" \n");
out.write("
\n");
out.write("
<!--Mit einem Skriptlet können Sie\n");
out.write("
lokale Variablen erzeugen-->\n");
out.write("
");
String s = new String("Hallo");
out.write("\n");
out.write("
\n");
out.write("
<!--Mit einer Expression können " +
113
4 Einstieg in die Webentwicklung: Servlets und JSPs
"Sie\n");
out.write("
Werte im Browser ausgeben-->\n");
out.write("
");
out.print(s);
out.write("\n");
out.write("
\n");
out.write("
</body>\n");
out.write("</html>\n");
} catch (Throwable t) {
if (!(t instanceof SkipPageException)){
out = _jspx_out;
if (out != null && out.getBufferSize() != 0)
out.clearBuffer();
if (_jspx_page_context != null) _jspx_page_context.
handlePageException(t);
}
} finally {
if (_jspxFactory != null) _jspxFactory.
releasePageContext(_jspx_page_context);
}
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
}
}
Listing 4.14: syntaxElemente_jsp.java
Verwenden der JSP Standard Tag Library (JSTL)
Das wichtigste Ziel unserer JSP-Datei ist es, möglichst wenig Javaprogrammierzeilen zu enthalten
und die Programmierlogik in das Servlet auszulagern. So sollten in einem JSP nur Daten ein- und
ausgegeben werden. Dies reduziert den Programmieraufwand und erleichtert die Zusammenarbeit
von Programmierern und Webdesignern. Die oben genannten Syntaxelemente erreichen dieses
Ziel nur unzureichend, so wurde zusätzlich die JSP Standard Tag Library (JSTL) eingeführt, die
auch Custom Tags oder Taglibs genannt wird.
Wollen Sie die Custom Tags verwenden, benötigen Sie zwei Bibliotheken, und zwar die jstl.jar
und die standard.jar. Beide Bibliotheken hat NetBeans bereits zu Beginn in das Projekt integriert
und Sie können sie in der Project-Ansicht unter Libraries finden. Wollen Sie in Ihrem JSP Taglibs
verwenden, müssen Sie in Ihr Dokument die bereits weiter oben kennen gelernt Taglib-Direktive
schreiben.
Auslesen von Parametern und Attributen
Wie können Sie Parameter und Attribute im JSP auslesen? Der Befehl <c:out> entspricht der
print()-Methode, mit ${Formatierung} können Sie den Wert eines Attributes und mit ${param.Formatierung} den Wert eines Parameters aus dem Request auslesen. Weiter oben haben
wir ein Servlet und ein JSP (ZweitesServlet.java und zweitesJSP.jsp) erstellt, das zu unten stehendem JSP Daten sendet.
<%@page language="java" %>
1
114
4.4 JSP
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<%@page contentType="text/html"%>
<%@page pageEncoding="UTF-8"%>
<!--Hier legen Sie fest, dass Sie im JSP Taglibs
einsetzen moechten-->
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>drittesJSP.jsp</title>
</head>
<body>
<!--Hier wird der Wert des
Session-Attributes ausgegeben-->
<c:out value="${Formatierung}" />
<br><br>
<!--Hier wird der Wert des
Request-Parameters ausgegeben-->
<c:out value="${param.Formatierung}" />
</body>
</html>
Listing 4.15: drittesJSP.jsp
Auslesen von Daten aus Listen und Maps
Mit dem Tag <c:forEach>-Tag werden Elemente aus einer Liste und einer Map ausgelesen und
<c:forEach> entspricht der for-Schleife in Java. Der Tag besteht aus drei Teilen: dem Anfangstag
<c:forEach>, der Ausgabe und dem Endtag </c:forEach>.
In der Klasse ListenUndMaps.java erstellen wir sowohl eine ArrayList als auch eine HashMap
und fügen jeweils zwei Elemente hinzu, übergeben beide der Session als Attribut und senden
die Daten an das JSP mit Namen ListenUndMaps.jsp weiter. Da Session-Attribute Teile des
Requests sind, also der Anfrage vom Client an den Server, brauchen wir unten stehendes JSP
mit einem Hyperlink, das einen Request erzeugt und diesen an unser Servlet weitersendet.
1
2
3
4
5
6
<%@page language="java" %>
<%@page contentType="text/html"%>
<%@page pageEncoding="UTF-8"%>
<html>
<head>
115
4 Einstieg in die Webentwicklung: Servlets und JSPs
<title>ausgaben.jsp</title>
</head>
<body>
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!--Der Hyperlink hat das ListenUndMaps-Servlet
mit Namen ListenUndMaps als Ziel. Zusaetzlich wird
mit GET die Variable aus versendet, die den Wert ja
hat. Die Variable aus wird bei GET mit dem ? angehaengt.-->
<a href="<%=request.getContextPath()%>/ListenUndMaps?aus=ja">
Auslesen
</a>
</body>
</html>
Listing 4.16: ausgaben.jsp
package Kap11;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import
import
import
import
import
import
import
import
Datenbankentwurf.Formatierung;
java.io.IOException;
java.util.ArrayList;
java.util.HashMap;
javax.servlet.http.HttpServlet;
javax.servlet.http.HttpServletRequest;
javax.servlet.http.HttpServletResponse;
javax.servlet.ServletException;
public class ListenUndMaps extends HttpServlet {
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
/*Ist der Wert des Parameters aus ausgaben.jsp nicht leer,
*dann soll der Code innerhalb der if-Struktur
*ausgeführt werden.*/
if(request.getParameter("aus")!=null){
/*Wir erstellen 2 Formatierungsobjekte.*/
Formatierung f = new Formatierung("font1");
Formatierung fo = new Formatierung("font2");
/*Wir erstellen eine ArrayList.*/
ArrayList<Formatierung> liste = new ArrayList<Formatierung>();
116
4.4 JSP
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
/*Wir fügen der ArrayList 2 Formatierungsobjekte
*hinzu.*/
liste.add(f);
liste.add(fo);
/*Wir übergeben die ArrayList der Session als
*Attribut*/
request.getSession().setAttribute("liste", liste);
/*Wir instanziieren eine HashMap()*/
HashMap fehler = new HashMap();
/*Wir fügen der HashMap zwei Elemente hinzu*/
fehler.put("eins", "Hier fehlt etwas!");
fehler.put("zwei", "Hier fehlt noch etwas!");
/*Wir übergeben die HashMap der Session als
*Attribut*/
request.getSession().setAttribute("fehler", fehler);
/*Das Servlet wird zum JSP ListenUndMaps.jsp umgeleitet*/
request.getRequestDispatcher("/Kap11/ListenUndMaps.jsp").
forward(request, response);
}
}
protected void doPost(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
Listing 4.17: Erstellen einer Liste und einer Map
Anschließend lesen wir in unten stehendem JSP mithilfe des Tags <c:forEach> die Elemente der
ArrayList und der HashMap wieder aus. Mit formatierung.name können Sie direkt den Namen
eines CSS-Formats auslesen, wobei die Getter-Methode getName() durch name ersetzt wird.
Bei Maps können Sie auf das Element selbst mit value, auf den Schlüssel mit key und auf ein
bestimmtes Element mit dem Namen des Schlüssels zugreifen.
1
2
3
4
5
6
7
8
<%@page language="java" %>
<%@page contentType="text/html"%>
<%@page pageEncoding="UTF-8"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
117
4 Einstieg in die Webentwicklung: Servlets und JSPs
<html>
<head>
<title>ListenUndMaps.jsp</title>
</head>
<body>
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<!--forEach liest alle Elemente unserer ArrayList aus-->
<c:forEach var="formatierung" items="${liste}">
<!--mit formatierung.name koennen Sie direkt
auf den Namen der Formatierung zugreifen, es
ersetzt formatierung.getName() -->
<c:out value="${formatierung.name}" />
<!--Endtag nicht vergessen!-->
</c:forEach>
<br>
<!--forEach liest alle Elemente unserer HashMap aus-->
<c:forEach var="fehlerausgabe" items="${fehler}">
<!--mit fehlerausgabe.key wird direkt auf den
Schluessel der Map zugegriffen-->
<c:out value="${fehlerausgabe.key}" />
<br>
<!--mit fehlerausgabe.value wird direkt auf das
Element der Map zugegriffen-->
<c:out value="${fehlerausgabe.value}" />
<br>
<!--Endtag nicht vergessen!-->
</c:forEach>
<br>
<!--Sie koennen auch direkt mithilfe des Schluessels
auf ein bestimmtes Element zugreifen-->
<c:out value="${fehler.eins}" />
</body>
</html>
Listing 4.18: Auslesen der ArrayList und der HashMap im JSP(ListenUndMaps.jsp)
Vergessen Sie nicht, dass das Servlet in die web.xml eingetragen werden muss:
118
4.4 JSP
1
2
3
4
5
6
7
8
<servlet>
<servlet-name>ListenUndMaps</servlet-name>
<servlet-class>Kap11.ListenUndMaps</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ListenUndMaps</servlet-name>
<url-pattern>/ListenUndMaps</url-pattern>
</servlet-mapping>
Listing 4.19: listenWeb.xml
Starten Sie nun das Projekt, rufen das ausgabe.jsp auf und klicken auf den Hyperlink, so erhalten
Sie folgende Ausgabe im Browser:
Abbildung 4.15: Ausgabe im Browser
Der <c:set>-Tag
Mit dem <c:set>-Tag können Attribute gesetzt werden, und zwar für folgende Geltungsbereiche:
Page, Request, Session und Context/Application. Der Geltungsbereich Page ist der Kleinste,
da das Attribut nur auf dieser Seite gültig ist, in dem das Attribut definiert wurde. Wird kein
Geltungsbereich (scope) festgelegt, gilt automatisch der Geltungsbereich Page, sprich die Variable
ist nur auf dieser Seite gültig.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<%@page language="java" %>
<%@page contentType="text/html"%>
<%@page pageEncoding="UTF-8"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>Variablen und Attribute setzen</title>
</head>
<body>
119
4 Einstieg in die Webentwicklung: Servlets und JSPs
<c:set var="eins" value="Hallo" />
<c:out value="${eins}"/>
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<br>
<c:set var="zwei" scope="session" value="Guten Tag" />
<c:out value="${zwei}"/>
<br>
<c:set var="drei" scope="request" value="Tschuess" />
<c:out value="${drei}"/>
<br>
<c:set var="vier" scope="application" value="Guten Morgen" />
<c:out value="${vier}"/>
</body>
</html>
Listing 4.20: Festlegen der Geltungsbereiche von Attributen in einem JSP (set.jsp)
Sie erhalten im Browser das unten stehende Ergebnis:
Abbildung 4.16: set.jsp
Kontrollstruktur: <c:if>-Tag
Der <c:if>-Tag entspricht der bereits bekannten if-Struktur aus Java, wobei es allerdings keinen
else-Zweig gibt. Sie sind also darauf angewiesen, statt eines else-Zweiges wieder ein <c:if>-Tag
zu verwenden.
<%@page language="java" %>
<%@page contentType="text/html"%>
<%@page pageEncoding="UTF-8"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
1
2
3
4
5
6
7
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
120
4.4 JSP
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<html>
<head>
<title>If-Struktur</title>
</head>
<body>
<c:set var="eins" value="eins" />
<c:set var="zwei" value="zwei" />
<!--Fuer den Fall, dass die Variable eins
existiert, soll der Inhalt der Variablen eins
ausgegeben werden.-->
<c:if test="${eins != null}" >
<c:out value="${eins}"/>
</c:if>
<br>
<!--Fuer den Fall, dass die Variable zwei nicht
existiert, soll der Inhalt der Variablen eins
ausgegeben werden.-->
<c:if test="${zwei == null}" >
<c:out value="${eins}"/>
</c:if>
</body>
</html>
Listing 4.21: kontrollstrukturIf.jsp
Da nur die erste Bedingung zutrifft, wird im Browser eins ausgegeben:
Abbildung 4.17: Ausgabe von kontrollstrukturIf.jsp
Merke: Sie benötigen für die Kontrollstruktur sowohl den Anfangstag <c:if> als auch den Endtag
</c:if>.
121
4 Einstieg in die Webentwicklung: Servlets und JSPs
Kontrollstruktur: <c:choose>-Tag
Gibt es auch eine switch-Struktur in den Taglibs? Ja! Das <c:choose>-Tag mit den Fällen
<c:choose> und dem Default-Fall <c:otherwise>. Der <c:choose>-Tag unterscheidet sich allerdings geringfügig von der gewohnten Vorgehensweise in Java. Es wird immer nur ein Fall
des <c:choose>-Tag ausgeführt, und zwar der erste Fall <c:when>, auf den true zutrifft, auch
wenn mehrere zutreffen. Ein break, wie es in der switch-Struktur unbedingt erforderlich ist, um
nur einen Fall ausführen zu lassen, ist bei der Kontrollstruktur <c:choose> nicht notwendig.
Trifft kein Fall zu, wird der Default-Fall, <c:otherwise> durchgeführt. In der Kontrollstruktur
<c:choose> kann der Tag <c:otherwise> weggelassen werden.
<%@page language="java" %>
<%@page contentType="text/html"%>
<%@page pageEncoding="UTF-8"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>Choose-Struktur</title>
</head>
<body>
<c:set var="eins" value="Sonnenschein" />
<c:set var="zwei" value="Regen" />
<c:choose>
<c:when test="${eins != null}" >
<c:out value="${eins}"/>
</c:when>
<c:when test="${zwei != null}" >
<c:out value="${zwei}"/>
</c:when>
<c:otherwise>
Ich bin der Standardfall
</c:otherwise>
</c:choose>
</body>
</html>
Listing 4.22: kontrollstrukturChoose.jsp
122
4.5 Filter
Es treffen zwar beide Fälle zu, aber nur der erste zutreffende Fall wird tatsächlich abgearbeitet:
Abbildung 4.18: Ausgabe von kontrollstrukturChoose.jsp
4.5 Filter
Was ist ein Filter? Ein Filter steht innerhalb der Verarbeitungskette zwischen Client und Server.
Sendet ein Client eine Anfrage an den Server oder sendet ein Server eine Antwort an den Client
wird der Filter vorher ausgeführt. Wobei sich allerdings die Methoden des Requests und des
Responses erheblich unterscheiden. Sie können nur aus dem Request Parameter auslesen und
Attribute setzen. Welche Aufgaben kann ein Filter innerhalb einer Webapplikation übernehmen?
Es sind u.a. die Folgenden:
1. Beim Login kann das Passwort überprüft werden.
2. Es kann überprüft werden, ob eine Session noch gültig ist.
3. Es können Benutzerbewegungen protokolliert werden.
4. Es können Daten aktualisiert werden.
Kehren wir zu unserem Content-Management-System zurück: Hier soll unten stehender FormatierungsFilter.java folgende Aufgaben übernehmen: Erstens soll er jedes Mal wenn Daten hinzugefügt oder gelöscht werden, alle Daten in den JSP-Seiten auf den neuesten Stand bringen.
Und zweitens soll er uns Daten zur Verfügung stellen, wenn die Webseite zum ersten Mal durch
den Client aufgerufen wird. Warum gibt es keine Daten aus der Datenbank, wenn die Webseite
zum ersten Mal aufgerufen wird? Wir erinnern uns, Attribute und Parameter, die in einem JSP
ausgelesen werden, werden aus dem Request ausgelesen. Ein Request besteht aber erst ab dem
Zeitpunkt, an dem der Client eine Anfrage zum Server schickt und dies ist bei erstmaligem Aufruf
nicht der Fall, da es zu diesem Zeitpunkt nur eine Antwort gibt, also einen Response, vom Server
zum Client gesendet und keinen Request.
Wie erstellen wir einen Filter? Ein Filter wird erstellt, indem Sie das Interface Filter und seine drei
Methoden implementieren. Die Methoden init(), doFilter() und destroy() stellen Anfangspunkt,
Leben und Endpunkt eines Filters dar.
1
2
3
4
5
6
7
8
package Eingang;
import
import
import
import
import
import
Datenbankabfragen.FormatierungDaten;
Datenbankentwurf.Formatierung;
Datenbankentwurf.Text;
java.io.IOException;
java.util.ArrayList;
javax.servlet.Filter;
123
4 Einstieg in die Webentwicklung: Servlets und JSPs
import
import
import
import
import
import
import
import
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
javax.servlet.FilterChain;
javax.servlet.FilterConfig;
javax.servlet.ServletException;
javax.servlet.ServletRequest;
javax.servlet.ServletResponse;
javax.servlet.http.HttpServletRequest;
javax.servlet.http.HttpServletResponse;
javax.servlet.http.HttpServletResponseWrapper;
public class FormatierungFilter implements Filter {
private FilterConfig filterConfig = null;
/*Die init()-Methode wird bei der Instanziierung des
Filters aufgerufen und es wird über das FilterConfig und
die Methode getServletContext() einen Zugriff auf
den ServletContext ermöglicht.*/
public void init(FilterConfig filterConfig) throws
ServletException {
this.filterConfig = filterConfig;
}
/*In der doFilter()-Methode werden die Aufgaben
des Filters durchgeführt.*/
public void doFilter(ServletRequest request,
ServletResponse response, FilterChain chain)
throws IOException, ServletException {
/*Das ServletRequest request muss in ein
HttpServletRequest gecastet werden, da
es sich um eine Webanwendung handelt*/
HttpServletRequest req = (HttpServletRequest) request;
/*Das Attribut formatierung soll aus der Session
entfernt werden*/
req.getSession().removeAttribute("formatierung");
/*Wir lesen alle Formatierungsobjekte aus der
Datenbank aus. Da wir im Projekt mit dem
Embedded-Modus arbeiten, müssen wir der Methode auslesen
zusätzlich ein Objekt der Klasse HttpServletRequest req
übergeben*/
FormatierungDaten format = new FormatierungDaten();
ArrayList<Formatierung> formatierung = format.auslesen(req);
/*Wir übergeben die soeben ausgelesene ArrayList der
124
4.5 Filter
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
Session als Attribut*/
req.getSession().setAttribute("formatierung", formatierung);
/*Mit der Ausgabe auf der Konsole können Sie überprüfen, ob
der Filter durchgeführt wurde*/
System.out.println("aktuelle Daten");
/*Der Request und der Response wird der FilterChain
übergeben, sprich gibt es mehrere Filter, werden diese
jetzt in der Reihenfolge, wie sie in der web.xml stehen,
abgearbeitet*/
chain.doFilter(request, response);
}
/*Diese Methode wird als letztes aufgerufen bevor der Server
geschlossen wird und Sie können in dieser Methode
gegebenenfalls Ressourcen freigeben*/
public void destroy() {
}
}
Listing 4.23: FormatierungFilter.java
Filter müssen genau wie Servlets in die web.xml eingetragen werden, da ihm spezielle Funktionen
zufallen und dies Tomcat mitgeteilt werden muss. Im Filter-Mapping können Sie festlegen, ob
der Filter immer ausgeführt wird, oder nur bei bestimmten Servlets. In unserem Projekt ist
es sinnvoll, den Filter FormatierungFilter.java nur bei einem bestimmten Servlet ablaufen zu
lassen, da es nicht zweckmäßig ist, bei jeder x-beliebigen Abfrage die Formatierungsobjekte zu
aktualisieren und auf diesem Weg, die Datenbank über Gebühr in Anspruch zu nehmen.
Gibt es mehrere Filter werden Sie in der Reihenfolge, in der Sie in der web.xml stehen, abgearbeitet. Dies ist die so genannte FilterChain. Wobei Sie allerdings beachten sollten, dass für
den Response und den Request im Filter unterschiedliche Methoden zur Verfügung stehen. Sie
können nur aus dem Request Parameter auslesen und Attribute setzen. Wenn Sie also die Daten
des Request weiterleiten wollen, können Sie dies mit FORWARD des RequestDispatchers tun.
Dies müssen Sie im <dispatcher>-Element des Filter-Mappings separat festlegen. Das gleiche
gilt für die Fälle Include und ErrorPage.
1
2
3
4
5
6
7
8
9
10
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<filter>
<!-- Name des Filters -->
<filter-name>FormatierungFilter</filter-name>
125
4 Einstieg in die Webentwicklung: Servlets und JSPs
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<!-- Ort, an dem der Filter gespeichert wurde -->
<filter-class>Eingang.FormatierungFilter</filter-class>
</filter>
<filter-mapping>
<!-- Name des Filters -->
<filter-name>FormatierungFilter</filter-name>
<!-- Filter wird immer ausgefuehrt -->
<url-pattern>/*</url-pattern>
<!-- oder Filter wird bei folgendem Servlet ausgefuehrt:
<servlet-name>BestimmtesServlet</servlet-name>-->
<!-- Filter wird ausgefuehrt, wenn ein Request mit
dem RequestDispatcher weitergeleitet wurde-->
<dispatcher>FORWARD</dispatcher>
<!-- Filter wird bei einem Request ausgefuehrt, dies
ist der Standard, fuer den Fall, dass keine dispatcherElemente festgelegt wurden-->
<dispatcher>REQUEST</dispatcher>
<!-- Filter wird ausgefuehrt, wenn eine Datei
mit inlude eingebunden wurde-->
<dispatcher>INCLUDE</dispatcher>
<!-- Filter wird im Zusammenhang mit einer ErrorPage
ausgefuehrt -->
<dispatcher>ERROR</dispatcher>
</filter-mapping>
</web-app>
Listing 4.24: Filter-Mapping in der web.xml (formatierungWeb.xml)
Wollen Sie einen Filter erstellen, der ausgeführt wird, wenn Daten vom Server zum Client gesendet werden, können Sie nur auf Methoden des Response zurückgreifen. Sie benötigen wie oben
beim Servlet wieder ein PrintWriter-Objekt, das es Ihnen ermöglicht, Daten in einen Puffer und
anschließend in die HTML-Datei zu schreiben.
package Kap11;
1
2
3
4
import java.io.IOException;
import java.io.PrintWriter;
126
4.5 Filter
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import
import
import
import
import
import
import
import
import
import
java.util.ArrayList;
javax.servlet.Filter;
javax.servlet.FilterChain;
javax.servlet.FilterConfig;
javax.servlet.ServletException;
javax.servlet.ServletRequest;
javax.servlet.ServletResponse;
javax.servlet.http.HttpServletRequest;
javax.servlet.http.HttpServletResponse;
javax.servlet.http.HttpServletResponseWrapper;
public class PrintWriterFilter implements Filter {
private FilterConfig filterConfig = null;
public void init(FilterConfig filterConfig)
throws ServletException {
this.filterConfig = filterConfig;
}
public void doFilter(ServletRequest request,
ServletResponse response, FilterChain chain)
throws IOException, ServletException {
/*Das Objekt ServletResponse response muss in ein
HttpServletResponse-Objekt gecastet werden, da
es sich um eine Webanwendung handelt*/
HttpServletResponse res = (HttpServletResponse)response;
res.setContentType("text/html");
/*Sie brauchen wieder - wie beim Servlet - das PrintWriter-Objekt
um zu dem Response Daten hinzufügen zu können*/
PrintWriter out = res.getWriter();
out.println("<html>");
out.println(" <head>");
out.println("
<title>HelloWorld</title>");
out.println(" </head>");
out.println(" <body>");
out.println("
Hello World");
out.println(" </body>");
out.println("</html>");
chain.doFilter(request, response);
}
127
4 Einstieg in die Webentwicklung: Servlets und JSPs
public void destroy() {
54
55
56
57
58
}
}
Listing 4.25: PrintWriterFilter.java
4.6 Deployment Descriptor: web.xml
Der Deployment Descriptor, die web.xml, beschreibt für den Webserver die Aufgabenteilung
verschiedener Teile der Webapplikation und legt Parameter fest. Wie wir auf den vergangenen
Seiten gesehen haben, müssen Servlets und Filter eingetragen werden, und es kann die Dauer einer
Session bestimmt werden. Was wir noch nicht gesehen haben: Wie legen wir Parameter in der
web.xml fest und welche Parameter gibt es? Es gibt zweierlei Parameter: den Servlet-Parameter
und den Context-Parameter. Der Servlet-Parameter steht Ihnen für ein bestimmtes Servlet zur
Verfügung und wird innerhalb des Servlet Tags festgelegt. Wohingegen der Context-Parameter
einen größeren Geltungsbereich besitzt, Sie können innerhalb des gesamten Projektes auf diesen
Parameter zugreifen. Sie legen einmal z.B. eine E-Mail-Adresse an, dann steht Sie Ihnen in der
gesamten Webapplikation zur Verfügung. Sollte sich Ihre E-Mail-Adresse ändern, müssen Sie sie
nur noch an einer Stelle ändern.
Parameter des Contexts und des Servlets können mit der getInitParameter()-Methode abgefragt
werden. Auf den Context-Parameter können Sie nur zugreifen, wenn Sie sich vorher Zugriff auf
den Context verschaffen:
getServletContext ( ) . getInitParameter (" email ")
Wollen Sie den Parameter des Servlets abfragen, müssen Sie zuerst die Methode getServletConfig() ausführen:
g e t S e r v l e t C o n f i g ( ) . getInitParameter (" email ")
Wie legen wir Servlet-Parameter und Context-Parameter an? In der web.xml erstellen wir für
den Context-Parameter ein <context-param>-Element und für den Servlet-Parameter innerhalb
des <servlet>-Element ein <init-param>-Element.
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- Folgender Parameter gilt fuer die ganze Webapplikation: -->
<context-param>
<param-name>Leiter</param-name>
<param-value>Herr Markus Muster</param-value>
</context-param>
<servlet>
128
4.6 Deployment Descriptor: web.xml
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<servlet-name>InitParameter</servlet-name>
<servlet-class>Kap11.InitParameter</servlet-class>
<!-- Folgender Parameter gilt fuer das Servlet InitParameter: -->
<init-param>
<param-name>Mail</param-name>
<param-value>[email protected]</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>InitParameter</servlet-name>
<url-pattern>/InitParameter</url-pattern>
</servlet-mapping>
</web-app>
Listing 4.26: initParameterWeb.xml
Erstellen wir ein Servlet mit Namen InitParameter.java und greifen auf die Parameter mit den
entsprechenden Methoden zu, gibt Tomcat folgendes auf der Konsole aus:
Abbildung 4.19: Ausgabe des Context-Parameters und des Servlet-Parameters
Wohingegen Sie im JSP nur auf den Context-Parameter zugreifen können. Und für den ServletParameter wird null ausgegeben:
129
4 Einstieg in die Webentwicklung: Servlets und JSPs
Abbildung 4.20: Ausgabe im Browser des InitParamer.jsp
Das zugehörige JSP sieht wie folgt aus:
<%@page language="java" %>
<%@page contentType="text/html"%>
<%@page pageEncoding="UTF-8"%>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>InitParameter.jsp</title>
</head>
<body>
<%
/*Sie koennen nicht auf den Parameter Mail
zugreifen, da er nur fuer das Servlet
InitParamter.java gueltig ist.*/
ServletConfig sc = getServletConfig();
String s = sc.getInitParameter("Mail");
/*Aber das JSP kann auf den Context-Parameter
zugreifen.*/
ServletContext scon = getServletContext();
String st = scon.getInitParameter("Leiter");
%>
Servlet-Parameter:
<br>
<%=s%>
<br><br>
Context-Parameter:
<br>
<%=st%>
130
4.7 Listener
37
38
</body>
</html>
Listing 4.27: initParameter.jsp
4.7 Listener
Wollen Sie wissen, wann eine Session oder ein Request beginnt? Oder: Interessiert es Sie, wann der
Session Attribute hinzugefügt werden? Dann brauchen Sie einen Listener. Ein Listener „hört“ auf
alle Ereignisse Ihrer Applikation und ist in der Lage, sie aufzuzeichnen oder sie zu beeinflussen.
Also: Immer wenn irgendetwas Interessantes in Ihrer Web-Applikation passiert, der Listener ist
dabei.
4.7.1 HttpSessionAttributeListener
Mit dem HTTPSessionAttributeListener können Sie beobachten, wann Attribute der Session
hinzugefügt oder aus ihr wieder entfernt werden. Wie wird ein solcher Listener erstellt? Als
Erstes brauchen wir einen Eintrag in der web.xml:
1
2
3
4
5
<listener>
<listener-class>
Listeners.MyHttpSessionAttributeListener
</listener-class>
</listener>
Listing 4.28: webListener.xml
Als Nächstes erstellen wir einen Listener mit Namen MyHttpAttributeListener, der das Interface
HttpSessionAttributeListener und seine Methoden implementiert. Diesen Methoden wird als Parameter ein Event übergeben: das so genannte HttpSessionBindingEvent. Dieses Event macht es
Ihnen möglich, die Session-ID, die dazugehörigen Attribute und deren Aktionen auszulesen.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package Listeners;
import javax.servlet.http.HttpSessionAttributeListener;
import javax.servlet.http.HttpSessionBindingEvent;
public class MyHttpSessionAttributeListener implements
HttpSessionAttributeListener {
/*Es werden alle Attribute beobachtet, die der Session hinzugefügt
werden.*/
public void attributeAdded(HttpSessionBindingEvent event) {
/*Mit getSession().getId() wird die entsprechend ID der
Session ausgelesen. */
131
4 Einstieg in die Webentwicklung: Servlets und JSPs
System.out.println("HttpSessionAttribute sessionId=" +
event.getSession().getId());
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/*Es wird der Name des Session-Attributs ausgelesen.*/
System.out.println("HttpSessionAttribute added: name=" +
event.getName() );
/*Es wird der Wert des Session-Attributes ausgelesen.*/
System.out.println("value=" +event.getValue());
}
/*Es werden die gelöschten Attribute beobachtet.*/
public void attributeRemoved(HttpSessionBindingEvent event) {
System.out.println("HttpSessionAttribute removed: name:"
+event.getName());
System.out.println("value=" +event.getValue());
}
/*Es werden die ersetzten Session-Attribute beobachtet.*/
public void attributeReplaced(HttpSessionBindingEvent event) {
System.out.println("HttpSessionAttribute replaced: name:"
+event.getName());
System.out.println("value=" +event.getValue());
}
}
Listing 4.29: MyHttpSessionAttributeListener.java
Die unten stehende Ausgabe erhalten Sie, wenn Sie unser Projekt (vgl. Kapitel Unser ContentManagement-System) mit einer gefüllten Datenbank starten und auf einen Hyperlink klicken:
HttpSessionAttribute removed: name:webSiteMenu
value=[Datenbankentwurf.Link@1cfa965, Datenbankentwurf.Link@1843ca4]
HttpSessionAttribute replaced: name:webSiteTextFormatierung
value=[Datenbankentwurf.Formatierung@476914]
HttpSessionAttribute replaced: name:webSiteBild
value=[Datenbankentwurf.Bild@197871d]
HttpSessionAttribute replaced: name:webSitePdf
value=[Datenbankentwurf.PDF@187b5ff]
HttpSessionAttribute sessionId=9D0028D256C5B321F2CF7F317B4A4B83
HttpSessionAttribute added: name=webSiteMenu
value=[Datenbankentwurf.Link@d0005e, Datenbankentwurf.Link@18a270a]
HttpSessionAttribute removed: name:ListLink
132
4.7 Listener
4.7.2 ServletContextListener
Was macht ein ServletContextListener? Er kann den Anfang und das Ende des Servlet-Contexts
im Auge behalten. Außerdem ist seine Methode contextInitialized() die erste Methode, die durchgeführt wird, wenn die Web-Applikation gestartet wird. So kann der ServletContextListener dazu
verwendet werden, eine Datenbank zu öffnen und wieder zu schließen.
Wie wird ein ServletContextListener erzeugt? Sie erstellen eine Klasse, die den ServletContextListener und seine Methoden contextInitalized() und contextDestroyed() implementieren. Ihnen
wird ein ServletContextEvent als Parameter übergeben. In der Methode contextInitialized() kann
die Datenbank geöffnet werden und in der Methode contextInitialzed() wieder geschlossen werden.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package Listeners;
import javax.servlet.*;
public class MyServletContextListener
implements ServletContextListener {
public void contextInitialized(ServletContextEvent event) {
/*Hier wird die Datenbank geöffnet.*/
}
public void contextDestroyed(ServletContextEvent event) {
/*Hier wird die Datenbank geschlossen.*/
}
}
Listing 4.30: MyServletContextListener.java
Der entsprechende Eintrag für diesen Listener in der web.xml lautet wie folgt:
1
2
3
4
5
<listener>
<listener-class>
Listeners.MyServletContextListener
</listener-class>
</listener>
Listing 4.31: webMyContextListener.xml
Weiter unten in einem separaten Kapitel werden wir sehen, wie wir in einem ServletContextListener den db4o-Server öffnen können.
4.7.3 HttpSessionListener
Wollen Sie wissen, wann eine Session beginnt und wann sie wieder aufhört? Dann brauchen Sie
die Methoden sessionCreated() und sessionDestroyed() des HttpSessionListener und das HttpSessionEvent.
133
4 Einstieg in die Webentwicklung: Servlets und JSPs
package Listeners;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
public class MyHttpSessionListener implements HttpSessionListener {
/*Diese Methode wird gestartet, wenn eine Session beginnt.*/
public void sessionCreated(HttpSessionEvent event) {
System.out.println("sessionCreated : sessionId="
+ event.getSession().getId());
}
/*Diese Methode wird gestartet, wenn eine Session beendet wird.*/
public void sessionDestroyed(HttpSessionEvent event) {
System.out.println("sessionDestroyed : sessionId="
+ event.getSession().getId());
}
}
Listing 4.32: MyHttpSessionListener.java
Mit oben stehendem HttpSessionListener erhalten Sie folgende Ausgabe:
sessionCreated : sessionId=7A163058060BE2B3F436027A4765B451
sessionDestroyed : sessionId=7A163058060BE2B3F436027A4765B451
Und der entsprechende Eintrag für den HttpSessionListener in die web.xml lautet wie folgt:
<listener>
<listener-class>
Listeners.MyHttpSessionListener
</listener-class>
</listener>
1
2
3
4
5
6
Listing 4.33: mySessionWeb.xml
4.7.4 Weitere Listener
Insgesamt gibt es acht Listener, drei haben wir bereits kennen gelernt und die Restlichen möchte ich Ihnen jetzt kurz vorstellen. Die Listener lassen sich unterscheiden in Context-Listener,
Request-Listener und Session-Listener. Sie beobachten jeweils einen bestimmten Bereich: ContextListener beobachten, alle Vorgänge, die sich auf das ganze Web-Projekt beziehen. Session-Listeners
nehmen alles rund um die Session wahr und die Request-Listener alles in Bezug auf die Requests.
134
4.7 Listener
Session-Listener:
1. HttpSessionBindingListener: Er behält alle Attribute, die an die Session gebunden
sind, im Auge. Das entsprechende Event heißt HttpSessionBindingEvent und die Methoden
valueBound() and valueUnbound().
2. HttpSessionActivationListener: Wenn ein Objekt von einer Virtual-Machine zur Nächsten wandert, können Sie dies mit den Methoden sessionDidActivate() und sessionWillPassivate() und dem HttpSessionEvent beobachten.
Context-Listener:
1. ServletContextAttributeListener: Dieser Listener hört darauf, ob dem Context Attribute hinzugefügt oder aus dem Context gelöscht oder ersetzt worden sind. Er funktioniert
analog zum HttpSessionAttributeListener, der uns bereits oben begegnet ist. Sie müssen
drei Methoden implementieren: attributeAdded(), attributeRemoved() und attributeReplaced(), denen Sie jeweils als Parameter das ServletContextAttributeEvent übergeben
müssen.
Request-Listener:
1. HttpRequestAttributeListener: Von diesem Listener werden die Attribute des Requests beobachtet. Es besitzt die gleichen Methoden wie der ServletContextAttributeListener, wobei allerdings das übergebene Event anders heißt, nämlich ServletRequestAttributeEvent.
2. ServletRequestListener: Die Methoden requestInitialized() und requestDestroyed() und
das ServletRequestEvent registrieren, wann ein Request beim Server eingeht.
135
5 Webentwicklung mit Spring
137
6 Webentwicklung mit Grails
6.1 Model-View-Controller in Grails
139
7 Grundlagen der Datenbankabfragen mit JPA
7.1 Die 1:1-Beziehung
7.1.1 Die 1:1-Beziehung in db4o
Speichern, Auslesen und Löschen von Objekten der Klasse TextEinfach in db4o
Unsere Datenbank besteht bis dato aus drei Klassen: der Klasse TextEinfach, FormatierungEinfach und Bild. Der Datenbankentwurf nimmt an Komplexität und Umfang zu. Ebenso wie unsere
Abfragen. Und so wird unsere nächste Frage sein: Welche Besonderheiten sind bei den Datenbankabfragen und einer has-a-Beziehung zu beachten? Und wie können Probleme gelöst werden?
Speichern
Lassen Sie uns mit dem Speichern unseres TextEinfach-Objektes beginnen. Folgendes ist unser
Ziel: Wir wollen nicht, dass beim Speichern eines neuen TextEinfach-Objektes, jedes Mal ein neues
FormatierungEinfach-Objekt erzeugt wird. Wie können wir dies in unserer objektorientierten
Datenbank db4o erreichen? Sie dürfen die CSS-Formatierung eines Textes erst zuweisen, wenn
die Datenbank offen ist, da Sie ja das bereits existierende FormatierungEinfach-Objekt aus der
Datenbank auslesen und dann zuweisen wollen.
Nach Öffnen der Datenbank lesen Sie als Erstes das entsprechende bereits existierende Objekt aus und erst dann weisen Sie mit der Methode setFormatierungEinfach() dem Text ein
FormatierungEinfach-Objekt zu. Im Anschluß daran speichern Sie das TextEinfach-Objekt und
schließen die Datenbank wieder.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package db4oAndJPA;
import
import
import
import
import
Datenbankentwurf.FormatierungEinfach;
Datenbankentwurf.TextEinfach;
com.db4o.Db4oEmbedded;
com.db4o.ObjectContainer;
com.db4o.ObjectSet;
public class TextEinfachSpeichern {
public void speichern
(TextEinfach textEinfach, FormatierungEinfach formatierungEinfach){
ObjectContainer db = Db4oEmbedded.openFile
("C:/Datenbank/DasErsteProjekt/datenbank.yap");
try {
141
7 Grundlagen der Datenbankabfragen mit JPA
/*Sie muessen das bereits in der Datenbank gespeicherte
Objekt auslesen und anschliessend dem TextEinfach-Objekt zuweisen.*/
ObjectSet<FormatierungEinfach> result =
db.queryByExample(formatierungEinfach);
FormatierungEinfach formatierungResult = result.next();
textEinfach.setFormatierungEinfach(formatierungResult);
db.store(textEinfach);
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
} catch (Exception e) {
e.printStackTrace();
} finally{
db.close();
}
}
}
Listing 7.1: TextEinfachSpeichern.java
Auslesen
Wie werden die TextEinfach-Objekte wieder ausgelesen? Müssen wir auch auf Besonderheiten
achten, wie wir es beim Speichern der Objekte getan haben? Nein. Sie können analog vorgehen, wie beim Auslesen von FormatierungEinfach-Objekten: mit der Methode get() und einem
Beispielobjekt der Klasse TextEinfach.
package db4oAndJPA;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import
import
import
import
import
Datenbankentwurf.TextEinfach;
com.db4o.Db4oEmbedded;
com.db4o.ObjectContainer;
com.db4o.ObjectSet;
java.util.ArrayList;
public class TextEinfachAuslesen {
public ArrayList<TextEinfach> auslesen(){
ObjectContainer db = Db4oEmbedded.openFile
("C:/Datenbank/DasErsteProjekt/datenbank.yap");
ArrayList<TextEinfach> textEinfachListe = new ArrayList<TextEinfach>();
try {
TextEinfach textEinfach = new TextEinfach(null);
ObjectSet<TextEinfach> result = db.queryByExample(textEinfach);
while (result.hasNext()){
textEinfach = result.next();
142
7.1 Die 1:1-Beziehung
25
26
27
28
29
30
31
32
33
34
35
36
textEinfachListe.add(textEinfach);
}
} catch (Exception e) {
e.printStackTrace();
} finally{
db.close();
}
return textEinfachListe;
}
}
Listing 7.2: TextEinfachAuslesen.java
Löschen
Welche Probleme können beim Löschen auftauchen? Wie können Sie in db4o ungewolltes Löschen
unterbinden? Dazu sollten wir verstehen, was genau passiert, wenn wir ein Textobjekt löschen().
Wird das FormatierungEinfach-Objekt mitgelöscht?
Wir erstellen eine Klasse TextEinfachLoeschen.java mit einer Methode loeschen, die unsere
TextEinfach-Objekte löschen soll:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package db4oAndJPA;
import
import
import
import
Datenbankentwurf.TextEinfach;
com.db4o.Db4oEmbedded;
com.db4o.ObjectContainer;
com.db4o.ObjectSet;
public class TextEinfachLoeschen {
public void loeschen(TextEinfach textEinfach){
ObjectContainer db = Db4oEmbedded.openFile
("C:/Datenbank/DasErsteProjekt/datenbank.yap");
try {
ObjectSet<TextEinfach> result = db.queryByExample(textEinfach);
TextEinfach textEinfachResult = result.next();
db.delete(textEinfachResult);
} catch (Exception e) {
e.printStackTrace();
} finally{
db.close();
}
}
}
143
7 Grundlagen der Datenbankabfragen mit JPA
Listing 7.3: TextEinfachLoeschen.java
Um TextEinfach-Objekte löschen zu können, müssen wir als Erstes TextEinfach-Objekte anlegen. Wir erstellen sowohl zwei FormtierungEinfach-Objekte als auch zwei TextEinfach-Objekte.
Zuerst legen wir zwei FormatierungEinfach-Objekte an, da sie die Voraussetzung für das Erstellen von TextEinfach-Objekten sind. Auf Basis der CSS-Formatierungen font1 und font2 erstellen
wir in der Klasse TextEinfachInDatenbank.java zwei Texte und speichern diese ebenfalls in der
Datenbank:
package db4oAndJPA;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import Datenbankentwurf.FormatierungEinfach;
import Datenbankentwurf.TextEinfach;
import db4oFirstQueries.FormatierungEinfachDatenSpeichern;
public class TextEinfachInDatenbank {
public static void main(String[] args) {
FormatierungEinfach formatierungEinfachEins =
new FormatierungEinfach("font1");
FormatierungEinfach formatierungEinfachZwei =
new FormatierungEinfach("font2");
FormatierungEinfachDatenSpeichern formatierungEinfachDatenSpeichern =
new FormatierungEinfachDatenSpeichern();
formatierungEinfachDatenSpeichern.speichern(formatierungEinfachEins);
formatierungEinfachDatenSpeichern.speichern(formatierungEinfachZwei);
TextEinfach textEinfach = new TextEinfach("Ich bin ein Text!");
TextEinfach textEinfach1 = new TextEinfach("Ich bin noch ein Text!");
TextEinfachSpeichern textSpeichern = new TextEinfachSpeichern();
textSpeichern.speichern(textEinfach, formatierungEinfachEins);
textSpeichern.speichern(textEinfach1, formatierungEinfachZwei);
}
}
Listing 7.4: TextEinfachInDatenbank.java
Lassen Sie uns nun sowohl die Formatierungen als auch die Texte mit den zugehörigen Formatierungen auslesen, um zu überprüfen, ob unsere Objekte in der Datenbank angelegt worden
sind. Und wie unten stehende Ausgabe auf der Konsole zeigt, wurden alle FormatierungEinfachObjekte und alle TextEinfachObjekte so gespeichert, wie wir es erwartet haben.
package db4oAndJPA;
1
144
7.1 Die 1:1-Beziehung
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import
import
import
import
db4oFirstQueries.FormatierungEinfachDatenAuslesen;
Datenbankentwurf.FormatierungEinfach;
Datenbankentwurf.TextEinfach;
java.util.ArrayList;
public class TextEinfachAusDatenbank {
public static void main(String[] args){
FormatierungEinfachDatenAuslesen formatierungDatenAuslesen =
new FormatierungEinfachDatenAuslesen();
ArrayList<FormatierungEinfach> formatierungEinfachListe =
formatierungDatenAuslesen.auslesen();
for(FormatierungEinfach formatierungEinfach: formatierungEinfachListe){
System.out.println("Auslesen Formatierung: "+formatierungEinfach.getName());
}
TextEinfachAuslesen textAuslesen = new TextEinfachAuslesen();
ArrayList<TextEinfach> textEinfachListe = textAuslesen.auslesen();
for(TextEinfach textEinfach: textEinfachListe){
System.out.println("Auslesen Text: "+ textEinfach.getName());
System.out.println("Auslesen Text-Formatierung: "
+ textEinfach.getFormatierungEinfach().getName());
}
}
}
Listing 7.5: TextEinfachAusDatenbank.java
Ausgabe:
Auslesen
Auslesen
Auslesen
Auslesen
Auslesen
Auslesen
Formatierung: font2
Formatierung: font1
Text: Ich bin ein Text!
Text-Formatierung: font1
Text: Ich bin noch ein Text!
Text-Formatierung: font2
Im nächsten Schritt löschen wir in der Klasse TextEinfachDatenbankLoeschen.java das Objekt
TextEinfach mit dem Inhalt „Ich bin ein Text!“ wieder.
1
2
3
4
5
package db4oAndJPA;
import Datenbankentwurf.FormatierungEinfach;
import Datenbankentwurf.TextEinfach;
145
7 Grundlagen der Datenbankabfragen mit JPA
public class TextEinfachDatenbankLoeschen {
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static void main(String[] args){
FormatierungEinfach formatierungEinfach =
new FormatierungEinfach("font1");
TextEinfach textEinfach =
new TextEinfach("Ich bin ein Text!", formatierungEinfach);
TextEinfachLoeschen textLoeschen = new TextEinfachLoeschen();
textLoeschen.loeschen(textEinfach);
}
}
Listing 7.6: TextEinfachDatenbankLoeschen.java
Lesen wir nach diesem Löschvorgang die Texte und die Formatierungen wieder mit der Klasse
TextEinfachAusDatenbank aus der Datenbank aus, erhalten wir eine Ausgabe, wie wir sie erhofft
hatten:
Auslesen
Auslesen
Auslesen
Auslesen
Formatierung: font2
Formatierung: font1
Text: Ich bin noch ein Text!
Text-Formatierung: font2
Fazit: Das FormatierungEinfach-Objekt wurde nicht gelöscht, es wurde nur das TextEinfachObjekt gelöscht. Es wird also nur der Wert der Variablen eines Objektes gelöscht und nicht die
darin enthaltenen Objekte. Übertragen wir das Ergebnis auf unser Rechnungsbeispiel, würde nur
beim Löschen einer Rechnung nur die Rechnung gelöscht und nicht die darin enthaltenen Artikeloder Kundenstammdaten.
Löschen mit cascadeOnDelete()
Wollen Sie aber nicht nur das Objekt TextEinfach löschen, sondern auch das darin enthaltene
FormatierungEinfach-Objekt, benötigen Sie die Methode cascadeOnDelete(). Das Listing muss
entsprechend abgeändert werden:
package db4oAndJPA;
1
2
3
4
5
6
7
8
9
10
11
12
import
import
import
import
import
Datenbankentwurf.TextEinfach;
com.db4o.Db4oEmbedded;
com.db4o.ObjectContainer;
com.db4o.ObjectSet;
com.db4o.config.EmbeddedConfiguration;
public class TextEinfachLoeschenCascade {
public void loeschen(TextEinfach t){
146
7.1 Die 1:1-Beziehung
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
EmbeddedConfiguration config = Db4oEmbedded.newConfiguration();
config.common().objectClass(TextEinfach.class).cascadeOnDelete(true);
ObjectContainer db = Db4oEmbedded.openFile
(config,"C:/Datenbank/DasErsteProjekt/datenbank.yap");
try {
ObjectSet<TextEinfach> result = db.queryByExample(t);
TextEinfach textEinfach = result.next();
db.delete(textEinfach);
} catch (Exception e) {
e.printStackTrace();
} finally{
db.close();
}
}
}
Listing 7.7: TextEinfachLoeschenCascade.java
Testen wir oben stehendes Listing: Sie entfernen oben stehende Datenbank, indem Sie sie mit der
Entf-Taste löschen, und erstellen erneut die Objekte mithilfe der Klasse FormatierungInDatenbank.java und TextEinfachInDatenbank.java. Anschließend löschen wir das TextEinfach-Objekt
mit der Methode loeschen() aus der Klasse TextLoeschenCascade.java.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package db4oAndJPA;
import Datenbankentwurf.FormatierungEinfach;
import Datenbankentwurf.TextEinfach;
public class TextEinfachDatenbankLoeschenCascade {
public static void main(String[] args){
FormatierungEinfach formatierungEinfach =
new FormatierungEinfach("font1");
TextEinfach textEinfach =
new TextEinfach("Ich bin ein Text!", formatierungEinfach);
TextEinfachLoeschenCascade textLoeschenCascade = new TextEinfachLoeschenCascade();
textLoeschenCascade.loeschen(textEinfach);
}
}
Listing 7.8: TextEinfachDatenbankLoeschenCascade.java
147
7 Grundlagen der Datenbankabfragen mit JPA
Wir erhalten mit db4o-7.12.156.14667-all-java5.jar mit TextEinfachAusDatenbank.java nicht folgende Ausgabe:
Auslesen Formatierung: font2
Auslesen Text: Ich bin noch ein Text!
Auslesen Text-Formatierung: font2
Dies ist ein Bug, der sicherlich in einer der nächsten Version behoben sein wird. So sollte es
sein: Wir haben also beide gelöscht, sowohl das TextEinfach- als auch das FormatierungEinfachObjekt. Also Vorsicht: Diese Art zu löschen bitte nur verwenden, wenn Sie wirklich wollen,
dass auch das FormatierungEinfach-Objekt gelöscht wird und nicht nur das TextEinfach-Objekt.
Gemäß Handbuch ist auch vorgesehen, nur die darin enthaltenen Objekte zu löschen, die nicht
mehr referenziert sind.
Ändern
Welche Besonderheiten gilt es zu beachten, wenn Sie ein TextEinfach-Objekt und gleichzeitig das
darin enthaltene FormatierungsEinfach-Objekt ändern wollen? Hierzu benötigen wir die Methode
cascadeOnUpdate(true) mit dem Übergabeparameter true. Das vollständige Listing lautet dann
wie folgt:
package db4oAndJPA;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import
import
import
import
import
import
Datenbankentwurf.FormatierungEinfach;
Datenbankentwurf.TextEinfach;
com.db4o.Db4oEmbedded;
com.db4o.ObjectContainer;
com.db4o.ObjectSet;
com.db4o.config.EmbeddedConfiguration;
public class TextEinfachAendern {
public void aendern(TextEinfach textEinfach, TextEinfach textEinfachNeu,
FormatierungEinfach formatierungEinfachNeu) {
EmbeddedConfiguration config = Db4oEmbedded.newConfiguration();
config.common().objectClass(TextEinfach.class).cascadeOnUpdate(true);
ObjectContainer db = Db4oEmbedded.openFile
(config, "C:/Datenbank/DasErsteProjekt/datenbank.yap");
try {
/*Das zu veraendernde TextEinfach-Objekt muss aus der
Datenbank ausgelesen werden.*/
ObjectSet<TextEinfach> result = db.queryByExample(textEinfach);
TextEinfach textEinfachResult = result.next();
/*Der Inhalt des Textes wird veraendert:*/
textEinfachResult.setName(textEinfachNeu.getName());
/*Das Format des Textes wird veraendert:*/
148
7.1 Die 1:1-Beziehung
31
32
33
34
35
36
37
38
39
40
41
42
textEinfachResult.setFormatierungEinfach(formatierungEinfachNeu);
/*Das TextEinfach-Objekt wird wieder gespeichert:*/
db.store(textEinfachResult);
} catch (Exception e) {
e.printStackTrace();
} finally {
db.close();
}
}
}
Listing 7.9: TextEinfachAendern.java
Wir beginnen wieder damit die Datenbank zu löschen und erstellen erneut unsere Objekte mithilfe der Klasse TextEinfachInDatenbank.java. In der Klasse TextEinfachDatenbankAendern.java
wollen wir unser TextEinfach-Objekt mit Namen „Ich bin ein Text!“ ändern:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package db4oAndJPA;
import Datenbankentwurf.FormatierungEinfach;
import Datenbankentwurf.TextEinfach;
public class TextEinfachDatenbankAendern {
public static void main(String[] args){
FormatierungEinfach formatierungEinfachEins =
new FormatierungEinfach("font1");
TextEinfach textEinfachEins =
new TextEinfach("Ich bin ein Text!", formatierungEinfachEins);
FormatierungEinfach formatierungEinfachZwei =
new FormatierungEinfach("font2");
TextEinfach textEinfachZwei =
new TextEinfach("Ich bin ein neuer Text!");
TextEinfachAendern textAendern = new TextEinfachAendern();
textAendern.aendern
(textEinfachEins, textEinfachZwei, formatierungEinfachZwei);
}
}
Listing 7.10: TextEinfachDatenbankAendern.java
149
7 Grundlagen der Datenbankabfragen mit JPA
Geben wir unsere Daten mit TextEinfachAusDatenbank.java wieder aus, können wir sehen, dass
uns das Ändern eines TextEinfach-Objektes gelungen ist und immer noch genauso viele Formatierungsobjekte vorhanden sind wie vorher.
Ausgabe:
Auslesen
Auslesen
Auslesen
Auslesen
Auslesen
Auslesen
Formatierung: font2
Formatierung: font1
Text: Ich bin ein neuer Text!
Text-Formatierung: font2
Text: Ich bin noch ein Text!
Text-Formatierung: font2
7.1.2 Die 1:1-Beziehung in Hibernate
Speichern, Auslesen und Löschen von Objekten der Klasse TextEinfach in Hibernate
Speichern
Es stellt sich die Frage, wie funktioniert Abfragen bei 1:1-Beziehungen in Hibernate. Das Prinzip
ist das gleiche. Das FormatierungEinfach-Objekt muss ausgelesen und dem TextEinfach-Objekt
zugewiesen werden und anschließend muss das TextEinfach-Objekt mit der Methode save() gespeichert werden. Wir verwenden zum Auslesen einen Select-Befehl aus der Hibernate Query
Language (HQL), dieser unterscheidet sich nur geringfügig von dem Select-Befehl aus SQL.
package hibernateUndJPA;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import
import
import
import
import
import
Datenbankentwurf.TextEinfach;
Datenbankentwurf.FormatierungEinfach;
org.hibernate.Session;
org.hibernate.SessionFactory;
org.hibernate.Transaction;
util.HibernateUtil;
public class TextEinfachSpeichernHibernate {
public void speichern(TextEinfach textEinfach,
FormatierungEinfach formatierungEinfach) {
Session session = null;
SessionFactory sessionFactory = HibernateUtil.getSessionFactory();
session = sessionFactory.openSession();
try {
String name = formatierungEinfach.getName();
Transaction tx = session.beginTransaction();
150
7.1 Die 1:1-Beziehung
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/*Sie muessen das bereits in der Datenbank gespeicherte
Objekt auslesen und anschliessend dem TextEinfach-Objekt zuweisen.*/
FormatierungEinfach formatierungResult =
(FormatierungEinfach) session.createQuery
("Select f from FormatierungEinfach f " +
"where f.name = :formatierungName").
setString("formatierungName", name).uniqueResult();
textEinfach.setFormatierungEinfach(formatierungResult);
session.save(textEinfach);
tx.commit();
} catch (RuntimeException e) {
e.printStackTrace();
} finally {
if (session != null) {
session.close();
}
}
}
}
Listing 7.11: TextEinfachSpeichernHibernate.java
Auslesen
Mithilfe der Methode list() und einem HQL-Befehl werden alle TextEinfach-Objekte wieder ausgelesen.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package hibernateUndJPA;
import
import
import
import
import
import
Datenbankentwurf.TextEinfach;
java.util.ArrayList;
org.hibernate.Session;
org.hibernate.SessionFactory;
org.hibernate.Transaction;
util.HibernateUtil;
public class TextEinfachAuslesenHibernate {
public ArrayList<TextEinfach> auslesen() {
Session session = null;
SessionFactory sessionFactory = HibernateUtil.getSessionFactory();
session = sessionFactory.openSession();
ArrayList<TextEinfach> textList = new ArrayList<TextEinfach>();
151
7 Grundlagen der Datenbankabfragen mit JPA
try {
Transaction tx = session.beginTransaction();
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/*Folgende Abfrage liefert saemtliche Objekte*/
textList = (ArrayList<TextEinfach>)session.createQuery
("Select t from TextEinfach t").list();
tx.commit();
} catch (RuntimeException e) {
e.printStackTrace();
} finally {
if (session != null) {
session.close();
}
}
return textList;
}
}
Listing 7.12: TextEinfachAuslesenHibernate.java
Loeschen
Das zu löschende TextEinfach-Objekt wird ausgelesen und mit der Methode delete() gelöscht.
package hibernateUndJPA;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import
import
import
import
import
Datenbankentwurf.TextEinfach;
org.hibernate.Session;
org.hibernate.SessionFactory;
org.hibernate.Transaction;
util.HibernateUtil;
public class TextEinfachLoeschenHibernate {
public void loeschen(TextEinfach textEinfach) {
Session session = null;
SessionFactory sessionFactory = HibernateUtil.getSessionFactory();
session = sessionFactory.openSession();
try {
Transaction tx = session.beginTransaction();
/*Sie muessen das bereits in der Datenbank gespeicherte
152
7.1 Die 1:1-Beziehung
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
Objekt auslesen und anschliessend loeschen.*/
TextEinfach textResult = (TextEinfach) session.createQuery
("Select t from TextEinfach t " +
"where t.name = :textName").
setString("textName", textEinfach.getName()).uniqueResult();
session.delete(textResult);
tx.commit();
} catch (RuntimeException e) {
e.printStackTrace();
} finally {
if (session != null) {
session.close();
}
}
}
}
Listing 7.13: TextEinfachLoeschenHibernate.java
Hierbei stellt sich wieder die Frage, ob gleichzeitig die FormatierungsEinfach-Objekte, die sich im
TextEinfach-Objekt befinden mitgelöscht wurden. Nein. Sie werden nur mitgelöscht, wenn in der
Klasse TextEinfach der CascadeType.All bei der 1:1-Beziehung zu dem FormatierungEinfachObjekt festgelegt wurde.
@OneToOne( c a s c a d e = CascadeType . ALL)
Ändern
Was ist, wenn wir gleichzeitig beide Objekte ändern wollen? Wirkt sich der CascadeType.All
auch hier aus? Nein. In Hibernate muss im Gegensatz zu db4o jedes Objekt separat ausgelesen
und verändert werden.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package hibernateUndJPA;
import
import
import
import
import
import
Datenbankentwurf.TextEinfach;
Datenbankentwurf.FormatierungEinfach;
org.hibernate.Session;
org.hibernate.SessionFactory;
org.hibernate.Transaction;
util.HibernateUtil;
public class TextEinfachAendernHibernate {
public void aendern(TextEinfach textEinfach, TextEinfach textEinfachNeu,
FormatierungEinfach formatierungEinfachNeu) {
Session session = null;
153
7 Grundlagen der Datenbankabfragen mit JPA
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
SessionFactory sessionFactory = HibernateUtil.getSessionFactory();
session = sessionFactory.openSession();
try {
String name = textEinfach.getName();
Transaction tx = session.beginTransaction();
/*Sie muessen beide in der Datenbank gespeicherte
Objekt auslesen und anschliessend das FormatierungEinfach-Objekt
wieder dem TextEinfach-Objekt zuweisen.*/
TextEinfach textResult = (TextEinfach) session.createQuery
("Select t from TextEinfach t " +
"where t.name = :textName").
setString("textName", name).uniqueResult();
FormatierungEinfach formatierungResult =
(FormatierungEinfach)session.createQuery
("Select f from FormatierungEinfach f "+
"where f.name = :formatierungName").
setString("formatierungName",
formatierungEinfachNeu.getName()).uniqueResult();
textResult.setName(textEinfachNeu.getName());
textResult.setFormatierungEinfach(formatierungResult);
session.update(textResult);
tx.commit();
} catch (RuntimeException e) {
e.printStackTrace();
} finally {
if (session != null) {
session.close();
}
}
}
}
Listing 7.14: TextEinfachAendernHibernate.java
7.1.3 Die 1:1-Beziehung in Spring
Was verändert sich bei unseren Abfragen in Hibernate mit Spring? Durch die Erstellung eines
Interfaces und einer Klasse, die dieses Interface implementiert, wird automatisch eine Ordnung
erzeugt: Sie sind von Anfang an angehalten, mehrere Methoden in einem Interface zusammenzufassen. Diese Methoden werden dann in der abgeleiteten Klasse mit Inhalt gefüllt.
154
7.1 Die 1:1-Beziehung
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package DAO;
import Datenbankentwurf.FormatierungEinfach;
import Datenbankentwurf.TextEinfach;
import java.util.ArrayList;
public interface TextEinfachFormatierungEinfachDAO {
public void speichernFormatierungEinfach(FormatierungEinfach formatierungEinfach);
public ArrayList<FormatierungEinfach> auslesenFormatierungEinfach();
public void speichern(TextEinfach textEinfach,
FormatierungEinfach formatierungEinfach);
public ArrayList<TextEinfach> auslesen();
public void loeschen(TextEinfach textEinfach);
public void aendern(TextEinfach textEinfach, TextEinfach textEinfachNeu,
FormatierungEinfach formatierungEinfachNeu);
}
Listing 7.15: TextEinfachFormatierungEinfachDAO.java
Die Abfragen sind die gleichen wie in Hibernate geblieben. Was ändert sich? Die Transaktionsgrenzen werden mit einer Annotation @Transactional mit den Methodengrenzen gleichgesetzt
und müssen somit nicht mehr explizit wie in Hibernate gesetzt werden. Näheres zum Transaktionsmanagement finden Sie im Kapitel Transaktionen. In Hibernate wird mit sessionFactory.openSession() eine Session pro Transaktion geöffnet, wohingegen in Spring mit Hibernate mit
der Methode sessionFactory.getCurrentSession() auf eine kontextabhängige Session zugegriffen
wird. Gemäß offizieller Springdokumentation sind beide Konzepte fast identisch.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package DAO;
import
import
import
import
import
Datenbankentwurf.FormatierungEinfach;
Datenbankentwurf.TextEinfach;
java.util.ArrayList;
org.hibernate.SessionFactory;
org.springframework.transaction.annotation.Transactional;
@Transactional
public class TextEinfachFormatierungEinfachDAOImpl
implements TextEinfachFormatierungEinfachDAO {
private SessionFactory sessionFactory;
/*Die Informationen fuer die SessionFactory werden injiziiert.*/
public void setSessionFactory(SessionFactory sessionFactory){
this.sessionFactory = sessionFactory;
}
/*Das FormatierungEinfach-Objekt wird gespeichert.*/
155
7 Grundlagen der Datenbankabfragen mit JPA
@Transactional
public void speichernFormatierungEinfach
(FormatierungEinfach formatierungEinfach) {
sessionFactory.getCurrentSession().save(formatierungEinfach);
}
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
/*Das FormatierungEinfach-Objekt wird ausgelesen.*/
@Transactional
public ArrayList<FormatierungEinfach> auslesenFormatierungEinfach() {
return (ArrayList<FormatierungEinfach>)sessionFactory.
getCurrentSession().createQuery
("Select f from FormatierungEinfach f").list();
}
/*Das TextEinfach-Objekt wird gespeichert.*/
@Transactional
public void speichern(TextEinfach textEinfach,
FormatierungEinfach formatierungEinfach) {
String name = formatierungEinfach.getName();
FormatierungEinfach formatierungResult =
(FormatierungEinfach)sessionFactory.
getCurrentSession().createQuery
("Select f from FormatierungEinfach f " +
"where f.name = :formatierungName").
setString("formatierungName", name).uniqueResult();
textEinfach.setFormatierungEinfach(formatierungResult);
sessionFactory.getCurrentSession().save(textEinfach);
}
/*Das TextEinfach-Objekt wird ausgelesen.*/
@Transactional
public ArrayList<TextEinfach> auslesen() {
return (ArrayList<TextEinfach>)sessionFactory.
getCurrentSession().createQuery
("Select t from TextEinfach t").list();
}
/*Das TextEinfach-Objekt wird geloescht.*/
@Transactional
public void loeschen(TextEinfach textEinfach) {
TextEinfach textResult = (TextEinfach) sessionFactory.
getCurrentSession().createQuery
("Select t from TextEinfach t " +
"where t.name = :textName").
setString("textName", textEinfach.getName()).uniqueResult();
sessionFactory.getCurrentSession().delete(textResult);
156
7.1 Die 1:1-Beziehung
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
}
/*Das TextEinfach-Objekt wird geaendert.*/
@Transactional
public void aendern(TextEinfach textEinfach, TextEinfach textEinfachNeu,
FormatierungEinfach formatierungEinfachNeu) {
String name = textEinfach.getName();
TextEinfach textResult = (TextEinfach) sessionFactory.
getCurrentSession().createQuery
("Select t from TextEinfach t " +
"where t.name = :textName").
setString("textName", name).uniqueResult();
FormatierungEinfach formatierungResult =
(FormatierungEinfach)sessionFactory.
getCurrentSession().createQuery
("Select f from FormatierungEinfach f "+
"where f.name = :formatierungName").
setString("formatierungName",
formatierungEinfachNeu.getName()).uniqueResult();
textResult.setName(textEinfachNeu.getName());
textResult.setFormatierungEinfach(formatierungResult);
sessionFactory.getCurrentSession().update(textResult);
}
}
Listing 7.16: TextEinfachFormatierungEinfachDAOImpl.java
7.1.4 Die 1:1-Beziehung in Grails
In Grails ist die genaue Verzeichnisstruktur bereits vorgegeben. Betrachten wir die zwei Verzeichnisse controllers und services. In Grails heißt eine Klasse, die die Programmierlogik enthält,
Controller. Eine Klasse, die aus Business-Logik besteht, heißt Service. Eine Service-Klasse in
Grails hat sogenannte Actions zum Inhalt, die sich wiederum zusammensetzen aus transaktionalen Abfragen.
157
7 Grundlagen der Datenbankabfragen mit JPA
Abbildung 7.1: Verzeichnisstruktur in Grails
Werden in der Service-Klasse die Transaktionen auf true gesetzt, verwaltet Grails die Transaktionen für Sie, und die Transaktionsgrenzen entsprechen dem Anfang und Ende der Aktion.
s t a t i c transactional = true
In unten stehender Service-Klasse kommen auch so genannte Dynamic Finders zum Einsatz. Es
sind die Methoden findAll() und findByName(). Die Methode findAll() sucht nach allen Objekten eines bestimmten Typs. Die Methode findByName() ist eine zusammengesetzte Methode,
die Grails automatisch erzeugt und aus findBy + Attributname besteht. Sowohl in der Klasse
TextEinfach als auch in der Klasse FormatierungEinfach gibt es eine Variablen mit Namen name.
Ist das nicht einfach?
package grailsAndJPA
1
2
3
4
5
6
7
8
9
10
import Datenbankentwurf.FormatierungEinfach
import Datenbankentwurf.TextEinfach
class TextEinfachFormatierungEinfachService {
static transactional = true
/*FormatierungEinfach-Objekte werden gespeichert.*/
158
7.1 Die 1:1-Beziehung
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
def speichernFormatierungEinfach(FormatierungEinfach formatierungEinfach){
formatierungEinfach.save()
}
/*Das FormatierungEinfach-Objekte werden ausgelesen.*/
def auslesenFormatierungEinfach(){
def formatierungEinfach = FormatierungEinfach.findAll()
}
/*Ein TextEinfach-Objekt wird gespeichert.*/
def speichern(TextEinfach textEinfach,
FormatierungEinfach formatierungEinfach){
textEinfach.formatierungEinfach = formatierungEinfach;
textEinfach.save()
}
/*Alle TextEinfach-Objekte werden ausgelesen.*/
def auslesen(){
def textEinfach = TextEinfach.findAll()
}
/*Ein TextEinfach-Objekt wird wieder geloescht.*/
def loeschen(TextEinfach textEinfach){
def textResult = TextEinfach.findByName(textEinfach.getName());
textResult.delete()
}
/*Bei einem TextEinfach-Objekt wird sowohl der Name als auch das
*darin enthaltende FormatierungsObjekt geaendert.*/
def aendern(TextEinfach textEinfach, TextEinfach textEinfachNeu,
FormatierungEinfach formatierungEinfachNeu){
def textResult = TextEinfach.findByName(textEinfach.name);
textResult.name = textEinfachNeu.name
textResult.formatierungEinfach = formatierungEinfachNeu;
textResult.save()
}
}
Listing 7.17: TextEinfachFormatierungEinfachService.groovy
Wollen Sie beim Speichern, Löschen und Ändern des TextEinfach-Objektes in der 1:1-Beziehung
das FormatierungEinfach-Objekt jeweils mitspeichern, mitlöschen oder mitändern, müssen Sie
Folgendes dem Formatierungs-Objekt hinzufügen:
s t a t i c belongsTo = TextEinfach
Unten stehende Klasse enthält ein einfaches Beispiel einer Methode in einem Controller. Der
Service textEinfachFormatierungEinfachService wird im Controller mithilfe von Dependency Injection injiziiert, so können Sie problemlos auf dessen Methoden zugreifen.
159
7 Grundlagen der Datenbankabfragen mit JPA
package Datenbankentwurf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
class FormatierungEinfachController {
/*Mithilfe von Dependency Injection wird der Service injiziiert.*/
def textEinfachFormatierungEinfachService
/*scoffold erstellt Standardaus- und Eingabeformulare. */
def scaffold = FormatierungEinfach
def speichernAuslesen = {
/*Objekte der Klasse FormatierungEinfach werden gespeichert.*/
FormatierungEinfach formatierungEinfachEins = new FormatierungEinfach()
formatierungEinfachEins.name = "font1"
textEinfachFormatierungEinfachService.
speichernFormatierungEinfach(formatierungEinfachEins);
FormatierungEinfach formatierungEinfachZwei = new FormatierungEinfach()
formatierungEinfachZwei.name = "font2"
textEinfachFormatierungEinfachService.
speichernFormatierungEinfach(formatierungEinfachZwei);
/*Objekte der Klasse FormatierungEinfach werden ausgelesen.*/
ArrayList<FormatierungEinfach> formatierungEinfachListe =
textEinfachFormatierungEinfachService.
auslesenFormatierungEinfach()
formatierungEinfachListe.each{
element ->
println "Formatierungen: "+ element.name;
}
/*Objekte der Klasse TextEinfach werden gespeichert.*/
TextEinfach textEinfachEins = new TextEinfach()
textEinfachEins.name = "Dies ist ein Text"
textEinfachFormatierungEinfachService.speichern(textEinfachEins,
formatierungEinfachEins)
TextEinfach textEinfachZwei = new TextEinfach()
textEinfachZwei.name = "Dies ist noch ein Text"
textEinfachFormatierungEinfachService.speichern(textEinfachZwei,
formatierungEinfachZwei)
/*Objekte der Klasse TextEinfach werden ausgelesen*/
def textEinfachListe = textEinfachFormatierungEinfachService.auslesen()
textEinfachListe.each(){
elemente ->
println "Texte: " + elemente.name
def formatierungEinfachTextEinfachListe =
160
7.2 Vererbungsbeziehungen
50
51
52
53
54
55
56
57
58
elemente.formatierungEinfach
formatierungEinfachTextEinfachListe.each{
elementeFormatierungEinfach ->
println "Formatierung in Text:" + elementeFormatierungEinfach.
name
}
}
}
}
Listing 7.18: FormatierungEinfachController.groovy
Da wir im Controller das Scaffolding eingestellt haben, werden uns einfache Ein- und Ausgabeformulare zur Verfügung gestellt.
def s c a f f o l d = FormatierungEinfach
Wollen Sie die Action speichernAuslesen ausführen, geben Sie folgendes im Browser als Zieladresse ein:
http://localhost:8080/DasErsteProjektInGrails/formatierungEinfach/speichernAuslesen
Es kommt in diesem Fall zu einer Fehlermeldung, da Tomcat eigentlich eine GSP-Seite erwartet,
aber wir erhalten - wie erwartet - folgende Ausgabe auf der Konsole:
Formatierungen: font1
Formatierungen: font2
Texte: Dies ist ein Text
Formatierung in Text:font1
Texte: Dies ist noch ein Text
Formatierung in Text:font2
7.2 Vererbungsbeziehungen
7.2.1 Die Vererbungsbeziehung in db4o
Speichern und Auslesen von Objekten der Klasse Verlinkung, Link und PDF in db4o
Wie wirkt sich die Vererbungsbeziehung zwischen der Klasse Verlinkung, Link und PDF auf das
Speichern und Auslesen aus? Was gilt es Besonderes zu beachten?
Speichern
Lassen Sie uns zwei Klassen erstellen, die Methoden zum Speichern unserer Objekte enthalten.
Die Methode store(), die ein Objekt in der Datenbank speichert, ist uns bereits aus den vorangegangenen Abschnitten bekannt. Die Klasse LinkSpeichern.java und ihre Methode speichern()
speichert die Objekte der Klasse Link:
1
2
package db4oAndJPA;
161
7 Grundlagen der Datenbankabfragen mit JPA
import Datenbankentwurf.Link;
import com.db4o.Db4oEmbedded;
import com.db4o.ObjectContainer;
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class LinkSpeichern {
public void speichern(Link link){
ObjectContainer db = Db4oEmbedded.openFile
("C:/Datenbank/DasErsteProjekt/datenbank.yap");
try {
db.store(link);
} catch (Exception e) {
e.printStackTrace();
} finally{
db.close();
}
}
}
Listing 7.19: LinkSpeichern.java
Und die Klasse PDFSpeichern.java liest PDF-Objekte mithilfe der Methode speichern() in die
Datenbank ein:
package db4oAndJPA;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import Datenbankentwurf.PDF;
import com.db4o.Db4oEmbedded;
import com.db4o.ObjectContainer;
public class PDFSpeichern {
public void speichern(PDF pdf){
ObjectContainer db = Db4oEmbedded.openFile
("C:/Datenbank/DasErsteProjekt/datenbank.yap");
try {
db.store(pdf);
} catch (Exception e) {
e.printStackTrace();
} finally{
db.close();
162
7.2 Vererbungsbeziehungen
22
23
24
}
}
}
Listing 7.20: PDFSpeichern.java
Wir löschen die bereits existierende Datenbank wieder und erstellen mit unten stehender Klasse
LinkPDFInDatenbank.java eine neue, in der wir sowohl zwei Links als auch zwei PDFs speichern.
Wir können sowohl Links als auch PDFs Namen vergeben, da sie die entsprechenden Methoden
aus der Vaterklasse Verlinkung geerbt haben.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package db4oAndJPA;
import Datenbankentwurf.Link;
import Datenbankentwurf.PDF;
public class LinkPDFInDatenbank {
public static void main(String[] args){
//Es werden zwei Links gespeichert
LinkSpeichern linkSpeichern = new LinkSpeichern();
Link linkEins = new Link("eins");
Link linkZwei = new Link("zwei");
linkSpeichern.speichern(linkEins);
linkSpeichern.speichern(linkZwei);
//Es werden zwei PDFs gespeichert
PDFSpeichern pdfSpeichern = new PDFSpeichern();
PDF pdfLinks = new PDF("links", "PDF/links.pdf");
PDF pdfRechts = new PDF("rechts", "PDF/rechts.pdf");
pdfSpeichern.speichern(pdfLinks);
pdfSpeichern.speichern(pdfRechts);
}
}
Listing 7.21: LinkPDFInDatenbank.java
Soweit nichts Neues: Verwandtschaftsbeziehungen haben keinen Einfluss beim Speichern von
Objekten.
Auslesen
Wie werden unsere Objekte der Klasse Link und der Klasse PDF wieder ausgelesen? Und welche
Bedeutung spielt hierbei, dass beide Klassen Subklassen der Klasse Verlinkung sind? Was lässt
163
7 Grundlagen der Datenbankabfragen mit JPA
sich vermuten? Da es sich um eine is-a-Beziehung handelt und sowohl Links als auch PDFs eine isa-Beziehung zu der Klasse Verlinkung haben, müssten eigentlich alle Links und PDFs ausgelesen
werden, wenn wir Objekte der Klasse Verlinkung auslesen. Dies werden wir überprüfen. Folgende
Klasse mit der Methode auslesen(), liest alle Objekte der Klasse Verlinkung aus:
package db4oAndJPA;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import
import
import
import
import
Datenbankentwurf.Verlinkung;
com.db4o.Db4oEmbedded;
com.db4o.ObjectContainer;
com.db4o.ObjectSet;
java.util.ArrayList;
public class VerlinkungAuslesen {
public ArrayList<Verlinkung> auslesen(){
ObjectContainer db = Db4oEmbedded.openFile
("C:/Datenbank/DasErsteProjekt/datenbank.yap");
ArrayList<Verlinkung> verlinkungList = new ArrayList<Verlinkung>();
try {
Verlinkung verlinkung = new Verlinkung(null);
ObjectSet<Verlinkung> result = db.queryByExample(verlinkung);
while (result.hasNext()){
verlinkung = result.next();
verlinkungList.add(verlinkung);
}
} catch (Exception e) {
e.printStackTrace();
} finally{
db.close();
}
return verlinkungList;
}
}
Listing 7.22: VerlinkungAuslesen.java
Und tatsächlich führen wir die Methode auslesen() durch, werden sowohl alle Links als auch alle
PDFs auf der Konsole ausgegeben:
package db4oAndJPA;
1
2
164
7.2 Vererbungsbeziehungen
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import Datenbankentwurf.Verlinkung;
import java.util.ArrayList;
public class VerlinkungAusDatenbank {
public static void main(String[] args){
VerlinkungAuslesen verlinkungAuslesen = new VerlinkungAuslesen();
ArrayList<Verlinkung> verlinkungList = verlinkungAuslesen.auslesen();
for(Verlinkung verlinkung : verlinkungList){
System.out.println("Verlinkungen Name: "+verlinkung.getName() );
}
}
}
Listing 7.23: VerlinkungAusDatenbank.java
Ausgabe:
Verlinkungen:
Verlinkungen:
Verlinkungen:
Verlinkungen:
eins
zwei
rechts
links
Machen wir nun die Probe aufs Exempel: Werden nur alle Links ausgelesen, wenn wir eine
Methode auslesen() für die Klasse Link erstellen?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package db4oAndJPA;
import
import
import
import
import
Datenbankentwurf.Link;
com.db4o.Db4oEmbedded;
com.db4o.ObjectContainer;
com.db4o.ObjectSet;
java.util.ArrayList;
public class LinkAuslesen {
public ArrayList<Link> auslesen(){
ObjectContainer db = Db4oEmbedded.openFile
("C:/Datenbank/DasErsteProjekt/datenbank.yap");
ArrayList<Link> linkList = new ArrayList<Link>();
try {
165
7 Grundlagen der Datenbankabfragen mit JPA
Link link = new Link(null);
ObjectSet<Link> result = db.queryByExample(link);
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
while (result.hasNext()){
link = result.next();
linkList.add(link);
}
} catch (Exception e) {
e.printStackTrace();
} finally{
db.close();
}
return linkList;
}
}
Listing 7.24: LinkAuslesen.java
Wir lesen alle Linkobjekte und ihre Namen aus. Sie können keine Pfade auslesen, da die Klasse
Link keine Variable pfad und die entsprechenden Getter- und Setter-Methoden enthält. Diese
gibt es nur in der Klasse PDF.
package db4oAndJPA;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import Datenbankentwurf.Link;
import java.util.ArrayList;
public class LinkAusDatenbank {
public static void main(String[] args){
LinkAuslesen linkAuslesen = new LinkAuslesen();
ArrayList<Link> linkList = linkAuslesen.auslesen();
for(Link link : linkList){
System.out.println("Link Name: "+link.getName() );
}
}
}
Listing 7.25: LinkAusDatenbank.java
Ausgabe:
166
7.2 Vererbungsbeziehungen
Link Name: eins
Link Name: zwei
Es werden nur die Objekte der Klasse Link ausgelesen und nicht die Objekte der Klasse PDF.
Fazit: Unser obige Vermutung kann also mit Ja beantwortet werden. Es werden alle Links und
PDFs ausgelesen, wenn alle Verlinkungen ausgelesen werden. Und nur alle Links, wenn Links
ausgelesen werden und nur alle PDFs, wenn PDFs ausgelesen werden. Also: Denken Sie immer
an die Vererbungsbeziehungen zwischen den einzelnen Klassen der Datenbank, wenn Sie Daten
aus der Datenbank auslesen wollen.
Interfaces und Abstrakte Klassen
Interfaces, übersetzt Schnittstellen, stellen eine Art Vorschriftenkatalog dar, der festlegt welche
Methoden in einer Klasse eingefügt werden müssen, die das Interface implementieren. Da das
Interface nur sicherstellen soll, dass sich Methoden in der Klasse befinden, enthält ein Interface
nur leere Methoden, die in der Klasse selbst mit Inhalt gefüllt werden müssen. Eine Klasse besitzt
eine implements-Beziehung zu einem Interface:
p u b l i c c l a s s Formatierung implements I n t e r f a c e F o r m a t i e r u n g {}
Abstrakte Klassen haben ähnliche Aufgaben wie Interfaces. Sie besitzen auch Implementierungsvorschriften, aber im Gegensatz zu einem Interface können Sie auch Methoden enthalten, die
nicht leer sind.
Da Sie weder Interfaces noch Abstrakte Klassen instanziieren können, können Sie auch nicht mit
Query-by-Example ein Beispielobjekt aus einem Interface oder einer Abstrakten Klasse erstellen.
Wie können Sie aber Objekte auslesen, die ein bestimmtes Interface implementieren? Und konkret: Wie lesen wir Objekte aus, die das Interface InterfaceFormatierung implementieren? db4o
stellt Ihnen deswegen eine zusätzliche Funktion zur Verfügung:
InterfaceFormatierung . c l a s s
Diese Befehlszeile müssen Sie anstelle eines Beispielobjektes der Methode get() übergeben:
O b j e c t . S e t r e s u l t = db . g e t ( I n t e r f a c e F o r m a t i e r u n g . c l a s s ) ;
Das gleiche gilt für alle Objekte, die eine extends-Beziehung zu einer abstrakten Klasse haben.
7.2.2 Die 1:n-Beziehung
In diesem Abschnitt werden wir anhand von Beispielen die bidirektionalen 1:n-Beziehung näher
betrachten: Wir beginnen damit mit Formatierungsobjekten Abfragen zu gestalten, die es uns
ermöglichen sollen, Formatierungen anzulegen, zu löschen und zu ändern. Erst danach werden
wir zu den Formatierungen, Texte hinzufügen.
Die Eingabemaske für Formatierungen in unserem Content-Management-System soll wie folgt
aussehen:
167
7 Grundlagen der Datenbankabfragen mit JPA
Abbildung 7.2: Eingabemaske für unsere Formatierungen
Im Kapitel Unser Content-Management-System werden wir sehen, wie wir diese Eingabemaske
und die dazugehörigen Löschen- und Ändern-Formulare mit JSPs und Servlets umsetzen können.
Speichern, Auslesen, Ändern und Löschen von Formatierungsobjekten
Im ersten Schritt erstellen wir Klassen, die Abfragen bezüglich unseres Formatierungsobjektes
enthalten. Texte werden wir später hinzufügen und wieder löschen. Im Gegensatz zum vorherigen
Abschnitt verwenden wir jetzt die Klasse Formatierung, die im Gegensatz zur Klasse FormatierungEinfach eine 1:n-Beziehung und keine 1:1-Beziehung abbildet.
Speichern
Erstens speichern wir wieder, wie gehabt, mit der store()-Methode unser Objekt der Klasse
Formatierung:
package db4oAndJPA;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import Datenbankentwurf.Formatierung;
import com.db4o.Db4oEmbedded;
import com.db4o.ObjectContainer;
public class FormatierungSpeichern {
public void speichern(Formatierung formatierung){
ObjectContainer db = Db4oEmbedded.openFile
("C:/Datenbank/DasErsteProjekt/datenbank.yap");
try {
db.store(formatierung);
168
7.2 Vererbungsbeziehungen
18
19
20
21
22
23
24
} catch (Exception e) {
e.printStackTrace();
} finally{
db.close();
}
}
}
Listing 7.26: FormatierungSpeichern.java
Auslesen
Zweitens wird das Formatierungsobjekt in der Klasse FormatierungAuslesen.java wieder mit
der Methode queryByExample() und einem Beispielobjekt aus der Datenbank ausgelesen. Dem
Beispielobjekt der Klasse Formatierung müssen Sie zwei Standardwerte übergeben, da sie zwei
Bestandteile hat:
Formatieru ng p = new Formatierung ( n u l l , n u l l ) ;
Die gesamte Methode auslesen(), die eine ArrayList<Formatierung> zurückgibt, sieht dann wie
folgt aus:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package db4oAndJPA;
import
import
import
import
import
Datenbankentwurf.Formatierung;
com.db4o.Db4oEmbedded;
com.db4o.ObjectContainer;
com.db4o.ObjectSet;
java.util.ArrayList;
public class FormatierungAuslesen {
public ArrayList<Formatierung> auslesen(){
ObjectContainer db = Db4oEmbedded.openFile
("C:/Datenbank/DasErsteProjekt/datenbank.yap");
ArrayList<Formatierung> formatierungListe = new ArrayList<Formatierung>();
try {
Formatierung formatierung = new Formatierung(null, null);
ObjectSet<Formatierung> result = db.queryByExample(formatierung);
while (result.hasNext()){
formatierung = result.next();
formatierungListe.add(formatierung);
}
} catch (Exception e) {
169
7 Grundlagen der Datenbankabfragen mit JPA
e.printStackTrace();
} finally{
db.close();
}
return formatierungListe;
29
30
31
32
33
34
35
}
}
Listing 7.27: FormatierungAuslesen.java
Eine bestimmte Formatierung können Sie auslesen, indem Sie der Methode queryByExample()
als Parameter folgendes Beispielobjekt übergeben:
Formatierung p = new Formatierung ( name , n u l l ) ;
Dem Beispielobjekt wurde als Parameter der Name der Formatierung und null als Standardwert
der ArrayList übergeben. Lassen Sie uns dies in der Klasse FormatierungAuslesenEinzeln.java
umsetzen. Die darin enthaltene Methode auslesenEinzeln() liest ein bestimmtes Formatierungsobjekt aus:
package db4oAndJPA;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import
import
import
import
Datenbankentwurf.Formatierung;
com.db4o.Db4oEmbedded;
com.db4o.ObjectContainer;
com.db4o.ObjectSet;
public class FormatierungAuslesenEinzeln {
public Formatierung auslesenEinzeln(String name) {
ObjectContainer db = Db4oEmbedded.openFile
("C:/Datenbank/DasErsteProjekt/datenbank.yap");
Formatierung formatierungAusgelesen = new Formatierung();
try {
Formatierung formatierung = new Formatierung(name, null);
ObjectSet<Formatierung> result = db.queryByExample(formatierung);
formatierungAusgelesen = result.next();
} catch (Exception e) {
e.printStackTrace();
} finally {
db.close();
}
return formatierungAusgelesen;
}
}
170
7.2 Vererbungsbeziehungen
Listing 7.28: FormatierungAuslesenEinzeln.java
Ändern
Drittens ändern wir eine Formatierung, indem wir zuerst das entsprechende Formatierungsobjekt
mit der Methode get() auslesen, mit der Methode setName() den Namen der Formatierung
verändern und anschließend wieder mit der Methode set() in der Datenbank speichern:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package db4oAndJPA;
import
import
import
import
Datenbankentwurf.Formatierung;
com.db4o.Db4oEmbedded;
com.db4o.ObjectContainer;
com.db4o.ObjectSet;
public class FormatierungAendern {
public void aendern(Formatierung formatierung, Formatierung formatierungNeu){
ObjectContainer db = Db4oEmbedded.openFile
("C:/Datenbank/DasErsteProjekt/datenbank.yap");
try {
ObjectSet<Formatierung> result = db.queryByExample(formatierung);
Formatierung formatierungAusgelesen = result.next();
String name = formatierungNeu.getName();
formatierungAusgelesen.setName(name);
db.store(formatierungAusgelesen);
} catch (Exception e) {
e.printStackTrace();
} finally{
db.close();
}
}
}
Listing 7.29: FormatierungAendern.java
Löschen
Und viertens löschen wir unser Formatierungsobjekt mit der Methode delete() wieder aus der
Datenbank, wobei nicht automatisch die darin enthaltene ArrayList, die die Texte enthält, gelöscht wird. Die ArrayList wird im Konstruktor mit Parameter name in der Klasse Formatierung
angelegt, um später jeder Formatierung, Texte hinzufügen zu können. So enthält jedes Formatierungsobjekt eine ArrayList, auch wenn noch keine Texte hinzugefügt worden sind.
Die ArrayList<Text> des Formatierungsobjektes wird nicht gelöscht, so muss die folgende Befehlszeile vor dem Öffnen der Datenbank eingefügt werden:
171
7 Grundlagen der Datenbankabfragen mit JPA
Db4o . c o n f i g u r e ( ) . o b j e c t C l a s s ( Formatierung . c l a s s ) . c a s c a d e O n D e l e t e ( t r u e ) ;
Dies hat folgenden Grund: Die ArrayList könnte weitere Elemente enthalten und die sollen nicht
unabsichtlicherweise gelöscht werden. Also: Löschen Sie mit folgender Methode loeschen() der
Klasse FormatierungLoeschen.java ein Objekt der Klasse Formatierung, löschen Sie nur das Formatierungsobjekt und das darin enthaltene Stringobjekt, das den Namen zum Inhalt hat, aber
nicht die auch darin enthaltene ArrayList.
package db4oAndJPA;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import
import
import
import
Datenbankentwurf.Formatierung;
com.db4o.Db4oEmbedded;
com.db4o.ObjectContainer;
com.db4o.ObjectSet;
public class FormatierungLoeschen {
public void loeschen(Formatierung formatierung){
ObjectContainer db = Db4oEmbedded.openFile
("C:/Datenbank/DasErsteProjekt/datenbank.yap");
try {
ObjectSet<Formatierung> result = db.queryByExample(formatierung);
Formatierung formatierungAusgelesen = result.next();
db.delete(formatierungAusgelesen);
} catch (Exception e) {
e.printStackTrace();
} finally{
db.close();
}
}
}
Listing 7.30: FormatierungLoeschen.java
Lassen Sie uns dieses Verhalten anhand eines konkreten Beispiels überprüfen: Als Erstes legen wir
in unten stehendem Listing FormatierungOhneTextInDatenbank.java zwei Formatierungsobjekte
mit Namen font1 und font2 an:
package db4oAndJPA;
1
2
3
4
5
6
7
import Datenbankentwurf.Formatierung;
public class FormatierungOhneTextInDatenbank {
public static void main(String[] args){
172
7.2 Vererbungsbeziehungen
8
9
10
11
12
13
14
15
16
17
18
FormatierungSpeichern formatierungSpeichern = new FormatierungSpeichern();
Formatierung formatierungEins = new Formatierung("font1");
Formatierung formatierungZwei = new Formatierung("font2");
formatierungSpeichern.speichern(formatierungEins);
formatierungSpeichern.speichern(formatierungZwei);
}
}
Listing 7.31: FormatierungOhneTextInDatenbank.java
Wir lesen beide Formatierungsobjekte einschließlich der darin enthaltenen ArrayList mit der
unten stehenden Klasse FormatierungTextAusDatenbank.java wieder aus:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package db4oAndJPA;
import Datenbankentwurf.Formatierung;
import Datenbankentwurf.Text;
import java.util.ArrayList;
public class FormatierungTextAusDatenbank {
public static void main(String[] args){
FormatierungAuslesen formatierungAuslesen = new FormatierungAuslesen();
ArrayList<Formatierung> formatierungListe = formatierungAuslesen.auslesen();
for(Formatierung formatierung : formatierungListe){
/*Name der Formatierung wird ausgegeben:*/
System.out.println("Formatierung: "+ formatierung.getName());
ArrayList<Text> textListe = (ArrayList<Text>) formatierung.getText();
/*Anzahl der Textobjekte, die sich in der ArrayList<Text> befinden,
werden ausgegeben:*/
System.out.println("Elemente in der ArrayList: " + textListe.size());
for(Text text : textListe){
/*Die Inhalte der Textobjekte werden ausgegeben:*/
System.out.println("Text zu Formatierung: "+ text.getName());
System.out.println("Text und darin enthaltene Formatierung: "
+ text.getFormatierung().getName());
}
173
7 Grundlagen der Datenbankabfragen mit JPA
}
33
34
35
}
}
Listing 7.32: FormatierungTextAusDatenbank.java
Ausgabe:
Formatierung: font1
Elemente in der ArrayList: 0
Formatierung: font2
Elemente in der ArrayList: 0
Es wurden zwei Formatierungsobjekte angelegt, die jeweils einen Namen und eine noch leere
ArrayList enthalten.
Bevor wir versuchen wollen, die zwei Objekte mit der Klasse FormatierungLoeschen.java und der
Methode loeschen() zu löschen, erstellen wir eine Klasse ArrayListAuslesen.java mit der Methode
auslesen, die es uns ermöglicht, eine ArrayList ohne das Formatierungsobjekt auszugeben. Da es
weder möglich ist, ein Beispielobjekt ArrayList(null) zu erstellen, noch eine generische ArrayList
auszulesen, lesen wir eine nicht generische ArrayList mit
ArrayList . c l a s s
aus der Datenbank aus. Vergleichen sie hierzu auch die Ausführungen zu Interfaces und Abstrakten Klassen im Kapitel Vererbungsbeziehungen.
package db4oAndJPA;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import
import
import
import
com.db4o.Db4oEmbedded;
com.db4o.ObjectContainer;
com.db4o.ObjectSet;
java.util.ArrayList;
public class ArrayListAuslesen {
public ArrayList<ArrayList> auslesen(){
ObjectContainer db = Db4oEmbedded.openFile
("C:/Datenbank/DasErsteProjekt/datenbank.yap");
ArrayList<ArrayList> listeAllerListen = new ArrayList<ArrayList>();
ArrayList list = new ArrayList();
try {
/*Wir lesen eine ArrayList aus:*/
ObjectSet<ArrayList> result = db.queryByExample(ArrayList.class);
while (result.hasNext()){
174
7.2 Vererbungsbeziehungen
24
25
26
27
28
29
30
31
32
33
34
35
list = result.next();
listeAllerListen.add(list);
}
} catch (Exception e) {
e.printStackTrace();
} finally{
db.close();
}
return listeAllerListen;
}
}
Listing 7.33: ArrayListAuslesen.java
Und tatsächlich lassen sich die beiden ArrayListen auch ohne die zugehörigen Formatierungsobjekte mit der Klasse ArrayListAuslesenAusDatenbank.java wieder auslesen:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package db4oAndJPA;
import java.util.ArrayList;
public class ArrayListAuslesenAusDatenbank {
public static void main(String[] args){
ArrayListAuslesen arrayListAuslesen = new ArrayListAuslesen();
ArrayList<ArrayList> listeAllerListen = arrayListAuslesen.auslesen();
for(ArrayList list : listeAllerListen){
System.out.println("Groesse: "+list.size());
}
}
}
Listing 7.34: ArrayListAuslesenAusDatenbank.java
Ausgabe:
Größe: 0
Größe: 0
Jetzt löschen wir unsere beiden Formatierungsobjekte. Anschließend wollen wir feststellen, ob
tatsächlich die ArrayListen in der Datenbank verbleiben:
175
7 Grundlagen der Datenbankabfragen mit JPA
package db4oAndJPA;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import Datenbankentwurf.Formatierung;
public class FormatierungLoeschenAusDatenbank {
public static void main(String[] args){
FormatierungLoeschen formatierungLoeschen = new FormatierungLoeschen();
Formatierung formatierungEins = new Formatierung("font1");
Formatierung formatierungZwei = new Formatierung("font2");
formatierungLoeschen.loeschen(formatierungEins);
formatierungLoeschen.loeschen(formatierungZwei);
}
}
Listing 7.35: FormatierungLoeschenAusDatenbank.java
Lesen wir wieder die Formatierungsobjekte mit FormatierungTextAusDatenbank.java aus, erhalten wir - wie erwartet - keine Ausgabe. Diese wurden erwartungsgemäß gelöscht. Lesen wir
aber die ArrayListen mit ArrayListAuslesenAusDatenbank.java aus, stellen wir fest, dass die
ArrayListen nicht gelöscht wurden und weiterhin in der Datenbank verbleiben. Dies kann ein
gewünschter Nebeneffekt sein, oder auch nicht.
Ausgabe:
Größe: 0
Größe: 0
Also: Überprüfen Sie immer, ob auch die Daten gelöscht worden sind, die Sie löschen wollten.
Bevor wir aber auch die ArrayList löschen werden, lernen wir zuerst, wie wir unseren Formatierungsobjekten, Textobjekte hinzufügen können.
Text zu den Formatierungen hinzufügen
Wie können Sie Formatierungsobjekten, Texte hinzufügen? Lassen Sie uns das nahe liegende
versuchen: Wir lesen das Formatierungsobjekt und die ArrayList aus und fügen der ArrayList mit
der Methode add() ein Textobjekt hinzu. Wir müssen der Datenbank db4o außerdem mitteilen,
sie solle der ArrayList, die sich im Formatierungsobjekt befindet, Elemente hinzufügen. Dies
geschieht mit den folgenden dafür vorgesehenen Befehlszeilen:
EmbeddedConfiguration c o n f i g = Db4oEmbedded . n e w C o n f i g u r a t i o n ( ) ;
c o n f i g . common ( ) . o b j e c t C l a s s ( Formatieru ng . c l a s s ) . cascadeOnUpdate ( t r u e ) ;
O b j e c t C o n t a i n e r db = Db4oEmbedded . o p e n F i l e
( c o n f i g , "C: / Datenbank / D a s E r s t e P r o j e k t / datenbank . yap " ) ;
Die vollständige Klasse lautet wie folgt:
176
7.2 Vererbungsbeziehungen
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package db4oAndJPA;
import
import
import
import
import
import
import
Datenbankentwurf.Formatierung;
Datenbankentwurf.Text;
com.db4o.Db4oEmbedded;
com.db4o.ObjectContainer;
com.db4o.ObjectSet;
com.db4o.config.EmbeddedConfiguration;
java.util.ArrayList;
public class FormatierungTextCascade {
public void addText(Formatierung formatierung, Text text) {
EmbeddedConfiguration config = Db4oEmbedded.newConfiguration();
config.common().objectClass(Formatierung.class).cascadeOnUpdate(true);
ObjectContainer db = Db4oEmbedded.openFile
(config, "C:/Datenbank/DasErsteProjekt/datenbank.yap");
try {
ObjectSet<Formatierung> result = db.queryByExample(formatierung);
Formatierung formatierungAusgelesen = result.next();
ArrayList<Text> textList =
(ArrayList<Text>) formatierungAusgelesen.getText();
text.setFormatierung(formatierungAusgelesen);
textList.add(text);
formatierungAusgelesen.setText(textList);
db.store(formatierungAusgelesen);
} catch (Exception e) {
e.printStackTrace();
} finally {
db.close();
}
}
}
Listing 7.36: FormatierungTextCascade.java
Fügen wir nun mit dieser geänderten Methode addText() der Klasse FormatierungTextCascade.java der Formatierung „font1“ zwei Texte hinzu, erhalten wir das gewünschte Ergebnis:
1
2
3
4
5
package db4oAndJPA;
import Datenbankentwurf.Formatierung;
import Datenbankentwurf.Text;
177
7 Grundlagen der Datenbankabfragen mit JPA
public class FormatierungTextCascadeInDatenbank {
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public static void main(String[] args){
FormatierungTextCascade fa = new FormatierungTextCascade();
Formatierung f = new Formatierung("font1");
Text t = new Text("erster Text");
Formatierung fo = new Formatierung("font1");
Text te = new Text("zweiter Text");
fa.addText(f,t);
fa.addText(fo, te);
}
}
Listing 7.37: FormatierungTextCascadeInDatenbank.java
Ausgabe:
Formatierung: font1
Elemente in der ArrayList: 2
Text zu Formatierung: erster Text
Text und darin enthaltene Formatierung: font1
Text zu Formatierung: zweiter Text
Text und darin enthaltene Formatierung: font1
Formatierung: font2
Elemente in der ArrayList: 0
Der ArrayList des Formatierungsobjektes „font1“ wurden beide Textobjekte hinzugefügt.
Zusammenfassend lässt sich also sagen: Enthalten Objekte eine ArrayList, wird diese nicht automatisch mit verändert. Wollen Sie Elemente der ArrayList hinzufügen, müssen Sie dies separat
festlegen.
Textobjekte aus der ArrayList des Formatierungsobjektes wieder entfernen
Wie löschen wir den Text „erster Text“ aus der ArrayList des Formatierungsobjektes mit Namen
„font1“ wieder? Der folgende Versuch den Text mit der folgenden Befehlszeile:
EmbeddedConfiguration c o n f i g = Db4oEmbedded . n e w C o n f i g u r a t i o n ( ) ;
c o n f i g . common ( ) . o b j e c t C l a s s ( Formatieru ng . c l a s s ) . c a s c a d e O n D e l e t e ( t r u e ) ;
O b j e c t C o n t a i n e r db = Db4oEmbedded . o p e n F i l e
( c o n f i g , "C: / Datenbank / D a s E r s t e P r o j e k t / datenbank . yap " ) ;
aus der ArrayList zu entfernen gelingt, wenn Sie den zu entfernenden Text aus der Datenbank
auslesen, bevor Sie ihn aus der ArrayList mit der Methode remove() entfernen:
178
7.2 Vererbungsbeziehungen
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package db4oAndJPA;
import
import
import
import
import
import
import
Datenbankentwurf.Formatierung;
Datenbankentwurf.Text;
com.db4o.Db4oEmbedded;
com.db4o.ObjectContainer;
com.db4o.ObjectSet;
com.db4o.config.EmbeddedConfiguration;
java.util.ArrayList;
public class FormatierungTextRemoveCascade {
public void removeText(Formatierung formatierung, Text text) {
EmbeddedConfiguration config = Db4oEmbedded.newConfiguration();
config.common().objectClass(Formatierung.class).cascadeOnUpdate(true);
ObjectContainer db = Db4oEmbedded.openFile
(config, "C:/Datenbank/DasErsteProjekt/datenbank.yap");
try {
ObjectSet<Formatierung> result = db.queryByExample(formatierung);
Formatierung formatierungAusgelesen = result.next();
ArrayList<Text> textList = (ArrayList<Text>) formatierungAusgelesen.getText();
/*Sie muessen zuerst das Textobjekt, das Sie aus der ArrayList
entfernen wollen, aus der Datenbank auslesen, um das
entsprechende Objekt in der Datenbank zu referenzieren.*/
ObjectSet<Text> resultText = db.queryByExample(text);
Text textAusgelesen = resultText.next();
/*Danach koennen Sie das ausgelesene Textobjekt aus der
ArrayList mit der Methode remove() entfernen.*/
textList.remove(textAusgelesen);
formatierungAusgelesen.setText(textList);
db.store(formatierungAusgelesen);
} catch (Exception e) {
e.printStackTrace();
} finally {
db.close();
}
}
}
Listing 7.38: FormatierungTextRemoveCascade.java
Ohne cascadeOnDelete() würde sich der Text nach wie vor in der ArrayList befinden. Wir wenden
die oben stehende Methode removeText() der Klasse FormatierungTextRemoveCascade.java in
179
7 Grundlagen der Datenbankabfragen mit JPA
unten stehender Klasse FormatierungTextLoeschenInDatenbank.java an:
package db4oAndJPA;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import Datenbankentwurf.Formatierung;
import Datenbankentwurf.Text;
public class FormatierungTextLoeschenInDatenbank {
public static void main(String[] args){
FormatierungTextRemoveCascade formatierungTextRemoveCascade =
new FormatierungTextRemoveCascade();
Formatierung formatierung = new Formatierung("font1", null);
Text text = new Text("erster Text");
formatierungTextRemoveCascade.removeText(formatierung,text);
}
}
Listing 7.39: FormatierungTextLoeschenInDatenbank.java
Und die Ausgabe auf der Konsole entspricht nicht unseren Erwartungen: Es wurde leider nichts
gelöscht. Dies ist ganz eindeutig nicht so gewollt.
Ausgabe:
Formatierung: font2
Elemente in der ArrayList: 0
Formatierung: font1
Elemente in der ArrayList: 2
Text zu Formatierung: erster Text
Text und darin enthaltene Formatierung: font1
Text zu Formatierung: zweiter Text
Text und darin enthaltene Formatierung: font1
Im folgenden finden Sie einen Workaround, der in jedem Fall funktioniert: Wir füllen die Elemente
der einen ArrayList in eine andere ArrayList und filtern dabei das zu löschende Element aus. Die
erste ArrayList enthält das zu löschende Element und die zweite enthält das Element nicht.
package db4oAndJPA;
1
2
3
4
5
6
7
import
import
import
import
import
180
Datenbankentwurf.Formatierung;
Datenbankentwurf.Text;
com.db4o.Db4oEmbedded;
com.db4o.ObjectContainer;
com.db4o.ObjectSet;
7.2 Vererbungsbeziehungen
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import java.util.ArrayList;
public class FormatierungTextLoeschen {
public void removeText(Formatierung f, Text t){
ObjectContainer db = Db4oEmbedded.openFile
("C:/Datenbank/DasErsteProjekt/datenbank.yap");
ArrayList<Text> arrayText = new ArrayList();
try {
ObjectSet<Formatierung> result = db.queryByExample(f);
Formatierung p = result.next();
ArrayList<Text> a = (ArrayList<Text>) p.getText();
String name = t.getName();
for(Text text : a){
/*sollte der Name nicht identisch dem Namen des
zu loeschenden Objektes sein, wird es der neuen
ArrayList hinzugefuegt*/
if(!text.getName().equals(name)){
arrayText.add(text);
}
}
p.setText(arrayText);
db.store(p);
} catch (Exception e) {
e.printStackTrace();
} finally{
db.close();
}
}
}
Listing 7.40: FormatierungTextLoeschen.java
Texte auch aus der Datenbank entfernen
Wurde bei diesem Vorgang das Textobjekt auch aus der Datenbank gelöscht? Oder nur aus der
ArrayList entfernt? Dieser Frage werden wir jetzt nachgehen. Wir erstellen eine Klasse mit einer
Methode, die unabhängig von den Formatierungen, Texte ausliest.
181
7 Grundlagen der Datenbankabfragen mit JPA
package db4oAndJPA;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import
import
import
import
import
Datenbankentwurf.Text;
com.db4o.Db4oEmbedded;
com.db4o.ObjectContainer;
com.db4o.ObjectSet;
java.util.ArrayList;
public class TextOhneFormatierungAuslesen {
public ArrayList<Text> auslesen(){
ObjectContainer db = Db4oEmbedded.openFile
("C:/Datenbank/DasErsteProjekt/datenbank.yap");
ArrayList<Text> textList = new ArrayList<Text>();
try {
Text text = new Text(null);
ObjectSet<Text> result = db.queryByExample(text);
while (result.hasNext()){
text = result.next();
textList.add(text);
}
} catch (Exception e) {
e.printStackTrace();
} finally{
db.close();
}
return textList;
}
}
Listing 7.41: TextOhneFormatierungAuslesen.java
Wenden wir die Methode auslesen() der Klasse TextOhneFormatierungAuslesen.java an und lesen
alle Textobjekte aus der Datenbank, stellen wir fest, dass sich das Objekt Text mit Inhalt „erster
Text“ nach wie vor in der Datenbank befindet. Und das obwohl wir den Text aus der ArrayList
des Formatierungsobjektes mit Namen „font1“ entfernt haben.
package db4oAndJPA;
1
2
3
4
5
6
import Datenbankentwurf.Text;
import java.util.ArrayList;
public class TextOhneFormatierungAusDatenbank {
182
7.2 Vererbungsbeziehungen
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static void main(String[] args){
TextOhneFormatierungAuslesen textOhneFormatierungAuslesen =
new TextOhneFormatierungAuslesen();
ArrayList<Text> textList = textOhneFormatierungAuslesen.auslesen();
for(Text text: textList){
System.out.println("Name des Textes: "+text.getName());
}
}
}
Listing 7.42: TextOhneFormatierungAusDatenbank.java
Ausgabe:
Name des Textes: zweiter Text
Name des Textes: erster Text
Wäre dies ein Artikel und Sie hätten einen Artikel aus einer Rechnung nicht gelöscht, wären Sie
jetzt überglücklich. Aber in unserem Fall ist so ein allein existierender Text nur Datenmüll und
wir wollen diesen Text nicht nur aus der ArrayList entfernen, sondern auch aus der Datenbank
löschen. Die bereits bekannte Methode delete() löscht unser Textobjekt, nachdem es aus der ArrayList entfernt wurde. In der Klasse InFormatierungTextLoeschen.java werden beide Vorgänge
hintereinander durchgeführt:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package db4oAndJPA;
import
import
import
import
import
import
Datenbankentwurf.Formatierung;
Datenbankentwurf.Text;
com.db4o.Db4oEmbedded;
com.db4o.ObjectContainer;
com.db4o.ObjectSet;
java.util.ArrayList;
public class FormatierungAuchTextLoeschen {
public void removeText(Formatierung f, Text t){
ObjectContainer db = Db4oEmbedded.openFile
("C:/Datenbank/DasErsteProjekt/datenbank.yap");
ArrayList<Text> arrayText = new ArrayList();
try {
ObjectSet<Formatierung> result = db.queryByExample(f);
183
7 Grundlagen der Datenbankabfragen mit JPA
Formatierung p = result.next();
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
ArrayList<Text> a = (ArrayList<Text>) p.getText();
String name = t.getName();
for(Text text : a){
/*sollte der Name nicht identisch dem Namen des
zu loeschenden Objektes sein, wird es der neuen
ArrayList hinzugefuegt*/
if(!text.getName().equals(name)){
arrayText.add(text);
}
}
p.setText(arrayText);
db.delete(t);
db.delete(a);
db.store(p);
} catch (Exception e) {
e.printStackTrace();
} finally{
db.close();
}
}
}
Listing 7.43: FormatierungAuchTextLoeschen.java
Wir löschen unsere Datenbank und erstellen eine neue Datenbank mit der Klasse FormatierungOhneTextInDatenbank.java und der Klasse FormatierungTextCascadeInDatenbak.java. Diese enthält wieder unsere Formatierungsobjekte „font1“ und „font2“ und die dazugehörigen Texte
„erster Text“ und „zweiter Text“ . Wir löschen erneut den Text „erster Text“ aus dem Formatierungsobjekt „font1“ , dieses Mal mit mit der Methode removeText() der Klasse FormatierungTextAuchAusDatenbankLoeschen.java:
package db4oAndJPA;
1
2
3
4
5
6
7
8
9
10
11
import Datenbankentwurf.Formatierung;
import Datenbankentwurf.Text;
public class FormatierungTextAuchAusDatenbankLoeschen {
public static void main(String[] args){
FormatierungAuchTextLoeschen formatierungAuchTextLoeschen =
new FormatierungAuchTextLoeschen();
Formatierung formatierung = new Formatierung("font1", null);
184
7.2 Vererbungsbeziehungen
12
13
14
15
Text text = new Text("erster Text");
formatierungAuchTextLoeschen.removeText(formatierung, text);
}
}
Listing 7.44: FormatierungTextAuchAusDatenbankLoeschen.java
Lesen wir wieder die Text- und Formatierungsobjekte aus, stellen wir fest, dass sowohl der Text
aus der ArrayList entfernt wurde als auch aus der Datenbank. Wie folgende zwei Abfragen beweisen: Die erste Abfrage liest die Formatierungsobjekte und ihre Bestandteile mit der Klasse
FormatierungTextAusDatenbank.java aus:
Formatierung: font1
Elemente in der ArrayList: 1
Text zu Formatierung: zweiter Text
Text und darin enthaltene Formatierung: font1
Formatierung: font2
Elemente in der ArrayList: 0
Und die Zweite Abfrage mit der Klasse TextOhneFormatierungAusDatenbank.java, die nur die
Textobjekte ausliest, ergibt unten stehende Ausgabe:
Name des Textes: zweiter Text
Wir haben das gewünschte Ergebnis: Es gibt nur noch das Textobjekt mit Inhalt „zweiter Text“ in
unser Datenbank.
Ergänzende Bemerkungen zum Löschvorgang von Objekten der Klasse Formatierung
Wir wollen uns jetzt wieder dem Löschen von Objekten der Klasse Formatierung zuwenden. Wir
erinnern uns: Der Befehl cascadeOnDelete() funktioniert nicht, deswegen müssen wir uns wieder
mit einem Workaround begnügen. Und die ganze Klasse sieht wie folgt aus:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package db4oAndJPA;
import
import
import
import
import
import
Datenbankentwurf.Formatierung;
Datenbankentwurf.Text;
com.db4o.ObjectContainer;
com.db4o.ObjectSet;
com.db4o.Db4oEmbedded;
java.util.ArrayList;
public class FormatierungAllesLoeschen {
public void removeFormatierung(Formatierung f){
ObjectContainer db = Db4oEmbedded.openFile
("C:/Datenbank/DasErsteProjekt/datenbank.yap");
185
7 Grundlagen der Datenbankabfragen mit JPA
try {
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
ObjectSet<Formatierung> result = db.queryByExample(f);
Formatierung p = result.next();
ArrayList<Text> arrayListText = p.getText();
db.delete(p);
/*Die ArrayList, die die Texte enthaelt, muss nach der Formatierung
geloescht werden, da es sonst beim Auslesen von ArrayListen zu
einer Fehlermeldung kommt.*/
db.delete(arrayListText);
} catch (Exception e) {
e.printStackTrace();
} finally{
db.close();
}
}
}
Listing 7.45: FormatierungAllesLoeschen.java
Jetzt müssten bei Ihnen alle Warnblinklichter angehen: Wenn ich jetzt aber die Formatierungen
noch brauche? Solange noch Texte darin enthalten sind? Hier kommen wir wieder zu unserem
Stichwort referentielle Integrität. Ich will nicht, dass Daten gelöscht werden, die ich später noch
brauche. Eine möglich Lösung dies zu verhindern, ist eine if-Abfrage, mit der ich vorher abfragen
kann, ob es noch Daten in unserer ArrayList gibt. Sollte dies der Fall sein, wird eine Meldung
auf der Konsole ausgegeben und es ist unmöglich das Formatierungsobjekt zu löschen.
Lassen Sie uns dies testen: Wir löschen unsere Datenbank wieder und erstellen - wie oben Formatierungsobjekte mit Inhalt, indem wir zuerst die Klasse FormatierungOhneTextInDatenbank.java und dann die Klasse FormatierungTextCascadeInDatenbank.java durchführen. Nun
versuchen wir, ob wir die Formatierung „font1“ mit unten stehender Klasse FormatierungAllesLoeschenAusDatenbank.java, die die if-Abfrage enthält, löschen können.
package db4oAndJPA;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import Datenbankentwurf.Formatierung;
public class FormatierungAllesLoeschenAusDatenbank {
public static void main(String[] args){
FormatierungAllesLoeschen fal = new FormatierungAllesLoeschen();
String name = new String("font2");
Formatierung f = new Formatierung(name, null);
FormatierungAuslesenEinzeln a = new FormatierungAuslesenEinzeln();
186
7.2 Vererbungsbeziehungen
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
Formatierung fo = a.auslesenEinzeln(name);
/*Sollte sich noch ein Textobjekt in der ArrayList befinden,
dann soll eine Meldung ausgegeben werden.*/
if(fo.getText().size()!= 0){
System.out.println("Sie koennen dieses " +
"Formatierungsobjekt nicht loeschen!");
/*Befindet sich kein Text mehr in der ArrayList, kann
das Formatierungsobjekt geloescht werden*/
} else{
fal.removeFormatierung(f);
}
}
}
Listing 7.46: FormatierungAllesLoeschenAusDatenbank.java
Ausgabe:
Sie können dieses Formatierungsobjekt nicht loeschen!
Es wird also wie gewünscht unsere Fehlermeldung ausgegeben. Wurde aber tatsächlich nichts gelöscht? Dies überprüfen wir mit FormatierungTextAusDatenbank.java und wir erhalten folgende
Ausgabe:
Formatierung: font1
Elemente in der ArrayList: 2
Text zu Formatierung: erster Text
Text und darin enthaltene Formatierung: font1
Text zu Formatierung: zweiter Text
Text und darin enthaltene Formatierung: font1
Formatierung: font2
Elemente in der ArrayList: 0
Unsere if-Abfrage hat nach unseren Wünschen funktioniert. Was passiert, wenn wir in oben
stehender Klasse statt „font1“ , „font2“ löschen? Sie erhalten keine Fehlermeldung, da das Formatierungsobjekt direkt gelöscht wird. Wir wollen sichergehen, dass tatsächlich das Formatierungsobjekt und die ArrayList gelöscht worden ist und lesen wieder die Daten mit FormatierungTextAusDatenbank.java aus der Datenbank aus und erhalten folgendes Ergebnis:
Formatierung: font1
Elemente in der ArrayList: 2
Text zu Formatierung: erster Text
Text und darin enthaltene Formatierung: font1
187
7 Grundlagen der Datenbankabfragen mit JPA
Text zu Formatierung: zweiter Text
Text und darin enthaltene Formatierung: font1
Anschließend lesen wir noch alle ArrayListen mit der Klasse ArrayListAuslesenAusDatenbank.java
aus unserer Datenbank aus:
Größe: 2
Genau wie erwartet: Es gibt nur noch die ArrayList des Formatierungsobjektes font1 in der Datenbank und die ArrayList mit Größe 0 der Formatierung „font2“ wurde erfolgreich gelöscht.
Fazit: Sie können Ebene für Ebene festlegen, welche Objekte gelöscht werden und welche nicht.
Achten Sie also bei jedem Löschvorgang darauf, ob Sie alle Objekte gelöscht haben oder nicht.
Jede Stufe muss separat gelöscht werden. Dies hat z. B. beim Löschen von Rechnungen große Vorteile, da nicht automatisch die darin enthaltenen Kunden- und Artikelstammdaten mitgelöscht
werden.
Texte ändern
Wir haben Objekte der Klasse Text der ArrayList<Text> hinzugefügt, die sich in einem Objekt
der Klasse Formatierung befindet. So bleibt die Frage wie verändern wir das Textobjekt selbst?
Sie lesen das Textobjekt aus der Datenbank aus, verändern und speichern es dann wieder mit
store() in der Datenbank. So wird das Textobjekt selbst verändert und gleichzeitig auch in der
ArrayList des Formatierungsobjektes.
package db4oAndJPA;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import
import
import
import
Datenbankentwurf.Text;
com.db4o.Db4oEmbedded;
com.db4o.ObjectContainer;
com.db4o.ObjectSet;
public class TextSeparatAendern {
public void updateText(Text t, String s){
ObjectContainer db = Db4oEmbedded.openFile
("C:/Datenbank/DasErsteProjekt/datenbank.yap");
try {
ObjectSet<Text> resultText = db.queryByExample(t);
t = resultText.next();
t.setName(s);
db.store(t);
} catch (Exception e) {
e.printStackTrace();
188
7.2 Vererbungsbeziehungen
25
26
27
28
29
} finally{
db.close();
}
}
}
Listing 7.47: TextSeparatAendern.java
Wir löschen unsere Datenbank und erstellen wieder eine Neue mit zwei Formatierungsobjekten
und zwei Texten mit den Klassen FormatierungOhneTextInDatenbank.java und FormatierungTextCascadeInDatenbank.java und ändern wie unten stehend das Textobjekt „erster Text“ in
„geänderter Text“ und lesen das Ergebnis mit der Klasse FormatierungTextAusDatenbank.java
aus.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package db4oAndJPA;
import Datenbankentwurf.Text;
public class TextAendernInDatenbank {
public static void main(String[] args){
TextSeparatAendern fa = new TextSeparatAendern();
Text t = new Text("erster Text");
String s = new String("geaenderter Text");
fa.updateText(t, s);
}
}
Listing 7.48: TextAendernInDatenbank.java
Ausgabe:
Formatierung: font1
Elemente in der ArrayList: 2
Text zu Formatierung: geänderter Text
Text und darin enthaltene Formatierung: font1
Text zu Formatierung: zweiter Text
Text und darin enthaltene Formatierung: font1
Formatierung: font2
Elemente in der ArrayList: 0
Also wie Sie sehen können, erhalten wir das gewünschte Ergebnis.
189
8 Fortgeschrittenes Wissen über Abfragen in db4o
Wie wir bereits gesehen haben, gibt es in einer objektorientierten Datenbank keine SQL-Befehle,
sondern entsprechende Methoden und Abfragekonzepte aus db4o. Diese Methoden und Abfragekonzepte sind entweder direkt aus Java entnommen oder basieren auf Java. In diesem Kapitel
werden wir die Abfragekonzepte Query-by-Example, Native Queries und S.O.D.A. näher beleuchten.
Hier ein erster kurzer Überblick:
1. Native Queries: Die Methoden der Programmmiersprache Java werden als Abfragesprache verwendet. Außerdem stellt db4o einige zusätzliche Funktionalitäten zur Verfügung,
wie z. B. die Methoden der abstrakten Klasse Predicate des Packages com.db4o.query. Mit
Native Queries können Abfragen formuliert werden, die Bedingungen erfüllen müssen.
2. S.O.D.A.: db4o gibt Ihnen ein eigenes Abfragekonzept an die Hand. Die entsprechenden
Methoden finden Sie im Interface Query und Interface Constraint, die sich im Package
com.db4o.query befinden. S.O.D.A. umfasst viele Möglichkeiten, Abfragen zu erstellen, die
Datensätze suchen, die bestimmten Bedingungen entsprechen müssen.
3. Query-by-Example: Mithilfe von Beispielobjekten werden Daten ausgelesen. Hierbei
kann aber nur entweder nach Objekten gesucht werden, die genau diesem Beispiel entsprechen, oder nach allen Objekten einer Klasse.
8.1 Native Queries
Das Abfragekonzept Native Queries macht die Programmiersprache zur Abfragesprache. Dies hat
folgende Vorteil: Sie brauchen keine SQL-Befehle mehr, in die Programmiersprache Java zu integrieren. Sie können direkt mit bekannten Methoden und Hilfsmitteln aus Java Daten speichern
und wieder auslesen. In db4o können Sie also immer auf native Methoden der Programmiersprache zurückgreifen. Native Queries eignen sich im Gegensatz zu Queries-by-Example besonders
für komplexe Abfragen und Abfragen mit Bedingungen.
Zusätzlich stellt Ihnen aber auch db4o, z. B. im Package com.db4o.query, die abstrakte Klasse
Predicate mit seinen Methoden zur Verfügung. Die Klasse Predicate macht es Ihnen möglich,
Bedingungen für Abfragen zu formulieren.
8.1.1 Speichern und Auslesen von Objekten der Klasse WebSite
Bisher haben wir immer einfache Objekte unseres Content-Management-Systems ein- und wieder
ausgelesen. Wie aber werden komplexe Objekte gespeichert und wieder ausgelesen? Und wie
können komplexe Objekte ausgelesen werden, die bestimmte Bedingungen erfüllen? Dies möchte
ich anhand der Klasse WebSite näher erläutern.
Beginnen wir mit dem Speichern eines Objektes der Klasse WebSite: Wird eine ganze Webseite
abgespeichert, müssen Sie mehrere Speichervorgänge, die logisch zusammengehören, gleichzeitig
191
8 Fortgeschrittenes Wissen über Abfragen in db4o
durchführen. Es macht keinen Sinn den Text in einer separaten addText()-Methode dem Formatierungsobjekt hinzufügen und dann erst in einer anderen Methode dem WebSite-Objekt.
Diese Vorgänge müssen gleichzeitig erfolgen und zusammen gespeichert oder ausgelesen werden.
Abfragen, die zusammengehören und in einem Schritt durchgeführt werden sollten, nennt man
Transaktionen. Sollte es zu Problemen kommen, während die Datenbank geöffnet ist, können
alle Abfragen rückgängig gemacht werden, indem ein so genannter Rollback-Befehl durchgeführt
wird. Dem Thema Transaktionen widmen wir weiter unten ein ausführliches Kapitel.
Wir speichern ein Objekt der Klasse WebSite in der Klasse WebSiteEin.java in mehreren Schritten:
1. Der Text wird dem Formatierungsobjekt hinzugefügt und anschließend gespeichert.
2. Die Formatierung wird dem Textobjekt zugewiesen.
3. Der Text wird dem Objekt WebSite hinzugefügt.
4. Der Link, die Texte, die Bilder, die PDFs, die Reihenfolge und die Ebene werden dem
Objekt WebSite hinzugefügt
5. Und zum Schluß wird das Objekt WebSite gespeichert:
package Kap06;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import
import
import
import
import
import
import
import
import
Datenbankentwurf.Formatierung;
Datenbankentwurf.Text;
Datenbankentwurf.WebSite;
com.db4o.Db4o;
com.db4o.ObjectContainer;
com.db4o.ObjectSet;
com.db4o.DatabaseFileLockedException;
java.util.ArrayList;
java.util.List;
public class WebSiteEin {
public void addWebSite(WebSite w){
/*Die Methode cascadeOnUpdate() benötigen wir,
um der ArrayList<Text> Elemente hinzufügen zu können.
Die ArrayList<Text> befindet sich innerhalb des
Formatierungsobjektes.*/
Db4o.configure().objectClass(Formatierung.class).
cascadeOnUpdate(true);
ObjectContainer db = Db4o.openFile
("C:/Datenbank/DasErsteProjekt/datenbank.yap");
try {
List<Text> arrayListText = new ArrayList<Text>();
192
8.1 Native Queries
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
List<Text> text = w.getText();
Formatierung form = null;
Text t = null;
/*Wir fügen der ArrayList<Text> des Objektes Formatierung
den Text hinzu:*/
for (Text tex : text) {
String textName = tex.getName();
String formatierungName = tex.getFormatierung().getName();
Formatierung fo = new Formatierung(formatierungName);
ObjectSet<Formatierung> result = db.get(fo);
form = result.next();
t = new Text(textName);
/*Wir weisen dem Text die Formatierung hinzu.*/
t.setFormatierung(form);
form.getText().add(t);
db.set(form);
arrayListText.add(t);
}
/*Wir weisen der WebSite we alle Eigenschaften zu, ohne
der Formatierung, da wir die Formatierung bereits gespeichert haben.*/
WebSite we = new WebSite();
we.setLink(w.getLink());
we.setText(arrayListText);
we.setBild(w.getBild());
we.setPdf(w.getPdf());
we.setReihenfolge(w.getReihenfolge());
we.setEbene(w.getEbene());
/*Und wir speichern die WebSite als Ganzes in der Datenbank. */
db.set(we);
} catch (DatabaseFileLockedException e) {
e.printStackTrace();
} finally{
db.close();
}
}
}
Listing 8.1: WebSiteEin.java
Im nächsten Schritt fügen wir unserer Datenbank eine bestimmte WebSite hinzu, nämlich die
WebSite Home, die einen Text, ein Bild und ein PDF beinhaltet. Sie hat die Reihenfolge 1 und
die Ebene lassen wir leer, da wir wollen, dass die WebSite Home Teil der ersten Menüleiste ist
und kein Unterpunkt. Vorher müssen wir allerdings wieder die Datenbank löschen und wieder
193
8 Fortgeschrittenes Wissen über Abfragen in db4o
Formatierungsobjekte mit der Klasse FormatierungOhneTextInDatenbank.java in der Datenbank
speichern.
package Kap06;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import
import
import
import
import
import
import
Datenbankentwurf.Bild;
Datenbankentwurf.Formatierung;
Datenbankentwurf.Link;
Datenbankentwurf.PDF;
Datenbankentwurf.Text;
Datenbankentwurf.WebSite;
java.util.ArrayList;
public class WebSiteEinlesenInDatenbank {
public static void main(String[] args){
Link link = new Link("Home");
Formatierung f = new Formatierung("font1");
Text t = new Text("Erster Text",f);
ArrayList<Text> at = new ArrayList<Text>();
at.add(t);
Bild b = new Bild("BildHome", "Bilder/home.jpg");
ArrayList<Bild> ab = new ArrayList<Bild>();
ab.add(b);
PDF p = new PDF("PDFHome", "Dokumente/home.pdf");
ArrayList<PDF> ap = new ArrayList<PDF>();
ap.add(p);
String reihenfolge = new String("1");
String ebene = new String("");
WebSite w = new WebSite(link, at, ab, ap, reihenfolge, ebene);
WebSiteEin webSiteEin = new WebSiteEin();
webSiteEin.addWebSite(w);
}
}
Listing 8.2: WebSiteEinlesenInDatenbank.java
Wir lesen mit der Methode auslesen() der unten stehenden Klasse WebSiteAuslesen.java alle
Objekte der Klasse WebSite wieder aus.
package Kap06;
1
194
8.1 Native Queries
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import
import
import
import
import
import
Datenbankentwurf.WebSite;
com.db4o.Db4o;
com.db4o.ObjectContainer;
com.db4o.ObjectSet;
com.db4o.DatabaseFileLockedException;
java.util.ArrayList;
public class WebSiteAuslesen {
public ArrayList<WebSite> auslesen(){
ObjectContainer db = Db4o.openFile
("C:/Datenbank/DasErsteProjekt/datenbank.yap");
ArrayList<WebSite> f = new ArrayList<WebSite>();
try {
WebSite w = new WebSite(null, null, null, null, null, null);
ObjectSet<WebSite> result = db.get(w);
while (result.hasNext()){
w = result.next();
f.add(w);
}
} catch (DatabaseFileLockedException e) {
e.printStackTrace();
} finally{
db.close();
}
return f;
}
}
Listing 8.3: WebSiteAuslesen.java
Wenden wir anschließend die Methode auslesen() in der folgenden Klasse WebSiteAuslesenAusDatenbank.java an, erhalten wir genau die Daten zurück, die wir eingegeben haben:
1
2
3
4
5
6
7
8
package Kap06;
import
import
import
import
import
Datenbankentwurf.Bild;
Datenbankentwurf.PDF;
Datenbankentwurf.Text;
Datenbankentwurf.WebSite;
java.util.ArrayList;
195
8 Fortgeschrittenes Wissen über Abfragen in db4o
public class WebSiteAuslesenAusDatenbank {
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public static void main(String[] args){
WebSiteAuslesen ws = new WebSiteAuslesen();
ArrayList<WebSite> aw = ws.auslesen();
for(WebSite w: aw){
System.out.println("Link: "+ w.getLink().getName());
ArrayList<Text> al = (ArrayList<Text>) w.getText();
for(Text t :al){
System.out.println("Text: " + t.getName()
+ " Formatierung: " + t.getFormatierung().getName());
}
ArrayList<Bild> ab = (ArrayList<Bild>) w.getBild();
for(Bild b :ab){
System.out.println("Bild: " + b.getName());
}
ArrayList<PDF> ap = (ArrayList<PDF>) w.getPdf();
for(PDF p:ap){
System.out.println("PDF: " + p.getName());
}
System.out.println("Reihenfolge "+ w.getReihenfolge());
System.out.println("Ebene "+ w.getEbene());
}
}
}
Listing 8.4: WebSiteAuslesenAusDatenbank.java
Ausgabe:
Link: Home
Text: Erster Text Formatierung: font1
Bild: BildHome
PDF:PDFHome
Reihenfolge 1
Ebene
Wurde auch der Text den Formatierungsobjekten korrekt zugewiesen? Ja. Lesen Sie die Formatierungsobjekte mit den zugehörigen Texten aus, indem Sie die Klasse FormatierungTextAusDatenbank.java laufen lassen, erhalten Sie die folgende Ausgabe:
196
8.1 Native Queries
Formatierung: font1
Elemente in der ArrayList: 1
Text zu Formatierung: Erster Text
Text und darin enthaltene Formatierung: font1
Formatierung: font2
Elemente in der ArrayList: 0
Der Text wurden gleichzeitig in dem Formatierungsobjekt mit dem Namen „font1“ und in dem
WebSiteobjekt mit Namen „Home“ gespeichert.
8.1.2 Bedingungen formulieren
Wie können wir in db4o Bedingungen formulieren? Die abstrakte Klasse Predicate und seine Methode match() macht es uns möglich einfache und zusammengesetzte Bedingungen für Abfragen
zu erstellen.
Abfragen mit einfachen Bedingungen
Beginnen wir mit einer einfachen Bedingung: Wir wollen alle Hyperlinks aller Webseiten auslesen,
die auf unserer ersten Menüleiste rechts oben erscheinen, wie in unten stehender Abbildung. Wir
brauchen also alle Objekte der Klasse WebSite, für die die Bedingung "Ebene ist gleich leer"
zutrifft.
Abbildung 8.1: Obere Menüleiste mit den Hauptmenüpunkten
Um die Elemente der Menüleiste auslesen zu können, brauchen wir die abstrakte Klasse Predicate,
die wir in der API der Datenbank db4o im Package com.db4o.query.Predicate finden, und die
Methode query() des Interfaces ObjectContainer. Hier ein Ausschnitt der db4o-API, der Ihnen
einen kleinen Einblick in die abstrakte Klasse Predicate verschafft:
197
8 Fortgeschrittenes Wissen über Abfragen in db4o
Abbildung 8.2: Auszug aus der API für db4o: Die abstrakte Klasse Predicate
Wie wird eine Bedingung formuliert? Sie übergeben der Methode query() als Parameter eine
anonyme Klasse Predicate. Was ist eine anonyme Klasse? Bei anonymen Klassen werden zwei
Schritte in einem durchgeführt: Es wird eine Klasse ohne Namen und ein Objekt erzeugt. Anonyme Klassen gehören zu den inneren Klassen und werden häufig in der Programmierung von
graphischen Oberflächen verwendet.
In unten stehender Klasse WebSiteAuslesenLinkErste.java erstellen wir als Parameter für die
Methode query() eine anonyme Klasse Predicate, in der die Methode match() überschrieben wird.
Die Methode match() beinhaltet eine Bedingung. In unserem Fall ermöglicht sie, alle Objekte
der Klasse WebSite auszulesen, für die Folgendes zutrifft: die Ebene besitzt keinen Inhalt.
package Kap06;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import
import
import
import
import
import
import
import
Datenbankentwurf.Link;
Datenbankentwurf.WebSite;
com.db4o.Db4o;
com.db4o.ObjectContainer;
com.db4o.ObjectSet;
com.db4o.DatabaseFileLockedException;
java.util.ArrayList;
com.db4o.query.Predicate;
public class WebSiteAuslesenLinkErste {
public ArrayList<Link> auslesenLinkErste(){
ObjectContainer db = Db4o.openFile
("C:/Datenbank/DasErsteProjekt/datenbank.yap");
ArrayList<Link> al = new ArrayList<Link>();
try{
ObjectSet<WebSite> result = db.query(/*Hier steht
eine anonyme Klasse als Parameter einer Methode*/
new Predicate<WebSite>() {
198
8.1 Native Queries
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
/*die Methode match() muss überschrieben werden*/
public boolean match(WebSite webSite){
/*Hier steht die Bedingung: Es sollen alle WebSiteobjekte
ausgewählt werden, bei denen die Ebene leer ist*/
return webSite.getEbene().equals("");
}
}
);/*Hier steht die zweite Klammer der Methode query() und
danach ein Strichpunkt*/
/*Es werden die entsprechenden Links ausgelesen und der
ArrayList<Link> al hinzugefügt*/
while (result.hasNext()){
WebSite w = result.next();
Link l = w.getLink();
al.add(l);
}
} catch (DatabaseFileLockedException e) {
e.printStackTrace();
} finally{
db.close();
}
return al;
}
}
Listing 8.5: WebSiteAuslesenLinkErste.java
Bevor wir die Methode auslesenLinkErste() anwenden, fügen wir der Datenbank in der Klasse
WebSiteEinlesenWeitere.java drei weitere Objekte der Klasse WebSite hinzu. Die WebSite Bücher
enthält einen String ebene mit dem Inhalt "1". Diese dürfte also beim Anwenden oben stehender
Methode nicht mit ausgelesen werden.
1
2
3
4
5
6
7
8
9
10
11
12
13
package Kap06;
import
import
import
import
import
import
import
Datenbankentwurf.Bild;
Datenbankentwurf.Formatierung;
Datenbankentwurf.Link;
Datenbankentwurf.PDF;
Datenbankentwurf.Text;
Datenbankentwurf.WebSite;
java.util.ArrayList;
public class WebSiteEinlesenWeitere {
199
8 Fortgeschrittenes Wissen über Abfragen in db4o
public static void main(String[] args){
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
//Wir lesen die WebSite mit Namen Produkte ein
Link link = new Link("Produkte");
Formatierung f = new Formatierung("font1");
Text t = new Text("TextProdukte", f);
ArrayList<Text> at = new ArrayList<Text>();
at.add(t);
Bild b = new Bild("BildProdukte", "Bilder/produkte.jpg");
ArrayList<Bild> ab = new ArrayList<Bild>();
ab.add(b);
PDF p = new PDF("PDFProdukte", "Dokumente/produkte.pdf");
ArrayList<PDF> ap = new ArrayList<PDF>();
ap.add(p);
String reihenfolge = new String("2");
String ebene = new String("");
WebSiteEin webSiteEin = new WebSiteEin();
WebSite w = new WebSite(link, at, ab, ap, reihenfolge, ebene);
webSiteEin.addWebSite(w);
//Wir lesen die WebSite mit Namen DVD ein
Link linkD = new Link("DVD");
Formatierung foD = new Formatierung("font2");
Text teD = new Text("DVDText", foD);
ArrayList<Text> ateD = new ArrayList<Text>();
ateD.add(teD);
Bild biD = new Bild("BildDVD", "Bilder/dvd.jpg");
ArrayList<Bild> abiD = new ArrayList<Bild>();
abiD.add(biD);
PDF pdD = new PDF("PDF DVD", "Dokumente/dvd.pdf");
ArrayList<PDF> apdD = new ArrayList<PDF>();
apdD.add(pdD);
String reihenfolgeD = new String("2");
String ebeneD = new String("2");
WebSite we = new WebSite(linkD, ateD, abiD, apdD, reihenfolgeD, ebeneD);
webSiteEin.addWebSite(we);
200
8.1 Native Queries
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
//Wir lesen die WebSite mit Namen Bücher ein
Link linkNeu = new Link("Bücher");
Formatierung fo = new Formatierung("font2");
Text te = new Text("TextBücher", fo);
ArrayList<Text> ate = new ArrayList<Text>();
ate.add(te);
Bild bi = new Bild("BildBücher", "Bilder/buecher.jpg");
ArrayList<Bild> abi = new ArrayList<Bild>();
abi.add(bi);
PDF pd = new PDF("PDFBücher", "Dokumente/buecher.pdf");
ArrayList<PDF> apd = new ArrayList<PDF>();
apd.add(pd);
String reihenfolgeNeu = new String("2");
String ebeneNeu = new String("1");
WebSite web = new WebSite(linkNeu, ate, abi, apd, reihenfolgeNeu, ebeneNeu);
webSiteEin.addWebSite(web);
}
}
Listing 8.6: WebSiteEinlesenWeitere.java
Werden jetzt tatsächlich nur die zwei Objekte „Home“ und „Produkte“ ausgegeben? Wir testen
es und starten die folgende Klasse WebSiteAusDatenbankLinkErste.java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package Kap06;
import Datenbankentwurf.Link;
import java.util.ArrayList;
public class WebSiteAusDatenbankLinkErste {
public static void main(String[] args){
WebSiteAuslesenLinkErste ws = new WebSiteAuslesenLinkErste();
ArrayList<Link> aw = ws.auslesenLinkErste();
for(Link l : aw){
System.out.println("Link "+ l.getName());
}
}
}
201
8 Fortgeschrittenes Wissen über Abfragen in db4o
Listing 8.7: WebSiteAusDatenbankLinkErste.java
Ausgabe:
Link Home
Link Produkte
Und tatsächlich: Es hat geklappt! Unser erster Anwendungsfall für die anonyme Klasse Predicate
hat funktioniert. Es wurden nur die Daten ausgelesen, die der Bedingung entsprechen.
Sollten Sie nicht nur einen Teil der WebSites auslesen wollen, sondern alle, geht dies mit unten
stehender überschriebener Methode match():
ObjectSet<WebSite> result = db.query(new Predicate<WebSite>() {
1
2
3
4
5
6
7
8
public boolean match(WebSite webSite){
return true;
}
}
);
Listing 8.8: Auslesen aller WebSite mit Native Queries(AlleLinksNative.txt)
Abfragen mit zusammengesetzten Bedingungen
Als Nächstes wollen wir uns zusammengesetzte Bedingungen für Abfragen näher ansehen: Wir
lesen mithilfe der abstrakten Klasse Predicate und der Methode match() die Unterpunkte unseres Menüs aus. Wenn Sie auf den Hyperlink Produkte klicken, soll auf der linken Seite die
entsprechende Unterpunkte Bücher und DVDs erscheinen. Das vollständige Beispiel mit JSPs
und Servlets wird im Kapitel „Unser Content-Management-System“ ausführlich beschrieben. Im
Moment wollen wir uns auf die entsprechenden Abfragen konzentrieren.
Abbildung 8.3: Linke Menüleiste mit den Unterpunkten
202
8.1 Native Queries
Zuerst lesen wir die Variable reihenfolge der entsprechenden WebSite aus. Wir übergeben der
Methode auslesenLinkReihenfolge() als Parameter den Namen des Links der WebSite, der ausgewählt bzw. angeklickt wird. Der Parameter muß final sein, da von innerhalb der anonymen
Klasse nur auf finale Variablen des Blocks zugegriffen werden kann.
Unser Ergebnis muss in diesem Fall zwei Bedingungen erfüllen: Erstens muss das gesuchte Objekt
der Klasse WebSite einen Link mit dem Namen des übergegebenen Namen linkName besitzen
und zweitens muss die Ebene des gleichen Objektes leer sein.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package Kap06;
import
import
import
import
import
import
Datenbankentwurf.WebSite;
com.db4o.Db4o;
com.db4o.ObjectContainer;
com.db4o.ObjectSet;
com.db4o.DatabaseFileLockedException;
com.db4o.query.Predicate;
public class WebSiteAuslesenLinkReihenfolge {
/*Anonyme Klassen können nur auf Bestandteile des Blocks zugreifen,
die final sind, deshalb muss der String als final deklariert werden*/
public String auslesenLinkReihenfolge(final String linkName){
ObjectContainer db = Db4o.openFile
("C:/Datenbank/DasErsteProjekt/datenbank.yap");
String reihenfolge = new String();
try{
ObjectSet<WebSite> result = db.query(new Predicate<WebSite>() {
public boolean match(WebSite webSite){
/*Es soll das Objekt der Klasse WebSite ausgewählt werden,
für das folgende Bedingungen zutrifft:
Der Name ist identisch mit dem Parameter linkName und
die Ebene ist vom Inhalt leer.*/
return webSite.getLink().getName().equals(linkName)
&& webSite.getEbene().equals("");
}
});
/*Die Methode soll die Reihenfolge zurückgeben, für die
oben stehende Bedingungen zutrifft.*/
WebSite w = result.next();
reihenfolge = w.getReihenfolge();
203
8 Fortgeschrittenes Wissen über Abfragen in db4o
42
43
44
45
46
47
48
49
50
} catch (DatabaseFileLockedException e) {
e.printStackTrace();
} finally{
db.close();
}
return reihenfolge;
}
}
Listing 8.9: WebSiteAuslesenLinkReihenfolge.java
Dann brauchen wir die Namen der entsprechenden Links, die Unterpunkte zu unserem Hauptpunkt Produkte aus der Menüleiste sind. Der zugehörigen Abfrage auslesenLinkZweite() übergeben wir die Reihenfolge als Parameter und sie muss zwei Bedingungen erfüllen: Der String
reihenfolge des gesuchten WebSite-Objektes muss mit dem übergebenen Parameter reihenfolge übereinstimmen und der String Ebene darf nicht leer sein, da es sich um einen Unterpunkt
handelt.
package Kap06;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import
import
import
import
import
import
import
import
Datenbankentwurf.Link;
Datenbankentwurf.WebSite;
com.db4o.Db4o;
com.db4o.ObjectContainer;
com.db4o.ObjectSet;
com.db4o.DatabaseFileLockedException;
com.db4o.query.Predicate;
java.util.ArrayList;
public class WebSiteAuslesenLinkZweite {
/*Anonyme Klassen können nur auf Bestandteile des Blocks zugreifen,
die final sind.*/
public ArrayList<Link> auslesenLinkZweite(final String reihenfolge){
ObjectContainer db = Db4o.openFile
("C:/Datenbank/DasErsteProjekt/datenbank.yap");
ArrayList<Link> al = new ArrayList<Link>();
try{
ObjectSet<WebSite> result = db.query(new Predicate<WebSite>() {
public boolean match(WebSite webSite){
/*Es soll das Objekt der Klasse WebSite ausgewählt werden,
für das folgende Bedingungen zutrifft:
204
8.1 Native Queries
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
Die Reihenfolge ist identisch mit dem Parameter reihenfolge
und die Ebene ist nicht leer (! bedeutet nicht).*/
return webSite.getReihenfolge().equals(reihenfolge)
&& !webSite.getEbene().equals("");
}
});
/*Die Methode gibt alle Objekte der Klasse WebSite zurück, auf
die oben stehende Bedingungen zutreffen.*/
while (result.hasNext()){
WebSite w = result.next();
Link l = w.getLink();
al.add(l);
}
} catch (DatabaseFileLockedException e) {
e.printStackTrace();
} finally{
db.close();
}
return al;
}
}
Listing 8.10: WebSiteAuslesenLinkZweite.java
Wenden wir die zwei oben stehenden Abfragen in der Klasse WebSiteEbeneAuslesen.java an,
erhalten wir als Ergebnis für den Menüpunkt Produkte, den Link mit Namen „Bücher“ und den
Link mit Namen „DVD“ als Ausgabe:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package Kap06;
import Datenbankentwurf.Link;
import java.util.ArrayList;
public class WebSiteEbeneAuslesen {
public static void main(String[] args){
WebSiteAuslesenLinkReihenfolge wr =
new WebSiteAuslesenLinkReihenfolge();
String reihenfolge = wr.auslesenLinkReihenfolge("Produkte");
WebSiteAuslesenLinkZweite wz = new WebSiteAuslesenLinkZweite();
ArrayList<Link> al = wz.auslesenLinkZweite(reihenfolge);
for (Link l : al){
System.out.println("Link: "+l.getName());
205
8 Fortgeschrittenes Wissen über Abfragen in db4o
}
19
20
21
}
}
Listing 8.11: WebSiteEbeneAuslesen.java
Ausgabe:
Link: DVD
Link: Bücher
Alles hat hervorragend geklappt. Unsere Abfragen tun genau das, was sie tun sollen.
8.1.3 Sortieren
Wie können wir unsere Ergebnisse auf- und absteigend sortieren? Mithilfe der abstrakten Klasse
Predicate und dem Interface Comparator. Wir wollen in unserem Content-Management-System
die obere Menüleiste absteigend sortieren lassen. Es soll immer das Element mit der Reihenfolge
1, also unser Hyperlink Home, immer ganz links stehen, wie in unten stehender Abbildung.
Abbildung 8.4: Sortierte obere Menüleiste
Um dies realisieren zu können, erstellen wir wieder eine anonyme Klasse Predicate, der wir die
Bedingung übergeben. Zusätzlich erstellen wir eine anonyme Klasse Comparator und überschreiben deren Methode compare(), die es uns möglich macht, die Elemente dem Linknamen nach
aufsteigend zu sortieren. Das Comparator Interface ist in Java eine häufig benutzte Möglichkeit,
Elemente eines TreeSets oder einer ArrayList zu sortieren. db4o nutzt dieses Interface. Wir übergeben der Methode query() des Interfaces ObjectContainer als Parameter eine anonyme Klasse
Comparator zusammen mit der anonymen Klasse Predicate. Sie erhalten ein ObjectSet zurück,
das sortierte Elemente enthält.
206
8.1 Native Queries
query ( P r e d i c a t e <TargetType>p r e d i c a t e , Comparator<TargetType>comparator )
Folgende Klasse gibt eine ArrayList<Link> zurück, deren Elemente aufsteigend der Reihenfolge
nach sortiert sind:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package Kap06;
import
import
import
import
import
import
import
import
import
Datenbankentwurf.Link;
Datenbankentwurf.WebSite;
com.db4o.Db4o;
com.db4o.ObjectContainer;
com.db4o.ObjectSet;
com.db4o.DatabaseFileLockedException;
java.util.ArrayList;
com.db4o.query.Predicate;
java.util.Comparator;
public class WebSiteAuslesenLinkErsteSortieren {
public ArrayList<Link> auslesenLinkErste(){
ObjectContainer db = Db4o.openFile
("C:/Datenbank/DasErsteProjekt/datenbank.yap");
ArrayList<Link> al = new ArrayList<Link>();
/*Wir erstellen vom Interface Comparator eine anonyme Klasse und
überschreiben deren Methode compare(), die es uns möglich macht,
die Links der Reihenfolge nach aufsteigend zu sortieren.*/
Comparator<WebSite> comp = new Comparator<WebSite>(){
public int compare(WebSite w, WebSite we){
return w.getReihenfolge().compareTo(we.getReihenfolge());
}
};
try{
/*Der Methode query() wird sowohl die anonyme Klasse Predicate
als auch die anonyme Klasse Comparator als Parameter übergeben.*/
ObjectSet<WebSite> result = db.query(new Predicate<WebSite>() {
public boolean match(WebSite webSite){
return webSite.getEbene().equals("");
}
}, comp);
/*Die Objekte der Klasse Link werden sortiert aus der Datenbank
ausgelesen und ebenfalls sortiert der ArrayList<Link>
hinzugefügt.*/
while (result.hasNext()){
WebSite w = result.next();
207
8 Fortgeschrittenes Wissen über Abfragen in db4o
Link l = w.getLink();
al.add(l);
46
47
48
49
50
51
52
53
54
55
56
57
}
} catch (DatabaseFileLockedException e) {
e.printStackTrace();
} finally{
db.close();
}
return al;
}
}
Listing 8.12: WebSiteAuslesenLinkErsteSortieren.java
Wir lesen die Elemente mit der folgenden Klasse WebSiteAusDatenbankLinkErsteSortieren.java
aus und erhalten das gewünschte Ergebnis:
package Kap06;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import Datenbankentwurf.Link;
import java.util.ArrayList;
public class WebSiteAusDatenbankLinkErsteSortieren {
public static void main(String[] args){
WebSiteAuslesenLinkErsteSortieren ws =
new WebSiteAuslesenLinkErsteSortieren();
ArrayList<Link> aw = ws.auslesenLinkErste();
for(Link l : aw){
System.out.println("Link "+ l.getName());
}
}
}
Listing 8.13: WebSiteAusDatenbankLinkErsteSortieren.java
Ausgabe:
Link Home
Link Produkte
Dies entspricht aber nicht den Erfordernissen unserer Menüleiste, für unsere Menüleiste brauchen
wir Elemente, die nicht aufsteigend, sondern absteigend sortiert sind. Nachdem wir die anonyme
Klasse Comparator implementiert haben, können wir mithilfe der Methode reverse() der Klasse
Collections aus dem Package java.util die Reihenfolge der Element umdrehen:
208
8.1 Native Queries
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package Kap06;
import
import
import
import
import
import
import
import
import
import
Datenbankentwurf.Link;
Datenbankentwurf.WebSite;
com.db4o.Db4o;
com.db4o.ObjectContainer;
com.db4o.ObjectSet;
com.db4o.DatabaseFileLockedException;
java.util.ArrayList;
com.db4o.query.Predicate;
java.util.Collections;
java.util.Comparator;
public class WebSiteAuslesenLinkErsteSortierenAbsteigend {
public ArrayList<Link> auslesenLinkErste(){
ObjectContainer db = Db4o.openFile
("C:/Datenbank/DasErsteProjekt/datenbank.yap");
ArrayList<Link> al = new ArrayList<Link>();
Comparator<WebSite> comp = new Comparator<WebSite>(){
public int compare(WebSite w, WebSite we){
return w.getReihenfolge().compareTo(we.getReihenfolge());
}
};
try{
ObjectSet<WebSite> result = db.query(new Predicate<WebSite>() {
public boolean match(WebSite webSite){
return webSite.getEbene().equals("");
}
}, comp);
while (result.hasNext()){
WebSite w = result.next();
Link l = w.getLink();
al.add(l);
/*Die Methode reverse() der Klasse Collections aus
dem Package java.util, dreht die Reihenfolge der
Elemente einer Liste um. So erhalten Sie eine Liste,
deren Elemente absteigend sortiert sind.*/
Collections.reverse(al);
}
} catch (DatabaseFileLockedException e) {
209
8 Fortgeschrittenes Wissen über Abfragen in db4o
e.printStackTrace();
} finally{
db.close();
}
return al;
50
51
52
53
54
55
56
}
}
Listing 8.14: WebSiteAuslesenLinkErsteSortierenAbsteigend.java
Führen wir die modifizierte Methode auslesenLinkErste() in der unten stehenden Klasse WebSiteAusDatenbankLinkErsteSortierenAbsteigend.java durch, erhalten wir die Reihenfolge der Links,
die wir für unsere Menüleiste brauchen:
package Kap06;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import Datenbankentwurf.Link;
import java.util.ArrayList;
public class WebSiteAusDatenbankLinkErsteSortierenAbsteigend {
public static void main(String[] args){
WebSiteAuslesenLinkErsteSortierenAbsteigend ws =
new WebSiteAuslesenLinkErsteSortierenAbsteigend();
ArrayList<Link> aw = ws.auslesenLinkErste();
for(Link l : aw){
System.out.println("Link "+ l.getName());
}
}
}
Listing 8.15: WebSiteAusDatenbankLinkErsteSortierenAbsteigend.java
Ausgabe:
Link Produkte
Link Home
In dieser Reihenfolge benötigen wir die Elemente für unser Hauptmenüleiste, die sich in unserem
Content-Management-System oben befindet.
8.2 S.O.D.A.
S.O.D.A. steht abgekürzt für Simple Object Database Access und umfasst Methoden und Interfaces, die db4o im Package com.db4o.query zur Verfügung stellt, mit deren Hilfe Sie Abfragen
mit Bedingungen erstellen können.
210
8.2 S.O.D.A.
Im Package com.db4o.query finden Sie alle wichtigen Informationen zu den entsprechenden Interfaces, wie z. B. das Interface Query und Constraint. S.O.D.A. stellt neben den Native Queries
zusätzliche Abfragemöglichkeiten zur Verfügung, wobei allerdings empfohlen wird, Native Queries
den Methoden von S.O.D.A. vorzuziehen.
Verschaffen wir uns einen ersten Überblick über das Interfaces Query: Es stellt Ihnen u. a. die
Möglichkeit zur Verfügung, Abfrageergebnisse zu sortieren. Wollen Sie Ihr Ergebnis alphabetisch
aufsteigend sortieren, können Sie dies mit orderAscending() tun. Die Methode orderDescending()
sortiert absteigend. Der Methode constrain() können Sie als Parameter eine Bedingung übergeben, nach der die Daten aus der Datenbank ausgelesen werden.
Abbildung 8.5: Das Interface Query im Package com.db4o.query
Das Interface Constraint beinhaltet zuätzliche Methoden, die Ihnen bei der Formulierung von einfachen und zusammengesetzten Bedingungen hilfreich sein können, wie z.B. not(). Die Methode
not() entspricht dem Ausrufezeichen !, das nicht bedeutet.
211
8 Fortgeschrittenes Wissen über Abfragen in db4o
Abbildung 8.6: Das Interface Constraint im Package com.db4o.query
8.2.1 Bedingungen formulieren
Mit den Mitteln von S.O.D.A. werden wir Bedingungen formulieren, die teilweise identisch sein
werden, mit den bereits erstellten Bedingungen aus dem Kapitel Native Queries. Dies soll es
Ihnen ermöglichen den direkten Vergleich zu ziehen, zwischen unterschiedlichen Wegen, die zum
gleichen Ergebnis führen.
Abfragen mit einfachen Bedingungen
Wie können wir die Hyperlinks für die obere Menüleiste mit S.O.D.A.-Methoden auslesen? Mithilfe der Methoden constrain(), descend() und execute(). Der Methode constrain() werden Bedingungen übergeben, nach denen die entsprechenden Objekte oder Felder von Objekten durchsucht
werden. Mit der Methode descend() können Sie festlegen, auf welchen Bestandteil einer Klasse
die Bedingung angewendet werden soll und mit execute() wird die Abfrage durchgeführt. In der
Klasse AuslesenOben.java wollen wir alle Objekte der Klasse Link auslesen, auf die die Bedingung
„Element ebene ist gleich leer“ zutrifft.
package Kap06;
1
2
3
4
5
6
7
import
import
import
import
import
212
Datenbankentwurf.Link;
Datenbankentwurf.WebSite;
com.db4o.Db4o;
com.db4o.ObjectContainer;
com.db4o.ObjectSet;
8.2 S.O.D.A.
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import com.db4o.DatabaseFileLockedException;
import com.db4o.query.Query;
import java.util.ArrayList;
public class AuslesenOben {
public ArrayList<Link> auslesenLinkErste(){
ObjectContainer db = Db4o.openFile
("C:/Datenbank/DasErsteProjekt/datenbank.yap");
ArrayList<Link> al = new ArrayList<Link>();
try{
Query query = db.query();
/*Der Methode constrain() wird folgende Bedingung übergeben:
Es sollen alle Elemente der WebSite ausgelesen werden*/
query.constrain(WebSite.class);
/*Die Bedingung wird eingschränkt:
Die Methode descend() macht es Ihnen möglich, in
dem Element ebene der WebSite zu suchen, unter der
Bedingung, die Sie der Methode constrain() übergeben:
Es sollen nur die Elemente ausgegeben werden, bei denen
die ebene leer ist.*/
query.descend("ebene").constrain("");
/*Die Methode execute() führt die Abfrage aus.*/
ObjectSet<WebSite> result = query.execute();
/*Es werden aus den Objekten der Klasse WebSite nur
alle Links ausgelesen, auf die oben stehende Bedingung
zutrifft. Diese werden der ArrayList<Link> hinzugefügt,
die die Methode als Rückgabetyp zurückgibt.*/
while (result.hasNext()){
WebSite w = result.next();
Link l = w.getLink();
al.add(l);
}
} catch (DatabaseFileLockedException e) {
e.printStackTrace();
} finally{
db.close();
}
return al;
}
213
8 Fortgeschrittenes Wissen über Abfragen in db4o
}
57
Listing 8.16: AuslesenOben.java
Führen wir die Abfrage durch, erhalten wir die gewünschte Ausgabe auf der Konsole, nämlich
alle Links, auf die die Bedingung zutrifft:
package Kap06;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import Datenbankentwurf.Link;
import java.util.ArrayList;
public class AuslesenLinkErste {
public static void main(String[] args){
AuslesenOben ws = new AuslesenOben();
ArrayList<Link> aw = ws.auslesenLinkErste();
for(Link l : aw){
System.out.println("Link "+ l.getName());
}
}
}
Listing 8.17: AuslesenLinkErste.java
Ausgabe:
Link Home
Link Produkte
Abfragen mit zusammengesetzten Bedingungen
Wir erstellen jetzt Methoden, die es uns möglich machen, Hyperlinks für die linke Menüleiste
auszulesen. Wie geschieht dies? Mit zusammengesetzten Bedingungen, die wir in S.O.D.A. erstellen. Da es in unten stehenden Fällen um Und-Verknüpfungen handelt, können diese entweder
mit der Methode and() verknüpft werden oder sie werden einfach hintereinander ausgeführt, was
den gleichen Effekt hat. Zusätzlich verwenden wir noch die Methode not(), die die gleiche Bedeutung hat wie der logisch Not-Operator. Vergleichen Sie bitte unten stehende Methoden mit
den gleichnamigen aus dem Kapitel Native Queries.
Vorsicht: Sie übergeben der Methode constrain() einen String, und es wird intern nicht überprüft,
ob es das entsprechende Feld in der Datenbank gibt oder nicht. Sollten Sie z.B. Link statt link
schreiben, erhalten Sie als Ergebnis eine leere Menge und keine Fehlermeldung. Also achten Sie
auf korrekte Schreibung und überprüfen Sie in Ihrer Klasse, wie die Variable genau benannt
wurde.
214
8.2 S.O.D.A.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package Kap06;
import
import
import
import
import
import
import
import
import
Datenbankentwurf.Link;
Datenbankentwurf.WebSite;
com.db4o.Db4o;
com.db4o.ObjectContainer;
com.db4o.ObjectSet;
com.db4o.DatabaseFileLockedException;
com.db4o.query.Constraint;
com.db4o.query.Query;
java.util.ArrayList;
public class AuslesenLinks {
/*Die Methode auslesenLinkReihenfolge() liest die
Reihenfolge aus:*/
public String auslesenLinkReihenfolge(String linkName){
ObjectContainer db = Db4o.openFile
("C:/Datenbank/DasErsteProjekt/datenbank.yap");
String reihenfolge = new String();
try{
Query query = db.query();
query.constrain(WebSite.class);
/*Es werden zwei Bedingungen mit der Methode and()
verknüpft.*/
Constraint c = query.descend("link").descend("name").
constrain(linkName);
query.descend("ebene").constrain("").and(c);
ObjectSet<WebSite> result = query.execute();
WebSite w = result.next();
reihenfolge = w.getReihenfolge();
} catch (DatabaseFileLockedException e) {
e.printStackTrace();
} finally{
db.close();
}
return reihenfolge;
}
/*Die Methode auslesenLinkZweite() liest alle Links aus, die
Teil der linken Menüleiste sein sollen:*/
215
8 Fortgeschrittenes Wissen über Abfragen in db4o
public ArrayList<Link> auslesenLinkZweite(String reihenfolge){
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
ObjectContainer db = Db4o.openFile
("C:/Datenbank/DasErsteProjekt/datenbank.yap");
ArrayList<Link> al = new ArrayList<Link>();
try{
Query query = db.query();
query.constrain(WebSite.class);
/*Es werden auch zwei Bedingungen verknüpft, indem
sie hintereinander durchgeführt werden. Dies
hat den gleichen Effekt wie oben stehende Verknüpfung
mit der Methode and()*/
query.descend("reihenfolge").constrain(reihenfolge);
/*Die Methode not() entspricht dem Operator !, der
übersetzt nicht bedeutet.*/
query.descend("ebene").constrain("").not();
ObjectSet<WebSite> result = query.execute();
while (result.hasNext()){
WebSite w = result.next();
Link l = w.getLink();
al.add(l);
}
} catch (DatabaseFileLockedException e) {
e.printStackTrace();
} finally{
db.close();
}
return al;
}
}
Listing 8.18: AuslesenLinks.java
Testen wir oben stehende zwei Methoden mit der Klasse AuslesenLinksAusDatenbank.java, erhalten wir die Ausgabe, die wir erwartet haben, nämlich die zwei Unterpunkte des Links mit
Namen „Produkte“ :
package Kap06;
1
2
3
4
import Datenbankentwurf.Link;
import java.util.ArrayList;
216
8.2 S.O.D.A.
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class AuslesenLinksAusDatenbank {
public static void main(String[] args){
AuslesenLinks a = new AuslesenLinks();
String reihenfolge = a.auslesenLinkReihenfolge("Produkte");
ArrayList<Link> al = a.auslesenLinkZweite(reihenfolge);
for (Link l : al){
System.out.println("Link: "+ l.getName());
}
}
}
Listing 8.19: AuslesenLinksAusDatenbank.java
Ausgabe:
Link: DVD
Link: Bücher
Bedingungen mit Vergleichen
Welche Vergleichsmöglichkeiten stellt Ihnen S.O.D.A. zur Verfügung? Es sind die Methoden
smaller(), greater(), startsWith(), endsWith(), contains(), identity() und like() des Interfaces
Constraint. Vergleichen Sie hierzu auch den Auszug aus der db4o-API, den Sie weiter oben
finden können. Ich will Ihnen die Methoden startsWith() und like() anhand eines Beispiels näher
erläutern. Diese beiden Methoden haben ähnliche Funktionalitäten, unterscheiden sich aber in
der konkreten Anwendung.
Beginnen wir mit startsWith(): Wir wollen alle Objekte der Klasse WebSite auslesen, die einen
Link enthalten, dessen Name mit dem Großbuchstaben „B“ anfängt. Dies tun wir mit der Methode
auslesenLinkAnfang() der Klasse AuslesenStart.java:
1
2
3
4
5
6
7
8
9
10
11
12
13
package Kap06;
import
import
import
import
import
import
Datenbankentwurf.WebSite;
com.db4o.Db4o;
com.db4o.ObjectContainer;
com.db4o.ObjectSet;
com.db4o.DatabaseFileLockedException;
com.db4o.query.Query;
public class AuslesenStart {
public WebSite auslesenLinkAnfang(){
217
8 Fortgeschrittenes Wissen über Abfragen in db4o
ObjectContainer db = Db4o.openFile
("C:/Datenbank/DasErsteProjekt/datenbank.yap");
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
WebSite w = new WebSite();
try{
Query query = db.query();
query.constrain(WebSite.class);
/*Mit folgender Bedingung suchen Sie nach allen
Objekten der Klasse WebSite, deren Linkname
mit dem Großbuchstaben B anfängt.*/
query.descend("link").descend("name").
constrain("B").startsWith(true);
ObjectSet<WebSite> result = query.execute();
w = result.next();
} catch (DatabaseFileLockedException e) {
e.printStackTrace();
} finally{
db.close();
}
return w;
}
}
Listing 8.20: AuslesenStart.java
Und tatsächlich erhalten wir die gewünschte Ausgabe, nämlich das Objekt WebSite, das den
Link mit Namen Bücher enthält:
package Kap06;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import Datenbankentwurf.WebSite;
public class StartAusDatenbank {
public static void main(String[] args){
AuslesenStart as = new AuslesenStart();
WebSite w = as.auslesenLinkAnfang();
System.out.println("Link: " + w.getLink().getName());
}
}
Listing 8.21: StartAusDatenbank.java
218
8.2 S.O.D.A.
Ausgabe:
Link: Bücher
Achtung: Die Methode startsWith() ist case sensitive und sollten Sie den Großbuchstaben „B“ durch
den Kleinbuchstaben „b“ ersetzen wird eine IllegalStateException geworfen, wie Sie in unten stehender Abbildung sehen können, da keine Entsprechung in der Datenbank gefunden wird.
Abbildung 8.7: Es wird eine IllegalStateException geworfen
Führen wir die gleiche Abfrage mit der Methode like() durch, stellen wir fest, dass die Methode
nicht zwischen Klein- und Großbuchstaben unterscheidet.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package Kap06;
import
import
import
import
import
import
Datenbankentwurf.WebSite;
com.db4o.Db4o;
com.db4o.ObjectContainer;
com.db4o.ObjectSet;
com.db4o.DatabaseFileLockedException;
com.db4o.query.Query;
public class AuslesenLike {
public WebSite auslesenLinkAnfang(){
ObjectContainer db = Db4o.openFile
("C:/Datenbank/DasErsteProjekt/datenbank.yap");
WebSite w = new WebSite();
219
8 Fortgeschrittenes Wissen über Abfragen in db4o
try{
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
Query query = db.query();
query.constrain(WebSite.class);
query.descend("link").descend("name").
constrain("b").like();
ObjectSet<WebSite> result = query.execute();
w = result.next();
} catch (DatabaseFileLockedException e) {
e.printStackTrace();
} finally{
db.close();
}
return w;
}
}
Listing 8.22: AuslesenLike.java
Wie Sie unten stehend bei der Klasse LikeAusDatenbank.java sehen können, wird bei der Methode
like() auch bei dem Kleinbuchstaben „b“ der Link Bücher ausgelesen. Die Methode like() ist also
nicht case sensitive.
package Kap06;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import Datenbankentwurf.WebSite;
public class LikeAusDatenbank {
public static void main(String[] args){
AuslesenLike as = new AuslesenLike();
WebSite w = as.auslesenLinkAnfang();
System.out.println("Link: " + w.getLink().getName());
}
}
Listing 8.23: LikeAusDatenbank.java
Ausgabe:
Link: Bücher
Sollten Sie aber den Buchstaben „b“ durch eine Buchstaben ersetzen, den es nicht gibt, wird auch
eine IllegalStateException geworfen.
220
8.2 S.O.D.A.
8.2.2 Sortieren
Abfrageergebnisse können mit der Methode orderAscending() aufsteigend sortiert werden. Wir
wollen die Hyperlinks auf der linken Seite aufsteigend nach dem Feld ebene sortieren und das
Ergebnis soll wie folgt aussehen:
Abbildung 8.8: Sortierte linke Menüleiste
Wir wollen, dass das Objekt Bücher, das die Reihenfolge 2 und die Ebene 1 hat, sich an erster
Stelle oben befindet, und das Objekt DVD mit der Reihenfolge 2 und der Ebene 2 an zweiter
Stelle. Mit welchen Methoden erreichen wir dies? Mit der Methode orderAscending() und mit der
Methode descend(). Mit der Ersten wird sortiert und mit der Zweiten wird das Feld festgelegt,
nach dem sortiert werden soll. Wie Sie in unten stehender Klasse sehen können, werden zuerst
die Bedingungen ausgeführt und anschließend die Methode orderAscending().
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package Kap06;
import
import
import
import
import
import
import
import
Datenbankentwurf.Link;
Datenbankentwurf.WebSite;
com.db4o.Db4o;
com.db4o.ObjectContainer;
com.db4o.ObjectSet;
com.db4o.DatabaseFileLockedException;
com.db4o.query.Query;
java.util.ArrayList;
public class Sortieren {
public ArrayList<Link> auslesenLinkZweite(String reihenfolge){
ObjectContainer db = Db4o.openFile
221
8 Fortgeschrittenes Wissen über Abfragen in db4o
("C:/Datenbank/DasErsteProjekt/datenbank.yap");
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
ArrayList<Link> al = new ArrayList<Link>();
try{
Query query = db.query();
query.constrain(WebSite.class);
query.descend("reihenfolge").constrain(reihenfolge);
query.descend("ebene").constrain("").not();
/*Die Methode orderAscending() sortiert die Objekte
der Klasse WebSite, die der oben stehenden Bedingungen
entsprechen, und zwar aufsteigend nach dem Element ebene.*/
query.descend("ebene").orderAscending();
ObjectSet<WebSite> result = query.execute();
while (result.hasNext()){
WebSite w = result.next();
Link l = w.getLink();
al.add(l);
}
} catch (DatabaseFileLockedException e) {
e.printStackTrace();
} finally{
db.close();
}
return al;
}
}
Listing 8.24: Sortieren.java
8.2.3 Direkter Zugriff auf Elemente einer ArrayList
Mit S.O.D.A. haben Sie folgenden besonderen Vorteil: Sie haben einen direkten Zugriff auf Elemente einer ArrayList. So können Sie in unten stehender Methode auslesenLinkBild() das Objekt
der Klasse WebSite auslesen, das ein Bild mit dem Namen bildName enthält.
package Kap06;
1
2
3
4
5
6
7
8
import
import
import
import
import
import
222
Datenbankentwurf.WebSite;
com.db4o.Db4o;
com.db4o.ObjectContainer;
com.db4o.ObjectSet;
com.db4o.DatabaseFileLockedException;
com.db4o.query.Query;
8.2 S.O.D.A.
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class ArrayListAus {
public WebSite auslesenLinkBild(String bildName){
ObjectContainer db = Db4o.openFile
("C:/Datenbank/DasErsteProjekt/datenbank.yap");
WebSite w = new WebSite();
try{
Query query = db.query();
query.constrain(WebSite.class);
/*Sie können direkt auf die ArrayList<Bild> bild der Klasse
WebSite und anschließend auf die Variable name der Klasse
Bild mit descend() zugreifen. Mit constrain() überprüfen
Sie die Variable name auf Übereinstimmung mit bildName.*/
query.descend("bild").descend("name").constrain(bildName);
ObjectSet<WebSite> result = query.execute();
w = result.next();
} catch (DatabaseFileLockedException e) {
e.printStackTrace();
} finally{
db.close();
}
return w;
}
}
Listing 8.25: ArrayListAus.java
Wir lesen in der Klasse ArrayListAusDatenbank.java ein bestimmtes Objekt aus. Nämlich ein
Objekt der Klasse WebSite, das ein Bild mit dem Namen BildProdukte enthält. Wir erhalten als
Antwort, dass dies auf die WebSite Produkte zutrifft:
1
2
3
4
5
6
7
8
9
10
11
12
13
package Kap06;
import Datenbankentwurf.WebSite;
public class ArrayListAusDatenbank {
public static void main(String[] args){
ArrayListAus aa = new ArrayListAus();
WebSite w = aa.auslesenLinkBild("BildProdukte");
System.out.println("Linkname: " +w.getLink().getName());
223
8 Fortgeschrittenes Wissen über Abfragen in db4o
}
14
15
}
Listing 8.26: ArrayListAusDatenbank.java
Ausgabe:
Linkname: Produkte
8.3 Query-by-Example
Im Kapitel „Erste Datenbankabfragen in db4o“ haben wir das Abfragekonzept Query-by-Example
bereits ausführlich kennen gelernt. Sie erstellen ein Beispielobjekt und nach diesem wird in der
Datenbank gesucht. Diese Abfragemethode findet seine Grenzen, sobald Sie nach bestimmten Daten suchen, die Bedingungen erfüllen müssen. Müssen Abfragen Bedingungen erfüllen, benötigen
wir die Abfragekonzepte Native Queries und S.O.D.A..
Query-by-Example stellt Ihnen eine weitere Funktionalität zur Verfügung: Sie können nach einem
Element, das sich innerhalb einer ArrayList befindet, suchen. Den Zugriff auf Elemente einer
ArrayList werden wir uns im nächsten Abschnitt näher ansehen.
8.3.1 Zugriff auf Elemente einer ArrayList
Wie können Sie mithilfe von Query-by-Example Elemente auslesen, die sich innerhalb einer ArrayList eines Objektes befinden? Nehmen wir an, Sie suchen nach einer Website, die ein bestimmtes Bild enthält. Sie erstellen ein Beispielobjekt eines Bildobjektes, dieses übergeben Sie einer
ArrayList<Bild>, das Sie dann wiederum einem Beispielobjekt der Klasse WebSite übergeben.
Unten stehend finden Sie die entsprechende Klasse ArrayListAuslesenExample.java:
package Kap06;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import
import
import
import
import
import
import
Datenbankentwurf.Bild;
Datenbankentwurf.WebSite;
com.db4o.Db4o;
com.db4o.ObjectContainer;
com.db4o.ObjectSet;
com.db4o.DatabaseFileLockedException;
java.util.ArrayList;
public class ArrayListAuslesenExample {
public ArrayList<WebSite> auslesenLinkBild(String bildName){
ObjectContainer db = Db4o.openFile
("C:/Datenbank/DasErsteProjekt/datenbank.yap");
WebSite w = new WebSite();
ArrayList<WebSite> we = new ArrayList<WebSite>();
224
8.3 Query-by-Example
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
try{
/*Sie erstellen ein Beispielobjekt eines Bildes mit bildName
und fügen dieses Bild einer ArrayList<Bild> hinzu,*/
Bild bild = new Bild(bildName, null);
ArrayList<Bild> b = new ArrayList<Bild>();
b.add(bild);
/*und Sie übergeben die ArrayList einem Beispielobjekt der
Klasse WebSite.*/
w = new WebSite(null, null, b, null, null, null);
/*Jetzt wird nach allen Entsprechungen des Beispielobjektes w
in der Datenbank gesucht.*/
ObjectSet<WebSite> result = db.get(w);
while (result.hasNext()){
w = result.next();
we.add(w);
}
} catch (DatabaseFileLockedException e) {
e.printStackTrace();
} finally{
db.close();
}
return we;
}
}
Listing 8.27: ArrayListAuslesenExample.java
Wir lesen das Objekt der Klasse WebSite aus, das als Element ein Bild mit Namen BildProdukte
enthält:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package Kap06;
import Datenbankentwurf.WebSite;
import java.util.ArrayList;
public class ArrayListAusDatenbankExample {
public static void main(String[] args){
ArrayListAuslesenExample aa = new ArrayListAuslesenExample();
ArrayList<WebSite> we = aa.auslesenLinkBild("BildProdukte");
for(WebSite w:we){
System.out.println("Linkname: " +w.getLink().getName());
}
}
225
8 Fortgeschrittenes Wissen über Abfragen in db4o
}
18
Listing 8.28: ArrayListAusDatenbankExample.java
Ausgabe:
Linkname: Produkte
226
9 Client-Server-Modus in db4o
Bis jetzt haben wir db4o im Solo-Modus benutzt. Was aber machen Sie, wenn Sie db4o in
einem Netzwerk oder im Internet verwenden wollen? Dann haben Sie mindestens einen Client
und einen Server. Der Server läuft und wartet auf Anfragen von verschiedenen Benutzern, den
Clients, und arbeitet diese Anfragen ab. Der Benutzer fragt Daten ab oder gibt sie ein, und der
Server bearbeitet sie. Es gibt drei verschiedene Arten von Client-Server-Beziehungen, die von
db4o unterstützt werden:
1. Netzwerkmodus: Der Client und der Server interagieren in einem Netzwerk via TCP/IP
miteinander. Dieser Modus wird z. B. benötigt, wenn der Datenbankserver und der Client
sich nicht innerhalb der gleichen Virtual Machine befinden.
2. Embedded-Modus: Es findet eine Interaktion von Client und Server innerhalb einer Virtual Machine statt und der Client und Server befindet sich in einer Einheit. Dieser Modus
eignet sich besonders für PDAs oder Industrieroboter. Der Embedded Modus eignet sich
auch hervorragend für unser Webprojekt, da sich das gesamte Projekt normalerweise auf
einem Server mit einem Webcontainer, wie z. B. Tomcat, befindet. So finden alle Serverund Clientaktionen auf einem Webcontainer statt, da es sich bei den Clients nur um virtuelle Clients handelt. Alle Abfragen eines Webprojektes werden innerhalb einer Virtual
Machine durchgeführt und an die Benutzer werden nur fertige HTML-Seiten gesendet. Der
Embedded-Modus hat außerdem den Vorteil gegenüber dem Client-Server-Modus schneller
zu sein, da hier keine Verluste durch das Versenden von Daten mit dem TCP/IP-Protokoll
stattfinden. In diesem Modus kann der db4o interne Cache verwendet werden, der es erlaubt mit Objekten zu arbeiten, die sich im Arbeitsspeicher befinden. So kann die Anzahl
der tatsächlichen Anfragen reduziert werden und es können mehr Personen gleichzeitig auf
die Datenbank zugreifen.
3. Out-of-Band-Signalling: Clients können dem Server Nachrichten senden. Wichtige Anwendungsfälle für das Out-of-Band-Signalling sind: Sie können den Server stoppen oder
den Befehl erteilen, die Defragmentation zu starten.
Wie Sie sehen, haben wir die Welt des Solo-Modus verlassen und bewegen uns jetzt in einer
komplexeren Welt, in der Welt des Client-Server-Modus und der Embedded Modus-Interaktion.
Der Embedded-Modus stellt die Basis für die Datenbankabfragen in unserem Webprojekt dar.
9.1 Netzwerkmodus
Im Netzwerkmodus benötigen wir einen Client und einen Server, die jeweils in einem Thread
gestartet werden. Beginnen wir mit dem Server: Der Server wird mit der Methode openServer()
geöffnet. Die Methode openServer() wirft eine DatabaseFileLockedException und Sie übergeben
227
9 Client-Server-Modus in db4o
ihr zwei Parameter. Der erste Parameter ist der Pfad zu Ihrer Datenbank, die geöffnet werden
soll, und der zweite Parameter ist ein TCP/IP-Port, der nicht vergeben sein sollte.
Außerdem brauchen Sie eine Verbindung vom Server zum Client: Der Client kommuniziert mit
dem Server, indem der Server dem Client Zutritt mit der Methode grantAccess() gewährt. Der
Methode werden zwei Parameter übergeben, wobei der erste der Namen des Client ist und der
zweite ein Passwort, das Sie beliebig vergeben können.
Unten stehender Thread öffnet unseren Server, der gleich nach dem Start mithilfe der Methode
wait() in einen Wartezustand übergeht, sprich er wartet auf Clients, die mit ihm in Interaktion
treten. Die Methode wait() ist eine Methode der Klasse Object, die eine InterruptedException
wirft, die aufgefangen werden muss, deshalb muss die Methode wait() in einem try-catch-Block
stehen. Außerdem muss sie sich in einem synchronized-Block befinden.
Eigentlich müssten sowohl die DatabaseFileLockedException als auch die InterruptedException
jeweils in einem separaten catch-Block aufgefangen werden, der Übersichtlichkeit halber habe
ich darauf verzichtet. So fange ich jetzt beide Exceptions mit nur einem catch-Block auf, der
alle Exceptions auffängt. Wie wir den Server wieder schließen, werden wir in dem Kapitel zum
Thema Out-of-Band-Signalling lernen.
Hier die Klasse Server.java, die in einem Thread einen Server mit der Methode openServer()
öffnet:
package Kap09;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import com.db4o.Db4o;
import com.db4o.ObjectServer;
public class Server extends Thread {
public static void main(String[] args){
Server s = new Server();
s.start();
}
public void run() {
synchronized(this){
ObjectServer server = Db4o.openServer
("C:/Datenbank/DasErsteProjekt/datenbank.yap", 8080);
server.grantAccess("user1", "password");
try{
System.out.println("Server");
this.wait();
} catch(Exception e){
e.printStackTrace();
} finally{
System.out.println("aus");
228
9.1 Netzwerkmodus
30
31
32
33
34
server.close();
}
}
}
}
Listing 9.1: Server.java
Sollte noch eine Datenbank vorhanden sein, löschen Sie diese, da wir jetzt eine neue mit neuem
Inhalt erstellen wollen. Starten wir nun unseren Thread, in dem der Server geöffnet wird, mit
Shift + F6, können wir in NetBeans gut sehen, wie unser Server läuft und läuft..........
Abbildung 9.1: Server wartet auf den Client, der mit ihm in Interaktion treten wird
Als Nächstes brauchen wir einen Client: Einen Client öffnen wir mit der Methode openClient().
Der Methode openClient() müssen Sie als Parameter zuerst localhost, die Portnummer, den
Namen des Client und anschließend das Passwort übergeben. Die Methode openClient() wirft
eine Db4oIOException, die Sie im catch-Block auffangen müssen.
Unten stehender Thread mit Namen Client.java öffnet einen Client, der einen Link mit Namen
"Home" in der Datenbank speichert .
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package Kap09;
import
import
import
import
Datenbankentwurf.Link;
com.db4o.Db4o;
com.db4o.ObjectContainer;
java.io.IOException;
public class Client extends Thread{
ObjectContainer client = null;
public static void main(String[] args){
Client c = new Client();
c.start();
}
public void run() {
229
9 Client-Server-Modus in db4o
try{
client = Db4o.openClient
("localhost", 8080, "user1", "password");
System.out.println("Client");
Link l = new Link("Home");
client.set(l);
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
} catch(IOException e){
e.printStackTrace();
} finally{
System.out.println("Client aus");
client.close();
}
}
}
Listing 9.2: Client.java
Starten wir den Client, wird ein zweiter Thread gestartet und wieder beendet, wobei der Thread
des Servers weiterläuft. Der Server bleibt also immer geöffnet, solange bis ihn eine Nachricht
erreicht, er solle aufhören zu laufen.
Abbildung 9.2: Client tritt in Kommunikation mit dem Server
In einem zusätzlichen Thread öffnen wir einen weiteren Client und lesen den soeben gespeicherten
Link mit Namen "Home" wieder aus.
package Kap09;
1
2
3
4
5
6
import
import
import
import
230
Datenbankentwurf.Link;
com.db4o.Db4o;
com.db4o.ObjectContainer;
com.db4o.ObjectSet;
9.1 Netzwerkmodus
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import java.io.IOException;
public class ClientAus extends Thread{
ObjectContainer client = null;
public static void main(String[] args){
ClientAus c = new ClientAus();
c.start();
}
public void run() {
try{
client = Db4o.openClient
("localhost", 8080, "user1", "password");
System.out.println("Client");
Link l = new Link(null);
ObjectSet<Link> result = client.get(l);
while(result.hasNext()){
l = result.next();
System.out.println(l.getName());
}
} catch(IOException e){
e.printStackTrace();
} finally{
System.out.println("Client aus");
client.close();
}
}
}
Listing 9.3: ClientAus.java
Wir erhalten das gewünschte Ergebnis und folgende Ausgabe:
Client
Home
Client aus
Wir haben gesehen: Der Server wird gestartet und wartet ständig auf Anfragen der Clients. Die
Clients fragen beim Server an, und sie speichern dann anschließend Daten oder lesen sie aus.
231
9 Client-Server-Modus in db4o
9.2 Embedded-Modus
Der Embedded-Modus läuft auf einer Virtual Machine, sprich auf einem einzigen Gerät, wie
z. B. einem Handy. Oder: Auf einem Webcontainer, wie z. B. Tomcat. Wir werden im Kapitel
„Embedded-Modus in einem Web-Projekt“ sehen, wie der Embedded-Modus in unser ContentManagement-System integriert werden kann.
Beginnen wir hier an dieser Stelle mit der allgemeinen Funktionsweise des Embedded Modus:
Genau wie im Netzwerkmodus müssen Sie den Server mit der Methode openServer() öffnen.
Im Embedded Modus müssen Sie ihr aber nur zwei Parameter übergeben, und zwar den Pfad
der Datenbank und den Port. Im Embedded-Modus wird der Port 0 vergeben, da dieser keine
bestimmte Aufgabe hat. Der Client wird mit openClient() geöffnet und ihr wird kein Parameter
übergeben.
package Kap09;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import
import
import
import
Datenbankentwurf.Link;
com.db4o.Db4o;
com.db4o.ObjectContainer;
com.db4o.ObjectServer;
public class EmbeddedServer extends Thread {
public static void main(String[] args){
EmbeddedServer s = new EmbeddedServer();
s.start();
}
public void run() {
ObjectContainer client = null;
ObjectServer server = Db4o.openServer
("C:/Datenbank/DasErsteProjekt/datenbank.yap", 0);
try{
System.out.println("Embedded Server");
client = server.openClient();
System.out.println("Embedded Client");
Link l = new Link("Home");
client.set(l);
} catch(Exception e){
e.printStackTrace();
} finally{
System.out.println("aus");
server.close();
}
}
}
232
9.3 Out-of-Band-Signalling
Listing 9.4: EmbeddedServer.java
Die Daten werden gespeichert und wir erhalten folgende Ausgabe:
Embedded Server
Embedded Client
aus
9.3 Out-of-Band-Signalling
Wie stoppen wir den Server wieder? Der Client schickt dem Server eine Nachricht. Werden Nachrichten vom Client an den Server gesendet, wird dieser Vorgang in db4o Out-of-Band-Signalling
genannt. Der Client ist der Sender der Nachricht und der Server ist der Empfänger. Der Nachrichtenempfänger heißt in db4o MessageRecipient und der Nachrichtensender MessageSender.
Die entsprechenden Interfaces finden Sie in der db4o-API im Package com.db4o.messaging:
Abbildung 9.3: Das Package com.db4o.messaging
Wie werden Nachrichten in unserer Client-Server-Umgebung integriert? Lassen Sie uns mit dem
Server beginnen: Der Server muss das Interface MessageRecipient und dessen Methode processMessage() implementieren. In dieser Methode wird die Methode notify() auf dem Server mit
Namen serverOff ausgeführt. Die Methode notify() ist eine Methode der Klasse java.lang.Object,
die den Server serverOff benachrichtigt, dass er aufhören soll zu warten. Die Objektreferenz this
im synchronized-Block stellt in diesem Zusammenhang sicher, dass notify() auch den wartenden
Server aufweckt, ohne this passiert dies nicht. Mit this wird außerdem gewährleistet, dass auf
den Server serverOff Bezug genommen wird.
In der Klasse ServerOff.java finden Sie das vollständige Listing:
1
2
3
4
5
6
7
8
9
10
11
12
package Kap09;
import
import
import
import
com.db4o.Db4o;
com.db4o.ObjectContainer;
com.db4o.ObjectServer;
com.db4o.messaging.MessageRecipient;
public class ServerOff extends Thread implements MessageRecipient{
public static void main(String[] args){
ServerOff serverOff = new ServerOff();
serverOff.start();
233
9 Client-Server-Modus in db4o
}
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
public void processMessage
(ObjectContainer objectContainer, Object message){
/*Die Methode notify() muss in einem synchronisierten
Block stehen und this bezieht sich auf das Objekt serverOff,
das in der oben stehenden main instanziiert wurde. Es bezieht
sich also auf den Thread selbst, in dem sich der Server
befindet.*/
synchronized(this){
/*Der Operator instanceof stellt fest, ob es sich bei
der Nachricht, um ein StopServer-Objekt handelt.*/
if(message instanceof StopServer){
/*Handelt es sich um ein StopServer-Objekt, wird das
Objekt serverOff benachrichtigt, er solle aufhören zu
laufen.*/
this.notify();
}
}
}
public void run() {
synchronized(this){
ObjectServer server = Db4o.openServer
("C:/Datenbank/DasErsteProjekt/datenbank.yap", 8080);
server.grantAccess("user1", "password");
/*Der Thread, indem sich der Server befindet, wird als
Nachrichtenempfänger festgelegt.*/
server.ext().configure().clientServer().setMessageRecipient(this);
try{
System.out.println("Server läuft und soll gestoppt werden");
this.wait();
} catch(Exception e){
e.printStackTrace();
} finally{
System.out.println("aus");
server.close();
}
}
}
234
9.3 Out-of-Band-Signalling
62
}
Listing 9.5: ServerOff.java
Wir starten unseren Thread ServerOff.java mit Shift + F6 und erhalten folgende Ausgabe:
Abbildung 9.4: Der Server wird gestartet
Jetzt fehlt nur noch der Client, der unseren Server stoppt: Sie senden mit der Methode send() des
Interfaces MessageSender eine Nachricht an den Server. Dies tun Sie mithilfe eines StopServerObjektes, das Sie weiter unten finden. In unten stehendem Thread StopClient.java finden Sie das
entsprechende Listing:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package Kap09;
import
import
import
import
com.db4o.Db4o;
com.db4o.ObjectContainer;
com.db4o.messaging.MessageSender;
java.io.IOException;
public class StopClient extends Thread {
ObjectContainer client = null;
public static void main(String[] args){
StopClient stopClient = new StopClient();
stopClient.start();
}
public void run() {
try{
client = Db4o.openClient
("localhost", 8080, "user1", "password");
System.out.println("Client sagt Stop");
} catch(IOException e){
e.printStackTrace();
} finally{
MessageSender messageSender =
client.ext().configure().clientServer().getMessageSender();
235
9 Client-Server-Modus in db4o
messageSender.send(new StopServer());
30
31
32
33
34
35
36
System.out.println("Client aus");
client.close();
}
}
}
Listing 9.6: StopClient.java
Was ist ein StopServer-Objekt? Ein einfaches Objekt, das keinen Inhalt haben muss, sondern
nur im Server zur Identifizierung der Nachricht dient.
package Kap09;
1
2
3
4
5
6
7
8
public class StopServer {
public StopServer() {
}
}
Listing 9.7: StopServer.java
Starten wir den Thread StopClient.java wird tatsächlich, wie Sie unten sehen können, der Server
gestoppt:
Abbildung 9.5: Der Server wird gestoppt
Und außerdem läuft der Thread StopClient.java und endet mit folgender Ausgabe auf der Konsole:
Abbildung 9.6: Der Thread des Clients wird vollständig abgearbeitet
236
10 Transaktionen
10.1 Theoretische Grundlagen
Was ist eine Transaktion? Eine Transaktion umfasst eine Datenbankoperation oder mehrere
Datenbankoperationen, die logisch zusammengehören und zu einer zusammengefasst werden. Bei
Transaktionen werden mehrere Speicher- oder Löschvorgänge zu einem Schritt zusammengefasst
und als Einheit betrachtet und ausgeführt oder als Ganzes wieder rückgängig gemacht.
10.1.1 ACID
Leider kann ich Ihnen ein klein wenig Theorie zu den Transaktionen nicht ersparen, da dies
unerlässlich für das tiefer gehende Verständnis der Zusammenhänge ist. Transaktionen werden
anhand der so genannten ACID-Kriterien beschrieben und gemessen. Was versteht man unter
der Abkürzung ACID? ACID ist die Abkürzung für die Begriffe Atomicity, Consistency, Isolation
und Durability. Was aber verbirgt sich hinter diesen Fachbegriffen?
1. Atomicity: Mehrere Operationen können als Einzige aufgefasst werden, und damit als
Ganzes ausgeführt oder rückgängig gemacht werden.
2. Consistency: Daten vor und nach der Transaktion müssen widerspruchsfrei sein. Liefern
Sie z.B. 2 Bücher aus, so muss auch der Bücherbestand um 2 Bücher abnehmen.
3. Isolation: Gleichzeitig ablaufende Transaktionen dürfen sich nicht gegenseitig beeinflussen.
4. Durability: Speichervorgänge, die vom Benutzer bestätigt worden sind, dürfen nicht verloren gehen.
Atomicity und Durability werden in der Datenbank db4o sichergestellt. In den Transaktionen
spiegelt sich die Atomicity wider, da mehrere Datenbankvorgänge zu einem zusammengefasst
werden können. Und für die Durability wurden spezielle Mechanismen, wie z.B. die Replikation,
entwickelt.
Wie sieht es aber mit der Consistency und der Isolation aus? Insbesondere die Isolation spielt bei
Transaktionen eine große Rolle. So können, verschiedene Isolationsstufen definiert und gestaltet
werden. Dies werden wir im nächsten Abschnitt sehen.
10.1.2 Isolationsstufen
Oben steht, gleichzeitig ablaufende Transaktionen dürfen sich nicht gegenseitig beeinflussen. Was
aber passiert, wenn 2 Kunden gleichzeitig das gleiche Buch bestellen wollen? Und es gibt nur noch
eines auf Lager und Sie wollen verhindern, dass dieses Buch beide erhalten? Es gibt verschiedene
so genannte Isolationsstufen, die versuchen dieses Problem zu lösen:
1. Serializable: Transaktionen laufen hintereinander ab, so können sie sich nicht gegenseitig
beeinflussen, und es geht keine Transaktion verloren. Dies setzt voraus, dass auf bestimmte
Transaktionen nur ein Benutzer zugreifen kann. Transaktionen werden also gelockt, was
auf Kosten der Performance gehen kann.
237
10 Transaktionen
2. Repeatable Read: Normalerweise erhalten Sie während einer Transaktion, die lesenden
Zugriff auf die Datenbank hat, immer das gleiche Ergebnis, wenn Sie diesen Zugriff während
der Transaktion wiederholen. Bei Repeatable Read besteht allerdings die Möglichkeit von
so genannten Phantom Reads, bei denen eine Transaktion Daten ausliest, und das Ergebnis
wenig später einen zusätzlichen Datensatz enthält, da im Verlaufe der Transaktion durch
eine andere Transaktion, ein Datensatz hinzugefügt wurde.
3. Read Committed: Geänderte Daten innerhalb einer Transaktion werden für andere nach
einem commit sofort sichtbar. Hierdurch kann die Situation entstehen, dass die Daten, auf
die in einer Transaktion zugegriffen werden soll, am Anfang einer Transaktion andere sind
wie am Ende einer Transaktion.
4. Read Uncommitted: Bei dieser Isolationsstufe sind alle Daten einer Transaktion sofort
für alle anderen Transaktionen - auch ohne commit - sichtbar.
Diese vier Stufen entsprechen im Falle von Serializable einer extrem pessimistischen Sicht der
Speicherabläufe, die dann von einer Stufe zur nächsten auf eine immer positivere Sicht übergeht.
So geht Read Uncommited davon aus, dass es keine Transaktionen gibt, die sich wechselseitig
beeinflussen. Dies ist sicherlich nur bei Einzelplatzanwendungen eine sinnvolle Annahme.
Das Locken von Transaktionen, das analog zu synchronisierten Blöcken in Java funktioniert, ist
sicherlich bei sensiblen Daten, wie z.B. bei Ein- und Auszahlungen auf Bankkonten unbedingt
notwendig. Es geht aber mit eventuellen Einbußen bei der Performance einher.
In unten stehendem Unterkapitel zum Thema Locking werden wir sehen, wie das Locken von
bestimmten Objekten nur einen begrenzt negativen Einfluss auf die Performance haben kann. Es
muss aber oft - je nach Erfordernissen - ein Kompromiss gefunden werden, da es die optimale
Lösung nicht gibt.
Hersteller von Datenbanksystemen verfolgen diesbezüglich unterschiedliche Strategien. Bei db4o
sind alle Transaktionen Read Committed. Es besteht aber zusätzlich die Möglichkeit mithilfe von
Semaphores bestimmte kritische Bereiche zu locken, sprich als Serializable zu deklarieren. Eine
andere Art Bereiche zu locken, wäre die Vergabe von Rechten und Passwörtern für bestimmte
Daten der Datenbank.
10.2 Transaktionen in db4o
In db4o wird immer mit Transaktionen gearbeitet, auch wenn es sich nur um einen einzigen
Arbeitsschritt handelt. Eine Transaktion beginnt in db4o mit der Methode openFile() und endet
mit close().
Sollte es zu einem Abbruch während einer Transaktion kommen, können alle Operationen mit der
Methode rollback() wieder rückgängig gemacht werden. Gründe für einen Abbruch können sein:
Stromausfall, Abbruch durch den Benutzer, Arbeitsspeicherprobleme, Netzwerkprobleme und
Softwarefehler. Die Methode commit() stellt sicher, dass alle Speichervorgänge in die Datenbank
geschrieben werden und somit für Transaktionen durch andere Personen sichtbar werden. Sie
beendet aber nicht die Transaktion. Die Methode close() beendet die Transaktion und schreibt
nicht nur Daten in die Datenbank, sondern schließt sie auch.
Lassen Sie uns diesen Vorgang anhand des Löschvorgangs eines WebSite-Objektes demonstrieren.
Zuvor löschen wir eine eventuell vorhandene Datenbank und erstellen mit unten stehender Klasse
eine Neue:
package Kap10;
1
238
10.2 Transaktionen in db4o
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import
import
import
import
import
import
import
import
import
Datenbankentwurf.Bild;
Datenbankentwurf.Formatierung;
Datenbankentwurf.Link;
Datenbankentwurf.PDF;
Datenbankentwurf.Text;
Datenbankentwurf.WebSite;
Kap05.FormatierungSpeichern;
Kap06.WebSiteEin;
java.util.ArrayList;
public class FormatierungWebSite {
public static void main(String[] args) {
FormatierungSpeichern fd = new FormatierungSpeichern();
Formatierung f = new Formatierung("font1");
Formatierung form = new Formatierung("font2");
fd.speichern(f);
fd.speichern(form);
Link link = new Link("Home");
Text t = new Text("Erster Text", f);
Text te = new Text("Zweiter Text", f);
ArrayList<Text> at = new ArrayList<Text>();
at.add(t);
at.add(te);
Bild b = new Bild("BildHome", "Bilder/home.jpg");
Bild bZwei = new Bild("BildHomeZwei", "Bilder/homeZwei.jpg");
ArrayList<Bild> ab = new ArrayList<Bild>();
ab.add(b);
ab.add(bZwei);
PDF p = new PDF("PDFHome", "Dokumente/home.pdf");
PDF pZwei = new PDF("PDFHomeZwei", "Dokumente/homeZwei.pdf");
ArrayList<PDF> ap = new ArrayList<PDF>();
ap.add(p);
ap.add(pZwei);
String reihenfolge = new String("1");
String ebene = new String("");
WebSiteEin webSiteEin = new WebSiteEin();
WebSite w = new WebSite(link, at, ab, ap, reihenfolge, ebene);
webSiteEin.addWebSite(w);
}
}
239
10 Transaktionen
Listing 10.1: FormatierungWebSite.java
Als Nächstes erstellen wir eine Klasse, die die Daten einzeln ausliest, sprich jedes Objekt für sich,
um später nach dem Löschen vergleichen zu können, ob nicht nur das Objekt der Klasse WebSite
gelöscht wurde, sondern alle anderen auch. Zu diesem Zweck greifen wir in der Klasse FormatierungWebSiteAus.java auf Methoden und Klassen zurück, die wir bereits in den vorangegangenen
Kapiteln erstellt haben.
package Kap10;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import
import
import
import
import
import
import
import
import
import
import
Datenbankentwurf.Bild;
Datenbankentwurf.Formatierung;
Datenbankentwurf.Text;
Datenbankentwurf.Verlinkung;
Kap05.ArrayListAuslesen;
Kap05.FormatierungAuslesen;
Kap05.TextOhneFormatierungAuslesen;
Kap05.VerlinkungAuslesen;
Kap05.BildAuslesen;
java.util.ArrayList;
java.util.List;
public class FormatierungWebSiteAus {
public static void main(String[] args){
FormatierungAuslesen fau = new FormatierungAuslesen();
ArrayList<Formatierung> al = fau.auslesen();
for(Formatierung f : al){
System.out.println("Formatierung: "+ f.getName());
List<Text> at = f.getText();
System.out.println("Elemente in der ArrayList: " + at.size());
for(Text t : at){
System.out.println("Text zu Formatierung: "+ t.getName());
}
}
TextOhneFormatierungAuslesen to = new TextOhneFormatierungAuslesen();
ArrayList<Text> at = to.auslesen();
for(Text t: at){
System.out.println("Name des Textes: "+t.getName());
}
VerlinkungAuslesen va = new VerlinkungAuslesen();
ArrayList<Verlinkung> av = va.auslesen();
for(Verlinkung v : av){
System.out.println("Pdf- oder Linkname: "+v.getName() );
}
240
10.2 Transaktionen in db4o
42
43
44
45
46
47
48
49
50
51
52
53
54
55
BildAuslesen ba = new BildAuslesen();
ArrayList<Bild> b = ba.auslesen();
for(Bild bi: b){
System.out.println("Name des Bildes: "+bi.getName());
System.out.println("Pfad des Bildes: "+bi.getPfad());
}
ArrayListAuslesen ala = new ArrayListAuslesen();
ArrayList<ArrayList> alist = ala.auslesen();
for(ArrayList a : alist){
System.out.println("Größe: "+a.size());
}
}
}
Listing 10.2: FormatierungWebSiteAus.java
Ausgabe:
Formatierung: font1
Elemente in der ArrayList: 2
Text zu Formatierung: Erster Text
Text zu Formatierung: Zweiter Text
Formatierung: font2
Elemente in der ArrayList: 0
Name des Textes: Erster Text
Name des Textes: Zweiter Text
Pdf- oder Linkname: PDFHome
Pdf- oder Linkname: PDFHomeZwei
Pdf- oder Linkname: Home
Name des Bildes: BildHome
Pfad des Bildes: Bilder/home.jpg
Name des Bildes: BildHomeZwei
Pfad des Bildes: Bilder/homeZwei.jpg
Größe: 0
Größe: 2
Größe: 2
Größe: 2
Größe: 2
Jetzt kommen wir zu dem eigentlichen Löschvorgang: Mit unten stehender Methode deleteWebSite() der Klasse WebSiteLoeschen.java löschen wir ein Objekt der Klasse WebSite, wobei die
Methode rollback() im catch-Block steht, für den Fall, dass es zu Problemen beim Löschen kommen sollte. In diesem Fall werden alle Befehle des try-Blocks wieder rückgängig gemacht. Der
Löschvorgang besteht aus mehreren Vorgängen, die zu einer Transaktion zusammengefasst werden:
1. Sie müssen den Text der WebSite auch aus der ArrayList<Text> des entsprechenden Formatierungsobjektes entfernen.
241
10 Transaktionen
2. Sie müssen das WebSite-Objekt löschen.
In diesem Fall ist es sinnvoll, wirklich alle Daten zu löschen, die sich in dem Objekt WebSite
befinden. Aber Vorsicht: Wollen Sie z.B. eine Rechnung aus der Datenbank entfernen, dürfen Sie
nicht die dazugehörigen Artikel- und Kundendaten löschen, sondern nur die Rechnung selbst.
package Kap10;
import Datenbankentwurf.Formatierung;
import Datenbankentwurf.Link;
import Datenbankentwurf.Text;
import Datenbankentwurf.WebSite;
import com.db4o.Db4o;
import com.db4o.ObjectContainer;
import com.db4o.ObjectSet;
import com.db4o.DatabaseFileLockedException;
import java.util.ArrayList;
import java.util.List;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public class WebSiteLoeschen {
public void deleteWebSite(Link link){
/*Die Methode cascadeOnUpdate(true) ermöglicht es Ihnen,
die Objekte der Klasse Text aus der ArrayList<Text>
der Formatierungsobjekte zu löschen.*/
Db4o.configure().objectClass(Formatierung.class).
cascadeOnUpdate(true);
/*Es werden alle Objekte in dem entsprechenden Objekt
der Klasse WebSite gelöscht. */
Db4o.configure().objectClass(WebSite.class).
cascadeOnDelete(true);
ObjectContainer db = Db4o.openFile
("C:/Datenbank/DasErsteProjekt/datenbank.yap");
Formatierung form = new Formatierung();
try {
WebSite w = new WebSite(link, null, null, null, null, null);
ObjectSet<WebSite> resultWebSite = db.get(w);
w = resultWebSite.next();
List<Text> text = w.getText();
/*Die ArrayList<Text> des WebSite-Objektes wird ausgelesen,*/
for(Text te: text){
242
10.2 Transaktionen in db4o
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
/*die darin enthaltenen Textobjekte müssen zuerst aus der
Datenbank ausgelesen werden, da sie sonst nicht aus
der ArrayList<Text> der Formatierungsobjekte gelöscht
werden können. Und sie müssen wieder einer neuen
ArrayList<Text> listText hinzugefügt werden.*/
ObjectSet<Text> resultText = db.get(te);
while (resultText.hasNext()){
Text t = resultText.next();
String formatierungName = t.getFormatierung().getName();
/*Das Formatierungsobjekt zu dem der Text gehört,
wird aus der Datenbank ausgelesen, indem dem Beispielobjekt die soeben erstellte ArrayList<Text> listText
als Parameter übergeben wird.*/
Formatierung fo = new Formatierung(formatierungName);
ObjectSet<Formatierung> resultFormat = db.get(fo);
while (resultFormat.hasNext()){
Formatierung forme = resultFormat.next();
List<Text> formatText = forme.getText();
/*Der Text, der zur WebSite gehört, wird aus
der ArrayList<Text> des Formatierungsobjektes
gelöscht.*/
formatText.remove(t);
/*Die geänderte ArrayList<Text> wird wieder dem Formatierungsobjekt
zugewiesen,*/
forme.setText(formatText);
/*Danach wird wieder das Formatierungsobjekt gespeichert.*/
db.set(forme);
}
}
}
/*Das Objekt WebSite wird gelöscht.*/
db.delete(w);
} catch (DatabaseFileLockedException e) {
/*Sollte es zu Problemen beim Löschen des WebSite-Objektes
kommen, werden alle oben stehenden Vorgänge wieder rückgängig
gemacht.*/
db.rollback();
} finally{
db.commit();
db.close();
}
243
10 Transaktionen
}
93
94
}
Listing 10.3: WebSiteLoeschen.java
Wir löschen die WebSite, die einen Link mit Namen "Home" besitzt mit der folgenden Klasse:
package Kap10;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import Datenbankentwurf.Link;
public class WebSiteLoeschenAusDatenbank {
public static void main(String[] args){
WebSiteLoeschen wsl = new WebSiteLoeschen();
Link l = new Link("Home");
wsl.deleteWebSite(l);
}
}
Listing 10.4: WebSiteLoeschenAusDatenbank.java
Wir lesen die Daten mithilfe der Klasse FormatierungWebSiteAus.java aus, um festzustellen, ob
tatsächlich alles gelöscht wurde:
Formatierung: font1
Elemente in der ArrayList: 0
Formatierung: font2
Elemente in der ArrayList: 0
Größe: 0
Größe: 0
Und tatsächlich es wurde das Objekt der Klasse WebSite gelöscht und alle darin enthaltenen
Objekte. Es sind nur die zwei Formatierungsobjekte und die darin enthaltenen Listen übrig
geblieben. Sie können sich sicherlich vorstellen, welche Probleme auftauchen können, wenn nicht
alle Schritte innerhalb der Transaktion durchgeführt oder wieder rückgängig gemacht werden. So
könnte es passieren, dass Texte weiterhin den Formatierungsobjekten zugeordnet, aber tatsächlich
aus der Datenbank gelöscht worden sind, was zu einer NullPointerException führen würde. Dieses
Problem wird umgangen, indem alle Anweisungen, die ein Objekt der Klasse WebSite und die
darin enthaltenen Objekte löscht, entweder ganz oder gar nicht durchgeführt werden.
10.2.1 Transaktionen und die Methode refresh()
Wozu brauche ich bei Transaktionen die Methode refresh()? Um Daten nach einem Rollback
aktualisieren zu können. In dem obigen Beispiel für Transaktionen ist dies nicht notwendig, da
die Datenbank nach jedem Löschvorgang wieder geschlossen wird. Sollte aber im Embedded
244
10.3 Transaktionen und Sessions in Hibernate
Modus die Datenbank während eines längeren Zeitraums geöffnet bleiben, müssen die Daten
der Datenbank nach einem Rollback wieder auf den neuesten Stand gebracht werden, da der
Arbeitsspeicher von db4o nicht automatisch aktualisiert wird (siehe Kapitel „Embedded Modus
in einem Web-Projekt“ ). So könnte es sein, dass Sie nach einem Rollback mit Daten aus dem
Arbeitsspeicher weiterarbeiten. Die entsprechende Befehlszeile ist die Folgende:
db . e x t ( ) . r e f r e s h (w, I n t e g e r .MAX_VALUE) ;
Der Methode refresh() werden 2 Parameter übergeben: Ein Objekt der entsprechenden Klasse,
hier das Objekt der Klasse WebSite mit Namen w, und die Suchtiefe. Die Methode refresh()
befindet sich im Interface ExtObjectContainer im Package com.db4o.ext.
10.3 Transaktionen und Sessions in Hibernate
10.3.1 JTA und JDBC-Transaktionen
Die Java Database Connectivity (JDBC) API ist ein Industriestandard, der Datenbankverbindungen zwischen Java und einer Vielzahl an Datenbanken ermöglicht. Hierbei werden die Transaktionen direkt von der Datenbank verwaltet. Die JDBC-Transaktionsverwaltung wird in einer
Entwicklungsumgebung verwendet, in der kein Web-Container, wie z.B. JBoss notwendig ist, und
nur Tomcat zur Verfügung steht.
Die Alternative zu der JDBC-Transaktionsverwaltung stellt die Java Transaction API (JTA) dar,
die die Verwendung von Enterprise Java Beans (EJBs) und eines Java Applications Server, wie
z. B. JBoss, vorausetzt. JTA wird mit Enterprise Java Beans bei folgenden Anwendungsfällen
eingesetzt:
1. Wenn Client und Server sich auf verschiedenen Rechnern bzw. in verschiedenen Java Virtual
Machines befinden.
2. Für den Fall, dass Datenbank und Anwendung auf zwei Rechner aufgeteilt sind.
3. Wenn die Anwendung auf mehrere Computer verteilt ist und von einem Cluster gesprochen
werden kann.
4. Mit JTA können die Funktionalitäten von Webservices und Java Messaging Services eingebettet werden.
5. Sollte innerhalb einer Anwendung auf mehrere Datenbanken zugegriffen werden.
10.3.2 Transaktionsgrenzen bei den JDBC-Transaktionen
Session-per-Request-Pattern
In Hibernate müssen Sie den Beginn und das Ende einer Transaktion explizit angeben: Eine
Transaktion wird mit der Methode beginTransaction() begonnen und mit commit wieder beendet. Betrachten wir als Erstes das vollständige Standardverfahren für den Ablauf einer Transaktion: Zuerst verschaffen wir uns Zugriff auf die SessionFactory, dann wird eine Session und eine
Transaktion begonnen, die in umgekehrter Reihenfolge wieder beendet werden müssen, nachdem
eine oder mehrere Datenbankabfragen durchgeführt worden sind. Sollte es während der Transaktion zu einem Fehler kommen, müssen alle Datenbankoperationen mit der Methode rollback()
245
10 Transaktionen
wieder rückgängig gemacht werden, da es ansonsten zu nicht vollständigen Daten in der Datenbank kommen könnte. Denken Sie an folgenden Fall: So könnten bei einer Rechnung, nur die
Kundendaten und nicht die Artikeldaten gespeichert werden, und Sie hätten eine unvollständige
Rechnung.
package Kap10;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import
import
import
import
import
Datenbankentwurf.Formatierung;
org.hibernate.Session;
org.hibernate.SessionFactory;
org.hibernate.Transaction;
util.HibernateUtil;
public class JDBCStandardTransaktion {
public void speichern(Formatierung f) {
Session session = null;
/*Wir verschaffen uns Zugriff auf die SessionFactory von Hibernate.*/
SessionFactory sessionFactory = HibernateUtil.getSessionFactory();
/*Wir öffnen eine Session.*/
session = sessionFactory.openSession();
Transaction tx = null;
/*Im try-Block wird versucht, eine Transaktion zu öffnen und der
Datenbank ein Element hinzuzufügen.*/
try {
/*Wir beginnen die Transaktion.*/
tx = session.beginTransaction();
/*Wir speichern unser Objekt.*/
session.save(f);
/*Wir beenden die Transaktion und schreiben die Daten in
die Datenbank.*/
tx.commit();
/*Sollte es zu Problemen kommen, wird die Transaktion mit rollback()
rückgängig gemacht und es wird eine Fehlermeldung ausgegeben.*/
} catch (RuntimeException e) {
tx.rollback();
e.printStackTrace();
/*Alles was im finally-Block steht, wird auf jeden Fall durchgeführt,
so wird in jedem Fall die Session wieder geschlossen.*/
} finally {
246
10.3 Transaktionen und Sessions in Hibernate
45
46
47
48
49
50
51
52
if (session != null) {
/*Wir müssen die Session explizit beenden.*/
session.close();
}
}
}
}
Listing 10.5: JDBCStandardTransaktion.java
Diese Vorgehensweise wird Session-per-Request-Pattern genannt und es wird eine Session pro
Benutzeranfrage geöffnet und wieder geschlossen. In diesem Fall entspricht eine Session einer
Transaktion.
Open-Session-in-View-Pattern
Eine andere Vorgehensweise stellt das Pattern Open Session in View dar, das Probleme beim
Laden von Objekten verhindern soll. Für dieses Probleme ist das Lazy Loading verantwortlich,
das nur das Objekt selbst und nicht die darin enthaltenen Objekte einer ArrayList ausliest und
nur in der Lage ist, diese Daten nachzuladen, solange die Session geöffnet ist. Würden wir ein
Objekt der Klasse WebSite laden, würden die dazugehörigen Bilder, Texte und PDFs fehlen und
es würde eine LazyInitializationException geworfen.
Eine Lösung für dieses Problem ist das Open-Session-in-View-Pattern, das die Transaktionsverwaltung an einen Filter delegiert. Im Filter wird die Transaktion mit der Methode getCurrentSession() an den Thread gebunden. Ein Thread im Webcontext umfasst einen Request und
den dazugehörigen Response. So wird im Falle von unten stehendem Filter die Transaktion erst
geschlossen, wenn der Benutzer die Antwort, also die fertig gerenderte JSP-Seite erhalten hat.
So besteht jederzeit die Möglichkeit Daten, die nicht sofort geladen werden, nachzuladen. Der
Request wird mit chain.doFilter(request, response) solange an weitere Filter weitergeleitet bis es
bei einem Servlet landet, in dem dann die entsprechende Methode mit einer Datenbankabfrage
aufgerufen wird.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package Eingang;
import
import
import
import
import
import
import
import
import
java.io.IOException;
javax.servlet.Filter;
javax.servlet.FilterChain;
javax.servlet.FilterConfig;
javax.servlet.ServletException;
javax.servlet.ServletRequest;
javax.servlet.ServletResponse;
org.hibernate.SessionFactory;
util.HibernateUtil;
public class FilterOpenSessionInView implements Filter {
private SessionFactory factory;
247
10 Transaktionen
public void init(FilterConfig filterConfig) throws ServletException {
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/*Hier wird die SessionFactory erzeugt.*/
factory = HibernateUtil.getSessionFactory();
}
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
try {
factory.getCurrentSession().beginTransaction();
/*Die Anfrage wird an ein weiteren Filter oder an ein Servlet
weitergeleitet.*/
chain.doFilter(request, response);
factory.getCurrentSession().getTransaction().commit();
} catch (RuntimeException e) {
factory.getCurrentSession().getTransaction().rollback();
e.printStackTrace();
}
}
public void destroy() {
}
}
Listing 10.6: FilterOpenSessionInView.java
Dieser Filter muss natürlich in der web.xml registriert werden (vgl. Kapitel zum Thema Filter)
und der FilterOpenSessionInView wird bei jedem Request aufgerufen:
1
2
3
4
5
6
7
8
9
<filter>
<filter-name>FilterOpenSessionInView</filter-name>
<filter-class>Eingang.FilterOpenSessionInView</filter-class>
</filter>
<filter-mapping>
<filter-name>FilterOpenSessionInView</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Listing 10.7: EintragFilterOpenSessionInView.xml
Eine Methode speichern(), die dann die Datenbankabfrage enthält, sieht dann vereinfacht wie
folgt aus:
248
10.4 Persistenzlebenszyklus in Hibernate
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package Kap10;
import Datenbankentwurf.Formatierung;
import org.hibernate.Session;
import util.HibernateUtil;
public class JDBCOpenSessionInView {
Session currentSession;
public void speichern(Formatierung f) {
/*Wir greifen auf die aktuelle Session mit getCurrentSession() zu*/
currentSession = HibernateUtil.getSessionFactory().getCurrentSession();
currentSession.save(f);
}
}
Listing 10.8: JDBCOpenSessionInView.java
Die Konfigurationsdatei von Hibernate hibernate.cfg.xml muss um zwei Einträge erweitert werden:
1
2
3
4
5
6
7
<property name="transaction.factory_class">
org.hibernate.transaction.JDBCTransactionFactory
</property>
<property name="current_session_context_class">
thread
</property>
Listing 10.9: FilterHibernateConfig.xml
10.4 Persistenzlebenszyklus in Hibernate
Was versteht man unter dem Begriff Persistenzlebenszyklus? Im Laufe einer Anwendung mit
Hibernate durchlaufen Objekte verschiedene Stadien, die in direktem Zusammenhang mit dem
Öffnen und Schließen einer Session in Hibernate stehen. Die 4 Zustände von Objekten, die sie
in ihrem Leben durchlaufen können sind: transient, persistent, detached und removed. In der
deutschsprachigen Literatur wird oft der Begriff Objektlebenszyklus synonym für Persistenzlebenszyklus verwendet.
10.4.1 Die Zustände transient, persistent, detached und removed
Der Zustand transient
Der erste Zustand ist der Zustand transient, der mit dem Instanziieren eines Objektes mit dem
Operator new beginnt:
249
10 Transaktionen
Formatierung f o r m a t i e r u n g = new Formati erung ( ) ;
Sie erstellen ein neues Objekt und solange ein Objekt neu ist, besteht keine Verbindung mit der
Session und der Datenbank.
Der Zustand persistent
Der zweite Zustand heißt in Hibernate persistent und in der Java Persistenz API managed.
Diesen Zustand erreichen wir, sobald wir die Methode save() aufrufen und eine Verbindung zur
Datenbank herstellen. Zu diesem Zeitpunkt wird dem Objekt ein Primärschlüssel zugewiesen.
save ( formatierung ) ;
Das Objekt wird allerdings erst mit dem Inhalt der Datenbank synchronisiert, wenn die Methoden commit() oder flush() aufgerufen werden. Jedes Objekt muss separat in den Zustand
persistent überführt werden. Persistente Objekte werden von Hibernate gecacht und Hibernate kann feststellen, ob diese Objekte durch einen anderen Teilnehmer geändert worden sind.
Welche Methoden überführen Objekte ebenfalls in den Zustand persistent? Z.B. die Methoden
saveOrUpdate(), persist(), get() und load().
Der Zustand detached
So ist die Methode close(), die die Session beendet, auch der Übergang zu dem Zustand detached
(=losgelöst). Das Objekt ist nach dem close() nicht mehr mit der Datenbank verbunden und somit
losgelöst.
session . close ();
Ein Objekt, das detached ist, kann vom Inhalt her von dem gleichen Objekt in der Datenbank
differieren, da es nicht mehr mit dem Inhalt in der Datenbank synchronisiert ist.
Der Zustand removed
Ein Objekt geht vom persistenten in den Zustand removed über, in dem Moment, in dem es zum
Löschen angemeldet wird. Mit welchen Methoden geschieht dies? Mit delete() oder remove().
session . delete ();
Wobei es zu beachten gilt, dass es bis zum Ende der Session im persistenten Zustand bleibt, aber
nach dem das Objekt zum Löschen angemeldet worden ist, nicht mehr verwendet werden soll.
10.5 Lockingprozesse
10.5.1 Theoretische Grundlagen: Optimistisches und Pessimistisches Sperren
Pessimistisches Sperren
Pessimistisches Sperren (der englische Fachbegriff: Pessimistic Locking) sperrt einen Datensatz
während der ganzen Eingabe durch einen Benutzer. So kann, wenn z.B. eine Website neu angelegt
wird, u.U. eine Website während einer halben Stunde blockiert sein und somit durch keinen anderen Benutzer verändert werden. Dies verhindert auf der einen Seite Kollisionen beim Speichern
der Daten, kann aber auf der anderen Seite zu erheblichen Einbußen bei der Performance führen.
Die Java Persistence API unterstützt Pessimistisches Sperren nicht und Hibernate delegiert es
250
10.5 Lockingprozesse
an die Datenbank. Wollen Sie diese Art des Sperrens verwenden, benötigen Sie eine Datenbank,
die die verschiedenen Arten des Sperrens unterstützt. Diese extreme Vorgehensweise ist sicherlich
nur bei Einzelplatzanwendungen zu empfehlen.
Optimistisches Sperren
Optimistisches Sperren oder auch Optimistic Locking eine häufig verwendete Methode, die davon
ausgeht, dass es selten Fälle gibt, bei denen gleichzeitig auf den gleichen Datensatz zugegriffen
wird und es somit selten zu Problemen kommt. Beim Optimistischen Sperren wird überprüft, ob
während der Eingabe des Benutzers, das Objekt durch einen anderen Benutzer geändert worden
ist. Es wird also das Objekt aus der Datenbank mit dem Objekt in der aktuellen Transaktion
verglichen. Sollten sich beide Versionen unterscheiden, kann mithilfe einer if-Abfrage dem User
eine Meldung gesendet werden, die ihm mitteilt, das Objekt sei durch einen anderen User geändert worden. Optimistisches Sperren hat zwar einerseits enorme Performancevorteile, kann
aber anderseits zu nicht korrekten Daten führen, falls doch gleichzeitig Daten verändert werden. Optimistisches Sperren kann gemäß der Spezifikation für die Java Persistence API durch
kurzeitiges Sperren während einer Transaktion mithilfe der lock()-Methode des EntityManagers
ergänzt werden.
10.5.2 Lockingprozesse in db4o: Semaphores
Wie wird die Methode lock() des optimistischen Sperren in db4o umgesetzt? Mit Semaphores. Das
Konzept der Semaphores in der Datenbank db4o entspricht dem Konzept der synchronisierten
Blöcke in Java. Es gibt allerdings einen wesentlichen Unterschied: Synchronisierte Blöcke, regeln
nur den Zugriff auf Bereiche innerhalb einer Klasse, wohingegen Semaphores den Zugriff auf
bestimmte Bereiche der gesamten Datenbank regeln. Ein Lock existiert nur einmal pro Klasse
und ein Semaphore existiert nur einmal pro Datenbank.
Sie haben mit Semaphores die Möglichkeit verschiedene Bereiche zu locken. Lassen Sie uns dies
jetzt näher betrachten. Ich möchte Sie für die Problematik sensibilisieren, aber keine Patentlösungen anbieten, da diese immer von verschiedenen Größen abhängt, wie z.B. der Anzahl der
Personen, die auf einzelnen Bereiche Zugriff haben, dem Aufbau Ihrer Datenbank oder Ihrer
Anwendung.
Locken von Bereichen
Ein Semaphore kann ein beliebiger String sein, der am Anfang des Blockes mit der Methode
setSemaphore() gesetzt wird. Der zweite Parameter, der der Methode übergeben wird, ist eine
Zeitangabe in Millisekunden. Am Ende des Blockes wird die Semaphore mit releaseSemaphore()
und dem Namen der Semaphore wieder entfernt.
Betrachten wir unser erstes Beispiel: Wir erstellen zwei Threads und locken den Bereich, in dem
Links geändert werden. Dies ist der erste Thread, der den Link mit Namen "Home" ändert:
1
2
3
4
5
6
7
8
package Kap10;
import
import
import
import
Datenbankentwurf.Link;
com.db4o.Db4o;
com.db4o.ObjectContainer;
com.db4o.ObjectSet;
public class ClientSynchronized extends Thread{
251
10 Transaktionen
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
ObjectContainer client = null;
public void run() {
try{
client = Db4o.openClient
("localhost", 8080, "user1", "password");
/*Es wird ein Semaphore definiert.*/
if(client.ext().setSemaphore("Eins",1000)){
Link l = new Link("Home");
ObjectSet<Link> result = client.get(l);
l = result.next();
l.setName("Alt");
client.set(l);
/*Der Semaphore wird wieder entfernt.*/
client.ext().releaseSemaphore("Eins");
}
} catch(Exception e){
e.printStackTrace();
} finally{
client.close();
}
}
}
Listing 10.10: ClientSynchronized.java
und dies ist der Thread, der den Link mit Namen "Bücher" ändert:
package Kap10;
1
2
3
4
5
6
7
8
9
10
11
12
13
import
import
import
import
Datenbankentwurf.Link;
com.db4o.Db4o;
com.db4o.ObjectContainer;
com.db4o.ObjectSet;
public class ClientSynchronized2 extends Thread{
ObjectContainer client = null;
public void run() {
252
10.5 Lockingprozesse
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
try{
client = Db4o.openClient
("localhost", 8080, "user1", "password");
if(client.ext().setSemaphore("Eins",1000)){
Link l = new Link("Bücher");
ObjectSet<Link> result = client.get(l);
l = result.next();
l.setName("Neu");
client.set(l);
client.ext().releaseSemaphore("Eins");
}
} catch(Exception e){
e.printStackTrace();
} finally{
client.close();
}
}
}
Listing 10.11: ClientSynchronized2.java
Auf diese Art wird nicht ein bestimmtes Objekt gelockt, sondern ein bestimmter Bereich. Nehmen wir einmal an, Sie haben tausend Objekte der Klasse Link und 100 Personen wollen gleichzeitig Links verändern, so könnte es zu Wartezeiten bis zu 100 Sekunden kommen ((1 Sekunde
x 100 Personen )/ 60 ), was so ca. 1,6 Minuten entspricht. Diese Zeit würde bei 1000 Personen
sogar auf (( 1 Sekunde x 1000 Personen )/ 60) ca. 16 Minuten anwachsen.
Locken von Objekten
Dies entspricht nicht unseren Wünschen. Was ist die Lösung? Sie locken nicht ganze Bereiche für
alle Objekte der gleichen Klasse, sondern Sie locken einen Bereich für ein bestimmtes Objekt.
Wie können wir dies tun? Wir koppeln den Namen des Semaphores an den OID, den Unique
Object Identifier, der ein Objekt eindeutig identifiziert.
Für jedes Objekt wird in der Datenbank eine Nummer vergeben, die eindeutig ist. Unten stehende Klasse LockManager habe ich dem db4o-Tutorial entnommen, das Sie mit der Datenbank
heruntergeladen haben und das sich im Verzeichnis tutorial befindet. Es wird also der OID ausgelesen und dieser wird dem Namen der Semaphore hinzugefügt. So erstellen Sie für jedes Objekt
ein separaten Semaphore und es wird ein bestimmtes Objekt gelockt.
1
2
3
4
package Kap10;
import com.db4o.ObjectContainer;
import com.db4o.ext.ExtObjectContainer;
253
10 Transaktionen
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class LockManager {
private final String SEMAPHORE_NAME = "locked: ";
private final int WAIT_FOR_AVAILABILITY = 300;
private final ExtObjectContainer _objectContainer;
public LockManager(ObjectContainer objectContainer){
_objectContainer = objectContainer.ext();
}
public boolean lock(Object obj){
/*Wir lesen den OID des Objektes aus.*/
long id = _objectContainer.getID(obj);
/*Wir setzen den Semaphore.*/
return _objectContainer.setSemaphore
(SEMAPHORE_NAME + id, WAIT_FOR_AVAILABILITY);
}
public void unlock(Object obj){
/*Wir lesen den OID des Objektes aus.*/
long id = _objectContainer.getID(obj);
/*Wir entfernen den Semaphore wieder.*/
_objectContainer.releaseSemaphore
(SEMAPHORE_NAME + id);
}
}
Listing 10.12: LockManager.java
Wir erstellen wieder einen Client in einem Thread und wir wenden die Methoden der Klasse
LockManager an. Diesen Thread nennen wir SynchronizeObject.java:
package Kap10;
1
2
3
4
5
6
7
8
import
import
import
import
Datenbankentwurf.Link;
com.db4o.Db4o;
com.db4o.ObjectContainer;
com.db4o.ObjectSet;
public class SynchronizeObject extends Thread{
254
10.5 Lockingprozesse
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
public static void main(String[] args){
SynchronizeObject s = new SynchronizeObject();
s.start();
}
ObjectContainer client = null;
public void run() {
try{
client = Db4o.openClient
("localhost", 8080, "user1", "password");
/*Wir instanziieren ein Objekt der Klasse LockManager und
übergeben ihm als Parameter den ObjectContainer client.*/
LockManager lockManager = new LockManager(client);
/*Wir lesen den Link mit Namen "Home" aus der Datenbank aus.*/
Link l = new Link("Home");
ObjectSet<Link> result = client.get(l);
l = result.next();
/*Wir übergeben den ausgelesenen Link der Methode lock(), die
ein Semaphore mit dem OID des ausgelesenen Links setzt.*/
if(lockManager.lock(l)){
l.setName("Alt");
client.set(l);
/*Der Semaphore wird mit der Methode unlock()wieder entfernt.*/
lockManager.unlock(l);
}
} catch(Exception e){
e.printStackTrace();
} finally{
client.close();
}
}
}
Listing 10.13: SynchronizeObject.java
Jetzt wird jeweils nur ein bestimmtes Objekt gelockt und nur dieses wird gelockt. Auf alle anderen
Objekte der Klasse Link kann jederzeit zugegriffen werden, da sie weiterhin frei zugänglich sind.
255
10 Transaktionen
Sollte noch jemand anders genau dieses Objekt verändern wollen, muss er nicht allzu lange
warten. Und es ist sehr unwahrscheinlich, dass mehr als 2 oder 3 Personen auf das gleiche Objekt
warten. Und selbst, wenn es sich bei der Wartezeit um einige Sekunden handelt, wird der Benutzer
dies nicht bemerken.
10.5.3 Optimistisches Sperren in Hibernate
Wie wird optimistisches Sperren in Hibernate realisiert? Die JPA-Spezifikation sieht einen Versionsvergleich vor, der durch ein Sperren mit der Methode lock() und den dazugehörigen Einstellungen des LockMode ergänzt werden kann. Die Methode lock() sperrt den entsprechenden
Datensatz für die Dauer einer einzigen Datenbanktransaktion. Und die Version wird bei jedem
Update um 1 erhöht. Wir werden im folgenden die Versionskontrolle mit der Methode lock()
kombinieren.
Beginnen wir mit dem Versionselement: Sie fügen Ihrer Klasse ein Element Version hinzu:
@Version
private Integer version ;
Das Element Version braucht in der Datenbank eine Entsprechung: eine Versionsspalte:
v e r s i o n INT not n u l l
Die geänderte Tabelle WebSite in unserer Datenbank sieht dann wie folgt aus:
CREATE TABLE WebSite ( w e b S i t e I d INT AUTO_INCREMENT,
v e r s i o n INT not n u l l , l i n k I d INT , r e i h e n f o l g e VARCHAR( 5 ) ,
ebene VARCHAR( 5 ) , FOREIGN KEY( l i n k I d )REFERENCES Link ( v e r l i n k u n g I d ) ,
PRIMARY KEY( w e b S i t e I d ) ) ;
Nachdem wir die Klasse WebSite ergänzt haben, erhalten wir unten stehendes Listing:
package Datenbankentwurf;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import
import
import
import
import
import
import
import
import
import
import
import
java.io.Serializable;
java.util.ArrayList;
javax.persistence.CascadeType;
javax.persistence.Entity;
javax.persistence.GeneratedValue;
javax.persistence.GenerationType;
javax.persistence.Id;
javax.persistence.JoinColumn;
javax.persistence.JoinTable;
javax.persistence.OneToMany;
javax.persistence.OneToOne;
javax.persistence.Version;
@Entity
public class WebSite implements Serializable {
@Id
256
10.5 Lockingprozesse
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer webSiteId;
@Version
private Integer version;
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name="linkId")
private Link link;
@OneToMany(cascade = CascadeType.ALL)
@JoinTable(name = "WebSiteTexts",
joinColumns = {@JoinColumn(name = "webSiteId")},
inverseJoinColumns={@JoinColumn(name = "textId")}
)
private ArrayList<Text> text = new ArrayList<Text>();
@OneToMany(cascade = CascadeType.ALL)
@JoinTable(name = "WebSiteBilder",
joinColumns = {@JoinColumn(name = "webSiteId")},
inverseJoinColumns={@JoinColumn(name = "bildId")}
)
private ArrayList<Bild> bild = new ArrayList<Bild>();
@OneToMany(cascade = CascadeType.ALL)
@JoinTable(name = "WebSitePdfs",
joinColumns = {@JoinColumn(name = "webSiteId")},
inverseJoinColumns={@JoinColumn(name = "verlinkungId")}
)
private ArrayList<PDF> pdf = new ArrayList<PDF>();
private String reihenfolge;
private String ebene;
public WebSite() {
}
public WebSite(Link link, ArrayList<Text> text,
ArrayList<Bild> bild, ArrayList<PDF> pdf,
String reihenfolge, String ebene) {
this.link = link;
this.text = text;
this.bild = bild;
this.pdf = pdf;
257
10 Transaktionen
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
258
this.reihenfolge = reihenfolge;
this.ebene = ebene;
}
public Link getLink() {
return link;
}
public void setLink(Link link) {
this.link = link;
}
public String getReihenfolge() {
return reihenfolge;
}
public void setReihenfolge(String reihenfolge) {
this.reihenfolge = reihenfolge;
}
public String getEbene() {
return ebene;
}
public void setEbene(String ebene) {
this.ebene = ebene;
}
public ArrayList<Text> getText() {
return text;
}
public void setText(ArrayList<Text> text) {
this.text = text;
}
public ArrayList<Bild> getBild() {
return bild;
}
public void setBild(ArrayList<Bild> bild) {
this.bild = bild;
}
public ArrayList<PDF> getPdf() {
return pdf;
}
10.5 Lockingprozesse
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
public void setPdf(ArrayList<PDF> pdf) {
this.pdf = pdf;
}
public Integer getWebSiteId() {
return webSiteId;
}
public void setWebSiteId(Integer webSiteId) {
this.webSiteId = webSiteId;
}
public Integer getVersion() {
return version;
}
public void setVersion(Integer version) {
this.version = version;
}
}
Listing 10.14: WebSite.java
Sehen wir uns das optimistische Sperren anhand des Beispiels Update einer Website an: Die Methode lock() und die Versionskontrolle gelten jeweils nur während der Dauer einer Transaktion.
So ergänzen wir die automatische Versionskontrolle um eine manuelle, die es uns erlaubt die
Versionen von 2 Transaktionen miteinander zu vergleichen. Sowohl bei der manuellen als auch
der automatischen Versionskontrolle wird eine StaleObjectStateException geworfen. Wobei Sie
bei der Manuellen der Exception zwei Parameter übergeben müssen: die Klasse als String und
den Primärschlüssel. Wir unterziehen nur das Objekt der Klasse WebSite einer Versionskontrolle, da das Objekt der Klasse Formatierung jederzeit auch durch andere User geändert werden
kann, die ein Objekt der Klasse WebSite erstellen, ändern oder löschen. Wir sperren aber die
Formatierungen, solange ihnen Texte hinzugefügt werden.
Folgende Besonderheiten sind zu beachten, die in dem Update der Klasse WebSiteUpdateHibernate nicht in direktem Zusammenhang zu den Sperrvorgängen stehen: Erstens: Sie müssen
zuerst die alten Texte, Bilder, PDFs und den Link löschen bevor Sie die geänderten hinzufügen
können. Zweitens: Wollen Sie den Texten Formatierungen hinzufügen, tun Sie dies, indem Sie der
ArrayList<Text> des entsprechenden Formatierungsobjektes ein Element hinzufügen. Wohingen
es bei dem Löschvorgang des Textes genügt, nur den Text zu löschen, aus der ArrayList<Text>
wird er automatisch gelöscht.
1
2
3
4
5
6
7
8
package Kap10;
import
import
import
import
import
import
Datenbankentwurf.Bild;
Datenbankentwurf.Formatierung;
Datenbankentwurf.Link;
Datenbankentwurf.PDF;
Datenbankentwurf.Text;
Datenbankentwurf.WebSite;
259
10 Transaktionen
import
import
import
import
import
import
import
import
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
java.util.ArrayList;
java.util.List;
org.hibernate.LockMode;
org.hibernate.Session;
org.hibernate.SessionFactory;
org.hibernate.StaleObjectStateException;
org.hibernate.Transaction;
util.HibernateUtil;
public class WebSiteUpdateHibernate {
public void updateWebSite(WebSite we, Link link, Link linkNeu,
List<Text> text, List<Text> textAlt,
List<Bild> bildNeu, List<Bild> bildAlt,
List<PDF> pdfNeu, List<PDF> pdfAlt,
String reihenfolgeNeu, String ebeneNeu) {
Session session = null;
SessionFactory sessionFactory = HibernateUtil.getSessionFactory();
session = sessionFactory.openSession();
try {
Transaction tx = session.beginTransaction();
List<Text> arrayListText = new ArrayList<Text>();
Formatierung form = null;
Text t = null;
/*Wir lesen die urspruengliche Version der WebSite aus, die in unserem
Content-Management-System im ersten Aendern-Formular ausgelesen wurde.*/
Integer oldVersion = we.getVersion();
Integer webSiteKeys = we.getWebSiteId();
WebSite w = new WebSite();
w = (WebSite) session.createQuery
("Select w from WebSite w where w.webSiteId =:key").
setInteger("key", webSiteKeys).uniqueResult();
/*Wir sperren das Element WebSite. */
session.lock(w, LockMode.UPGRADE);
Integer primaryKey = w.getWebSiteId();
Integer newVersion = w.getVersion();
/*Sollte die urspruengliche Version nicht mit der soeben ausgelesenen
Version, die im letzten Formular der Methode uebergeben wurde,
uebereinstimmen, soll eine Exception geworfen werden.*/
if(!oldVersion.equals(newVersion))
{ throw new StaleObjectStateException("WebSite", primaryKey);}
else
260
10.5 Lockingprozesse
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
{System.out.println("Sie sind gleich");}
w.setLink(linkNeu);
w.setText(arrayListText);
w.setBild(bildNeu);
w.setPdf(pdfNeu);
w.setReihenfolge(reihenfolgeNeu);
w.setEbene(ebeneNeu);
session.saveOrUpdate(w);
for (Text tex : text) {
String textName = tex.getName();
Integer formatierungKey = tex.getFormatierung().getFormatierungId();
form = (Formatierung) session.createQuery
("Select f from Formatierung f where f.formatierungId = :key").
setInteger("key", formatierungKey).uniqueResult();
/*Wir sperren das Element form.*/
session.lock(form, LockMode.UPGRADE);
t = new Text(textName);
form.getText().add(t);
t.setFormatierung(form);
arrayListText.add(t);
session.save(t);
}
session.saveOrUpdate(form);
String linkNameAlt = link.getName();
Integer linkKey = link.getVerlinkungId();
link = (Link)session.createQuery
("Select v from Verlinkung v where v.verlinkungId = :key").
setInteger("key", linkKey).uniqueResult();
session.delete(link);
for (Text te : textAlt) {
String textName = te.getName();
Integer textKey = te.getTextId();
te = (Text) session.createQuery
("Select t from Text t where t.textId = :key").
setInteger("key", textKey).uniqueResult();
session.delete(te);
}
for (Bild bi : bildAlt) {
String bildName = bi.getName();
Integer bildKey = bi.getBildId();
bi = (Bild)session.createQuery
261
10 Transaktionen
("Select b from Bild b where b.bildId =:key").
setInteger("key", bildKey).uniqueResult();
session.delete(bi);
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
}
for (PDF p : pdfAlt) {
String pdfName = p.getName();
Integer pdfKey = p.getVerlinkungId();
p = (PDF)session.createQuery
("Select v from Verlinkung v where v.verlinkungId =:key").
setInteger("key", pdfKey).uniqueResult();
session.delete(p);
}
tx.commit();
} catch (RuntimeException e) {
e.printStackTrace();
} finally {
session.close();
}
}
}
Listing 10.15: WebSiteUpdateHibernate.java
Der Methode lock() müssen Sie als Parameter den LockMode übergeben. Es lassen sich u.a.
folgende drei wichtige Modi unterscheiden:
1. LockMode.UPGRADE: Der LockMode.UPGRADE greift direkt auf die entsprechende
Zeile in der Datenbank zu, und sperrt diese pessimistisch, falls dies die Datenbank zulässt.
Ansonsten greift Hibernate auf den LockMode.READ zurück.
2. LockMode.READ: Hibernate greift auf die Versionskontrolle zurück und überprüft die
Daten in der Datenbank.
3. LockMode.NONE: Hibernate holt die Objekte aus dem Cache und nicht aus der Datenbank, es sei denn das Objekt befindet sich nicht im Cache.
10.5.4 Lockingprozesse in Projekten und Performance
Wie können Lockingprozesse in größeren Projekten optimiert werden? Die folgenden Überlegungen sollen Ihnen Anregungen geben, wie die Antwortzeiten in einem Internet-Projekt reduziert
werden können.
Sollten Sie mehrere Objekte locken wollen, wird es kompliziert. Nehmen wir das Beispiel Löschen
einer WebSite, das wir bei den Transaktionen verwendet haben. Hier sind zwei Objekte gleichzeitig betroffen, nämlich ein Objekt der Klasse Formatierung und ein Objekt der Klasse WebSite.
262
10.5 Lockingprozesse
Die Wahrscheinlichkeit, dass zwei Personen die gleiche Webseite gleichzeitig löschen oder verändern wollen, ist sehr unwahrscheinlich. Problematischer könnte es in folgendem Fall werden: Wenn
es viele Personen gibt, die ein Objekt der Klasse WebSite zur gleichen Zeit entweder anlegen,
löschen oder ändern wollen und somit auch gleichzeitig auf ein und dasselbe Formatierungsobjekt zugreifen. Diese Situation könnte entzerrt werden, indem beim Ändern und Anlegen, der
Vorgang in seine Einzelteile zerlegt wird: Das Hinzufügen oder Ändern von Text, Bildern und
PDFs wird jeweils in einer separaten Methode und in einem separaten Formular durchgeführt.
So muss nicht beim Hinzufügen eines Bildes zu einer Website auch auf das Formatierungsobjekt
des Textes zugegriffen werden.
Ein anderer Lösungsansatz wäre das Denormalisieren unserer Daten, d. h. das teilweise rückgängig machen des Normalisierungsvorgangs. So wäre es z.B. denkbar, ein Stringobjekt Formatierung
in der Klasse Text anzulegen und mit jedem Speichern eines Textes auch das Stringobjekt Formatierung mit zu speichern. Dies hätte zwar auf der einen Seite zur Folge, dass z.B. der Namen
der Formatierung "font1" , mehrmals in der Datenbank vorkommen würde, aber auf der anderen
Seite würde es die Zugriffsgeschwindigkeiten erheblich beschleunigen. Sollten Sie dies tun, müsste
allerdings gewährleistet sein, dass die Namen der Formatierungen korrekt geschrieben werden.
Dies wäre ohne Probleme mithilfe eines Auswahlfeldes in einem Formular möglich.
Diese Vorgehensweise ist bei Rechnungen, die Artikel beinhalten, nicht zu empfehlen, da es auf
gar keinen Fall sinnvoll ist, mehrmals den gleichen Artikel in der Datenbank zu haben. Da es auf
keinen Fall machbar ist, alle Einzeldaten auf dem gleichen Stand zu halten und es außerdem auch
beim Auswerten der Daten, z.B. für Umsatzzahlen, große Probleme geben würde. Hier wäre es
vernünftig, die Datenbank nach Artikelgruppen zu teilen. Hierbei muss darauf geachtet werden,
dass alle Daten, die in einer Rechnung vorkommen, sich auch in der gleichen Datenbank befinden.
So ist es nicht möglich, dass eine Rechnung, Artikel aus zwei Datenbanken enthält.
Hier an dieser Stelle noch einige Bemerkungen zum Arbeitsspeicher von db4o, den ich im Kapitel
„Embedded Modus in einem Web-Projekt“ näher beschreiben werde. Sollte wie im Falle unseres
Content-Management-Systems die Zugriffe durch Benutzer relativ hoch sein, aber der Inhalt
relativ selten verändert werden, kann der interne Arbeitsspeicher von db4o dazu benutzt werden,
die Antwortzeiten zu beschleunigen.
263
11 Abfragen in Hibernate
11.1 Hibernate Query Language (HQL)
In Hibernate gibt es Hibernate Query Language, die der Abfragesprache SQL sehr ähnlich ist,
aber den Abfragen ein objektorientierte Perspektive verleiht.
11.1.1 Wichtige Methoden
Das Interface Query
Das Interface Query befindet sich im Package org.hibernate und enthält Methoden, die Sie für
die Formulierung von Abfragen brauchen. Die wichtigsten sind:
1. list(): Wenn Sie mehrere zutreffende Ergebnisse suchen, so gibt diese Methode als Ergebnis
eine Liste zurück.
2. uniqueResult(): Wenn Sie genau eine Entsprechung suchen, erhalten Sie mit dieser
Methode genau ein Element als Ergebnis.
Das Interface Session
Das Interface Session befindet sich ebenfalls im Package org.hibernate und es enthält neben vielen
Methoden die Methode createQuery(), die eine Abfrage erstellt. Als Parameter wird der Methode
ein HQL-Select-Befehl übergeben:
Query query = c r e a t e Q u e r y ( S e l e c t f from Fo rmat ierung ) ;
11.1.2 Verwendung von Aliasen
Was sind Aliase? Aliase sind Abkürzungen für Tabellennamen in Abfragen. Wollen Sie einen
Tabellennamen abkürzen, können Sie einen Alias verwenden. Im folgenden verwenden wir den
Alias f für die Tabelle Formatierung und schreiben Formatierung as f:
S e l e c t f from Formatierung a s f ;
Da das Keyword as optional ist, können Sie as weglassen und nur folgendes schreiben:
S e l e c t f from Formatierung f ;
265
11 Abfragen in Hibernate
11.1.3 Parameter-Binding
Suchen Sie eine bestimmte Formatierung? Mit einem bestimmten Namen? So brauchen Sie die
Methoden setString() des Interface Query, mit der Sie einen bestimmten Paramater vom Typ
String festlegen können. Können Sie auch nach anderen Typen suchen? Ja! Der Methode setInteger() können Sie ein Objekt vom Typ Integer als Parameter übergeben oder der Methode
setDouble() ein Parameter vom Typ Double.
Mit der folgenden Methode setString() wird der Wert des Parameters name festgelegt, der den
Wert aus der Variablen formatierungName übernimmt. Es wird dann in der Datenbank nach
einer Entsprechung zu dem Wert name gesucht. Die Methode setString() bindet den Query also
an den Parameter.
s e t S t r i n g ( " name " , formatierungName )
Der Methode createQuery() wird der folgende HQL-Select-Befehl übergeben:
S e l e c t f from Formatierung f where f . name = : name
Es wird also in der Tabelle Formatierung in der Spalte f.name nach dem Parameter name gesucht.
Diese HQL-Abfrage benötigen wir, um die Formatierung auszulesen, die dem Text zugewiesen
werden soll, der dann anschließend der Website hinzugefügt wird. Diese Abfragen gehören logisch
zusammen und müssen somit auch innerhalb einer Transaktion durchgeführt werden.
package Kap11;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import
import
import
import
import
import
import
import
import
Datenbankentwurf.Formatierung;
Datenbankentwurf.Text;
Datenbankentwurf.WebSite;
java.util.ArrayList;
java.util.List;
org.hibernate.Session;
org.hibernate.SessionFactory;
org.hibernate.Transaction;
util.HibernateUtil;
public class WebSiteSpeichernHibernateCascade {
public void addWebSite(WebSite w) {
Session session = null;
SessionFactory sessionFactory = HibernateUtil.getSessionFactory();
session = sessionFactory.openSession();
try {
Transaction tx = session.beginTransaction();
List<Text> arrayListText = new ArrayList<Text>();
List<Text> text = w.getText();
Formatierung form = null;
266
11.1 Hibernate Query Language (HQL)
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
Text t = null;
for (Text tex : text) {
String textName = tex.getName();
String formatierungName = tex.getFormatierung().getName();
/*Sie muessen zuerst die Formatierung aus der Datenbank
mit der Methode createQuery() und einem Select-Befehl auslesen,
sprich vom transienten in den persistenten Zustand bringen, dann
koennen Sie der Formatierung Texte hinzufuegen.*/
form = (Formatierung) session.createQuery
("Select f from Formatierung f where f.name = :name").
setString("name", formatierungName).uniqueResult();
t = new Text(textName);
form.getText().add(t);
t.setFormatierung(form);
arrayListText.add(t);
session.save(t);
}
/*Jetzt koennen Sie die Formatierung speichern.*/
session.saveOrUpdate(form);
/*Den geaenderten Text muessen Sie auch der WebSite hinzufuegen.*/
w.setText(arrayListText);
/*Und dann anschliessend muessen Sie die WebSite speichern.*/
session.saveOrUpdate(w);
tx.commit();
} catch (RuntimeException e) {
e.printStackTrace();
} finally {
if (session != null) {
session.close();
}
}
}
}
Listing 11.1: WebSiteSpeichernHibernateCascade.java
11.1.4 Exkurs: Kaskadierende Beziehungen
Da wir für alle 1:n-Beziehungen in der Klasse WebSite
267
11 Abfragen in Hibernate
@OneToMany( c a s c a d e = CascadeType . ALL)
festgelegt haben, müssen wir nicht jedes Bild, jeden Text und jedes PDF einzeln speichern. Es
werden automatisch alle Elemente einer ArrayList mitgespeichert, die sich innerhalb der Klasse
WebSite befindet.
Für alle 1:1-, 1:n- und n:m-Beziehungen lassen sich unten stehende kaskadierende Optionen (CascadeType) festlegen. Die kaskadierende Optionen legen fest, ob und inwieweit Abfragen an die
Beziehungen durchgereicht werden.
Folgende Abfragen werden bei den verschiedenen kaskadierenden Optionen (CascadeType) an
die Beziehungen weitergereicht:
Tabelle 11.1: Übersicht über die CascadeTypes
CascadeType
Abfrage, die weitergegeben wird
PERSIST
Die Methode persist()
MERGE
Die Methode merge()
REMOVE
Die Methode remove()
REFRESH
Die Methode refresh()
ALL
CascadeType.All fasst folgende
Optionen zu einer zusammen: PERSIST,
MERGE, REMOVE und REFRESH
Was würde passieren, wenn wir den CascadeType.All nicht festgelegt hätten? So müßte jedes
Objekt separat in der Datenbank gespeichert werden und Objekte, die sich innerhalb einer ArrayList befinden, wie z.B. Bilder, müssten vor der Website gespeichert werden. Erst dann könnte
das ganze Objekt WebSite gespeichert werden. Und um im Hibernatejargon zu bleiben: Bilder
müssten vom transienten Zustand in den persistenten Zustand überführt werden. Sollten Sie also
eine entsprechende Fehlermeldung erhalten, hätten Sie vergessen, Objekte persistent zu machen.
11.1.5 Ausdrücke und Operatoren in HQL: Bedingungen formulieren
Wie lesen wir alle Elemente der oberen Menüleiste aus? Indem wir alle Objekte der Klasse WebSite herausfiltern, für die die Bedingung zutrifft, dass die Ebene der WebSite leer ist, wobei wir
den Operator = verwenden können. Leere Elemente werden durch zwei einfache Anführungszeichen gekennzeichnet. Bedinungen werden durch die Klausel where eingeleitet, die Ihnen vielleicht
aus SQL bekannt ist:
S e l e c t w . l i n k form WebSite w where w . ebene = ’ ’
Anschließend übergeben wir der Methode createQuery() den select-Befehl:
c r e a t e Q u e r y ( " S e l e c t w . l i n k from WebSite
w where w . ebene = ’ ’ " )
Die Abfrage wird dann in die folgende Abfrage integriert:
268
11.1 Hibernate Query Language (HQL)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package Kap11;
import
import
import
import
import
Datenbankentwurf.Link;
java.util.ArrayList;
org.hibernate.Session;
org.hibernate.Transaction;
util.HibernateUtil;
public class AuslesenEbeneHibernate {
/*Liest alle Links aus, bei denen die Ebene leer ist.*/
public ArrayList<Link> auslesenLinkErste() {
ArrayList<Link> l = new ArrayList<Link>();
Session session = HibernateUtil.getSessionFactory().openSession();
try {
Transaction tx = session.beginTransaction();
/*Mit dem Operator = stellen wir fest, ob das Feld ebene leer ist.*/
l = (ArrayList<Link>) session.createQuery
("Select w.link from WebSite w where w.ebene = ’’ ").list();
tx.commit();
} catch (RuntimeException e) {
e.printStackTrace();
} finally {
session.close();
}
return l;
}
}
Listing 11.2: AuslesenEbeneHibernate.java
Bedingungen formulieren mit dem Ausdruck and
Was machen wir, wenn zwei Bedingungen zutreffen sollen? Wir verknüpfen sie mit dem Ausdruck " and". Lesen wir die Reihenfolge einer WebSite aus und soll sowohl die Ebene leer sein
als auch der Name eines Links mit einem vorgegebenen Namen übereinstimmen, werden beide
Bedingungen mit and verknüpft.
1
2
3
package Kap11;
import org.hibernate.Session;
269
11 Abfragen in Hibernate
import org.hibernate.Transaction;
import util.HibernateUtil;
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class AuslesenLinkReihenfolgeHibernate {
public String auslesenLinkReihenfolge(String linkName){
String s = new String();
Session session = HibernateUtil.getSessionFactory().openSession();
try {
Transaction tx = session.beginTransaction();
/*Es werden zwei Bedingungen mit and verknüpft.*/
s = (String) session.createQuery
("Select w.reihenfolge from WebSite w where w.ebene = ’’ " +
"and w.link.name = :name").
setString("name", linkName).uniqueResult();
tx.commit();
} catch (RuntimeException e) {
e.printStackTrace();
} finally {
session.close();
}
return s;
}
}
Listing 11.3: AuslesenLinkReihenfolgeHibernate.java
Lesen wir im nächsten Schritt mithilfe der Reihenfolge die linke Untermenüleiste aus, benötigen
wir alle Objekte der Klasse WebSite, deren Ebene nicht leer ist. In HQL lautet der Ausdruck
"!=".
package Kap11;
1
2
3
4
5
6
7
8
9
10
11
import
import
import
import
import
Datenbankentwurf.Link;
java.util.ArrayList;
org.hibernate.Session;
org.hibernate.Transaction;
util.HibernateUtil;
public class AuslesenLinkZweiteHibernate {
270
11.1 Hibernate Query Language (HQL)
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public ArrayList<Link> auslesenLinkZweite(String reihenfolge){
ArrayList<Link> link = new ArrayList<Link>();
Session session = HibernateUtil.getSessionFactory().openSession();
try {
Transaction tx = session.beginTransaction();
link = (ArrayList<Link>) session.createQuery
("Select w.link from WebSite w where w.ebene != ’’ " +
"and w.reihenfolge = :reihenfolge").
setString("reihenfolge", reihenfolge).list();
tx.commit();
} catch (RuntimeException e) {
e.printStackTrace();
} finally {
session.close();
}
return link;
}
}
Listing 11.4: AuslesenLinkZweiteHibernate.java
Sortieren mit dem Ausdruck order by
Wir sortieren die obere Menüleiste unseres Content-Management-Systems aufsteigend nach der
Reihenfolge mit dem Ausdruck order by und asc.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package Kap11;
import
import
import
import
import
Datenbankentwurf.Link;
java.util.ArrayList;
org.hibernate.Session;
org.hibernate.Transaction;
util.HibernateUtil;
public class EbeneHibernateSortieren {
public ArrayList<Link> auslesenLinkErste() {
ArrayList<Link> l = new ArrayList<Link>();
Session session = HibernateUtil.getSessionFactory().openSession();
271
11 Abfragen in Hibernate
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
try {
Transaction tx = session.beginTransaction();
l = (ArrayList<Link>) session.createQuery
("Select w.link from WebSite w where " +
"w.ebene = ’’ order by w.reihenfolge asc").list();
tx.commit();
} catch (RuntimeException e) {
e.printStackTrace();
} finally {
session.close();
}
return l;
}
}
Listing 11.5: EbeneHibernateSortieren.java
Wie können wir die obere Menüleiste absteigend sortieren? Absteigend sortieren wir sie mit dem
Ausdruck order by und desc.
package Kap11;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import
import
import
import
import
Datenbankentwurf.Link;
java.util.ArrayList;
org.hibernate.Session;
org.hibernate.Transaction;
util.HibernateUtil;
public class EbeneHibernateSortierenAbsteigend {
public ArrayList<Link> auslesenLinkErste() {
ArrayList<Link> l = new ArrayList<Link>();
Session session = HibernateUtil.getSessionFactory().openSession();
try {
Transaction tx = session.beginTransaction();
l = (ArrayList<Link>) session.createQuery
("Select w.link from WebSite w where " +
"w.ebene = ’’ order by w.reihenfolge desc").list();
272
11.1 Hibernate Query Language (HQL)
25
26
27
28
29
30
31
32
33
34
tx.commit();
} catch (RuntimeException e) {
e.printStackTrace();
} finally {
session.close();
}
return l;
}
}
Listing 11.6: EbeneHibernateSortierenAbsteigend.java
Der Ausdruck join
Wie können Sie mithilfe von HQL nach Kriterien suchen, die sich innerhalb einer ArrayList
eines Objektes befinden? Oder: Wie können wir feststellen, zu welcher WebSite ein bestimmtes
Bild gehört? Wir benötigen den Ausdruck join. Sie übergeben unten stehender Methode den
Namen des Bildes, zu dem Sie die entsprechende WebSite finden wollen. Also: Immer, wenn Sie
nach Objekten mit Kriterien suchen, die sich innerhalb einer ArrayList einer Klasse befinden,
benötigen Sie den Ausdruck join. Sie sagen zu Hibernate, stelle mir eine Verbindung zwischen
der WebSite und der ArrayList mit den Bildern her bzw. stelle mir eine Verbindung zwischen
den jeweiligen Tabellen in der Datenbank her:
WebSite w j o i n w . b i l d
Die komplette Abfrage sieht dann wie folgt aus:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package Kap11;
import
import
import
import
import
import
import
import
import
Datenbankentwurf.Bild;
Datenbankentwurf.PDF;
Datenbankentwurf.Text;
Datenbankentwurf.WebSite;
java.util.List;
org.hibernate.Hibernate;
org.hibernate.Session;
org.hibernate.Transaction;
util.HibernateUtil;
public class BildAuslesenHibernate {
public WebSite auslesenLinkBild(String bildName) {
WebSite w = new WebSite();
Session session = HibernateUtil.getSessionFactory().openSession();
try {
273
11 Abfragen in Hibernate
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
Transaction tx = session.beginTransaction();
/*Wir lesen ein Objekt der Klasse WebSite aus, das ein Bild
mit einem bestimmten Namen enthält. */
w = (WebSite) session.createQuery
("Select w from WebSite w join w.bild b where " +
"b.name =:name").setString("name", bildName).uniqueResult();
List<Text> text = w.getText();
Hibernate.initialize(text);
List<Bild> bild = w.getBild();
Hibernate.initialize(bild);
List<PDF> pdf = w.getPdf();
Hibernate.initialize(pdf);
tx.commit();
} catch (RuntimeException e) {
e.printStackTrace();
} finally {
session.close();
}
return w;
}
}
Listing 11.7: BildAuslesenHibernate.java
Diese Art join wird auch inner join genannt. Der Begriff inner join entstammt wie der Begriff
left und right outer join der Welt der relationalen Datenbanken. Wodurch unterscheidet sich ein
outer von einem inner join? Und: Wodurch unterscheidet sich ein right outer on einem left outer
join? Nehmen wir folgendes Beispiel an: Wir lesen alle Objekte der Klasse WebSite mit den darin
enthaltenen Bilder aus, wobei es WebSites gibt, die keine Bilder enthalten. Bei einem inner join
werden alle WebSites ausgegeben, die Bilder enthalten. Führen Sie einen left outer join durch,
werden alle WebSites ausgegeben, auch die, die keine Bilder enthalten. Wohingegen bei einem
right outer join alle Bilder ausgelesen werden, auch die, die keinem Objekt WebSite zugewiesen
sind. Wobei es in unserem Fall keine Bilder ohne WebSite geben dürfte.
Operatoren, Ausdrücke und Methoden in Abfragen
Soeben haben wir Operatoren und Ausdrücke in der Klausel where kennengelernt. Welche weiteren Operatoren stehen uns in Hibernate zur Verfügung? Hier eine Aufstellung der wichtigsten
Operatoren:
1. Logische Operatoren: and, or, not
274
11.2 Named Queries
2. Vergleichsoperatoren: =, >=, <=, <>, !=, like
3. Mathematische Operatoren: +, -, *, /
Und eine Aufstellung der wichtigsten Ausdrücke und Methoden:
1. count(): Die Methode count() addiert z. B. alle Formatierungen auf: select count(f) from
Formatierung f
2. min(): Die Methode min() findet das Minimum.
3. max(): Die Methode max() findet das Maximum.
4. sum(): Die Methode sum() berechnet die Summe aller ausgewählten Felder.
5. avg(): Die Methode avg() berechnet den Durchschnitt aller Felder.
6. group by: Mit group by werden die Ergebnisse einer Abfrage nach bestimmten Kriterien
gruppiert.
7. having: Bei Gruppierungen mit group by wird in Abfragen die Klausel where durch having
ersetzt.
11.2 Named Queries
Named Queries ermöglicht es Ihnen Abfragen entweder in eine XML-Mapping-Datei oder in die
Entity Klasse auszulagern. Sie geben der Abfrage einen eindeutigen Namen und unter diesem
Namen kann innerhalb der Anwendung auf diese Abfrage zugegriffen. Der Name der Abfrage darf
innerhalb der Anwendung nur ein einziges Mal vorkommen. Welche Vorteile bringen uns Named
Queries? Es sind die folgenden:
1. Jede Abfrage wird nur ein einziges Mal erstellt und dann immer wieder verwendet.
2. Es wird die Wartbarkeit der Applikation erhöht, da die Abfragen nur noch an einer Stelle
verändert werden müssen.
11.2.1 Erstellen einer Named Query mit Annotations
Wie erstellen Sie Named Queries? Als Erstes müssen Sie die entsprechenden Packages importieren:
import j a v a x . p e r s i s t e n c e . NamedQueries ;
import j a v a x . p e r s i s t e n c e . NamedQuery ;
Dann können Sie eine vordefinierte Abfrage erstellen, die alle Formatierungen ausliest und der
wir den Namen alleFormatierungen geben.
275
11 Abfragen in Hibernate
@Entity
@NamedQueries ( {
@NamedQuery (
name=" a l l e F o r m a t i e r u n g e n " ,
query=" s e l e c t f from Formatierung f "
) // ,
// w e i t e r e Named Q u e r i e s
})
Die Annotation für @NamedQueries muss direkt hinter der Annotation @Entity stehen. Mit der
Annotation @NamedQueries können Sie mehrere Named Queries erstellen und mit der Annotation @NamedQuery nur eine einzige.
11.2.2 Zugreifen auf eine NamedQuery
Wie können Sie auf eine NamedQuery zugreifen? Mit der Methode getNamedQuery(), der wir
als Parameter den Namen der Named Query übergeben.
s e s s i o n . getNamedQuery ( " a l l e F o r m a t i e r u n g e n " ) . l i s t ( ) ;
11.3 Criteria Queries
11.4 Query by example
276
12 Abfragen in Grails
277
13 LazyLoading in Hibernate und Aktivierungstiefe
in db4o
13.1 Aktivierungstiefe, Tiefe Objektgraphen und Lazy Loading
Tiefe Objektgraphen? Was stellen Sie sich darunter vor? Es ist halb so geheimnisvoll, wie es
klingt. Klassen besitzen has-a-Beziehungen, so hat z. B. unsere WebSite Bilder und diese könnten
theoretisch wieder ein Objekt enthalten und so weiter und so weiter............. Und so ist bereits
ein Tiefer Objektgraph entstanden. So einfach! Was versteht man unter Aktivierungstiefe? Es
sind Objekte einer bestimmten Ebene, die bei einer Abfrage in den Arbeitsspeicher von db4o
geladen werden. Warum sollen nur bestimmte Objekte aktiviert werden? Da dies ansonsten bei
großen Datenmengen sehr schnell zu Problemen mit dem Arbeitsspeicher führen würde.
Dies waren Begriffsdefinitionen aus db4o, aber dasgleiche Prinzip gilt in Hibernate. Enthalten
in Hibernate Objekte z.B. eine ArrayList, werden auch in Hibernate nicht automatisch die darinenthaltenen Elemente mit in den Arbeitsspeicher geladen. Dieses Phänomen nennt sich Lazy
Loading, wobei man lazy im deutschen mit faul oder träge übersetzen kann, somit bedeutet Lazy
Loading langsames oder träges Laden.
13.2 Aktivierungstiefe in db4o
Wie können die verschiedenen Ebenen unserer Objekte aktiviert werden? Mit der Methode activationDepth(), der Sie als Parameter eine Zahl übergeben.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package Kap12;
import
import
import
import
import
import
Datenbankentwurf.WebSite;
com.db4o.Db4o;
com.db4o.ObjectContainer;
com.db4o.ObjectSet;
com.db4o.DatabaseFileLockedException;
java.util.ArrayList;
public class WebSiteAuslesenActivation {
public ArrayList<WebSite> auslesen(){
ObjectContainer db = Db4o.openFile
("C:/Datenbank/DasErsteProjekt/datenbank.yap");
ArrayList<WebSite> f = new ArrayList<WebSite>();
try {
279
13 LazyLoading in Hibernate und Aktivierungstiefe in db4o
db.ext().configure().activationDepth(2);
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
WebSite w = new WebSite(null, null, null, null, null, null);
ObjectSet<WebSite> result = db.get(w);
while (result.hasNext()){
w = result.next();
f.add(w);
}
} catch (DatabaseFileLockedException e) {
e.printStackTrace();
} finally{
db.close();
}
return f;
}
}
Listing 13.1: WebSiteAuslesenActivation.java
Der Standardwert für die Aktivierungstiefe beträgt 5. Setzen wir die Aktivierungstiefe auf 0
wird eine NullPointerException geworfen. Setzen wir die Aktivierungstiefe auf 1 erhalten wir mit
der Klasse AuslesenActivation.java folgende Ausgabe, nachdem wir mit FormatierungOhneTextInDatenbank.java, WebSiteEinlesenInDatenbank.java und WebSiteEinlesenWeitere.java wieder
unsere Datenbank erstellt haben:
package Kap12;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import
import
import
import
import
Datenbankentwurf.Bild;
Datenbankentwurf.PDF;
Datenbankentwurf.Text;
Datenbankentwurf.WebSite;
java.util.List;
public class AuslesenActivation {
public static void main(String[] args){
WebSiteAuslesenActivation ws = new WebSiteAuslesenActivation();
List<WebSite> aw = ws.auslesen();
for(WebSite w: aw){
System.out.println("Link: "+ w.getLink().getName());
List<Text> al = w.getText();
for(Text t :al){
System.out.println("Text: " + t.getName());
280
13.2 Aktivierungstiefe in db4o
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
System.out.println("Formatierung zu Text: " + t.getFormatierung().getName());
}
List<Bild> ab = w.getBild();
for(Bild b :ab){
System.out.println("Bild: " + b.getName());
}
List<PDF> ap = w.getPdf();
for(PDF p:ap){
System.out.println("PDF: " + p.getName());
}
System.out.println("Reihenfolge "+ w.getReihenfolge());
System.out.println("Ebene "+ w.getEbene());
}
}
}
Listing 13.2: AuslesenActivation.java
Ausgabe:
Link: null
Reihenfolge
Ebene 2
Link: null
Reihenfolge
Ebene
Link: null
Reihenfolge
Ebene 1
Link: null
Reihenfolge
Ebene
2
1
2
2
Grundsätzlich werden bei der Aktivierungstiefe 1 das Objekt selbst, die darin enthaltenen Strings,
primitiven Elemente und Objekte aktiviert. In der oberen Ausgabe sehen Sie die Strings Reihenfolge und Ebene, die mit ihren Werten ausgegeben werden. Das darin enthaltene Objekt Link
wird mit dem Wert null aktiviert. So werden in unserem Beispiel alle Stringobjekte der Klasse
WebSite mit ihren Werten aktiviert und die Objekte der Klasse Link mit dem Wert null. Die
ArrayListen für die Texte, Bilder und PDFs werden bei der Aktivierungstiefe 1 nicht aktiviert.
Setzen wir die Aktivierungstiefe auf 2, werden alle Elemente der nächsten Ebene aktiviert
und ausgegeben: die ArrayListen und ihre Objekte, einschließlich der darin enthaltenen StringObjekte. Die übernächste Ebene, die Objekte der Klasse Formatierung, werden nicht ausgegeben,
sondern nur mit dem Wert null aktiviert.
281
13 LazyLoading in Hibernate und Aktivierungstiefe in db4o
Ausgabe:
Link: Bücher
Text: TextBücher
Formatierung zu Text:
Bild: BildBücher
PDF: PDFBücher
Reihenfolge 2
Ebene 1
Link: Home
Text: Erster Text
Formatierung zu Text:
Bild: BildHome
PDF: PDFHome
Reihenfolge 1
Ebene Link: DVD
Text: DVDText
Formatierung zu Text:
Bild: BildDVD
PDF: PDF DVD
Reihenfolge 2
Ebene 2
Link: Produkte
Text: TextProdukte
Formatierung zu Text:
Bild: BildProdukte
PDF: PDFProdukte
Reihenfolge 2
Ebene
null
null
null
null
Dieses Ergebnis erhalten Sie auch ohne explizites Festlegen der Aktivierungstiefe, da die Standardaktivierungstiefe 5 beträgt und somit höher ist, als die soeben erstellte.
13.2.1 Aktivierungstiefe und die LinkedList
Verwenden Sie eine LinkedList, ist ein anderes Verhalten in Bezug auf die Aktivierungstiefe zu
beobachten. Da jedes Element innerhalb einer LinkedList eine Referenz zum nächsten Element besitzt, wird jedem darin enthaltenen Element eine separate Tiefe zugewiesen. Also: Jedes Element
einer LinkedList beinhaltet ein Element der nächsten Ebene, sprich es gibt eine has-a-Beziehung
zwischen den Elementen einer LinkedList. Wohingegen allen Elementen einer ArrayList eine einzige Tiefe zugewiesen wird.
Lassen Sie mich dies anhand eines Beispiels näher erläutern: Nehmen wir einmal an, Sie haben
sowohl in einer LinkedList als auch in einer ArrayList 5 Elemente. Verlassen Sie sich jetzt auf
die Standardaktivierungstiefe von 5, ist dies bei einer ArrayList kein Problem, da alle Elemente
der ArrayList in den Arbeitsspeicher geladen werden, wohingegen es bei einer LinkedList nur die
ersten 3 Elemente sind.
282
13.3 Lazy Loading in Hibernate
13.2.2 Update-Tiefe
Erinnern wir uns an die Methode cascadeOnUpdate(), die es uns ermöglichte, einer ArrayList, die
sich in einem Objekt befindet, Elemente hinzufügen. Es gibt für diese Methode eine Entsprechung
im Interface ExtObjectContainer: die Update-Tiefe. Die Ebene der ArrayList muss aktiviert
werden und da es sich hier um ein Update handelt, spricht man von der Update-Tiefe. Die
entsprechende Zeile, die - wie bei der Aktivierungstiefe - in den try-Block gehört, lautet wie
folgt:
db . e x t ( ) . c o n f i g u r e ( ) . updateDepth ( 1 ) ;
Eine Update-Tiefe von 0 würde wiederum bedeuten, dass der ArrayList keine Elemente hinzugefügt werden. Im Kapitel Embedded-Modus in einem Web-Projekt werden wir sehen, dass die
Update-Tiefe die Methode cascadeOnUpdate() im Embedded-Modus ersetzt.
13.2.3 Transparente Aktivierung
Die Transparente Aktivierung ist ein neuer wichtiger Bestandteil von db4o, der zum Zeitpunkt
des Erstellens dieses Buches noch in der Planungs- und Umsetzungsphase war. Die Transparente
Aktivierung soll Objekte in den Arbeitsspeicher laden, die noch nicht aktiviert wurden, die aber
benötigt werden. Der Programmierer soll von der manuellen Aktivierung der Objekte entlastet
werden. Zu diesem Thema können Sie sich auf der db4o-Website auf dem Laufenden halten. Dort
gibt es auch in regelmäßigen Abständen neue Versionen von db4o zum Herunterladen.
13.3 Lazy Loading in Hibernate
Wir haben bereits im Kapitel Transaktionen eine Lösung für das Problem Lazy Loading kennengelernt, und zwar das Pattern Open-Session-in-View. Da dies keine optimale Lösung war und es
eine flexiblere und einfachere Lösung gibt, lernen wir diese Möglichkeit jetzt kennen, nämlich das
Initialisieren der Daten, die tatsächlich gebraucht werden.
Ich werde dies anhand eines Beispiels näher erläutern: Wir werden Formatierungsobjekte in die
Datenbank oneToManyBidirektional.sql ein- und wieder auslesen. Um diese Datenbank verwenden zu können, müssen wir die url zur Datenbank in der context.xml ändern, die sich im Verzeichnis web/META-INF befindet: Wir ersetzen das letzte Wort datenbank nach dem Backslash
durch das Wort oneToManyBidirektional:
u r l ="j d b c : mysql : / / l o c a l h o s t : 3 3 0 6 / o n e T o M a n y B i d i r e k t i o n a l "
Beginnen wir mit dem Speichern: Die Methode save() speichert unser Formatierungsobjekt in
der Datenbank:
1
2
3
4
5
6
7
8
9
10
package Kap12;
import
import
import
import
import
Datenbankentwurf.Formatierung;
org.hibernate.Session;
org.hibernate.SessionFactory;
org.hibernate.Transaction;
util.HibernateUtil;
public class FormatierungSpeichernHibernate {
283
13 LazyLoading in Hibernate und Aktivierungstiefe in db4o
public void speichern(Formatierung f) {
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
Session session = null;
SessionFactory sessionFactory = HibernateUtil.getSessionFactory();
session = sessionFactory.openSession();
try {
Transaction tx = session.beginTransaction();
session.save(f);
tx.commit();
} catch (RuntimeException e) {
e.printStackTrace();
} finally {
if (session != null) {
session.close();
}
}
}
}
Listing 13.3: FormatierungSpeichernHibernate.java
Um in die Datenbank, Daten eingeben zu können, die in JNDI registriert wurde, benötigen wir
einen Zugriff auf den InitialContext, der mit Tomcat gestartet wird und auf den Sie in einem
Webcontext, also in einem Servlet oder in einem JSP, zugreifen können. Wir geben mit einem
Formular input.jsp (im Ordner web/Formular) das Format "font1" ein:
Abbildung 13.1: Eingabe von Formatierungen
Die Daten des Eingabeformulars werden an das Servlet geschickt, das unter dem Namen InOutServlet in der web.xml registriert wurde.
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
1
2
3
4
5
6
7
<html>
<head>
<title>Input Formats</title>
</head>
284
13.3 Lazy Loading in Hibernate
8
9
10
11
12
13
14
15
16
17
18
19
20
<body>
<form name=""
action="<%=request.getContextPath()%>/InOutServlet"
method="GET">
<input type="text" name="Formats" value=""/>
<input type="submit" name="Input" value="Input"/>
</form>
</body>
</html>
Listing 13.4: input.jsp
Im Servlet wird dann das Formatierungsobjekt aus dem Requestparameter ausgelesen und in der
Datenbank gespeichert:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package ServletsHibernate;
import
import
import
import
import
import
import
Datenbankentwurf.Formatierung;
Kap11.FormatierungSpeichern;
java.io.IOException;
javax.servlet.http.HttpServlet;
javax.servlet.http.HttpServletRequest;
javax.servlet.http.HttpServletResponse;
javax.servlet.ServletException;
public class InOutServlet extends HttpServlet {
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
if (request.getParameter("Input") != null) {
String name = request.getParameter("Formats");
Formatierung formatierung = new Formatierung();
formatierung.setName(name);
FormatierungSpeichern formatierungSpeichern
= new FormatierungSpeichern();
formatierungSpeichern.speichern(formatierung);
}
}
protected void doPost(HttpServletRequest request,
285
13 LazyLoading in Hibernate und Aktivierungstiefe in db4o
HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
31
32
33
34
35
}
}
Listing 13.5: InOutServlet.java
Im nächsten Schritt wollen wir unserem Formatierungsobjekt Texte hinzufügen. Wir lesen im
Filter die Formatierungsobjekte wieder aus und übergeben es dem Sessionattribut, das an das
Formular inputFormats.jsp weitergesendet wird:
package FilterHibernate;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import
import
import
import
import
import
import
import
import
import
import
Kap11.FormatierungAuslesen;
Datenbankentwurf.Formatierung;
java.io.IOException;
java.util.ArrayList;
javax.servlet.Filter;
javax.servlet.FilterChain;
javax.servlet.FilterConfig;
javax.servlet.ServletException;
javax.servlet.ServletRequest;
javax.servlet.ServletResponse;
javax.servlet.http.HttpServletRequest;
public class FormatsTextsFilter implements Filter {
private FilterConfig filterConfig = null;
public void init(FilterConfig filterConfig) throws
ServletException {
this.filterConfig = filterConfig;
}
public void doFilter(ServletRequest request,
ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
FormatierungAuslesen formatierungAuslesen = new FormatierungAuslesen();
ArrayList<Formatierung> formatList = formatierungAuslesen.auslesen();
req.getSession().setAttribute("formatList", formatList);
chain.doFilter(request, response);
286
13.3 Lazy Loading in Hibernate
38
39
40
41
42
43
}
public void destroy() {
}
}
Listing 13.6: FormatsTextsFilter.java
Die entsprechende Methode auslesen() befindet sich in der Klasse FormatierungAuslesen.java.
Der Methode createQuery() wird ein hibernatespezifische Abfrage übergeben, die alle Formatierungsobjekte ausliest:
s e l e c t f from Formatierung a s f
Und die Methode list() gibt alle Ergebnisse aus. Mehr zum Thema Abfragen in Hibernate finden
Sie im entsprechenden Kapitel.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package Kap12;
import
import
import
import
import
Datenbankentwurf.Formatierung;
java.util.ArrayList;
org.hibernate.Session;
org.hibernate.Transaction;
util.HibernateUtil;
public class FormatierungAuslesenHibernate {
public ArrayList<Formatierung> auslesen() {
ArrayList<Formatierung> f = new ArrayList<Formatierung>();
Session session = HibernateUtil.getSessionFactory().openSession();
try {
Transaction tx = session.beginTransaction();
/*Die folgende Abfrage liest alle Formatierungsobjekte aus:*/
f = (ArrayList<Formatierung>) session.createQuery
("select f from Formatierung as f ").list();
tx.commit();
} catch (RuntimeException e) {
e.printStackTrace();
} finally {
session.close();
}
287
13 LazyLoading in Hibernate und Aktivierungstiefe in db4o
return f;
32
33
34
}
}
Listing 13.7: FormatierungAuslesenHibernate.java
Die ausgelesenen Daten werden an unser Formular gesendet, das die Formatierungen in einem
select-Feld ausliest, um anschließend Texte hinzufügen zu können:
<%@page contentType="text/html"%>
<%@page pageEncoding="UTF-8"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>Input Formats und Texts</title>
</head>
<body>
<form name=""
action="<%=request.getContextPath()%>/InOutServlet"
method="GET">
<table align="center" valign="middle">
<tr>
<td>Text</td>
<td>
<input type="text" name="Texts" value=""/>
</td>
</tr>
<tr>
<td>Format</td>
<td>
<!--Die Formatierungen werden in einem Auswahlfeld ausgegeben: -->
<select name="Formats">
<c:forEach var="format" items="${formatList}">
<option value="<c:out value="${format.formatierungId}" />">
<c:out value="${format.name}" />
</option>
</c:forEach>
</select>
</td> </tr>
<tr><td></td>
288
13.3 Lazy Loading in Hibernate
41
42
43
44
45
46
47
48
49
<td>
<input type="submit" name="InputFormat" value="InputFormat"/>
</td>
</tr>
</table>
</form>
<br>
</body>
</html>
Listing 13.8: inputFormats.jsp
Wir fügen zu unserer Formatierung den Text Hallo hinzu und speichern beides:
Abbildung 13.2: Hinzufügen von Texten zu den Formatierungen
Mit der Methode speichernMitText() der folgenden Klasse wird ein Text einer Formatierung
zugeordnet. Zuerst muss die Formatierung mithilfe des Primarschlüssels aus der Datenbank augelesen werden. Dies geschieht mit dem so genannten Parameter Binding. Mit der setInteger()
wird festgelegt, dass in der Tabellenspalte formatierungId nach dem Parameter IDFormats gesucht werden soll und die Methode uniqueResult() sucht nach einem bestimmten Ergebnis. Und
die folgende Abfrage mit select in Hibernate sucht nach der Formatierung, die einen Primärschlüssel hat, der dem Parameter entspricht, der der Methode setInteger() übergeben wurde.
S e l e c t f from Formatierung f where f . f o r m a t i e r u n g I d =
: formatierungId
Die gesamte Klasse, mit der darin enthaltenen Abfrage, sieht dann wie folgt aus:
1
2
3
4
5
6
7
8
9
10
11
package Kap12;
import
import
import
import
import
Datenbankentwurf.Formatierung;
Datenbankentwurf.Text;
org.hibernate.Session;
org.hibernate.Transaction;
util.HibernateUtil;
public class FormatierungSpeichernMitText {
public void speichernMitText(String textName, Integer IDFormats) {
289
13 LazyLoading in Hibernate und Aktivierungstiefe in db4o
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
Session session = HibernateUtil.getSessionFactory().openSession();
try {
Transaction tx = session.beginTransaction();
/*Abfrage die eine bestimmte Formatierung mithilfe des
Primärschlüssels ausliest.*/
Formatierung form = (Formatierung) session.createQuery
("Select f from Formatierung f " +
"where f.formatierungId = :formatierungId").
setInteger("formatierungId", IDFormats).uniqueResult();
Text t = new Text(textName);
/*Der Formatierung wird der Text hinzugefügt.*/
form.getText().add(t);
/*Dem Text wird die Formatierung zugewiesen.*/
t.setFormatierung(form);
session.save(t);
session.save(form);
tx.commit();
} catch (RuntimeException e) {
e.printStackTrace();
} finally {
session.close();
}
}
}
Listing 13.9: FormatierungSpeichernMitText.java
Lesen wir jetzt erneut die Formatierungen in einem Servlet (siehe Servlet InOutServletText.java
weiter unten) und mit der Methode auslesen() der Klasse FormatierungAuslesen.java aus, erleben
wir eine Überraschung: Ups! Was ist jetzt passiert? Es wird eine Exception geworfen und wir
erhalten folgende Fehlermeldung:
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: Datenbankentwurf.Formatierung.text, no session or session was closed
Hibernate teilt uns genau mit, wo das Problem liegt: Die ArrayList<Text> wurde nicht mit in
den Arbeitsspeicher geladen, sie muss separat mit der statischen Methode
Hibernate . i n i t i a l i z e ()
290
13.3 Lazy Loading in Hibernate
initialisiert werden, was wir in der Klasse FormatierungAuslesenInitialize.java tun. Im Formular
inputFormats.jsp benötigten wir die Texte nicht, da wir im Auswahlfeld nur die Formatierungen
auslesen und es würde den Arbeitsspeicher überbeanspruchen, wenn z.B. 1000 Texte mitausgelesen werden würden. So brauchen wir auch die Text nicht zu initialisieren, aber sobald wir
die dazugehörigen Texte auslesen wollen, muss die entsprechende ArrayList<Text> initialisiert
werden. Da wir in diesem Fall explizit nach allen zugehörigen Texten suchen.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package Kap12;
import
import
import
import
import
import
import
import
Datenbankentwurf.Formatierung;
Datenbankentwurf.Text;
java.util.ArrayList;
java.util.List;
org.hibernate.Hibernate;
org.hibernate.Session;
org.hibernate.Transaction;
util.HibernateUtil;
public class FormatierungAuslesenInitialize {
public ArrayList<Formatierung> auslesen() {
ArrayList<Formatierung> f = new ArrayList<Formatierung>();
Session session = HibernateUtil.getSessionFactory().openSession();
try {
Transaction tx = session.beginTransaction();
f = (ArrayList<Formatierung>) session.createQuery
("select f from Formatierung as f ").list();
for(Formatierung form: f){
List<Text> text = form.getText();
Hibernate.initialize(text);
}
tx.commit();
} catch (RuntimeException e) {
e.printStackTrace();
} finally {
session.close();
}
return f;
}
}
291
13 LazyLoading in Hibernate und Aktivierungstiefe in db4o
Listing 13.10: FormatierungAuslesenInitialize.java
Eine andere Möglichkeit dieses Problem zu lösen war das Pattern Open-Session-in-View, das wir
im Kapitel Transaktionen in db4o und Hibernate kennengelernt haben. Da bei diesem Pattern
die Session nicht geschlossen wird, können jederzeit die Text der ArrayList<Text> nachgeladen
werden. Wird die Session geschlossen - wie in unserem Beispiel -, besteht keine Datenbankverbindung mehr und es wird eine Exception geworfen, falls der Versuch unternommen wird, die
Texte auszulesen.
Wie können wir noch die LazyInitializationException umgehen? Mit dem Ausdruck "left join
fetch", den wir in eine Abfrage integrieren, und dem FetchType.Eager. Lassen Sie uns mit dem
FetchTyp.EAGER beginnen: Der englische Begriff eager lässt sich ins Deutsche mit eifrig übersetzen und er stellt das Gegenteil von lazy dar, was faul bedeutet. Der FetchType.LAZY ist
die Standardeinstellung in Hibernate, der zur Folge hat, dass nie der Inhalt einer ArrayList mit
in den Arbeitspeicher geladen wird. Hierzu verändern wir die @OneToMany-Beziehung in der
Klasse Formatierung, indem wir dieses um "fetch = FetchType.EAGER" ergänzen:
@OneToMany( mappedBy = " f o r m a t i e r u n g " , c a s c a d e = CascadeType . ALL,
f e t c h = FetchType .EAGER)
Das gleiche machen mir für die @ManyToOne-Beziehung in der Klasse Text:
@ManyToOne( f e t c h = FetchType .EAGER)
Haben wir dies getan, können wir ohne Probleme die Formatierungen mit den entsprechenden
Texten auslesen (siehe auch unten stehendes Servlet InOutServletText.java).
Ausgabe:
Groesse eager: 2
Name eager: font1
Text eager: text
Text eager: text2
Text eager: hello
Name eager: font2
Diese Vorgehensweise hat einen entscheidenden Nachteil: Es werden immer, bei jeder Abfrage,
alle Texte mitausgelesen und würde so u.U. zu Performanceproblemen führen.
Wollen Sie die Texte nur bei einzelnen Abfragen mitauslesen, benötigen Sie den Ausdruck "left
join fetch". Dies hat aber leider einen unschönen Nebeneffekt, wie die unten stehende Ausgabe
zeigt:
package Kap12;
1
2
3
4
5
6
7
8
9
10
import
import
import
import
import
Datenbankentwurf.Formatierung;
java.util.ArrayList;
org.hibernate.Session;
org.hibernate.Transaction;
util.HibernateUtil;
public class FormatierungAuslesenFetch {
292
13.3 Lazy Loading in Hibernate
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public ArrayList<Formatierung> auslesen() {
ArrayList<Formatierung> f = new ArrayList<Formatierung>();
Session session = HibernateUtil.getSessionFactory().openSession();
try {
Transaction tx = session.beginTransaction();
/*Abfrage mit left join fetch*/
f = (ArrayList<Formatierung>) session.createQuery
("select f from Formatierung as f left join fetch f.text ").list();
tx.commit();
} catch (RuntimeException e) {
e.printStackTrace();
} finally {
session.close();
}
return f;
}
}
Listing 13.11: FormatierungAuslesenFetch.java
Ausgabe:
Groesse fetch: 4
Name fetch: font1
Text fetch: text
Text fetch:text2
Text fetch: hello
Name fetch: font1
Text fetch: text
Text fetch: text2
Text fetch: hello
Name fetch: font1
Text fetch: text
Text fetch: text2
Text fetch: hello
Name fetch: font2
Hier das dazugehörige Servlet: Mit diesem Servlet werden die Daten ein- und ausgelesen. Geben
wir nochmals einige Texte ein und wenden die Methode an, die die Texte initialisert, so erhalten
wir das gewünschte Ergebnis und es werden auch die Texte ausgeben und es wird keine Exception
geworfen:
293
13 LazyLoading in Hibernate und Aktivierungstiefe in db4o
package ServletsHibernate;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import
import
import
import
import
import
import
import
import
import
import
import
import
Datenbankentwurf.Formatierung;
Datenbankentwurf.Text;
Kap12.FormatierungAuslesenFetch;
Kap12.FormatierungAuslesenHibernate;
Kap12.FormatierungAuslesenInitialize;
Kap12.FormatierungSpeichernMitText;
java.io.IOException;
java.util.ArrayList;
java.util.List;
javax.servlet.http.HttpServlet;
javax.servlet.http.HttpServletRequest;
javax.servlet.http.HttpServletResponse;
javax.servlet.ServletException;
public class InOutServletText extends HttpServlet {
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
if (request.getParameter("InputFormat") != null) {
String textName = request.getParameter("Texts");
int formatID = Integer.parseInt(request.getParameter("Formats"));
FormatierungSpeichernMitText formatierungSpeichernMitText =
new FormatierungSpeichernMitText();
formatierungSpeichernMitText.speichernMitText(textName, formatID);
/* Mit folgenden Befehlszeilen lassen sich nur die Formatierungen
auslesen und nicht die darin enthaltenen Texte und es
wird eine LazyInitializationException geworfen, es sei denn es
wurde fuer die Beziehungen "fetch = FetchType.EAGER" festgelegt:*/
/* FormatierungAuslesenHibernate formatierungAuslesen
= new FormatierungAuslesenHibernate();
ArrayList<Formatierung> al = formatierungAuslesen.auslesen();
System.out.println("Groesse eager: " + al.size());
for (Formatierung fo : al) {
System.out.println("Name eager: " + fo.getName());
List<Text> textOben = fo.getText();
for (Text t : textOben) {
System.out.println("Text eager: " + t.getName());
}
}*/
/*Mit folgenden Zeilen wird der Ausdruck left join fetch angewandt:*/
/* FormatierungAuslesenFetch formatierungAuslesenFetch
294
13.3 Lazy Loading in Hibernate
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
= new FormatierungAuslesenFetch();
ArrayList<Formatierung> alFetch
= formatierungAuslesenFetch.auslesen();
System.out.println("Groesse fetch: " + alFetch.size());
for (Formatierung foFetch : alFetch) {
System.out.println("Name fetch: " + foFetch.getName());
List<Text> textFetch = foFetch.getText();
for (Text tFetch : textFetch) {
System.out.println("Text fetch: " + tFetch.getName());
}
}*/
FormatierungAuslesenInitialize formatierungAuslesenInitialize =
new FormatierungAuslesenInitialize();
ArrayList<Formatierung> alf
= formatierungAuslesenInitialize.auslesen();
System.out.println("Groesse: " + alf.size());
for (Formatierung f : alf) {
System.out.println("Name: " + f.getName());
List<Text> text = f.getText();
for (Text t : text) {
System.out.println("Text: " + t.getName());
}
}
}
}
protected void doPost(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
Listing 13.12: InOutServletText.java
Ausgabe:
Groesse: 2
Name: font1
Text: text
Text: text2
Text: hello
Name: font2
295
14 Besonderheiten bei Webprojekten mit db4o
Im Kapitel Client-Server-Modus für db4o haben wir gelernt, dass der Embedded-Modus der
richtige Modus für Client-Server-Abfragen in einem Web-Projekt ist. So stellt sich die Frage:
Wie integrieren wir den Embedded Modus von db4o in unser Web-Projekt? Wir öffnen in einem
speziellen ServletContextListener den db4o-Server und in einem HttpSessionListener den Client.
So besteht während einer Session eine Verbindung zwischen Client und Server und wir können
den Arbeitspeicher von db4o in unser Content-Management-System integrieren und seine Vorteile
nutzen.
Den Db4oServletContextListener habe ich dem db4o-Tutorial entnommen und geringfügig verändert. Bevor wir den ContextListener erstellen, tragen wir den Namen unserer Datenbank in
die web.xml als Context-Parameter ein. Sie können auf einen Context-Parameter von überall,
innerhalb eines Web-Projektes zugreifen. Ändern Sie den Namen der Datenbank, so müssen Sie
dies nur noch an dieser Stelle tun.
1
2
3
4
<context-param>
<param-name>db4oFileName</param-name>
<param-value>C:/Datenbank/DasErsteProjekt/datenbank.yap</param-value>
</context-param>
Listing 14.1: contextElement.xml
Als Nächstes erstellen wir für unseren Db4oServletContextListener einen Eintrag in die web.xml:
1
2
3
4
5
<listener>
<listener-class>
Listeners.Db4oServletContextListener
</listener-class>
</listener>
Listing 14.2: webdb4oListener.xml
Unten stehend der Db4oServletContextListener, der beim Starten eines Web-Projektes mit F6
den db4o-Server startet. Der Server wird dem Context mit dem Namen db4oServer als Attribut
übergeben.
1
2
3
4
5
6
7
package Listeners;
import com.db4o.Db4o;
import com.db4o.ObjectServer;
import javax.servlet.*;
public class Db4oServletContextListener implements ServletContextListener {
297
14 Besonderheiten bei Webprojekten mit db4o
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
private ObjectServer server = null;
/*Wird das Web-Projekt gestartet, wird folgende Methode
durchgeführt*/
public void contextInitialized(ServletContextEvent event) {
close();
/*Sie verschaffen sich Zugriff auf den ServletContext.*/
ServletContext context = event.getServletContext();
/*Wir starten den Server und geben einem User Zugriff auf
die Datenbank. Mit getInitParameter greifen Sie
auf den Namen der Datenbank, der als Context-Parameter
hinterlegt wurde, zu.*/
server = Db4o.openServer(context.getInitParameter("db4oFileName"), 0);
/*Wir übergeben dem Context den Server als Attribut.*/
context.setAttribute("db4oServer", server);
/*Es wird eine Meldung ausgegeben, wenn der Server startet.*/
context.log("db4o startup on " +
context.getInitParameter("db4oFileName"));
}
/*Wird das Web-Projekt beendet, wird folgende Methode
durchgeführt*/
public void contextDestroyed(ServletContextEvent event) {
ServletContext context = event.getServletContext();
/*Das Attribut wird wieder aus dem Context gelöscht.*/
context.removeAttribute("db4oServer");
/*Der Server wird geschlossen.*/
close();
context.log("db4o shutdown");
}
private void close() {
if (server != null) {
server.close();
}
server = null;
}
}
Listing 14.3: Db4oServletContextListener.java
298
Ausgabe in Tomcat
11.07.2007 18:33:55 org.apache.catalina.core.ApplicationContext log
INFO: db4o shutdown
11.07.2007 18:33:57 org.apache.catalina.core.ApplicationContext log
INFO: db4o startup on C:\Datenbank\DasErsteProjekt\datenbank.yap
Der Server ist geöffnet und wartet auf Anfragen. Jetzt brauchen wir nur noch einen Client und
den öffnen wir in einem HttpSessionListener. Warum machen wir dies? Wir wollen zusätzlich
den Cache nutzen, den uns db4o zur Verfügung stellt. Im Embedded Modus stellt db4o einen
Arbeitsspeicher zur Verfügung, der es möglich macht, mit gecachten Objekten zu arbeiten. Dies
entlastet die Datenbank, da nicht jede Abfrage auf die Datenbank direkt zugreift, sondern nur
auf die sich im Arbeitsspeicher befindlichen Objekte. So ist es möglich, einer größeren Anzahl
Benutzer gleichzeitig Zugriff zu gewähren. Sollten Sie aber sofort mit Daten arbeiten wollen,
die durch andere Benutzer eingegeben worden sind, müssen Sie die ausgelesenen Daten vorher
aktualisieren. Hierzu lernen wir am Ende des Kapitels die Methode refresh() kennen.
Jedes Mal, wenn der Benutzer eine Session startet, wird ein Datenbankclient von db4o geöffnet.
Wird die Session wieder beendet, wird auch der Datenbankclient wieder geschlossen. Der Client
bleibt also so lange geöffnet, wie die Session existiert.
Mit der Befehlszeile
( ObjectServer ) event . g e t S e s s i o n ( ) . getServletContext ( )
. g etAt trib ute (" db4oServer ")
können Sie auf das Context-Attribut zugreifen, das den Server beinhaltet. Der Server wurde im
Db4oServletContextListener dem Context-Attribut mit Namen db4oServer übergeben und kann
nun wieder ausgelesen werden.
Haben Sie den Bezug auf den Server aus dem Context ausgelesen, können Sie die Methode
openClient() ausführen und Sie erhalten den ObjectContainer, also unseren Client, zurück.
( ( ObjectServer ) event . g e t S e s s i o n ( ) . getServletContext ( )
. g etAt trib ute (" db4oServer " ) . openClient ( ) ;
Der Client wird der Session als Attribut übergeben und erhält den Namen db4oClient:
e v e n t . g e t S e s s i o n ( ) . s e t A t t r i b u t e ( " d b 4 o C l i e n t " , oc ) ;
Unten stehend finden Sie den entsprechenden HttpSessionListener:
1
2
3
4
5
6
7
8
9
10
11
12
package Listeners;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
import com.db4o.ObjectContainer;
import com.db4o.ObjectServer;
public class Db4oSessionListener implements
HttpSessionListener {
299
14 Besonderheiten bei Webprojekten mit db4o
/*Diese Methode wird am Anfang einer Session durchgeführt*/
public void sessionCreated(HttpSessionEvent event) {
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/*Der Client des Embedded Modus wird geöffnet*/
ObjectContainer oc = ((ObjectServer) event.getSession()
.getServletContext().getAttribute("db4oServer")).openClient();
/*und der Session als Attribut db4oClient übergeben.*/
event.getSession().setAttribute("db4oClient", oc);
}
/*Diese Methode wird durchgeführt, wenn die Session beendet
wird.*/
public void sessionDestroyed(HttpSessionEvent event) {
/*Am Ende der Session wird der Client geschlossen.*/
((ObjectServer) event.getSession()
.getServletContext().getAttribute("db4oServer")).close();
}
}
Listing 14.4: Db4oSessionListener.java
Der Listener muss in der web.xml registriert werden:
<listener>
<listener-class>
Listeners.Db4oSessionListener
</listener-class>
</listener>
1
2
3
4
5
Listing 14.5: sessionWeb.xml
Im Db4oSessionListener wurde der Client für eine Session geöffnet, so können Sie jetzt in einer
Abfrage, den ObjectContainer, also den Client, aus dem Request und der Session auslesen:
c l i e n t =( O b j e c t C o n t a i n e r ) r e q u e s t . g e t S e s s i o n ( )
. getAttribute (" db4oClient " ) ;
Lassen Sie uns dies anhand einer Beispielabfrage tun: Die vollständige Methode auslesenLink()
finden Sie in der Klasse AuslesenClient.java. Sie müssen der Methode auslesenLink() ein Objekt
der Klasse HttpServletRequest übergeben, da Sie in einer „normalen“ Klasse nicht auf den Request
zugreifen können. Auf ein Objekt der Klasse HttpServletRequest können Sie nur innerhalb von
Servlets und Filter zugreifen. Sie benötigen aber ein Objekt der Klasse HttpServletRequest, da
Sie nur so in der Lage sind, ein Session-Attribut auszulesen.
300
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package Kap12;
import
import
import
import
import
import
import
import
import
import
import
Datenbankentwurf.Link;
Datenbankentwurf.WebSite;
com.db4o.Db4o;
com.db4o.ObjectContainer;
com.db4o.ObjectServer;
com.db4o.ObjectSet;
com.db4o.ext.DatabaseFileLockedException;
java.io.IOException;
java.util.ArrayList;
javax.servlet.ServletContext;
javax.servlet.http.HttpServletRequest;
public class AuslesenClient {
public ArrayList<Link> auslesenLink(HttpServletRequest request){
ObjectContainer client = null;
ArrayList<Link> f = new ArrayList<Link>();
try{
/*Aus dem HttpServletRequest request können Sie den
ObjectContainer auslesen.*/
client = (ObjectContainer)request.getSession().
getAttribute("db4oClient");
Link l = new Link(null);
ObjectSet<Link> result = client.get(l);
while (result.hasNext()){
l = result.next();
f.add(l);
}
} catch (DatabaseFileLockedException e) {
e.printStackTrace();
}
return f;
}
}
Listing 14.6: AuslesenClient.java
Nachdem wir den Embedded-Modus eingerichtet haben, lassen Sie uns die Daten mithilfe eines
Filters und eines JSPs im Browser ausgeben. Im Filter werden die Daten mit unserer Methode
auslesenLink() ausgelesen:
301
14 Besonderheiten bei Webprojekten mit db4o
package Eingang;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import
import
import
import
import
import
import
import
import
import
import
import
import
import
Datenbankabfragen.WebSiteDaten;
Datenbankentwurf.Link;
Kap12.AuslesenClient;
com.db4o.ObjectContainer;
java.io.IOException;
java.util.ArrayList;
javax.servlet.Filter;
javax.servlet.FilterChain;
javax.servlet.FilterConfig;
javax.servlet.ServletContext;
javax.servlet.ServletException;
javax.servlet.ServletRequest;
javax.servlet.ServletResponse;
javax.servlet.http.HttpServletRequest;
public class ServerClientFilter implements Filter {
private FilterConfig filterConfig = null;
public void init(FilterConfig filterConfig) throws ServletException {
this.filterConfig = filterConfig;
}
public void doFilter(ServletRequest request,
ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
req.getSession().removeAttribute("ListLink");
AuslesenClient a = new AuslesenClient();
ArrayList<Link> ListLink = a.auslesenLink(req);
req.getSession().setAttribute("ListLink", ListLink);
chain.doFilter(request, response);
}
public void destroy() {
}
}
Listing 14.7: ServerClientFilter.java
Vergessen Sie bitte nicht den Filter in die web.xml einzutragen:
<filter>
1
302
2
3
4
5
6
7
8
<filter-name>ServerClientFilter</filter-name>
<filter-class>Eingang.ServerClientFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>ServerClientFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Listing 14.8: webServerClient.xml
Starten wir nun das Projekt mit F6 und öffnen das serverClientJSP.jsp,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<%@page language="java" %>
<%@page contentType="text/html"%>
<%@page pageEncoding="UTF-8"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<html>
<head>
<link rel="stylesheet" href="Styles/stil.css" type="text/css">
<title>Client-Server</title>
</head>
<body>
<c:forEach var="ListLink" items="${ListLink}">
<c:out value="${ListLink.name}" />
</c:forEach>
</body>
</html>
Listing 14.9: serverClientJSP.jsp
erhalten wir folgende Ausgabe im Browser:
Abbildung 14.1: Ausgabe des serverClientJSP.jsp im Browser
303
14 Besonderheiten bei Webprojekten mit db4o
Diese Ausgabe setzt voraus, dass Sie vorher die Datenbank gelöscht und mit den folgenden
Klassen eine Neue erstellt haben: FormatierungOhneTextInDatenbank.java, WebSiteEinlesenInDatenbank.java und WebSiteEinlesenWeitere.java.
Wie Sie gesehen haben, erfordert das Implementieren des Embedded-Modus von db4o in einem
Web-Projekt nur wenige Schritte: Sie müssen die Datenbank als Context-Parameter anlegen,
anschließend öffnen Sie den Server in einem ServletContextListener und den Client in einem
HttpSessionListener. So einfach! Und schon haben wir alle Voraussetzungen für unser ContentManagement-System geschaffen, das wir im Kapitel Unser Content-Management-System ausführlich besprechen werden.
14.1 Die Methode refresh()
Werden Daten durch andere Benutzer eingegeben, werden die Daten beim Auslesen nicht automatisch aktualisiert. Wollen Sie also wissen, welche Daten durch andere Benutzer eingegeben
worden sind, müssen Sie, die ausgelesenen Daten mit der Methode refresh() auf den neuesten
Stand bringen.
Die Methode refresh() befindet sich im Interface ExtObjectContainer im Package com.db4o.ext.
Sie müssen der Methode refresh() zwei Parameter übergeben, der Erste ist ein Objekt und der
Zweite ist die Suchtiefe. In unserem Fall ist das Objekt der Link link, der aus der Datenbank
ausgelesen worden ist. Die vollständige Befehlszeile lautet:
c l i e n t . e x t ( ) . r e f r e s h ( l i n k , I n t e g e r .MAX_VALUE) ;
In unten stehender Klasse AuslesenRefreshClient.java wird diese Zeile eingefügt, nachdem die
Daten ausgelesen wurden:
package Kap12;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import
import
import
import
import
import
import
import
import
import
import
Datenbankentwurf.Link;
Datenbankentwurf.WebSite;
com.db4o.Db4o;
com.db4o.ObjectContainer;
com.db4o.ObjectServer;
com.db4o.ObjectSet;
com.db4o.ext.DatabaseFileLockedException;
java.io.IOException;
java.util.ArrayList;
javax.servlet.ServletContext;
javax.servlet.http.HttpServletRequest;
public class AuslesenRefreshClient {
public ArrayList<Link> auslesenLink(HttpServletRequest request){
ObjectContainer client = null;
ArrayList<Link> f = new ArrayList<Link>();
try{
client = (ObjectContainer)request.getSession().
304
14.2 Update-Tiefe im Embedded Modus
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
getAttribute("db4oClient");
Link link = new Link(null);
ObjectSet<Link> result = client.get(link);
while (result.hasNext()){
link = result.next();
/*Wurden durch einen anderen Benutzer Daten
eingegeben, soll der Cache des aktuellen Benutzers
auf den neuesten Stand der Datenbank gebracht werden.*/
client.ext().refresh(link, Integer.MAX_VALUE);
f.add(link);
}
} catch (DatabaseFileLockedException e) {
e.printStackTrace();
}
return f;
}
}
Listing 14.10: AuslesenRefreshClient.java
Sollten die Daten in der Datenbank nur selten aktualisiert werden, können Sie den refresh()Befehl weglassen. So ist es z. B. bei einem Content-Mangement-System nicht notwendig, während
einer Session, mehrmals neue Daten von der Datenbank zu holen. Dies würde die Datenbank über
Gebühr belasten. Sie können also die Datenbank entlasten, indem Sie viel mit Objekten arbeiten,
die sich im Arbeitsspeicher der Datenbank db4o befinden.
Wohingegen bei einer Online-Banking-Datenbankanwendung immer alle Daten mit der Methode
refresh() aktualisiert werden sollen. Dies stellt aber in diesem Fall keinen Nachteil dar, da bei
Online-Banking-Vorgängen die Benutzer bereit sind länger zu warten, schon aus Gründen der
Sicherheit.
14.2 Update-Tiefe im Embedded Modus
Im Embedded-Modus können Sie die Methode cascadeOnUpdate() nicht verwenden, sondern nur
die Methode updateDepth() des Interfaces ExtObjectContainer (vgl. Kapitel Update-Tiefe). In
unten stehender Klasse UpdateWebSite.java habe ich die entsprechende Befehlszeile integriert.
Außerdem habe ich bestimmte Bereiche gelockt und einen Rollback mit zugehörigem Refresh und
Commit eingebaut (vgl. Kapitel Transaktionen). Etwas Neues ist auch dabei: Wir ersetzen eine
ArrayList durch eine andere ArrayList, wobei Sie allerdings darauf achten müssen, nicht mehr
benötigte Objekte separat zu löschen. Diese Vorgehensweise gibt es sowohl im Embedded-Modus
als auch im Solo-Modus, sie ist aber im Embedded-Modus vorzuziehen, da es hier, wenn Sie
gleichzeitig aus der ArrayList Elemente hinzufügen und entfernen, zu Problemen kommen kann.
1
package Kap12;
305
14 Besonderheiten bei Webprojekten mit db4o
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import
import
import
import
import
import
import
import
import
import
import
import
Datenbankentwurf.Bild;
Datenbankentwurf.Formatierung;
Datenbankentwurf.Link;
Datenbankentwurf.PDF;
Datenbankentwurf.Text;
Datenbankentwurf.WebSite;
Kap10.LockManager;
com.db4o.ObjectContainer;
com.db4o.ObjectSet;
com.db4o.ext.DatabaseFileLockedException;
java.util.ArrayList;
javax.servlet.http.HttpServletRequest;
public class UpdateWebSite {
public void updateWebSite(Link link, Link linkNeu,
ArrayList<Formatierung> formatierungNeu, ArrayList<Text> textAlt,
ArrayList<Bild> bildNeu, ArrayList<Bild> bildAlt,
ArrayList<PDF> pdfNeu, ArrayList<PDF> pdfAlt,
String reihenfolgeNeu, String ebeneNeu, HttpServletRequest request){
ObjectContainer db = null;
Formatierung form = new Formatierung();
Formatierung forme = new Formatierung();
WebSite w = new WebSite();
ArrayList<Text> textNeu = new ArrayList<Text>();
try {
db = (ObjectContainer)request.getSession().getAttribute("db4oClient");
/*Die Methode updateDepth(1) ermöglicht es, einer ArrayList Objekte
hinzuzufügen, die sich innerhalb eines Objektes befinden.*/
db.ext().configure().updateDepth(1);
/*Die Klasse LockManager.java stellt Ihnen Methoden zur Verfügung,
die es Ihnen erlauben, bestimmte Dateien zu locken.*/
LockManager lockManager = new LockManager(db);
w = new WebSite(link, null, null, null, null, null);
ObjectSet<WebSite> resultWebSite = db.get(w);
w = resultWebSite.next();
if(lockManager.lock(w)){
/*Die alten Bildobjekte müssen gelöscht werden.*/
for(Bild b: bildAlt){
ObjectSet<Bild> resultBild = db.get(b);
306
14.2 Update-Tiefe im Embedded Modus
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
while(resultBild.hasNext()){
Bild be = resultBild.next();
db.delete(be);
}
}
/*Die alten PDF-Objekte werden gelöscht.*/
for(PDF pdf: pdfAlt){
ObjectSet<PDF> resultPdf = db.get(pdf);
while(resultPdf.hasNext()){
PDF p = resultPdf.next();
db.delete(p);
}
}
/*Der Name des Linkobjektes wird ersetzt.*/
ObjectSet<Link> resultLink = db.get(link);
link = resultLink.next();
String name = linkNeu.getName();
link.setName(name);
/*Alte Texte werden aus den entsprechenden Formatierungsobjekten
entfernt und anschließend gelöscht.*/
for(Text te: textAlt){
ObjectSet<Text> resultText = db.get(te);
while (resultText.hasNext()){
Text t = resultText.next();
ArrayList<Text> listText = new ArrayList<Text>();
listText.add(t);
Formatierung fo = new Formatierung(null, listText);
ObjectSet<Formatierung> resultFormat = db.get(fo);
while (resultFormat.hasNext()){
forme = resultFormat.next();
if(lockManager.lock(forme)){
ArrayList<Text> formatText = forme.getText();
formatText.remove(t);
db.delete(t);
forme.setText(formatText);
db.set(forme);
lockManager.unlock(forme);
}
}
}
}
/*Die neuen Texte werden den entsprechenden Formatierungsobjekten
307
14 Besonderheiten bei Webprojekten mit db4o
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
308
hinzugefügt.*/
for(Formatierung f : formatierungNeu){
Formatierung fo = new Formatierung(f.getName());
ArrayList<Text> al = f.getText();
ObjectSet<Formatierung> result = db.get(fo);
form = result.next();
if(lockManager.lock(form)){
for(Text t: al){
ArrayList<Text> texte = form.getText();
texte.add(t);
form.setText(texte);
db.set(form);
}
lockManager.unlock(form);
}
}
/*Die neuen Texte werden einer ArrayList<Text> textNeu hinzugefügt.*/
for(Formatierung f : formatierungNeu){
ArrayList<Text> al = f.getText();
Text te = null;
for(Text tex: al){
ObjectSet<Text> result = db.get(tex);
while (result.hasNext()){
te = result.next();
}
if(lockManager.lock(te)){
textNeu.add(te);
lockManager.unlock(te);
}
}
}
/*Der Link wird gespeichert und die entsprechen ArrayListen
des WebSite-Objektes werden ersetzt. Und anschließend wird
das WebSite-Objekt gespeichert.*/
db.set(link);
w.setText(textNeu);
w.setBild(bildNeu);
w.setPdf(pdfNeu);
w.setReihenfolge(reihenfolgeNeu);
w.setEbene(ebeneNeu);
db.set(w);
lockManager.unlock(w);
}
14.2 Update-Tiefe im Embedded Modus
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
} catch (DatabaseFileLockedException e) {
/*Sollte es Probleme beim Speichern geben, werden
alle Abfragen im try-Block mit rollback() rückgängig gemacht
und die Daten im Arbeitspeicher werden mit refresh()
wieder auf den alten Stand gebracht.*/
db.rollback();
db.ext().refresh(forme, Integer.MAX_VALUE);
db.ext().refresh(form, Integer.MAX_VALUE);
db.ext().refresh(w, Integer.MAX_VALUE);
} finally{
db.commit();
}
}
}
Listing 14.11: UpdateWebSite.java
Wie verhält es sich mit dem Löschvorgang? Gibt es eine Entsprechung zu der Methode cascadeOnDelete(), die es uns ermöglicht das Linkobjekt und die Stringobjekte innerhalb der WebSite
mit dem Objekt der Klasse WebSite zu löschen? Nein. Wenn Sie ein Objekt der Klasse WebSite im
Embedded-Modus löschen wollen, müssen Sie die Objekte der Klasse Link und die Stringobjekte
separat löschen.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package Kap12;
import
import
import
import
import
import
import
import
import
import
import
import
Datenbankentwurf.Bild;
Datenbankentwurf.Formatierung;
Datenbankentwurf.Link;
Datenbankentwurf.PDF;
Datenbankentwurf.Text;
Datenbankentwurf.WebSite;
Kap10.LockManager;
com.db4o.ObjectContainer;
com.db4o.ObjectSet;
com.db4o.ext.DatabaseFileLockedException;
java.util.ArrayList;
javax.servlet.http.HttpServletRequest;
public class DeleteWebSite {
public void deleteWebSite(Link link, HttpServletRequest request){
ObjectContainer db = null;
Formatierung form = new Formatierung();
WebSite w = new WebSite();
try {
309
14 Besonderheiten bei Webprojekten mit db4o
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
db = (ObjectContainer)request.getSession().getAttribute("db4oClient");
db.ext().configure().updateDepth(1);
w = new WebSite(link, null, null, null, null, null);
ObjectSet<WebSite> resultWebSite = db.get(w);
w = resultWebSite.next();
LockManager lockManager = new LockManager(db);
if(lockManager.lock(w)){
/*Die Objekte reihenfolge, ebene, Link, Bild
und PDF werden gelöscht.*/
String reihenfolge = w.getReihenfolge();
db.delete(reihenfolge);
String ebene = w.getEbene();
db.delete(ebene);
Link l = w.getLink();
db.delete(l);
ArrayList<Bild> bild = w.getBild();
for(Bild b: bild){
db.delete(b);
}
db.delete(bild);
ArrayList<PDF> pdf = w.getPdf();
for(PDF p: pdf){
db.delete(p);
}
db.delete(pdf);
ArrayList<Text> text = w.getText();
/*Die Textobjekte werden aus den ArrayListen der entsprechenden
Formatierungsobjekte entfernt und dann gelöscht.*/
for(Text te: text){
ObjectSet<Text> resultText = db.get(te);
while (resultText.hasNext()){
Text t = resultText.next();
ArrayList<Text> listText = new ArrayList<Text>();
listText.add(t);
Formatierung fo = new Formatierung(null, listText);
ObjectSet<Formatierung> resultFormat = db.get(fo);
310
14.2 Update-Tiefe im Embedded Modus
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
while (resultFormat.hasNext()){
form = resultFormat.next();
if(lockManager.lock(form)){
ArrayList<Text> formatText = form.getText();
formatText.remove(t);
form.setText(formatText);
db.set(form);
lockManager.unlock(form);
}
}
}
}
for(Text t: text){
db.delete(t);
}
db.delete(text);
/*Das Objekt w der Klasse WebSite wird gelöscht.*/
db.delete(w);
lockManager.unlock(w);
}
} catch (DatabaseFileLockedException e) {
db.rollback();
db.ext().refresh(form, Integer.MAX_VALUE);
db.ext().refresh(w, Integer.MAX_VALUE);
} finally{
db.commit();
}
}
}
Listing 14.12: DeleteWebSite.java
311
15 Anhang A: Threads und Multithreading
An dieser Stelle möchte ich einen Ausflug zu den Thread-Grundlagen machen, da dieses Wissen eine wichtige Voraussetzung für das Verständnis der Verwendung von Semaphores in db4o
darstellt, die im Kapitel Transaktionen besprochen werden. Zusätzlich werden Ihnen die Ausführungen im Kapitel zum Thema Client-Server-Modus von Nutzen sein.
Was sind Threads? Threads (deutsch: Fäden) sind Aktivitäten, wie z. B. Datenbankabfragen,
die vorgeben gleichzeitig abzulaufen, die sich aber tatsächlich in der Abarbeitung abwechseln. So
kann es sein, dass bei mehreren Threads, die gleichzeitig durchgeführt werden, zuerst der erste
zu einem drittel durchgeführt und dann der nächste zu einem drittel usw. So wird dem Benutzer
vorgegaukelt, dass Prozesse, wie z. B. Programme gleichzeitig ablaufen, was sie aber tatsächlich
nicht tun.
Theoretisch gibt es die Möglichkeit, die Reihenfolge der einzelnen Threads zu beeinflussen, indem Sie die Prioritäten der Threads festlegen. Dieses Verhalten ist aber plattformabhängig, so
haben Sie keinerlei Garantie, dass die Reihenfolge der Threads tatsächlich einem bestimmten
gewünschten Verhalten entspricht. Die Priorität können Sie mit setPriority() festlegen und es
gibt drei verschiedene Prioritäten, die Sie festlegen können: Die Standardpriorität liegt bei 5
(Thread.NORM_PRIORITY), die maximale bei 10 (Thread.MAX_PRIORITY) und die minimale bei 1 (Thread.MIN_PRIORITY).
Threads lassen sich synchronisieren, sprich es kann festgelegt werden, dass die Teilprozesse hintereinander ablaufen. Ein Thread wartet also bis der eine Thread fertig ist und läuft erst im
Anschluss.
15.1 Erstellung eines Threads
Wie wird ein Thread erstellt? Es gibt zwei Möglichkeiten: Entweder durch Implementierung des
Runnable Interface oder durch eine Vererbungsbeziehung mit der Klasse Thread.
15.1.1 Mithilfe des Runnable Interfaces
Das Interface Runnable ist Teil des Package java.lang und enthält eine Methode run(), die implementiert werden muss, wenn Sie einen Thread mithilfe dieses Interfaces erstellen wollen. Die
Instanziierung erfolgt in diesem Fall in 2 Schritten: Erstens wird eine Instanz der neuen Klasse
erstellt und zweitens wird diese einem neuen Threadobjekt als Parameter übergeben.
Wir erstellen also unseren ersten Thread mit dem Namen MeinFaden.java und nachdem wir ihn
gestartet haben, erhalten wir unten stehende Ausgabe:
1
2
3
4
5
6
7
package Kap08;
public class MeinFaden implements Runnable{
public static void main(String[] args){
/*Es wird eine Instanz der Klasse MeinFaden erstellt.*/
313
15 Anhang A: Threads und Multithreading
MeinFaden meinFaden = new MeinFaden();
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/*Es wird ein Threadobjekt erstellt, dem das Objekt
meinFaden als Parameter übergeben wird.*/
Thread t = new Thread(meinFaden);
/*Der so erstellte Thread wird gestartet.*/
t.start();
}
/*Die Methode run() muss implementiert werden.*/
public void run() {
System.out.println("Ich bin der Thread MeinFaden");
}
}
Listing 15.1: MeinFaden.java
Ausgabe:
Ich bin der Thread MeinFaden
Der Thread wird gestartet mit der Methode start(). Diese führt zuerst die Methode start() aus
und anschließend die Methode run(). Ein Thread kann nur einmal mit der Methode start()
gestartet werden. Wenden Sie die Methode run() an, wird nur die Methode run() durchgeführt
und kein Thread gestartet.
15.1.2 Mithilfe der Klasse Thread
Erstellen Sie einen neuen Thread mithilfe einer Vererbungsbeziehung zu der Klasse Thread,
müssen Sie nicht die Methode run() implementieren, sie dürfen sie aber überschreiben.
Unser nächster Thread heißt ZweiterThread.java und er gibt nachfolgenden Satz auf der Konsole
aus:
package Kap08;
1
2
3
4
5
6
7
8
9
10
11
12
public class ZweiterThread extends Thread{
public static void main(String[] args){
/*Der Thread wird instanziiert und*/
ZweiterThread zweiterThread = new ZweiterThread();
/*anschließend gestartet.*/
zweiterThread.start();
314
15.2 Synchronisation
13
14
15
16
17
18
19
20
21
}
/*Die Methode run() der Klasse Thread darf überschrieben
werden, sie muss es aber nicht.*/
public void run() {
System.out.println("Ich bin der Thread ZweiterThread");
}
}
Listing 15.2: ZweiterThread.java
Ausgabe:
Ich bin der Thread ZweiterThread
15.2 Synchronisation
Was verstehen wir unter Synchronisation? Synchronisation stellt sicher, dass Vorgänge hintereinander und nicht gleichzeitig und durcheinander ablaufen. Werden Prozesse synchronisiert, so
nennt man diesen Zustand threadsafe oder in der deutschen Übersetzung threadsicher.
15.2.1 Synchronisierte Blöcke
Wie synchronisiere ich einen Thread? Eine Möglichkeit ist es, einen ganzen Codeblock zu synchronisieren. Lassen Sie uns zuerst das Verhalten von Threads betrachten, die nicht synchronisiert
sind. Dies lässt sich anhand eines einfachen Beispiels demonstrieren: Wir erstellen in der forSchleife des unten stehenden Threads mit Namen NichtSynchro.java die zweimalige Ausgabe der
Zahlen von 1 – 199. Die beiden Threads laufen nicht hintereinander ab, sondern in willkürlicher
plattformabhängiger Reihenfolge, wie die Ausgabe zeigt:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package Kap08;
public class NichtSynchro extends Thread{
public static void main(String[] args){
NichtSynchro nichtSynchro = new NichtSynchro();
NichtSynchro nichtSynchro2 = new NichtSynchro();
nichtSynchro.start();
nichtSynchro2.start();
}
public void run(){
315
15 Anhang A: Threads und Multithreading
for(int i=0; i<200;i++){
System.out.println(i);
}
17
18
19
20
21
22
}
}
Listing 15.3: NichtSynchro.java
Ausgabe (Ausschnitt):
124
0
125
1
126
2
127
3
Wollen Sie, dass diese beiden Threads hintereinander ablaufen, benötigen Sie ein sogenanntes
LOCK: das
p r i v a t e s t a t i c O b j e c t LOCK =
new Objekt ( ) ;
Statische Bestandteile einer Klasse gibt es nur ein einziges Mal pro Klasse, wohingegen jeder
nicht statische Bestandteile einer Klasse, pro Objekt existiert. So wird ein statisches LOCK, da
es pro Klasse nur einmal existiert, von allen Objekten dieser Klasse geteilt. Alle Objekte der
Klasse haben also nur ein einziges LOCK, das sie sich teilen müssen. So kann auch immer nur ein
Objekt auf dieses LOCK zugreifen. Statische Variablen werden deshalb auch Klassenvariablen
genannt und nicht-statische Variablen Instanzvariablen, da es sie pro Instanz, also pro Objekt,
einmal gibt.
Im folgenden Thread mit Namen Synchro.java stellen wir sicher, dass jeweils immer nur ein
Thread den Block ausführen darf, in dem sich die for-Schleife befindet.
package Kap08;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Synchro extends Thread{
private static Object LOCK = new Object();
public static void main(String[] args){
Synchro synchro = new Synchro();
Synchro synchro2 = new Synchro();
synchro.start();
synchro2.start();
316
15.2 Synchronisation
}
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public void run(){
/*Wir synchronisieren den Ablauf der for-Schleife
mithilfe eines statischen LOCKs.*/
synchronized(LOCK){
for(int i=0; i<200;i++){
System.out.println(i);
}
}
}
}
Listing 15.4: Synchro.java
Ausgabe (Ausschnitt):
196
197
198
199
0
1
2
3
Wie die Ausgabe zeigt, funktioniert das Locken von Blöcken ohne Probleme. Die Threads werden
hintereinander abgearbeitet und es kommt zu einer Ausgabe von 1 – 199 zweimal hintereinander.
Was aber würde passieren, wenn Sie das LOCK durch this ersetzen? Es kommt wieder zu einer
nicht synchronisierten Ausgabe der Zahlen, da sich this auf das jeweilige Objekt bezieht und
nicht auf die Klasse. So besitzt jedes Objekt seine eigene Methode run() und jedes Objekt kann
problemlos darauf zugreifen. Es gibt also keinen gelockten Bereich.
Folgender Thread ThisSynchro.java und die dazugehörige Ausgabe zeigt, dass die for-Schleife
nicht hintereinander auflaufen:
1
2
3
4
5
6
7
8
9
10
package Kap08;
public class ThisSynchro extends Thread{
public static void main(String[] args){
ThisSynchro thisSynchro = new ThisSynchro();
ThisSynchro thisSynchro2 = new ThisSynchro();
thisSynchro.start();
317
15 Anhang A: Threads und Multithreading
thisSynchro2.start();
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
}
public void run(){
synchronized(this){
for(int i=0; i<200;i++){
System.out.println(i);
}
}
}
}
Listing 15.5: ThisSynchro.java
Ausgabe (Ausschnitt):
124
0
125
1
126
2
127
3
Wie werden in db4o Datenbankabfragen synchronisiert? Mithilfe von Semaphores. Semaphores
werden ausführlich im Kapitel Transaktionen besprochen.
318
16 Anhang B: Eine kurze Einführung in Groovy
16.1 Einführung
Zu Beginn einige Bemerkungen zu Groovy: Das Groovy-Projekt hat zum Ziel die Java-Syntax
zu vereinfachen, wobei hierfür Ruby on Rails als Vorbild dient. Die Vereinfachung hat auch zur
Folge, dass der gesamte Code viel kürzer wird. Die Groovy-Klassen werden beim kompilieren
in Java-Bytecode umgewandelt, sprich es entstehen ganz normale Java-Klassen. Groovy-Klassen
enden nicht mit .java, sondern mit .groovy. In Groovy-Klassen kann beliebig Java-Code geschrieben werden. Meiner zweijährigen Erfahrung mit Groovy nach gibt es nur drei Probleme, über
die man stolpern könnte: Das Erste taucht auf, wenn man lange Codezeilen auf zwei Zeilen verteilen will, dies ist in Groovy nur bedingt möglich. Über das zweite Problem stolpern Sie, wenn
Sie anonyme Klassen in Groovy verwenden wollen. Anonyme Klassen sind in Groovy Closures
und haben eine andere Syntax. Das dritte Problem bezieht sich auf die Syntax von Arrays, hier
müssen Sie auf jeden Fall die Java-Syntax in Bezug auf Groovy anpassen.
Die folgenden Ausführungen setzen Java-Grundkenntnisse voraus.
16.1.1 Groovy-Klassen in NetBeans-Projekten
Wie erstellen Sie in NetBeans eine Groovy-Klasse? Sie können unter New File statt einer JavaKlasse eine Groovy-Klasse auswählen. Soweit nichts Kompliziertes. In einem normalen Javaprojekt können Sie auch problemlos den Befehl Run ausführen. Kompliziert wird es in einem
Grailsprojekt: Hier steht der Befehl Run nicht zur Verfügung. Es gibt einen Workaround: Man
muss die GroovyConsole starten. Gehen Sie in das Register Project und klicken Sie das entsprechende Projekt mit der rechten Maustaste an und wählen den Punkt Run/Debug Grails
Command aus.
Abbildung 16.1: Wählen Sie Run/Debug Grails Command
Es geht ein Fenster auf, in dem Sie console auswählen können:
319
16 Anhang B: Eine kurze Einführung in Groovy
Abbildung 16.2: Wählen Sie console aus
Sobald Sie auf Run geklickt haben, geht die GroovyConsole auf.
Abbildung 16.3: GroovyConsole
320
16.2 Klassen
16.1.2 Was kann in Groovy weggelassen werden?
Ein wichtiger Aspekt der Vereinfachung in Groovy ist die Verkürzung von Programmiercode. In
Groovy kann folgendes weggelassen werden:
• Der Strichpunkt am Ende einer Befehlszeile.
• Bei einer Methode mit Rückgabewert das return.
• Folgende imports: java.lang.*, java.util.*, java.net.*, java.io.*, groovy.lang.*, groovy.util.*
• Klassen müssen nicht als public deklariert werden, da sie dies bereits implizit sind.
• Attribute in Klassen müssen nicht als private deklariert werden, da sie dies bereits implizit
sind.
16.2 Klassen
Beginnen wir mit der Syntax von Klassen in Groovy. Wir vergleichen sie mit der entprechenden
Syntax in Java. Eine Klasse in Java benötigt neben Getter- und Setter-Methoden meist auch
Konstruktoren mit Parametern:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package groovyforbeginners.Klassen.Java;
public class Book {
private String title;
private String author;
public Book(){}
public Book(String title){
this.title = title;
}
public Book(String title, String name){
this.title = title;
this.author = name;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getName() {
return author;
321
16 Anhang B: Eine kurze Einführung in Groovy
}
29
30
31
32
33
34
public void setName(String name) {
this.author = name;
}
}
Listing 16.1: Book.java
Was ändert sich an dieser Klasse in Groovy? Wie sieht die gleiche Klasse in Groovy aus? Wie
folgt:
package groovyforbeginners.Klassen.Groovy
1
2
3
4
5
6
7
class Book {
String title
String author
}
Listing 16.2: Book.groovy
Sie fragen sich sicherlich, wo sind die Getter- und Setter-Methoden, Konstruktoren und Zugriffsbezeichner geblieben? Sie werden in Groovy für Sie automatisch erstellt. Klassen sind implizit
public und Attribute sind implizit private. Anhand von unten stehendem Beispiel soll gezeigt
werden, wie man Werte zuweisen bzw. auslesen und wie man Objekte mit Konstruktoren instanziieren kann.
package groovyforbeginners.Klassen.Groovy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class BookExample {
static void main(args){
Book book = new Book();
/*Der Titel wird in Groovy zugewiesen und wieder ausgelesen.*/
book.title = "Ich bin ein Buch in Groovy"
println book.title
/*Der Titel wird in Java zugewiesen und wieder ausgelesen.*/
book.setTitle("Ich bin ein Buch in Java");
System.out.println(book.getTitle());
/*In Groovy kann auf Konstruktoren zugegriffen werden, die
*nur implizit existieren.*/
Book bookZwei = new Book(title: "Ich bin ein Buch mit Konstruktoraufruf")
Book bookDrei = new Book(title: "BuchDrei", author: "Ina")
322
16.3 Dynamische Typisierung
21
22
23
24
25
println bookZwei.title + " " + bookDrei.title + " " + bookDrei.author
}
}
Listing 16.3: BookExample.groovy
Ausgabe:
Ich bin ein Buch in Groovy
Ich bin ein Buch in Java
Ich bin ein Buch mit Konstruktoraufruf BuchDrei Ina
16.3 Dynamische Typisierung
Noch ein neues Konzept: die dynamische Typisierung. Ich persönlich finde es manchmal sehr
verwirrend, wenn statt einem konkreten Rückgabetyp, wie z.B. einem String, ein def steht. Wenn
man def schreibt, muss man nicht festlegen bzw. auch nicht wissen, welchen Rückgabetyp eine
Methode hat. Es kann aber manchmal passieren, dass man es trotzdem wissen sollte, besonders,
wenn man fremden Code lesen oder ergänzen muss. Der Rückgabetyp wird von Groovy erst zur
Laufzeit festgelegt, so genügt ein def, anstelle eines expliziten Rückgabetyps.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package groovyforbeginners.DynamischeTypisierung
import groovyforbeginners.Klassen.Groovy.Book
class DynamischeRueckgabetypen {
static void main(args){
Book book = new Book(author: "Ina");
/*Dynamischer Rueckgabetyp in Groovy.*/
def author = book.author
/*Rueckgabetyp String in klassischem Java.*/
String authorJava = book.getAuthor();
}
}
Listing 16.4: DynamischeRueckgabetypen.groovy
Parameter, die einer Methode übergeben werden, müssen auch nicht mit einem konkreten Typ
versehen werden. Sie können auch mit def dynamisch typisiert werden.
323
16 Anhang B: Eine kurze Einführung in Groovy
16.4 Arrays
Wie bereits oben erwähnt, unterscheidet sich die Syntax bei der Initialisierung von Arrays gegenüber Java. Sie müssen die geschweiften Klammern gegen eckige Klammern austauschen. Des
Weiteren gibt es in Groovy ein For-Each-Schleife. Ansonsten können Sie auf Array-Elemente wie
gewohnt zugreifen.
package groovyforbeginners.Arrays
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class FirstArray {
static void main(args) {
/*Deklaration und Initialisierung eines Arrays mit Groovy-Syntax*/
int [] erstesArray = [1, 2, 3]
/*Die entsprechende Syntax mit Java.
int [] erstesArray = {1,2,3};*/
/*Ein Array wird mithilfe einer For-Each-Schleife in Groovy ausgelesen.*/
erstesArray.each{
elemente ->
println elemente
}
/*Auf einzelne Elemente wird nach wie vor mit der Indexposition
*zugegriffen.*/
println "Dies ist das Element an der Stelle O: " + erstesArray[0]
}
}
Listing 16.5: FirstArray.groovy
Ausgabe:
1
2
3
Dies ist das Element an der Stelle O: 1
16.5 Das Collections-Framework
Das Collections-Framework umfasst Klassen, die Ihnen komfortable Methoden zur Verfügung
stellen: sowohl zum Speichern als auch zum Ein- und Auslesen von mehreren Objekten oder
mehreren primitiven Datentypen. Im folgenden wollen wir insbesondere auf die Verwendung von
Listen und Maps eingehen.
324
16.5 Das Collections-Framework
16.5.1 Listen
Listen haben wie Arrays Elemente mit Index. Im Unterschied zu Arrays haben aber Listen keine von Anfang an vorgegebene Größe, sondern können vom Inhalt her beliebig wachsen oder
schrumpfen. Wir nehmen die ArrayList als Beispiel, da sie - aufgrund eines sehr schnellen wahlfreien Zugriffs auf die enthaltenen Elemente einer ArrayList - die beste Implementierung aller
Listen ist. Die Deklaration und Initialisierung wurde gegenüber Java vereinfacht und ist jetzt
fast identisch mit dem gleichen Vorgang bei Arrays. Mit dem kleinen, aber feinen Unterschied,
dass sich der Rückgabetyp unterscheidet. Bei einer dynamischen Typisierung würde man keinen
Unterschied erkennen.
Besonders hervorzuheben ist noch die Methode sort(). Das Sortieren einer ArrayList ist in Java sehr viel komplizierter und nur mithilfe einer Implementierung des Comparator-Interfaces
möglich.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package groovyforbeginners.CollectionFramework
class FirstArrayList {
static void main(args) {
/*Deklaration und Initialisierung einer ArrayList mit Groovy-Syntax*/
ArrayList ersteArrayList = [5, 2, 1]
/*Wie in Java koennen mithilfe der Methode add() der ArrayList
*Elemente hinzugefuegt werden.*/
ersteArrayList.add(4);
/*Die Methode sort() sortiert den Inhalt der ArrayList aufsteigend*/
ersteArrayList.sort();
/*Eine ArrayList wird mithilfe einer For-Each-Schleife in Groovy ausgelesen.*/
ersteArrayList.each{
elemente ->
println elemente
}
/*Die ArrayList wird mit der Methode join() in einen String umgewandelt,
*der als Trennzeichen zwischen jedem einzelnen Element einen ";" besitzt.*/
String einString = ersteArrayList.join(";");
println einString
/*Auf einzelne Elemente kann im Gegensatz zu Java mit einer eckigen Klammer
*und der Indexposition zugegriffen werden.*/
println "Dies ist das Element an der Stelle O: " + ersteArrayList[0]
}
}
325
16 Anhang B: Eine kurze Einführung in Groovy
Listing 16.6: FirstArrayList.groovy
Ausgabe:
1
2
4
5
1;2;4;5
Dies ist das Element an der Stelle O: 1
16.5.2 Maps
Maps bestehen aus einem Wert und einem Schlüssel. Ich habe die Klasse LinkedHashMap ausgewählt, da bei ihr im Gegensatz zu der Klasse HashMap, die Elemente in der gleichen Reihenfolge
ein- und wieder ausgelesen werden. Auch für Maps gibt es in Groovy eine vereinfachte Syntax
für viele Operationen:
package groovyforbeginners.CollectionFramework
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class FirstLinkedHashMap {
static void main(args) {
/*Deklaration und Initialisierung einer LinkedHashMap mit Groovy-Syntax*/
LinkedHashMap buch = [’Autor’: ’ina’, ’Titel’: ’Java’ ]
/*Hinzufuegen eines Elements in Java-Syntax*/
buch.put("Verlag", "Eigenverlag")
/*Hinzufuegen eines Elementes in Groovy-Syntax*/
buch["Seiten"] = "233"
/*Die Groovy For-Each-Schleife liest die Schluessel und die Werte aus:*/
buch.each{
elemente ->
println elemente.key + " " + elemente.value
}
/*Direkter Zugriff auf einen Wert der LinkedHashMap in Groovy.*/
println buch["Autor"]
}
}
Listing 16.7: FirstLinkedHashMap.groovy
326
16.6 Closures
Ausgabe:
Autor ina
Titel Java
Verlag Eigenverlag
Seiten 233
ina
16.6 Closures
Closures entsprechen in Java den anonymen inneren Klassen. Innere Klassen sind Klassen, die
sich innerhalb einer anderen Klasse befinden. Es sind also Klassen, die ineinander geschachtelt
sind. Anonyme innere Klassen sind eine besondere Form von inneren Klassen. Bei anonymen
Klassen werden zwei Vorgänge gleichzeitig ausgeführt: Eine Klasse ohne Namen wird erstellt und
gleichzeitig wird diese Klasse instanziiert. Beim Kompilieren wird eine Closure in eine innere
Klasse umgewandelt und es entsteht eine zusätzliche Klasse:
Abbildung 16.4: Kompilierte Closures
In Java werden anonyme innere Klassen hauptsächlich bei der Programmierung von graphischen
Benutzeroberflächen verwendet. In Groovy werden sie viel häufiger eingesetzt: z.B. bei der ForEach-Schleife und bei der Erzeugung von XML (s.u.).
Wir haben bereits Closures und ihre Verwendung kennengelernt: bei der For-Each-Schleife. Die
Syntax ist einfach: Funktionalitäten, wie das Auslesen eines Arrays, werden zwischen zwei geschweifte Klammern gesetzt.
1
2
3
4
5
package groovyforbeginners.Closures
class SimpleExample {
static void main(args) {
[1,2,2].each{
327
16 Anhang B: Eine kurze Einführung in Groovy
element->
println element
6
7
8
9
10
11
}
}
}
Listing 16.8: SimpleExample.groovy
Ausgabe:
1
2
2
Sie können einer Closure auch eigenen Inhalt zuweisen, dann hat eine Closure die Funktion einer
Methode. Es gibt auch die Möglichkeit der Closure - wie bei einer Methode - Parameter zu
übergeben. Auf diesen Parameter können Sie innerhalb der Closure mithilfe von it zugreifen.
package groovyforbeginners.Closures
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Closures {
def ausgabe = {println "Hello World"}
def ausgabeMitParameter = {println "Hello ${it}"}
static void main(args) {
Closures closures = new Closures()
closures.ausgabe()
closures.ausgabeMitParameter("ina")
}
}
Listing 16.9: Closures.groovy
Ausgabe:
Hello World
Hello ina
Diese Art und Weise Closures zu benutzen, finden wir in Grails wieder. In Controllern werden
Closures als so genannte Actions verwendet.
In Groovy ist es möglich mithilfe eines GStrings den übergebenen Parameter in einen String
einzubauen. Der String wird dadurch in einen GString umgewandelt. Die Syntax lautet:
" H e l l o $ { i t }"
328
16.6 Closures
In Java ist diese Syntax z.B. bei der Verwendung von JSPs möglich. Ansonsten müssen Sie auf
reguläre Konkatenation zurückgreifen:
" Hello " + i t
Eine weitere Funktionalität, die mit mithilfe von Closures zur Verfügung gestellt wird: Es kann
in eine Methode eingedrungen und deren Inhalt nachträglich verändert werden. So kann der ArrayList in unten stehendem Beispiel ein weiteres Element hinzugefügt werden:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package groovyforbeginners.Closures
class FirstClosure {
/*Der Methode readList() wird als Parameter eine Closure uebergeben.*/
void readList(Closure closure){
/*Eine ArrayList wird instanziiert und es werden ihr zwei Elemente
*hinzugefuegt.*/
ArrayList arrayList = new ArrayList();
arrayList.add("Hallo");
arrayList.add("Good-Bye");
/*Der Closure wird als Parameter, die soeben erstellte und mit
*Inhalt gefuellte arrayList uebergeben.*/
closure(arrayList);
}
static void main(args) {
FirstClosure firstClosure = new FirstClosure();
/*Innerhalb der Methode kann deshalb auf die arrayList innerhalb der
*Methode zugegriffen werden. */
firstClosure.readList(){arrayList ->
/*Der ArrayList wird ein weiteres Element hinzugefuegt und
*anschliessend werden alle Elemente ausgelesen.*/
arrayList.add("Guten Tag");
arrayList.each{entry ->
println(entry)
}
}
}
}
Listing 16.10: FirstClosure.groovy
329
16 Anhang B: Eine kurze Einführung in Groovy
Ausgabe:
Hallo
Good-Bye
Guten Tag
16.7 Reguläre Ausdrücke
Mit regulären Ausdrücken kann nach bestimmten Mustern oder exakten Entsprechungen gesucht
werden. Reguläre Ausdrücke sind unverzichtbar bei der Validierung von Datentypen. Wollen Sie
z.B. wissen, ob ein Benutzer eine korrekte E-Mail-Adresse eingegeben hat, die ein @-Zeichen
enthält, so können Sie dies mit einem regulären Ausdruck feststellen. In Java finden Sie die
entsprechenden Methoden in den Klassen Pattern und Matcher des Packages java.util.regex. In
Groovy gibt es folgende Ausdrücke dafür:
• =~ sucht nach einer Entsprechung und es darf zusätzlicher Text enthalten sein. In diesem
Fall wird true zurückgegeben.
• ==~ sucht nach einer exakten Entsprechung. Der Ausdruck gibt true zurück, wenn der
String, der mit dem Muster verglichen wird, zu 100% dem Muster entspricht und kein
zusätzlicher Text enthalten ist.
Außerdem muß in Java jeder einzelne reguläre Ausdruck mit einem zusätzlichen Backslash versehen werden: „ \\s+ “. Dies ist in Groovy nicht notwendig, stattdessen muss der gesamte zusammengesetzte reguläre Ausdruck mit einem Schrägstrich begonnen und beendet werden. In nach
stehendem Listing finden Sie einige Beispiele:
package groovyforbeginners.RegularExpressions
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class RegularExpressions {
static void main(args) {
/* =~ sucht nach einer Entsprechung. Der String muss eine Uhrzeit enthalten,
*die aus 4 Ziffern besteht, die durch einen Doppelpunkt getrennt sind.
*Es wird true zurueckgegeben, da er eine Entsprechung findet. */
boolean uhrzeitString = "Es ist 15:00" =~ /(\d\d):(\d\d)/
println uhrzeitString
/* Es wird nach einem Doppelpunkt gesucht, wenn er einen Doppelpunkt
* findet, gibt er true aus. Der String darf zusaetzlichen Text enthalten.
* Der Ausdruck nach dem =~ kann auch nur ein
* Zeichen enthalten, es muss kein regulaerer Ausdruck sein.*/
boolean uhrzeit = ’15:01’ =~ /:/
println uhrzeit;
/* ==~ erwartet eine exakte Entsprechung fuer das entsprechende Format.
330
16.7 Reguläre Ausdrücke
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
* Sprich das Format darf nicht wie folgt aussehen: 02. Februar 2005, sondern
* darf nur aus jeweils 2 Ziffern bestehen, die jeweils durch einen Punkt
* getrennt sind.*/
boolean datum = ’02.02.05’==~ /(\d\d)\.(\d\d)\.(\d\d)/
println datum;
/*Ausserdem darf bei dem Ausdruck ==~ kein zusaetzlicher Text enthalten
*sein, da sonst false ausgegeben wird.*/
boolean datumString = ’Dies ist ein Datum 02.02.05’==~ /(\d\d)\.(\d\d)\.(\d\d)/
println datumString
/*Der Ausdruck /\s+/* sucht nach mehreren hintereinander kommenden
*Leerzeichen und die Methode replaceAll() ersetzt jede Entsprechung
*durch ein einziges Leerzeichen.*/
String string = "hallo
hallo
hallo"
string = string.replaceAll(/\s+/, " ");
println string
}
}
Listing 16.11: RegularExpressions.groovy
Ausgabe:
true
true
true
false
hallo hallo hallo
Sie haben sich sicherlich schon gefragt, warum vor dem Punkt ein Escape-Zeichen steht und vor
dem Doppelpunkt nicht? Ein Punkt ohne Escape-Zeichen hat eine andere Bedeutung wie ein
Punkt mit Escape-Zeichen, wie unten stehende Aufzählung zeigt. Ein Punkt würde bedeuten,
dass jedes Zeichen erlaubt ist und erst ein Punkt mit einem Escape-Zeichen schreibt in einem
Muster einen Punkt vor.
Dies waren ein paar Beispiele, wie reguläre Ausdrücke in Groovy verwendet werden. Die Syntax
wurde gegenüber Java stark vereinfacht. Zum Schluß noch eine Tabelle, die die Wichtigsten
aufzählt:
331
16 Anhang B: Eine kurze Einführung in Groovy
Tabelle 16.1: Übersicht über die wichtigsten regulären Ausdrücke in Groovy
Regulärer Ausdruck
Bedeutung
\d
Ziffer
\D
Jedes Zeichen, außer Ziffern
\s
Leerzeichen
\S
Alle Zeichen, außer Leerzeichen
\w
Buchstaben
\W
Alle Zeichen, außer Buchstaben
.
Jedes Zeichen
()
Gruppierung
+
ein oder mehrere vom gleichen Zeichen
*
kein oder mehrere vom gleichen Zeichen
?
kein oder ein Zeichen
16.8 Ein- und Ausgabe von Dateien
So genannte Zeichenströme machen es möglich, Text in einer Datei zu speichern und wieder
auszulesen. Wir erstellen das folgende Dokument input.txt, das als Grundlage für unser Beispiel
dienen soll.
Zeile 1
Zeile 2
Zeile 3
1
2
3
Listing 16.12: input.txt
In unten stehendem Beispiel lesen wir mit Hilfe der Klasse PrintWriter dieses Dokument sowohl
auf der Konsole als auch in ein neues Dokument aus. Die Klasse PrintWriter gibt es auch in Java,
aber in Groovy wurde die Syntax stark vereinfacht. Mit der Methode newPrintWriter() wird ein
Buffer geöffnet, der mit der Methode close() wieder geschlossen wird. In diesem Buffer wird der
Text zwischengespeichert, der anschliessend in das Dokument output.txt mit writer.println(line)
geschrieben wird. Würde der Buffer nicht geschlossen werden, wäre das Dokument output.txt leer.
package groovyforbeginners.InputOutput
1
2
3
4
5
class AusUndEinlesen {
static void main(args) {
332
16.9 Groovy und XML
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/*Das Dokument input.txt wird ausgelesen*/
File file = new File("src/groovyforbeginners/InputOutput/input.txt");
/*In das Dokument output.txt wird der Inhalt vom Textfile input.txt
*eingelesen*/
File fileNew = new File("src/groovyforbeginners/InputOutput/output.txt");
/*Die KlassePrintWriter macht es moeglich Textdateien aus- und einzulesen.*/
PrintWriter writer = fileNew.newPrintWriter();
/*Die Methode eachLine() liest das Dokument aus.*/
file.eachLine{
line ->
/*Der Inhalt wird auf der Konsole ausgegeben.*/
println(line)
/*Der Inhalt wird in das Dokument output.txt ausgelesen.*/
writer.println(line);
}
/*Da bis jetzt alles nur in den Puffer geschrieben worden ist,
*muss dieser wieder geschlossen werden.*/
writer.close()
}
}
Listing 16.13: AusUndEinlesen.groovy
Wir schreiben den Inhalt in ein Dokument output.txt und geben es auf der Konsole aus. Es wird
eine neue Textdatei erstellt oder es wird eine vorhandene output.txt überschrieben.
1
2
3
Zeile 1
Zeile 2
Zeile 3
Listing 16.14: output.txt
16.9 Groovy und XML
Sollte Sie die Syntax von Groovy bis zu diesem Zeitpunkt nicht überzeugt haben, wird es dieses Kapitel. XML-Verarbeitung ist mit Java furchtbar kompliziert und mit Groovy schrecklich
einfach :-). Das wichtigste Konzept hierbei sind die Builder, insbesondere der StreamingMarkupBuilder. Ich werde mich nicht lange mit der Theorie aufhalten. Am besten lässt sich der
StreamingMarkupBuilder anhand von Beispielen näher bringen.
333
16 Anhang B: Eine kurze Einführung in Groovy
16.9.1 Eine einfache XML-Datei
Eine XML-Datei besteht aus Tags, die korrekt ineinander verschachtelt werden. Jedes Start-Tag
<MeinTag> muss mit dem entsprechenden End-Tag </MeintTag> beendet werden. Werden
diese beiden Bedingungen erfüllt, spricht man von einem wohlgeformten XML-Dokument. Es ist
wie bei den russischen Matrjoschka-Puppen, wo beim Öffnen einer Puppe jeweils eine kleinere
Puppe zum Vorschein kommt. So kann ein Element, jeweils ein anderes Element enthalten.
Beginnen wir mit unserem wohlbekannten Beispiel einer FormatierungEinfach-XML-Datei, die
als einziges Element, den Namen einer Formatierung enthält. Die Elemente werden mithilfe von
Closures und den Namen der Elemente erzeugt. Es wird außerdem ein Pfad zu einer Schema-Datei
festgelegt. Weiter unten sehen wir wie eine Schema-Datei angelegt wird und wozu sie gebraucht
wird.
package groovyforbeginners.GroovyUndXML.SimpleExample
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class CreateFormatierungEinfachXML {
static void main(args) {
/*Eine neue XML-Datei wird erstellt.*/
File fileNew =
new File("src/groovyforbeginners/GroovyUndXML/SimpleExample/FormatierungEinfach.xml");
/*Der Kopf der XML-Datei wird mithilfe des StreamingMarkupBuilders erzeugt.*/
def builder = new groovy.xml.StreamingMarkupBuilder()
builder.encoding = "ISO_8859-1"
def FormatierungEinfachListe = {
mkp.xmlDeclaration()
mkp.declareNamespace(’xsi’ :’http://www.w3.org/2001/XMLSchema-instance’ )
/*Es wird der Pfad zur Schema-Datei mit Namen Schema.xsd festgelegt.*/
FormatierungEinfachListe(’xsi:noNamespaceSchemaLocation’:’Schema.xsd’ ){
/*Es wird der Tag FormatierungEinfach mit dem Attribut Id erstellt.*/
FormatierungEinfach(Id:1){
/*Dieser Tag enthaelt einen Tag mit Namen name und den Inhalt font1*/
name("font1")
}
}
}
/*Mit der Methode bind() wird die FormatierungEinfachListe an die XML-Datei
*gebunden.*/
def writer = new FileWriter(fileNew);
writer << builder.bind(FormatierungEinfachListe)
writer.close();
System.out << builder.bind(FormatierungEinfachListe)
}
334
16.9 Groovy und XML
37
}
Listing 16.15: CreateFormatierungEinfachXML.groovy
Vergessen Sie nicht die Methode writer.close() am Ende durchzuführen, das dies zur Folge hätte,
dass Ihr Dokumnet leer wäre. Als Ergebnis erhalten wir folgende XML-Datei:
1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="ISO_8859-1"?>
<FormatierungEinfachListe xsi:noNamespaceSchemaLocation=’Schema.xsd’
xmlns:xsi=’http://www.w3.org/2001/XMLSchema-instance’>
<FormatierungEinfach Id=’1’>
<name>font1</name>
</FormatierungEinfach>
</FormatierungEinfachListe>
Listing 16.16: FormatierungEinfach.xml
Ich habe die Id als Attribut festgelegt, um zu zeigen, dass es durchaus möglich ist, dies zu tun.
Es spricht, aber sicherlich nichts dagegen, die Id als Element anzulegen.
Variablen beim Erstellen einer XML-Datei
Ich wollte Sie noch auf einen Stolperstein hinweisen, der nicht auf den ersten Blick ersichtlich ist.
Wollen Sie z.B. die Werte, die Sie als Elemente für die XML-Datei festlegen aus einer anderen
XML-Datei oder aus einer Methode in eine Variable auslesen, erhalten Sie eine Fehlermeldung.
Warum? Groovy erwartet an dieser Stelle ein Objekt vom Typ GString. Die abstrakte Klasse
GString ist eine Besonderheit in Groovy und stellt eine Art String-Klasse dar. Wie wandele ich
einen String in einen GString um? Indem ich die Variable formatierungEinfachVariable wie folgt
abändere:
" $ { f o r m a t i e r u n g E i n f a c h V a r i a b l e }"
Das Beispiel sieht dann wie folgt aus:
1
2
3
4
5
6
7
8
9
10
11
12
13
package groovyforbeginners.GroovyUndXML.XMLUndVariablen
class XMLVariablen {
static void main(args) {
/*Wir uebergeben den Namen eines XML-Elementes einer String-Variablen*/
String formatierungEinfachVariable = new String("FormatierungEinfach")
File fileNew =
new File("src/groovyforbeginners/GroovyUndXML/XMLUndVariablen/FormatierungEinfach.xml");
/*Der Kopf der XML-Datei wird mithilfe des StreamingMarkupBuilders erzeugt.*/
335
16 Anhang B: Eine kurze Einführung in Groovy
def builder = new groovy.xml.StreamingMarkupBuilder()
builder.encoding = "ISO_8859-1"
def FormatierungEinfachListe = {
mkp.xmlDeclaration()
mkp.declareNamespace(’xsi’ :’http://www.w3.org/2001/XMLSchema-instance’ )
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
FormatierungEinfachListe(’xsi:noNamespaceSchemaLocation’:’Schema.xsd’ ){
/*Der Knoten in einem Builder darf nicht vom Typ String sein. In
*diesem Fall wird folgende Fehlermeldung ausgegeben:
*groovy.lang.MissingMethodException: No signature of method:
*java.lang.String.call()
formatierungEinfachVariable{*/
/*Der erwartete Datentyp fuer einen Knoten in einem Builder ist
*vom Typ GString, die Variable vom Typ String wird wie folgt
*in einen GString umgewandelt:*/
"${formatierungEinfachVariable}"{
name("font1")
}
}
}
def writer = new FileWriter(fileNew);
writer << builder.bind(FormatierungEinfachListe)
writer.close();
System.out << builder.bind(FormatierungEinfachListe)
}
}
Listing 16.17: XMLVariablen.groovy
Sie können auf diese Art und Weise auch ein Objekt vom Typ GPathResult in eine GString
umwandeln. GPathResult ist der Rückgabetyp der Methode parse() der Klasse XMLSlurper, mit
der Sie Daten aus einer XML-Datei in Groovy auslesen können.
16.9.2 Auslesen von Werten aus einer XML-Datei
Wie können Sie Daten aus einer XML-Datei auslesen? Die Klasse XMLSlurper und die darin
enthaltene Methode parse() macht es möglich. Wir nehmen unten stehende Liste von Formatierungen:
<?xml version="1.0" encoding="ISO_8859-1"?>
<FormatierungEinfachListe xsi:noNamespaceSchemaLocation=’Schema.xsd’
xmlns:xsi=’http://www.w3.org/2001/XMLSchema-instance’>
1
2
3
4
5
6
7
8
<FormatierungEinfach Id=’1’>
<name>font1</name>
</FormatierungEinfach>
336
16.9 Groovy und XML
9
10
11
12
13
14
15
16
17
<FormatierungEinfach Id=’2’>
<name>font2</name>
</FormatierungEinfach>
<FormatierungEinfach Id=’3’>
<name>font3</name>
</FormatierungEinfach>
</FormatierungEinfachListe>
Listing 16.18: FormatierungEinfachListe.xml
Und lesen sie aus. Wie bereits weiter oben erwähnt, ist der Rückgabetyp beim Auslesen vom Typ
GPathResult. Mit der Methode children() kann man auf die Kindelemente eines XML-Elementes
zugreifen. Mit der Methode name() kann man den Namen eines Elementes auslesen und mit der
Methode text() den Inhalt desselben. Vorsicht: Unser Element name, von dem wir den Namen und
den Inhalt auslesen, hat zufälligerweise den Namen name, somit taucht name in unten stehendem
Beispiel zweimal auf.
Außerdem können Sie ähnlich wie bei einem Array mit dem Index, auf die verschiedenen Elemente
zugreifen, wobei auch hier das Element 0 das Erste ist. Das @-Zeichen vor dem Attributnamen
macht es Ihnen außerdem möglich, den Inhalt eines Attributes auszulesen.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package groovyforbeginners.GroovyUndXML.XMLAuslesen
import groovy.util.slurpersupport.GPathResult
class XMLParsen {
static void main(args) {
/*Der Pfad zu einem XML-Dokument wird hergestellt.*/
def file =
new File("src/groovyforbeginners/GroovyUndXML/XMLAuslesen/FormatierungEinfachListe.xml");
/*Mithilfe der Methode parse() der Klasse XmlSlurper koennen Sie die
Elemente einer XML-Datei auslesen.*/
GPathResult formatierungFile = new XmlSlurper().parse(file)
/*Wir lesen die Formatierung mit dem Attribut Id 2 aus.*/
println formatierungFile.FormatierungEinfach.find{it[’@Id’] == ’2’}
/*Wir lesen das Attribut Id des ersten Elementes aus.*/
println formatierungFile.FormatierungEinfach[0].@Id
/*Die Methode size() liest die Anzahl der FormatierungEinfach-Elemente
*aus.*/
println "Anzahl: "+ formatierungFile.FormatierungEinfach.size()
/*Folgende For-Each-Schleife liest die Kindelemente der
337
16 Anhang B: Eine kurze Einführung in Groovy
*XML-Datei aus.*/
formatierungFile.children().each{
formatierungEinfach ->
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/*Die Methode name() liest den Namen des Elementes name aus, das sich
*innerhalb des Elementes FormatierungEinfach befindet.*/
println formatierungEinfach.name.name()
/*Die Methode text() liest den Inhalt des Elementes name aus, das sich
*innerhalb des Elementes FormatierungEinfach befindet.*/
println formatierungEinfach.name.text()
}
}
}
Listing 16.19: XMLParsen.groovy
Ausgabe:
font2
1
Anzahl: 3
name
font1
name
font2
name
font3
16.9.3 XML-Schema
Wozu braucht man eine XML-Schema-Datei? Eine Schema-Datei validiert eine XML-Datei. Was
bedeudet dies konkret? Es werden in der Schema-Datei Regeln aufgestellt, welche Elemente eine
XML-Datei besitzt, wie die Elemente ineinander verschachtelt werden und welchen Datentyp sie
haben. Elemente, wie z.B. das Datum, können aufgrund ihres Datentyps auf korrekte Eingabe
überprüft werden. Kommt es bei der Validierung zu keiner Fehlermeldung, ist das Dokument
gültig.
In einer Schema-Datei werden Tags mit xs:element angelegt. Der xs:complexType ermöglicht es
Ihnen, in ein Element, jeweils ein Neues oder mehrere Neue zu schachteln. Diese Elemente werden
als xs:sequence aufgezählt. Außerdem legt man innerhalb des xs:complexType auch ein Attribut
mit xs:attribute fest.
package groovyforbeginners.GroovyUndXML.SimpleExample
1
2
3
4
5
class CreateFormatierungEinfachSchema {
static void main(args) {
338
16.9 Groovy und XML
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
def builder = new groovy.xml.StreamingMarkupBuilder()
builder.encoding = "ISO_8859-1"
def FormatierungEinfachListe = {
mkp.xmlDeclaration()
mkp.declareNamespace(’xs’ :’http://www.w3.org/2001/XMLSchema’ )
xs.schema(’elementFormDefault’ :’qualified’){
/*In der Datei FormatierungEinfach.xml ist das erste Element,
*der Tag mit Namen FormatierungEinfachListe*/
xs.element( name:’FormatierungEinfachListe’){
/*Enthaelt ein Element noch ein Element muss man einen complexType()
*erstellen, der wiederum eine Anzahl (sequence) von Elementen
*enthalten kann.*/
xs.complexType() {
xs.sequence {
/*Da, das FormatierungEinfach-Tag wieder ein Element einschliesst,
*benoetigen wir eine Referenz zu dem complexType mit Namen Detail.*/
xs.element(name:’FormatierungEinfach’, type:’Detail’,
minOccurs:’0’, maxOccurs:’unbounded’)
}
}
}
/*Der complexType mit Namen Detail besteht aus einem Element, das den Namen
*name hat.*/
xs.complexType(name: ’Detail’) {
xs.sequence {
/*Die Daten, die in dieses Feld eingefuegt werden, muessen vom Typ
*String sein.*/
xs.element(name:’name’, type:’xs:string’)
}
/*Wir legen die Id als Attribut fest.*/
xs.attribute(name:’Id’)
}
}
}
def outFile =
new File("src/groovyforbeginners/GroovyUndXML/SimpleExample/Schema.xsd");
def writer = new FileWriter(outFile);
writer << builder.bind(FormatierungEinfachListe)
System.out << builder.bind(FormatierungEinfachListe)
writer.close()
339
16 Anhang B: Eine kurze Einführung in Groovy
}
55
56
}
Listing 16.20: CreateFormatierungEinfachSchema.groovy
Jetzt bleibt noch zu klären was
minOccurs : ’ 0 ’ , maxOccurs : ’ unbounded ’
bedeutet. Hiermit legen Sie fest, dass ein Element O mal oder beliebig oft vorkommen darf. Würde dies dort nicht stehen, geht XML-Schema standardmäßig von nur einem einzigen Element aus.
Beim Validieren würde eine Fehlermeldung geworfen werden, falls es zwei FormatierungEinfachTags geben würde.
Als Ergebnis erhalten Sie die folgende Schema-Datei schema.xsd. Die Elemente werden wie oben
festgelegt ineinandergeschachtelt.
<?xml version="1.0" encoding="ISO_8859-1"?>
<xs:schema elementFormDefault=’qualified’
xmlns:xs=’http://www.w3.org/2001/XMLSchema’>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<xs:element name=’FormatierungEinfachListe’>
<xs:complexType>
<xs:sequence>
<xs:element name=’FormatierungEinfach’
type=’Detail’ minOccurs=’0’ maxOccurs=’unbounded’/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:complexType name=’Detail’>
<xs:sequence>
<xs:element name=’name’ type=’xs:string’/>
</xs:sequence>
<xs:attribute name=’Id’/>
</xs:complexType>
</xs:schema>
Listing 16.21: Schema.xsd
Wir können mit der Schema-Datei, die XML-Datei mithilfe der Klassen SchemaFactory und
Validator und der Methode validate() validieren. Stimmt die XML-Datei mit den Regeln der
Schema-Datei überein, erhalten wir beim Validieren keinerlei Fehlermeldung. Wann wäre eine
Regel verletzt? Z.B. in folgendem Fall: in der XML-Datei fehlt das Tag name innerhalb des Elementes FormatierungEinfach.
340
16.9 Groovy und XML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package groovyforbeginners.GroovyUndXML.SimpleExample
import javax.xml.XMLConstants
import javax.xml.transform.stream.StreamSource
import javax.xml.validation.SchemaFactory
class ValidateXML {
static void main(args) {
/*Mit der Klasse SchemaFactory ist ein validieren der XML-Datei moeglich*/
def factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI)
/*Ein XML-File wird erstellt.*/
File fileXML =
new File("src/groovyforbeginners/GroovyUndXML/SimpleExample/FormatierungEinfach.xml");
/*Ein Schema-File wird erstellt.*/
def schemaXSD =
new File("src/groovyforbeginners/GroovyUndXML/SimpleExample/Schema.xsd");
/*Es wird ein neues Schema mithilfe der Methode newSchema() erstellt.*/
def schema = factory.newSchema(schemaXSD)
def validator = schema.newValidator()
/*Die Methode validate() vergleicht die Schema-Datei mit der
*XML-Datei fileXML.*/
validator.validate(new StreamSource(fileXML))
}
}
Listing 16.22: ValidateXML.groovy
16.9.4 Reguläre Ausdrücke in einer XML-Schema-Datei
Wenn Ihnen die Datentypen, die XML-Schema standardmäßig zur Verfügung stellt, nicht ausreichen, können Sie selbst Datentypen mithilfe von regulären Ausdrücken festlegen. Bitte beachten
Sie auch, dass es Unterschiede bei den Datentypen zwischen Java, XML und SQL gibt, sprich
sie sind nicht immer identisch. So sind Anpassungen notwendig.
Ein eigener Datentyp wird in XML-Schema mit dem Tag xs:simpleType erstellt. Darin wird
ein xs:restriction-Element geschachtelt, dem das Format zugewiesen wird, auf dem das neu zu
erstellende Format basiert. Für unser Datumsformat nehmen wir den Typ String. Das Tag
xs:restriction wiederum enthält das Element xs:pattern, das das Muster (deutsch für pattern)
für ein bestimmtes Format festlegt. Das folgende Beispiel zeigt wie ein eigenes Datums- und
Uhrzeitformat aussehen könnte:
1
package groovyforbeginners.GroovyUndXML.RegularExpressionExample
341
16 Anhang B: Eine kurze Einführung in Groovy
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
class SchemaValidateDateTime {
static void main(args) {
def builder = new groovy.xml.StreamingMarkupBuilder()
builder.encoding = "ISO_8859-1"
def EreignisListe = {
mkp.xmlDeclaration()
mkp.declareNamespace(’xs’ :’http://www.w3.org/2001/XMLSchema’ )
xs.schema(’elementFormDefault’ :’qualified’){
xs.element( name:’EreignisListe’){
xs.complexType() {
xs.sequence {
xs.element(name:’Ereignis’, type:’Detail’,
minOccurs:’0’, maxOccurs:’unbounded’)
}
}
}
xs.complexType(name: ’Detail’) {
xs.sequence {
xs.element(name:’Datum’, type:’typeDate’)
xs.element(name:’Zeit’, type:’typeTime’)
}
}
/*Wir erstellen ein benutzerdefiniertes Format fuer das Datum.*/
xs.simpleType(name:’typeDate’) {
/*Das neue Format basiert auf dem Typ String.*/
xs.restriction(base:’xs:string’) {
/*Das Datum muss folgende Form haben: Tag und Monat muessen
*genau 2 Zahlen enthalten und das Jahr kann 2-, 3 oder 4-stellig
*sein. Als Trennzeichen wird ein Punkt erwartet. */
xs.pattern(value:/[0-9]{2}\.[0-9]{2}\.[0-9]{2,4}/)
}
}
/*Wir erstellen ein eigenes Uhrzeitformat.*/
xs.simpleType(name:’typeTime’) {
xs.restriction(base:’xs:string’) {
/*Die Stunde der Uhrzeit besteht aus ein oder zwei Ziffern
*und die Minuten muessen genau 2 Ziffern haben, wobei die
*Stunden und Minuten durch einen Doppelpunkt getrennt werden.*/
xs.pattern(value:’[0-9]{1,2}:[0-9]{2}’)
}
342
16.9 Groovy und XML
51
52
53
54
55
56
57
58
59
60
61
62
}
}
}
def outFile =
new File("src/groovyforbeginners/GroovyUndXML/RegularExpressionExample/SchemaDatum.xsd");
def writer = new FileWriter(outFile);
writer << builder.bind(EreignisListe)
System.out << builder.bind(EreignisListe)
writer.close()
}
}
Listing 16.23: SchemaValidateDateTime.groovy
Das Ergebnis, die generierte XML-Schema-Datei, enthält in dem Tag xs:simpleType selbstdefinierte Datentypen:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<?xml version="1.0" encoding="ISO_8859-1"?>
<xs:schema elementFormDefault=’qualified’
xmlns:xs=’http://www.w3.org/2001/XMLSchema’>
<xs:element name=’EreignisListe’>
<xs:complexType>
<xs:sequence>
<xs:element name=’Ereignis’ type=’Detail’
minOccurs=’0’ maxOccurs=’unbounded’/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:complexType name=’Detail’>
<xs:sequence>
<xs:element name=’Datum’ type=’typeDate’/>
<xs:element name=’Zeit’ type=’typeTime’/>
</xs:sequence>
</xs:complexType>
<xs:simpleType name=’typeDate’>
<xs:restriction base=’xs:string’>
<xs:pattern value=’[0-9]{2}\.[0-9]{2}\.[0-9]{2,4}’/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name=’typeTime’>
<xs:restriction base=’xs:string’>
<xs:pattern value=’[0-9]{1,2}:[0-9]{2}’/>
</xs:restriction>
</xs:simpleType>
343
16 Anhang B: Eine kurze Einführung in Groovy
32
33
</xs:schema>
Listing 16.24: SchemaDatum.xsd
Wie oben in Groovy muss auch in der Schema-Datei der Punkt mit einem Escape-Zeichen versehen werden, da ein Punkt ohne Escape-Zeichen jedes x-beliebige Zeichen zulassen würde.
Testen wir unser Schema. Nehmen wir folgende XML-Datei, in der wir einen Formatierungsfehler
eingebaut haben: Die zweite Uhrzeit enthält einen Punkt anstelle eines Doppelpunktes.
<?xml version="1.0" encoding="ISO_8859-1"?>
<EreignisListe xsi:noNamespaceSchemaLocation=’SchemaDatum.xsd’
xmlns:xsi=’http://www.w3.org/2001/XMLSchema-instance’>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<Ereignis>
<Datum>12.12.2010</Datum>
<Zeit>20:20</Zeit>
</Ereignis>
<Ereignis>
<Datum>12.13.2010</Datum>
<Zeit>1.15</Zeit>
</Ereignis>
</EreignisListe>
Listing 16.25: Ereignisse.xml
Validieren wir die XML-Datei mit der SchemaDatum.xsd erhalten wir eine Fehlermeldung, die
sehr informativ und ausführlich ist.
package groovyforbeginners.GroovyUndXML.RegularExpressionExample
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import javax.xml.XMLConstants
import javax.xml.transform.stream.StreamSource
import javax.xml.validation.SchemaFactory
class ValidateDatumUhrzeit {
static void main(args) {
def factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI)
File fileXML =
new File("src/groovyforbeginners/GroovyUndXML/RegularExpressionExample/Ereignisse.xml");
def schemaXSD =
new File("src/groovyforbeginners/GroovyUndXML/RegularExpressionExample/SchemaDatum.xsd");
def schema = factory.newSchema(schemaXSD)
def validator = schema.newValidator()
344
16.9 Groovy und XML
18
19
20
validator.validate(new StreamSource(fileXML))
}
}
Listing 16.26: ValidateDatumUhrzeit.groovy
E x c e p t i o n i n t h r e a d " main " o r g . xml . sa x . SAXParseException :
cvc−p a t t e r n −v a l i d : Value ’ 1 . 1 5 ’ i s not f a c e t −v a l i d with
r e s p e c t t o p a t t e r n ’ [ 0 − 9 ] { 1 , 2 } : [ 0 − 9 ] { 2 } ’ f o r t y p e ’ typeTime ’ .
Zum Schluß eine Tabelle, die die wichtigsten regulären Ausdrücke für XML-Schema zusammenfaßt:
Tabelle 16.2: Übersicht über die wichtigsten regulären Ausdrücke für XML-Schema
Regulärer Ausdruck
Bedeutung
\d
Ziffer
\D
Jedes Zeichen, außer Ziffern
\s
Leerzeichen
\S
Alle Zeichen, außer Leerzeichen
\w
Buchstaben
\W
Alle Zeichen, außer Buchstaben
.
Jedes Zeichen
()
Gruppierung
[]
Bereich
{}
Wiederholung
+
eines oder mehrere vom gleichen Zeichen
345
17 Anhang
17.1 Literaturverzeichnis
Basham, Bryan; Bates, Bert; Sierra Kathy: Head First Servlet and JSP, O’Reilly Media, 2008.
Bates, Bert; Sierra Kathy: Java von Kopf bis Fuß, O’Reilly Media, 2006.
Bates, Bert; Sierra Kathy: SCJP Sun Certified Programmer for Java 6 Study Guide, O’Reilly
Media, 2008.
Bauer, Christian; Gavin King: Java Persistence mit Hibernate, Hanser, 2007.
Brenner, Ina: Das große SCJP Trainingsbuch, Franzis, 2006.
Burke, Bill; Manson-Haefel, Richard: Enterprise JavaBeans 3.0., 2006.
Kuhlmann, Gregor; Müllmerstadt, Friedrich: SQL: Der Schlüssel zu relationalen Datenbanken,
rororo, 2004.
Lane, Derek; Panda, Debu; Rahman, Reza: EJB3 in Action, Manning, 2007.
Edlich, Stefan; Hörning, Henrik; Hörning, Reidar; Paterson, Jim: The definite Guide to db4o,
Apress, 2006.
Hennebrüder, Sebastian: Hibernate: Das Praxisbuch für Entwickler, Galileo Computing, 2007.
Römer, Patrick; Visengeriyeva, Larysa: db4o, Software & Support Verlag GmbH, 2007.
17.2 Informative Quellen im Netz
www.css4you.de
CSS-Referenz auf deutsch: Alle wichtige Informationen rund um das Thema CSS finden Sie bei
css4you.
www.db4o.de
Deutschsprachige Seite der db4objects, Inc. Hier finden Sie alle Informationen rund um das Thema db4o, einschließlich hilfreicher Links zu weitergehenden Informationen.
db4o-Tutorial
Dieses Tutorial wird mit der Datenbank db4o heruntergeladen und befindet sich im Verzeichnis
doc/tutorial.
347
17 Anhang
db4o-HTML-Referenz
Diese Referenz ist ebenfalls Teil des db4o-Paketes und Sie finden es im Ordner doc/reference.
www.java-forum.org
Deutschsprachiges Forum rund um das Thema Datenbanken, XML, SOA, AJAX und Java. Es
gibt zu Java unterschiedliche Unterforen, wie z.B. AWT, Swing & SWT, Java 2 MicroEdition,
Hibernate, EJB und Spring.
http://www.javaranch.com/
Englischsprachiges Forum für alle, die sich für alle Java-Zertifizierungen von Sun Microsystems
anmelden und vorbereiten möchten.
http://www.jcp.org/en/jsr/detail?id=220
Hier finden Sie die Spezifikation der Java Persistence API. Die Spezifikation unter Leitung von
Linda DeMichiel (Sun) legt herstellerübergreifend den Standard für die Java Persistence API
fest. Sie enthält Regeln an die sich alle Persistenzprovider, wie z. B. Hibernate, halten müssen.
http://www.hibernate.org/
Englischsprachige Seite rund um das Thema Hibernate mit Tutorials und Bibliotheken zum Downloaden.
http://www.netbeans.org/
Seite von NetBeans, auf der Sie Informationen zu NetBeans finden und NetBeans herunterladen
können.
http://www.netbeans-forum.de/
Sollten Sie Fragen rund um das Thema NetBeans haben, können Sie Ihre Fragen in dieses Forum
stellen.
http://odbms.org/
Englischsprachiges Portal rund um das Thema Objektorientierte Datenbanken, das Unterrichtsund Forschungszwecken dient. Die Seite enthält eine große Sammlung an Artikeln und Links zum
Thema.
http://static.springsource.org/spring/docs/3.0.2.RELEASE/spring-framework-reference/html/
Offizielle Springdokumentation für das 3.0.2.RELEASE, das in diesem Buch verwendet wird.
http://www.polepos.org/
Englischsprachige Seite, die Teil des Sourceforge.net-Projektes ist, die Datenbanken, wie z. B.
MySQL und db4o, nach verschiedenen Kriterien miteinander in Beziehung setzt.
http://theserverside.com
Englischsprachiges Sprachrohr und Forum für alle Fragen rund um alle Themengebiete, die Java
betreffen.
http://de.wikipedia.org/wiki/Db4o
Deutschsprachiger schnellwachsender Eintrag für db4o bei Wikipedia.org.
348
Index
.yap, 7
<c:choose>, 122
<c:forEach>, 115
<c:if>, 120
<c:otherwise>, 122
<c:set>, 119
<c:when>, 122
<context-param>, 128
<dispatcher>, 125
<init-param>, 128
<servlet>, 128
1:1-Beziehung, 45, 46
1:1-Beziehung, db4o, 141
1:n-Beziehung, 45, 46, 64
a:hover, 100
Abfragekonzepte, 191
Abfragen in Hibernate, 265
Abstrakte Klassen, 52, 167
ACID, 237
action, 158
actions, Grails, 328
activationDepth(), 279
Aktion, 158
Aktivierungstiefe, 279
aktualisieren, 305
Alias, 265
and, 269
and(), 214
Anfrage, 90
anonyme innere Klassen, 327
anonyme Klasse, 198, 203
Antwort, 90
Antwortzeiten, 44, 262
applicationContext.xml, 28
Applikation, 94
Arbeitsspeicher, 227, 297, 305
Array, Groovy, 324
ArrayList, 9, 115, 224, 282
ArrayList, Groovy, 325
asc, 271
Atomicity, 237
attributeAdded(), 135
attributeRemoved(), 135
attributeReplaced(), 135
AUTO_INCREMENT, 46
avg(), 275
Bedingungen, 191
Bedingungen, einfache, 197, 212
Bedingungen, mit Vergleichen, 217
Bedingungen, zusammengesetzte, 197, 202,
214
beginTransaction(), 246
Beispielobjekt, 167
belongsTo, 68, 159
Beziehungen, 45
bidirektionale 1:1-Beziehung, 50
bidirektionale 1:n-Beziehung, 64
bidirektionale 1:n-Beziehung in Grails, 68
bidirektionale m:n-Beziehung, 77
Cache, 227, 299
cascade, 71, 268
cascadeOnDelete(), 146
cascadeOnUpdate(), 283, 305
CascadeType, 71, 268
CascadeType, ALL, 268
CascadeType, MERGE, 268
CascadeType, PERSIST, 268
CascadeType, REFRESH, 268
CascadeType, REMOVE, 268
CascadeType.ALL, 71
CascadeType.All, 268
Casten, 10
children(), Groovy, 337
349
Index
Client, 90, 297
Client-Server-Modus, 297
close(), 7, 238
Closure, 327
Collections, 208
Collections-Framework, Groovy, 324
commit(), 250
commit(), db4o, 238
commit, Hibernate, 246
Comparator, 206
compare(), 206
Consistency, 237
constrain(), 211, 212, 214
Constraint, 50, 211, 217
contains(), 217
contentType, 109
Context, 94
Context path, 97
Context-Listener, 135
Context-Parameter, 128, 297
context.xml, 17, 97, 283
contextDestroyed(), 133
contextInitialized(), 133
Controller, 83
Controller, Grails, 157
Convention over Configuration, 34
count(), 275
CREATE-TABLE-Befehl, 41
createQuery(), 265, 287
Criteria Queries, 276
CSS, 98
CSS-Format, 39
Custom Tags, 109, 114
DataBaseFileLockedException, 228
DataSource.groovy, 35
Datenbankabfragen, 83
Datenbanken teilen, 263
Datenbankentwurf, 83
Datenbanktreiber MySQL Connector/J, 15
Datenbankverbindung, 17
Datenredundanz, 44, 46
Db4oEmbedded, 6
Db4oServletContextListener, 297
Db4oSessionListener, 299
Default-Fall, 122
Default-Wert, 9
Deklaration, 108, 111
delete(), 7, 11
350
delete(), Hibernate, 250
Denormalisieren, 263
Denormalisierung, 44
Dependency Injection, 30, 32, 159
Deployment, 97
Deployment Descriptor, 128
desc, 272
descend(), 212
destroy(), 123
detached, 250
Direktive, 108
DiscriminatorValue, 57
div-Tag, 99
doFilter(), 123
doGet(), 84
doPost(), 84
double, 9
Durability, 237
Durchschnitt, 275
Dynamic Finders, 158
Dynamische Typisierung, Groovy, 323
eager, 292
Eager-Loading-Strategie, 68
Ein- und Ausgabe von Dateien, Groovy, 332
Embedded-Modus, 227, 232, 245, 297
endsWith(), 217
Enterprise Java Beans, 245
ErrorPage, 125
Exception, StaleObjectStateException, 259
execute(), 212
Expression, 108, 109
ext(), 7
ExtObjectContainer, 245, 304
fetch, 292
FetchType.Eager, 292
FetchType.Lazy, 292
Filter, 83, 123
Filter-Mapping, 125
FilterChain, 125
final, 203
float, 9
flush, 250
For-Each-Schleife, Groovy, 327
FOREIGN KEY, 49
FORWARD, 125
forward(), 92
Fremdschlüssel, 44, 49
Index
gültiges XML-Dokument, 338
Geltungsbereich, 119
GenerationType.AUTO, 46
Generics, 10
generisch, 10
GET, 84, 90
get(), 7, 167, 250
getAttribute(), 93, 94
getContextPath(), 86, 92
getCurrentSession(), 247
getID(java.lang.Object obj), 7
getInitParameter(), 128
getMessageSender(), 235
getName(), 117
getNamedQuery(), 276
getParameter(), 92, 94
getRequestDispatcher(), 92, 93
getServletConfig(), 128
getServletContext(), 94
getSession(), 93
Getter- und Setter-Methoden, 39
Getter- und Setter-Methohden, Groovy, 322
getWriter(), 91
GPathResult, 336, 337
grantAccess(), 228
greater(), 217
group by, 275
Gruppierung, 275
GString, 328, 335
has-a-Beziehung, 46, 279
HashMap, 115, 326
hasNext(), 7
having, 275
Hibernate, 4
Hibernate Query Language, 265
hibernate.cfg.xml, 24
Hibernate.initialize(), 291
HibernateUtil.java, 24
HQL, 265
HTTP-Protokoll, 90
HttpRequestAttributeListener, 135
HttpServlet, 84
HttpServletRequest, 92, 94, 300
HttpServletResponse, 91, 93
HttpSessionActivationListener, 135
HttpSessionAttributeListener, 131
HttpSessionBindingEvent, 131, 135
HttpSessionBindingListener, 135
HttpSessionEvent, 133, 135
HttpSessionListener, 133, 297, 299
identity(), 217
IllegalStateException, 219
import, 109
Include, 109, 125
InheritanceType, 55
init(), 123
InitialContext, 17, 284
initialisieren, 283, 291
initialize(), 291
inner join, 274
INSERT-Befehl, 8
Installation von db4o, 5
Installation von MySQL, 15
Instanzen, 39
Instanzvariable, 111, 316
int, 9
Interface, 52, 167
Interface Comparator, 206
Interface Constraint, 191, 211
Interface HttpServletRequest, 92, 94
Interface HttpServletResponse, 93
Interface Query, 191, 211, 265
Interface Session, 265
Interfaces Constraint, 217
InterruptedException, 228
invalidate(), 93
Inversion of Control, 30
is-a-Beziehung, 53, 164
Isolation, 237
Isolationsstufen, 237
Java Database Connectivity, 245
Java Naming und Directory Interface, 17
Java Persistence API, 15, 44, 46
Java Transaction API, 245
javax.persistence.NamedQueries, 275
javax.persistence.NamedQuery, 275
JDBC, 245
JDBC-Transaktionen, 245
JNDI, 17, 284
join, 273
Joined, 55
JOINED-Mapping, 59
JPA, 15, 46
JPA-Spezifikation, 44, 251, 256
JSP, 83, 84, 98
351
Index
JSTL, 114
jstl.jar, 114
JTA, 245
Kapselung, 39
Kaskadierende Beziehungen, 267
kaskadierende Beziehungen, 71
kaskadierende Optionen, 268
key, 117
Klasse, 39
Klasse Collections, 208
Klasse LockManager, 253
Klasse Predicate, 191, 197, 202, 206
Klasse Thread, 314
Klassen, Groovy, 321
Klassenvariable, 111, 316
Klausel where, 268
Konstruktoraufruf, 55
Konstruktoren, 39
Kontrollstruktur, 120, 122
lazy, 292
Lazy Loading, 247, 279, 283
LazyInitializationException, 247, 290, 292
left join fetch, 292
left outer join, 274
libraries, 6
like(), 217
LinkedHashMap, 326
LinkedList, 282
list(), 265, 287
List, Groovy, 325
Listener, 131
load(), 250
localhost, 229
LOCK, 316
lock(), 259
lock(), Hibernate, 256
locken, 251, 253
Lockingprozesse, 251, 262
LockManager, 253
LockMode, 256, 262
LockMode.None, 262
LockMode.Read, 262
LockMode.UPGRADE, 262
log4j, 22
log4j.properties, 22
Logische Operatoren:, 275
352
m:n-Beziehung, 45, 46, 77
managed, 250
Map, 326
mappedBy, 52
match(), 197, 198, 202
Mathematische Operatoren, 275
max(), 275
Maximum, 275
maxOccurs, 340
merge(), 268
MessageRecipient, 233
MessageSender, 233
META-INF, 17, 23, 97
Methode ext(), 7
Mime-Type, 109
min(), 275
Minimum, 275
minOccurs, 340
Model, 83
Model-View-Controller, 5, 83
Multithreading, 313
MVC, 83
MySQL Community Server, 15
MySQL Connector/J, 15
MySQL Tools, 15
MySQL Workbench, 15
mysql-connector-java-5.1.5-bin.jar, 16
name(), Groovy, 337
Named Queries, 275
native Methoden, 191
Native Queries, 191, 224
NetBeans, 3, 83, 85
Netzwerkmodus, 227
newPrintWriter(), Groovy, 332
next(), 7
Normalisieren, 263
Normalisierung, 44
not(), 211, 214
notify(), 233
NullPointerException, 244, 280
ObjectContainer, 6, 7, 299, 300
ObjectSet, 7, 9
Objekte, 39
Objektlebenszyklus in Hibernate, 249
OID, 7, 42, 253
Open-Session-in-View, 283
Open-Session-in-View-Pattern, 247, 292
Index
openClient(), 229, 232, 299
openFile(), 7, 238
openServer(), 228, 232
Operator =, 268
Operatoren, 275
optimistic locking, 251
optimistisches Sperren, 251
optimistisches Sperren, Hibernate, 256
optimistisches, Sperren, 251
order by, 271
orderAscending(), 211, 221
orderDescending(), 211
Out-of-Band-Signalling, 227, 233
outer join, 274
Package com.db4o, 6
Package com.db4o.ext, 245, 304
Package com.db4o.messaging, 233
Package com.db4o.query, 191, 210
Package com.db4o.query.Predicate, 197
Package java.io, 91
Package java.lang, 313
Package java.util, 208
Page, 109
Page-Attribut, 109
pageEncoding, 109
Parameter Binding, 289
Parameter-Binding, 266
parse(), 336
Pattern, Open-Session-in-View, 247, 292
Performance, 262
persist(), 250, 268
persistence.xml, 23
persistent, 250
Persistenzlebenszyklus in Hibernate, 249
pessimistic locking, 251
pessimistisches Sperren, 251
pessimistisches, Sperren, 251
Portnummer, 229
POST, 84, 90
Predicate, 191, 197, 202, 206
Primärschlüssel, 42, 44, 49
PRIMARY KEY, 42, 49
primitiven Datentyp, 9
print(), 109
println(), 91
PrintWriter, 126
PrintWriter, Groovy, 332
PrintWriter-Objekt, 91
private, Groovy, 322
processMessage(), 233
public, Groovy, 322
Query, 211
Query by example, 276
query(), 198, 206
Query-by-Example, 8, 167, 191, 224
queryByExample(), 8
Rückgabetyp, Groovy, 323
Read Committed, 238
Read Uncommitted, 238
referentielle Integrität, 45, 186
refresh(), 245, 268, 299, 304
reguläre Ausdrücke, Groovy, 330, 331
reguläre Ausdrücke, XML-Schema, 341, 345
releaseSemaphore(), 251
remove(), 268
remove(), Hibernate, 250
removeAttribute(), 94
removed, 250
Repeatable Read, 238
Request, 90, 94, 123
Request Message Body, 90
Request-Listener, 135
requestDestroyed(), 135
RequestDispatcher, 125
requestInitialized(), 135
RESOURCE_LOCAL, 23
Response, 90, 123
reverse(), 208
right outer join, 274
Rollback, 192
rollback(), db4o, 238
Rollback, db4o, 238, 245
rollback, Hibernate, 246
Rollover-Effekt, 100
run(), 313
Runnable Interface, 313
S.O.D.A., 191, 210, 224
saveOrUpdate(), 250
Schema-Datei, 338
SchemaFactory, 340
scope, 94, 119
SELECT, 11
select, 266
select-Befehl, 266
353
Index
Semaphore, 238, 251, 318
send(), 235
sendRedirect(), 93
Serializable, 46, 237
Server, 90, 297
Service, Grails, 157
Servlet, 83, 84, 91
Servlet-Mapping, 85
Servlet-Parameter, 128
ServletContextAttributeEvent, 135
ServletContextAttributeListener, 135
ServletContextEvent, 133
ServletContextListener, 133, 297
ServletRequestAttributeEvent, 135
ServletRequestEvent, 135
ServletRequestListener, 135
Session, 93, 94, 246
Session in Hibernate, 24
Session, Hibernate, 24, 245
Session, kontextabhängige, 31
Session-Config, 93
Session-Listener, 135
Session-per-Request-Pattern, 246, 247
sessionCreated(), 133
sessionDestroyed(), 133
sessionDidActivate(), 135
SessionFactory, 24, 246
sessionWillPassivate(), 135
set(), 7, 161
setAttribute(), 93, 94
setDouble(), 266
setInteger(), 266, 289
setMessageRecipient(), 233
setPriority(), 313
setSemaphore(), 251
setString(), 266
Simple Object Database Access, 210
Singelton-Pattern, 24
Single_Table, 55
Skriptlet, 108, 111
smaller(), 217
Solo-Modus, 227
Sortieren, 206, 221
span-Tag, 99
Sperren in Hibernate, 256
Sperren, optimistisches, 259
Sperren, optimistisches, Hibernate, 256
Spezifikation für die Java Persistence API,
44, 251, 256
354
SQL-Befehle, 191
StaleObjectStateException, 259
Standard Tag Library, 114
standard.jar, 114
Standardaktivierungstiefe, 280
Standardkonstruktor, 39, 55
Standardwert, 9
start(), 314
startsWith(), 217
stil.css, 98
StopServer-Objekt, 235
store(), 7
StreamingMarkupBuilder, 333
Subklasse, 52
sum(), 275
Summe, 275
super(), 55
Superklasse, 52
Synchronisation, 315
synchronisierte Blöcke, 238, 251, 315
Syntaxelemente, JSP, 108
Table per class, 55
Table-per-Concrete-Class-Mapping, 62
Table-per-Hierarchy, 55
Taglib, 109
Taglib-Direktive, 109
TCP/IP-Port, 228
TCP/IP-Protokoll, 227
teilen, Datenbanken, 263
text(), Groovy, 337
this, 233
Thread, 90, 228, 314
Thread.MAX_PRIORITY, 313
Thread.MIN_PRIORITY, 313
Thread.NORM_PRIORITY, 313
Threads, 313
threadsafe, 315
threadsicher, 315
Tiefe Objektgraphen, 279
Timeout, 93
Tomcat, 17, 85, 97, 232
Transaktion, 26, 31
Transaktion, Grails, 158
Transaktionen, 192, 237, 238
Transaktionen in Hibernate, 245
Transaktionsgrenzen, Hibernate, 246
transient, 249
Transparente Aktivierung, 283
Index
try-catch-finally-Block, 7
unidirektionale 1:1-Beziehung, 46
unidirektionale 1:n-Beziehung, 69
unidirektionale Beziehung (Grails), 50
unidirektionale m:n-Beziehung, 81
UNIQUE, 71
Unique, 50
Unique Object Identifier, 42, 253
uniqueResult(), 265, 289
update, Hibernate, 259
Update-Tiefe, 283, 305
updateDepth(), 283, 305
UTF-8, 109
validate(), Groovy, 340
Validator, 340
Validieren, XML-Schema, 340
value, 117
valueBound(), 135
valueUnbound(), 135
Vererbungsbeziehungen, 45, 46, 52
Vergleichsoperatoren, 275
Version, 256
Versionskontrolle, Hibernate, 256
Versionsspalte, 256
Versionsvergleich, Hibernate, 256
View, 83
wait(), 228
WAR-Datei, 97
Web Deployment Descriptor, 85
WEB-INF, 18, 83
web.xml, 17, 83, 85, 125, 128
webapp, 83
Webcontainer, 85
where, 268
wohlgeformtes XML-Dokument, 334
XML, Groovy, 333
XML-Schema, 338
XMLSlurper, 336
xs:attribute, 338
xs:complexType, 338
xs:pattern, 341
xs:restriction, 341
xs:simpleType, 341
Zugriff, 299
Zugriffsgeschwindigkeit, 263
355
Herunterladen