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 |
댓글