1.Langage JPQL
1.1. Généralités
JPQL est un langage de requêtes proche de SQL et qui est défini par la spécification JPA; il permet de gérer la persistance de données dans des applications java. Avec ce langage développé avec la syntaxe SQL on fait des requêtes sur des JPA Entity mappées dans des tables de bases de données relationnelles .
JPQL récupère des informations ou des données en utilisant la clause SELECT, effectue des mises à jour à l'aide de la clause UPDATE et la clause DELETE.
1.2. Interfaces Query et TypedQuery
Dans la spécification JPA 2 les requêtes sont représentées par deux interfaces; l'interface Query (déjà introduite dès JPA 1) et l'interface TypedQuery introduite à partir de JPA 2. L'interface TypedQuery dérive de Query.
Depuis JPA 2, l'interface Query est utilisée lorsque le type du résultat de la requête est inconnu ou lorsqu'une requête renvoie des résultats polymorphes et que le plus petit dénominateur commun connu de tous les objets du résultat est Object.
Lorsqu'un type de résultat plus spécifique est attendu, les requêtes utilisent l'interface TypedQuery. Il est plus facile d'exécuter des requêtes et de traiter les résultats d'une manière sûre en utilisant l'interface TypedQuery.
L'exécution d'une requête JPQL passe par la création d'un objet de type Query à partir d'une instance d'EntityManager.
Il faut créer un objet de la classe EntityManager qu'on peut définir comme suite;
EntityManager em = ... ;
Une fois qu'on ait créé une instance de EntityManager , l'exécution d'une requête JPQL se fait en deux temps :
On définit l'objet requête de type Query avec la methode createQuery() de "em" qui retourne l'unique objet résultat de la requête; Si la requête retourne plusieurs objets, une exception de type NonUniqueResultException est levée.
On exécute la requête, et on récupère le résultat.
Notons que la classe Query possède également la méthode getSingleResult() qui peut servir .
1.3. Structure d'une requête JPQL
Comme on l'a dit ci-dessus le syntaxe des requêtes JPQL ressemble beaucoup à celui de SQL. Toutefois SQL permet de faire des requêtes directement sur les champs des tables de la base de données et JPQL lui requête sur des objets Entity.
D'une façon générale la structure d'une requête JPQL est de la forme:
SELECT ... FROM ... [WHERE ...] [GROUP BY ... [HAVING ...]] [ORDER BY ...]
La requête SELECT et la clause FROM sont requises à chaque fois qu'on veut récupérer des données, par contre les requêtes de mise à jour et de suppression de données ont une forme légèrement différente. Les autres clauses JPQL à savoir WHERE, GROUP BY, HAVING et ORDER BY sont facultatives.
Les structures des requêtes DELETE et UPDATE de JPQL sont:
DELETE FROM ... [WHERE ...] UPDATE ... SET ... [WHERE ...]
1.4. Faire une requête avec la méthode createQuery ou createNamedQuery
Les méthodes "EntityManager.createQuery()" et "EntityManager.createNamedQuery()" servent à faire des requêtes sur une base de données en utilisant le langage de requêtes JPQL. La méthode createQuery est utilisée pour créer des requêtes dynamiques, c'est à dire des requêtes définies directement dans la logique métier d'une application comme on le voit ci-dessous:
public List findWithName(String name) { return em.createQuery( "SELECT c FROM Customer c WHERE c.name LIKE :custName") .setParameter("custName", name) .setMaxResults(10) .getResultList(); }
On voit bien ici que la requête va être construite au moment de l’exécution de l'application; d'où l'utilisation de la méthode createQuery(...).
La méthode createNamedQuery est utilisée pour créer des requêtes statiques ou des requêtes définies dans les métadonnées à l'aide de l'annotation javax.persistence.NamedQuery. Cette annotation @NamedQuery spécifie le nom de la requête qui sera utilisée avec la méthode createNamedQuery; ce qui est illustré dans le code ci-dessous:
@NamedQuery( name="findAllCustomersWithName", query="SELECT c FROM Customer c WHERE c.name LIKE :custName" ) ... @PersistenceContext public EntityManager em; ... customers = em.createNamedQuery("findAllCustomersWithName") .setParameter("custName", "Smith") .getResultList();
Comme pour la plupart des autres opérations dans JPA, on voit bien que si on fabrique des requêtes , on utilise un EntityManager (représenté par em dans les extraits de code suivants); c'est en quelque sorte une fabrique pour Query et TypedQuery;
Query q1 = em.createQuery("SELECT c FROM Country c"); TypedQuery q2 = em.createQuery("SELECT c FROM Country c", Country.class);
Dans le code ci-dessus, la même requête JPQL qui récupère tous les objets Pays dans une table de la base de données est représentée à la fois par q1 et q2. Lors de la construction d'une instance TypedQuery, le type de résultat attendu doit être passé comme argument supplémentaire, comme démontré pour q2. Comme dans ce cas le type du résultat est connu (la requête renvoie uniquement les objets Pays), on utilise TypedQuery.
Il existe un autre avantage qui incite à utiliser TypedQuery. Dans un contexte de requêtes identique à celui ci-dessus, s'il n'existe pas encore d'instances de pays dans la base de données et que la classe Pays est inconnue, seule la variante TypedQuery est valide car elle introduit la classe Country dans la base de données.
.......
1.5. Exemple de fonctions simples et de fonctions imbriquées
Les fonctions simples retournent des valeurs basées sur des données d'entrée. Les fonctions imbriquées retournent les valeurs résultantes de calcul issus des données d'entrée. Ici on va créer une application qui illustre ces deux types de fonctions; On suppose avoir une table mappée issues d'un Entity appelé Employee et qui a un certain nombre d'attributs tels que: Eid, Ename, Edeg et Esalary:
package com.omara.eclipselink.service; import java.util.List; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.Persistence; import javax.persistence.Query; public class ScalarandAggregateFunctions { public static void main( String[ ] args ) { EntityManagerFactory emfactory = Persistence.createEntityManagerFactory( "Eclipselink_JPA" ); EntityManager entitymanager = emfactory.createEntityManager(); //fonction simple Query query = entitymanager.createQuery("Select UPPER(e.ename) from Employee e"); List<String> list = query.getResultList(); for(String e:list) { System.out.println("Employee NAME :"+e); } //fonction imbriquées Query query1 = entitymanager.createQuery("Select MAX(e.salary) from Employee e"); Double result = (Double) query1.getSingleResult(); System.out.println("Max Employee Salary :" + result); } }
......
2. Chargement de type Eager ou de type Lazy
Un des concepts principaux dans la spécification de JPA c'est le faite de faire une copie de la base de données en mémoire cache. Lorsque dans une application il y a des transactions sur des données présentes dans la base de données, celles-ci affectent d'abord les données dupliquées qui sont en mémoire cache, et c'est uniquement lorsque les données sont validées par le gestionnaire de JPA Entity que les modifications sont effectuées dans la base de données.
Si avec JPA on doit charger une Entity (entité) depuis la base de données (qu'il s'agisse par un appel à EntityManager.find(...) ou par une requête), on doit être précis sur les informations à charger. Est ce qu'on doit-il charger tous les attributs de l'Entity? et parmi ces attributs, doit-on charger les Entity qui sont en relation avec l'Entity chargée ? Ces questions sont importantes, car la façon d'y répondre peut avoir un impact sur les performances de l'application.
Dans JPA, l'opération de chargement d'une entité depuis la base de données est appelée fetch; Il existe deux façons de charger des enregistrements depuis la base de données: soit le chargement se fait par stratégie Eager (chargement impatiente) ou par stratégie Lazy (chargement différé ou paresseux).
3. Chargement immédiat - Eager fetch
Dans cette stratégie de chargement on charge l'enregistrement entier de la base en utilisant la clé primaire. Dans ce cas les données associées à l'Entity sont récupérées immédiatement; Cette stratégie est appliquée par défaut pour les annotation @Basic, @OneToOne et @ManyToOne.
4. Chargement différé - Lazy fetch
Dans cette stratégie de chargement, Il y a une vérification de la possibilité de l'extraction de L'Entity avec la clé primaire. Ensuite, plus tard, si on utilise des méthodes getterXXX de cette Entity on extrait l'ensemble de l'Entity. C'est la stratégie Lazy qui est utilisée lorsqu'on charge l'enregistrement pour la première fois. De cette façon, une copie de l'enregistrement entier est stockée dans la mémoire cache. Cette stratégie est appliquée par défaut pour les annotations @OneToMany et @ManyToMany
5. Stratégies d'héritage
JPA est un API défini au sein du langage Java. Par conséquent, il prend en charge tous les concepts orientés objet pour la persistance des Entity. L'héritage est le concept central du langage orienté objet, donc nous pouvons utiliser des relations d'héritage ou des stratégies d'héritage entre les Entity. On va voir que JPA prend en charge trois types de stratégies d'héritage telles que SINGLE_TABLE, JOINED_TABLE et TABLE_PER_CONCRETE_CLASS.
Single Table strategy
La stratégie Single-Table (de table unique) prend tous les champs de classes (super et sous-classes) et les mappe dans une seule table connue sous le nom de stratégie SINGLE_TABLE
.....
....
(à suivre)..