이 전 코드의 Entity 내의 필드를 방문하는 과정에서
BoardEntity 클래스에 게시글 리스트를 id 내림차순으로 정렬되는 postList로 필드에 추가했고, PostEntity타입 리스트로 선언했다.
public class BoardEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String boardName;
private String status;
@Transient
private List<PostEntity> postList = List.of();
}
PostEntity 역시 같은 방식으로 답변 리스트를 가지도록 replyList를 필드에 추가했다.
public class PostEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Long boardId;
private String userName;
private String password;
private String email;
private String status;
private String title;
@Column(columnDefinition = "TEXT")
private String content;
private LocalDateTime postedAt;
@Transient
private List<ReplyEntity> replyList = List.of();
}
이 또한 ReplyEntity타입의 리스트로 선언했다.
@ManyToOne
@JsonIgnore
@ToString.Exclude
private BoardEntity board;
각 boardEntity의 게시글로 post가 등록되도록 boardId를 BoardEntity 타입으로 변경했고 한 board에 여러 post가 등록될 수 있도록 연관관계를 @ManyToOne 어노테이션을 통해 설정했다.
@ManyToOne
@ToString.Exclude
@JsonIgnore
private PostEntity post;
reply에서도 postId를 PostEntity 타입으로 변경했다.
이 과정에서 문제가 일어나게 된다.
BoardEntity에 있는 PostEntity가 BoardEntity를 포함하고 있어 다시 BoardEntity를 방문하게 되고, postList를 참조하면 다시 BoardEntity를 방문하는 loop를 생성한다.
@JsonIgnore
@ToString.Exclude
그래서 존재하는 어노테이션이 JsonIgnore이다.
상위 테이블의 Entity에 포함된 필드가 다시 상위 테이블의 Entity를 필드로 가지고 있을 때 재방문하는 것을 무시(Ignore)하는 어노테이션이다.
Controller에서 각자의 Entity를 반환하는 것은 좋지 않다.
반환하기 위한 데이터를 Entity로 직접 전달하기보다 적절하게 데이터의 조작을 중재하는 것이 좋다.
간단하게 얘기해서 Controller에서 Service레이어에 전달하는 Entity가 직접 전해지면
Controller에 Service가 의존하게 되는 역방향 의존이 일어나게 된다.
이를 해결하기 위해서 계층간 DTO와 Converter를 이용한다.
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@ToString
@JsonNaming(value = PropertyNamingStrategies.SnakeCaseStrategy.class)
public class BoardDTO {
private Long id;
private String boardName;
private String status;
private List<PostDTO> postList = List.of();
}
@Getter
@Setter
@Builder
@ToString
@NoArgsConstructor
@AllArgsConstructor
@JsonNaming(value = PropertyNamingStrategies.SnakeCaseStrategy.class)
public class PostDTO {
private Long id;
private Long boardId;
private String userName;
private String password;
private String email;
private String status;
private String title;
private String content;
private LocalDateTime postedAt;
}
각 테이블관계마다 DTO를 만들어주고 Converter를 작성한다.
@Service
@RequiredArgsConstructor
public class BoardConverter {
private final PostConverter postConverter;
public BoardDTO toDTO(BoardEntity boardEntity){
var postList = boardEntity.getPostList()
.stream()
.map(postConverter::toDTO)
.collect(Collectors.toList());
return BoardDTO.builder()
.id(boardEntity.getId())
.boardName(boardEntity.getBoardName())
.status(boardEntity.getStatus())
.postList(postList)
.build();
}
} postList를 반환할 수 있도록 BoardEntity에 존재하는 postList를 참조함
@Service
public class PostConverter {
public PostDTO toDTO(PostEntity postEntity){
return PostDTO.builder()
.id(postEntity.getId())
.boardId(postEntity.getBoard().getId())
.userName(postEntity.getUserName())
.password(postEntity.getPassword())
.email(postEntity.getEmail())
.status(postEntity.getStatus())
.title(postEntity.getTitle())
.content(postEntity.getContent())
.postedAt(LocalDateTime.now())
.build();
}
}
데이터의 변환 과정을 Converter에서만 관리하도록한다.
이는 한 곳에서 데이터를 관리할 수 있는 장점이 있는 반면
Converter에서 오류가 나면 모든 곳에서 오류가 나는 단점도 존재한다.
Controller의 코드는 포스팅이 너무 길어져 다음 글에서 ,,