(TIL) 20220629, 스프링 Jackson error

2022. 6. 29. 03:31TIL(Today I learned)

반응형

🏴󠁩󠁤󠁪󠁷󠁿Facts(한 것) & Findings(배운 것)


스프링 과제를 진행하다가 infinite recurision(stack overflow)에러를 만났다.

 

이는 Jackon 과 객체 연관관계 때문에 발생한 일인데,

 

어떻게 해결해야할지 몰라서 구글링을 했고 결국에는 방법을 찾았다.

 

이렇게 one to many의 관계 혹은 Many to one 관계에서 다음과 같이 사용할 수 있다.

 

import com.fasterxml.jackson.annotation.JsonManagedReference;
import lombok.*;
import org.hibernate.annotations.GenericGenerator;

import javax.persistence.*;
import javax.validation.constraints.NotNull;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

@Getter
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PUBLIC)
@Entity
@Builder
@Table(name = "USER", indexes = {
        @Index(name = "idx_user", columnList = "user_id")
})
public class User extends BaseTimeEntity {
    @Id
    @GeneratedValue(generator = "UUID")
    @Column(name = "USER_ID", nullable = false, columnDefinition = "BINARY(16)")
    @GenericGenerator(
            name="UUID",
            strategy = "org.hibernate.id.UUIDGenerator"
    )
    private UUID id;

    @OneToMany(mappedBy = "user")
    @Builder.Default
    @JsonManagedReference
    @NotNull
    private List<Review> reviewList = new ArrayList<>();

    @Column(name = "POINT", nullable = false)
    @Builder.Default
    @NotNull
    private Integer point = 0;

    public User(UUID id, List<Review> reviewList) {
        this.id = id;
        this.reviewList = reviewList;
        this.point = 0;
    }

    public void addReview(Review review) {
        this.reviewList.add(review);

        if(review.getUser() != this) {
            review.setUser(this);
        }
    }

    public void setPoint(Integer point) {
        this.point = point;
    }
}

 

import com.fasterxml.jackson.annotation.JsonBackReference;
import com.fasterxml.jackson.annotation.JsonManagedReference;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.GenericGenerator;

import javax.persistence.*;
import javax.validation.constraints.NotNull;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

@NoArgsConstructor(access = AccessLevel.PUBLIC)
@Getter
@Entity
@Table(name = "REVIEW", indexes = {@Index(name = "idx_review", columnList = "review_id, user_id, place_id")})
public class Review extends BaseTimeEntity {
    @Id
    @GeneratedValue(generator = "UUID")
    @Column(name = "REVIEW_ID", nullable = false, columnDefinition = "BINARY(16)")
    @GenericGenerator(
            name="UUID",
            strategy = "org.hibernate.id.UUIDGenerator"
    )
    private UUID id;

    @NotNull
    @Column(name = "CONTENT")
    private String content;

    @ManyToOne
    @NotNull
    @JoinColumn(name = "USER_ID")
    @JsonBackReference
    private User user;

    @ManyToOne
    @NotNull
    @JoinColumn(name = "PLACE_ID")
    @JsonBackReference
    private Place place;

    @OneToMany(fetch = FetchType.EAGER, mappedBy = "review")
    @JsonManagedReference
    @NotNull
    private List<AttachedPhoto> attachedPhotoList = new ArrayList<>();

    @Builder
    public Review(UUID id, String content, User user, Place place, List<AttachedPhoto> attachedPhotoList) {
        this.id = id;
        this.content = content;
        this.user = user;
        this.place = place;
        this.attachedPhotoList = attachedPhotoList;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public void setUser(User user) {
        this.user = user;

        if(!user.getReviewList().contains(this)) {
            user.getReviewList().add(this);
        }
    }

    public void setPlace(Place place) {
        this.place = place;

        if(!place.getReviewList().contains(this)) {
            place.getReviewList().add(this);
        }
    }

    public void addPhoto(AttachedPhoto photo) {
        this.attachedPhotoList.add(photo);

        if(photo.getReview() != this) {
            photo.setReview(this);
        }
    }

    public void setAttachedPhotoList(List<AttachedPhoto> photoList) {
        this.attachedPhotoList = photoList;
    }
}

Many 위에는 JsonManagedReference를 붙여주고

 

참조하는 One 위에는 JsonBackRefernce를 붙여주면 문제가 말끔하게 해결된다.

 

여기까지는 이 문제의 해결 방법이고 이 문제가 왜 발생했는지를 알아야 한다.

 

이 문제는 어떻게보면 간단한데, 쉽게 생각하기 어렵다.

 

위의 코드를 예로 들면 Review 인스턴스를 생성할 때, User인스턴스도 함께 생성된다.(연관관계 매핑에 의해)

 

User객체는 여러개의 Review객체를 포함하고 있다.

 

하나 이상의 객체를 포함하고 있기 때문에 User객체와 Revie객체는 직렬화를 시도할 것이고,

 

이 시도가 실패하면 다시 또 시도하고, 시도하고 무한히 반복하게 된다.

 

그렇기 때문에 stack overflow가 발생하게 된다.

 

이를 해결하기 위해서 Jackson에게 양방향이라고 알려주는 어노테이션이 필요한 것이다.

 

내가 해결책을 찾은 원문을 첨부하겠다.

 

 

 

Spring Boot and Jackson: How to Get Around That Infinite Recursion or StackOverflow Problem | Carey Development

Having a problem using Jackson to serialize your Java objects? Getting something like "Infinite recursion (StackOverflowError)?" It's probably because you're dealing with a bidirectional relationship. Fortunately, it's easy to fix the problem you're experi

careydevelopment.us

🏴󠁩󠁤󠁪󠁷󠁿Affirmation(자기선언)


  • 매일 알고리즘 문제 1문제 이상 풀기 -> 성공

 

🏴󠁩󠁤󠁪󠁷󠁿여담


 

반응형