M1 informatique 2007-08 Bases de données avancées, première
Transcription
M1 informatique 2007-08 Bases de données avancées, première
M1 informatique 2007-08 Bases de données avancées, première partie Examen nal - octobre 2007 Durée de l'épreuve : 2 heures L'usage des polycopiés du cours est autorisé. Tous les autres documents, sous quelque forme que ce soit (papier, électronique ou autre) sont interdits, y compris les énoncés et corrigés des exercices des TD. Éteignez vos portables. Le barême est donné entre crochets au début de chaque question. 1 [10 pts] Soit le schéma conceptuel suivant : Ce schéma représente une base de données contenant des informations sur les étudiants, leurs cours et leurs notes. Les relations : Un étudiant peut avoir 0 ou plusieurs notes et une note est associée à un et un seul étudiant ; Un cours peut avoir 0 ou plusieurs notes et une note est associée à un et un seul cours. Les identiants : ID_ETU : identiant d'un étudiant ; ID_NOTE : Identiant d'une note ; ID_COURS : Identiant d'un cours. (a) Dénir les types de données objets-relationnels NOTE_T, COURS_T et ETUDIANT_T. Vous prendrez en compte uniquement les liens d'association de cardinalité 1 (par exemple de NOTES vers ETUDIANTS ; ne traduisez pas la direction de ETUDIANTS vers NOTES). (b) Dénir les tables objets NOTE_O, COURS_O et ETUDIANT_O. Les contraintes d'intégrité sont à prendre en compte. Éviter les clés étrangères puisque vous avez les références. (c) Insérer au moins une ligne par table, en renseignant toutes les colonnes. (d) Ecrire une requête qui permet de lister les notes d'un étudiant donné pour un cours donné. (e) Transformer le schéma ci-dessus en un schéma relationnel (NOTES, ETUDIANTS, COURS). Reconsidérer les clés étrangères. (f) Insérer des lignes dans les tables relationnelles à partir des tables objets dénies en (b). (g) Ecrire un programme JDBC qui permet de lister les notes d'un étudiant donné pour un cours donné. 2 [10 pts] Vous allez écrire les classes de base pour implémenter la gestion des notes des étudiants d'une lière durant un seul semestre : Classe Cours qui représente un cours, par exemple le cours de bases de données avancées. Chaque cours a un nom. Classe Etudiant qui représente un étudiant, avec un numéro d'étudiant et un nom. Un étudiant peut suivre plusieurs cours et peut donc avoir plusieurs notes. Le numéro d'étudiant est un nombre entier. Classe Note. Une note concerne un seul cours et un seul étudiant. Un cours peut être associé à plusieurs notes (une note par étudiant inscrit au cours). Chaque note a une valeur (note sur 20 qui peut avoir des décimales). Pour simplier vous supposerez que toutes les notes ont la même importance pour le calcul de la moyenne. Important : toutes les notes d'un étudiant seront automatiquement rendues persistantes quand l'étudiant sera rendu persistant. Vous ne vous intéresserez qu'aux 2 associations entre les notes et les cours et entre les notes et les étudiants. Vous ne vous intéresserez pas à l'association directe entre les étudiants et les cours. Le schéma relationnel pourrait être : Cours(id, Nom) Etudiant(numéro, nom) Note(id, valeur, cours, étudiant) Les informations nécessaires à cette gestion sont enregistrées dans une base de données et sont gérées en utilisant l'API JPA (n'utilisez pas JDBC). (a) Écrivez les classes décrites ci-dessus. N'oubliez pas les annotations pour qu'elles puissent être utilisées avec JPA. Les entités Note et Cours auront des identicateurs non signicatifs, des nombres entiers générés automatiquement par le SGBD. Les numéros des étudiants ne seront pas générés automatiquement. Toutes les associations seront bidirectionnelles. Mettez dans les classes seulement les informations strictement nécessaires à l'exécution de la méthode main demandée ci-dessous. (b) Écrivez une méthode main qui récupère un étudiant dans la base de données (repéré par son numéro d'étudiant) ; ajoute une note à cet étudiant et la sauvegarde dans la base de données ; utilise une requête pour récupérer toutes les notes de l'étudiant dans la base de données et pour calculer sa moyenne calcule la moyenne de l'étudiant. (c) Pour calculer la moyenne de tous les étudiants, un programmeur récupère tous les étudiants et ensuite reprend le code de la méthode main pour appeler une méthode qui récupère les notes de chaque étudiant et calculer sa moyenne. Proposez une amélioration pour diminuer le nombre d'accès à la base de données. Vous donnerez, si nécessaire, le code JPQL des requêtes que lancera votre code. Vous expliquerez votre stratégie mais on ne vous demande pas le détail du code Java. M1 informatique 2007-08 Bases de données avancées, première partie Examen nal - octobre 2007 - Correction 2 (a) Les classes : package fr.unice.notes; import import import import import import java.util.ArrayList; java.util.Collection; javax.persistence.Entity; javax.persistence.GeneratedValue; javax.persistence.Id; javax.persistence.OneToMany; @Entity /** * Un cours. */ public class Cours { @Id @GeneratedValue private int id; private String nom; @OneToMany(mappedBy="cours") private Collection<Note> notes = new ArrayList(); /** * Constructeur requis par JPA. */ public Cours() { } public Cours(String nom) { this.nom = nom; } } public void add(Note note) { this.notes.add(note); note.setCours(this); } package fr.unice.notes; import import import import import import import java.math.BigDecimal; java.util.ArrayList; java.util.Collection; javax.persistence.CascadeType; javax.persistence.Entity; javax.persistence.Id; javax.persistence.OneToMany; @Entity /** * Un étudiant. */ public class Etudiant { @Id private long numeroEtudiant; private String nom; @OneToMany(mappedBy="etudiant", cascade=CascadeType.PERSIST) private Collection<Note> notes = new ArrayList(); public Etudiant() { } public Etudiant(long numero, String nom) { this.numeroEtudiant = numero; this.nom = nom; } public String getNom() { return nom; } public void setNom(String nom) { this.nom = nom; } /** * Ajoute une note à cet étudiant. * Il est important de fournir ce genre de méthode qui oblige * l'utilisateur à gérer les 2 bouts des associations. * @param valeurNote * @param cours */ public void ajouterNote(BigDecimal valeurNote, Cours cours) { Note note = new Note(); note.setValeur(valeurNote); note.setEtudiant(this); this.notes.add(note); cours.add(note); } } package fr.unice.notes; import import import import import import java.math.BigDecimal; java.util.Date; javax.persistence.Entity; javax.persistence.GeneratedValue; javax.persistence.Id; javax.persistence.ManyToOne; @Entity /** * Une note d'un étudiant à un cours. */ public class Note { @Id @GeneratedValue private int id; private BigDecimal valeur; @ManyToOne private Etudiant etudiant; @ManyToOne private Cours cours; public Note() { } public BigDecimal getValeur() { return valeur; } public void setValeur(BigDecimal valeur) { this.valeur = valeur; } public Etudiant getEtudiant() { return etudiant; } public void setEtudiant(Etudiant etudiant) { this.etudiant = etudiant; } public Cours getCours() { return cours; } public void setCours(Cours cours) { this.cours = cours; } } (b) La méthode main : package fr.unice.notes; import import import import import import import java.math.BigDecimal; java.math.RoundingMode; java.util.Collection; javax.persistence.EntityManager; javax.persistence.EntityManagerFactory; javax.persistence.EntityTransaction; javax.persistence.Persistence; public class Main { /** * Ajoute une note à un étudiant et la sauvegarde dans la base de données. * Ensuite toutes les notes de l'étudiant sont récupérées et sa moyenne est * calculée. */ public static void main(String[] args) { EntityManagerFactory emf = null; EntityManager em = null; try { emf = Persistence.createEntityManagerFactory("Notes"); em = emf.createEntityManager(); long numeroEtudiant = 1000; // creationEtudiant(numeroEtudiant, em); EntityTransaction tx = em.getTransaction(); tx.begin(); // Récupère l'étudiant Etudiant etudiant = em.find(Etudiant.class, numeroEtudiant); Cours cours = new Cours("BD avancées"); etudiant.ajouterNote(new BigDecimal(19), cours); // Sauvegarde le cours (inutile pour l'étudiant qui est géré // puisque récupéré par un find). em.persist(cours); // Pour rendre persistante la note. Le cascade=CascadeType.PERSIST // sur notes de Etudiant ne rend persistant la note que // si on lance em.persist(etudiant) explicitement. // Sinon, etudiant est automatiquement géré puisqu'il // a été récupéré par un find. em.persist(etudiant); tx.commit(); // Récupère toutes les notes de l'étudiant // On pourrait le faire par un query : // String texteQuery = "select e.notes from Etudiant e where // e.numeroEtudiant = :numero"; // Query query = em.createQuery(texteQuery ); // query.setParameter("numero", 1000); // List<Note> notes = query.getResultList(); // Mais on obtiendra la même résultat en utilisant getNotes de Etudiant // (qui lancera un select vers la base de données pour récupérer // les notes puisque les associations OneToMany sont Lazy par défaut). Collection<Note> notes = etudiant.getNotes(); BigDecimal total = new BigDecimal(0); System.out.println("Nb notes = " + notes.size()); for (Note note : notes) { System.out.println("Valeur note = " + note.getValeur()); totalNotes = total.add(note.getValeur()); } // Il faut prévoir le cas où la division ne donne pas // un nombre rationnel. On arrondit à 2 chiffre après la virgule. BigDecimal moyenne = total.divide(new BigDecimal(notes.size()), 2, RoundingMode.HALF_UP); System.out.println("Total = " + totalNotes); System.out.println(moyenne); } } } finally { if (em != null) { em.close(); } if (emf != null) { emf.close(); } } /** * Juste pour que l'étudiant existe déjà dans la base de données. * @param numeroEtudiant * @param em */ private static void creationEtudiant(long numeroEtudiant, EntityManager em) { em.getTransaction().begin(); Etudiant etudiant = new Etudiant(numeroEtudiant, "Toto"); em.persist(etudiant); em.getTransaction().commit(); // Juste pour que l'étudiant ne soit pas dans le cache de em em.clear(); } (c) Il faut éviter le phénomène des N + 1 selects. Si on récupère le code tel quel, on eectuera d'abord un select pour récupérer tous les étudiants et ensuite N select pour récupérer les notes de chaque étudiant. Pour diminuer le nombre de selects, il sut de modier le texte de la requête pour récupérer les étudiants et les notes dans une seule requête : select e from Etudiant e join fetch e.notes Il n'y aura plus qu'un seul select qui ramènera tous les étudiants ainsi que toutes leurs notes (sans doute traduit en SQL par une jointure externe).