전 API 반환타입을 대부분 DTO를 따로 만들어서 지정합니다. 때문에 DTO 내부에
Entity To Dto 메소드를 만들어 놓습니다. 여기서 list에 대한 처리를 할 때 주의사항을 몇 가지 소개하겠습니다.
Entity에서 List는 대부분 ManyToOne 관계의 Many 객체입니다. 따라서 일반 필드와는 달리 멤버에 One의 객체를 가지고 있어 얼마든지 One을 호출할 수 있는 상태입니다. 이 개념을 가지고 다음 코드를 확인하겠습니다.
public static PostDto entityToDto(Post post){
PostDto build = PostDto.builder()
.title(post.getTitle())
.content(post.getContent())
.memberId(post.getMember().getId())
.memberName(post.getMember().getUsername())
.build();
if(post.getCommentList() != null){
build = build.toBuilder().commentList(post.getCommentList()).build();
}
if(post.getImage()!=null){
build=build.toBuilder().frontImage(post.getImage().getFileUrl()).build();
}
return build;
}
일반 필드 변수처럼 list를 똑같이 매핑했다고 볼 수 있겠지만 이렇게 매핑하면 에러가 발생합니다. 바로 postDto의 commentList 필드에 post 객체의 필드를 직접 매핑했기 때문이죠. 이 경우를 순환 참조라고 합니다.
순환 참조린?
: 객체 A를 참조하고 있는 객체 B를 조회하는 과정에서 객체 B역시 객체 A를 참조하고 있어 무한 참조가 발생 (에러 : stackoverflow)
이 무한 참조가 발생하는 구간은 바로 데이터 직렬화 과정입니다.
직렬화
일반적으로 Web에서 객체를 반환할 떄는 JSON 형태로 직렬화하여 반환합니다.
이 때 객체가 참조하고 있는 객체를 조회하여 모두 직렬화를 하기 때문에 참조 관계를 신경써서 반환해야 합니다.
이를 해결하려면
class Post {
@OneToMany(mappedBy = "post")
@JsonManagedReference
private List<PostComment> comments;
}
class PostComment {
@ManyToOne
@JoinColumn(name = "post_id")
@JsonBackReference
private Post post;
}
이렇게 태그를 선언하면 참조를 방지할 수 있는데 사실 Dto를 만드는게 훨씬 유지보수가 좋다고 생각합니다.
DTO에는 참조관계가 없기 때문에 각 객체들을 DTO로 변환해주면 참조를 막을 수 있습니다.
class PostDto {
private Long id;
private String title;
private List<PostCommentDto> comments;
}
class PostCommentDto {
private Long id;
private String comment;
}
list가 null 이어도 최소한 초기화를 하고 넘겨줘야 좋다고 생각합니다.
(반복적인 null 체크 문제 ....)
public static PostDto entityToDto(Post post){
List<PostCommentDto> postCommentDtos = post.getCommentList() != null
? post.getCommentList().stream().map(PostCommentDto::entityToDto).collect(Collectors.toList())
: new ArrayList<>();
PostDto build = PostDto.builder()
.title(post.getTitle())
.content(post.getContent())
.memberId(post.getMember().getId())
.memberName(post.getMember().getUsername())
.commentList(postCommentDtos)
.build();
if(post.getImage()!=null){
build=build.toBuilder().frontImage(post.getImage().getFileUrl()).build();
}
return build;
}
최종적으로 이렇게 반환했습니다.