Fråga JPA-enheter med JPQL och Native SQL

exempelprogram

kodavsnitten som diskuteras i artikeln tas från Java-källfilerna som används i exempelprogrammet som åtföljer artikeln. Om du tittar igenom exemplarkivet kanske du märker att detta är en enkel webbapplikation baserad på Java Servlet och Java Persistens API-teknik. För enkelhetens skull använder den inte enterprise beans, som utfärdar JPQL-frågor direkt från servlets. Det betyder dock inte att du inte kommer att kunna använda JPQL—frågorna som diskuteras här i enterprise beans-du kan definiera JPQL-frågor i alla Java EE-komponenter.

Figur 1 illustrerar strukturen för urvalsenheter. Som du kan se innehåller den en uppsättning enheter relaterade till varandra med relationer av olika slag. En sådan grenstruktur behövs för att illustrera användningen av jpql join-frågor som diskuteras i avsnittet definiera JPQL Joins senare i artikeln.

Figur 1 relationer mellan de enheter som används inom exempelapplikationen

för en detaljerad instruktion om hur du ställer in och sedan startar exempelappen kan du hänvisa till readme.txt-fil i rotkatalogen i exemplarkivet.

använda JPQL i Java EE-applikationer

om du har praktisk erfarenhet av databaser har du troligtvis redan fått fötterna våta med SQL, standardverktyget som erbjuder en uppsättning uttalanden för att komma åt och manipulera information i relationsdatabaser. Det finns faktiskt många likheter mellan JPQL och SQL. Båda används för att komma åt och manipulera databasdata på lång sikt. Och båda använder icke-procedurella uttalanden-kommandon som erkänns av en särskild tolk. Dessutom liknar JPQL SQL i sin syntax.

huvudskillnaden mellan JPQL och SQL ligger i att den förra handlar om JPA-enheter, medan den senare handlar direkt om relationsdata. Som Java-utvecklare kanske du också är intresserad av att lära dig att använda JPQL, till skillnad från SQL/JDBC, eliminerar behovet av att du använder JDBC API från din Java—kod-behållaren gör allt detta för dig bakom kulisserna.

JPQL låter dig definiera frågor med en av följande tre satser: välj, Uppdatera eller ta bort. Det är intressant att notera att EntityManager API-gränssnittet erbjuder metoder som också kan användas för att utföra hämta, uppdatera och ta bort operationer över enheter. I synnerhet är dessa hitta, slå samman och ta bort metoder. Användningen av dessa metoder är emellertid vanligtvis begränsad till en enda entitetsinstans, såvida inte kaskad naturligtvis träder i kraft. Däremot har JPQL-uttalanden inte en sådan begränsning-du kan definiera massuppdatering och ta bort operationer över uppsättningar av entiteter och definiera frågor som returnerar uppsättningar av entitetsinstanser.

för att utfärda en jpql-fråga från din Java-kod måste du använda lämpliga metoder för EntityManager API och Query API, som utför följande allmänna steg:

  • 1. Skaffa en instans av EntityManager, med injektion eller uttryckligen genom en EntityManagerFactory-instans.
  • 2. Skapa en instans av Query genom att anropa en lämplig Entitymanagers metod, till exempel createQuery.
  • 3. Ställ in en frågeparameter eller parametrar, om sådana finns, med hjälp av en lämplig frågas setParameter-metod.
  • 4. Om det behövs ställer du in det maximala antalet instanser som ska hämtas och/eller anger positionen för den första instansen som ska hämtas med hjälp av setMaxResults och / eller setFirstResult-frågans metoder.
  • 5. Om det behövs, ange en leverantörsspecifik ledtråd, med setHint-frågans metod.
  • 6. Om det behövs ställer du in spolningsläget för frågekörningen med setFlushMode-frågans metod och åsidosätter Enhetshanterarens spolningsläge.
  • 7. Utför frågan med hjälp av en lämplig frågas metod: getsinglerresult eller getResultList. När det gäller en uppdatering eller radering måste du dock använda metoden executeUpdate, som returnerar antalet entitetsinstanser som uppdaterats eller tagits bort.

