[JPA] 순환참조, 무한참조
- [ Backend ]/Spring DB, JPA
- 2022. 7. 26.
JPA 양방향 매핑 사용시 순환참조 문제가 발생할 수 있다.
상단 코드에서 Board와 Reply 엔티티는 일대다 양방향 매핑을 사용하고 있으며, Reply가 연관관계의 주인이 되어 fk를 가진다. 얼핏 보면 큰 문제가 없어보이지만, 다음과 같은 코드를 작성하면 문제가 발생할 수 있다.
@GetMapping("/test/board/{id}")
public Board getBoard(@PathVariable int id) {
return boardRepository.findById(id).get();
}
위 코드는 @PathVariable로 넘겨받은 id값을 바탕으로 레포지토리에서 Board객체를 찾아서 반환하는 코드이다.
여기서 jackson이라는 라이브러리가 Board객체의 getter를 사용하여 엔티티를 JSON으로 리턴하게 되는데, 이 때 양방향 연관관계로 인한 무한참조가 발생한다. Board객체는 getReply()라는 Reply에 관한 getter를 갖고 있고, Reply또한 getBoard()라는 getter를 갖고 있기 때문이다.
테스트 주소로 접속해보면 위와 같이 무한참조에 빠지고 결국 스택오버플로우 에러가 발생하는 것을 볼 수 있다.
@JsonIgnoreProperties({"..."})
이처럼 순환참조가 발생하는 것을 방지하기 위해서 위 어노테이션을 사용할 수 있다. 위의 사례를 보면 Board를 조회했는데, 조회된 Reply가 다시 Board를 조회하기 때문에 문제가 발생한다. 이럴 경우 Board의 Reply 위에
@OneToMany(mappedBy = "board", fetch = FetchType.EAGER) // 연관관계의 주인이 아니다.
@JsonIgnoreProperties({"board"}) //Reply에서 다시 Board board를 호출할경우 getter 호출 X
private List<Reply> replies;
이렇게 어노테이션을 추가하고 속성값으로 Board객체명을 넣어주면, getter로 호출된 Reply는 Board를 다시 조회하지 않게 된다. 하지만 이렇게 설정하더라도 fetchType을 LAZY로 설정했다면 또 다른 오류가 발생한다.
fetchType을 LAZY로 설정했다는 말은 실제 객체를 조회하지 않고, 프록시(가짜) 객체를 사용하여 실제 객체가 필요해지는 순간 쿼리가 나가게 한다는 것을 의미한다. 그런데 그때 json으로 뿌리려하니까 오류가 발생하는 것. 이를 해결하기 위해서는 response가 나가기 이전에 lazy loading이 이루어지도록 조회하는 코드를 넣어주면 된다.
(지연로딩이 필요할때 정보를 가져오는 것이므로, 필요하게 만들어주면 된다)
해결 방법
사실 인위적으로 만든 케이스라 그렇지, json api통신할때는 당연히 엔티티를 직접 사용하지 않고 dto를 사용하기 때문에 문제될 일은 없어 보인다. 엔티티를 바깥으로 노출하지 말자.
'[ Backend ] > Spring DB, JPA' 카테고리의 다른 글
[JPA] JDBC (0) | 2023.02.05 |
---|---|
[QueryDsl] QueryDsl 사용해보기 (0) | 2022.11.22 |
[Spring JPA] 변경감지 vs 병합 (0) | 2022.02.02 |
[JPA] 객체지향 쿼리 언어(JPQL) 이해하기 (0) | 2022.01.17 |
[JPA] 값 타입 (0) | 2022.01.15 |