一对多
一对多的表关系如下所示:
在关系数据库系统中,一对多表关系基于子表中的 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<>();
//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),用于同步双向关联的双方。
您应该在使用双向关联时提供这些方法,否则,您将面临very subtle state propagation issues 的风险。
应避免使用单向 @OneToMany 关联,因为它的效率低于使用 @ManyToOne 或双向 @OneToMany 关联。
一对一
一对一的表关系如下:
在关系数据库系统中,一对一的表关系基于子表中的 Primary Key 列链接两个表,该列也是引用父表行的 Primary Key 的 Foreign Key。
因此,我们可以说子表与父表共享Primary Key。
在上表图中,post_details 表中的id 列与post 表idPrimary Key 列也有Foreign Key 关系:
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;
}
//Getters and setters omitted for brevity
}
这样,id 属性同时用作主键和外键。您会注意到@Id 列不再使用@GeneratedValue 注释,因为标识符填充了post 关联的标识符。
多对多
多对多表关系如下:
在关系数据库系统中,多对多表关系通过一个子表链接两个父表,该子表包含两个引用两个父表的Primary Key 列的Foreign Key 列。
在上面的表格图中,post_tag 表中的post_id 列也与post 表id Primary Key 列有Foreign Key 关系:
ALTER TABLE
post_tag
ADD CONSTRAINT
fk_post_tag_post_id
FOREIGN KEY (post_id) REFERENCES post
并且,post_tag 表中的tag_id 列与tag 表ID Primary Key 列具有Foreign 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);
}
}
-
Post 实体中的tags 关联仅定义PERSIST 和MERGE 级联类型。 REMOVE 实体状态转换对于 @ManyToMany JPA 关联没有任何意义,因为它可能触发链删除,最终会擦除关联的双方。
- 如果您使用双向关联,则添加/删除实用程序方法是强制性的,这样您就可以确保关联的双方保持同步。
-
Post 实体使用实体标识符表示相等,因为它缺少任何唯一的业务密钥。只要确保它在所有实体状态转换中保持一致,就可以使用实体标识符来实现相等性。
-
Tag 实体具有唯一的业务密钥,该密钥用 Hibernate 特定的 @NaturalId 注释进行标记。在这种情况下,唯一的业务密钥是 the best candidate for equality checks。
-
Tag 实体中posts 关联的mappedBy 属性标志着,在这种双向关系中,Post 实体拥有该关联。这是必需的,因为只有一方可以拥有关系,并且更改只会从这一特定方传播到数据库。
-
Set 是首选,因为使用 List 和 @ManyToMany 效率较低。