den fullständiga listan över EntityManager-gränssnittsmetoderna, liksom Query API-gränssnittsmetoderna, finns i Enterprise JavaBeans 3.0-specifikationen: Java Persistence API-dokument, som ingår i JSR-220.

nu när du har en grov uppfattning om hur du kan skapa och sedan utfärda en JPQL-fråga, kanske du vill se några praktiska exempel. Följande kodfragment tas från en servlets doGet-metod som använder en JPQL-Fråga för att få information om alla kunder som lagras i den underliggande relationstabellen som är associerad med den Kundenhet som anges i frågan.

  ... @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("----------------" + ""); } ... 

av särskilt intresse här är createQuery-metoden för entitymanager-instansen och getResultList-metoden för Frågeinstansen. Entitymanagers createQuery används för att skapa Frågeinstansen vars getResultList-metod sedan används för att köra JPQL-frågan som skickas till createQuery som parameter. Som du kanske gissar returnerar frågans getResultList-metod resultatet av en fråga som en lista vars element, i det här exemplet, är gjutna för att skriva kund.

om du behöver hämta ett enda resultat erbjuder Query API-gränssnittet getSingleResult-metoden, som visas i följande exempel. Observera dock att användning av getSingleResult kommer att orsaka ett undantag om du får flera resultat tillbaka.

även detta exempel illustrerar användningen av frågans setParameter-metod genom vilken du kan binda ett argument till en frågeparameter. Med setParameter kan du binda både namngivna och positionsparametrar. Här binder du dock en namngiven 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()+""); ... 

det är intressant att notera att användning av ett SELECT JPQL-uttalande inte är det enda sättet att gå när det gäller att hämta en enda entitetsinstans. Alternativt kan du använda Entitymanagers sökmetod, som låter dig hämta en enskild entitetsinstans baserat på enhetens id som skickas in som parameter.

i vissa situationer kan du behöva hämta endast viss information från målenhetens instans eller instanser, definiera en jpql-fråga mot ett visst entitetsfält eller fält. Så här skulle ovanstående utdrag se ut om du bara behöver hämta värdet på fältet cust_name för den kundinstans som frågades här:

  ... 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+""); ... 

på samma sätt kan du använda följande kod för att få hela listan över kunders namn:

  ... 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/>"); } ... 

när du återgår till SQL kanske du kommer ihåg att select-listan för en SQL-fråga kan bestå av flera fält från tabellen eller tabellerna som anges i from-satsen. I JPQL kan du också använda en sammansatt select-lista och välja data endast från entitetsfälten av intresse. I så fall måste du dock skapa den klass som du kommer att kasta frågeresultatet till. I följande avsnitt ser du ett exempel på en jpql-anslutningsfråga vars select-lista består av fälten som härrör från mer än en enhet.

definiera JPQL Joins

liksom SQL låter JPQL dig definiera join-frågor. I SQL definierar du dock normalt en koppling som kombinerar poster från två eller flera tabeller och/eller vyer, inklusive endast Obligatoriska fält från dessa tabeller och vyer i listan välj i kopplingsfrågan. Däremot innehåller select-listan för en jpql-anslutningsfråga vanligtvis en enda enhet eller till och med ett enda entitetsfält. Anledningen till detta ligger i natur JPA teknik. När du har fått en entitetsinstans kan du sedan navigera till dess relaterade instanser med motsvarande gettermetoder. Detta tillvägagångssätt gör det onödigt för dig att definiera en fråga som returnerar alla relaterade entitetsinstanser av intresse på en gång.

