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).