Interrogazione di entità JPA con JPQL e SQL nativo

Applicazione di esempio

I frammenti di codice discussi nell’articolo sono presi dai file sorgente Java utilizzati nell’applicazione di esempio che accompagna l’articolo. Guardando attraverso l’archivio di esempio, è possibile notare che si tratta di una semplice applicazione Web basata sulle tecnologie Java Servlet e Java Persistence API. Per semplicità, non utilizza i bean aziendali, emettendo query JPQL direttamente dai servlet. Ciò non significa, tuttavia, che non sarai in grado di utilizzare le query JPQL discusse qui nei bean aziendali: puoi definire query JPQL in qualsiasi componente Java EE.

La figura 1 illustra la struttura delle entità di esempio. Come puoi vedere, contiene un insieme di entità correlate tra loro con relazioni di tipi diversi. Tale struttura ramificata è necessaria per illustrare l’uso delle query di join JPQL discusse nella sezione Defining JPQL Join più avanti nell’articolo.

Figura 1 relazioni tra le entità utilizzate nell’applicazione di esempio

Per istruzioni dettagliate su come configurare e quindi avviare l’app di esempio, è possibile fare riferimento a readme.file txt nella directory principale dell’archivio di esempio.

Utilizzo di JPQL nelle applicazioni Java EE

Se hai una certa esperienza pratica con i database, molto probabilmente hai già i piedi bagnati con SQL, lo strumento standard che offre una serie di istruzioni per accedere e manipolare le informazioni nei database relazionali. In effetti, ci sono molte somiglianze tra JPQL e SQL. Entrambi sono utilizzati per accedere e manipolare i dati del database, a lungo termine. Ed entrambi usano istruzioni non processuali-comandi riconosciuti da un interprete speciale. Inoltre, JPQL è simile a SQL nella sua sintassi.

La differenza principale tra JPQL e SQL sta nel fatto che il primo si occupa delle entità JPA, mentre il secondo si occupa direttamente dei dati relazionali. Come sviluppatore Java, potresti anche essere interessato a sapere che l’uso di JPQL, a differenza di SQL / JDBC, elimina la necessità di utilizzare l’API JDBC dal tuo codice Java—il contenitore fa tutto questo lavoro per te dietro le quinte.

JPQL consente di definire le query utilizzando una delle seguenti tre istruzioni: SELECT, UPDATE o DELETE. È interessante notare che l’interfaccia API EntityManager offre metodi che possono essere utilizzati anche per eseguire operazioni di recupero, aggiornamento ed eliminazione su entità. In particolare, questi sono i metodi find, merge e remove. L’uso di questi metodi, tuttavia, è in genere limitato a una singola istanza di entità, a meno che la cascata non abbia effetto, ovviamente. Al contrario, le istruzioni JPQL non hanno tale limitazione: è possibile definire le operazioni di aggiornamento ed eliminazione in blocco su set di entità e definire query che restituiscono set di istanze di entità.

Per emettere una query JPQL dall’interno del codice Java, è necessario utilizzare metodi appropriati dell’API EntityManager e dell’API Query, eseguendo i seguenti passaggi generali:

  • 1. Ottenere un’istanza di EntityManager, utilizzando injection o esplicitamente tramite un’istanza EntityManagerFactory.
  • 2. Creare un’istanza di Query richiamando un metodo EntityManager appropriato, ad esempio createQuery.
  • 3. Impostare un parametro o parametri di query, se presenti, utilizzando il metodo setParameter di una query appropriata.
  • 4. Se necessario, impostare il numero massimo di istanze da recuperare e/o specificare la posizione della prima istanza da recuperare, utilizzando i metodi setMaxResults e/o setFirstResult Query.
  • 5. Se necessario, impostare un suggerimento specifico del fornitore, utilizzando il metodo della query setHint.
  • 6. Se necessario, impostare la modalità flush per l’esecuzione della query con il metodo setFlushMode Query, sovrascrivendo la modalità flush del gestore entità.
  • 7. Eseguire la query utilizzando il metodo di una query appropriata: getSingleResult o getResultList. Nel caso di un’operazione di aggiornamento o eliminazione, tuttavia, è necessario utilizzare il metodo executeUpdate, che restituisce il numero di istanze di entità aggiornate o eliminate.

