Quelle est la différence entre @JoinColumn et mappedBy lors de l'utilisation d'une association JPA @OneToMany

516

Quelle est la différence entre:

@Entity
public class Company {

    @OneToMany(cascade = CascadeType.ALL , fetch = FetchType.LAZY)
    @JoinColumn(name = "companyIdRef", referencedColumnName = "companyId")
    private List<Branch> branches;
    ...
}

et

@Entity
public class Company {

    @OneToMany(cascade = CascadeType.ALL , fetch = FetchType.LAZY, mappedBy = "companyIdRef")
    private List<Branch> branches;
    ...
}
Mykhaylo Adamovych
la source
1
Voir également Quel est le côté propriétaire dans une question de mappage ORM pour une très bonne explication des problèmes impliqués.
dirkt

Réponses:

545

L'annotation @JoinColumnindique que cette entité est le propriétaire de la relation (c'est-à-dire que la table correspondante a une colonne avec une clé étrangère vers la table référencée), tandis que l'attribut mappedByindique que l'entité de ce côté est l'inverse de la relation, et le propriétaire réside dans «l'autre» entité. Cela signifie également que vous pouvez accéder à l'autre table à partir de la classe que vous avez annotée avec "mappedBy" (relation entièrement bidirectionnelle).

En particulier, pour le code dans la question, les annotations correctes ressembleraient à ceci:

@Entity
public class Company {
    @OneToMany(fetch = FetchType.LAZY, mappedBy = "company")
    private List<Branch> branches;
}

@Entity
public class Branch {
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "companyId")
    private Company company;
}
Óscar López
la source
3
dans les deux cas, la succursale possède un champ avec l'ID de la société.
Mykhaylo Adamovych
3
La table société n'a pas de colonne avec une clé étrangère vers la table référencée - la branche a la référence société. Pourquoi dites-vous "la table correspondante a une colonne avec une clé étrangère vers la table référencée"? Pourriez-vous expliquer quelques pls supplémentaires.
Mykhaylo Adamovych
13
@MykhayloAdamovych J'ai mis à jour ma réponse avec un exemple de code. Notez que c'est une erreur à utiliser @JoinColumndansCompany
Óscar López
10
@MykhayloAdamovych: Non, ce n'est pas tout à fait vrai. Si Branchn'a pas de propriété qui fait référence Company, mais la table sous-jacente a une colonne qui le fait, vous pouvez l'utiliser @JoinTablepour la mapper. Il s'agit d'une situation inhabituelle, car vous mapperiez normalement la colonne de l'objet qui correspond à sa table, mais cela peut arriver et c'est parfaitement légitime.
Tom Anderson
4
C'est une autre raison de ne pas aimer les ORM. La documentation est souvent trop douteuse, et dans mes livres, cela serpente sur le territoire trop magique. J'ai eu du mal avec ce problème et lorsqu'ils sont suivis mot à mot pour a @OneToOne, les lignes enfants sont mises à jour avec un nulldans leur colonne FKey qui fait référence au parent.
Ashesh
225

@JoinColumnpourrait être utilisé des deux côtés de la relation. La question portait sur l'utilisation @JoinColumnsur le @OneToManycôté (cas rare). Et le point ici est dans la duplication d'informations physiques (nom de colonne) avec une requête SQL non optimisée qui produira des UPDATEinstructions supplémentaires .

Selon la documentation :

Étant donné que plusieurs à un sont (presque) toujours le côté propriétaire d'une relation bidirectionnelle dans la spécification JPA, l'association un à plusieurs est annotée par@OneToMany(mappedBy=...)

@Entity
public class Troop {
    @OneToMany(mappedBy="troop")
    public Set<Soldier> getSoldiers() {
    ...
}

@Entity
public class Soldier {
    @ManyToOne
    @JoinColumn(name="troop_fk")
    public Troop getTroop() {
    ...
} 

Troopa une relation un à plusieurs bidirectionnelle avec Soldierla propriété de la troupe. Vous n'avez pas (ne devez pas) définir de mappage physique sur le mappedBycôté.

Pour mapper un un à plusieurs bidirectionnel, avec le côté un à plusieurs comme côté propriétaire , vous devez supprimer l' mappedByélément et définir le plusieurs à un @JoinColumncomme insertableet updatableà faux. Cette solution n'est pas optimisée et produira des UPDATEinstructions supplémentaires .

@Entity
public class Troop {
    @OneToMany
    @JoinColumn(name="troop_fk") //we need to duplicate the physical information
    public Set<Soldier> getSoldiers() {
    ...
}

@Entity
public class Soldier {
    @ManyToOne
    @JoinColumn(name="troop_fk", insertable=false, updatable=false)
    public Troop getTroop() {
    ...
}
Mykhaylo Adamovych
la source
1
Je ne suis pas en mesure de comprendre comment Troop peut être propriétaire dans votre deuxième extrait, Soldier est toujours le propriétaire, car il contient une clé étrangère faisant référence à Troop. (J'utilise mysql, j'ai vérifié avec votre approche).
Akhilesh
10
Dans votre exemple, l'annotation fait mappedBy="troop"référence à quel champ?
Fractaliste
5
@Fractaliste l'annotation mappedBy="troop"fait référence à la troupe de propriété de la classe Soldat. Dans le code ci-dessus, la propriété n'est pas visible car ici Mykhaylo l'a omise, mais vous pouvez déduire son existence par le getter getTroop (). Vérifiez la réponse d' Óscar López , elle est très claire et vous obtiendrez le point.
nicolimo86
1
Cet exemple est un abus de la spécification JPA 2. Si l'objectif de l'auteur est de créer une relation bidirectionnelle, il doit utiliser mappedBy du côté parent et JoinColumn (si nécessaire) du côté enfant. Avec l'approche présentée ici, nous obtenons 2 relations unidirectionnelles: OneToMany et ManyToOne qui sont indépendantes mais juste par chance (plus par mauvaise utilisation) ces 2 relations sont définies en utilisant la même clé étrangère
aurelije
1
Si vous utilisez JPA 2.x, ma réponse ci-dessous est un peu plus propre. Bien que je suggère d'essayer les deux routes et de voir ce que fait Hibernate lorsqu'il génère les tables. Si vous êtes sur un nouveau projet, choisissez la génération qui, selon vous, correspond à vos besoins. Si vous êtes sur une base de données héritée et que vous ne voulez pas changer la structure, choisissez celle qui correspond à votre schéma.
Snekse
65

Puisque c'est une question très courante, j'ai écrit cet article , sur lequel cette réponse est basée.

Association un-à-plusieurs unidirectionnelle

Comme je l'ai expliqué dans cet article , si vous utilisez l' @OneToManyannotation avec @JoinColumn, vous avez alors une association unidirectionnelle, comme celle entre l' Postentité parent et l'enfant PostCommentdans le diagramme suivant:

Association un-à-plusieurs unidirectionnelle

Lorsque vous utilisez une association un-à-plusieurs unidirectionnelle, seul le côté parent mappe l'association.

Dans cet exemple, seule l' Postentité définira une @OneToManyassociation avec l' PostCommententité enfant :

@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
@JoinColumn(name = "post_id")
private List<PostComment> comments = new ArrayList<>();

Association un-à-plusieurs bidirectionnelle

Si vous utilisez le @OneToManyavec l' mappedByensemble d'attributs, vous avez une association bidirectionnelle. Dans notre cas, l' Postentité a une collection d' PostCommententités enfants et l' PostCommententité enfant a une référence à l' Postentité parent , comme illustré par le diagramme suivant:

Association un-à-plusieurs bidirectionnelle

Dans l' PostCommententité, la postpropriété de l' entité est mappée comme suit:

@ManyToOne(fetch = FetchType.LAZY)
private Post post;

La raison pour laquelle nous définissons explicitement l' fetchattribut FetchType.LAZYest que, par défaut, toutes les associations @ManyToOneet @OneToOnesont extraites avec impatience, ce qui peut entraîner des problèmes de requête N + 1. Pour plus de détails sur ce sujet, consultez cet article .

Dans l' Postentité, l' commentsassociation est mappée comme suit:

@OneToMany(
    mappedBy = "post",
    cascade = CascadeType.ALL,
    orphanRemoval = true
)
private List<PostComment> comments = new ArrayList<>();

L' mappedByattribut de l' @OneToManyannotation fait référence à la postpropriété dans l' PostCommententité enfant et, de cette façon, Hibernate sait que l'association bidirectionnelle est contrôlée par le @ManyToOnecôté, qui est chargé de gérer la valeur de colonne de clé étrangère sur laquelle cette relation de table est basée.

Pour une association bidirectionnelle, vous devez également disposer de deux méthodes utilitaires, comme addChildet removeChild:

public void addComment(PostComment comment) {
    comments.add(comment);
    comment.setPost(this);
}

public void removeComment(PostComment comment) {
    comments.remove(comment);
    comment.setPost(null);
}

Ces deux méthodes garantissent que les deux côtés de l'association bidirectionnelle ne sont pas synchronisés. Sans synchronisation des deux extrémités, Hibernate ne garantit pas que les changements d'état d'association se propageront à la base de données.

Pour plus de détails sur le meilleur wat pour synchroniser les associations bidirectionnelles avec JPA et Hibernate, consultez cet article .

Lequel choisir?

L' association unidirectionnelle @OneToManyne fonctionne pas très bien , vous devez donc l'éviter.

Il vaut mieux utiliser le bidirectionnel @OneToManyqui est plus efficace .

Vlad Mihalcea
la source
32

L'annotation mappedBy devrait idéalement toujours être utilisée dans le côté parent (classe Company) de la relation bidirectionnelle, dans ce cas, elle devrait être dans la classe Company pointant vers la variable membre 'company' de la classe Child (classe Branch).

L'annotation @JoinColumn est utilisée pour spécifier une colonne mappée pour rejoindre une association d'entité, cette annotation peut être utilisée dans n'importe quelle classe (Parent ou Enfant) mais elle devrait idéalement être utilisée uniquement d'un côté (soit dans la classe parent soit dans la classe Child non dans les deux) ici, dans ce cas, je l'ai utilisé dans le côté enfant (classe Branch) de la relation bidirectionnelle indiquant la clé étrangère dans la classe Branch.

ci-dessous est l'exemple de travail:

classe parent, Entreprise

@Entity
public class Company {


    private int companyId;
    private String companyName;
    private List<Branch> branches;

    @Id
    @GeneratedValue
    @Column(name="COMPANY_ID")
    public int getCompanyId() {
        return companyId;
    }

    public void setCompanyId(int companyId) {
        this.companyId = companyId;
    }

    @Column(name="COMPANY_NAME")
    public String getCompanyName() {
        return companyName;
    }

    public void setCompanyName(String companyName) {
        this.companyName = companyName;
    }

    @OneToMany(fetch=FetchType.LAZY,cascade=CascadeType.ALL,mappedBy="company")
    public List<Branch> getBranches() {
        return branches;
    }

    public void setBranches(List<Branch> branches) {
        this.branches = branches;
    }


}

classe enfant, branche

@Entity
public class Branch {

    private int branchId;
    private String branchName;
    private Company company;

    @Id
    @GeneratedValue
    @Column(name="BRANCH_ID")
    public int getBranchId() {
        return branchId;
    }

    public void setBranchId(int branchId) {
        this.branchId = branchId;
    }

    @Column(name="BRANCH_NAME")
    public String getBranchName() {
        return branchName;
    }

    public void setBranchName(String branchName) {
        this.branchName = branchName;
    }

    @ManyToOne(fetch=FetchType.LAZY)
    @JoinColumn(name="COMPANY_ID")
    public Company getCompany() {
        return company;
    }

