JPA JoinColumn vs mappedBy
В чем разница между:
@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
Аннотация @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;
}
Ответ 2
@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
через свойство troop . Вам не нужно (не должно) определять какое-либо физическое отображение на стороне mappedBy
.
Чтобы сопоставить двунаправленный параметр "один ко многим" со стороной "один ко многим" в качестве стороны-владельца, вы должны удалить mappedBy
элемент и установить для параметра "много" значение "один" @JoinColumn
как insertable
и updatable
значение false. Это решение не оптимизировано и приведет к появлению некоторых дополнительных 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() {
...
}
Ответ 3
Однонаправленная ассоциация "один ко многим"
Если вы используете @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
entity отображается следующим образом:
@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 не гарантирует, что изменения состояния ассоциации будут распространяться на базу данных.
Какой из них выбрать?
Однонаправленная @OneToMany
ассоциация работает не очень хорошо, поэтому вам следует избегать ее.
Вам лучше использовать двунаправленный @OneToMany
который более эффективен.
Ответ 4
Я не согласен с принятым здесь ответом Оскара Лопеса. Этот ответ неточен!
Это НЕ @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") в класс owner (см. Ниже) предотвратит создание таблицы соединений и вместо этого создаст столбец внешнего ключа 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;
}