В чем разница между @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 для действительно хорошего объяснения проблем.
Диркт

Ответы:

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
В таблице компании нет столбца с внешним ключом для ссылочной таблицы - у Branch есть ref для Company .. почему вы говорите, что "в соответствующей таблице есть столбец с внешним ключом для ссылочной таблицы"? Не могли бы вы объяснить еще несколько пожалуйста.
Михаил Адамович
13
@MykhayloAdamovych Я обновил свой ответ с примером кода. Обратите внимание , что это ошибка , чтобы использовать @JoinColumnвCompany
Óscar López
10
@MykhayloAdamovych: Нет, это на самом деле не совсем верно. Если Branchнет свойства, которое ссылается Company, но в базовой таблице есть столбец, который имеет, то вы можете использовать его @JoinTableдля сопоставления. Это необычная ситуация, потому что вы обычно сопоставляете столбец в объекте, который соответствует его таблице, но это может произойти, и это совершенно законно.
Том Андерсон
4
Это еще одна причина не любить ORM. Документация часто слишком изворотлива, и в моих книгах это блуждает на слишком большой волшебной территории. Я боролся с этой проблемой, и когда слово за словом следовало за @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"относятся к какому полю?
Фракталист
5
@Fractaliste аннотация mappedBy="troop"относится к отряду свойств в классе Soldier. В приведенном выше коде свойство не видно, потому что здесь Михайло его опускает, но вы можете определить его существование с помощью getTroop (). Проверьте ответ Оскара Лопеса , это очень ясно, и вы получите точку.
nicolimo86
1
Этот пример является нарушением спецификации JPA 2. Если целью автора является создание двунаправленного отношения, то он должен использовать mappedBy на родительской стороне и JoinColumn (если необходимо) на дочерней стороне. С подходом, представленным здесь, мы получаем 2 однонаправленных отношения: OneToMany и ManyToOne, которые независимы, но просто по счастливой случайности (больше из-за неправильного использования), эти 2 отношения определены с использованием одного и того же внешнего ключа
aurelije
1
Если вы используете JPA 2.x, мой ответ немного чище. Хотя я предлагаю попробовать оба маршрута и посмотреть, что делает Hibernate при создании таблиц. Если вы находитесь в новом проекте, выберите поколение, которое, по вашему мнению, соответствует вашим потребностям. Если вы находитесь в устаревшей базе данных и не хотите менять структуру, выберите тот, который соответствует вашей схеме.
Снексе
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 не гарантирует, что изменения состояния ассоциации будут распространяться на базу данных.

Для получения более подробной информации о лучшем wat для синхронизации двунаправленных ассоциаций с JPA и Hibernate, ознакомьтесь с этой статьей .

Какой выбрать?

Однонаправленная @OneToManyассоциация не выполняет очень хорошо , поэтому следует избегать его.

Вам лучше использовать двунаправленный, @OneToManyкоторый является более эффективным .

Влад Михалча
источник
32

В идеале аннотацию mappedBy всегда следует использовать в родительской части (класс Company) двунаправленного отношения, в этом случае она должна быть в классе Company, указывая на переменную-член 'company' класса Child (класс Branch)

Аннотация @JoinColumn используется для указания сопоставленного столбца для присоединения к ассоциации сущностей, эту аннотацию можно использовать в любом классе (родительском или дочернем), но в идеале ее следует использовать только на одной стороне (либо в родительском классе, либо в дочернем классе, но не в обоих случаях) здесь, в этом случае, я использовал его в дочерней стороне (класс Branch) двунаправленного отношения, указывающего внешний ключ в классе Branch.

ниже рабочий пример:

родительский класс, компания

@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+. Это полезно в ситуациях, когда вы хотите, чтобы дочерний класс просто содержал идентификатор родителя, а не полную ссылку на него.

Snekse
источник
Вы правы, поддержка однонаправленной системы 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 фактически создаст ДВЕ таблицы соединения! Почему? Потому что во взаимоотношениях «многие ко многим» существует симметрия, а в Hibernate нет смысла выбирать одно направление над другим.

Поэтому мы используем, MappedByчтобы сообщить Hibernate, мы выбрали другую сущность, чтобы диктовать отображение отношений между двумя сущностями.

@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;
}
IqbalHamid
источник
1

JPA является многоуровневым API, различные уровни имеют свои собственные аннотации. Самый высокий уровень - это (1) уровень сущности, который описывает постоянные классы, тогда у вас есть (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 в таблице Branch.
2) Двунаправленное картирование от компании к филиалу.
Используйте mappedBy в таблице компаний, как описано в ответе @Mykhaylo Adamovych.
3) Однонаправленное отображение от Компании к Филиалу.
Просто используйте @JoinColumn в таблице компании.

@Entity
public class Company {

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

Это говорит о том, что, основываясь на отображении внешнего ключа "courseId" в таблице ветвей, получите список всех ветвей. ПРИМЕЧАНИЕ: в этом случае вы не можете получить компанию из филиала, существует только однонаправленное отображение от компании к филиалу.

Винджамури Сатья Кришна
источник