L’elenco completo dei metodi di interfaccia EntityManager, così come i metodi di interfaccia API Query, può essere trovato nel documento Enterprise JavaBeans 3.0 Specification: Java Persistence API, che fa parte di JSR-220.

Ora che hai un’idea approssimativa di come puoi creare e quindi emettere una query JPQL, potresti voler vedere alcuni esempi pratici. Il seguente frammento di codice è tratto dal metodo doGet di un servlet che utilizza una query JPQL per ottenere informazioni su tutti i clienti memorizzati nella tabella relazionale sottostante associata all’entità cliente specificata nella query.

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

Di particolare interesse qui sono il metodo createQuery dell’istanza EntityManager e il metodo getResultList dell’istanza di query. createQuery di EntityManager viene utilizzato per creare l’istanza di query il cui metodo getResultList viene quindi utilizzato per eseguire la query JPQL passata a createQuery come parametro. Come si può intuire, il metodo getResultList della query restituisce il risultato di una query come un elenco i cui elementi, in questo particolare esempio, sono espressi per digitare Customer.

Se è necessario recuperare un singolo risultato, l’interfaccia API Query offre il metodo getSingleResult, come mostrato nell’esempio seguente. Si noti, tuttavia, che l’utilizzo di getSingleResult causerà un’eccezione se si ottengono più risultati.

Anche questo esempio illustra l’uso del metodo setParameter della query attraverso il quale è possibile associare un argomento a un parametro di query. Con setParameter, è possibile associare entrambi i parametri denominati e posizionali. Qui, però, si associa un parametro con nome.

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

È interessante notare che l’utilizzo di un’istruzione SELECT JPQL non è l’unico modo per recuperare una singola istanza di entità. In alternativa, è possibile utilizzare il metodo find di EntityManager, che consente di recuperare una singola istanza di entità in base all’ID dell’entità passato come parametro.

In alcune situazioni, potrebbe essere necessario recuperare solo alcune informazioni dall’istanza o dalle istanze dell’entità di destinazione, definendo una query JPQL rispetto a un determinato campo o campi dell’entità. Questo è ciò che il frammento di cui sopra sarebbe simile, se è necessario recuperare solo il valore del campo cust_name dell’istanza dell’entità cliente interrogata qui:

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

Allo stesso modo, per ottenere l’intero elenco dei nomi dei clienti, è possibile utilizzare il seguente codice:

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

Tornando a SQL, è possibile ricordare che l’elenco di selezione di una query SQL può essere composto da diversi campi della tabella o delle tabelle specificate nella clausola FROM. In JPQL, è anche possibile utilizzare un elenco di selezione composto, selezionando i dati solo dai campi di entità di interesse. In tal caso, tuttavia, è necessario creare la classe a cui verrà lanciato il risultato della query. Nella sezione seguente, verrà visualizzato un esempio di una query JPQL join il cui elenco select è composto dai campi derivati da più di un’entità.

Definizione dei join JPQL

Come SQL, JPQL consente di definire le query join. In SQL, tuttavia, normalmente si definisce un join che combina i record di due o più tabelle e/o viste, inclusi solo i campi obbligatori di tali tabelle e viste nell’elenco Seleziona della query join. Al contrario, l’elenco di selezione di una query di join JPQL include in genere una singola entità o anche un singolo campo di entità. La ragione di ciò risiede nella natura della tecnologia JPA. Una volta ottenuta un’istanza di entità, è possibile accedere alle istanze correlate utilizzando i metodi getter corrispondenti. Questo approccio rende inutile la definizione di una query che restituirà tutte le istanze di entità correlate di interesse contemporaneamente.

