В чем разница между @JoinColumn и mappedBy при использовании ассоциации JPA @OneToMany

В чем разница между:

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

9 ответов

Решение

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

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

Чтобы отобразить двунаправленный канал " один-ко-многим" со стороной " один-ко-многим" в качестве стороны-владельца, вы должны удалить элемент mappedBy и установить для "многие" один @JoinColumn как вставляемый и обновляемый на "ложь". Это решение не оптимизировано и будет производить некоторые дополнительные операторы 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() {
    ...
}

Аннотация @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;
}

Как я объяснил в этой статье, если вы используете @OneToMany аннотация с @JoinColumn тогда у вас есть однонаправленная ассоциация.

Если вы используете @OneToMany с mappedBy набор атрибутов, у вас есть двунаправленная связь, то есть вам нужно иметь @ManyToOne ассоциация на стороне ребенка, которая mappedBy Рекомендации.

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

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

Я не согласен с принятым здесь ответом Оскара Лопеса. Этот ответ неточный!

Это не @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;
}

В идеале аннотацию mappedBy всегда следует использовать в родительской части (класс Company) двунаправленного отношения, в этом случае она должна быть в классе Company, указывая на переменную-член 'company' дочернего класса (класс 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;
    }


}

Я просто хотел бы добавить, что @JoinColumn не всегда должно быть связано с физическим местоположением информации, как предполагает этот ответ. Вы можете объединить @JoinColumn с @OneToMany даже если родительская таблица не имеет табличных данных, указывающих на дочернюю таблицу.

Как определить однонаправленное отношение OneToMany в JPA

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

Кажется, он доступен только в JPA 2.x+ хоть. Это полезно в ситуациях, когда вы хотите, чтобы дочерний класс просто содержал идентификатор родителя, а не полную ссылку на него.

Позвольте мне сделать это проще.
Вы можете использовать @JoinColumn с любой стороны независимо от сопоставления.

Разделим это на три случая.
1) Однонаправленное отображение от филиала к компании.
2) Двунаправленное отображение от компании к филиалу.
3) Только однонаправленное отображение от компании к филиалу.

Таким образом, любой вариант использования подпадает под эти три категории. Итак, позвольте мне объяснить, как использовать @JoinColumn и mappedBy.
1) Однонаправленное отображение от филиала к компании.
Используйте JoinColumn в таблице ветвей.
2) Двунаправленное отображение от компании к филиалу.
Используйте mappedBy в таблице Company, как описано в ответе @Mykhaylo Adamovych.
3) Однонаправленное отображение от компании к филиалу.
Просто используйте @JoinColumn в таблице компании.

@Entity
public class Company {

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

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

JPA - это многоуровневый API, различные уровни имеют свои собственные аннотации. Самый высокий уровень - это (1) уровень сущности, который описывает постоянные классы, тогда у вас есть (2) уровень реляционной базы данных, который предполагает, что сущности отображаются в реляционную базу данных, и (3) модель Java.

Примечания уровня 1: @Entity, @Id, @OneToOne, @OneToMany, @ManyToOne, @ManyToMany. Вы можете внести постоянство в свое приложение, используя только эти высокоуровневые аннотации. Но тогда вы должны создать свою базу данных в соответствии с предположениями, которые делает JPA. Эти аннотации определяют модель сущности / отношения.

Аннотации уровня 2: @Table, @Column, @JoinColumn, ... Влиять на сопоставление сущностей / свойств с таблицами / столбцами реляционной базы данных, если вы не удовлетворены значениями по умолчанию JPA или если вам необходимо сопоставить существующую базу данных. Эти аннотации можно рассматривать как аннотации реализации, они определяют, как должно выполняться отображение.

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

Чтобы ответить на вопросы: @OneToMany/mappedBy хорош, потому что он использует только аннотации из домена объекта. @ OneToMany/@JoinColumn также хорош, но он использует аннотацию реализации, где это не является строго необходимым.

      @Entity
public class Company {
  
 @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
 @JoinColumn(name = "company_id_ref", referencedColumnName = "company_id")
 private List<Branch> branches;
  ...
}

Это даст ниже журналы Hibernate

      Hibernate: select nextval ('hibernate_sequence')
Hibernate: select nextval ('hibernate_sequence')
Hibernate: insert into company (name, company_id) values (?, ?)
Hibernate: insert into branch (company_id_ref, name, id) values (?, ?, ?)
Hibernate: update branch set company_id_ref=? where id=?

И

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

Это даст ниже журналы Hibernate

      Hibernate: select nextval ('hibernate_sequence')
Hibernate: select nextval ('hibernate_sequence')
Hibernate: insert into company (name, company_id) values (?, ?)
Hibernate: insert into branch (company_id_ref, name, id) values (?, ?, ?)

Мы можем ясно видеть, что@joinColumnвызовет дополнительные запросы на обновление. поэтому вам не нужно явно устанавливать родительский объект в дочерний объект, что мы должны делать при использованииmappedByспасти детей с родителем

Другие вопросы по тегам