149 7 Datenbankabfragen Dieses Kapitel widmet sich dem effektiven Zugriff auf Daten. Mit Datenbankabfragen können Objekte mit bestimmten Eigenschaften gesucht werden. Erst mit Abfragen ist es möglich, Objekte zu laden, deren ID nicht bekannt ist. Es werden die drei Möglichkeiten beschrieben, wie Abfragen definiert und ausgeführt werden können. Zuerst wird die Hibernate Query Language (HQL) erläutert. Wir zeigen auch die programmatische Form der Abfrage mit Queries und Criterias. Außerdem zeigen wir, wie in Hibernate SQL-Abfragen genutzt werden können. Zum Ende des Kapitels werden Filter beschrieben, welche die Ergebnismenge von Abfragen einschränken. Für die Beispiele wird in dem Kapitel das einfache Beispiel weiter verwendet. Wie in der folgenden Abbildung verwenden wir Termine, die ihre Teilnehmer kennen. Überblick Abb. 7–1 Termin und Benutzer 7.1 Die Hibernate Query Language Die Syntax der HQL ist der von SQL sehr ähnlich. Groß- und Kleinschreibung ist für die Schlüsselwörter der HQL unerheblich. Für die weiteren Beispiele werden wir Kleinschreibung benutzen. Die einfachste Abfrage mit der HQL ist in unserem Fall die folgende: from Termin gleichbedeutend zu FROM Termin 150 7 Datenbankabfragen Query ausführen Diese ganz einfache Abfrage liefert alle Termine aus der Datenbank und wird mit dem Befehl createQuery an einer Session erzeugt: Query q = session.createQuery(“from Termine“); List<Termin> alleTermine = q.list(); Die Ausführung der Query wird erst mit einer der Methoden list, scroll oder iterator gestartet. Ausführung der Query Befehl Ergebnis list() Liefert eine Liste der Ergebnisse vom Typ java.util.List iterator() Liefert einen Iterator über die Ergebnisse vom Typ java.util.Iterator scroll() Bietet die Möglichkeit, innerhalb des Ergebnisses zu blättern, liefert als Typ org.hibernate.ScrollableResults uniqueResult() Nützlich, wenn die Abfrage nur ein Ergebnis liefern kann, ist entweder null oder vom Typ java.lang.Object Falls mehrere Ergebnisse gefunden werden, wird eine Exception geworfen! Ergebnis der Abfrage auto-import Eine Query kann nur mit den beschriebenenen Methoden ausgeführt werden, solange die erzeugende Session aktiv und nicht geschlossen ist. Die gefundenen Objekte befinden sich im persistenten Zustand, solange die Session nicht beendet wurde. Daher werden Änderungen an Objekten auch gespeichert, wenn diese durch eine Abfrage bezogen wurden und die Session aktiv ist. Das Ergebnis der obigen Abfrage from Termin sind alle Termine, die in der Datenbank gespeichert sind. In der from-Klausel wird der Typ der Objekte angegeben, nach denen gesucht wird. Als Bezeichnung wird der Name des Typs angegeben. Es ist zu beachten, dass bei der Angabe des Typs auf Groß- und Kleinschreibung unterschieden wird. Die Schreibweise muss der der Klasse bzw. des Interfaces entsprechen! Im Beispiel haben wir den Typ Termin ohne das Package angegeben. Der Name des Typs muss in der Abfrage nur voll qualifiziert angegeben werden, wenn in der Mapping-Datei explizit das Attribut auto-import=“false“ gesetzt wurde. Ansonsten sucht Hibernate nach dem Typ in allen Klassen und Interfaces, die bei der Configuration angemeldet sind1. Dadurch können die Typen in den Abfrage ohne volle Qualifizierung angegeben werden. 7.1 Die Hibernate Query Language Wird innerhalb mehrerer Objekttypen gesucht, werden diese durch Kommata getrennt. Das entspricht einem Join in SQL. Um diese Objekttypen in anderen Teilen der Abfrage zu referenzieren, wird nach dem Objekttyp ein Alias angegeben. Dieses Alias steht dann stellvertretend in der Abfrage für alle persistenten Objekte dieses Typs. Zur besseren Lesbarkeit darf optional »as« dazwischen stehen. 151 Aliase from Termin as termin from Termin termin, Benutzer benutzer Sind in der from-Klausel einer Abfrage mehrere Typen angegeben, werden die Ergebnisse als Liste von Arrays geliefert. Die nächste Abfrage liefert alle Kombinationen von Terminen und Benutzern als Liste von Arrays: Tupel Query q = session.createQuery(“from Termin, Benutzer“); List tupel = q.list(); Object[] arr = (Object[]) tupel.get(0); Termin termin = arr[0]; Benutzer benutzer = arr[1]; Die Abfragen können auf Ergebnisse mit bestimmten Eigenschaften eingeschränkt werden. Wie in SQL gibt eine where-Klausel die Bedingung an, die für das Ergebnis zutreffen muss. Mit der folgenden Abfrage finden wir alle Termine, die im »Raum 123« stattfinden: from Termin as term where term.ort = ’Raum 123’ Der Bezeichner »ort« in der obigen Abfrage bezieht sich auf das gleichnamige Property der Klasse Termin und nicht auf den Spaltennamen in der Tabelle! Genauso bezieht sich der Bezeichner Termin auf den Namen der Klasse und nicht auf den Tabellennamen. Um Attribute von enthaltenen Objekten mit in die Abfrage einzubeziehen, werden sie einfach mit einem Punkt verknüpft. Als Beispiel suchen wir nach allen Terminen für einen Benutzer. Bei der Abfrage soll nur dessen Kürzel bekannt sein. Die Abfrage für die Termine des Benutzers, mit dem Kürzel »rb« lautet dann wie folgt: from Termin t where t.teilnehmer.kuerzel = ’rb’ 1. Wenn gleichnamige Klassen in unterschiedlichen Packages mit Hibernate verwaltet werden, muss auto-import ausgeschaltet werden, ansonsten ist der Typname für die Abfragen nicht eindeutig, und es wird eine Exception geworfen. Achtung 152 7 Datenbankabfragen Kriterien Für die Beschreibung der Kriterien stehen die Standard-SQL-Operatoren zur Verfügung: =, <>, <, >, >=, <=, like, between, not between, in und not in. Folgendes Beispiel zeigt eine Abfrage aller Termine, deren Property Ort mit dem String »Raum A« anfängt: from Termin term where term.ort like ’Raum A%’ Wie man sieht, wird die Wildcard % aus SQL auch in HQL für das String-Kriterium like verwendet und steht für beliebig viele Zeichen. Mehrere Kriterien werden innerhalb der where-Klausel mit den booleschen Operatoren and, or und not verknüpft und können mit Klammern gruppiert werden. Das folgende Beispiel liefert alle Termine, die in der Beschreibung mit »Projekt Hibernate« anfangen und in Hamburg oder Berlin stattfinden: from Termin termin where termin.beschreibung like ’Projekt Hibernate%’ and (termin.ort = ’Hamburg’ or termin.ort=’Berlin’) Die obige Abfrage lässt sich auch mit dem in-Operator formulieren: from Termin termin where termin.beschreibung like ’Projekt Hibernate%’ and termin in (’Hamburg’,’Berlin’) Der in-Operator kann – wie alle anderen Operatoren – mit not negiert werden. Die folgende Abfrage findet somit alle Termine mit der obigen Beschreibung, die weder in München noch in Köln stattfinden: from Termin termin where termin.beschreibung like ’Projekt Hibernate%’ and termin not in (’München’,’Köln’) Eine weitere Möglichkeit, ein Kriterium zu bestimmen, sind Funktionen. Die folgende Abfrage liefert alle Termine, für die mindestens zwei Teilnehmer eingetragen sind. from Termin t where size(t.teilnehmer) > 2 Mit empty ist es sehr einfach abzufragen, ob eine Collection leer ist. So können mit der Abfrage alle Termine gefunden werden, für die keine Teilnehmer eingetragen sind: from Termin t where t.teilnehmer is empty Select Sind für die Abfrage nur Objekte eines bestimmten Typs erforderlich, werden vor der from-klausel mit einer select-Klausel die Ergebnistypen ausgewählt. Im Vergleich zu SQL ist die select-Klausel bei der Hibernate Query Language optional. In der select-Klausel werden die Aliase 7.1 Die Hibernate Query Language 153 der Objekte angegeben, die als Ergebnis für die Abfrage von Interesse sind. Die nächste Abfrage liefert z.B. alle Benutzer als Liste, die bei Terminen mit der Beschreibung »Buchbesprechung« eingetragen sind: select t.teilnehmer from Termin as t where t.beschreibung = ’Buchbesprechung’ Eine Abfrage wie from Termin bezieht sich auf alle in der Datenbank gespeicherten Objekte des Typs Termin. Bei der Abfrage werden auch sämtliche Subklassen von Termin aus der Datenbank geladen, die der Session bzw. der Configuration bekannt sind. Als Typ kann natürlich auch ein Interface angegeben werden. Das Ergebnis bezieht dann alle persistenten Objekte mit ein, die dieses Interface implementieren. Polymorphe Abfragen werden in HQL immer identisch formuliert, unabhängig von der Abbildung der Vererbungshierarchie. Hibernate muss bei polymorphen Anfragen mehrere SQL-Statements an die Datenbank schicken. Es ist zu beachten, dass in den Resultaten nicht mit dem Befehl scroll geblättert werden kann! Nicht bei jeder Anfrage stehen zur Zeit der Programmierung die Kriterien fest. Häufig ist es notwendig, der Abfrage zur Laufzeit Parameter für die Kriterien zu übergeben. Hibernate bietet zwei Möglichkeiten, Parameter anzugeben. Ein Parameter kann in einer Abfrage mit einem Fragezeichen als Platzhalter angegeben werden. Anstelle dieses Platzhalters muss der Query zur Laufzeit vor der Ausführung der Abfrage ein Wert- oder ein kompatibles Objekt übergeben werden. Damit mehrere Parameter pro Abfrage benutzt werden können, übergibt man diese nummeriert der Query. Um Termine z.B. nach Ort zu suchen, würde man folgende Abfrage stellen: Polymorphe Abfragen Achtung Parameter String gesuchterOrt = xyz.getGesuchterOrt(); Query q = session.createQuery(“from Termin termin“ + “where termin.ort = ?“); q.setString(0, gesuchterOrt); List gefundeneTermine = q.list(); Bei Hibernate fängt die Nummerierung von Parametern immer bei 0 an und verhält sich somit wie Arrays oder die Collections aus dem JDK. Bei JDBC wird dagen die 1 für den ersten Parameter verwendet! Achtung 154 7 Datenbankabfragen Benannte Parameter Hibernate bietet auch die Möglichkeit, benannte Parameter zu benutzen. Ein benannter Parameter wird in der HQL mit einem Doppelpunkt eingeleitet. Diese benannten Platzhalter erhöhen die Lesbarkeit und verringern die Fehleranfälligkeit bei Abfragen. Die gleiche Abfrage mit einem benannten Parameter sieht wie folgt aus: String gesuchterOrt = xyz.getGesuchterOrt(); Query q = session.createQuery(“from Termin termin“ + “where termin.ort = :ort“); q.setString(“ort“, gesuchterOrt); List gefundeneTermine = q.list(); Entitäten als Parameter Einer Query kann auch eine Entität als Suchparameter übergeben werden. Für den Platzhalter wird der Query ein bereits geladenes Objekt eingesetzt. Hibernate erzeugt für die Query selbstständig die Verknüpfung in seiner SQL-Abfrage. Es kann damit z.B. nach allen Terminen für einen bestimmten Ort gesucht werden: Ort ort = xyz.getGesuchterOrt(); Query q = session.createQuery(“from Termin termin“ + “where termin.ort = :ort“); q.setEntity(“ort“, ort); List gefundeneTermine = q.list(); Sortierung Die Ergebnisse einer Abfrage können sortiert werden. Dazu wird nach der from- und der where-Klausel der Befehl order by angegeben, mit einem Attribut, nach dem sortiert werden soll. Das entspricht genau dem order by in SQL. Zusätzlich kann mit asc oder desc angegeben werden, ob auf- oder absteigend sortiert werden soll. Wird zu dem Befehl order by weder asc noch desc angegeben, wird aufsteigend sortiert (asc). Sind mehrere Sortierkriterien erwünscht, werden sie in absteigender Rangfolge mit Kommata getrennt angegeben. Die obige Anfrage suchte nach Terminen mit einem bestimmten Ort. Diese Anfrage sieht wie folgt aus, wenn sie um eine Sortierung nach dem Anfang erweitert wird: from Termin termin where termin.ort = :ort order by termin.anfang Joins Bei einer Abfrage, die sich auf mehrere Typen bezieht, werden die Tabellen zum kartesischen Produkt verknüpft. Alle möglichen Kombinationen der Zeilen der beteiligten Tabellen werden dabei kombiniert. Die Auswahl der gelieferten Kombinationen wird durch die unterschiedlichen Join-Typen bestimmt. Bei einem Join werden die gesuchten Typen in der from-Klausel angegeben. Die Basis der Abfrage bilden alle möglichen Kombinationen zwischen den angegebenen Typen. 7.1 Die Hibernate Query Language 155 Die folgende Abfrage liefert alle Termine und Benutzer, die sich in Berlin treffen. Die Ergebnisse sind Arrays mit jeweils dem Termin und einem der Teilnehmer. from Termin t inner join t.teilnehmer where t.ort = ’Berlin’ Um alle Termine zu bekommen, auch die ohne Teilnehmer, ist ein left outer join notwendig. In dem Ergebnis sind jetzt auch Termine ohne Teilnehmer enthalten. Das Ergebnis ist eine Liste von Arrays. Bei den Terminen ohne Teilnehmer ist das Feld des Arrays für die Teilnehmer null. from Termin t left outer join t.teilnehmer where t.ort = ’Berlin’ Ein right outer join liefert alle Kombinationen zwischen den beiden Tabellen und alle Einträge aus der zweiten Tabelle, für die es keinen entsprechenden Eintrag in der ersten Tabelle gibt. Die folgende Abfrage liefert eine Liste aller Benutzer, die einen Termin in Berlin eingetragen haben. Die Abfrage enthält keinen JoinBefehl. Hibernate macht intern einen Join über die FremdschlüsselTabelle für die Benutzer und die Einschränkung auf den Ort. Daher wird diese Art auch impliziter Join genannt. select t.teilnehmer from Termin t where t.ort = ’Berlin’ Es gibt die Möglichkeit, Queries in Mapping-Dateien zu definieren, anstatt diese im Progammcode zu schreiben. Diese können dann unabhängig vom Rest des Programms weiterentwickelt und optimiert werden. Eine solche Named Query wird direkt an der Session mit der Methode getNamedQuery bezogen. Ort ort = xyz.getGesuchterOrt(); Query q = session.getNamedQuery(“alleTermine“); q.setEntity(“ort“, ort); List alleTermine = q.list(); Wie in dem Beispiel zu sehen, entfällt der lange Text der eigentlichen Query im Programmcode. Als Nachteil ist sofort ersichtlich, dass die Namen der Parameter im Programmtext nicht erkennbar sind! Die Query wird in der Mapping-Datei durch das Element <query> definiert und mit dem Attribut name als »alleTermine« benannt. Die eigentliche Query wird zusätzlich in einem CDATA-Element verpackt: Benannte Queries 156 7 Datenbankabfragen <query name=“alleTermine“> <![CDATA[from Termin as t where t.ort = :ort ]]> </query> Projektion Genauere Angaben zu den Mapping-Dateien und deren Syntax sind in Kapitel 4 zu finden. Für Auswertungen und Berichte sind bei einer Abfrage nicht immer die kompletten Objekte mit allen Attributen notwendig. Auch wird bei diesen Abfragen nicht die transparente Persistenz benötigt, um geladene Objekte ändern zu können. Bei diesen Abfragen wird nur ein Teil der Werte aus den Ergebnissen benötigt. Das wird Projektion genannt. In der select-Klausel dürfen auch ausgewählte Attribute eines gesuchten Objektes angegeben werden. Diese Attribute werden in der Ergebnisliste dann als Array geliefert. Das Ergebnis ist nur ein aktuelles Abbild der Daten, es hat für Hibernate keinen Zustand und keine transparente Persistenz. Somit werden Änderungen an diesen Daten nicht wieder in die Datenbank geschrieben. Das folgende Beispiel liefert den Titel, die Anfangszeit und die Beschreibung aller Termine: Query q = session.createQuery(“select t.titel, “ “termin.anfang, termin.beschreibung “ + “from Termin as termin“); List<Object[]> uebersicht = q.list(); for (Object[] zeile : uebersicht) { String titel = (String) zeile[0]; Date anfang = (Date) zeile[1]; String beschreibung = (String) zeile[2]; ... } Gruppierungen und Zusammenfassungen In unserem Beispiel kann es gewünscht sein, dass wir in einem Bericht die Anzahl der Termine pro Ort ausgeben. Wir wollen die Frage beantworten, in welchen Städten die meisten Termine stattfinden, um damit eine Rangliste zu erzeugen. Anstatt alle Objekte zu laden, zu vergleichen, Termine zu addieren usw., können wir eine Abfrage mit Gruppierung benutzen. 7.1 Die Hibernate Query Language Query q = session.createQuery(“select termin.ort, “ “count(termin) “ + “from Termin as termin group by termin.ort“ + “ order by count(termin) desc“); List<Object[]> uebersicht = q.list(); for (Object[] zeile : uebersicht) { String ort = (String) zeile[0]; int anzahl = (Integer) zeile[1]; System.out.println("Stadt=" + ort + ", Anzahl Termine=" + anzahl); } In der Abfrage werden alle Orte ausgewählt und nach den Orten gruppiert. Die Aggregatsfunktion count() zählt alle Einträge, die in dieselbe Gruppierung fallen. In diesem Fall zählen wir die Termine mit dem gleichen Ort. Bei der order-by-Klausel darf auch die Aggregatsfunktion verwendet werden, damit nach deren Ergebnis sortiert wird. Das Beispiel sortiert absteigend nach der Anzahl der Termine pro Gruppierung. Neben count() gibt es noch die folgenden Aggregatsfunktionen: Aggregatsfunktionen Funktion Zweck count() Berechnet die Anzahl der Elemente in der Gruppierung min() Bestimmt das Minimum der Elemente in der Gruppierung max() Bestimmt das Maximum der Elemente in der Gruppierung sum() Summiert die Elemente in der Gruppierung avg() Berechnet den Mittelwert Eine Aggregatsfunktion ist entweder mit einer Gruppierung kombiniert oder bezieht sich auf alle Ergebnisse der Abfrage. Wenn keine Gruppierung angegeben wird, dann gibt es nur ein Ergebnis. Die in der select-Klausel ausgewählten Attribute dürfen in diesem Fall dann auch nur Aufrufe von Aggregatsfunktionen sein! So kann als Beispiel die Abfrage dienen, um zu erfahren, wie viele Termine in einer gegebenen Stadt geplant sind: Query query = session.createQuery("select count(t) “ + “from Termin as t where t.ort = :ort"); query.setString("ort", "Berlin"); int anzahl = (Integer) query.uniqueResult(); 157 158 7 Datenbankabfragen Blättern Nicht jede Anfrage liefert eine überschaubare Anzahl von Ergebnissen. Mitunter können die Ergebnisse sehr lange Listen sein, die komplett geladen den Speicher der Anwendung überlasten würden. Hibernate bietet zwei Ansätze, diesem Problem bei Abfragen zu begegnen. Das Ergebnis einer Query kann durch eine untere Schranke eingegrenzt werden. Dazu kann die maximale Anzahl von Ergebnissen angegeben werden. Query q = session.createQuery(“from Termin“); q.setFirstResult(90); q.setMaxResults(10); List<Termin> termine = q.list(); In diesem Beispiel wurde das Ergebnis auf zehn Termine eingeschränkt. Die ersten 90 Termine der Abfrage sollen ignoriert werden. Die Methode setFirstResult ist nullbasiert wie alle Methoden in Hibernate mit Bezug auf Indizes. Der zweite Lösungsansatz liefert Queries, in denen geblättert werden kann. Die Methode scroll liefert das Ergebnis als ScrollableResults. Das Ergebnis kann sehr variabel iteriert werden. Ergebnisse können auch direkt angesprungen werden. Spätestens bei dem Zugriff auf ein bestimmtes Objekt wird dieses erst aus der Datenbank geladen. Das ScrollableResults hat einen Zeiger auf eine Zeile des Ergebnisses, der mit den Methoden next, first, scroll, previous, last, beforeFirst, afterLast und setRowNumber verschoben werden kann. Entsprechend der select-Klausel der Anfrage können in jeder Zeile der Ergebnisse mehrere Objekte sein. Auf das aktuell selektierte Ergebnis wird immer indiziert zugegriffen, auch wenn pro Zeile nur ein Objekt ausgewählt wird. Folgende Abfrage liefert eine Übersicht der Benutzer, bei der nur jeder zwanzigste angegeben wird: Query q = session.createQuery(“select b.nachname “ + “from Benutzer b “ + “order by b.nachname“); ScrollableResults ergebnis = q.scroll(); List<String> benutzer = new ArrayList<String>(); if (ergebnis.first()) { do { benutzer.add(ergebnis.getString(0)); } while(ergebnis.scroll(20)); } 7.2 Typsichere Abfragen mit Criteria Wird über ein ScrollableResults iteriert und auf enthaltene Objekte zugegriffen, muss die Session noch aktiv sein. Das ScrollableResults kann nur benutzt werden, wenn der eingesetzte JDBC-Treiber Blättern unterstützt. Bisher wurden mit der HQL nur Objekte und Daten ausgelesen. Es ist auch seit Hibernate 3 möglich, mit einer Abfrage zu ändern und zu löschen. Sollen z.B. alle Termine aus Berlin nach Hamburg verlegt werden, kann das wie folgt passieren: 159 Ändern und Löschen Query q = session.createQuery(“update Termin “ + “set ort = :neuerOrt where ort = :alterOrt “); q.setString(“alterOrt“, “Berlin“); q.setString(“neuerOrt“, “Hamburg“); q.executeUpdate(); Auf das Schlüsselwort update folgt der zu ändernde Typ. Dem folgen der Befehl set und die Zuweisung der zu ändernden Attribute. Abschließend kann mit einem where ein Kriterium angegeben, das die Änderung einschränkt. Mit der Methode executeUpdate wird diese Form einer verändernden Abfrage ausgeführt. Die Methode liefert als Ergebnis die Anzahl der veränderten Objekte. Mit der HQL können auch Objekte gelöscht werden, die bestimmten Kriterien entsprechen. Somit können auch Objekte gelöscht werden, die nicht geladen sind. Alle Termine, die in München stattfinden sollen, werden mit dem folgenden Beispiel gelöscht: Query q = session.createQuery(“delete Termin “ + “where ort = :ort“); q.setString(“ort“, “München“); q.executeUpdate(); Auch bei dem Löschen wird die verändernde Query mit der Methode executeUpdate ausgeführt. Bei einer ändernden Abfrage dürfen sowohl die Änderung als auch das Kriterium sich dabei nur auf einen Typ beziehen. Subtypen und enthaltene Objekte dürfen nicht mit in eine ändernde Abfrage einbezogen werden! 7.2 Typsichere Abfragen mit Criteria Hibernate bietet mit Criterias eine programmatische Möglichkeit, Abfragen zu definieren. An der Session kann ein Criteria erzeugt wer- Achtung 160 7 Datenbankabfragen den, dabei muss der Ergebnistyp angegeben werden. Das einfachste Criteria, das alle Benutzer als Liste liefert, ist das folgende: Criteria criteria = session.createCriteria(Benutzer.class); List<Benutzer> benutzer = criteria.list(); Es sind bei den programmatischen Abfragen mit Criterias Einschränkungen der Ergebnismenge durch Kriterien möglich. Die Klasse Restrictions aus dem Package org.hibernate.criterion ist eine Fabrik für solche Kriterien. Die folgende Abfrage liefert alle Termine aus Hamburg: Criteria crit = session.createCriteria(Termin.class); c.add(Restrictions.eq("ort", "Hamburg")); List<Termin> termine = criteria.list(); Diese Kriterien liefern als Rückgabewert immer wieder das geänderte Criteria-Objekt. Somit können komplexere Abfragen mit einem JavaStatement definiert werden. Das folgende Beispiel zeigt, wie mit einem einzigen Statement alle Termine aus Hamburg als Liste geliefert werden, die in der Beschreibung »Hibernate« enthalten: List liste = session.createCriteria(Termin.class) .add(Restrictions.eq("ort", "Hamburg")) .add(Restrictions.like(“beschreibung“, “%Hibernate%“)) .list(); Diese Abfrage kann vereinfacht werden durch das neue Feature des statischen Imports in Java 5. Damit können die statischen Methoden und Attribute der importierten Klasse ohne Qualifizierung durch den Namen der Klasse benutzt werden. import static org.hibernate.criterion.Restrictions.*; .... List liste = session.createCriteria(Termin.class) .add(eq("ort", "Hamburg")) .add(like(“beschreibung“, “%essen%“)) .list(); Gruppierung Mit der Criteria-API können auch Gruppierungen in Abfragen erzeugt werden. Ein Beispiel zur Gruppierung hatte die Anzahl der Termine pro Ort abgefragt. Dem Criteria muss eine Projection gesetzt werden. int anzahl = (Integer) session.createCriteria(Termin.class) .setProjection(Projections.rowCount()) .add(like("ort", "Berlin")) .uniqueResult(); 7.3 Abfragen mit SQL 161 Das Beispiel hat eine Gruppierung auf die gesamte Ergebnismenge. Um mit Criterias für jeden Ort die Anzahl der Termine auszugeben, wird nach dem Ort gruppiert. Um eine Projektion auf mehrere Attribute und Aggregierungen zu erreichen, muss eine ProjectionList benutzt werden. Das folgende Beispiel nutzt die Möglichkeit von Hibernate, die Befehle »aneinander zu ketten«. So liefert z.B. die Methode setProjection dasselbe Criteria als Ergebnis, an dem sie aufgerufen wurde: List<Object[]> list = session.createCriteria(Termin.class) .setProjection( Projections.projectionList() .add(Projections.property("ort")) .add(Projections.rowCount()) .add(Projections.groupProperty("ort"))).list(); for (Object[] zeile : list) { String ort = (String) zeile[0]; int anzahl = (Integer) zeile[1]; ... } Abfragen mit Criterias bieten einen großen Vorteil, denn durch die Angabe des Typs als Class-Objekt bei der Erzeugung des Criterias bieten sie Typsicherheit. Werden Änderungen an dem Namen des Typs gemacht, fällt das schon zur Kompilierzeit auf. Für die Erstellung einer Abfrage ist keine Kenntnis von HQL oder von SQL notwendig. Wenn Abfragen optionale Kriterien enthalten, ist das mit Criterias einfacher zu lösen als mit HQL, wobei dort der Abfragetext jeweils angepasst werden muss. Criterias unterstützen keine Joins, sie sind somit nicht so mächtig wie Abfragen mit der HQL. Der Vorteil der Typsicherheit gilt nicht für die Angabe der Properties, diese müssen als String angegeben werden. Sie sind somit anfällig gegenüber Änderungen an den persistierten Klassen und werden zur Kompilierzeit nicht aufgedeckt. Abfragen mit Criterias sind nicht so »übersichtlich« wie HQL-Abfragen. 7.3 Abfragen mit SQL An der Session kann eine SQL-Query erzeugt werden. Das kann für mehrere Fälle sinnvoll sein: ■ Stored Procedures sollen an der Datenbank aufgerufen werden. ■ Auf die spezielle Datenbank optimierte Abfragen sollen gerufen werden. ■ Bei der Umstellung von alten SQL-Abfragen auf HQL Vor- und Nachteile 162 7 Datenbankabfragen Damit Hibernate den Bezug von der Abfrage zu den verwalteten persistenten Klassen bekommt, müssen diese der Query angegeben werden. In dem einfachsten Beispiel sollen mit SQL alle Termine ausgegeben werden: SQLQuery q = session.createSQLQuery(“select {termin.*} “ + “ from TERMIN {termin}“); q.addEntity(“termin“, Termin.class); List termine = q.list(); Named Queries Als Erstes fällt die select-Klausel auf. Die geschweiften Klammern sind Platzhalter für Hibernate. Und in der from-Klausel wird der Tabellenname angegeben, nicht der Typ! In dem Beispiel werden Objekte vom Typ Termin mit allen Attributen erwartet, daher der Stern hinter dem Platzhalter termin. Es wäre hier auch möglich, durch die Angabe von Propertys an dieser Stelle nur einzelne Attribute aus der Datenbank auszulesen. An Stelle der Platzhalter werden die Spalten aus der Datenbank eingesetzt. Dies muss mit einer Benennung und Reihenfolge geschehen, die Hibernate kennt. Ansonsten könnte Hibernate aus dem Ergebnis kein Objekt des Typs Termin erzeugen. Hibernate muss den Hinweis bekommen, dass Objekte des Typs Termin gesucht werden. Durch den Aufruf der Methode addEntity wird Hibernate dieser Hinweis gegeben, und der Typ Termin wird mit dem Platzhalter termin verbunden. Ansonsten ist SQLQuery eine Spezialisierung – und Subklasse – einer Query und kann genauso zur Auflistung des Ergebnisses verwendet werden. SQL-Queries können ähnlich wie HQL-Queries in der MappingDatei definiert werden. Die Query wird in einem sql-query-Element mit einem Namen verpackt. <sql-query name=“sqlAlleTermine“> <return alias=“termin“ class=“de.abc.Termin“ /> <![CDATA[ select {termin.*} from TERMIN {termin} ]]> </sql-query> Diese Query kann genau wie benannte HQL-Queries an der Session mit der Methode getNamedQuery abgerufen werden. Einer SQL-Query können auch benannte und nummerierte Parameter mit übergeben werden. Der Typ des Rückgabewertes wird einer benannten Query nicht mehr im Programmcode gesetzt, er ist in der Definition mit dem Element return inklusive Alias angegeben. 7.4 Filter Aus dem Beispiel ist schon ersichtlich, dass Abfragen mit HQL oder Criterias gegenüber Abfragen mit SQL vorzuziehen sind. Es ist umständlicher und unsicherer als HQL und Criterias, denn z.B. die Namen der Tabellen und Spalten müssen bekannt sein, polymorphe Abfragen und implizite Joins sind nicht möglich. SQL-Abfragen können in einer Migration für den Umstieg auf Hibernate geeignet sein. Außerdem ist es seit Hibernate 3 möglich, mit der Hilfe benannter SQL-Queries stored procedures aufzurufen. 7.4 Filter Filter bieten die Möglichkeit, für eine Session sämtliche Abfragen auf Ergebnisse mit bestimmten Kriterien einzuschränken. Ein Filter wird in der Mapping-Datei definiert. Nach der Mapping-Definition der Klassen gibt man dort den Namen und die Parameter des Filters an: </hibernate-mapping> ..... </class> <filter-def name=“nurEinOrt“> <filter-param name=“ort“ type=“string“ /> </filter-def>... </hibernate-mapping> Hier wurden nur der Name und die Parameter eines Filters definiert. Innerhalb des Mappings für persistente Klassen und Collections kann dieser Filter mit Kriterien gefüllt werden. </hibernate-mapping> ... <class name=“Termin“> ..... <property name="ort"/> <filter name="nurEinOrt" condition="ort = :ort"/> </class> <filter-def name=“nurEinOrt“> <filter-param name=“ort“ type=“string“ /> </filter-def>... </hibernate-mapping> Innerhalb des Mappings für die Klasse Termin wird jetzt für den Filter mit dem Namen nurEinOrt eine Bedingung angegeben. Dieser Filter bestimmt die zurückgelieferten Termine, die von jeder Abfrage mit diesem Filter geliefert werden. In dem Beispiel müssen Ergebnisse in der Spalte ort den gleichen Wert haben wie der Parameter :ort. 163 Vor- und Nachteile 164 7 Datenbankabfragen Achtung Der Bezeichner »ort« für das obige Kriterium bezieht sich nicht auf das Property mit dem Namen Ort, sondern die gleichnamige Tabellenspalte! Filterkriterien beziehen sich direkt auf die Namen der Spalten und nicht – wie sonst in Hibernate üblich – auf die Namen der Properties. Da Filter in der Mapping-Datei definiert werden, ist an dieser Stelle der Name der entsprechenden Tabellenspalte bekannt. Um den eben definierten Filter zu aktivieren, muss dieser an der Session mit enableFilter aktiviert werden. Zusätzlich müssen an dem Filter die benötigten Parameter gesetzt werden. Filter ortFilter = session.enableFilter("nurEinOrt"); ortFilter.setParameter("ort", "Hamburg"); List termine = session.createQuery(“from Termin“).list(); Hier in dem Beispiel wurden nur Termine aus Hamburg geliefert. Der Filter beschränkt nur die Ergebnisse von sondierenden Abfragen. Ist in dem System noch ein Objekt, das dem Filter nicht entspricht, z.B. ein Termin aus München, dann kann dieser mit delete an der Session gelöscht werden, obwohl der Filter aktiv ist. Genauso können noch Objekte mit ihrer ID geladen werden, deren Attribute nicht den Filterkriterien entsprechen. Wird ein Filter nicht mehr benötigt, deaktiviert man ihn mit der Methode disableFilter und dem Namen des Filters: session.disableFilter("nurEinOrt"); Filter können in der Praxis mehrere sinnvolle Einsatzgebiete finden. In unserem Beispiel könnte man z.B. für bestimmte Umstände sämtliche Termine herausfiltern, die in der Vergangenheit stattfanden. 7.5 Referenzen [Hibernate] http://www.hibernate.org Die Hibernate-Website mit der Dokumentation bietet sehr viel Information zu Abfragen.