web-dev-qa-db-fra.com

JPA @OneToMany -> Parent - Référence de l'enfant (clé étrangère)

j'ai une question sur la référence à ParentEntities à partir d'entités enfants ir Si j'ai quelque chose comme ça:

Parent.Java:

@Entity(name ="Parent")
public class Parent {
    @Id
    @Generate.....
    @Column
    private int id;

    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "parent")
    private Set<Child> children;

    simple ... getter and setter ...
}

Et l'enfant.Java:

@Entity(name ="Child")
public class Child{
    @Id
    @Generate....
    @Column
    private int id;

    @ManyToOne
    private Parent parent;

    ... simple getter an setter
}

Les tables suivantes vont être créées:

Parent:
     int id

Child:
     int id
     int parent_id (foreign key: parent.id)

Ok, jusqu'ici, tout va bien. Mais quand il s'agit d'utiliser cette référence à partir de Java, je pense que vous pouvez faire quelque chose comme ça.

 @Transactional
 public void test() {
    Parent parent = new Parent();

    Child child = new Child();
    Set<Child> children = new HashSet<Child>();
    children.add(child);

    parent.setChildren(children);
    entityManager.persist(parent);
  }

ce qui conduit à cela dans la base de données:

Parent:
     id
     100

Child
     id     paren_id
     101    100

Mais ce n'est pas le cas, vous devez définir explicitement le parent à l'enfant (ce que, je pense, le cadre pourrait probablement faire tout seul).

Alors, qu'est-ce qui est vraiment dans la base de données?

Parent:
     id
     100

Child
     id     paren_id
     101    (null)

parce que je n'ai pas défini le parent à l'enfant. Donc ma question:

Dois-je vraiment faire ça? comme ça?

Parent.Java:

...
setChildren(Set<Child> children) {
   for (Child child : children) {
     child.setParent.(this);
   }

   this.children = children;
}
...

Modifier:

D'après les réponses rapides, j'ai pu résoudre ce problème en utilisant le @JoinColumn sur l'entité propriétaire de la référence. Si nous prenons l'exemple ci-dessus, j'ai fait qch. comme ça:

Parent.Java:

  @Entity(name ="Parent")
    public class Parent {
        @Id
        @Generate.....
        @Column
        private int id;

        @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
        @JoinColumn(name= "paren_id")
        private Set<Child> children;

        simple ... getter and setter ...
    }

Et l'enfant.Java:

@Entity(name ="Child")
public class Child{
    @Id
    @Generate....
    @Column
    private int id;

    ... simple getter an setter
}

Maintenant, si nous faisons cela: 

 @Transactional
 public void test() {
    Parent parent = new Parent();

    Child child = new Child();
    Set<Child> children = new HashSet<Child>();
    children.add(child);

    parent.setChildren(children);
    entityManager.persist(parent);
  }

La référence est correctement définie par le parent:

Parent:
     id
     100

Child
     id     paren_id
     101    100

Merci pour les réponses.

41
Sim0rn

Dois-je vraiment faire qc? comme ça?

C'est une stratégie, oui. 

Dans les relations bidirectionnelles, il existe un côté «propriétaire» et un côté «non propriétaire». Étant donné que le côté propriétaire de votre cas est sur Child, vous devez définir la relation à cet endroit pour qu'elle soit persistante. Le côté propriétaire est généralement déterminé par l'endroit où vous spécifiez @JoinColumn, mais il ne semble pas que vous utilisiez cette annotation, il est donc probablement déduit du fait que vous avez utilisé mappedBy dans l'annotation Parent.

Vous pouvez lisez beaucoup plus à ce sujet ici .

14
beerbajay

Oui, c'est le cas. JPA ne se préoccupe pas de la cohérence de votre graphe d'entité. En particulier, vous devez le définir du côté du propriétaire de la relation bidirectionnelle (dans votre cas, de l'attribut parent de Child).

Dans la spécification JPA 2.0, ceci est dit avec les mots suivants: 

Notez que c'est l'application qui porte la responsabilité de maintenir la cohérence des relations d'exécution, par exemple, pour assurer que le "un" et les "nombreux" côtés d'un bidirectionnel relation sont cohérents les uns avec les autres lorsque la demande met à jour la relation au moment de l'exécution.

3
Mikko Maunu

Cela semble toujours être le cas. Dans parent Entity, vous pouvez avoir quelque chose comme

@PrePersist
private void prePersist() {
   children.forEach( c -> c.setParent(this));
}

afin d'éviter de répéter le code pour définir la relation enfant/parent ailleurs dans le code.

1
pirho

Nous avons rencontré un problème en persistant un graphe d'objet simple comme celui présenté ci-dessus. Tout fonctionnait sous H2, mais cela fonctionnait, mais lorsque nous utilisions MySQL, le "paren_id" de la table enfant (défini dans l'annotation @JoinColumn) n'était pas renseigné avec l'identifiant généré du parent colonne -null avec une contrainte de clé étrangère dans la base de données. 

Nous aurions une exception comme celle-ci:

org.hibernate.exception.GenericJDBCException: Field 'paren_id' doesn't have a default value

Pour ceux qui pourraient être confrontés à cela, nous avons finalement découvert que nous avions besoin d'un autre attribut du @JoinColumn pour que cela fonctionne:

@JoinColumn(name="paren_id", nullable=false)
1
Phil Brock

Si je vous ai bien compris, selon EntityManager, si vous voulez que la commande d'insertion de la transaction soit gérée, vous devez lui "indiquer" que les enfants doivent également être conservés. Et vous ne le faites pas, alors "il" ne sait pas quoi conserver, mais la liste des enfants de vos parents n'est pas vide, donc "il" la prend comme correcte mais la valeur stockée est nulle.

Donc, vous devriez envisager de faire quelque chose comme:

... begin, etc
em.persist(child)
em.persist(parent)

faites ce que vous voulez avec l'objet parent ici puis validez et cela devrait également fonctionner pour des cas similaires.

0
João Rodrigues