jpa 엔티티 일대다 : 다대일 관계 생성시 흔하게 일어나는 순환 참조문제,
이전 프로젝트에서는 뭣도 모르고 @jsonignore로 처리했었는데, 이게 왜 일어나는지 공부해보니, 좋지 않은 방법이였다는 걸 깨달았다,..
JPA에서 순환참조 문제는 이제 알고보니까는, 엔티티를 조회할 떄 발생하는 게 아니라
entity를 json으로 변환할 떄, 즉, Entity To Json serialize할 때 (보통 controller 단)에서 일어나는 문제였다.
Entity를 Json으로 변환하면서, 연관된 객체를 다시 Json으로 변환하고, 이 변환 과정에서, 다시 연관된 객체의 연관된 객체를 참조... 참조 지옥,,, 무한 참조,,, 크기가 펑펑펑,,
스프링 무한 순환 참조 문제 해결 방법
@jsonignore (지양함)
- 간단하게 순환참조를 해결할 수 있지만, 연관관계 복잡하게 얽혀있으면 문제가 많음 (사용해야할 떄, 사용하지 못하기도 함)
- 어노테이션이 붙은 객체를 Json 직렬화를 하지 않음
DTO (지향)
- 무한 순환 참조는 객체를 JSON 형태로 변환하는 직렬화 과정에서 발생
- JSON으로 직렬화 할 객체에 연관 관계의 필드를 넣지 않음으로 문제 해결 가능
- 즉, DTO에, 필요한 필드만 만들어서 담아, 순환참조가 일어나지 않도록 하자
무한 순환 참조 에러 코드
Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: Infinite recursion (StackOverflowError);
이런식으로 계속 뭔가 참조되는 볼수 있다.
(StackOverflowError) (through reference chain: org.hibernate.collection.internal.PersistentBag[0]->com.sparta.weeklytestspring.domain.PostComment["user"]->com.sparta.weeklytestspring.domain.User["posts"]->org.hibernate.collection.internal.PersistentBag[0]->com.sparta.weeklytestspring.domain.Post["comments"]->org
Post 엔티티
package com.sparta.weeklytestspring.domain;
import com.sparta.weeklytestspring.dto.SetArticleDto;
import lombok.*;
import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.Where;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
@AllArgsConstructor
@NoArgsConstructor@Setter
@Getter
@Builder
@Entity
@Where(clause = "deleted_at IS NULL")
@SQLDelete(sql = "UPDATE post SET deleted_at = CURRENT_TIMESTAMP where id = ?")
public class Post extends Timestamped {
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Id
private Long idx;
@Column(nullable = false)
private String title;
@Column(nullable = false)
private String content;
//게시글에 달린 댓글 목록
@OneToMany(mappedBy = "post", cascade = CascadeType.ALL)
private List<PostComment> comments;
//게시글에 달린 좋아요 목록
@OneToMany(mappedBy = "post", cascade = CascadeType.ALL)
private List<PostLike> postLikes = new ArrayList<>();
//작성한 유저
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name="user_idx", nullable = false)
private User user;
}
엔티티를 바로 참조함(모든 필드를)
package com.sparta.weeklytestspring.dto;
import com.sparta.weeklytestspring.domain.PostComment;
import com.sparta.weeklytestspring.domain.PostLike;
import com.sparta.weeklytestspring.domain.UserRole;
import lombok.Getter;
import lombok.Setter;
import java.time.LocalDateTime;
import java.util.List;
@Setter
@Getter
public class GetPostDto {
@Getter
@Setter
public static class Response {
private Long idx;
private String title;
private String content;
private List<PostComment> comments;
private List<PostLike> postLikes;
private User user;
private LocalDateTime createdAt;
}
}
참조하는 엔티티의 필요한 부분만, 클래스로 만들어 순환참조가 일어나지 않도록 함
package com.sparta.weeklytestspring.dto;
import com.sparta.weeklytestspring.domain.PostComment;
import com.sparta.weeklytestspring.domain.PostLike;
import com.sparta.weeklytestspring.domain.UserRole;
import lombok.Getter;
import lombok.Setter;
import java.time.LocalDateTime;
import java.util.List;
@Setter
@Getter
public class GetPostDto {
@Getter
@Setter
public static class Response {
private Long idx;
private String title;
private String content;
private List<Comment> comments;
private List<Like> postLikes;
private User user;
private LocalDateTime createdAt;
}
@Getter
@Setter
public static class Comment {
private Long idx;
private String comment;
}
@Getter
@Setter
public static class Like {
private Long idx;
private String comment;
}
@Getter
@Setter
public static class User {
private String username;
private UserRole account_type;
}
}
*참고
https://yeon-kr.tistory.com/170
'Spring > Spring Boot' 카테고리의 다른 글
Spring JWT Refresh Token - 인증 인가의 흐름 (4) | 2022.08.17 |
---|---|
[Spring] 무한스크롤 구현 및 성능 개선 하기 - No Offset 페이지네이션 (6) | 2022.08.12 |
[Spring] 스프링 엔티티 삭제 시점 히스토리 기록하기 (Soft Delete) (3) | 2022.04.20 |
[JPA] Fetch join 과 Join 차이점 (0) | 2022.04.20 |
[Spring] 스프링 부트 JPA 페이징 성능 개선 - querydsl 페이지네이션(오프셋 페이징, 커서 페이징, querydsl 정렬) (0) | 2022.04.10 |