3. Définition de requêtes

3.1. Requêtes dynamiques

La première façon de créer une requête JPQL consiste à créer une chaîne de caractères qui porte cette requête, et à la passer à la méthode createQuery() de l' entity manager . Cette approche est parfaitement légale, mais elle pose un problème, et comporte un piège. Le problème est que la traduction de la requête JPQL en SQL se fait au moment de la création de l'objet Query, et à chaque instanciation de cet objet. Si la requête est créée dans une servlet, ou dans un EJB, le coût de traitement devra être payé à chaque fois que l'utilisateur fait appel à cette servlet ou à cet EJB. Le piège se trouve dans la façon dont on veut prendre en compte les paramètres de la requête. Supposons que l'on ait la méthode suivante.

Exemple 32. Une (mauvaise) requête dynamique paramétrée

public List<Marin> getMarinByName(String name) {

    Query query = em.createQuery("select marin from Marin marin " + 
                                  "where marin.nom = '" + name +  "'") ;
    
    List<Marin> marins = query.getResultList() ;
    
     return marins ;
}

Cette requête est censée retourner tous les marins dont le nom est passé en paramètre, et c'est ce qu'elle fait. Cela dit, on peut aussi lui passer en paramètre la chaîne de caractères suivante :
Surcouf' and prenom = 'Robert
Et dans ce cas, elle effectue une tout autre requête. Cette attaque, connue sous le nom d'injection de code est très classique, et doit être absolument évitée. Pour cela, JPQL expose une fonctionnalité analogue aux preparedStatement de JDBC.

3.2. Requêtes paramétrées

Il existe deux moyens de paramétrer une requête. Les deux moyens consistent à placer des éléments particuliers dans la chaîne JPQL, puis de donner une valeur à ces éléments. Dans le premier cas ces éléments sont juste numérotés : ?1, ?2, etc... Dans le second cas, ils sont nommés : :nom, :adresse, etc...

3.2.1. Paramètrage numéroté

Voyons tout d'abord le paramétrage anonyme.

Exemple 33. Paramètrage d'une requête, première méthode

Query query = em.createQuery("select marin from Marin marin " + 
                              "where marin.nom = ?1 and marin.prenom = ?2") ;
query.setParameter(1,  "Surcouf") ;
query.setParameter(2,  "Robert") ;

List<Marin> marins = query.getResultList() ;

3.2.2. Paramétrage nommé

Voici la même requête, avec des paramètres nommés.

Exemple 34. Paramètrage d'une requête, deuxième méthode

Query query = em.createQuery("select marin from Marin marin " + 
                              "where marin.nom = :nom and marin.prenom = :prenom") ;
query.setParameter("nom",  "Surcouf") ;
query.setParameter("prenom",  "Robert") ;

List<Marin> marins = query.getResultList() ;

On prendra garde dans cette deuxième méthode : les noms des paramètres sont préfixés par ":" dans la chaîne JPQL, mais ce caractère n'est pas repris lors des appels à setParameter().

3.2.3. Cas particulier des dates

Comme à l'habitude, les objets java.util.Date et java.util.Calendar posent problème en tant que paramètre. De sorte qu'ils soient correctement convertis, il faut ajouter un paramètre à l'appel à setParameter(), qui porte le type temporel dans lequel la conversion doit se faire.

Exemple 35. Paramètrage d'une requête, cas des dates

Query query = em.createQuery("select marin from Marin marin " + 
                              "where marin.dateDeNaissance = :ddn") ;
query.setParameter("ddn", dateDeNaissance, TemporalType.DATE) ;

List<Marin> marins = query.getResultList() ;

Cette méthode existe également dans le cas de paramètres anonymes et numérotés.

3.3. Requêtes nommées

