[Spring] 게시판 대댓글 구현

hyewon jeong·2023년 2월 11일
0

Spring

목록 보기
37/59

개발 진행에 따른 기록 작성(★★★★★)

1. 엔티티 재 설계

  • 댓글과 대댓글을 한 엔티티에서 구현
  • 게시글과 댓글 , 대댓글 등 연관관계없이 구현

1. 어떠한 이유로 해당 기능을 사용하였는지

게시글
|---- 댓글
|---- 댓글
|---댓글
|-- 댓글

이런 구조로 댓글에 댓글을 달수 있도록 계층구조를 구현 시도했다.

1-1 시도

  1. 시도 : commentResponse에 자식댓글을 가져와서 게시글 조회를 같이 볼 수 있게 함
  2. 결과 : 대댓글 구조는 완성됐지만, 불필요하게 하나하나의 계층구조도 반복적으로 보여지고 있었다.


1-2 시도

  1. 시도 : 반복되게 보여지는 계층구조를 없애기 위해 우선 1차원적으로 시도하기 위해 commentResposne에 있는 자식댓글을 없앴다.
  2. 결과 : 게시글에 해당 댓글을 다 가져 오긴 하지만 계층구조가 전혀보이지 않는다.

1-3 시도

  1. 시도 : 1-1, 1-2 시도 후 ,, 생각 한 결과 .. commentResponse는 자식댓글을 가지고 있게 하고, 게시글 조회할때 부모 댓글인 것만 조회하면 , 대댓글이 구현 되지 않을까?

게시글 서비스단에서 혹시 parent가 널값인것도 쿼리로 잡아주지 않을까 해서 레포지토리에 구현해봤더니 parent 널값인 댓글들만 가져온다. 또한 자식 댓글 들도 조회

  1. 결과 : 성공 🙈

    📌 InquiryService

       List<ContactComment> parentComments = contactCommentRepository. findAllByInquiryIdAndParentIsNull(id);
       return new InquiryResponse(inquiry, parentComments);
 📌 ContactCommentRepository 
 
public interface ContactCommentRepository extends JpaRepository<ContactComment,Long>{

  List<ContactComment> findAllByInquiryIdAndParentIsNull(Long id);

 
 
{
    "title": "뽀통령",
    "content": "뽀로로로",
    "username": "pororo12",
    "createdDate": "2023-02-11T16:00:46.994953",
    "modifiedDate": "2023-02-11T16:00:46.994953",
    "comments": [
        {
            "id": 1,
            "username": "pororo12",
            "comments": "코멘트",
            "createdDate": "2023-02-11T16:00:52.480822",
            "modifiedDate": "2023-02-11T16:00:52.480822",
            "children": [
                {
                    "id": 3,
                    "username": "pororo12",
                    "comments": "코멘트",
                    "createdDate": "2023-02-11T16:00:58.11333",
                    "modifiedDate": "2023-02-11T16:00:58.11333",
                    "children": []
                }
            ]
        },
        {
            "id": 2,
            "username": "pororo12",
            "comments": "코멘트",
            "createdDate": "2023-02-11T16:00:54.18415",
            "modifiedDate": "2023-02-11T16:00:54.18415",
            "children": [
                {
                    "id": 4,
                    "username": "pororo12",
                    "comments": "코멘트",
                    "createdDate": "2023-02-11T16:01:01.211186",
                    "modifiedDate": "2023-02-11T16:01:01.211186",
                    "children": []
                },
                {
                    "id": 5,
                    "username": "pororo12",
                    "comments": "코멘트",
                    "createdDate": "2023-02-11T16:01:02.291154",
                    "modifiedDate": "2023-02-11T16:01:02.291154",
                    "children": [
                        {
                            "id": 6,
                            "username": "pororo12",
                            "comments": "코멘트",
                            "createdDate": "2023-02-11T16:02:10.969203",
                            "modifiedDate": "2023-02-11T16:02:10.969203",
                            "children": [
                                {
                                    "id": 7,
                                    "username": "pororo12",
                                    "comments": "코멘트",
                                    "createdDate": "2023-02-11T16:02:47.758064",
                                    "modifiedDate": "2023-02-11T16:02:47.758064",
                                    "children": [
                                        {
                                            "id": 8,
                                            "username": "pororo12",
                                            "comments": "코멘트",
                                            "createdDate": "2023-02-11T16:02:52.327049",
                                            "modifiedDate": "2023-02-11T16:02:52.327049",
                                            "children": []
                                        }
                                    ]
                                }
                            ]
                        }
                    ]
                }
            ]
        }
    ]
}

2. 해당 기능의 코드는 어떠한 로직을 가지고 있는지

※ 입력값이 들어가면 어떠한 코드를 통해 어떠한 값으로 변화하는지

게시글 조회시
문의글 아이디를 입력하여
문의글과 해당 댓글 , 대댓글을 조회할 수 있는 로직

📌 ContactCommentRepository

public interface ContactCommentRepository extends JpaRepository<ContactComment,Long>{

 List<ContactComment> findAllByInquiryIdAndParentIsNull(Long id);

📌 Inquiry Service 단

  /**
   *  건당 문의 글 조회 시
   * @param id 문의글 아이디
   * @return  문의글 , 글에 해당하는 댓글, 대댓글
   */
  @Transactional(readOnly = true)
  @Override
  public InquiryResponse getSelectedInquiry(Long id) {
    Inquiry inquiry = inquiryRepository.findById(id).orElseThrow(
        () -> new IllegalArgumentException("해당 문의 글이 존재하지 않습니다."));
   // List<ContactComment> comments = contactCommentRepository.findAllByInquiryId(id);
    List<ContactComment> parentComments = contactCommentRepository. findAllByInquiryIdAndParentIsNull(id);
    return new InquiryResponse(inquiry, parentComments);
  }

📌 InquiryResponse 단

