7 Datenbankabfragen

Werbung
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.
Herunterladen