Titel   Inhalt   Suchen   Index   DOC  Handbuch der Java-Programmierung, 7. Auflage
 <<    <     >    >>   API  Kapitel 47 - Objektorientierte Persistenz

47.5 Objektorientierte Datenbankabfragen



Neben den Basisoperationen Anlegen, Speichern und Löschen eines Datensatzes ist die Suche nach Datensätzen eine weitere Hauptaufgabe der Datenbankzugriffsschicht. In Abschnitt 44.4.7 haben wir hierfür die Datenbankanfragesprache SQL kennengelernt. Mit der sogenannten JPA Query Language steht eine solche Anfragesprache auch für die objektorientierte Zugriffsschicht zur Verfügung.

47.5.1 Suche nach Datensätzen

Die Anfragesprache für Persistenz Beans orientiert sich stark an SQL und wird über ein spezielles Anfrageobjekt Query ausgeführt. Nachdem beispielsweise Listing 47.16 erfolgreich ausgeführt und die Datensätze für das Verzeichnis und die zwei anhängenden Datei-Objekte erstellt wurden, können wir mit Hilfe eines Query-Objekts die abgespeicherten Verzeichnissätze auslesen und beispielsweise auf der Kommandozeile ausgeben:

001 /* Listing4717.java */
002 
003 import javax.persistence.*;
004 import java.util.List;
005 
006 public class Listing4717
007 {
008   public static void main(String[] args)
009   {
010     //Erzeugen einer EntityManagerFactory mit Hilfe des symbolischen
011     //Namens aus dem Persistenz Descriptor (persistence.xml)
012     EntityManagerFactory emf =
013       Persistence.createEntityManagerFactory("persistenceExample");
014 
015     //Erzeugen eines EntityManagers für den Zugriff auf
016     //die Datenbank
017     EntityManager manager = emf.createEntityManager();
018 
019     //Beginn einer neuen Transanktion
020     EntityTransaction tx = manager.getTransaction();
021     tx.begin();
022 
023     //Erzeugen einer Anfrage
024     Query query = manager.createQuery("select d from Verzeichnis d"); 
025 
026     //Ausführen der Anfrage und ermittel der Ergebnisliste
027     List<Verzeichnis> directories = query.getResultList();  
028         
029     //Ausgabe der Datensätze
030     for(Verzeichnis directory : directories) {
031       System.out.println(directory.toString());
032     }
033         
034     //Abschluss der Transaktion mit einem Commit
035     tx.commit();
036         
037     //Freigabe der Ressourcen des EntityManagers
038     manager.close();
039 
040     //Schließen der EntityManagerFactory und Freigeben der
041     //belegten Ressourcen
042     emf.close();
043   }
044 }
Listing4717.java
Listing 47.17: Suche nach Datensätzen

Das Query-Objekt in Zeile 024 wird mit Hilfe einer SQL-ähnlichen Anfrage erzeugt und stellt das JPA-Pendant zu einem Statement dar, wie wir sie in Abschnitt 44.4.5 kennengelernt haben. Die allgemeine Syntax für SQL- und JPA-Anfragen finden Sie in Abschnitt 44.4.7. Im Unterschied zu den SQL-Anfragen operiert das Java Persistenz API allerdings ausschließlich auf Java-Objekten (wie beispielsweise Verzeichnis) - der Name der Datenbanktabelle spielt keine Rolle.

Durch Aufruf der Methode getResultList in Zeile 027 wird die Anfrage ausgeführt und das Ergebnis als Liste zurückgegeben. Hierbei muss der Typ der Liste natürlich zur formulierten Anfrage passen, andernfalls kommt es zu einer ClassCastException.

47.5.2 Eigenschaftsbasierte Suche nach Datensätzen

Bei einer umfangreichen Datenbank ist es natürlich indiskutabel, zunächst alle Datensätze einer Tabelle auszulesen und dann das gewünschte Objekt in eine Schleife zu ermitteln. Deshalb lässt sich auch im JPA die Suche über eine Where-Klausel einschränken. Ähnlich wie bei einem PreparedStatement in Abschnitt 44.4.6 verwenden wir dazu einen Platzhalter als Parameter für die Elemente des Suchausdrucks:

