Interrogation d’entités JPA avec JPQL et SQL natif

Exemple d’application

Les extraits de code discutés dans l’article sont extraits des fichiers source Java utilisés dans l’exemple d’application accompagnant l’article. En parcourant les exemples d’archives, vous remarquerez peut-être qu’il s’agit d’une application Web simple basée sur les technologies Java Servlet et Java Persistence API. Pour plus de simplicité, il n’utilise pas de beans d’entreprise, émettant des requêtes JPQL directement depuis les servlets. Cela ne signifie cependant pas que vous ne pourrez pas utiliser les requêtes JPQL discutées ici dans les beans d’entreprise — vous pouvez définir des requêtes JPQL dans tous les composants Java EE.

La figure 1 illustre la structure des entités de l’échantillon. Comme vous pouvez le voir, il contient un ensemble d’entités liées les unes aux autres avec des relations de types différents. Une telle structure ramifiée est nécessaire pour illustrer l’utilisation des requêtes de jointure JPQL discutées dans la section Définition des jointures JPQL plus loin dans l’article.

Figure 1 Relations entre les entités utilisées dans l’exemple d’application

Pour obtenir des instructions détaillées sur la configuration puis le lancement de l’exemple d’application, vous pouvez vous référer au fichier readme.fichier txt dans le répertoire racine de l’exemple d’archive.

Utilisation de JPQL dans les applications Java EE

Si vous avez une certaine expérience pratique des bases de données, vous vous êtes probablement déjà mouillé les pieds avec SQL, l’outil standard offrant un ensemble d’instructions pour accéder et manipuler les informations dans les bases de données relationnelles. En fait, il existe de nombreuses similitudes entre JPQL et SQL. Les deux sont utilisés pour accéder et manipuler les données de la base de données, à long terme. Et les deux utilisent des instructions non processuelles – des commandes reconnues par un interpréteur spécial. De plus, JPQL est similaire à SQL dans sa syntaxe.

La principale différence entre JPQL et SQL réside dans le fait que le premier traite des entités JPA, tandis que le second traite directement des données relationnelles. En tant que développeur Java, vous êtes peut—être également intéressé d’apprendre que l’utilisation de JPQL, contrairement à SQL / JDBC, élimine le besoin d’utiliser l’API JDBC à partir de votre code Java – le conteneur fait tout ce travail pour vous en coulisses.

JPQL vous permet de définir des requêtes à l’aide de l’une des trois instructions suivantes : SELECT, UPDATE ou DELETE. Il est intéressant de noter que l’interface API EntityManager propose des méthodes qui peuvent également être utilisées pour effectuer des opérations de récupération, de mise à jour et de suppression sur des entités. En particulier, il s’agit des méthodes find, merge et remove. L’utilisation de ces méthodes, cependant, est généralement limitée à une seule instance d’entité, sauf si la cascade prend effet, bien sûr. En revanche, les instructions JPQL n’ont pas une telle limitation : vous pouvez définir des opérations de mise à jour et de suppression en bloc sur des ensembles d’entités et définir des requêtes renvoyant des ensembles d’instances d’entités.

Pour émettre une requête JPQL à partir de votre code Java, vous devez utiliser les méthodes appropriées de l’API EntityManager et de l’API de requête, en effectuant les étapes générales suivantes:

  • 1. Obtenir une instance de EntityManager, en utilisant l’injection ou explicitement via une instance EntityManagerFactory.
  • 2. Créez une instance de Requête en invoquant une méthode appropriée de EntityManager, telle que createQuery.
  • 3. Définissez un ou des paramètres de requête, le cas échéant, à l’aide de la méthode setParameter d’une requête appropriée.
  • 4. Si nécessaire, définissez le nombre maximum d’instances à récupérer et/ou spécifiez la position de la première instance à récupérer, à l’aide des méthodes de la requête setMaxResults et/ou setFirstResult.
  • 5. Si nécessaire, définissez un indice spécifique au fournisseur, à l’aide de la méthode de la requête setHint.
  • 6. Si nécessaire, définissez le mode de vidage pour l’exécution de la requête avec la méthode de la requête setFlushMode, en remplaçant le mode de vidage du gestionnaire d’entités.
  • 7. Exécutez la requête en utilisant la méthode d’une requête appropriée : getSingleResult ou getResultList. Cependant, dans le cas d’une opération de mise à jour ou de suppression, vous devez utiliser la méthode executeUpdate, qui renvoie le nombre d’instances d’entité mises à jour ou supprimées.

La liste complète des méthodes d’interface EntityManager, ainsi que des méthodes d’interface de l’API de requête, se trouve dans le document Enterprise JavaBeans 3.0 Specification: Java Persistence API, qui fait partie de JSR-220.

Maintenant que vous avez une idée approximative de la façon dont vous pouvez créer puis émettre une requête JPQL, vous voudrez peut-être voir quelques exemples pratiques. Le fragment de code suivant est tiré de la méthode doGet d’une servlet qui utilise une requête JPQL pour obtenir des informations sur tous les clients stockés dans la table relationnelle sous-jacente associée à l’entité client spécifiée dans la requête.

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

La méthode createQuery de l’instance EntityManager et la méthode getResultList de l’instance de requête présentent un intérêt particulier. createQuery de l’EntityManager est utilisé pour créer l’instance de requête dont la méthode getResultList est ensuite utilisée pour exécuter la requête JPQL transmise à createQuery en tant que paramètre. Comme vous pouvez le deviner, la méthode getResultList de la requête renvoie le résultat d’une requête sous la forme d’une liste dont les éléments, dans cet exemple particulier, sont convertis pour taper Customer.

Si vous devez récupérer un seul résultat, l’interface de l’API de requête propose la méthode getSingleResult, comme indiqué dans l’exemple suivant. Notez cependant que l’utilisation de getSingleResult provoquera une exception si vous récupérez plusieurs résultats.

Cet exemple illustre également l’utilisation de la méthode setParameter de la Requête par laquelle vous pouvez lier un argument à un paramètre de requête. Avec setParameter, vous pouvez lier les paramètres nommés et positionnels. Ici, cependant, vous liez un paramètre nommé.

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

Il est intéressant de noter que l’utilisation d’une instruction SELECT JPQL n’est pas la seule solution pour récupérer une instance d’entité unique. Vous pouvez également utiliser la méthode find de EntityManager, qui vous permet de récupérer une seule instance d’entité en fonction de l’id de l’entité transmis en tant que paramètre.

Dans certaines situations, vous devrez peut-être récupérer uniquement certaines informations de la ou des instances d’entité cible, en définissant une requête JPQL par rapport à un ou plusieurs champs d’entité. Voici à quoi ressemblerait l’extrait de code ci-dessus, si vous devez récupérer uniquement la valeur du champ cust_name de l’instance d’entité client interrogée ici:

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

De même, pour obtenir la liste complète des noms des clients, vous pouvez utiliser le code suivant:

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

Pour revenir à SQL, vous vous souvenez peut-être que la liste select d’une requête SQL peut être composée de plusieurs champs de la ou des tables spécifiées dans la clause FROM. Dans JPQL, vous pouvez également utiliser une liste de sélection composée, en sélectionnant les données uniquement dans les champs d’entité d’intérêt. Dans ce cas, cependant, vous devez créer la classe dans laquelle vous allez convertir le résultat de la requête. Dans la section suivante, vous verrez un exemple de requête de jointure JPQL dont la liste de sélection est composée des champs dérivés de plusieurs entités.

Définition des jointures JPQL

Comme SQL, JPQL vous permet de définir des requêtes de jointure. Dans SQL, cependant, vous définissez normalement une jointure qui combine des enregistrements de deux tables et/ ou vues ou plus, y compris uniquement les champs obligatoires de ces tables et vues dans la liste de sélection de la requête de jointure. En revanche, la liste de sélection d’une requête de jointure JPQL comprend généralement une seule entité ou même un seul champ d’entité. La raison en est la nature de la technologie JPA. Une fois que vous obtenez une instance d’entité, vous pouvez ensuite naviguer vers ses instances associées à l’aide des méthodes getter correspondantes. Cette approche rend inutile la définition d’une requête qui renverra toutes les instances d’entité associées d’intérêt à la fois.

