一对多
一对多的表关系如下所示:
![One-to-many](https://istack.dev59.com/Gxitd.webp)
在关系型数据库系统中,一对多的表关系基于子表中的Foreign Key列与父表中一个记录的Primary Key相关联来关联两个表。
在上面的表图中,post_comment表中的post_id列与post表的id列的Primary Key有Foreign Key关系。
ALTER TABLE
post_comment
ADD CONSTRAINT
fk_post_comment_post_id
FOREIGN KEY (post_id) REFERENCES post
@ManyToOne注解
在JPA中,映射一对多表关系的最佳方法是使用@ManyToOne
注解。
在我们的案例中,PostComment
子实体使用@ManyToOne
注解映射了post_id
外键列:
@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<>();
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
关联效率低。
一对一
一对一表关系如下:
![One-to-one](https://istack.dev59.com/WOocn.webp)
在关系型数据库系统中,一个一对一的表关系是基于子表中的一个主键列与父表行中相同的主键列关联的外键列来连接两个表的。因此,我们可以说子表与父表共享同一个主键。在上面的表图中,post_details表中的id列也与post表的id主键列有一个外键关系。
ALTER TABLE
post_details
ADD CONSTRAINT
fk_post_details_id
FOREIGN KEY (id) REFERENCES post
使用JPA的@OneToOne
和@MapsId
注解
映射@OneToOne
关系的最佳方法是使用@MapsId
。这样,您甚至不需要双向关联,因为您始终可以通过使用Post
实体标识符来获取PostDetails
实体。
映射如下:
@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
关联的标识符填充的。
多对多
多对多表关系如下所示:
![Many-to-many](https://istack.dev59.com/2tXIa.webp)
在关系型数据库系统中,多对多表关系通过一个子表连接两个父表,该子表包含两个外键列,这些列引用了两个父表的主键列。
在上面的表格图中,post_tag表中的post_id列也与post表的id主键列具有外键关系。
ALTER TABLE
post_tag
ADD CONSTRAINT
fk_post_tag_post_id
FOREIGN KEY (post_id) REFERENCES post
此外,post_tag
表中的tag_id
列与tag
表的idPrimary Key
列有一个Foreign Key
关系:
ALTER TABLE
post_tag
ADD CONSTRAINT
fk_post_tag_tag_id
FOREIGN KEY (tag_id) REFERENCES tag
使用JPA的@ManyToMany
映射
以下是如何使用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 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<>();
@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);
}
}
Post
实体中的tags
关联仅定义了PERSIST
和MERGE
级联类型。对于@ManyToMany
JPA关联,REMOVE
实体状态转换没有任何意义,因为它可能触发一次链式删除,最终会清除关联的双方。
- 如果使用双向关联,则添加/删除实用程序方法是必需的,以确保关联的双方保持同步。
Post
实体使用实体标识符作为相等性,因为缺少任何唯一业务键。只要确保在所有实体状态转换中保持一致,就可以使用实体标识符作为相等性。
Tag
实体具有带有Hibernate特定的@NaturalId
注释的唯一业务键。在这种情况下,唯一业务键是相等性检查的最佳候选。
Tag
实体中posts
关联的mappedBy
属性标记着在这个双向关系中,Post
实体拥有该关联。这是必需的,因为只有一侧可以拥有关系,并且更改仅从这个特定侧面传播到数据库。
- 使用
Set
更佳,因为使用带有@ManyToMany
的List
效率较低。
Person
拥有List<Skill> skills
时,实际上是多对多的,因为一个人可以有多个技能,而一个技能可以在许多List<Skill> skills
列表中。我认为你想写的是“在单向关系中,Skill
类将拥有Person person
”。 - mixelPerson
中存在List<Skill> skills
就自动使关系变成@Many-to-many
,因为在生成的模式中,可能会对person_id
设置unique
约束,使得一个技能只属于一个人。明白我的意思吗? - Alexander Suraphel