user 정보를 front로 보내는 과정에서 위와 같은 오류 발생
프론트로 데이터를 보내기 위해 json으로의 변환이 필요한데
변환과정 중에 순환참조가 발생해서 오류가 발생한 것
💯 순환참조란 ?
- JPA에서 양방향으로 연결된 엔티티를 JSON 형태로 직렬화하는 과정에서, 서로의 정보를 계속 순환하며 참조하여 StackOverflowError를 발생시키는 현상
- A Entity(이하 A)와 B Entity(이하 B)가 양방향으로 연결된 상태
- A를 JSON으로 직렬화하기 위해 A가 참조하고 있는 B를 조회
- B를 조회하는 과정에서 B가 참조하고 있는 A를 조회
- A를 조회하는 과정에서 A가 참조하고 있는 B를 조회
- 무한반복... ☠️
💯 직렬화란?
객체/데이터를 바이트 형태로 변환하여 네트워크를 통해 송수할 수 있도록 만드는 것
해당 join 테이블이 필요없을 경우에는 간단하게 @JsonIgnore을 붙여주면 된다.
하지만 해당 테이블이 필요할 경우에는 순환참조 문제를 해결해줄 필요가 있는데 크게 @JsonMangedReference, Dto, 맵핑 재설정의 방법이 있다.
직렬화 방향을 설정해서 참조하는 방향을 하나로 정해줌으로써 순환참조 해결
연관관계 주인 반대 Entity 에 선언
정상적으로 직렬화 수행
@OneToMany(mappedBy = "product", cascade = {CascadeType.PERSIST, CascadeType.MERGE})
@JsonManagedReference
private Set<ProductUploadFile> productUploadFiles = new LinkedHashSet<>();
연관관계의 주인 Entity 에 선언
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "product_category_id", nullable = false)
@JsonBackReference
private ProductCategory productCategory;
직렬화가 되지 않도록 수행
반환을 entity로 하는 경우 순환참조가 일어나므로 반환을 dto로 하면 문제 가 해결된다.
💯 추가설명
- entity의 경우 반환시에 값을 가져오기 위해 db를 조회하는 과정에서 순환참조가 일어나지만 dto로 반환 할 경우 반환당시 정보가 이미 다 있으므로 순환참조가 일어나지 않는다.
또한 기존의 entity를 반환하는 경우, Entity 클래스를 기준으로 수많은 클래스나 비즈니스 로직들이 동작하고 있으므로 Entity를 직접 다루는 것은 이와 연관된 여러 클래스들이 영향을 받을 수 있다는 문제가 있다.
dto를 사용하는 경우 이러한 문제도 예방가능하다는 장점이 있다.
@Transactional
public ResponseEntity<?> retrieveUserInfo(CustomUser customUser) {
Users user = customUser.getUser();
UserInfoResponseDto dto = new UserInfoResponseDto(user);
// 인증된 사용자 정보
if(user != null)
return new ResponseEntity<>(dto, HttpStatus.OK);
// 인증되지 않은 사용자 정보
return new ResponseEntity<>("UNAUTHORIZED", HttpStatus.UNAUTHORIZED);
}
}
public class UserInfoResponseDto {
private Long id;
private String userId;
private String name;
private String nickname;
private String email;
private UserRoleDto userRoles;
public UserInfoResponseDto(Users user){
this.id = user.getId();
this.userId = user.getUserId();
this.name = user.getName();
this.email = user.getEmail();
this.nickname = user.getNickname();
this.userRoles = new UserRoleDto(user.getUserRoles());
}
@Getter
public static class UserRoleDto {
private final List<Role> userRole;
public UserRoleDto(List<UserRole> userRoleLists) {
this.userRole = userRoleLists.stream().map(role->role.getRole()).collect(Collectors.toList());
}
}
}
위에서 user와 userRole은 1:N 관계로 묶여 있는데 이에 따라 순환참조가 발생했다. 따라서 이 순환참조를 풀기 위해서 UserRoleDto를 따로 만들어서 그곳에서 필요한 정보를 조회해서 UserInfoResponseDto의 값을 넣어줬다.
순환참조가 양방향 맵핑에서 오는 문제인 만큼 정말로 양방향 맵핑이 필요한지 고민해서 단방향 맵핑으로 바꾼다면 순환참조 문제를 해결할 수 있다.
https://data-make.tistory.com/727