  private  List<ContactCommentResponse> comments;

  public InquiryResponse(Inquiry inquiry, List<ContactComment> comments) {
    this.title = inquiry.getTitle();
    this.content = inquiry.getContent();
    this.username = inquiry.getUsername();
    this.createdDate = inquiry.getCreatedDate();
    this.modifiedDate = inquiry.getModifiedDate();
    this.comments = comments.stream().map(ContactCommentResponse::new)
        .sorted(Comparator.comparing(ContactCommentResponse::getCreatedDate)).collect(Collectors.toList());

  }

📌 ContactCommentResponse 단

  private final List<ContactCommentResponse> children;
  public ContactCommentResponse(ContactComment contactComment) {
    this.id = contactComment.getId();
    this.username = contactComment.getUsername();
    this.comments = contactComment.getComments();
    this.createdDate = contactComment.getCreatedDate();
    this.modifiedDate = contactComment.getModifiedDate();
    this.children = contactComment.getChildren().stream().map(ContactCommentResponse::new)
        .sorted(Comparator.comparing(ContactCommentResponse::getCreatedDate)).collect(Collectors.toList());


  }

📌 ContactComment Entity 단

@Setter
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
public class ContactComment extends Timestamped {

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name = "id", nullable = false)
  private Long id;
  @Column(nullable = false)
  private String comments;

  @Column(nullable = false)
  private String username;


  //inquiry , user 연관관계  x  없이 구현
  private Long inquiryId;

  @ManyToOne(fetch = FetchType.LAZY)
  @JoinColumn(name = "parent_id")
  private ContactComment parent;

  @OneToMany(mappedBy = "parent", orphanRemoval = true)
  private List<ContactComment> children = new ArrayList<>();

  @Builder
  public ContactComment(String comments, Long inquiryId, String username, ContactComment parent) {
    this.comments = comments;
    this.username = username;
    this.inquiryId = inquiryId;
    this.parent = parent;
  }

  public void update(String comments) {
    this.comments = comments;
  }

}

📌 ContactComment Service 단

  @Override
  public void saveInquiryComment(Long inquiryId, CreateContactCommentRequest createContactCommentRequest,
      String username) {
   // String comments = createContactCommentRequest.getComments();
    if (!inquiryService.existsById(inquiryId)) {
      throw new CustomException(ExceptionStatus.BOARD_NOT_EXIST);
    } else {
      /**부모댓글이 있는 경우 - 대댓글 등록*/
      ContactComment parent = null;
      if (createContactCommentRequest.getParentId() != null) {
        parent = contactCommentRepository.findById(createContactCommentRequest.getParentId()).orElseThrow(
            () -> new CustomException(ExceptionStatus.BOARD_NOT_EXIST)
        );
        // 부모 댓글과 자식 댓글의 게시글 아이디가 같은지 확인
        if (!parent.getInquiryId().equals(inquiryId)) {
          throw new CustomException(ExceptionStatus.WRONG_POST_ID);
        }
        ContactComment contactComment = createContactCommentRequest.toEntity(inquiryId, username, parent);
       // ContactComment contactComment = new ContactComment(comments,inquiryId,username,parent);
        contactComment.getParent().setId(createContactCommentRequest.getParentId());
        contactCommentRepository.save(contactComment);
        /**부모댓글이 없는 경우 - 댓글 등록*/
      }else{
     //   ContactComment contactComment = new ContactComment(comments,inquiryId,username,parent);
        ContactComment contactComment = createContactCommentRequest.toEntity(inquiryId, username, parent);
        contactCommentRepository.save(contactComment);
      }
    }
  }

3. 코드를 작성하며 발견된 버그오류는 어떠한게 있었는지 그리고 어떻게 해결하였는지.

중간 중간 고치는 과정에서 ,
뭔가 잘못되었는지 대댓글을 떠나 댓글 등록은 잘되었는데 댓글등록이 안되기 시작했다.

에러의 원인은 중간 고치는 과정에서
comment entity의 값이 나도 모르는 사이에 생성자 값이 변경되어 builder 처리된부분에서 comments 값을 받아오지 않아 null값이 되어 포스트맨으로 테스트시 could not ~ SQL null ~이런 문구를 만났다.
디티오가 값을 받지 못하나? 싶어 로그 찍어보면 받아오고, 로직에서도 받아오는데 도대체 어디에서 널값이 생기는 건지 몰라
이 에러를 잡느라.. 3시간은 걸린듯
튜텨님이 아니었다면 난 아마 이거 풀때까지 놓지 못했을텐데 튜텨님께 감사하다.

이 에러를 만난 후 교훈은 ! ! 로직을 변경할때 엔티티부분도 신경써서 변경된 사항이 없는지 한번더 체크! 그리고 변경할때 조심히!

시간은 오래 걸렸지만 .. 넘나 뿌듯한것.. ㅠㅠㅎ 작은 기능이지만 나에게 소확행

개선사항

  1. @Setter 대신 parentId set 하는 방법 고안

변경전

contactComment.getParent().setId(createContactCommentRequest.getParentId());
      contactCommentRepository.save(contactComment);

변경후

      contactComment.setParent(parent);
      contactCommentRepository.save(contactComment);

setId 대신 위에서 parentId로 만들어진 parent를 set함으로 @Setter 없애고 별도로 set함

  1. 순환참조를 피하기위해 inquiryService 단에 contactCommentRepository를 직접 호출 하지 않는 방법 고안
profile
개발자꿈나무

0개의 댓글