Abfragen von JPA-Entitäten mit JPQL und nativem SQL
Beispielanwendung
Die im Artikel beschriebenen Codeausschnitte stammen aus den Java-Quelldateien, die in der dem Artikel beigefügten Beispielanwendung verwendet werden. Wenn Sie sich das Beispielarchiv ansehen, stellen Sie möglicherweise fest, dass es sich um eine einfache Webanwendung handelt, die auf den Technologien Java Servlet und Java Persistence API basiert. Der Einfachheit halber werden keine Enterprise-Beans verwendet, sondern JPQL-Abfragen direkt aus Servlets ausgegeben. Dies bedeutet jedoch nicht, dass Sie die hier in Enterprise Beans beschriebenen JPQL—Abfragen nicht verwenden können – Sie können JPQL-Abfragen in beliebigen Java EE-Komponenten definieren.
Abbildung 1 veranschaulicht die Beispielstruktur der Entitäten. Wie Sie sehen können, enthält es eine Reihe von Entitäten, die miteinander in Beziehung stehen und Beziehungen verschiedener Typen aufweisen. Eine solche verzweigte Struktur ist erforderlich, um die Verwendung von JPQL-Join-Abfragen zu veranschaulichen, die im Abschnitt Definieren von JPQL-Joins später im Artikel erläutert werden.
Abbildung 1 Beziehungen zwischen den in der Beispielanwendung verwendeten Entitäten
Eine detaillierte Anleitung zum Einrichten und anschließenden Starten der Beispielanwendung finden Sie in der Readme-Datei.txt-Datei im Stammverzeichnis des Beispielarchivs.
Verwenden von JPQL in Java EE-Anwendungen
Wenn Sie praktische Erfahrung mit Datenbanken haben, haben Sie höchstwahrscheinlich bereits SQL kennengelernt, das Standardwerkzeug, das eine Reihe von Anweisungen für den Zugriff auf und die Bearbeitung von Informationen in relationalen Datenbanken bietet. Tatsächlich gibt es viele Ähnlichkeiten zwischen JPQL und SQL. Beide werden verwendet, um auf lange Sicht auf Datenbankdaten zuzugreifen und diese zu manipulieren. Und beide verwenden nicht prozedurale Anweisungen – Befehle, die von einem speziellen Interpreter erkannt werden. Darüber hinaus ähnelt JPQL in seiner Syntax SQL.
Der Hauptunterschied zwischen JPQL und SQL besteht darin, dass ersteres sich mit JPA-Entitäten befasst, während letzteres sich direkt mit relationalen Daten befasst. Als Java-Entwickler möchten Sie vielleicht auch erfahren, dass die Verwendung von JPQL im Gegensatz zu SQL / JDBC die Verwendung der JDBC—API aus Ihrem Java-Code überflüssig macht.
Mit JPQL können Sie Abfragen mithilfe einer der folgenden drei Anweisungen definieren: SELECT, UPDATE oder DELETE. Es ist interessant festzustellen, dass die EntityManager-API-Schnittstelle Methoden bietet, mit denen auch Vorgänge zum Abrufen, Aktualisieren und Löschen von Entitäten ausgeführt werden können. Dies sind insbesondere die Methoden find, merge und remove. Die Verwendung dieser Methoden ist jedoch normalerweise auf eine einzelne Entitätsinstanz beschränkt, es sei denn, die Kaskadierung wird natürlich wirksam. Im Gegensatz dazu haben JPQL-Anweisungen keine solche Einschränkung — Sie können Massenaktualisierungs- und Löschvorgänge für Entitätssätze definieren und Abfragen definieren, die Sätze von Entitätsinstanzen zurückgeben.
Um eine JPQL-Abfrage in Ihrem Java-Code auszugeben, müssen Sie die entsprechenden Methoden der EntityManager-API und der Query-API verwenden und die folgenden allgemeinen Schritte ausführen:
- 1. Rufen Sie eine Instanz von EntityManager mithilfe der Injektion oder explizit über eine EntityManagerFactory-Instanz ab.
- 2. Erstellen Sie eine Instanz von Query, indem Sie die entsprechende EntityManager-Methode aufrufen, z. B. createQuery.
- 3. Legen Sie einen oder mehrere Abfrageparameter mithilfe der setParameter-Methode einer entsprechenden Abfrage fest.
- 4. Legen Sie bei Bedarf mithilfe der Abfragemethoden setMaxResults und/ oder setFirstResult die maximale Anzahl der abzurufenden Instanzen fest und / oder geben Sie die Position der ersten abzurufenden Instanz an.
- 5. Legen Sie bei Bedarf mithilfe der Methode der setHint-Abfrage einen herstellerspezifischen Hinweis fest.
- 6. Legen Sie bei Bedarf den Flush-Modus für die Abfrageausführung mit der setFlushMode-Abfragemethode fest und überschreiben Sie den Flush-Modus des Entity Managers.
- 7. Führen Sie die Abfrage mit der entsprechenden Abfragemethode aus: getSingleResult oder getResultList. Bei einem Update- oder Delete-Vorgang müssen Sie jedoch die executeUpdate-Methode verwenden, die die Anzahl der aktualisierten oder gelöschten Entitätsinstanzen zurückgibt.
Die vollständige Liste der EntityManager-Schnittstellenmethoden sowie der Query API-Schnittstellenmethoden finden Sie im Dokument Enterprise JavaBeans 3.0 Specification: Java Persistence API , das Teil von JSR-220 ist.
Nun, da Sie eine grobe Vorstellung davon haben, wie Sie eine JPQL-Abfrage erstellen und dann ausgeben können, möchten Sie vielleicht einige praktische Beispiele sehen. Das folgende Codefragment stammt aus der doGet-Methode eines Servlets, die mithilfe einer JPQL-Abfrage Informationen zu allen Kunden abruft, die in der zugrunde liegenden relationalen Tabelle gespeichert sind, die der in der Abfrage angegebenen Kundenentität zugeordnet ist.
... @PersistenceUnit private EntityManagerFactory emf; public void doGet( ... EntityManager em = emf.createEntityManager(); PrintWriter out = response.getWriter(); List<Customer> arr_cust = (List<Customer>)em.createQuery("SELECT c FROM Customer c") .getResultList(); out.println("List of all customers: "+""); Iterator i = arr_cust.iterator(); Customer cust; while (i.hasNext()) { cust = (Customer) i.next(); out.println(cust.getCust_id()+""); out.println(cust.getCust_name()+""); out.println(cust.getEmail()+""); out.println(cust.getPhone()+""); out.println("----------------" + ""); } ...
Von besonderem Interesse sind hier die createQuery-Methode der EntityManager-Instanz und die getResultList-Methode der Query-Instanz. Die createQuery des EntityManagers wird zum Erstellen der Abfrageinstanz verwendet, deren getResultList-Methode dann zum Ausführen der JPQL-Abfrage verwendet wird, die als Parameter an createQuery übergeben wird. Wie Sie sich vorstellen können, gibt die getResultList-Methode der Abfrage das Ergebnis einer Abfrage als Liste zurück, deren Elemente in diesem speziellen Beispiel in den Typ Customer umgewandelt werden.
Wenn Sie ein einzelnes Ergebnis abrufen müssen, bietet die Abfrage-API-Schnittstelle die Methode getSingleResult, wie im folgenden Beispiel gezeigt. Beachten Sie jedoch, dass die Verwendung von getSingleResult eine Ausnahme verursacht, wenn Sie mehrere Ergebnisse zurückerhalten.
Auch dieses Beispiel veranschaulicht die Verwendung der setParameter-Methode der Abfrage, mit der Sie ein Argument an einen Abfrageparameter binden können. Mit setParameter können Sie sowohl benannte als auch Positionsparameter binden. Hier binden Sie jedoch einen benannten Parameter.
... Integer cust_id =2; Customer cust = (Customer)em.createQuery("SELECT c FROM Customer c WHERE c.cust_id=:cust_id") .setParameter("cust_id", cust_id) .getSingleResult(); out.println("Customer with id "+cust.getCust_id()+" is: "+ cust.getCust_name()+""); ...
Es ist interessant festzustellen, dass die Verwendung einer SELECT JPQL-Anweisung nicht der einzige Weg ist, um eine einzelne Entitätsinstanz abzurufen. Alternativ können Sie die find-Methode des EntityManagers verwenden, mit der Sie eine einzelne Entitätsinstanz basierend auf der als Parameter übergebenen Entitäts-ID abrufen können.
In einigen Situationen müssen Sie möglicherweise nur einige Informationen von der oder den Zielentitätsinstanzen abrufen und eine JPQL-Abfrage für ein bestimmtes Entitätsfeld oder bestimmte Entitätsfelder definieren. So würde das obige Snippet aussehen, wenn Sie nur den Wert des Felds cust_name der hier abgefragten Instanz der Kundenentität abrufen müssen:
... Integer cust_id =2; String cust_name = (String)em.createQuery("SELECT c.cust_name FROM Customer c WHERE c.cust_id=:cust_id") .setParameter("cust_id", cust_id) .getSingleResult(); out.println("Customer with id "+cust_id+" is: "+cust_name+""); ...
In ähnlicher Weise können Sie den folgenden Code verwenden, um die gesamte Liste der Kundennamen abzurufen:
... List<String> arr_cust_name = (List<String>)em.createQuery("SELECT c.cust_name FROM Customer c") .getResultList(); out.println("List of all customers: "+"<br/>"); Iterator i = arr_cust_name.iterator(); String cust_name; while (i.hasNext()) { cust_name = (String) i.next(); out.println(cust_name+"<br/>"); } ...
Wenn Sie zu SQL zurückkehren, erinnern Sie sich möglicherweise daran, dass die Select-Liste einer SQL-Abfrage aus mehreren Feldern aus der Tabelle oder den Tabellen bestehen kann, die in der FROM-Klausel angegeben sind. In JPQL können Sie auch eine einfache Auswahlliste verwenden, indem Sie die Daten nur aus den gewünschten Entitätsfeldern auswählen. In diesem Fall müssen Sie jedoch die Klasse erstellen, in die Sie das Abfrageergebnis umwandeln möchten. Im folgenden Abschnitt sehen Sie ein Beispiel für eine JPQL-Join-Abfrage, deren Auswahlliste aus den Feldern besteht, die von mehr als einer Entität abgeleitet sind.
Definieren von JPQL-Joins
Wie SQL können Sie mit JPQL Join-Abfragen definieren. In SQL definieren Sie jedoch normalerweise einen Join, der Datensätze aus zwei oder mehr Tabellen und/oder Ansichten kombiniert, einschließlich nur erforderlicher Felder aus diesen Tabellen und Ansichten in der Auswahlliste der Join-Abfrage. Im Gegensatz dazu enthält die Auswahlliste einer JPQL-Join-Abfrage normalerweise eine einzelne Entität oder sogar ein einzelnes Entitätsfeld. Der Grund dafür liegt in der Natur der JPA-Technologie. Sobald Sie eine Entitätsinstanz erhalten haben, können Sie mithilfe der entsprechenden Getter-Methoden zu den zugehörigen Instanzen navigieren. Dieser Ansatz macht es für Sie unnötig, eine Abfrage zu definieren, die alle relevanten verwandten Entitätsinstanzen auf einmal zurückgibt.
Um beispielsweise Informationen über Bestellungen zusammen mit ihren Werbebuchungen in SQL zu erhalten, müssten Sie eine Join-Abfrage sowohl für die Tabellen purchaseOrders als auch orderLineItems definieren und die Felder aus beiden Tabellen in der Auswahlliste der Abfrage angeben. Bei Verwendung von JPQL können Sie jedoch eine Abfrage nur über die PurchaseOrder-Entität definieren und dann mit der getOrderLineItems-Methode von PurchaseOrder nach Bedarf zu den entsprechenden OrderLineItem-Instanzen navigieren. In diesem Beispiel möchten Sie möglicherweise nur dann eine JPQL-Abfrage für die Entitäten PurchaseOrder und OrderLineItem definieren, wenn Sie abgerufene PurchaseOrder-Instanzen basierend auf einer oder mehreren auf OrderLineItem angewendeten Bedingungen filtern müssen.
Das folgende Snippet zeigt ein Beispiel für eine JPQL-Join-Abfrage in Aktion. Um besser zu verstehen, wie die beteiligten Entitäten miteinander in Beziehung stehen, können Sie auf Abbildung 1 im Abschnitt Beispielanwendung zu Beginn des Artikels zurückgreifen.
... Double max = (Double) em.createQuery("SELECT MAX(p.price) FROM PurchaseOrder o JOIN o.orderLineItems l JOIN l.product p JOIN p.supplier s WHERE s.sup_name = 'Tortuga Trading'") .getSingleResult(); out.println("The highest price for an ordered product supplied by Tortuga Trading: "+ max + "<br/>"); ...
Im obigen Beispiel verwenden Sie die Funktion MAX aggregate in der SELECT-Klausel der Join-Abfrage, um das Produkt mit dem höchsten Preis zu ermitteln, das von Tortuga Trading geliefert und mindestens einmal bestellt wurde.
Eine häufigere Situation ist jedoch, wenn Sie beispielsweise den Gesamtpreis der bestellten Produkte berechnen müssen, die von einem bestimmten Lieferanten geliefert wurden. Hier kann die Summenaggregatfunktion nützlich sein. In SQL könnte eine solche Join-Abfrage folgendermaßen aussehen:
SELECT SUM(p.price*l.quantity) FROM purchaseorders o JOIN orderlineitems l ON o.pono=l.pono JOIN products p ON l.prod_id=p.prod_id JOIN suppliers s ON p.sup_id=s.sup_id WHERE sup_name ='Tortuga Trading';
Leider können Sie mit der in JPQL verwendeten Summenfunktion keinen arithmetischen Ausdruck als Argument übergeben. In der Praxis bedeutet dies, dass Sie p.price*l.quantity nicht als Argument an die Summe der JPQL übergeben können. Es gibt jedoch Möglichkeiten, dieses Problem zu umgehen. Im folgenden Beispiel definieren Sie die Klasse LineItemSum, deren Konstruktor dann in der Auswahlliste der Abfrage verwendet wird, wobei p.price und l.quantity als Parameter verwendet werden. Der LineItemSum Konstruktor multipliziert p.price mit l.quantity und speichert das Ergebnis in seiner rslt-Klassenvariablen. Als nächstes können Sie die von der Abfrage abgerufene LineItemSum-Liste durchlaufen und die Werte der rslt-Variablen von LineItemSum summieren. Das folgende Snippet zeigt, wie all dies in Code implementiert werden kann:
package jpqlexample.servlets; ... class LineItemSum { private Double price; private Integer quantity; private Double rslt; public LineItemSum (Double price, Integer quantity){ this.rslt = quantity*price; } public Double getRslt () { return this.rslt; } public void setRslt (Double rslt) { this.rslt = rslt; } } public class JpqlJoinsServlet extends HttpServlet { ... public void doGet( ... List<LineItemSum> arr = (List<LineItemSum>)em.createQuery ("SELECT NEW jpqlexample.servlets.LineItemSum(p.price, l.quantity) FROM PurchaseOrder o JOIN o.orderLineItems l JOIN l.product p JOIN p.supplier s WHERE s.sup_name = 'Tortuga Trading'") .getResultList(); Iterator i = arr.iterator(); LineItemSum lineItemSum; Double sum = 0.0; while (i.hasNext()) { lineItemSum = (LineItemSum) i.next(); sum = sum + lineItemSum.getRslt(); } out.println("The total cost of the ordered products supplied by Tortuga Trading: "+ sum + "<br/>"); } }
Das obige Beispiel veranschaulicht unter anderem, wie Sie eine benutzerdefinierte Java-Klasse und keine Entitätsklasse in der Auswahlliste der JPQL-Abfrage verwenden können, die die von mehr als einer Entität abgeleiteten Felder enthält, und das Ergebnis der Abfrage in diese Klasse umwandeln. In den meisten Fällen müssen Sie sich jedoch mit Abfragen befassen, die eine Instanz oder eine Liste von Instanzen einer bestimmten Entität erhalten.
Abgerufene Entitätsinstanzen und der aktuelle Persistenzkontext
Die Abfrageergebnisse in den bisherigen Artikelbeispielen wurden einfach ausgedruckt. In realen Anwendungen müssen Sie jedoch möglicherweise einige weitere Vorgänge für die Abfrageergebnisse ausführen. Beispielsweise müssen Sie möglicherweise die abgerufenen Instanzen aktualisieren und dann in der Datenbank beibehalten. Dies wirft die Frage auf: Sind die Instanzen, die von einer JPQL-Abfrage abgerufen werden, bereit, von der Anwendung weiter verarbeitet zu werden, oder sind einige zusätzliche Schritte erforderlich, um sie dafür vorzubereiten? Insbesondere wäre es interessant zu erfahren, in welchem Zustand sich die abgerufenen Entitätsinstanzen in Bezug auf den aktuellen Persistenzkontext befinden.
Wenn Sie Erfahrung mit Java Persistence haben, sollten Sie wissen, was ein Persistenzkontext ist. Zusammenfassend lässt sich sagen, dass ein Persistenzkontext eine Gruppe von Entitätsinstanzen ist, die von der diesem Kontext zugeordneten EntityManager-Instanz verwaltet werden. In den vorhergehenden Beispielen haben Sie die createQuery-Methode von EntityManager verwendet, um eine Instanz von Query zum Ausführen einer JPQL-Abfrage zu erstellen. Tatsächlich enthält die EntityManager-API mehr als zwanzig Methoden zum Verwalten des Lebenszyklus von Entitätsinstanzen, zum Steuern von Transaktionen und zum Erstellen von Abfrageinstanzen, deren Methoden dann zum Ausführen der angegebenen Abfrage und zum Abrufen des Abfrageergebnisses verwendet werden.
In Bezug auf einen Persistenzkontext kann sich eine Entitätsinstanz in einem der folgenden vier Zustände befinden: neu, verwaltet, getrennt oder entfernt. Mit einer geeigneten EntityManager-Methode können Sie den Status einer bestimmten Entitätsinstanz nach Bedarf ändern. Es ist jedoch interessant festzustellen, dass nur Instanzen im verwalteten Zustand mit der Datenbank synchronisiert werden, wenn eine Spülung in die Datenbank erfolgt. Um genau zu sein, werden auch die Entitätsinstanzen im entfernten Zustand synchronisiert, dh die Datenbankeinträge, die diesen Instanzen entsprechen, werden aus der Datenbank entfernt.
Im Gegensatz dazu werden Instanzen im neuen oder getrennten Status nicht mit der Datenbank synchronisiert. Wenn Sie beispielsweise eine neue PurchaseOrder-Instanz erstellen und dann die flush-Methode von EntityManager aufrufen, wird in der purchaseOrders-Tabelle, der die PurchaseOrder-Entität zugeordnet ist, kein anderer Datensatz angezeigt. Dies liegt daran, dass diese neue PurchaseOrder Instanz nicht an den Persistenzkontext angehängt wurde. So könnte der Code aussehen:
... em.getTransaction().begin(); Customer cust = (Customer) em.find(Customer.class, 1); PurchaseOrder ord = new PurchaseOrder(); ord.setOrder_date(new Date()); ord.setCustomer(cust); em.getTransaction().commit(); ...
Um das Problem zu beheben, müssen Sie die persist-Methode des EntityManagers für die neue PurchaseOrder-Instanz aufrufen, bevor Sie den Flush aufrufen, wie im folgenden Beispiel gezeigt:
... em.getTransaction().begin(); Customer cust = (Customer) em.find(Customer.class, 1); PurchaseOrder ord = new PurchaseOrder(); ord.setOrder_date(new Date()); ord.setCustomer(cust); em.persist(ord); em.getTransaction().commit(); ...
Wenn Sie beim Definieren der Beziehung zu PurchaseOrder in der Customer-Entität die Option cascade auf PERSIST oder ALL festgelegt haben, können Sie alternativ die neu erstellte PurchaseOrder-Instanz zur Liste der Bestellungen hinzufügen, die der customer-Instanz zugeordnet sind, und den Vorgang persist durch den folgenden Vorgang ersetzen:
cust.getPurchaseOrders().add(ord);
Die obige Diskussion über Entitätsinstanzzustände führt uns zu der interessanten Frage, ob die von einer JPQL-Abfrage abgerufenen Entitätsinstanzen automatisch verwaltet werden oder ob Sie darauf achten müssen, dass ihr Status explizit verwaltet wird. Gemäß der JPA-Spezifikation werden Entitäten unabhängig von der Art und Weise, wie Sie Entitäten abrufen — sei es die find—Methode des EntityManagers oder eine Abfrage – automatisch an den aktuellen Persistenzkontext angehängt. Dies bedeutet, dass von einer JPQL-Abfrage abgerufene Entitätsinstanzen automatisch verwaltet werden. Sie können beispielsweise den Wert des Felds einer abgerufenen Instanz ändern und diese Änderung dann mit der Datenbank synchronisieren, indem Sie die flush-Methode des EntityManagers aufrufen oder die aktuelle Transaktion festschreiben. Sie müssen sich auch keine Gedanken über den Status der Instanzen machen, die den abgerufenen Instanzen zugeordnet sind. Tatsache ist, dass beim ersten Zugriff auf eine zugeordnete Instanz diese automatisch verwaltet wird. Hier ist ein einfaches Beispiel, das zeigt, wie das alles in der Praxis funktioniert:
... em.getTransaction().begin(); PurchaseOrder ord = (PurchaseOrder)em.createQuery("SELECT o FROM PurchaseOrder o WHERE o.pono = 1") .getSingleResult(); List<OrderLineItem> items = ord.getOrderLineItems(); Integer qnt = items.get(0).getQuantity(); out.println("Quantity of the first item : "+ qnt +"<br/>"); items.get(0).setQuantity(qnt+1); qnt = items.get(0).getQuantity(); em.getTransaction().commit(); out.println("Quantity of the first item : "+ qnt +"<br/>"); ...
Beachten Sie, dass Sie die persist-Methode weder für die abgerufene PurchaseOrder-Instanz noch für die zugehörige OrderLineItem-Instanz aufrufen, die hier geändert wird. Trotzdem werden die Änderungen an der ersten Position in der Bestellung beim Festschreiben der Transaktion in der Datenbank beibehalten. Dies geschieht, weil sowohl die abgerufenen Entitätsinstanzen als auch ihre Zuordnungen automatisch an den aktuellen Persistenzkontext angehängt werden. Wie bereits erwähnt, werden erstere verwaltet, wenn sie abgerufen werden, und letztere werden beim Zugriff an den Kontext angehängt.
In einigen Situationen möchten Sie möglicherweise, dass Verknüpfungen bei der Abfrageausführung und nicht beim ersten Zugriff an den Kontext angehängt werden. Hier ist ein FETCH JOIN nützlich. Angenommen, Sie möchten alle Bestellungen eines bestimmten Kunden abrufen, wenn Sie diese Kundeninstanz abrufen. Dieser Ansatz garantiert, dass Sie es mit den zum Zeitpunkt der Abfrageausführung verfügbaren Kundenaufträgen zu tun haben. Wenn beispielsweise ein neuer Auftrag einem anderen Kontext hinzugefügt und dann mit der Datenbank synchronisiert wird, bevor Sie zum ersten Mal auf die Auftragsliste zugreifen, die der abgerufenen Kundeninstanz zugeordnet ist, wird diese Änderung erst angezeigt, wenn Sie den Status der Kundeninstanz aus der Datenbank aktualisieren. Im folgenden Snippet verwenden Sie die Join-Abfrage, die die Customer-Instanz zurückgibt, deren cust_id 1 ist, und die PurchaseOrder-Instanzen abruft, die der abgerufenen Customer-Instanz zugeordnet sind.
... Customer cust = (Customer)em.createQuery("SELECT DISTINCT c FROM Customer c LEFT JOIN FETCH c.purchaseOrders WHERE c.cust_id=1") .getSingleResult(); ... List<PurchaseOrder> orders = cust.getPurchaseOrders(); ...
Da sie nicht Teil des expliziten Abfrageergebnisses sind, werden die PurchaseOrder-Entitätsinstanzen, die der hier abgerufenen Kundeninstanz zugeordnet sind, auch abgerufen und bei der Abfrageausführung an den aktuellen Persistenzkontext angehängt.
Verwendung nativer SQL-Abfragen
Es ist interessant festzustellen, dass Sie beim Definieren von Abfragen, die dann mit der Abfrage-API ausgeführt werden sollen, nicht auf JPQL beschränkt sind. Sie werden überrascht sein zu erfahren, dass die EntityManager-API Methoden zum Erstellen von Instanzen von Abfragen zum Ausführen nativer SQL-Anweisungen bietet. Das Wichtigste bei nativen SQL-Abfragen, die mit EntityManager-Methoden erstellt wurden, ist, dass sie wie JPQL-Abfragen Entitätsinstanzen anstelle von Datenbanktabelleneinträgen zurückgeben. Hier ist ein einfaches Beispiel für eine dynamische native SQL-Abfrage:
... List<Customer> customers = (List<Customer>)em.createNativeQuery ("SELECT * FROM customers", jpqlexample.entities.Customer.class) .getResultList(); Iterator i = customers.iterator(); Customer cust; out.println("Customers: " + "<br/>"); while (i.hasNext()) { cust = (Customer) i.next(); out.println(cust.getCust_name() +"<br/>"); } ...
JPQL entwickelt sich immer noch weiter und verfügt nicht über viele dieser wichtigen Funktionen, die in SQL verfügbar sind. Im vorherigen Abschnitt Definieren von JPQL-Joins haben Sie ein Beispiel für die Unvollständigkeit von JPQL gesehen: Sie mussten viel selbst arbeiten, da die Summenaggregatfunktion von JPQL keinen arithmetischen Ausdruck als Parameter verwenden kann. Im Gegensatz dazu hat die SUM-Funktion von SQL keine solche Einschränkung. Dies ist also ein gutes Beispiel dafür, wo das Ersetzen von JPQL durch natives SQL effizient sein könnte. Der folgende Code veranschaulicht, wie Sie die Dinge in diesem speziellen Beispiel vereinfachen können, indem Sie native SQL über JPQL auswählen:
... String sup_name ="Tortuga Trading"; BigDecimal sum = (List)em.createNativeQuery("SELECT SUM(p.price*l.quantity) FROM orders o JOIN orderlineitems l ON o.pono=l.pono JOIN products p ON l.prod_id=p.prod_id JOIN suppliers s ON p.sup_id=s.sup_id WHERE sup_name =?1") .setParameter(1, sup_name) .getSingleResult(); out.println("The total cost of the ordered products supplied by Tortuga Trading: " + sum +"<br/>"); ...
Das obige Beispiel zeigt unter anderem, dass Sie Argumente an native Abfrageparameter binden können. Insbesondere können Sie Argumente wie bei einer JPQL-Abfrage an Positionsparameter binden.
Der Hauptnachteil nativer Abfragen ist die Komplexität der Ergebnisbindung. Im Beispiel erzeugt die Abfrage ein einzelnes Ergebnis eines einfachen Typs, wodurch dieses Problem vermieden wird. In der Praxis müssen Sie sich jedoch häufig mit einer Ergebnismenge eines komplexen Typs befassen. In diesem Fall müssen Sie eine Entität deklarieren, der Sie Ihre native Abfrage zuordnen können, oder eine komplexe Ergebnismenge definieren, die mehreren Entitäten oder einer Mischung aus Entitäten und skalaren Ergebnissen zugeordnet ist.
Verwenden gespeicherter Prozeduren
Ein weiterer Nachteil nativer Abfragen besteht darin, dass Ihr Java-Code direkt von der zugrunde liegenden Datenbankstruktur abhängig wird. Wenn Sie diese zugrunde liegende Struktur ändern, müssen Sie die betreffenden nativen Abfragen in Ihren Servlets und / oder anderen Anwendungskomponenten anpassen und diese Komponenten anschließend neu kompilieren und erneut bereitstellen. Um dieses Problem zu umgehen, können Sie gespeicherte Prozeduren nutzen, indem Sie komplexe SQL-Abfragen in Programme verschieben, die in der Datenbank gespeichert und ausgeführt werden, und dann diese gespeicherten Programme aufrufen, anstatt die zugrunde liegenden Tabellen direkt aufzurufen. In der Praxis bedeutet dies, dass gespeicherte Prozeduren Ihnen die Mühe ersparen können, mit den zugrunde liegenden Tabellen direkt aus den Abfragen umzugehen, die in Ihrem Java-Code fest codiert sind. Der Vorteil dieses Ansatzes besteht darin, dass Sie in den meisten Fällen Ihren Java-Code nicht ändern müssen, um den Änderungen in der zugrunde liegenden Datenbankstruktur zu folgen. Stattdessen müssen nur die gespeicherten Prozeduren repariert werden.
Wenn Sie zu dem im vorhergehenden Abschnitt beschriebenen Beispiel zurückkehren, können Sie die dort verwendete komplexe Join-Abfrage in eine gespeicherte Funktion verschieben, die wie folgt erstellt wird:
CREATE OR REPLACE FUNCTION sum_total(supplier VARCHAR2) RETURN NUMBER AS sup_sum NUMBER; BEGIN SELECT SUM(p.price*l.quantity) INTO sup_sum FROM orders o JOIN orderlineitems l ON o.pono=l.pono JOIN products p ON l.prod_id=p.prod_id JOIN suppliers s ON p.sup_id=s.sup_id WHERE sup_name = supplier; RETURN sup_sum; END; /
Dies vereinfacht die in Ihrem Java-Code verwendete native Abfrage und entfernt die Abhängigkeit von den zugrunde liegenden Tabellen:
... String sup_name ="Tortuga Trading"; BigDecimal sum = (BigDecimal)em.createNativeQuery("SELECT sum_total(?1) FROM DUAL") .setParameter(1, sup_name) .getSingleResult(); out.println("The total cost of the ordered products supplied by Tortuga Trading: " + sum +"<br/>"); ...
Fazit
Wie Sie in diesem Artikel gelernt haben, ist JPQL ein leistungsfähiges Werkzeug, wenn es darum geht, auf relationale Daten aus Java-Anwendungen zuzugreifen, die Java Persistence verwenden. Im Gegensatz zu SQL / JDBC definieren Sie bei JPQL Abfragen über JPA-Entitäten, die den zugrunde liegenden Datenbanktabellen zugeordnet sind, anstatt diese Tabellen direkt abzufragen. Sie haben auch gelernt, dass JPQL nicht die einzige Option ist, wenn es darum geht, Abfragen über JPA—Entitäten zu erstellen.