Ad esempio, per ottenere informazioni sugli ordini insieme ai relativi elementi pubblicitari in SQL, è necessario definire una query join su entrambe le tabelle purchaseOrders e orderLineItems, specificando i campi da entrambe le tabelle nell’elenco select della query. Quando si utilizza JPQL, tuttavia, è possibile definire una query solo sull’entità PurchaseOrder e quindi passare alle istanze OrderLineItem corrispondenti utilizzando il metodo getOrderLineItems di PurchaseOrder come richiesto. In questo esempio, è possibile definire una query JPQL sulle entità PurchaseOrder e OrderLineItem solo se è necessario filtrare le istanze PurchaseOrder recuperate in base a una o più condizioni applicate a OrderLineItem.

Il seguente frammento mostra un esempio di query join JPQL in azione. Per capire meglio come le entità coinvolte sono correlate tra loro, è possibile tornare alla Figura 1 mostrata nella sezione dell’applicazione di esempio in precedenza nell’articolo.

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

Nell’esempio precedente, si utilizza la funzione MAX aggregate nella clausola SELECT della query join per determinare il prodotto a prezzo più alto, di quelli che sono stati forniti da Tortuga Trading e sono stati ordinati almeno una volta.

Una situazione più comune, tuttavia, è quando è necessario calcolare, ad esempio, il prezzo totale dei prodotti ordinati, che sono stati forniti da un determinato fornitore. Questo è dove la funzione SOMMA aggregata può tornare utile. In SQL, tale query join potrebbe essere simile a questa:

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

Sfortunatamente, la funzione SUM utilizzata in JPQL non consente di passare un’espressione aritmetica come argomento. Ciò significa in pratica che non sarai in grado di passare p.price*l.quantity come argomento alla SOMMA di JPQL. Tuttavia, ci sono modi per aggirare questo problema. Nell’esempio seguente, si definisce la classe LineItemSum il cui costruttore viene quindi utilizzato nell’elenco select della query, prendendo p.price e l.quantity come parametri. Ciò che fa il costruttore LineItemSum è moltiplicare p. price per l. quantity, salvando il risultato nella sua variabile di classe rslt. Successivamente, è possibile scorrere l’elenco LineItemSum recuperato dalla query, sommando i valori della variabile rslt di LineItemSum. Il seguente frammento mostra come tutto questo può essere implementato nel codice:

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

Tra le altre cose, l’esempio precedente illustra come è possibile utilizzare una classe Java personalizzata, non una classe di entità, nell’elenco di selezione della query JPQL che include i campi derivati da più di un’entità, trasmettendo il risultato della query a quella classe. Nella maggior parte dei casi, tuttavia, dovrai gestire query che ricevono un’istanza o un elenco di istanze di una determinata entità.

Istanze di entità recuperate e il contesto di persistenza corrente

I risultati della query negli esempi di articoli finora sono stati semplicemente stampati. Nelle applicazioni del mondo reale, tuttavia, potrebbe essere necessario eseguire alcune ulteriori operazioni sui risultati della query. Ad esempio, potrebbe essere necessario aggiornare le istanze recuperate e quindi mantenerle nel database. Ciò solleva la domanda: le istanze recuperate da una query JPQL sono pronte per essere ulteriormente elaborate dall’applicazione o sono necessari alcuni passaggi aggiuntivi per renderle pronte per questo? In particolare, sarebbe interessante imparare in quale stato, relativo al contesto di persistenza corrente, sono le istanze di entità recuperate.

Se hai una certa esperienza con la persistenza Java, dovresti sapere cos’è un contesto di persistenza. Per ricapitolare, un contesto di persistenza è un insieme di istanze di entità gestite dall’istanza EntityManager associata a tale contesto. Negli esempi precedenti, è stato utilizzato il metodo createQuery di EntityManager per creare un’istanza di Query per l’esecuzione di una query JPQL. In realtà, l’API EntityManager include più di venti metodi per gestire il ciclo di vita delle istanze di entità, controllare le transazioni e creare istanze di query i cui metodi vengono quindi utilizzati per eseguire la query specificata e recuperare il risultato della query.

