Recherche sur le blog

mercredi 8 mars 2017

[Java] JPA : Fetch dans une requête JPQL

Quand on crée des entités avec la Java Persistence API, on peut spécifier des jointures qui se présentent alors sous forme d'objets liés à d'autres entités, plutôt que sous la forme de variables comme des chaines ou des Long. Voici un exemple de relation entre un client et ses factures.

@Entity
@Table(name = "CLIENT", schema = "maDb")
public class Client implements Serializable
{
   @Column(name = "CLI_ID_CLIENT")
   private Long l_idClient;

   @OneToMany(mappedBy = "objClient")
   private List<Facture> objListFactures;

   ...
}

@Entity
@Table(name = "FACTURE", schema = "maDb")
public class Facture implements Serializable
{
    @Column(name = "FAC_ID_FACTURE")
    private Long l_idFacture;

    @ManyToOne
    @JoinColumn(name = "FAC_ID_CLIENT")
    private Client objClient;

    ...
}

Ce qu'on a fait : 
  • Dans l'entité Client, on a créé une liste de factures, en spécifiant le tag @OneToMany (relation 1-N soit un client peut avoir plusieurs factures) avec pour attribut mappedBy le nom de la variable Client qui a été ajoutée dans notre entité Facture. 
  • Dans cette dernière, on doit spécifier la relation dans l'autre sens, donc avec le tag @ManyToOne (relation N-1, soit plusieurs factures pour un même client). 
  • On doit également ajouter le tag qui indiquera sur quelle colonne on effectue la jointure afin que la relation fonctionne (@JoinColumn + attribut name). Il faut indiquer le nom de la colonne tel que spécifié dans la base de données.
Par défaut, quand on récupérera plusieurs clients, leurs factures ne seront pas immédiatement chargées. C'est ce qu'on appelle "Lazy Loading" qui est le comportement par défaut quand on ne spécifie pas l'attribut fetch au niveau de la relation.

Afin de récupérer les factures lorsqu'on récupère un client, nous avons plusieurs choix qui se présentent à nous :

Spécifier l'attribut "fetch"

On peut spécifier au niveau de la relation, l'attribut fetch avec la valeur EAGER. Cela signifie que pour chaque client on récupérera toujours ses factures, ce qui n'est probablement pas ce que l'on souhaite (attention aux temps d'accès et aux accès en cascade s'il y a d'autres entités liées de cette manière).

@OneToMany(mappedBy = "objClient", fetch = FetchType.EAGER)
private List<Facture> objListFactures;

Accéder à la liste

Lorsqu'on récupère l'objet client depuis la base de données dans notre Session Bean, on peut accéder à la liste pour que son contenu soit chargé.

Client cliFromDb = ... ;  
cliFromDb.getObjListFactures().size();

Créer une requête JPQL

On crée une requête avec la syntaxe JPQL, ce qu'on appelle des "Named queries". Elles sont définies soit dans une méthode que vous aurez pris soin d'écrire, soit directement au niveau de l'entité, à l'aide des tags adéquats.

@NamedQueries({ @NamedQuery(
     name = "Client.findClientByPkWithFactures", 
     query = "select c from Client c left 
        join FETCH c.objListFactures 
        where c.l_idClient = :idClient")
  })

Exemple de classe qui définit des fonctions pour la gestion des clients. Ces fonctions seront appelées depuis notre Session Bean.

public class ClientManager
{
  private EntityManager em;

  public ClientManager(EntityManager em)
  {
     this.em = em;
  }

  public Client findClientByPkWithFactures(Long pId) 
  {
     Query query = em.createNamedQuery(
         "Client.findClientByPkWithFactures")
      .setParameter("idClient", pId);   
     Client res = (Client)query.getSingleResult();
     return res;
  }
} 

Bon développement !

Aucun commentaire: