팀 프로젝트를 진행하던 중, 한 팀원이 postman으로 api를 테스트했더니 값이 무한으로 출력된다고 말하면서 해당 부분을 아래처럼 수정했더니 정상 작동한다고 수정해도 되냐고 물었다.
@ManyToOne
@JoinColumn(name = "group_id")
private Group group;
->
@Column(name = "group_id")
private Long groupId;
@OneToMany(mappedBy = "group")
private Set<groupMember> groupMembers = new HashSet<>();
->
@OneToMany(mappedBy = "groupId")
private Set<groupMember> groupMembers;
JPA는 객체 관계 매핑을 통해 데이터베이스와 객체를 연결하는데, 해당 부분은 두 엔티티 간의 관계를 나타내는 부분이었다. 그래서 엔티티 타입인 Entity로 매핑해야 하기 때문에 Long 타입으로 변경하면, 이는 단순히 데이터베이스의 외래 키를 저장하는 것에 불과하고, 두 엔티티 간의 관계를 명시하지 않기 때문에 틀린 방법이었다.
일단 해당 문제가 왜 발생했는 지 알아보기 위해 'JPA 무한루프'라는 키워드로 검색을 해보았다. 다행히 많은 사람들이 겪고있는 문제였고 금방 해결법을 찾을 수 있었다.
해당 문제는 JPA의 순환참조에 대한 문제로 양방향 관계로 Entity를 설정할 때 두 Entity가 서로 참조를 하는 상황에서 발생할 수 있고, 양방향 관계에서 Entity를 JSON으로 직렬화할 때 주로 발생한다고 했다.
왜냐하면, A Entity가 B Entity를 참조하고 B Entity가 다시 A Entity를 참조하는 경우가 지속적으로 발생하게 되기 때문이다.
이를 해결하기 위해서는 순환참조를 방지하는 어노테이션인 @JsonIgnore 또는 @JsonBackReference, @JsonManagedReference를 사용하거나 데이터를 전송할 때 Entity가 아닌 DTO를 활용하면 된다고 했다.
각각의 어노테이션에 대한 기능을 보자면 아래와 같다.
- @JsonIgnore는 특정 필드를 JSON 직렬화 및 역직렬화 과정에서 무시하도록 지시한다. 즉, 해당 필드는 JSON 데이터에 포함되지 않으며, 역직렬화 시에도 해당 필드는 무시된다.
- @JsonBackReference와 @JsonManagedReference는 함께 사용되며, @JsonManagedReference는 직렬화 시 포함되고, @JsonBackReference는 직렬화 시 무시됩니다. 그러나 역직렬화 시에는 둘 다 사용된다.
나는 팀원에게 @JsonBackReference와 @JsonManagedReference을 사용하는 방법을 제시했고 적용 결과 문제는 해결되었다.
당시에는 간단하게 그 문제를 해결했지만 찾아보니 그런 방법들은 그다지 추천되는 방법이 아니라는 것을 알게 되었다. JSON으로 직렬화할 때 Entity를 바로 사용하는 것은 추천되지 않고 DTO 클래스를 따로 만들어 이를 데이터 전송에 사용하는 것이 좋은 방법이라는 것이다. Entity를 사용하면 안되는 이유는 아래와 같다.
- 엔티티 구조 변경 시 유지 보수가 어려워져 시간과 비용이 증가
- 필요한 데이터만 전송하기 어려움: 엔티티를 직접 반환하면 엔티티에 존재하는 모든 데이터가 반환되기 때문에 성능이나 비용적인 면에서 손실이 발생
- 순환참조 문제
- 보안문제: Entity는 DB 테이블로 매핑되기 때문에 이 정보를 공개하는 것은 문제를 발생시킬 수 있다.
즉, 순환 참조 문제에 대한 해결 뿐 아니라 대부분의 상황에서 Entity를 바로 사용하는 것 보다 Controller에서 DTO 형태로 데이터를 받아 Service Layer로 전달한 뒤 DTO를 Entity로 변환하여 로직을 수행한 후 Repository로 Entity를 전달하는 것이 더 좋은 방법이 될 것이다.
'Back-end > JPA' 카테고리의 다른 글
JPA - 0. 개념 (0) | 2023.09.07 |
---|