Relation many-to-one

Transcription

Relation many-to-one
Relation many-to-one
Ajoutons maintenant la classe User et l'association many-to-one qui la lie avec la classe Event.
L'arborescence du projet est maintenant la suivante :
Implémentation de l'association
Relation bidirectionnelle
Elle comporte les attributs suivants :
public class User {
private Integer id;
private String login;
private String email;
Mistra Formation - 19 rue Béranger 75003 Paris - Métro République - 01 82 52 25 25 - [email protected]
Relation many-to-one
private String pass;
private List eventList;
public User()
public User(String login, String email, String pass) {
this.login = login;
this.email = email;
this.pass = pass;
}
// Getters et setters
}
Contrairement à la classe Adresse vue précédemment, la classe User comporte un attribut
relatif à Event: puisqu'un User peut créer plusieurs Event, nous voulons conserver la liste de
ceux-ci. A l'inverse, il faut pouvoir accéder, depuis l'Event, à son créateur : la classe Event doit
donc également posséder un attribut de type User. En base de données, cela ne change rien,
puisque l'accès aux données dans une structure relationnelle n'a pas de "direction",
contrairement au monde objet du Java.
Le principe de cet accès possible dans les deux sens s'appelle une association bidirectionnelle
et le mapping qui s'ensuit doit être présent dans les deux entités. Afin d'éviter à Hibernate tout
conflit ou violation de contrainte, nous devons définir l'une des directions comme étant le sens
"principal" de l'association. Lors de l'enregistrement d'un couple Event-User, c'est l'entité
Mistra Formation - 19 rue Béranger 75003 Paris - Métro République - 01 82 52 25 25 - [email protected]
Relation many-to-one
maîtresse de l'association qui se chargera de lancer une requête SQL de type update pour
mettre à jour les clés étrangères.
Dans notre cas, nous aurons plus tendance à enregistrer un nouvel utilisateur, puis à créer un
Event qui lui sera lié. Ce sera donc plutôt l'Event qui mettra à jour la relation et qui sera donc
l'entité maîtresse.
Mapping de la classe User
Du côté de la classe User, nous sommes dans une relation OneToMany (un User pour
plusieurs Events), traduite par l'annotation bien nommée @OneToMany. Afin de traduire le
caractère secondaire de l'entité User dans cette relation, nous utilisons l'attribut "mappedBy" qui
référence le nom de l'attribut User dans la classe Event. Ici, la ligne 23 indiqué donc que la
classe Event devra donc comporter un attribut de type User appelé user :
import java.util.List;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;
@Entity
@Table(name="users")
Mistra Formation - 19 rue Béranger 75003 Paris - Métro République - 01 82 52 25 25 - [email protected]
Relation many-to-one
public class User {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
@Column(name="users_id")
private Integer id;
@Column(name="users_login")
private String login;
@Column(name="users_email")
private String email;
@Column(name="users_pass")
private String pass;
@OneToMany(mappedBy="user")
private List eventList;
public User()
public User(String login, String email, String pass) {
this.login = login;
this.email = email;
this.pass = pass;
Mistra Formation - 19 rue Béranger 75003 Paris - Métro République - 01 82 52 25 25 - [email protected]
Relation many-to-one
}
// Getters et setters
}
Modifications du mapping de la classe Event
L'entité Event constitue le côté principal de l'association, ce qui se traduit par la présence d'une
annotation @ManyToOne (sens inverse par rapport à User). C'est également de ce côté que
nous indiquerons les informations relatives aux cascades, à la colonne de jointure si l'on veut
choisir son nom et non laisser celui par défaut.
Concernant les types de cascades à choisir, il n'est ici pas judicieux de tous les sélectionner. En
effet, un User pouvant posséder plusieurs Events, il ne faudrait pas que la suppression d'un
Event engendre la suppression son User, celui-ci pouvant être également liés à d'autres
instances d'Events. Les types de cascade Persist et Merge, par contre, seront les bienvenus :
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.SimpleTimeZone;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
Mistra Formation - 19 rue Béranger 75003 Paris - Métro République - 01 82 52 25 25 - [email protected]
Relation many-to-one
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
@Entity
@Table(name="events")
public class Event {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
@Column(name="events_id")
private Integer id;
@ManyToOne(
cascade=
)
@JoinColumn(name="events_userID")
private User user;
Mistra Formation - 19 rue Béranger 75003 Paris - Métro République - 01 82 52 25 25 - [email protected]
Relation many-to-one
// Autres attributs vus précédemment
// Constructeurs
// Getters et setters
}
Modifications de la classe de test
L'implémentation de l'association étant prête, occupons-nous maintenant des modifications à
effectuer dans la classe Main pour pouvoir tester notre nouveau mapping.
Affichage du contenu de la table USERS
Sur le même modèle que les méthodes d'affichage précédentes, nous rédigeons une méthode
printUsers() chargées de lister le contenu de la table USERS et de l'afficher.
import java.util.ArrayList;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.Transaction;
import fr.mistra.pgejpav2.jpa.Event;
import fr.mistra.pgejpav2.jpa.User;
private static void printUsers() {
System.out.println("---Users---");
Mistra Formation - 19 rue Béranger 75003 Paris - Métro République - 01 82 52 25 25 - [email protected]
Relation many-to-one
// Création de la requête
Query q = s.createQuery("from User");
ArrayList list = (ArrayList) q.list();
// Affichage de la liste de résultats
for (User u: list) {
System.out.println("[id] = " + u.getId() + "\t" +
"[login] = " + u.getLogin() + "\t" +
"[email] = " + u.getEmail() + "\t" +
"[pass] = " + u.getPass());
if (u.getEventList() != null) {
System.out.print("Events ID : ");
for (Event e : u.getEventList())
System.out.print(e.getId() + " ");
System.out.println();
}
}
}
Notons qu'aux lignes, nous affichons, en plus des attributs "simples", la liste des identifiants de
tous les évènements liés à un utilisateur. De cette manière, nous pouvons vérifier le bon
fonctionnement de l'attribut mappedBy utilisé dans le mapping de l'entité User.
Mistra Formation - 19 rue Béranger 75003 Paris - Métro République - 01 82 52 25 25 - [email protected]
Relation many-to-one
Modification de l'affichage de la table EVENTS
De même, nous modifions la méthode printEvents() pour qu'elle affiche l'identifiant de
l'utilisateur asoscié :
private static void printEvents() {
System.out.println("---Events---");
// Création de la requête
Query q = s.createQuery("from Event");
ArrayList list = (ArrayList) q.list();
// Affichage de la liste de résultats
for (Event e: list) {
System.out.println("[id] = " + e.getId() + "\t" +
"[title] = " + e.getTitle() + "\t" +
"[desc] = " + e.getDescription() + "\t" +
"[date] = " + e.getBeginDate().getTime().toLocaleString() + "\t" +
"[allDay] = " + e.isAllDay());
if (e.getAddress() != null)
System.out.println("[addressID] = " + e.getAddress().getId());
if (e.getUser() != null)
System.out.println("[userID] = " + e.getUser().getId());
Mistra Formation - 19 rue Béranger 75003 Paris - Métro République - 01 82 52 25 25 - [email protected]
Relation many-to-one
}
}
Enregistrement de nouveaux utilisateurs
Modifions enfin la méthode testCreate(). Nous avions auparavant une Address a liée à un Event
e. Afin de bien observer les effets de l'association, nous allons créer puis enregistrer :
un User u lié l'Event e,
un nouvel Event e2 sans adresse lié lui aussi à l'User u,
un nouvel User u2 non lié à un Event.
Dans le chapitre précédent, nous avions vu que si un Event possède une relation one-to-one
avec une Address, le type de cascade Persist permettait de sauvegarder l'Address
automatiquement lorsqu'un appel à persist() est fait sur un Event.
Il serait donc tentant de faire de même ici : sauvegardons les Events e1 et e2. Puisque
l'Address a et l'User u sont liés à ces entités, il ne semble pas nécessaire de les demander
explicitement leur sauvegarde. L'User u2, par contre, n'est pas lié à un Event : il faut donc le
sauvegarder manuellement.
Implémentons donc ceci dans la méthode testCreate() :
private static void testCreate() {
//Création des objets à rendre persistants
Event e = new Event("Titre de l'évènement", "description", true);
Address a = new Address("Nom de l'adresse", "24 rue des cerisiers",
"75001", "Paris");
Mistra Formation - 19 rue Béranger 75003 Paris - Métro République - 01 82 52 25 25 - [email protected]
Relation many-to-one
User u = new User("mistra", "mistra(a)mistra.fr", "formation");
User u2 = new User("John Doe", "john.doe(a)mistra.fr", "password");
Event e2 = new Event("Event sans adresse", "", false);
// Liens d'associations pour l’Event e
e.setAddress(a);
e.setUser(u);
// Liens d'associations pour l'Event e2
e2.setUser(u);
// Liste des deux Event de l’User u
ArrayList eventList = new ArrayList();
eventList.add(e);
eventList.add(e2);
u.setEventList(eventList);
// Enregistrements
Transaction tx = s.beginTransaction();
s.persist(e);
s.persist(e2);
Mistra Formation - 19 rue Béranger 75003 Paris - Métro République - 01 82 52 25 25 - [email protected]
Relation many-to-one
s.persist(u2);
tx.commit();
// Affichage du contenu des tables
printEvents();
printAddresses();
printUsers();
}
Effectivement, lors de l'exécution, nos cinq entités sont bien présentes, et toutes les
associations ont été correctement tranformées en clés étrangères:
---Events--[id] = 1 [title] = Titre de l'évènement
[allDay] = true
[desc] = description
[date] = 16 juil. 2010 11:34:09
[addressID] = 1
[userID] = 1
[id] = 2
false
[title] = Event sans adresse [desc] =
[date] = 16 juil. 2010 11:34:09
[allDay] =
[userID] = 1
---Addresses--[id] = 1 [name] = Nom de l'adresse
[zipCode] = 75001 [city] = Paris
[street] = 24 rue des cerisiers
[comments] = null
Mistra Formation - 19 rue Béranger 75003 Paris - Métro République - 01 82 52 25 25 - [email protected]
Relation many-to-one
---Users--[id] = 1
[login] = mistra [email] = mistra(a)mistra.fr [pass] = formation
[Events ID ]: 1 2
[id] = 2
[login] = John Doe
[email] = john.doe(a)mistra.fr [pass] = password
[Events ID ]:
De la même manière, nous implémenterons la classe Items et l'association many-to-one
existant entre une liste d'Items et un Event.
L'importance des cascades
Nous avons découvert les cascades au chapitre précédent. L'association bidirectionnelle entre
Event et User nous donne une parfaite occasion d'en voir les implications réelles.
Dans ce chapitre, nous avons rendu persistants les Events de façon explicite avec l'appel à la
méthode persist(). Nous avons fait de même pour l'Address et l'User qui leurs étaient liés de
façon implicite et automatique grâce aux cascades de type Persist.
Enlevons maintenant les cascades du mapping entre Event et User :
@Entity
@Table(name="events")
Mistra Formation - 19 rue Béranger 75003 Paris - Métro République - 01 82 52 25 25 - [email protected]
Relation many-to-one
public class Event {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
@Column(name="events_id")
private Integer id;
// Autres attributs
@ManyToOne
@JoinColumn(name="events_userID")
private User user;
// Constructeurs
// Getters et setters
}
Si nous relançons maintenant l'exécution du programme de test, nous obtenons l'exception
suivante :Exception in thread "main" org.hibernate.TransientObjectException:
object references an unsaved transient instance - save the transient instance before flushing:
fr.mistra.pgejpav2.jpa.User
au niveau des lignes suivantes dans la méthode testCreate() :
Mistra Formation - 19 rue Béranger 75003 Paris - Métro République - 01 82 52 25 25 - [email protected]
Relation many-to-one
// Enregistrements
Transaction tx = s.beginTransaction();
s.persist(e);
s.persist(e2);
s.persist(u2);
tx.commit();
En effet, nous essayons de sauvegarder l'Event e liée à l'User u. Du fait de l'association
mappée, la table events possède l'identifiant de l'utilisateur lié au travers d'une clé étrangère.
Mais l'User associé n'est pas encore sauvegardé, et est donc transient : la clé étrangère ne
peut être initialisée à sa juste valeur.
C'est la raison pour laquelle Hibernate suggère de "sauvegarder l'instance transiente
: fr.mistra.pgejpav2.jpa.User"
Il existe deux solutions possibles à ce problème :
mettre en place une cascade pour que la persistance d'un Event entraine celle du User
associé :
@Entity
@Table(name="events")
public class Event {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
Mistra Formation - 19 rue Béranger 75003 Paris - Métro République - 01 82 52 25 25 - [email protected]
Relation many-to-one
@Column(name="events_id")
private Integer id;
// Autres attributs
@ManyToOne
(
cascade=
)
@JoinColumn(name="events_userID")
private User user;
public Event() {
}
// Getters et setters
}
rendre persistant l'User u avant de faire appel à s.persist(e) :
// Enregistrements
Transaction tx = s.beginTransaction();
// On sauvegarde u avant e
Mistra Formation - 19 rue Béranger 75003 Paris - Métro République - 01 82 52 25 25 - [email protected]
Relation many-to-one
s.persist(u);
s.persist(e);
s.persist(e2);
s.persist(u2);
tx.commit();
.
Mistra Formation - 19 rue Béranger 75003 Paris - Métro République - 01 82 52 25 25 - [email protected]
Powered by TCPDF (www.tcpdf.org)