Rispetto a un contesto di persistenza, un’istanza di entità può trovarsi in uno dei seguenti quattro stati: nuovo, gestito, staccato o rimosso. Utilizzando un metodo EntityManager appropriato, è possibile modificare lo stato di una determinata istanza di entità in base alle esigenze. È interessante notare, tuttavia, che solo le istanze nello stato gestito vengono sincronizzate con il database, quando si verifica il flushing nel database. Per essere precisi, anche le istanze di entità nello stato rimosso vengono sincronizzate, il che significa che i record del database corrispondenti a tali istanze vengono rimossi dal database.

Al contrario, le istanze nello stato nuovo o distaccato non verranno sincronizzate con il database. Ad esempio, se si crea una nuova istanza PurchaseOrder e quindi si richiama il metodo flush di EntityManager, un altro record non verrà visualizzato nella tabella purchaseOrders a cui è mappata l’entità PurchaseOrder. Questo perché la nuova istanza PurchaseOrder non è stata collegata al contesto di persistenza. Ecco come potrebbe apparire il codice:

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

Per risolvere il problema, è necessario richiamare il metodo persist di EntityManager per la nuova istanza PurchaseOrder prima di richiamare il flush, come mostrato nell’esempio seguente:

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

In alternativa, a condizione che l’opzione cascade sia impostata su PERSIST o ALL durante la definizione della relazione con PurchaseOrder nell’entità Cliente, è possibile aggiungere l’istanza PurchaseOrder appena creata all’elenco degli ordini associati all’istanza cliente, sostituendo l’operazione persist con quanto segue:

  cust.getPurchaseOrders().add(ord); 

La discussione di cui sopra riguardante gli stati delle istanze di entità ci porta alla domanda interessante se le istanze di entità recuperate da una query JPQL vengano gestite automaticamente, o si deve fare attenzione a impostare esplicitamente il loro stato da gestire. In base alla specifica JPA, indipendentemente dal modo in cui si recuperano le entità, che si tratti del metodo find di EntityManager o di una query, vengono automaticamente associate al contesto di persistenza corrente. Ciò significa che le istanze di entità recuperate da una query JPQL vengono gestite automaticamente. Ad esempio, è possibile modificare il valore del campo di un’istanza recuperata e quindi sincronizzare la modifica nel database richiamando il metodo flush di EntityManager o eseguendo il commit della transazione corrente. Non è necessario preoccuparsi dello stato delle istanze associate alle istanze recuperate. Il fatto è che la prima volta che si accede a un’istanza associata viene gestita automaticamente. Ecco un semplice esempio che mostra come tutto questo funziona nella pratica:

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

Si noti che non si richiama il metodo persist per l’istanza PurchaseOrder recuperata, né per la relativa istanza OrderLineItem modificata qui. Nonostante ciò, le modifiche apportate alla prima voce nell’ordine verranno mantenute nel database al momento del commit della transazione. Ciò accade perché sia le istanze di entità recuperate che le relative associazioni vengono automaticamente associate al contesto di persistenza corrente. Come accennato in precedenza, i primi vengono gestiti quando vengono recuperati e questi ultimi sono collegati al contesto quando vi si accede.

In alcune situazioni, è possibile che le associazioni siano associate al contesto all’esecuzione della query, anziché al primo accesso. Questo è dove un FETCH JOIN è utile. Ad esempio, si desidera ottenere tutti gli ordini appartenenti a un determinato cliente, dopo aver recuperato l’istanza del cliente. Questo approccio garantisce che hai a che fare con gli ordini dei clienti disponibili al momento dell’esecuzione della query. Se, ad esempio, un nuovo ordine viene aggiunto a un altro contesto e quindi sincronizzato al database prima di accedere per la prima volta all’elenco ordini associato all’istanza cliente recuperata, questa modifica non verrà visualizzata finché non si aggiorna lo stato dell’istanza cliente dal database. Nello snippet seguente, si utilizza la query join che restituisce l’istanza del cliente il cui cust_id è 1 e recupera le istanze PurchaseOrder associate all’istanza del cliente da recuperare.

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