Par exemple, pour obtenir des informations sur les commandes ainsi que leurs éléments de ligne en SQL, vous devez définir une requête de jointure sur les tables purchaseOrders et orderLineItems, en spécifiant les champs des deux tables dans la liste de sélection de la requête. Cependant, lorsque vous utilisez JPQL, vous pouvez définir une requête uniquement sur l’entité PurchaseOrder, puis accéder aux instances OrderLineItem correspondantes à l’aide de la méthode getOrderLineItems de PurchaseOrder, selon les besoins. Dans cet exemple, vous pouvez définir une requête JPQL sur les entités PurchaseOrder et OrderLineItem uniquement si vous devez filtrer les instances PurchaseOrder récupérées en fonction d’une ou de plusieurs conditions appliquées à OrderLineItem.

L’extrait suivant montre un exemple de requête de jointure JPQL en action. Pour mieux comprendre comment les entités impliquées sont liées les unes aux autres, vous pouvez revenir à la figure 1 illustrée dans la section Exemple d’application plus tôt dans l’article.

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

Dans l’exemple ci-dessus, vous utilisez la fonction d’agrégat MAX dans la clause SELECT de la requête join afin de déterminer le produit au prix le plus élevé, parmi ceux qui ont été fournis par Tortuga Trading et qui ont été commandés au moins une fois.

Une situation plus courante, cependant, est lorsque vous devez calculer, par exemple, le prix total des produits commandés, qui ont été fournis par un certain fournisseur. C’est là que la fonction d’agrégat de SOMME peut être utile. En SQL, une telle requête de jointure peut ressembler à ceci:

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

Malheureusement, la fonction SUM utilisée dans JPQL ne vous permet pas de passer une expression arithmétique comme argument. Cela signifie en pratique que vous ne pourrez pas passer p.price * l.quantity comme argument de la SOMME du JPQL. Cependant, il existe des moyens de contourner ce problème. Dans l’exemple suivant, vous définissez la classe LineItemSum dont le constructeur est ensuite utilisé dans la liste de sélection de la requête, en prenant p.price et l.quantity comme paramètres. Ce que fait le constructeur LineItemSum est de multiplier p. prix par l. quantité, en enregistrant le résultat dans sa variable de classe rslt. Ensuite, vous pouvez parcourir la liste LineItemSum récupérée par la requête, en additionnant les valeurs de la variable rslt de LineItemSum. L’extrait suivant montre comment tout cela peut être implémenté dans du code:

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

Entre autres choses, l’exemple ci-dessus illustre comment vous pouvez utiliser une classe Java personnalisée, et non une classe d’entité, dans la liste de sélection de la requête JPQL qui inclut les champs dérivés de plusieurs entités, en convertissant le résultat de la requête en cette classe. Dans la plupart des cas, cependant, vous devrez traiter des requêtes qui reçoivent une instance ou une liste d’instances d’une certaine entité.

Instances d’entité récupérées et Contexte de persistance actuel

Les résultats de la requête dans les exemples d’articles ont jusqu’à présent été simplement imprimés. Dans les applications du monde réel, cependant, vous devrez peut-être effectuer d’autres opérations sur les résultats de la requête. Par exemple, vous devrez peut-être mettre à jour les instances récupérées, puis les conserver dans la base de données. Cela soulève la question: les instances récupérées par une requête JPQL sont-elles prêtes à être traitées ultérieurement par l’application, ou certaines étapes supplémentaires sont nécessaires pour les préparer à cela ? En particulier, il serait intéressant de savoir dans quel état, concernant le contexte de persistance actuel, se trouvent les instances d’entités récupérées.

Si vous avez une certaine expérience de la persistance Java, vous devez savoir ce qu’est un contexte de persistance. Pour résumer, un contexte de persistance est un ensemble d’instances d’entité gérées par l’instance EntityManager associée à ce contexte. Dans les exemples précédents, vous avez utilisé la méthode createQuery de EntityManager pour créer une instance de Query pour exécuter une requête JPQL. En fait, l’API EntityManager comprend plus de vingt méthodes pour gérer le cycle de vie des instances d’entité, contrôler les transactions et créer des instances de requête dont les méthodes sont ensuite utilisées pour exécuter la requête spécifiée et récupérer le résultat de la requête.

