一对多
一对多表关系如下所示:
在关系数据库系统中,一对多表关系基于子表中引用父表中一条记录的主键的外键列将两个表关联起来。
在上面的图表中,post_comment表中的post_id列与post表id主键列有外键关系:
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集合的问题是,我们只能在它们的元素计数相当低时使用它们。
映射一个@ManyToOne关联的最好方法是依靠@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),用于同步双向关联的双方。
当您使用双向关联时,您应该提供这些方法,否则,您将面临非常微妙的状态传播问题。
要避免使用单向的@ManyToOne关联,因为它比使用@ManyToOne或双向的@OneToMany关联效率低。
一对一的
一对一的表关系如下所示:
在关系数据库系统中,一对一的表关系基于子表中的Primary Key列链接两个表,Primary Key也是引用父表行的Primary Key的外键。
因此,我们可以说子表与父表共享主键。
在上面的图表中,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;
}
//Getters and setters omitted for brevity
}
这样,id属性既是主键又是外键。您将注意到@Id列不再使用@GeneratedValue注释,因为标识符是由帖子关联的标识符填充的。
多对多
多对多表关系如下所示:
在关系数据库系统中,多对多表关系通过一个包含两个外键列的子表链接两个父表,该外键列引用两个父表的主键列。
在上面的图表中,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列与标签表id主键列有外键关系:
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<>();
//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);
}
}
The tags association in the Post entity only defines the PERSIST and MERGE cascade types. The REMOVE entity state transition doesn't make any sense for a @ManyToMany JPA association since it could trigger a chain deletion that would ultimately wipe both sides of the association.
The add/remove utility methods are mandatory if you use bidirectional associations so that you can make sure that both sides of the association are in sync.
The Post entity uses the entity identifier for equality since it lacks any unique business key. You can use the entity identifier for equality as long as you make sure that it stays consistent across all entity state transitions.
The Tag entity has a unique business key which is marked with the Hibernate-specific @NaturalId annotation. When that's the case, the unique business key is the best candidate for equality checks.
The mappedBy attribute of the posts association in the Tag entity marks that, in this bidirectional relationship, the Post entity owns the association. This is needed since only one side can own a relationship, and changes are only propagated to the database from this particular side.
The Set is to be preferred, as using a List with @ManyToMany is less efficient.