Dans de nombreux cas, les requêtes utilisées sont connues dès la compilation de notre application. JPQL nous offre alors la possibilité de déclarer ces requêtes en tant que requêtes nommées . Ces requêtes nommées sont déclarées dans des annotations, et donc analysées par l'implémentation JPA au chargement de la classe. Elles peuvent donc être converties en code SQL au moment de ce chargement, et ne pas surcharger l'exécution de notre application. Une requête nommée est déclarée dans une annotation @NamedQuery. Cette annotation prend deux attributs : name, qui porte le nom de la requête, et query, qui porte la requête JPQL. Le nom de la requête doit être unique pour une même unité de persistance. Sans que cela soit une obligation, il est donc pratique de le préfixer par le nom d'une entité. Plusieurs annotations @NamedQuery peuvent être regroupées dans une annotation @NamedQueries, qui prend un tableau de @NamedQuery en attribut. Ces deux annotations doivent être posées sur la classe d'une entité JPA.

Exemple 36. Déclaration de requêtes nommées

@NamedQueries({
    @NamedQuery(
        name="Marin.findAll",
        query="select marin from Marin marin"),
    @NamedQuery(
        name="Marin.findByName",
        query="select marin from Marin marin where marin.name = :name"), 
    @NamedQuery(
        name="Marin.countAll",
        query="select count(marin) from Marin marin"),
})
 @Entity(name="Marin")
 public  class Marin  implements Serializable {

	 // reste de la classe
}

Remarquons plusieurs choses sur cet exemple. Tout d'abord, nous avons donné un nom explicite à notre entité : Marin. Sans cela, il aurait fallu utiliser le nom complet de la classe Marin dans nos requêtes JPQL. Les annotations @NamedQueries doivent être posées sur la classe d'une entité. Rien ne dit que ces requêtes doivent nécessairement retourner des instances ou collection d'instances de ces entités. Il est simplement pratique, lisible et habituel de mettre sur une classe les requêtes qui s'y rapportent. On notera d'ailleurs que la dernière de nos requête retournent un entier. Enfin, et afin de garantir l'unicité du nommage de nos requêtes au niveau de l'unité de persistance, nous avons préfixé le nom de ces requêtes par le nom de l'entité sur laquelle elles sont définies. Il ne s'agit pas d'une obligation, mais d'une bonne façon de faire.

3.4. Requêtes natives

Le JPQL ne permet pas de faire tout ce que l'on peut faire en SQL, pour cela il est possible de définir également des requêtes en SQL natif en JPA. On dispose pour cela de deux méthodes.
  • On peut utiliser la méthode createNativeQuery() de l' entity manager . Cette méthode prend une chaîne de caractères en paramètre : le code SQL natif que l'on veut exécuter.
  • On peut également définir des requêtes nommées natives. La syntaxe de déclaration est exactement la même que pour les requêtes JPQL nommées que l'on vient de voir, sauf que les annotations à utiliser sont @NamedNativeQueries et @NamedNativeQuery.