Non essendo parte del risultato esplicito della query, anche le istanze di entità PurchaseOrder associate all’istanza cliente recuperata vengono recuperate e allegate al contesto di persistenza corrente durante l’esecuzione della query.

Utilizzando query SQL native

È interessante notare che non si è limitati a JPQL quando si definiscono query da eseguire con Query API. Potresti essere sorpreso di apprendere che l’API EntityManager offre metodi per la creazione di istanze di query per l’esecuzione di istruzioni SQL native. La cosa più importante da capire sulle query SQL native create con i metodi EntityManager è che, come le query JPQL, restituiscono istanze di entità, piuttosto che i record della tabella del database. Ecco un semplice esempio di una query SQL nativa dinamica:

  ... 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 è ancora in evoluzione e non ha molte di queste importanti funzionalità disponibili in SQL. Nella sezione Defining JPQL Joins in precedenza, hai visto un esempio dell’incompletezza di JPQL: dovevi fare molto lavoro da solo perché la funzione di somma aggregata di JPQL non può assumere un’espressione aritmetica come parametro. Al contrario, la funzione SUM di SQL non ha tale limitazione. Quindi, questo è un buon esempio di dove sostituire JPQL con SQL nativo potrebbe essere efficiente. Il codice seguente illustra come è possibile semplificare le cose in questo particolare esempio scegliendo SQL nativo su 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/>"); ... 

Tra le altre cose, l’esempio precedente illustra che è possibile associare gli argomenti ai parametri di query nativi. In particolare, è possibile associare gli argomenti ai parametri posizionali nello stesso modo in cui si ha a che fare con una query JPQL.

Lo svantaggio principale delle query native è la complessità dell’associazione dei risultati. Nell’esempio, la query produce un singolo risultato di un tipo semplice, evitando così questo problema. In pratica, tuttavia, spesso si ha a che fare con un set di risultati di tipo complesso. In questo caso, sarà necessario dichiarare un’entità a cui è possibile mappare la query nativa o definire un set di risultati complesso mappato a più entità o a una miscela di entità e risultati scalari.

Utilizzo di Stored Procedure

Un altro svantaggio delle query native è che il codice Java diventa direttamente dipendente dalla struttura del database sottostante. Se si modifica la struttura sottostante, sarà necessario regolare le query native interessate nei servlet e/o in altri componenti dell’applicazione, dovendo ricompilare e ridistribuire tali componenti successivamente. Per ovviare a questo problema, mentre si utilizzano ancora query native, è possibile sfruttare le stored procedure, spostare query SQL complesse in programmi memorizzati ed eseguiti all’interno del database e quindi chiamare quei programmi memorizzati invece di effettuare chiamate dirette alle tabelle sottostanti. Ciò significa in pratica che le stored procedure possono farti risparmiare la fatica di gestire le tabelle sottostanti direttamente dalle query che sono hard-coded nel tuo codice Java. Il vantaggio di questo approccio è che nella maggior parte dei casi non sarà necessario modificare il codice Java per seguire le modifiche nella struttura del database sottostante. Invece, solo le stored procedure dovranno essere riparate.

Tornando all’esempio discusso nella sezione precedente, è possibile spostare la query join complessa utilizzata in una funzione memorizzata, creata come segue:

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

Ciò semplifica la query nativa utilizzata nel codice Java e rimuove la dipendenza dalle tabelle sottostanti:

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

Conclusione

Come hai appreso in questo articolo, JPQL è uno strumento potente quando si tratta di accedere ai dati relazionali da applicazioni Java che utilizzano la persistenza Java. Con JPQL, a differenza di SQL / JDBC, si definiscono query su entità JPA mappate alle tabelle di database sottostanti piuttosto che interrogare direttamente tali tabelle, trattando così un livello di astrazione che nasconde i dettagli del database dal livello di logica aziendale. Hai anche imparato che JPQL non è l’unica opzione quando si tratta di creare query su entità JPA—in alcune situazioni l’utilizzo di query SQL native è più conveniente.