
신고내역 프로젝트에서 [member - report - image] 처럼 해당 멤버가 여러 신고내역을 가지고 각 신고내역은 신고이미지를 가지는 형식으로 1:N 맵핑형식을 이용하였다.
양방향 으로 연결하여 서로를 쉽게 접근할 수 있도록 한 부분에서 특정 Member을 조회시에 stackoverflow 에러가 발생하였다..
java.lang.IllegalStateException: Cannot call sendError() after the response has been committed
로그가 뜨는것을 보면 똑같은 쿼리가 무한으로 반복되다가 stackoverflow가 발생한다.
순환 참조 (Circular reference) 로 인한 문제였다.
순환참조는 맞물리는 DI(Dependency Injection)상황에서 스프링이 어느 스프링 빈을 먼저 생성할지 결정하지 못하기 때문이다.
(김영한 강사님의 스프링 기본 강의에서 본 내용이다!)
애플리케이션을 구동하면 이제 스프링 컨테이너(IOC)는 Report 빈을 생성하기위해 Image 주입해줘야하기 때문에 Image 찾을 것이다.
하지만 Image 생성하려 하니 Report 필요해서 결국 Report 주입하기위해 Report 찾게되면서 무한 반복이 생기게 된다.
report 생성해야해 -> image 생성해야해 -> report 생성해야해 -> ...
이 상황이 무한 반복되다 보면 stack에 메소드를 쌓다가 stack 메모리가 터져버려 오류가 발생한다.
순환 참조를 방지하기 위한 방법은 여러 가지가 있다!
필자는 @JsonManagedReference, @JsonBackReference 를 사용하여 해결하였다.
@JsonManagedReference 은 부모 클래스, @JsonBackReference 는 자식 클래스에 추가해주면 순환 참조를 막을 수 있다.
Report.java
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@Builder
@Table(name = "report")
@Getter
public class Report extends BaseTimeEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "report_id", nullable = false)
private Long reportId;
@Column(nullable = false, length = 40)
private String title;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id")
private Member member;
@JsonManagedReference -> 부모클래스에 설정
@OneToMany(mappedBy = "report", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Image> imageLink = new ArrayList<>();
}
Image.java
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@Builder
@Table(name = "image")
@Getter
public class Image extends BaseTimeEntity {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "image_id")
@Schema(type = "Long" ,description = "이미지 고유 ID", example = "3")
private Long imageId;
@JsonBackReference -> 자식클래스에 설정
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "report_id")
private Report report;
@Column(name = "image_Link")
private String imageLink;
}
@JsonManagedReference, @JsonBackReference 를 이용하여 문제를 해결했지만 이 방법 외에도 여러 해결방안이 있다.
-> https://dev-coco.tistory.com/133 이 블로그에서 잘 나와있다.
또한 DI에서 생성자 주입을 권장한다!
1. 생성자 주입
2. 필드 주입
3. 수정자 주입
생성자 주입은 생성자에서 의존관계 주입이 일어나기 때문에
즉 객체가 생성 될 때 의존 객체의 null 여부를 검사하므로
컴파일시에 오류를 발생시켜 런타임시에 오류가 발생하는 참사를 미연에 방지해주기 때문이다.