Cette possibilité offerte présente l'inconvénient d'attacher le code JPA à une base de données particulière. Cela dit, elle permet aussi de migrer des applications legacy plus simplement.
JPA & EJB
Retour au blog Java le soir
Cours & Tutoriaux
Table des matières
Introduction
1. Objet du mapping objet / relationnel
2. Un peu d'histoire
Un premier exemple
1. Introduction
2. Création de l'environnement technique
2.1. Introduction
2.2. Création de la base Derby
2.3. Création du projet NetBeans et d'une première entité
2.4. Structure d'un projet persistant
2.5. Une première classe persistante
2.6. Un premier fichier persistence.xml
3. Utilisation de ce premier exemple
3.1. Écriture du code d'utilisation
3.2. Exécution de notre premier exemple
3.3. Modification de la class Marin
3.4. Opérations CRUD
Mettre un jeu de classes en base
1. Introduction
2. Définition d'une entité JPA
2.1. Écriture de l'entité
2.2. Annotation de l'entité
2.3. Annotations des champs
2.4. Exemple d'utilisation
3. Opérations sur les entités
3.1. Introduction
3.2. Opération PERSIST
3.3. Opération REMOVE
3.4. Opération REFRESH
3.5. Opération DETACH
3.6. Opération MERGE
4. Mise en relation d'entités
4.1. Introduction
4.2. Relations unidirectionnelles et bidirectionnelles
4.3. Relation 1:1
4.4. Relation 1:p
4.5. Relation p:1
4.6. Relation n:p
4.7. Comportement cascade
4.8. Effacement des entités orphelines
5. Charger des entités et leurs relations
6. Objets inclus
6.1. Introduction
6.2. Déclaration d'un objet inclus
6.3. Utilisation d'objets inclus
6.4. Cas où l'objet inclus est nul
6.5. Renommer les colonnes incluses
6.6. Collections d'objets inclus
L'API Collection en base
1. Introduction
2. Enregistrer une collection d'entités
2.1. Enregistrement d'une collection simple
2.2. Enregistrement d'un Set
2.3. Enregistrement d'une List
3. Enregistrer une collection de types de base
4. Enregistrement d'une Map
4.1. Table de hachage de type (type de base, entité)
4.2. Cas où la clé est un champ de la valeur
4.3. Cas d'une table (entité, entité)
Héritage
1. Introduction
2. Enregistrement d'une hiérarchie de classes
2.1. Entité et super-classe non enregistrée
2.2. Position du problème
2.3. Trois façons de faire
3. Stratégie SINGLE_TABLE
3.1. Fonctionnement
3.2. Mise en place
3.3. Limitations
4. Stratégie JOINED
4.1. Fonctionnement
4.2. Mise en place
4.3. Limitations
5. Stratégie TABLE_PER_CLASS
5.1. Fonctionnement
5.2. Mise en place
5.3. Limitations
Requêtes
1. Introduction
2. Un premier exemple
2.1. Écriture d'une première requête
2.2. Exécution d'une première requête
2.3. Exécution d'une première requête d'agrégation
3. Définition de requêtes
3.1. Requêtes dynamiques
3.2. Requêtes paramétrées
3.3. Requêtes nommées
3.4. Requêtes natives
4. Exécution, analyse du résultat
4.1. Exécution d'une requête dynamique
4.2. Exécution d'une requête nommée
4.3. Analyse du résultat
4.4. Cas des résultats de grande taille
4.5. Remarques
5. Clause From
5.1. Définition des entités
5.2. Jointures dans la clause From
5.3. Remarque finale sur les jointures en JPQL
6. Clause Where
6.1. Variables et chemins dans une clause where
6.2. Expressions conditionnelles et opérateurs
6.3. Requêtes imbriquées
6.4. Opérateurs any, all et some
6.5. Expressions fonctionnelles
7. Clauses Group By et Having
8. Opérations Update et Delete
EJB
1. Introduction
2. Un premier exemple
2.1. Introduction
2.2. Installation dans Glassfish à l'aide de Netbeans
2.3. Création d'un premier EJB
2.4. Déploiement de notre premier EJB
2.5. Création d'un client
3. Mise en oeuvre du pattern session facade
3.1. Introduction
3.2. Modèle objet
3.3. Définition de l'unité de persistance
3.4. Assemblage de notre application
3.5. Assemblage et déploiement
3.6. Utilisation du client
4. Opération de persistance en façade
4.1. Introduction
4.2. Enrichissement du service
4.3. Création de la méthode findMarinById(long)
4.4. Ajout de la méthode findAllMarins()
4.5. Utilisation dans un code client
5. Types d'EJB
5.1. Introduction
5.2. Qu'est-ce qu'un EJB ?
5.3. Écriture d'un EJB session
5.4. Qu'est-ce qu'une méthode métier ?
5.5. EJB avec ou sans état
5.6. Gestion des transactions
5.7. Restrictions
6. Cycle de vie d'un EJB
6.1. Cas des EJB sans état
6.2. Cas des EJB avec état
6.3. Injection de dépendances
7. Transaction gérée par l'EJB
7.1. Introduction
7.2. Déclaration du mode transactionnel
7.3. Gestion de la transaction
7.4. Fonctionnement de la transaction
7.5. Cas des EJB avec état
8. Transaction gérée par le serveur
8.1. Introduction
8.2. Déclaration du mode transactionnel
8.3. Gestion de la transaction
8.4. Fonctionnement de la transaction
8.5. Remarques
9. Intercepteurs
9.1. Introduction
9.2. Aperçu général
9.3. Cycle de vie d'un intercepteur
9.4. Object InvocationContext
9.5. Interception d'un EJB ou d'une méthode métier
9.6. Exemple de mise en œuvre d'un intercepteur