En ce qui concerne un contexte de persistance, une instance d’entité peut se trouver dans l’un des quatre états suivants : new, managed, detached ou removed. À l’aide d’une méthode appropriée de EntityManager, vous pouvez modifier l’état d’une certaine instance d’entité selon les besoins. Il est intéressant de noter, cependant, que seules les instances en état géré sont synchronisées avec la base de données, lors du vidage vers la base de données. Pour être précis, les instances d’entité dans l’état supprimé sont également synchronisées, ce qui signifie que les enregistrements de base de données correspondant à ces instances sont supprimés de la base de données.

En revanche, les instances dans l’état nouveau ou détaché ne seront pas synchronisées avec la base de données. Par exemple, si vous créez une nouvelle instance PurchaseOrder puis invoquez la méthode flush de EntityManager, un autre enregistrement n’apparaîtra pas dans la table purchaseOrders à laquelle l’entité PurchaseOrder est mappée. En effet, cette nouvelle instance PurchaseOrder n’a pas été attachée au contexte de persistance. Voici à quoi pourrait ressembler le code:

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

Pour résoudre le problème, vous devez appeler la méthode persist de EntityManager pour la nouvelle instance PurchaseOrder avant d’appeler le flush, comme indiqué dans l’exemple suivant:

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

Alternativement, à condition que vous ayez défini l’option cascade sur PERSIST ou ALL lors de la définition de la relation avec PurchaseOrder dans l’entité Client, vous pouvez ajouter l’instance PurchaseOrder nouvellement créée à la liste des commandes associées à l’instance client, en remplaçant l’opération persister par ce qui suit:

  cust.getPurchaseOrders().add(ord); 

La discussion ci-dessus concernant les états d’instance d’entité nous amène à la question intéressante de savoir si les instances d’entité récupérées par une requête JPQL deviennent automatiquement gérées, ou si vous devez prendre soin de définir explicitement leur état à gérer. Selon la spécification JPA, quelle que soit la façon dont vous récupérez les entités — qu’il s’agisse de la méthode find de EntityManager ou d’une requête — elles sont automatiquement attachées au contexte de persistance actuel. Cela signifie que les instances d’entité récupérées par une requête JPQL sont automatiquement gérées. Vous pouvez, par exemple, modifier la valeur du champ d’une instance récupérée, puis synchroniser cette modification avec la base de données en appelant la méthode flush de EntityManager ou en validant la transaction en cours. Vous n’avez pas non plus à vous soucier de l’état des instances associées aux instances récupérées. Le fait est que la première fois que vous accédez à une instance associée, elle est gérée automatiquement. Voici un exemple simple montrant comment tout cela fonctionne dans la pratique:

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

Notez que vous n’appelez pas la méthode persist pour l’instance PurchaseOrder récupérée, ni pour son instance OrderLineItem associée en cours de modification ici. Malgré cela, les modifications apportées au premier élément de la commande seront conservées dans la base de données lors de la validation de la transaction. Cela se produit parce que les instances d’entité récupérées et leurs associations sont automatiquement attachées au contexte de persistance actuel. Comme mentionné précédemment, les premiers deviennent gérés lorsqu’ils sont récupérés, et les seconds sont attachés au contexte lorsque vous y accédez.

Dans certaines situations, vous pouvez souhaiter que les associations soient attachées au contexte lors de l’exécution de la requête, plutôt que lors du premier accès. C’est là qu’une JOINTURE de RÉCUPÉRATION est utile. Disons que vous souhaitez obtenir toutes les commandes appartenant à un certain client, lors de la récupération de cette instance client. Cette approche garantit que vous traitez les commandes clients disponibles au moment de l’exécution de la requête. Si, par exemple, une nouvelle commande est ajoutée à un autre contexte puis synchronisée avec la base de données avant d’accéder pour la première fois à la liste des commandes associées à l’instance client récupérée, vous ne verrez pas cette modification tant que vous n’aurez pas actualisé l’état de l’instance client à partir de la base de données. Dans l’extrait de code suivant, vous utilisez la requête join qui renvoie l’instance client dont cust_id est 1 et récupère les instances PurchaseOrder associées à l’instance client en cours de récupération.

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

