4. Exécution, analyse du résultat

4.1. Exécution d'une requête dynamique

Nous avons déjà vu comment exécuter une requête dynamique de sélection sur un exemple. Il suffit d'invoquer l'une des trois méthodes getResultList(), getSingleResult() ou getFirstResult() sur l'objet query que l'on a créé sur cette requête. Dans le cas d'une requête de mise à jour ( update ou delete), c'est la méthode executeUpdate(), dont le fonctionnement est le même qu'en JDBC. Cette méthode retourne un entier, qui correspond au nombre d'enregistrements modifiés ou effacés par cette requête.

4.2. Exécution d'une requête nommée

Voyons l'exécution d'une requête nommée. Le principe est légèrement différent, puisque l'objet Query n'est pas construit de la même façon.

Exemple 37. Exécution d'une requête nommée

// construction d'un entity manager
EntityManager em = ... ;

 // récupération d'une requête nommée par interrogation 
 // de l'entity manager
Query marinByName = em.createNamedQuery("Marin.findByName") ;

 // paramétrage de la requête
marinByName.setParameter("name",  "Surcouf") ;

 // exécution de la requête
List<Marin> marins = marinByName.getResultList() ;

Le paramètre passé à la méthode createNamedQuery est le nom de cette requête, défini par l'attribut name de l'annotation @NamedQuery. JPA nous impose l'unicité de ce nom au sein d'une unité de persistance donnée, ce qui nous permet de le récupérer sans doublon. En revanche, si l'on passe un nom qui n'existe pas, la méthode nous jettera une exception de type IllegalArgumentException. L'exécution d'une requête nommée suit le même processus, que cette requête soit exprimée en JPQL ou en SQL natif. Dans notre exemple, la requête demandée pourrait parfaitement être une requête native.

4.3. Analyse du résultat

4.3.1. Le résultat est une liste d'entités

Le cas où la requête retourne une liste d'entités JPA a déjà été vu en exemple. Cette liste est stockée dans une liste Java classique, sur laquelle on peut itérer. Tout ce qui a été vu, notamment sur le chargement LAZY ou EAGER en relation de ces entités s'applique bien sûr aux entités résultat.

4.3.2. Le résultat est une liste d'objets

Considérons l'exemple suivant.

Exemple 38. Écriture d'une requête ne retournant pas des entités

Query query = em.createQuery("select marin.nom, marin.prenom from Marin marin") ;
List result = em.getResultList() ;

Nous avons volontairement laissé la déclaration List sans la typer, car son contenu n'est plus le même que dans le cas où notre requête portait sur une entité JPA. Chaque élément de cette liste est un tableau. Il y a autant d'éléments tableau qu'il y a de lignes dans le résultat de notre requête SQL. Examinons à présent le contenu de chacun de ces tableaux. Pour cette requête, chaque ligne du résultat est composée de deux champs : nom et prenom, qui sont deux chaînes de caractères. On peut spécifier tous les champs que l'on veut, quel que soit leur type : type de base ou entité. JPA construit un tableau pour chaque ligne du résultat. Chacun de ces tableaux est composé d'autant d'éléments qu'il y a de champs spécifiés. Chaque élément correspond à un des champs spécifiés, dans le bon ordre. Nous pouvons donc écrire le code qui va nous permettre d'analyser ce résultat.

Exemple 39. Analyse d'une requête ne retournant pas des entités

// on reprend la requête précédente
Query query = em.createQuery("select marin.nom, marin.prenom from Marin marin") ;
List result = em.getResultList() ;

 // le résultat est composée d'autant de lignes qu'il y a 
 // de ligne dans le résultat :
 for (Object ligneAsObject : result) {

     // ligne correspond à une des lignes du résultat
    Object[] ligne = (Object[])ligneAsObject ;
	
     // cette liste est composée de deux éléments : nom et prenom
    String nom = (String)ligne[0] ;
    String prenom = (String)ligne[1] ;
	
    System.out.println(nom +  " " + prenom) ;
}

Cette analyse est un peu complexe techniquement, et surtout très fragile. L'utilisation de cast sur des éléments lus en aveugle sera effectivement pénible à mettre au point. De plus, le moindre changement de modèle risque de faire échouer tantôt la requête, tantôt son analyse, puisque l'on ne manipule que des Object, convertis à la volée. Notons que ce système fonctionne également si l'un des champs spécifiés dans la requête est une entité JPA. Dans ce cas, cette entité se retrouvera dans la liste des éléments de la requête.

4.3.3. Le résultat est un objet construit

