
1. 문제의 상황
Issue 와 Comment 는 1:N의 관계이다. 따라서 Comment의 코드를 보면 다음과 같이 Lazy 로딩이 걸려있다.
▶ Comment
@Getter
@Entity
@EqualsAndHashCode(of = "id")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Comment extends BaseTimeEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "comment_id")
    private Long id;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id")
    private User user;
    
    // 생략...
}
문제는 내가 Issue에 Comment를 추가 한 후, CommentDto를 통해 해당 정보를 반환하려 할 때 발생한다.
우선 해당 CommentDto는 다음과 같다.
▶ CommentDto
@Getter
@RequiredArgsConstructor
public class CommentDto {
    private final Long id;
    private final User user;
    private final String description;
    private final String avatarUrl;
    private final LocalDateTime createdDateTime;
    public static CommentDto of(Comment comment) {
        return new CommentDto(comment.getId(), comment.getUser(), comment.getDescription(), comment.getUser().getAvatarUrl(), comment.getCreatedDateTime());
    }
}
뒤에서 해결하겠지만, 위 코드는 User 도메인을 직접 반환한다는 문제가 도사리고 있다.
Entity를 직접 반환하는 것 은 좋지 못하다, DTO로 바꿔서 반환시켜야 한다.
Issue에 Comment를 추가하고 DTO로 변환하여 반환하는 코드는 다음과 같다.
▶ CommentService
public class CommentService {
    public CommentDto add(Long issueId, String comment, String userEmail) {
        // 생략...
        Comment newComment = Comment.builder()
                .description(comment)
                .issue(findIssue)
                .user(findUser)
                .build();
        findIssue.addComment(newComment);
        Issue savedIssue = issueRepository.save(findIssue);
        return CommentDto.of(savedIssue.getLastComment());
    }
}
CommentDto.of()의 인자로 넘어가는 comment에는 User가 초기화되있지 않다.
FetchType이 Lazy라 실제 User가 아닌, Proxy(User$HibernateProxy$wEvbAcCi["hibernateLazyInitializer"])가 담겨있다.
따라서, 해당 Proxy를 Serializer 시키려 하니 문제가 발생한 것 이다.
2. 해결 방법
2-1) application.properties에 spring.jackson.serialization.fail-on-empty-beans=false 추가하기
다음과 같이 설정에 추가해주자.
spring.jackson.serialization.fail-on-empty-beans=false
다만 이 방식은 문제의 근본을 해결한 방식이 아니다.

위 결과처럼 반환이 되기는 하는데, 우리가 축가한 적 없는 hibernateLazyInitializer 라는 필드가 생기게 된다.
2-2) Dto에서 초기화 해주기
개인적으로 Dto를 통해 초기화 시키는 방식을 선호한다.
우선 다음과 같이 UserResponseDto를 만들어보자!
▶ UserResponseDto
@Getter
@NoArgsConstructor
public class UserResponseDto {
    private String userName;
    private String avatarUrl;
    private UserResponseDto(String userName, String avatarUrl) {
        this.userName = userName;
        this.avatarUrl = avatarUrl;
    }
    public static UserResponseDto of(User user) {
        return new UserResponseDto(user.getUserName(), user.getAvatarUrl());
    }
}
이를 사용하여 CommentDto를 변경해보자.
▶ CommentDto
@Getter
@RequiredArgsConstructor
public class CommentDto {
    private final Long id;
    private final UserResponseDto userResponseDto;
    private final String description;
    private final String avatarUrl;
    private final LocalDateTime createdDateTime;
    public static CommentDto of(Comment comment) {
        return new CommentDto(comment.getId(),
                UserResponseDto.of(comment.getUser()),
                comment.getDescription(),
                comment.getUser().getAvatarUrl(),
                comment.getCreatedDateTime()
        );
    }
}
dto 내부에서 User를 변환하면서 초기화 시키고 있다. 따라서 정상적으로 반환할 수 있게되었다.
결과는 다음과 같이 나온다.

또한 이전과 달리 User Entity를 직접 반환하여 모든 정보를 공개하는 것 이 아니라, 내가 원하는 User의 정보만 반환하도록 되었다.
'BackEnd > JPA' 카테고리의 다른 글
| [JPA] Soft Delete 자동 처리하기 (0) | 2022.11.09 | 
|---|---|
| [JPA] Open Session In View 더 깊게 (0) | 2022.09.11 | 
| [JPA] JPA metamodel must not be empty! (0) | 2022.07.28 | 
| [JPA] 일급 컬렉션 (0) | 2022.07.17 | 
| [JPA] SpringBoot 2.5 이후부터 data.sql 초기화 시점 (0) | 2022.06.06 | 
			
			
				
댓글