001 /* Listing4718.java */
002 
003 import javax.persistence.*;
004 
005 public class Listing4718
006 {
007   public static void main(String[] args)
008   {
009     //Erzeugen einer EntityManagerFactory mit Hilfe des symbolischen
010     //Namens aus dem Persistenz Descriptor (persistence.xml)
011     EntityManagerFactory emf =
012       Persistence.createEntityManagerFactory("persistenceExample");
013 
014     //Erzeugen eines EntityManagers für den Zugriff auf
015     //die Datenbank
016     EntityManager manager = emf.createEntityManager();
017 
018     //Beginn einer neuen Transanktion
019     EntityTransaction tx = manager.getTransaction();
020     tx.begin();
021 
022     //Erzeugen einer Anfrage mit dem Parameter :name
023     Query query = manager.createQuery(                           
024       "select d from Verzeichnis d where d.name = :name"
025     );
026 
027     //Setzen des Parameters
028     query.setParameter("name", "temp");                        
029 
030     //Auslesen eines einzelnen Ergenisses
031     Verzeichnis directory = (Verzeichnis) query.getSingleResult(); 
032     System.out.println(directory.toString());
033 
034     //Abschluss der Transaktion mit einem Commit
035     tx.commit();
036 
037     //Freigabe der Ressourcen des EntityManagers
038     manager.close();
039 
040     //Schließen der EntityManagerFactory und Freigeben der
041     //belegten Ressourcen
042     emf.close();    
043   }
044 }
Listing4718.java
Listing 47.18: Suche nach Datensätzen mit einer Where-Klausel

In Zeile 023 wird eine Query mit einer Einschränkung auf den Namen des Verzeichnisses definiert. Dabei ist zu beachten, dass der Name des Java-Attributs (name) und nicht der Name der Spalte in der Datenbanktabelle (dname) verwendet wird. Der Name des Platzhalters ist frei wählbar und wird mit der Escape-Sequenz : eingeleitet.

In Zeile 028 wird der zuvor definierte Parameter mit dem Wert "temp" gefüllt, der in der anschließenden Abfrage an seiner Stelle eingesetzt wird. Hier wird nur der Name des Parameters ohne führenden Doppelpunkt verwendet.

Um die Anfrage auszuführen, könnten wir nun analog zu Listing 47.17 die Methode getResultList aufrufen. Da wir nur einen Treffer erhalten, würde sie uns eine Liste der Länge 1 zurückgeben. Für diesen Fall definiert das Java Persistenz API mit der Methode getSingleResult eine Abkürzung, die den gesuchten Datensatz direkt zurückgibt.

47.5.3 Definition von Standardanfragen

In größeren Programmen werden Datenbankanfragen meist nicht nur an einer einzigen Stelle benötigt, sondern sie sollen in verschiedenen Programmteilen wiederverwendet werden. Für diese Anwendungsfälle sieht das JPA die Definition von Standardanfragen in Form einer sogenannten Named Query vor.

Eine NamedQuery ist im Grunde nichts anders als eine parametrisierte Anfrage, die an einer Persistenz Bean definiert und anschließend nur noch über ihren symbolischen Namen referenziert wird. Das folgende Listing zeigt die die Anfrage aus dem letzten Abschnitt als Named Query am Verzeichnis-Objekt:

001 /* Verzeichnis.java */
002 
003 import javax.persistence.*;
004 import java.util.List;
005 import java.util.LinkedList;
006 
007 /**
008  * Diese Klasse repräsentiert die Tabelle 'dir' der 'DirDB'
009  * Jede Instanz der Klasse repräsentiert wiederum einen
010  * Datensatz
011  */
012 @Entity
013 @Table( name = "dir" )
014 @NamedQuery(name = "DIRECTORY_BY_NAME",          
015             query = "select d from Verzeichnis d where d.name = :name")
016 public class Verzeichnis 
017 {
018   @Id
019   @GeneratedValue
020   @Column(name = "did")
021   private Integer id;
022 
023   @Column(name = "dname")
024   private String name;
025 
026   @OneToMany(cascade = CascadeType.ALL, mappedBy = "directory")
027   @OrderBy(value = "name")
028   private List<Datei> files;
029 
030   /**
031    * Geschützter Minimalkonstruktor zur Verwendung von Hibernate
032    */
033   protected Verzeichnis() {
034   }
035 
036   /**
037    * Öffentlicher Konstruktor zur Verwendung durch den Entwickler.
038    * Dieser Konstruktor definiert alle Pflichtfelder der JavaBean.
039    * @param name - Name des Verzeichniseintrags
040      */
041   public Verzeichnis(String name)
042   {
043     this.name = name;
044     this.files = new LinkedList<Datei>();
045   }
046 
047   public Integer getId()
048   {
049     return id;
050   }
051 
052   protected void setId(Integer id)
053   {
054     this.id = id;
055   }
056 
057   public String getName()
058   {
059     return name;
060   }
061 
062   public void setName(String name)
063   {
064     this.name = name;
065   }
066 
067   public List<Datei> getFiles()
068   {
069     return files;
070   }
071 
072   public void setFiles(List<Datei> files)
073   {
074     this.files = files;
075   }
076 
077   public boolean equals(Object o)
078   {
079     if (this == o) return true;
080     if (o == null || getClass() != o.getClass()) return false;
081 
082     Verzeichnis dir = (Verzeichnis) o;
083     return !(id != null ? !id.equals(dir.id) : dir.id != null);
084   }
085 
086   public int hashCode()
087   {
088     return id != null ? id.hashCode() : 0;
089   }
090 
091   public String toString()
092   {
093     return "Directory[id:"+ id + ", name:" + name +  "]";
094   }
095 }
Verzeichnis.java
Listing 47.19: Definition einer Named Query

In Zeile 014 wird die Anfrage mit Hilfe der Annotation @NamedQuery zentral definiert und mit dem (frei wählbaren) symbolischen Namen DIRECTORY_BY_NAME versehen. Das folgende Listing zeigt, wie die Named Query aufgerufen werden kann:

001 /* Listing4720.java */
002 
003 import javax.persistence.*;
004 
005 public class Listing4720
006 {
007   public static void main(String[] args)
008   {
009     //Erzeugen einer EntityManagerFactory mit Hilfe des symbolischen
010     //Namens aus dem Persistenz Descriptor (persistence.xml)
011     EntityManagerFactory emf =
012       Persistence.createEntityManagerFactory("persistenceExample");
013 
014     //Erzeugen eines EntityManagers für den Zugriff auf
015     //die Datenbank
016     EntityManager manager = emf.createEntityManager();
017 
018     //Beginn einer neuen Transanktion
019     EntityTransaction tx = manager.getTransaction();
020     tx.begin();
021 
022     //Referenzieren der benannten Anfrage mit dem Parameter :name
023     Query query = manager.createNamedQuery("DIRECTORY_BY_NAME"); 
024 
025     //Setzen des Parameters
026     query.setParameter("name", "temp");
027 
028     //Auslesen eines einzelnen Ergebnisses
029     Verzeichnis directory = (Verzeichnis) query.getSingleResult();
030     System.out.println(directory.toString());
031 
032     //Abschluss der Transaktion mit einem Commit
033     tx.commit();
034 
035     //Freigabe der Ressourcen des EntityManagers
036     manager.close();
037 
038     //Schließen der EntityManagerFactory und Freigeben der
039     //belegten Ressourcen
040     emf.close();
041   }
042 }
Listing4720.java
Listing 47.20: Suche nach Datensätzen mit benannten Anfragen

In Zeile 023 wird beim Aufruf der Methode createNamedQuery der Name der benannten Anfrage synonym zur Anfrage verwendet. Anschließend werden die definierten Parameter gesetzt und schließlich wird die Anfrage mit den Methoden getSingleResult oder getResultList ausgeführt.

Um mehrere benannte Anfragen an einer Entity zu definieren, fasst man die @NamedQuery-Annotationen einfach als Attribute einer einzelnen @NamedQueries-Annotation zusammen.

 Tip 


 Titel   Inhalt   Suchen   Index   DOC  Handbuch der Java-Programmierung, 7. Auflage, Addison Wesley, Version 7.0
 <<    <     >    >>   API  © 1998, 2011 Guido Krüger & Heiko Hansen, http://www.javabuch.de