Springboot dto로 변활할 때 list 처리(순환 참조)

김정훈·2024년 5월 20일
0

전 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 형태로 직렬화하여 반환합니다.
이 때 객체가 참조하고 있는 객체를 조회하여 모두 직렬화를 하기 때문에 참조 관계를 신경써서 반환해야 합니다.

이를 해결하려면

1.@JsonManagedReference와 @JsonBackReference

class Post {
    @OneToMany(mappedBy = "post")
    @JsonManagedReference
    private List<PostComment> comments;
}

class PostComment {
    @ManyToOne
    @JoinColumn(name = "post_id")
    @JsonBackReference
    private Post post;
}

이렇게 태그를 선언하면 참조를 방지할 수 있는데 사실 Dto를 만드는게 훨씬 유지보수가 좋다고 생각합니다.

2.DTO로 변환

DTO에는 참조관계가 없기 때문에 각 객체들을 DTO로 변환해주면 참조를 막을 수 있습니다.

class PostDto {
    private Long id;
    private String title;
    private List<PostCommentDto> comments;
}

class PostCommentDto {
    private Long id;
    private String comment;
}

list 초기화

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;
  }

최종적으로 이렇게 반환했습니다.

profile
백엔드 개발자

0개의 댓글