Это очень распространенный вопрос, поэтому этот ответ основан на статье, которую я написал в своем блоге.
Один ко многим
Отношение таблиц "один ко многим" выглядит так:
В системе реляционной базы данных отношение таблиц «один ко многим» связывает две таблицы на основе Foreign Key
столбца в дочерней таблице, ссылающегося на Primary Key
одну запись в родительской таблице.
На диаграмме таблицы выше, post_id
столбец в post_comment
таблице имеет Foreign Key
отношения с post
идентификатор таблицы Primary Key
столбца:
ALTER TABLE
post_comment
ADD CONSTRAINT
fk_post_comment_post_id
FOREIGN KEY (post_id) REFERENCES post
@ManyToOne аннотация
В JPA лучший способ отобразить связь таблицы «один-ко-многим» - использовать @ManyToOne
аннотацию.
В нашем случае PostComment
дочерняя сущность отображает post_id
столбец внешнего ключа, используя @ManyToOne
аннотацию:
@Entity(name = "PostComment")
@Table(name = "post_comment")
public class PostComment {
@Id
@GeneratedValue
private Long id;
private String review;
@ManyToOne(fetch = FetchType.LAZY)
private Post post;
}
Использование @OneToMany
аннотации JPA
Тот факт, что у вас есть возможность использовать @OneToMany
аннотацию, не означает, что она должна быть параметром по умолчанию для всех отношений базы данных « один ко многим» .
Проблема с коллекциями JPA в том, что мы можем использовать их только тогда, когда в них достаточно мало элементов.
Лучший способ сопоставить @OneToMany
ассоциацию - положиться на @ManyToOne
сторону для распространения всех изменений состояния объекта:
@Entity(name = "Post")
@Table(name = "post")
public class Post {
@Id
@GeneratedValue
private Long id;
private String title;
@OneToMany(
mappedBy = "post",
cascade = CascadeType.ALL,
orphanRemoval = true
)
private List<PostComment> comments = new ArrayList<>();
public void addComment(PostComment comment) {
comments.add(comment);
comment.setPost(this);
}
public void removeComment(PostComment comment) {
comments.remove(comment);
comment.setPost(null);
}
}
Родительский Post
объект имеет два служебных метода (например, addComment
и removeComment
), которые используются для синхронизации обеих сторон двунаправленной ассоциации.
Эти методы следует предоставлять всякий раз, когда вы работаете с двунаправленной ассоциацией, поскольку в противном случае вы рискуете очень тонкими проблемами распространения состояния .
@OneToMany
Следует избегать однонаправленной ассоциации, поскольку она менее эффективна, чем использование @ManyToOne
или двунаправленная @OneToMany
ассоциация.
Для получения дополнительных сведений о наилучшем способе сопоставления @OneToMany
отношений с JPA и Hibernate ознакомьтесь с этой статьей .
Один к одному
Связь таблицы один-к-одному выглядит следующим образом:
В системе реляционной базы данных отношение таблицы один к одному связывает две таблицы на основе Primary Key
столбца в дочернем элементе, который также является Foreign Key
ссылкой на Primary Key
строку родительской таблицы.
Следовательно, мы можем сказать, что дочерняя таблица имеет общий Primary Key
с родительской таблицей.
На диаграмме таблицы выше id
столбец в post_details
таблице также Foreign Key
связан со столбцом post
таблицы id
Primary Key
:
ALTER TABLE
post_details
ADD CONSTRAINT
fk_post_details_id
FOREIGN KEY (id) REFERENCES post
Использование JPA @OneToOne
с @MapsId
аннотациями
Лучший способ обозначить @OneToOne
отношения - использовать @MapsId
. Таким образом, вам даже не потребуется двунаправленная ассоциация, поскольку вы всегда можете получитьPostDetails
объект, используя Post
идентификатор объекта.
Отображение выглядит так:
@Entity(name = "PostDetails")
@Table(name = "post_details")
public class PostDetails {
@Id
private Long id;
@Column(name = "created_on")
private Date createdOn;
@Column(name = "created_by")
private String createdBy;
@OneToOne(fetch = FetchType.LAZY)
@MapsId
@JoinColumn(name = "id")
private Post post;
public PostDetails() {}
public PostDetails(String createdBy) {
createdOn = new Date();
this.createdBy = createdBy;
}
}
Таким образом, id
свойство служит как первичным, так и внешним ключом. Вы заметите, что @Id
столбец больше не использует @GeneratedValue
аннотацию, поскольку идентификатор заполняется идентификаторомpost
ассоциации.
Для получения дополнительных сведений о лучшем способе сопоставления @OneToOne
отношений с JPA и Hibernate ознакомьтесь с этой статьей. .
Многие-ко-многим
Отношения таблицы многие-ко-многим выглядят следующим образом:
В системе реляционной базы данных отношение таблиц «многие ко многим» связывает две родительские таблицы через дочернюю таблицу, которая содержит два Foreign Key
столбца, ссылающихся на Primary Key
столбцы двух родительских таблиц.
На диаграмме таблицы выше post_id
столбец в post_tag
таблице также Foreign Key
связан со столбцом post
идентификатора таблицы Primary Key
:
ALTER TABLE
post_tag
ADD CONSTRAINT
fk_post_tag_post_id
FOREIGN KEY (post_id) REFERENCES post
Причем, tag_id
столбец в post_tag
таблице имеет Foreign Key
отношения с tag
ID таблицы Primary Key
столбца:
ALTER TABLE
post_tag
ADD CONSTRAINT
fk_post_tag_tag_id
FOREIGN KEY (tag_id) REFERENCES tag
Использование @ManyToMany
сопоставления JPA
Вот как вы можете сопоставить many-to-many
отношения таблиц с JPA и Hibernate:
@Entity(name = "Post")
@Table(name = "post")
public class Post {
@Id
@GeneratedValue
private Long id;
private String title;
@ManyToMany(cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
})
@JoinTable(name = "post_tag",
joinColumns = @JoinColumn(name = "post_id"),
inverseJoinColumns = @JoinColumn(name = "tag_id")
)
private Set<Tag> tags = new HashSet<>();
public void addTag(Tag tag) {
tags.add(tag);
tag.getPosts().add(this);
}
public void removeTag(Tag tag) {
tags.remove(tag);
tag.getPosts().remove(this);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Post)) return false;
return id != null && id.equals(((Post) o).getId());
}
@Override
public int hashCode() {
return 31;
}
}
@Entity(name = "Tag")
@Table(name = "tag")
public class Tag {
@Id
@GeneratedValue
private Long id;
@NaturalId
private String name;
@ManyToMany(mappedBy = "tags")
private Set<Post> posts = new HashSet<>();
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Tag tag = (Tag) o;
return Objects.equals(name, tag.name);
}
@Override
public int hashCode() {
return Objects.hash(name);
}
}
tags
Объединение в Post
сущности только определяет PERSIST
и MERGE
каскадные типов. Как объясняется в этой статье , изменение REMOVE
состояния объекта не имеет смысла для@ManyToMany
ассоциации JPA, поскольку он может вызвать удаление цепочки, которое в конечном итоге приведет к стиранию обеих сторон ассоциации.
- Как объясняется в этой статье , служебные методы добавления / удаления являются обязательными, если вы используете двунаправленные ассоциации, чтобы вы могли убедиться, что обе стороны ассоциации синхронизированы.
Post
Объект использует идентификатор сущности для равенства , поскольку в нем отсутствует какой - либо уникальный бизнес - ключ. Как объясняется в этой статье , вы можете использовать идентификатор объекта для равенства, если убедитесь, что он остается согласованным при всех переходах состояния объекта .
Tag
Объект имеет уникальный бизнес - ключ , который отмечается с Hibernate-специфической @NaturalId
аннотацией. В этом случае уникальный бизнес-ключ является лучшим кандидатом для проверки равенства .
mappedBy
Атрибут posts
ассоциации в Tag
отметках сущностей , что в этом двунаправленной связи, то Post
предприятие имеет ассоциацию. Это необходимо, поскольку только одна сторона может владеть отношением, и изменения распространяются в базу данных только с этой конкретной стороны.
Set
Является предпочтительным, как использование List
с @ManyToMany
менее эффективным.
Для получения дополнительных сведений о наилучшем способе сопоставления @ManyToMany
отношений с JPA и Hibernate ознакомьтесь с этой статьей .