Correction de l'examen du cours "Modèles pour la persistance des objets", session 1 2008

Exercice 1

Question 1

Il faut regarder le code SQL généré par le driver de persistance JPA.

Plusieurs possibilités :

  1. Trop de données récupérées à cause de la configuration ou de join fetch inutiles. Par exemple, si on veut récupérer une liste des départements d'une entreprise qui a de nombreux employés et si l'association des départements vers les employés est en mode EAGER, la mettre en mode LAZY.
    S'il y a une requête JPA avec 2 join fetch, il y aura de nombreuses lignes renvoyées (produit cartésien) et il faut alors décomposer la requête en plusieurs requêtes distinctes.
  2. Un problème de N + 1 select. Par exemple, une classe Departement et une classe Employe. Un département peut avoir de nombreux employés. On veut faire afficher tous les employés, avec le lieu où ils travaillent. Si l'association entre un employé et son département est en mode lazy et qu'on lance la requête JPA "select e from employe e", il peut y avoir 1 select pour récupérer tous les employés + un select pour chaque employé pour récupérer son département.
    Pour ce cas, 2 solutions : soit on met l'association de employé vers département en mode EAGER (peut-être pas bon dans un autre cas d'utilisation), soit on lance la requête suivante : "select e from employe e join fetch departement"
  3. Ouverture et fermeture des connexions à chaque appel de méthode. Cette possibilité est moins probable que les 2 autres car il semble peu probable qu'il y ait tant d'appels de méthode que ça.

Question 2

Le code comporte une erreur : il aurait fallu encadrer le code de la méthode par un try - finally qui ferme la connexion ou au moins qui ferme la transaction en lançant un commit ou un rollback suivant ce que l'on veut pour la méthode. Comme elle est écrite, on peut très bien sortir de la méthode sans terminer la transaction. Il suffit alors qu'un employé soit augmenté deux fois de suite pour que le SGBD mette l'application en attente car le 1er update bloque le 2ème update puisque la 1ère transaction n'est pas terminée.

Exercice 2

Question 1

package fr.unice.entreprise;

import java.util.ArrayList;
import java.util.Collection;
/**
 * Le département d'une entreprise.
 */
public class Departement {
  private long id;
  private String nom;
  private Collection<Employe> employes = new ArrayList<Employe>();
  
  public Departement(long id, String nom) {
    super();
    this.id = id;
    this.nom = nom;
  }
   
  public Collection<Employe> getEmployes() {
    return employes;
  }
   
  /**
   * Ajoute un employé dans un département.
   * Le minimum demandé. Il faudrait évidemment aussi ajouter une 
   * méthode remove pour enlever un employé d'un département.
   * @param employe
   */
  public void add(Employe employe) {
    Departement departement;
    if ((departement = employe.getDepartement()) != null) {
      // Retire l'employé de son ancien département
      departement.getEmployes().remove(employe);
    }
    employes.add(employe);
    employe.setDepartement(this);
   }
}


package fr.unice.entreprise;

public class Employe {
  private long id;
  private String nom;
  private Departement departement;
  


  public Employe(long id, String nom) {

    this.id = id;
    this.nom = nom;
  }
  
  public Departement getDepartement() {
    return departement;
  }

  public void setDepartement(Departement departement) {
    this.departement = departement;
  }

  public String getNom() {
    return nom;
  }
}

Question 2

Remarque : dans ce code et dans d'autres ensuite, il faudrait mettre le code dans un try - finally (fermeture de la connexion dans le finally). Ca n'est pas fait pour simplifier le code.
  public static void main(String[] args) {
    Employe dupond = new Employe(1, "Dupond");
    // Récupère le département Comptabilité
    ObjectContainer bd = Db4o.openFile("bd.data");
    ObjectSet set = bd.get(new Departement(0, "Comptabilité"));
    Departement compta = set.get(0);
    System.out.println(compta);
    compta.add(dupond);
    // La ligne suivante est inutile si on met la 
    // profondeur de modification à 2
//    bd.set(dupond); 
    // Ne suffit pas pour que la collection de compta 
    // soit mise à jour dans la bd si la profondeur est laissée à 1 (valeur par défaut)
    // bd.set(compta) ne suffit pas pour mettre à jour la collection de compta dans la bd
    // Il faut mettre la profondeur de modification à 2
    ((ExtObjectContainer)bd).set(compta, 2);
    bd.commit();
    bd.close();
  }

Exercice 3

Question 1

package fr.unice.entreprise;

import java.util.ArrayList;
import java.util.Collection;
/**
 * Le département d'une entreprise.
 */
@Entity
public class Departement {
  @Id @GeneratedValue
  private long id;
  private String nom;
  @OneToMany(mappedBy="departement")
  private Collection<Employe> employes = new ArrayList<Employe>();
   
  public Departement() { }

  public Departement(long id, String nom) {
    super();
    this.id = id;
    this.nom = nom;
  }
   
  public Collection<Employe> getEmployes() {
    return employes;
  }
   
  /**
   * Ajoute un employé dans un département.
   * @param employe
   */
  public void add(Employe employe) {
    Departement departement;
    if ((departement = employe.getDepartement()) != null) {
      // Retire l'employé de son ancien département
      departement.getEmployes().remove(employe);
    }
    employes.add(employe);
    employe.setDepartement(this);
   }
}


package fr.unice.entreprise;

@Entity
public class Employe {
  @Id @GeneratedValue
  private long id;
  private String nom;
  @ManyToOne
  private Departement departement;
  
  public Employe() { }

  public Employe(long id, String nom) {

    this.id = id;
    this.nom = nom;
  }
  
  public Departement getDepartement() {
    return departement;
  }

  public void setDepartement(Departement departement) {
    this.departement = departement;
  }

  public String getNom() {
    return nom;
  }
}
Ajout de Dupond :
  public static void main(String[] args) {
    EntityManagerFactory emf = 
      Persistence.createEntityManagerFactory("Employes");
    // Ajout de Dupond dans ce département
    em = emf.createEntityManager();
    tx = em.getTransaction();
    tx.begin();
    // Récupération du département de comptabilité
    String texteRequete = "select d from Departement d where d.nom = :dept";
    Query query = em.createQuery(texteRequete);
    query.setParameter("dept", "Comptabilité");
    List liste = query.getResultList();
    Departement compta = liste.get(0);
    Employe dupond = new Employe("Dupond");
    compta.add(dupond);
    em.persist(dupond);
    // Pas besoin de faire un persist sur compta qui a été récupéré par
    // un query et qui est donc géré.
    tx.commit();
    em.close();

Question 2

create table departement(
  id integer constraint pk_departement primary key,
  nom varchar(50))
  
create table employe(
  id integer constraint pk_employe primary key,
  nom varchar(50),
  id_departement integer constraint fk_employe_departement references departement)

Question 3

    em = emf.createEntityManager();
    String texteRequete = "select e from Employe e where substring(e.nom, 1, 1) = 'D'";
    Query query = em.createQuery(texteRequete);
    collEmp = query.getResultList();
    System.out.println("Employés dont le nom commence par un 'D' :");
    for(Employe emp : collEmp) {
      System.out.println(emp.getNom());
    }
    em.close();
    emf.close();