till exempel, för att få information om order tillsammans med sina radposter i SQL, skulle du behöva definiera en kopplingsfråga på både inköpsorder och orderLineItems tabeller, ange fälten från båda tabellerna i listan Välj för frågan. När du använder JPQL kan du dock bara definiera en fråga över PurchaseOrder-entiteten och sedan navigera till motsvarande OrderLineItem-instanser med Purchaseorders getOrderLineItems-metod efter behov. I det här exemplet kanske du vill definiera en jpql-fråga över PurchaseOrder-och OrderLineItem-enheterna endast om du behöver filtrera hämtade PurchaseOrder-instanser baserat på ett villkor eller villkor som tillämpas på OrderLineItem.

följande utdrag visar ett exempel på jpql join query i aktion. För att bättre förstå hur de berörda enheterna är relaterade till varandra kan du vända tillbaka till Figur 1 som visas i Exempelapplikationsavsnittet tidigare i artikeln.

  ... 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/>"); ... 

i exemplet ovan använder du funktionen MAX aggregate I select-klausulen i join-frågan för att bestämma den högsta prisprodukten, av de som har levererats av Tortuga Trading och har beställts minst en gång.

en vanligare situation är dock när du behöver beräkna, säg, det totala priset på de beställda produkterna, som har levererats av en viss leverantör. Det är här SUM aggregate-funktionen kan komma till nytta. I SQL kan en sådan anslutningsfråga se ut så här:

  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'; 

Tyvärr tillåter inte SUM-funktionen som används i JPQL att du skickar ett aritmetiskt uttryck som argument. Vad detta betyder i praktiken är att du inte kommer att kunna skicka p.Pris*l.kvantitet som argument till JPQL: s summa. Det finns dock sätt att komma runt denna fråga. I följande exempel definierar du class LineItemSum vars konstruktör sedan används i Select-listan för frågan, med p.price och l.quantity som parametrar. Vad LineItemSum-konstruktören gör är att multiplicera p. pris med L. kvantitet, vilket sparar resultatet till sin rslt-klassvariabel. Därefter kan du iterera genom LineItemSum-listan som hämtas av frågan och summera värdena för Lineitemsums rslt-variabel. Följande utdrag visar hur allt detta kan implementeras i kod:

  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/>"); } } 

exemplet ovan illustrerar bland annat hur du kan använda en anpassad Java-klass, inte en entitetsklass, i jpql-frågans select-lista som innehåller fälten som härrör från mer än en entitet och kastar resultatet av frågan till den klassen. I de flesta fall måste du dock hantera frågor som får en instans eller en lista över instanser av en viss enhet.

hämtade Entitetsinstanser och det aktuella Uthållighetskontexten

frågeresultaten i artikelexemplen hittills har helt enkelt skrivits ut. I verkliga applikationer kan du dock behöva utföra några ytterligare åtgärder på frågeresultaten. Du kan till exempel behöva uppdatera de hämtade instanserna och sedan fortsätta tillbaka till databasen. Detta väcker frågan: är instanserna som hämtas av en JPQL-fråga redo att behandlas vidare av applikationen, eller krävs några ytterligare steg för att göra dem redo för det? I synnerhet skulle det vara intressant att lära sig i vilket tillstånd, om det nuvarande uthållighetssammanhanget, hämtade entitetsinstanser är.

om du har erfarenhet av Java-uthållighet bör du veta vad ett uthållighetskontext är. För att sammanfatta är ett uthållighetskontext en uppsättning entitetsinstanser som hanteras av EntityManager-instansen som är associerad med det sammanhanget. I de föregående exemplen använde du Entitymanagers createQuery-metod för att skapa en instans av Query för att köra en JPQL-fråga. I själva verket innehåller ENTITYMANAGER API mer än tjugo metoder för att hantera livscykeln för entitetsinstanser, kontrollera transaktioner och skapa instanser av frågor vars metoder sedan används för att utföra den angivna frågan och hämta frågeresultatet.

med avseende på ett uthållighetskontext kan en entitetsinstans vara i ett av följande fyra tillstånd: ny, hanterad, fristående eller borttagen. Med hjälp av en lämplig EntityManager-metod kan du ändra tillståndet för en viss entitetsinstans efter behov. Det är dock intressant att notera att endast instanser i hanterat tillstånd synkroniseras till databasen när spolning till databasen sker. För att vara exakt synkroniseras också entitetsinstanser i det borttagna tillståndet, vilket innebär att databasposterna som motsvarar dessa instanser tas bort från databasen.