Il est enfin possible, en JPQL, de construire des objets résultat directement à partir des requêtes. Continuons avec l'exemple de nos objets marin, et de notre requête qui retourne leurs noms et prénoms. Construisons une classe simple, un bean, capable de stocker chaque ligne du résultat de notre requête.

Exemple 40. Objet résultat : NameBean

package org.paumard.cours.model;
	
 public  class NameBean {

     private String nom ;
     private String prenom ;

     public NameBean(String nom, String prenom) {
         this.nom = nom ;
         this.prenom = prenom ;
    }
    
     // suivent les getters et setters
}

Cet objet modélise exactement chaque ligne du résultat de notre requête précédente. On peut écrire à nouveau cette requête, de la façon suivante.

Exemple 41. Requête retournant un objet résultat

Query query = em.createQuery(
     "select new org.paumard.cours.model.NameBean(p.nom, p.prenom) " + 
     "from Personne p") ;
List result = query.getResultList() ;

La nouveauté de cette requête est l'appel à la construction de l'objet NameBean, en passant au constructeur les paramètres du résultat de la requête. Cet objet résultat doit être désigné par son nom complet (donc avec le nom du package), et, bien sûr, ce constructeur doit exister. Notons que de plus, il ne doit pas y avoir ambiguïté sur ce construteur. Écrivons le code qui permet d'analyser cette requête.

Exemple 42. Analyse d'une requête retournant un objet résultat

// exécution de la requête de l'exemple précédent
List result = query.getResultList() ;

 // itération sur les éléments de cette liste
 for (Object element : result) {

     // chaque élément est de type NameBean
    NameBean nameBean = (NameBean)element ;
    
    System.out.println(nameBean.getNom() +  " " + nameBean.getPrenom()) ;
}

Cette approche fonctionne également si l'un des champs sélectionnés est une entité JPA.

4.4. Cas des résultats de grande taille

Que l'on soit en JDBC ou en JPA, il est toujours coûteux de convertir de grandes quantités de données du domaine SQL vers le domaine Java. La solution généralement adoptée pour analyser des quantités importantes de lignes de résultats consiste à les prendre page par page, de façon à minimiser la charge que représenterait la conversion de tout un paquet en une seule fois. Pour cela, on dispose de deux méthodes sur l'objet Query, qui permettent de sélectionner une page de lignes dans un résultat de grande taille :
  • setFirstResult(int) : permet de fixer le numéro d'index de la première ligne résultat retournée.
  • setMaxResult(int) : permet de fixer le nombre de lignes retournées par le résultat.
Une fois ces deux paramètres fixés, on peut appeler la méthode getResultList(). Si cette méthode retourne un nombre d'enregistrements moins important que la quantité demandée, c'est que la requête a été épuisée.

4.5. Remarques

L'interaction entre les requêtes et les transactions est un point qui mérite que l'on s'y attarde. Si un résultat de requête va être exploité en vue de modifier les objets retournés par cette requête, alors cette requête doit être exécutée dans une transaction. Ces objets seront créés dans le contexte de cette transaction, et pourront donc être modifiés ou effacés sans problème. En revanche, si un résultat de requête ne sera exploité qu'en lecture, par exemple à des fins d'affichage, alors cet attachement des objets résultat à la transaction est une surcharge inutile. Exécuter la requête à l'extérieur de la transaction sera moins coûteux en temps de calcul. Enfin, que se passe-t-il lorsque l'on modifie des objets, et que dans la même transaction on exécute une requête JPQL dont le résultat comporte ces objets modifiés ? Pour répondre à cette question, il faut bien distinguer les appels à la méthode find() de l' entity manager , et les requêtes telles que nous les avons vues dans cette partie. La méthode em.find() demande un objet persistant par sa clé primaire. Si l' entity manager possède cet objet dans son cache, alors il le retourne sans faire de requête sur la base. Comme on ne peut pas changer la clé primaire d'un objet, tout se passera bien. Les requêtes JPQL fonctionnent différemment : elles sont converties en SQL, puis exécutées sur la base de données. On peut être dans une configuration où un objet se trouve toujours en base dans l'état dans lequel il était avant la transaction, et dans le cache de l' entity manager , dans un état modifié. La modification de cet état peut faire que sa version en base vérifie les contraintes de la requête, mais pas sa version dans le cache. Pour éviter ce problème, une façon de faire consiste à recopier le cache de l' entity manager en base, ce sont cette fois les bons objets qui seront retournés par la requête. Notons que certaines implémentations peuvent aussi avoir des moyens pour exécuter des requêtes SQL à la fois sur le cache de chaque entity manager et la base de données. Dans ce cas il n'y a pas à recopier le cache en base avant l'exécution de la requête.
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