Spring Boot中@OneToMany與@ManyToOne幾個需要注意的問題

@OneToMany如果不加@JoinColumn,系統會自動在主從表中增加一箇中間表。

主表:

@Entity(name = "Post")
public class Post {
 
    @Id
    @GeneratedValue
    private Long id;
 
    private String title;
 
    @OneToMany(
        cascade = CascadeType.ALL, 
        orphanRemoval = true
    )
    private List<PostComment> comments = new ArrayList<>(); 
}

從表:

@Entity(name = "PostComment")
public class PostComment {
 
    @Id
    @GeneratedValue
    private Long id;
 
    private String review; 
}

如果使用下面代碼添加1條主表記錄以及3條從表記錄:

Post post = new Post("First post");
 
post.getComments().add(
    new PostComment("My first review")
);
post.getComments().add(
    new PostComment("My second review")
);
post.getComments().add(
    new PostComment("My third review")
);
 
entityManager.persist(post);

實際上系統會執行7條SQL語句

insert into post (title, id) 
values ('First post', 1)
 
insert into post_comment (review, id) 
values ('My first review', 2) 
 
insert into post_comment (review, id) 
values ('My second review', 3)
 
insert into post_comment (review, id) 
values ('My third review', 4)
 
insert into post_post_comment (Post_id, comments_id) 
values (1, 2)
 
insert into post_post_comment (Post_id, comments_id) 
values (1, 3)
 
insert into post_post_comment (Post_id, comments_id) 
values (1, 4)


這樣如果記錄比較多,將會影響到系統性能。我們可以使用@JoinColumn來避免產生中間表:

@JoinColumn(name = "post_id")

但即使是沒有中間表,系統任然會執行7條SQL語句:

insert into post (title, id) 
values ('First post', 1)
 
insert into post_comment (review, id) 
values ('My first review', 2)
 
insert into post_comment (review, id) 
values ('My second review', 3)
 
insert into post_comment (review, id) 
values ('My third review', 4)
 
update post_comment set post_id = 1 where id = 2
 
update post_comment set post_id = 1 where id =  3
 
update post_comment set post_id = 1 where id =  4

如果我們想刪除一條從表記錄

post.getComments().remove(0);

系統任然會執行2條語句:

update post_comment set post_id = null where post_id = 1 and id = 2 
delete from post_comment where id=2

要想避免這種情況,就要使用@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);
    }
}
 
@Entity(name = "PostComment")
@Table(name = "post_comment")
public class PostComment {
 
    @Id
    @GeneratedValue
    private Long id;
 
    private String review;
 
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "post_id")
    private Post post;
  
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof PostComment )) return false;
        return id != null && id.equals(((PostComment) o).id);
    }
    @Override
    public int hashCode() {
        return 31;
    }
}

這樣系統就只會產生4條SQL語句:

insert into post (title, id) 
values ('First post', 1)
 
insert into post_comment (post_id, review, id) 
values (1, 'My first review', 2)
 
insert into post_comment (post_id, review, id) 
values (1, 'My second review', 3)
 
insert into post_comment (post_id, review, id) 
values (1, 'My third review', 4)

刪除一條從表記錄

PostComment comment1 = post.getComments().get( 0 ); 
post.removeComment(comment1);

系統也只會執行1條SQL語句:

delete from post_comment where id = 2


但是使用這樣同時使用@OneToMany和@ManyToOne要注意以下幾點:

1. 在從表@ManyToOne中要使用FetchType.LAZY,否則會導致性能降低。

2. 主表中增加了2個方法,addComment和removeComment。

3. 從表重載了equals和hashCode方法。

4. 在使用Json來序列化對象時,會產生無限遞歸(Infinite recursion)的錯誤。這裏有2個解決方法:

   a. 在@ManyToOne下面使用@JsonIgnore.

   b. 在@OneToMany下面使用@JsonManagedReference,在@ManyToOne下面使用@JsonBackReference

@JsonBackReference和@JsonManagedReference:@JsonBackReference標註的屬性在序列化(serialization)時,會被忽略。@JsonManagedReference標註的屬性則會被序列化。在序列化時,@JsonBackReference的作用相當於@JsonIgnore,此時可以沒有@JsonManagedReference。但在反序列化(deserialization)時,如果沒有@JsonManagedReference,則不會自動注入@JsonBackReference標註的屬性;如果有@JsonManagedReference,則會自動注入@JsonBackReference標註的屬性。  


@JsonIgnore:直接忽略某個屬性,以斷開無限遞歸,序列化或反序列化均忽略。當然如果標註在get、set方法中,則可以分開控制,序列化對應的是get方法,反序列化對應的是set方法。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章