6

JPA JoinColumn против mappedBy: в чем разница?

1

Какова разница между двумя приведенными выше аннотациями в контексте JPA (Java Persistence API)?

В первом примере:

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

аннотация @JoinColumn используется для определения колонки, которая будет связывать сущность Company с сущностью Branch. Здесь указано, что в таблице Branch будет столбец companyIdRef, который является внешним ключом, ссылающимся на companyId в таблице Company.

Во втором примере:

@Entity
public class Company {

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

используется аннотация mappedBy, которая указывает, что связь управляется стороной Branch и что в классе Branch есть поле companyIdRef, которое несет информацию о связи. Таким образом, таблица Branch должна содержать информацию о том, к какой компании принадлежит каждый филиал.

В чем разница между этими подходами? Какой из них предпочтительнее в определенных ситуациях, и какие могут быть последствия выбора одного подхода над другим?

5 ответ(ов)

6

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

В частности, для приведенного в вопросе кода правильные аннотации будут выглядеть следующим образом:

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

@Entity
public class Branch {
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "companyId")
    private Company company;
}
0

Я не согласен с принятой здесь ответом Óscar López. Этот ответ неточный!

Именно аннотация @ManyToOne, а не @JoinColumn, указывает на то, что данная сущность является владельцем отношений (в его примере).

Аннотации, такие как @ManyToOne, @OneToMany и @ManyToMany, сообщают JPA/Hibernate о необходимости создать связь. По умолчанию это происходит через отдельную таблицу соединений.

@JoinColumn

Цель аннотации @JoinColumn - создать колонку соединения, если она еще не существует. Если она уже есть, то эта аннотация может быть использована для именования колонки соединения.

MappedBy

Цель параметра MappedBy - дать инструкцию JPA: НЕ создавайте другую таблицу соединений, так как связь уже отображается другой (противоположной) сущностью данного отношения.

Запомните: MappedBy — это свойство аннотаций отношений, задача которого - создать механизм для установления соотношения между двумя сущностями, которые по умолчанию создаются через таблицу соединений. MappedBy останавливает этот процесс в одном направлении.

Сущность, не использующая MappedBy, считается владельцем отношений, поскольку механика отображения определяется внутри ее класса с помощью одной из трех аннотаций отображения, использующих поле внешнего ключа. Это не только указывает на природу отображения, но и дает указание на создание таблицы соединений. Более того, существует возможность подавить создание таблицы соединений, применив аннотацию @JoinColumn на внешнем ключе, что сохраняет его внутри таблицы сущности-владельца.

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

Чтобы проиллюстрировать, как работает MappedBy, рассмотрим код ниже. Если удалить параметр 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;
}
0

Аннотация mappedBy лучше всего использовать на стороне родителя (в классе Company) в двунаправленных отношениях. В данном случае она должна находиться в классе Company и указывать на член переменной 'company' в дочернем классе (классе Branch).

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

Ниже приведён рабочий пример:

Родительский класс, Company

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

}

Дочерний класс, Branch

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

}
0

Давайте упростим.

Вы можете использовать аннотацию @JoinColumn с любой стороны без учета типа связывания.

Рассмотрим три случая:

  1. Uni-directional связывание от Branch (филиала) к Company (компании).
  2. Bi-directional связывание от Company к Branch.
  3. Uni-directional связывание только от Company к Branch.

Таким образом, любой случай будет подпадать под одну из этих категорий. Далее объясню, как использовать @JoinColumn и mappedBy.

  1. Uni-directional связывание от Branch к Company.

Используйте @JoinColumn в таблице Branch.

  1. Bi-directional связывание от Company к Branch.

Используйте mappedBy в таблице Company, как описано в ответе @Mykhaylo Adamovych.

  1. Uni-directional связывание только от Company к Branch.

Просто используйте @JoinColumn в таблице Company.

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

Это означает, что на основе внешнего ключа "courseId" в таблице branches получите список всех филиалов. Обратите внимание: в этом случае вы не сможете получить компанию из филиала, так как существует только uni-directional связывание от компании к филиалам.

0

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

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

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

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

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

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь