IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

JPA Java Discussion :

Problème à l'insertion d'un objet ayant une PK @Embedded composé de deux clés étrangères


Sujet :

JPA Java

  1. #1
    Membre régulier
    Profil pro
    Étudiant
    Inscrit en
    Décembre 2006
    Messages
    102
    Détails du profil
    Informations personnelles :
    Âge : 36
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Décembre 2006
    Messages : 102
    Points : 86
    Points
    86
    Par défaut Problème à l'insertion d'un objet ayant une PK @Embedded composé de deux clés étrangères
    Bonjour,

    Merci d'avance pour votre aide éventuelle.

    Je me suis mis récemment à JPA, et je ne trouve pas de solutions à l'heure actuelle au problème suivant.
    J'insère un objet Groupe composé d'une liste d'objets Droits au sein d'une transaction JPA. Le soucis est sur l'objet Droits dont la clé primaire est composé de la clé primaire de Groupe et de celle d'un autre objet Module.
    Lors du remplissage de la liste des droits, l'id de Groupe n'est pas encore connu, et lors de l'insertion des objets, la clé étrangère de Droits faisant référence à Groupe n'est pas actualisé.

    Je vous mets le détail de mes classes
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    @Entity
    @Table(name="Droits")
    public class Droits  implements java.io.Serializable {
    	private static final long serialVersionUID = 4489581477725526437L;
     
    	private DroitsId id;
        private Module Module;
        private Groupe Groupe;
        private int droitStatut;
     
        public Droits() {
        }
     
        public Droits(DroitsId id, Module Module, Groupe Groupe, short droitStatut) {
           this.id = id;
           this.Module = Module;
           this.Groupe = Groupe;
           this.droitStatut = droitStatut;
        }
     
        @EmbeddedId
        @AttributeOverrides( {
            @AttributeOverride(name="groupId", column=@Column(name="group_id", nullable=false) ), 
            @AttributeOverride(name="modId", column=@Column(name="mod_id", nullable=false) ) } )
        public DroitsId getId() {
            return this.id;
        }
     
        public void setId(DroitsId id) {
            this.id = id;
        }
     
        @ManyToOne(fetch=FetchType.LAZY, cascade = CascadeType.MERGE)
    	@JoinColumn(name="mod_id", nullable=false, insertable=false, updatable=false)
        public Module getModule() {
            return this.Module;
        }
     
        public void setModule(Module Module) {
            this.Module = Module;
        }
     
        @ManyToOne(fetch=FetchType.LAZY, cascade = CascadeType.ALL)
        @JoinColumn(name="group_id",nullable=false, insertable=false, updatable=false)
        public Groupe getGroupe() {
            return this.Groupe;
        }
     
        public void setGroupe(Groupe Groupe) {
            this.Groupe = Groupe;
        }
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
     
     
    @Embeddable
    public class DroitsId  implements java.io.Serializable {
    	private static final long serialVersionUID = 5377378130220718206L;
     
    	private int groupId;
    	private int modId;
     
        public DroitsId() {
        }
     
        public DroitsId(int groupId, int modId) {
           this.groupId = groupId;
           this.modId = modId;
        }
     
     
     
        @Column(name="group_id", nullable=false)
        public int getGroupId() {
            return this.groupId;
        }
     
        public void setGroupId(int groupId) {
            this.groupId = groupId;
        }
     
     
        @Column(name="mod_id", nullable=false)
        public int getModId() {
            return this.modId;
        }
     
        public void setModId(int modId) {
            this.modId = modId;
        }
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
     
    @Entity
    @Table(name="T_Sec_Groupe"
        , uniqueConstraints = @UniqueConstraint(columnNames="group_nom") 
    )
    public class Groupe  implements java.io.Serializable {
    	private static final long serialVersionUID = -4866064782151701760L;
     
    	private int groupId;
        private String groupNom;
        private Set<Droits> Droitses = new HashSet<Droits>(0);
        private Set<Utilisateur> Utilisateurs = new HashSet<Utilisateur>(0);
     
        public Groupe() {
        }
     
     
        public Groupe(String groupNom) {
            this.groupNom = groupNom;
        }
     
        public Groupe(String groupNom, Set<Droits> Droitses) {
            this.groupNom = groupNom;
            this.Droitses = Droitses;
         }
     
        public Groupe(String groupNom, Set<Droits> Droitses, Set<Utilisateur> Utilisateurs) {
           this.groupNom = groupNom;
           this.Droitses = Droitses;
           this.Utilisateurs = Utilisateurs;
        }
     
        @Id 
        @GeneratedValue
        @Column(name="group_id", unique=true, nullable=false)
        public int getGroupId() {
            return this.groupId;
        }
     
        public void setGroupId(int groupId) {
            this.groupId = groupId;
        }
     
     
        @Column(name="group_nom", unique=true, nullable=false, length=50)
        public String getGroupNom() {
            return this.groupNom;
        }
     
        public void setGroupNom(String groupNom) {
            this.groupNom = groupNom;
        }
     
        @OneToMany(fetch=FetchType.LAZY, mappedBy="Groupe", cascade=CascadeType.ALL)
        public Set<Droits> getDroitses() {
            return this.Droitses;
        }
     
        public void setDroitses(Set<Droits> Droitses) {
            this.Droitses = Droitses;
        }
    La propriété Groupe de [Droits] est bien mise à jour lors de l'insertion de Groupe, mais pas le groupId de DroitsId.
    Donc j'obtiens :
    INSERT est en conflit avec la contrainte FOREIGN KEY "T_Sec_Groupe_T_Sec_Droits". Le conflit s'est produit dans la base de données "", table "dbo.T_Sec_Groupe", column 'group_id'.
    Si quelqu'un comprend ce que je souhaite et voit le soucis au niveau du mappage, pour pouvoir insérer un objet Groupe et une liste de Droits, sans avoir ce problème.

    Merci.

    Cordialement,

    Antoine

  2. #2
    Membre confirmé Avatar de Lordsephiroth
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Mai 2006
    Messages
    199
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 39
    Localisation : Suisse

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Mai 2006
    Messages : 199
    Points : 494
    Points
    494
    Par défaut
    Salut Antoine,

    Je dois avouer ne pas avoir 100% compris ton cas. Le message d'erreur me paraît clair et logique : une contrainte de clé étrangère n'est pas satisfaite car tu essaies d'insérer un droit sans avoir initialisé la clé étrangère du groupe dans la clé à double champ du droit.

    Premièrement, je ne comprends pas bien pourquoi tu aurais envie d'enregistrer un droit qui ne serait pas associé à un groupe. Ton application devrait faire le persist() uniquement dès que le groupe et le module ont été sélectionné.

    De plus, si tu as vraiment besoin d'enregistrer des droits liés uniquement à un module mais pas à un groupe, alors la clé primaire du droit ne doit absolument pas être sur la clé du groupe et du module. Dans ce cas, je te propose plutôt de faire une clé primaire numérique dans ton objet droit et de faire deux liaisons @ManyToOne, une pour le module (avec optional=false) et une avec groupe (avec optional=true).

    Je sais pas si ça répond à ta question. Si ce n'est pas le cas, essaie de m'indiquer pourquoi tu as besoin de persister tes droits avant l'association avec le groupe, c'est le point que j'ai du mal à saisir. Indique également si tu as possibilité de modifier la structure de ta base de données ou si tu construis une application sur une base avec une structure existante.

    Salutations
    Always code as if the guy maintaining your application is a violent psychopath!
    Site personnel sur la saga Final Fantasy : http://www.final-fantasy.ch

  3. #3
    Membre régulier
    Profil pro
    Étudiant
    Inscrit en
    Décembre 2006
    Messages
    102
    Détails du profil
    Informations personnelles :
    Âge : 36
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Décembre 2006
    Messages : 102
    Points : 86
    Points
    86
    Par défaut
    Merci d'avoir pris le temps de t'intéresser au problème.

    Déjà impossibilité de modifier la structure de la base.

    Le problème intervient quand je persiste un nouveau Groupe avec une liste de Droits, les modules sont toujours connus au préalable.

    Voilà le code que j'ai simplifié à l'origine :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     
    Set<Droits> droits = new HashSet<Droits>();
    groupe = new Groupe(txtName.getText());
    Droits droit1 = new Droits(new DroitsId(groupe.getGroupId(),module1.getModId()),module1,groupe,0);
    droits.add(droit1);
    Droits droit2 = new Droits(new DroitsId(groupe.getGroupId(),module2.getModId()),module2,groupe,0);
    droits.add(droit2);
     
    groupe.setDroitses(droits);
    groupeManager.add(groupe);
    Je crée un nouveau Groupe, auquel j'associe une liste de nouveaux Droits, les modules module1 et module2 sont connus en base.
    Et la méthode add de groupeManager réalise une transaction réalisant un persist de l'objet Groupe.

    Le problème est que l'objet DroitsId est instancié avec les ids des clés étrangères non persisté dans le cas du Groupe lors de l'initialisation, et non mis à jour, lorsque la méthode persist est appelé. Donc le groupId indique dans DroitsId ne correspond à aucune clé de Groupe, d'où l'erreur.

    Je ne sais pas si je suis plus clair ?

  4. #4
    Membre confirmé Avatar de Lordsephiroth
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Mai 2006
    Messages
    199
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 39
    Localisation : Suisse

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Mai 2006
    Messages : 199
    Points : 494
    Points
    494
    Par défaut
    Je vois mieux le problème, c'est un peu particulier mais tu n'as visiblement pas le choix de la structure de base de données.

    Pourrais-tu me donner le type de ta variable groupeManager ?
    Autre question : le morceau de code que tu as mis est dans quelle partie de l'application ? Une couche d'intégration ? Une couche applicative ?
    Dois-tu faire l'ajout du groupe et des droits au sein de la même transaction ?

    Pour ma part je chercherais à isoler l'ajout du groupe et l'ajout des droits. Tu peux effectuer la persistence sur le groupe sans y ajouter aucun droit. Ensuite tu récupères l'identifiant du groupe persisté et tu crées tes droits, au besoin dans une seconde transaction.

    Je ne vois pas encore ton architecture et l'endroit où tu te trouves, j'aurai peut être plus de détail avec la réponse aux questions que je t'ai mises ci-dessus.
    Always code as if the guy maintaining your application is a violent psychopath!
    Site personnel sur la saga Final Fantasy : http://www.final-fantasy.ch

  5. #5
    Membre régulier
    Profil pro
    Étudiant
    Inscrit en
    Décembre 2006
    Messages
    102
    Détails du profil
    Informations personnelles :
    Âge : 36
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Décembre 2006
    Messages : 102
    Points : 86
    Points
    86
    Par défaut
    La variable groupeManager est une instance de la classe GroupeManager permettant de gérer les transactions lié à l'objet Groupe.

    Détail de la méthode add de cette classe :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    public int add(Groupe groupe) {
            log.debug(GroupeManager.class,"create a Groupe instance");
     
    		EntityTransaction transac = GenericDAO.getEntityManager().getTransaction();
    		transac.begin();
    		try {
    			dao.persist(groupe);
    			transac.commit();
    		}
    		catch (RuntimeException re) {
    			Throwable e = re.getCause();
    			if (e.toString().contains("ConstraintViolationException")) {
    				e = e.getCause().getCause();
    				if (e.toString().contains("SQLServerException")) {
    					if (e.getMessage().contains("L'instruction INSERT est en conflit avec la contrainte FOREIGN KEY \"T_Sec_Groupe_T_Sec_Droits\"")) {
    						log.error(GroupeManager.class,"persist failed : L'instruction INSERT est en conflit avec la contrainte FOREIGN KEY \"T_Sec_Groupe_T_Sec_Droits\"", re);
    						transac.rollback();
    						return -1;
    					}
    				}
    			}
    			log.error(GroupeManager.class,"persist failed", re);
    			throw re;
    		}
    		fireGroupeSave();
     
    		return 0;
    	}
    et la variable dao correspond à une classé généré automatiquement dont la méthode persist est :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
     public void persist(Groupe transientInstance) {
            log.debug(GroupeDAO.class,"persisting Groupe instance");
            try {
            	GenericDAO.getEntityManager().persist(transientInstance);
                log.debug(GroupeDAO.class,"persist successful");
            }
            catch (RuntimeException re) {
            	re.getCause();
                log.error(GroupeDAO.class,"persist failed", re);
                throw re;
            }
        }
    C'est une couche applicative le premier code indiqué.

    Et c'est voulu d'ajouter le Groupe et les Droits dans la même transaction.

    La solution proposée marche effectivement, mais je ne veux pas séparer dans deux transactions, l'ensemble doit fonctionner, sinon rollback.

  6. #6
    Membre confirmé Avatar de Lordsephiroth
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Mai 2006
    Messages
    199
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 39
    Localisation : Suisse

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Mai 2006
    Messages : 199
    Points : 494
    Points
    494
    Par défaut
    Je vois deux solutions possibles avec ta structure :

    1) La première solution que je vois est de commencer ta transaction au niveau de ton code applicatif. Ca demande passablement de refactoring, mais tu pourrais essayer un code qui ressemble à :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    EntityTransaction transac = GenericDAO.getEntityManager().getTransaction();
    transac.begin();
     
    Set<Droits> droits = new HashSet<Droits>();
    groupe = new Groupe(txtName.getText());
     
    // persister le groupe à ce moment
     
    Droits droit1 = new Droits(new DroitsId(groupe.getGroupId(),module1.getModId()),module1,groupe,0);
    droits.add(droit1);
    Droits droit2 = new Droits(new DroitsId(groupe.getGroupId(),module2.getModId()),module2,groupe,0);
    droits.add(droit2);
     
    groupe.setDroitses(droits);
    groupeManager.add(groupe);
     
    transac.commit();
    Tu montes ta gestion de la transaction d'un niveau, te permettant de réaliser un persist() sur le groupe avant d'exécuter groupe.getGroupId(). Tout est donc au sein de la même transaction, mais comme tu as appelé persist() sur le groupe, l'identifiant devrait être connu (à vérifier, je n'ai pas testé comment se comporte JPA, mais ce devrait être ok si on se base sur ce qu'il se passerait si on gérait la chose par des requêtes SQL natives en JDBC).

    Cette solution n'est peut être pas optimale et n'entre peut être pas du tout dans ton architecture.

    2) L'autre solution, peut être plus propre, serait de supprimer la génération automatique des clés de ton objet groupe. En enlevant l'annotation @GeneratedValue qui est placé sur ta clé primaire de ton groupe, tu devras gérer l'incrémentation manuellement. Cependant, ça te résous ton problème, tu devras calculer la clé avant la création du groupe, en conséquence la clé primaire du groupe sera initialisée (bien que non persistée) au moment de la création de tes droits.

    Je ne vois pas de solution plus simple que ces deux pour tout réaliser au sein de la même transaction, navré
    Always code as if the guy maintaining your application is a violent psychopath!
    Site personnel sur la saga Final Fantasy : http://www.final-fantasy.ch

  7. #7
    Membre régulier
    Profil pro
    Étudiant
    Inscrit en
    Décembre 2006
    Messages
    102
    Détails du profil
    Informations personnelles :
    Âge : 36
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Décembre 2006
    Messages : 102
    Points : 86
    Points
    86
    Par défaut
    Merci pour tes propositions.

    Je n'ai pas trop le temps de chercher la solution idéale, donc pour l'instant j'ai opté pour modifier le comportement de ma classe DroitsId.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
     
    @Embeddable
    public class DroitsId  implements java.io.Serializable {
    	private static final long serialVersionUID = 5377378130220718206L;
     
    	private Groupe groupe;
    	private Module module;
    	private int groupId;
    	private int modId;
     
        public DroitsId() {
        }
     
        public DroitsId(Groupe groupe, Module module) {
           this.groupe = groupe;
           this.module = module;
        }
     
        @Column(name="group_id", nullable=false)
        public int getGroupId() {
            if(this.groupe==null) return groupId;
            return groupe.getGroupId();
        }
     
        public void setGroupId(int groupId) {
            this.groupId=groupId;
        }
     
        @Column(name="mod_id", nullable=false)
        public int getModId() {
            if(this.module==null) return modId;
            return module.getModId();
        }
     
        public void setModId(int modId) {
            this.modId=modId;
     
        }
     
       public boolean equals(Object other) {
             if ( (this == other ) ) return true;
    		 if ( (other == null ) ) return false;
    		 if ( !(other instanceof DroitsId) ) return false;
    		 DroitsId castOther = ( DroitsId ) other; 
     
    		 return (this.getGroupId()==castOther.getGroupId()) && (this.getModId()==castOther.getModId());
       }
     
       public int hashCode() {
             int result = 17;
     
             result = 37 * result + this.getGroupId();
             result = 37 * result + this.getModId();
             return result;
       }   
     
     
    }
    C'est pas génial, je reviendrais dessus plus tard.

    J'ai rajouté les objets dans les propriétés de l'objet. Lorsque l'objet est chargé de la base, il ne connait pas les objets, mais le mapping est bien fait sur les ids. Par contre, quand je fais un insert, il instancie l'objet, et au moins je récupère le bon id une fois le Groupe persisté, tout ça dans une seule transaction.

Discussions similaires

  1. [EF4] Problème lors de l'insertion d'un objet ayant des dépendances
    Par redcurve dans le forum Entity Framework
    Réponses: 3
    Dernier message: 06/03/2011, 13h12
  2. Réponses: 5
    Dernier message: 03/06/2010, 23h36
  3. problème affichage et disparition d'objet sur une slide donnée
    Par carlostropico dans le forum VBA PowerPoint
    Réponses: 10
    Dernier message: 24/09/2008, 17h58
  4. [MySQL] problème d'insertion des caractères arabe dans une base mysql
    Par sasaas dans le forum PHP & Base de données
    Réponses: 2
    Dernier message: 07/03/2008, 11h56
  5. Réponses: 8
    Dernier message: 12/04/2007, 11h32

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo