Яка різниця між @JoinColumn та mappedBy при використанні асоціації JPA @OneToMany


516

Яка різниця між:

@Entity
public class Company {

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

і

@Entity
public class Company {

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

1
Також дивіться, що є власником у питанні щодо ORM-карти щодо дійсно хорошого пояснення пов'язаних з цим проблем.
dirkt

Відповіді:


545

Анотація @JoinColumnвказує, що ця сутність є власником відносин (тобто у відповідній таблиці є стовпець із зовнішнім ключем до посилається таблиці), тоді як атрибут mappedByвказує, що сутність у цій стороні є зворотною залежністю, і власник проживає в "іншому" об'єкті. Це також означає, що ви можете отримати доступ до іншої таблиці з класу, який ви помітили за допомогою "mappedBy" (повністю двонаправлене співвідношення).

Зокрема, для коду у питанні правильні примітки виглядали б так:

@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;
}

3
в обох випадках відділення має поле з ідентифікатором компанії.
Михайло Адамович

3
У таблиці компанії немає стовпця із зовнішнім ключем до посилається таблиці - Відділення має посилання на компанію .. чому ви говорите "у відповідній таблиці є стовпець із зовнішнім ключем до посилається таблиці"? Не могли б ви пояснити ще кілька PLS.
Михайло Адамович

13
@MykhayloAdamovych Я оновив свою відповідь зразковим кодом. Зауважте, що помилка використання @JoinColumnуCompany
Оскар Лопес

10
@MykhayloAdamovych: Ні, це насправді не зовсім правильно. Якщо у вас Branchнемає властивості, на яку посилається Company, але в нижній таблиці є стовпець, який є, ви можете використовувати її @JoinTableдля відображення. Це незвична ситуація, оскільки ви зазвичай відображаєте стовпець у об'єкті, який відповідає його таблиці, але це може статися, і це цілком законно.
Том Андерсон

4
Це ще одна причина, що не подобається ORM. Документація часто занадто хитра, і в моїх книгах це хитається на занадто великій магічній території. Я боровся з цією проблемою, і коли слово за словом дотримується слово a @OneToOne, дочірні рядки оновлюються nullв колонці FKey, що посилається на батьків.
Еш

225

@JoinColumnможе використовуватися з обох сторін відносин. Питання полягало у використанні @JoinColumnна @OneToManyстороні (рідкісний випадок). І суть тут полягає у дублюванні фізичної інформації (назва стовпця) разом з неоптимізованим запитом SQL, який дасть додаткові UPDATEзаяви .

Відповідно до документації :

Оскільки багато хто з них є (майже) завжди власником двосторонніх відносин у специфікації JPA, асоціація «багато хто» зазначається@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() {
    ...
} 

Troopмає двосторонній зв'язок між багатьма за Soldierдопомогою власності війська. Ви не повинні (не повинні) визначати будь-яке фізичне відображення mappedByзбоку.

Щоб зіставити двонаправлене на багато, при цьому сторона "один на багато" є власною стороною , ви повинні вилучити mappedByелемент і встановити багато на одне @JoinColumnяк " insertableі" updatableна "хибне". Це рішення не оптимізоване і дасть додаткові UPDATEзаяви.

@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() {
    ...
}

1
Я не в змозі зрозуміти, як Troop може бути власником у вашому другому фрагменті, Soldier все ще є власником, оскільки він містить зовнішній ключ, що посилається на Troop. (Я використовую mysql, я перевірив ваш підхід).
Ахілеш

10
У вашому прикладі примітки mappedBy="troop"посилайтеся на яке поле?
Fractaliste

5
@Fractaliste анотація mappedBy="troop"стосується групи властивостей класу Soldier. У коді вище властивість не видно, оскільки тут Михайло її опустив, але ви можете вивести його існування за допомогою getter getTroop (). Перевірте відповідь Оскара Лопеса , це дуже зрозуміло, і ви отримаєте бал.
nicolimo86

1
Цей приклад - зловживання специфікацією JPA 2. Якщо метою автора є створення двонаправленого відношення, то він повинен використовувати mappedBy на батьківській стороні, а JoinColumn (за потреби) на дочірніх сторонах. З представленим тут підходом ми отримуємо 2 однонаправлені відносини: OneToMany та ManyToOne, які є незалежними, але просто пощастило (більше зловживань), ці 2 відносини визначаються за допомогою того ж зовнішнього ключа
aurelije

1
Якщо ви використовуєте JPA 2.x, моя відповідь нижче - трохи зрозуміліша. Хоча я пропоную спробувати обидва маршрути і побачити, що робить сплячий режим, коли він генерує таблиці. Якщо ви працюєте над новим проектом, виберіть будь-яке покоління, яке, на вашу думку, відповідає вашим потребам. Якщо ви перебуваєте в застарілій базі даних і не хочете змінювати структуру, виберіть те, що відповідає вашій схемі.
Снексе

65

Оскільки це дуже поширене питання, я написав цю статтю , на якій ґрунтується ця відповідь.

Односпрямована асоціація «багато хто»

Як я пояснив у цій статті , якщо ви користуєтесь @OneToManyанотацією @JoinColumn, ви маєте однонаправлену асоціацію, як та, яка знаходиться між материнським Postоб'єктом та дитиною PostCommentна наступній схемі:

Односпрямована асоціація «багато хто»

При використанні однонаправленої асоціації один на багато, лише батьківська сторона відображає асоціацію.

У цьому прикладі лише Postсутність визначатиме @OneToManyасоціацію з дочірнім PostCommentоб'єктом:

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

Двонаправлена ​​асоціація «багато хто»

Якщо ви використовуєте @OneToManyз mappedByнабором атрибутів, у вас є двостороння асоціація. У нашому випадку обидва суб'єкти Postмають сукупність PostCommentдочірніх сутностей, а дочірнє PostCommentутворення має посилання на материнське Postсутність, як це проілюстровано наступною схемою:

Двонаправлена ​​асоціація «багато хто»

В PostCommentсутності, то postвластивість об'єкта відображаються наступним чином :

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

Причина, з якою ми явно встановлюємо fetchатрибут, FetchType.LAZYполягає в тому, що, за замовчуванням, всі @ManyToOneта @OneToOneасоціації збираються охоче, що може спричинити N + 1 запитів. Більш детально про цю тему перегляньте цю статтю .

В Postоб'єкті commentsасоціація відображається так:

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

mappedByАтрибут @OneToManyанотацію посилається на postвласність в дитячій PostCommentсуті, і, таким чином, Hibernate знає , що двунаправленное об'єднання під контролем@ManyToOne стороною, яка відповідає за управління значенням стовпця зовнішнього ключа цього відношення таблиці засноване на.

Для двосторонньої асоціації вам також потрібно мати два корисні методи, як-от addChildі removeChild:

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

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

Ці два методи забезпечують синхронізацію обох сторін двонаправленої асоціації. Без синхронізації обох кінців, Hibernate не гарантує, що зміни стану асоціації поширюватимуться на базу даних.

Щоб отримати докладнішу інформацію про найкращий Ват для синхронізації двосторонніх асоціацій із JPA та Hibernate, перегляньте цю статтю .

Який вибрати?

Односпрямована @OneToManyасоціація не виконує дуже добре , тому слід уникати його.

Вам краще використовувати двонаправлену, @OneToManyяка є більш ефективною .


32

Анотація, нанесена на карту, в ідеалі завжди повинна використовуватися на батьківській стороні (клас компанії) двонаправлених відносин, в цьому випадку вона повинна бути в класі компанії, що вказує на змінну члена "компанія" дочірнього класу (клас відділення)

анотацію @JoinColumn використовується для вказівки зіставленого стовпця для приєднання до асоціації суб'єкта, ця примітка може використовуватися в будь-якому класі (батьківський чи дочірній), але в ідеалі вона повинна використовуватися лише в одній стороні (або в батьківському класі, або в дочірньому класі, не в обох випадках) тут у цьому випадку я використав це у дочірній (клас відділення) двонаправленого зв’язку із зазначенням зовнішнього ключа класу Відділення.

нижче робочий приклад:

батьківський клас, компанія

@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;
    }


}

дитячий клас, Відділення

@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;
    }


}

20

Я хотів би лише додати, що @JoinColumnне завжди має бути пов’язано з місцем фізичної інформації, як підказує ця відповідь. Ви можете поєднувати @JoinColumnз @OneToManyнавіть, якщо в батьківській таблиці немає даних таблиці, що вказують на дочірню таблицю.

Як визначити однонаправлене відношення OneToMany в JPA

Односпрямований OneToMany, немає зворотного ManyToOne, немає таблиці приєднання

Здається, він доступний лише у, JPA 2.x+хоча. Це корисно для ситуацій, коли ви хочете, щоб дочірній клас просто містив ідентифікатор батьків, а не повний посилання.


ви праві, підтримка однонаправленої OneToMany без таблиці приєднання введена в JPA2
aurelije

17

Я не погоджуюся з прийнятою тут відповіддю Оскара Лопеса. Ця відповідь неточна!

НЕ @JoinColumnвказує на те, що ця організація є власником відносин. Натомість це робить @ManyToOneанотація (на його прикладі).

Відносини анотації , такі як @ManyToOne, @OneToManyі @ManyToManyсказати JPA / Hibernate , щоб створити відображення. За замовчуванням це робиться через окрему таблицю приєднання.


@JoinColumn

Мета @JoinColumn- створити стовпчик приєднання, якщо його ще не існує. Якщо так, то ця примітка може бути використана для іменування стовпця приєднання.


MappedBy

Метою MappedByпараметра є вказівка ​​JPA: НЕ створюйте іншу таблицю приєднання, оскільки відношення вже відображається протилежною сутністю цього відношення.



Пам'ятайте: MappedByвластивість анотацій відносин, метою яких є створення механізму відношення двох сутностей, які за замовчуванням вони роблять, створюючи таблицю приєднання. MappedByзупиняє процес в одному напрямку.

MappedByКажуть, що суб'єкт, який не використовує, є власником відносин, оскільки механіка відображення диктується в межах його класу за допомогою використання однієї з трьох анотацій зі відображенням проти зовнішнього ключового поля. Це не тільки визначає характер відображення, але й дає інструкцію створити таблицю приєднання. Крім того, можливість придушити таблицю приєднання також існує, застосувавши анотацію @JoinColumn над зовнішнім ключем, який зберігає її всередині таблиці об'єкта власника.

Отже, підсумовуючи: @JoinColumnабо створює новий стовпець приєднання, або перейменовує існуючий; в той час якMappedBy параметр співпрацює з анотаціями відношень іншого (дочірнього) класу, щоб створити відображення або через таблицю приєднання, або шляхом створення стовпця із зовнішнім ключем у пов'язаній таблиці сутності власника.

Щоб проілюструвати, як MapppedByпрацює, розгляньте наведений нижче код. Якби MappedByпараметр був видалений, Hibernate фактично створив би ДВІ таблиці приєднання! Чому? Тому що існує симетрія у відносинах між багатьма людьми, і сплячка не має обґрунтування для вибору одного напрямку над іншим.

Тому ми використовуємо, MappedByщоб сказати сплячому, ми обрали іншу сутність, щоб продиктувати відображення відносин між двома сутностями.

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

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

Додавання @JoinColumn (name = "driverID") у клас власника (див. Нижче), запобіжить створенню таблиці приєднання та натомість створить стовпчик із зовнішнім ключем driverID у таблиці Cars для побудови відображення:

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

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

1

JPA - це багатошаровий API, різні рівні мають свої анотації. Найвищим рівнем є рівень (1) Entity, який описує стійкі класи, тоді у вас є (2) рівень реляційної бази даних, який передбачає, що об'єкти відображаються в реляційній базі даних і (3) java-моделі.

Рівень 1 анотації: @Entity, @Id, @OneToOne, @OneToMany, @ManyToOne,@ManyToMany . Ви можете ввести стійкість у свою програму, використовуючи лише ці примітки високого рівня. Але тоді вам доведеться створити свою базу даних відповідно до припущень, зроблених JPA. Ці анотації визначають модель особи / відносини.

Рівень 2 анотацій: @Table, @Column, @JoinColumn, ... Вплив на відображення суб'єктів / властивостей для реляційних таблиць бази даних / стовпці , якщо ви не задоволені за замовчуванням JPA або , якщо вам потрібно відобразити в існуючу базу даних. Ці анотації можна розглядати як примітки про реалізацію, вони визначають, як слід робити відображення.

На мою думку, найкраще дотримуватися якомога більше анотацій високого рівня, а потім вводити анотації нижчого рівня за необхідності.

Щоб відповісти на запитання: @OneToMany/ mappedByнайприємніше, оскільки він використовує лише примітки з домену сутності. @oneToMany/ @JoinColumnТеж добре , але він використовує анотацію реалізації , де це не є строго необхідним.


1

Дозвольте зробити це просто.
Ви можете використовувати @JoinColumn з обох сторін незалежно від відображення.

Розділимо це на три випадки.
1) Односпрямоване відображення від Філії до Компанії.
2) Двостороннє відображення від компанії до філії.
3) Тільки односпрямоване відображення від компанії до філії.

Тож будь-який випадок використання підпадає під ці три категорії. Тож дозвольте мені пояснити, як використовувати @JoinColumn та mappedBy .
1) Односпрямоване відображення від Філії до Компанії.
Використовуйте JoinColumn у таблиці відділення.
2) Двостороннє відображення від компанії до філії.
Використовуйте mappedBy в таблиці компанії, як описано у відповіді @Mykhaylo Adamovych.
3) Односпрямоване відображення від компанії до філії.
Просто використовуйте @JoinColumn у таблиці компанії.

@Entity
public class Company {

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

Це говорить про те, що, грунтуючись на зовнішньому ключовому "mapId" відображенні в таблиці філій, знайдіть мені список усіх гілок. ПРИМІТКА: у цьому випадку ви не можете взяти компанію з філії, лише існує односпрямоване відображення від компанії до філії.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.