    public void setCompany(Company company) {
        this.company = company;
    }


}
corail
la source
20

Je voudrais juste ajouter que @JoinColumncela ne doit pas toujours être lié à l' emplacement des informations physiques comme le suggère cette réponse. Vous pouvez combiner @JoinColumnavec @OneToManymême si la table parent n'a pas de données de table pointant vers la table enfant.

Comment définir une relation OneToMany unidirectionnelle dans JPA

OneToMany unidirectionnel, pas de ManyToOne inverse, pas de table de jointure

Il ne semble cependant être disponible qu'en JPA 2.x+. Il est utile dans les situations où vous souhaitez que la classe enfant contienne simplement l'ID du parent, et non une référence complète.

Snekse
la source
vous avez raison, le support de OneToMany unidirectionnel sans table de jointure est introduit dans JPA2
aurelije
17

Je ne suis pas d'accord avec la réponse acceptée ici par Óscar López. Cette réponse est inexacte!

Ce n'est PAS @JoinColumnce qui indique que cette entité est le propriétaire de la relation. C'est plutôt l' @ManyToOneannotation qui fait cela (dans son exemple).

Les annotations de relation telles que @ManyToOne, @OneToManyet @ManyToManyindiquent à JPA / Hibernate de créer un mappage. Par défaut, cela se fait via une table de jointure distincte.


@JoinColumn

Le but de @JoinColumnest de créer une colonne de jointure si elle n'existe pas déjà. Si c'est le cas, cette annotation peut être utilisée pour nommer la colonne de jointure.


MappedBy

Le but du MappedByparamètre est d'indiquer à JPA: NE PAS créer une autre table de jointure car la relation est déjà mappée par l' entité opposée de cette relation.



Rappelez-vous: MappedByest une propriété des annotations de relations dont le but est de générer un mécanisme pour relier deux entités, ce qu'elles font par défaut en créant une table de jointure. MappedByarrête ce processus dans une direction.

L'entité qui n'utilise pas MappedByest réputée être le propriétaire de la relation, car la mécanique du mappage est dictée au sein de sa classe par l'utilisation de l'une des trois annotations de mappage par rapport au champ de clé étrangère. Cela spécifie non seulement la nature du mappage, mais donne également des instructions sur la création d'une table de jointure. En outre, l'option de suppression de la table de jointure existe également en appliquant l'annotation @JoinColumn sur la clé étrangère qui la conserve à l'intérieur de la table de l'entité propriétaire.

Donc en résumé: @JoinColumnsoit crée une nouvelle colonne de jointure, soit renomme une colonne existante; tandis que le MappedByparamètre fonctionne en collaboration avec les annotations de relation de l'autre classe (enfant) afin de créer un mappage via une table de jointure ou en créant une colonne de clé étrangère dans la table associée de l'entité propriétaire.

Pour illustrer le MapppedByfonctionnement, considérez le code ci-dessous. Si le MappedByparamètre devait être supprimé, Hibernate créerait en fait DEUX tables de jointure! Pourquoi? Parce qu'il y a une symétrie dans les relations plusieurs-à-plusieurs et Hibernate n'a aucune raison de choisir une direction plutôt qu'une autre.

Nous utilisons donc MappedBypour dire à Hibernate, nous avons choisi l'autre entité pour dicter la cartographie de la relation entre les deux entités.

@Entity
public class Driver {
    @ManyToMany(mappedBy = "drivers")
    private List<Cars> cars;
}

@Entity
public class Cars {
    @ManyToMany
    private List<Drivers> drivers;
}

L'ajout de @JoinColumn (name = "driverID") dans la classe propriétaire (voir ci-dessous), empêchera la création d'une table de jointure et, à la place, créera une colonne de clé étrangère driverID dans la table Cars pour construire un mappage:

@Entity
public class Driver {
    @ManyToMany(mappedBy = "drivers")
    private List<Cars> cars;
}

@Entity
public class Cars {
    @ManyToMany
    @JoinColumn(name = "driverID")
    private List<Drivers> drivers;
}
IqbalHamid
la source
1

JPA est une API en couches, les différents niveaux ont leurs propres annotations. Le niveau le plus élevé est le (1) niveau d'entité qui décrit les classes persistantes, puis vous avez (2) le niveau de base de données relationnelle qui suppose que les entités sont mappées à une base de données relationnelle et (3) le modèle java.

Niveau 1 annotations: @Entity, @Id, @OneToOne, @OneToMany, @ManyToOne, @ManyToMany. Vous pouvez introduire la persistance dans votre application en utilisant uniquement ces annotations de haut niveau. Mais ensuite, vous devez créer votre base de données selon les hypothèses que JPA fait. Ces annotations spécifient le modèle entité / relation.

Niveau 2 annotations: @Table, @Column, @JoinColumn, ... Influence du mappage des entités / propriétés aux tables de base de données relationnelles / colonnes si vous n'êtes pas satisfait des réglages par défaut de l' APP ou si vous avez besoin de la carte à une base de données existante. Ces annotations peuvent être considérées comme des annotations d'implémentation, elles spécifient comment le mappage doit être effectué.

À mon avis, il est préférable de s'en tenir autant que possible aux annotations de haut niveau, puis d'introduire les annotations de niveau inférieur selon les besoins.

Pour répondre aux questions: le @OneToMany/ mappedByest plus agréable car il n'utilise que les annotations du domaine d'entité. Le @oneToMany/ @JoinColumnest également correct, mais il utilise une annotation d'implémentation lorsque cela n'est pas strictement nécessaire.

Bruno Ranschaert
la source
1

Permettez-moi de faire simple.
Vous pouvez utiliser @JoinColumn de chaque côté indépendamment du mappage.

Divisons cela en trois cas.
1) Cartographie unidirectionnelle de la succursale à l'entreprise.
2) Cartographie bidirectionnelle de l'entreprise à la succursale.
3) Uniquement cartographie unidirectionnelle de l'entreprise à la succursale.

Ainsi, tout cas d'utilisation tombera dans ces trois catégories. Alors laissez-moi vous expliquer comment utiliser @JoinColumn et mappedBy .
1) Cartographie unidirectionnelle de la succursale à l'entreprise.
Utilisez JoinColumn dans la table Branch.
2) Cartographie bidirectionnelle de l'entreprise à la succursale.
Utilisez mappedBy dans le tableau Company comme décrit par la réponse de @Mykhaylo Adamovych.
3) Cartographie unidirectionnelle de l'entreprise à la succursale.
Utilisez simplement @JoinColumn dans la table Company.

@Entity
public class Company {

@OneToMany(cascade = CascadeType.ALL , fetch = FetchType.LAZY)
@JoinColumn(name="courseId")
private List<Branch> branches;
...
}

Cela signifie que, sur la base du mappage de la clé étrangère "courseId" dans la table des branches, obtenez-moi la liste de toutes les branches. REMARQUE: dans ce cas, vous ne pouvez pas extraire une entreprise d'une succursale, seul le mappage unidirectionnel existe d'une entreprise à l'autre.

Vinjamuri Satya Krishna
la source