Ne faisant pas partie du résultat explicite de la requête, les instances d’entité PurchaseOrder associées à l’instance client récupérée ici sont également récupérées et attachées au contexte de persistance actuel lors de l’exécution de la requête.

Utilisation de requêtes SQL natives

Il est intéressant de noter que vous n’êtes pas limité à JPQL lors de la définition de requêtes à exécuter ensuite avec l’API de requête. Vous serez peut-être surpris d’apprendre que l’API EntityManager propose des méthodes pour créer des instances de requête pour exécuter des instructions SQL natives. La chose la plus importante à comprendre à propos des requêtes SQL natives créées avec les méthodes EntityManager est qu’elles, comme les requêtes JPQL, renvoient des instances d’entité, plutôt que des enregistrements de table de base de données. Voici un exemple simple de requête SQL native dynamique:

  ... 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 est toujours en évolution et n’a pas beaucoup de ces fonctionnalités importantes disponibles dans SQL. Dans la section précédente de Définition des jointures JPQL, vous avez vu un exemple d’incomplétude de JPQL: vous avez dû faire beaucoup de travail par vous-même car la fonction d’agrégat de somme de JPQL ne peut pas prendre une expression arithmétique comme paramètre. En revanche, la fonction SUM de SQL n’a pas une telle limitation. C’est donc un bon exemple d’où le remplacement de JPQL par du SQL natif pourrait être efficace. Le code suivant illustre comment vous pouvez simplifier les choses dans cet exemple particulier en choisissant SQL natif plutôt que 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/>"); ... 

Entre autres choses, l’exemple ci-dessus illustre que vous pouvez lier des arguments à des paramètres de requête natifs. En particulier, vous pouvez lier des arguments aux paramètres positionnels de la même manière que si vous traitiez une requête JPQL.

Le principal inconvénient des requêtes natives est la complexité de la liaison des résultats. Dans l’exemple, la requête produit un résultat unique de type simple, évitant ainsi ce problème. En pratique, cependant, vous devez souvent faire face à un ensemble de résultats de type complexe. Dans ce cas, vous devrez déclarer une entité à laquelle vous pouvez mapper votre requête native, ou définir un ensemble de résultats complexes mappé à plusieurs entités ou à un mélange d’entités et de résultats scalaires.

Utilisation de procédures stockées

Un autre inconvénient des requêtes natives est que votre code Java dépend directement de la structure de la base de données sous-jacente. Si vous modifiez cette structure sous-jacente, vous devrez ajuster les requêtes natives concernées dans vos servlets et/ou d’autres composants d’application, puis recompiler et redéployer ces composants. Pour contourner ce problème, tout en utilisant des requêtes natives, vous pouvez tirer parti des procédures stockées, déplacer des requêtes SQL complexes dans des programmes stockés et exécutés dans la base de données, puis appeler ces programmes stockés au lieu de faire des appels directs aux tables sous-jacentes. Cela signifie en pratique que les procédures stockées peuvent vous éviter de traiter les tables sous-jacentes directement à partir des requêtes codées en dur dans votre code Java. L’avantage de cette approche est que dans la plupart des cas, vous n’aurez pas besoin de modifier votre code Java pour suivre les modifications de la structure de la base de données sous-jacente. Au lieu de cela, seules les procédures stockées devront être corrigées.

Pour revenir à l’exemple discuté dans la section précédente, vous pouvez déplacer la requête de jointure complexe utilisée dans une fonction stockée, créée comme suit:

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

Cela simplifie la requête native utilisée dans votre code Java et supprime la dépendance des tables sous-jacentes:

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

Conclusion

Comme vous l’avez appris dans cet article, JPQL est un outil puissant pour accéder aux données relationnelles à partir d’applications Java utilisant la persistance Java. Avec JPQL, contrairement à SQL/JDBC, vous définissez des requêtes sur des entités JPA mappées sur des tables de base de données sous-jacentes plutôt que d’interroger directement ces tables, traitant ainsi une couche d’abstraction qui masque les détails de la base de données de la couche logique métier. Vous avez également appris que JPQL n’est pas la seule option pour créer des requêtes sur des entités JPA — dans certaines situations, l’utilisation de requêtes SQL natives est plus pratique.