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