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.
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() ;
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.
LAZY
ou
EAGER
en relation de ces entités s'applique bien sûr aux entités résultat.
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() ;
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) ; }
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.
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
}
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() ;
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()) ; }
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.
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.
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.