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)