däremot synkroniseras inte instanser i det nya eller fristående tillståndet till databasen. Om du till exempel skapar en ny instans för inköpsorder och sedan anropar entitymanager ’ s flush-metod visas inte en annan post i tabellen för inköpsorder som enheten för inköpsorder är mappad till. Detta beror på att den nya Inköpsorderinstansen inte har kopplats till uthållighetskontexten. Så här kan koden se ut:

  ... 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(); ... 

för att åtgärda problemet måste du anropa EntityManager ’ s persist-metod för den nya PurchaseOrder-instansen innan du anropar spolningen, som visas i följande exempel:

  ... 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(); ... 

alternativt, förutsatt att du har ställt in alternativet cascade att fortsätta eller alla när du definierar förhållandet med PurchaseOrder i Kundenheten, kan du lägga till den nyskapade PurchaseOrder-instansen i listan över order som är associerade med kundinstansen och ersätta åtgärden persistent med följande:

  cust.getPurchaseOrders().add(ord); 

ovanstående diskussion om entitetsinstansstater leder oss till den intressanta frågan om huruvida entitetsinstanser som hämtas av en JPQL-fråga hanteras automatiskt, eller du måste ta hand om att uttryckligen ställa in deras tillstånd som ska hanteras. Enligt JPA-specifikationen, oavsett hur du hämtar enheter—oavsett om det är Entitymanagers sökmetod eller en fråga—kopplas de automatiskt till det aktuella uthållighetskontexten. Detta innebär att entitetsinstanser som hämtas av en JPQL-fråga hanteras automatiskt. Du kan till exempel ändra värdet för en hämtad instans fält och sedan synkronisera den ändringen till databasen genom att anropa EntityManager flush metod eller begå den aktuella transaktionen. Du behöver inte oroa dig för tillståndet för de instanser som är associerade med de hämtade instanserna heller. Faktum är att första gången du öppnar en associerad instans hanteras den automatiskt. Här är ett enkelt exempel som visar hur allt detta fungerar i praktiken:

  ... 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/>"); ... 

Observera att du inte åberopar metoden Fortsätt för den hämtade PurchaseOrder-instansen, och inte heller för att dess relaterade OrderLineItem-instans ändras här. Trots detta faktum kommer de ändringar som gjorts i den första raden i ordern att kvarstå i databasen när transaktionen begås. Detta händer eftersom både de hämtade entitetsinstanserna och deras föreningar automatiskt kopplas till det aktuella uthållighetskontexten. Som tidigare nämnts hanteras de förstnämnda när de hämtas, och de senare är kopplade till sammanhanget när du kommer åt dem.

i vissa situationer kanske du vill att associationer ska bifogas sammanhanget vid frågekörningen, snarare än vid den första åtkomsten. Det är här en HÄMTNINGSKOPPLING kommer till nytta. Säg att du vill få alla beställningar som tillhör en viss kund, när du hämtar den kundinstansen. Detta tillvägagångssätt garanterar att du har att göra med de kundorder som är tillgängliga vid tidpunkten för frågekörning. Om till exempel en ny order läggs till i ett annat sammanhang och sedan synkroniseras till databasen innan du först öppnar orderlistan som är kopplad till kundinstansen som hämtats ser du inte den här ändringen förrän du uppdaterar tillståndet för kundinstansen från databasen. I följande utdrag använder du kopplingsfrågan som returnerar kundinstansen vars cust_id är 1 och hämtar de Inköpsorderinstanser som är associerade med Kundinstansen som hämtas.

  ... 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(); ... 

eftersom de inte ingår i det explicita frågeresultatet hämtas också de instanser för PurchaseOrder-enheter som är associerade med Kundinstansen som hämtas här och bifogas det aktuella uthållighetskontexten vid frågekörningen.

använda inbyggda SQL-frågor

det är intressant att notera att du inte är begränsad till JPQL när du definierar frågor som ska utföras med Query API. Du kan bli förvånad över att veta att EntityManager API erbjuder metoder för att skapa instanser av Query för att utföra infödda SQL-satser. Det viktigaste att förstå om inbyggda SQL-frågor som skapats med EntityManager-metoder är att de, som JPQL-frågor, returnerar entitetsinstanser, snarare än databastabellposter. Här är ett enkelt exempel på en dynamisk inbyggd SQL-fråga:

  ... 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 utvecklas fortfarande och har inte många av de viktiga funktionerna tillgängliga i SQL. I avsnittet Defining JPQL Joins tidigare såg du ett exempel på JPQL: s ofullständighet: du var tvungen att göra mycket arbete på egen hand eftersom JPQL: s sum aggregate-funktion inte kan ta ett aritmetiskt uttryck som parameter. Däremot har SQL: s SUM-funktion inte en sådan begränsning. Så det här är ett bra exempel på var ersättning av JPQL med inbyggd SQL kan vara effektiv. Följande kod illustrerar hur du kan förenkla saker i det här exemplet genom att välja native SQL över JPQL:

  ... 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/>"); ... 

exemplet ovan illustrerar bland annat att du kan binda argument till inbyggda frågeparametrar. I synnerhet kan du binda argument till positionsparametrar på samma sätt som om du hade att göra med en JPQL-fråga.

den största nackdelen med inhemska frågor är komplexiteten i resultatbindning. I exemplet ger frågan ett enda resultat av en enkel typ, vilket undviker detta problem. I praktiken måste du dock ofta hantera en resultatuppsättning av en komplex typ. I det här fallet måste du deklarera en entitet som du kan mappa din ursprungliga fråga till, eller definiera en komplex resultatuppsättning mappad till flera entiteter eller till en blandning av entiteter och skalära resultat.

använda lagrade procedurer

en annan nackdel med inbyggda frågor är att din Java-kod blir direkt beroende av den underliggande databasstrukturen. Om du ändrar den underliggande strukturen måste du justera de ursprungliga frågorna i dina servlets och/eller andra applikationskomponenter, och måste kompilera om och omfördela dessa komponenter efter det. För att komma runt det här problemet, medan du fortfarande använder inbyggda frågor, kan du dra nytta av lagrade procedurer, flytta komplexa SQL-frågor till program som lagras och körs i databasen och sedan ringa de lagrade programmen istället för att ringa direkt till de underliggande tabellerna. Vad detta innebär i praktiken är att lagrade procedurer kan spara dig besväret att hantera de underliggande tabellerna direkt från de frågor som är hårdkodade i din Java-kod. Fördelen med detta tillvägagångssätt är att du i de flesta fall inte behöver ändra din Java-kod för att följa ändringarna i den underliggande databasstrukturen. Istället behöver endast de lagrade procedurerna fixas.

om du återgår till exemplet som diskuterades i föregående avsnitt kan du flytta den komplexa kopplingsfrågan som används där till en lagrad funktion, skapad enligt följande:

  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; / 

detta förenklar den ursprungliga frågan som används i din Java-kod och tar bort beroendet från de underliggande tabellerna:

  ... 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/>"); ... 

slutsats

som du lärde dig i den här artikeln är JPQL ett kraftfullt verktyg när det gäller att komma åt relationsdata från Java-applikationer som använder Java-uthållighet. Med JPQL, till skillnad från med SQL/JDBC, definierar du frågor över JPA-enheter som är mappade till underliggande databastabeller snarare än att fråga dessa tabeller direkt och därmed hantera ett lager av abstraktion som döljer databasinformation från affärslogiklager. Du lärde dig också att JPQL inte är det enda alternativet när det gäller att skapa frågor över JPA—enheter-i vissa situationer är det bekvämare att använda inbyggda SQL-frågor.