Это то, чему я научился до сих пор.
Если я ошибаюсь в каком-либо из пунктов - сообщите мне об этом в комментариях, и я отредактирую ответ.
База данных
Владелец отношения
`Согласно этому ответу
По сути, владельцем является адрес, так как ссылка принадлежит владельцу.
Таким образом, отношение * owner
в базе данных является сущностью с внешним ключом - Address
в случае OneToOne / OneToMany.
Более того - кажется, что в то же время "owner
" отношения базы данных является Child
в отношении Hibernate - но мне нужно подтверждение для этого
OneToOne
Parent / Child
Согласно этой статье
Сущность Post является родительской, а PostDetails - дочерней ассоциацией, поскольку внешний ключ находится в таблице базы данных post_details
Итак, User
- это Parent
, а Address
- это Child
здесь (потому что Address
содержит внешний ключ)
mappedBy
Согласно этот ответ
Атрибут mappedBy отмечает сторону двунаправленной ассоциации, которой не принадлежит эта ассоциация. Обычно это сторона, у которой нет внешнего ключа.
Итак, mappedBy
следует поместить в User
, потому что foreign key
находится в Address
.
Кажется, что в @OneToOne
mappedBy
всегда следует помещать в Parent
(сущность, не имеющая внешнего ключа)
JoinColumn
Кажется, что JoinColumn
в OneToOne
всегда должно быть помещено в Child
(так как он содержит внешний ключ)
Каскады
Согласно этот ответ
каскадные переходы состояний сущности имеют смысл только от родителей к дочерним сущностям.
Так что cascades
в OneToOne
всегда следует делать в Parent
, поэтому - согласно предыдущим ссылкам - User
в этом примере
установить служебный метод
Я не уверен, но кажется, что установщик утилит должен быть помещен в User
.
Всегда ли он помещен в Parent
?
Результат
CREATE SEQUENCE IF NOT EXISTS hibernate_sequence;
CREATE TABLE users (
id BIGINT PRIMARY KEY
);
CREATE TABLE addresses (
id BIGINT PRIMARY KEY,
user_id BIGINT NOT NULL UNIQUE CONSTRAINT fk_addresses_user_id REFERENCES users(id) ON DELETE CASCADE,
);
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
@OneToOne(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY, optional = false, orphanRemoval = true)
private Address address;
public void setAddress(Address address) {
if (address != null) {
address.setUser(this);
}
this.address = address;
}
}
@Table(name = "addresses")
public class Address {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private User user;
}
OneToMany
Parent / Child
Это похоже на OneToOne
, поэтому User
- это Parent
, а Address
- это Child
(потому что он имеет внешний ключ), но я не уверен на 100%. ..
mappedBy
В эта статья mappedBy
помещена в Parent
(но я не уверен, что это практическое правило)
JoinColumn
Похоже, что JoinColumn
в OneToMany
всегда нужно помещать в Child
(так как он содержит внешний ключ)
Каскады
В вышеприведенной статье cascades
также помещены в Parent
(но я не уверен, что и это правило большого пальца)
добавить / удалить служебные методы
В вышеприведенной статье utility methods
также помещены в Parent
(но я не уверен, что и это правило большого пальца)
Результат
CREATE SEQUENCE IF NOT EXISTS hibernate_sequence;
CREATE TABLE users (
id BIGINT PRIMARY KEY
);
CREATE TABLE addresses (
id BIGINT PRIMARY KEY,
user_id BIGINT NOT NULL CONSTRAINT fk_addresses_user_id REFERENCES users(id) ON DELETE CASCADE,
);
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
private List<Address> addresses = new ArrayList<>();
public void addAddress(Address address) {
if (address != null) {
address.setUser(this);
}
this.addresses.add(address);
}
public void removeAddress(Address address) {
this.addresses.remove(address);
if (address != null) {
address.setUser(null);
}
}
public Set<Address> getAddresses() {
return Collections.unmodifiableSet(this.addresses);
}
}
@Table(name = "addresses")
public class Address {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private User user;
}
ManyToMany
Parent / Child
Согласно этой статье
Типичная ассоциация базы данных «многие ко многим» включает две родительские таблицы
То есть User
и Address
Parents
mappedBy
В той же статье Влад написал:
Атрибут mappedBy ассоциации сообщений в объекте Tag отмечает, что в этом двунаправленном отношении объект Post владеет ассоциацией
Следовательно, здесь есть смысл, потому что у нас его нет Parent
.
Мне не ясно, что именно Влад имеет в виду под «своим», но если User
«владеет» отношением, mappedBy
должно быть помещено в Address
,
JoinTable
Кажется, что JoinTable
всегда должен быть помещен в сущность, которая "владеет" отношением, поэтому User
в этом случае.
Я прав?
Каскады
В соответствии с той же статьей cascades
всегда следует указывать в «владельце», указанном выше, поэтому User
в этом случае.
Также важно отметить, что мы не можем использовать REMOVE
каскад
добавить / удалить служебные методы
Похоже, что add/remove utility methods
следует поместить в User
.
Это эмпирическое правило, что utility methods
всегда следует помещать в сущность, которая "владеет" отношением?
Результат
CREATE SEQUENCE IF NOT EXISTS hibernate_sequence;
CREATE TABLE users (
id BIGINT PRIMARY KEY
);
CREATE TABLE addresses (
id BIGINT PRIMARY KEY,
);
CREATE TABLE users_addresses (
user_id BIGINT NOT NULL CONSTRAINT fk_users_addresses_user_id REFERENCES users(id) ON DELETE CASCADE,
address_id BIGINT NOT NULL CONSTRAINT fk_users_addresses_address_id REFERENCES addresses(id) ON DELETE CASCADE,
PRIMARY KEY(user_id,address_id)
);
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
@ManyToMany(cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
})
@JoinTable(name = "post_tag",
joinColumns = @JoinColumn(name = "post_id"),
inverseJoinColumns = @JoinColumn(name = "tag_id")
)
private List<Address> addresses = new ArrayList<>();
public void addAddress(Address address) {
addresses.add(address);
address.getUsers().add(this);
}
public void removeAddress(Address address) {
addresses.remove(address);
address.getUsers().remove(this);
}
}
@Table(name = "addresses")
public class Address {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
@ManyToMany(mappedBy = "addresses")
private List<User> users = new ArrayList<>();
}
Резюме
Я думаю, что эта таблица подводит итог: https://i.ibb.co/zNjZ3md/JPA-relations.png
Последнее, что я не понимаю, это:
- почему Влад в этой статье в разделе "ManyToMany" использует
mappedBy
в Author
("владелец") вместо Book
(особенно из-за исключения в моем коде)
- почему в javadoc
mappedBy
включен CustomerRecord
(дочерний) не включен Customer
(но здесь, возможно, внешний ключ находится в Customer, поэтому, хотя счетчик интуитивно понятен - это правильно)