Один ко многим
Отношения между таблицами "один-ко-многим" выглядят так:
В системе реляционной базы данных отношение таблиц «один ко многим» связывает две таблицы на основе столбца 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;
}
Использование аннотации JPA @OneToMany
Тот факт, что у вас есть возможность использовать аннотацию @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<>();
//Constructors, getters and setters removed for brevity
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
ассоциации.
Один к одному
Связь таблицы один-к-одному выглядит следующим образом:
В системе реляционной базы данных взаимно-однозначное отношение таблиц связывает две таблицы на основе 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;
}
//Getters and setters omitted for brevity
}
Таким образом, свойство id
служит как первичным, так и внешним ключом. Вы заметите, что столбец @Id
больше не использует аннотацию @GeneratedValue
, поскольку идентификатор заполняется идентификатором ассоциации post
.
Многие-ко-многим
Отношения таблицы многие-ко-многим выглядят следующим образом:
В системе реляционной базы данных отношение таблиц «многие ко многим» связывает две родительские таблицы через дочернюю таблицу, которая содержит два 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
идентификатора таблицы Primary Key
:
ALTER TABLE
post_tag
ADD CONSTRAINT
fk_post_tag_tag_id
FOREIGN KEY (tag_id) REFERENCES tag
Использование сопоставления JPA @ManyToMany
Вот как вы можете сопоставить связь таблицы 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<>();
//Getters and setters ommitted for brevity
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 getClass().hashCode();
}
}
@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<>();
//Getters and setters ommitted for brevity
@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
имеет уникальный бизнес-ключ, помеченный аннотацией @NaturalId
для Hibernate. В этом случае уникальный бизнес-ключ является лучшим кандидатом для проверки равенства .
- Атрибут
mappedBy
ассоциации posts
в сущности Tag
отмечает, что в этой двунаправленной связи сущность Post
владеет ассоциацией. Это необходимо, поскольку только одна сторона может владеть отношением, и изменения распространяются в базу данных только с этой конкретной стороны.
- Предпочтительно использовать
Set
, поскольку использование List
с @ManyToMany
менее эффективно.
person
Vlad Mihalcea
schedule
16.05.2020