这是一个非常常见的问题,因此此答案基于我在博客上写的这篇文章。
一对多
一对多表关系如下所示:
在关系数据库系统中,一对多表关系基于Foreign Key
子级中引用Primary Key
父表行的的列链接两个表。
在上面的表图post_id
中,post_comment
表中的列Foreign Key
与post
表id Primary Key
列具有关系:
ALTER TABLE
post_comment
ADD CONSTRAINT
fk_post_comment_post_id
FOREIGN KEY (post_id) REFERENCES post
@ManyToOne批注
映射一对多表关系的最佳方法是使用@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
批注,并不意味着对于每个一对多的数据库关系,这应该是默认选项。集合的问题在于我们只能在子记录的数量相当有限的情况下使用它们。
映射@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
关联效率低。
有关映射@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
实体标识符来获取实体。
映射如下所示:
[代码语言=“ java”] @Entity(name =“ PostDetails”)@Table(name =“ post_details”)公共类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
} [/ code]
这样,该id
属性既可以用作主键又可以用作外键。您会注意到,该@Id
列不再使用@GeneratedValue
注释,因为该标识符已用post
关联的标识符填充。
有关映射@OneToOne
与JPA和Hibernate关系的最佳方法的更多详细信息,请参阅本文。
多对多
多对多表关系如下所示:
在关系数据库系统中,多对多表关系通过子表链接两个父表,该子表包含Foreign Key
引用Primary Key
两个父表的列的两列。
在上面的表图post_id
中,post_tag
表中的列也Foreign Key
与post
表ID 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
使用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 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<>();
//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
级联类型。如本文所述,对于JPA关联,REMOVE
实体状态转换没有任何意义,@ManyToMany
因为它可能触发链删除,最终删除关联的两面。
- 如本文所述,如果您使用双向关联,则添加/删除实用程序方法是强制性的,这样您可以确保关联的两侧同步。
- 该
Post
实体使用的相等实体标识符,因为它没有任何独特的业务重点。如本文所述,只要确保实体标识符在所有实体状态转换之间保持一致,就可以使用实体标识符进行相等性处理。
- 该
Tag
实体具有唯一的业务密钥,该密钥带有特定于Hibernate的@NaturalId
注释。在这种情况下,唯一的业务密钥是进行平等检查的最佳人选。
- 实体中关联的
mappedBy
属性标记为,在这种双向关系中,实体拥有关联。这是必需的,因为只有一方可以拥有关系,并且更改仅从该特定方传播到数据库。posts
Tag
Post
- 的
Set
是优选的,如使用List
具有@ManyToMany
效率较低。
有关映射@ManyToMany
与JPA和Hibernate关系的最佳方法的更多详细信息,请参阅本文。