[Spring] 나만의 게시판 만들기 4 - Comment

최진민·2022년 2월 12일
0

게시판 만들기

목록 보기
4/9
post-thumbnail

Model


  • Comment
    @Entity
    @Getter
    @NoArgsConstructor(access = AccessLevel.PROTECTED)
    public class Comment extends TimeEntity {
    
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        @Column(name = "comment_id")
        private Long comment_id;
    
        @Column(name = "comment_content")
        private String content;
    
        @Column(name = "comment_writer")
        private String writer;
    
        @JsonBackReference
        @ManyToOne(fetch = FetchType.LAZY)
        @JoinColumn(name = "board_id")
        private Board board;
    
        @JsonBackReference
        @ManyToOne(fetch = FetchType.LAZY)
        @JoinColumn(name = "user_id")
        private User user;
    
    }
    • 중요한 부분 ! ⇒ 한 유저여러 댓글을 작성할 수 있고, 한 게시판에는 여러 댓글이 존재할 수 있다는 것을 요구사항 설계 시 고려했습니다.
      • 이를, JPA의 연관관계를 활용했습니다. (간략한 설명은 앞선 게시물에 있습니다!)
      • Usercomments
        @Entity
        @Getter
        @NoArgsConstructor(access = AccessLevel.PROTECTED)
        public class User {
        
        		//...
        		@JsonManagedReference
            @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
            private List<Comment> comments = new ArrayList<>();
        
        		//...
        }
      • Boardcomments
        @Entity
        @Getter
        @NoArgsConstructor(access = AccessLevel.PROTECTED)
        public class Board extends TimeEntity {
        
        		//...
        		@JsonManagedReference
            @OneToMany(mappedBy = "board", cascade = CascadeType.ALL, orphanRemoval = true)
            private List<Comment> comments;
        		//...
        }
        • 연관관계를 설정할 때의 옵션 fetch, mappedBy, cascade, orphanRemoval은 JPA의 중요 개념이기 때문에 따로 공부할 필요가 있습니다! (추후 포스팅 할수도...)

Repository


  • CommentRepository
    public interface CommentRepository extends JpaRepository<Comment, Long> {
    
        List<Comment> findByBoard(Board board);
    
        Optional<Comment> findByUserAndBoard(User user, Board board);
    }
    • List<Comment> findByBoard(Board board); : 특정 게시판의 모든 댓글 조회
    • Optional<Comment> findByUserAndBoard(User user, Board board); : 특정 게시판의 특정 사용자가 작성한 단일 댓글 조회

Service


CommentFindService

@Service
@RequiredArgsConstructor
public class CommentFindService {

    private final CommentRepository commentRepository;
    private final BoardFindService boardFindService;
    private final UserFindService userFindService;

    @Transactional(readOnly = true)
    public List<Comment> findAllCommentsInBoard(Long boardId) {
        Board board = boardFindService.findById(boardId);
        return commentRepository.findByBoard(board);
    }

    @Transactional(readOnly = true)
    public Comment findCommentByUserAndBoard(Long userId, Long boardId) {
        User user = userFindService.findById(userId);
        Board board = boardFindService.findById(boardId);
        return commentRepository.findByUserAndBoard(user, board)
                .orElseThrow(() -> new NotFoundCommentException(String.format("There is no comment")));
    }
}
  • 댓글 조회 서비스는 게시판사용자정보가 필요하기 때문에 BoardFindServiceUserFindService를 주입시켰습니다.
  • 조회 서비스를 먼저 구현하는 이유는 Repository 와의 연결성때문입니다.

CommentWriteService

FirebaseCloudMessageService는 FCM을 통한 댓글 알림 서비스 구현 부입니다. 이 후 포스팅 할 글에 해당합니다.

@Slf4j
@Service
@RequiredArgsConstructor
public class CommentWriteService {

    private final CommentRepository commentRepository;
    private final UserFindService userFindService;
    private final BoardFindService boardFindService;
    private final FirebaseCloudMessageService messageService;

    @Transactional
    public Long writeComment(Long userId, Long boardId, CommentWriteRequest commentWriteRequest) throws FirebaseMessagingException, IOException, ExecutionException, InterruptedException {
        Board board = boardFindService.findById(boardId);
        User user = userFindService.findById(userId);

        Comment comment = Comment.builder() // 중요 부분
                .content(commentWriteRequest.getContent())
                .writer(user.getName())
                .board(board)
                .build();

        Comment savedComment = commentRepository.save(comment);
        user.writeComment(savedComment);

        String targetToken = board.getUser().getDeviceToken();
        sendMessageToBoardWriter(targetToken, "Comment Notification!", comment.getWriter(), comment.getContent());
        return savedComment.getComment_id();
    }

    private void sendMessageToBoardWriter(String targetToken, String title, String writer, String content) throws FirebaseMessagingException, IOException, ExecutionException, InterruptedException {
        messageService.sendMessageTo(targetToken, title, "[" + writer + "]" + "가 댓글 : <" + content + ">을 작성했습니다.");
    }
}
  • 게시판을 작성하기 위한 게시판사용자 정보와 작성에 필요한 request를 파라미터로 사용합니다.
  • 객체를 생성하는 부분을 자세하게 볼 필요가 있습니다.
    @Entity
    @Getter
    @NoArgsConstructor(access = AccessLevel.PROTECTED)
    public class Comment extends TimeEntity {
    
    		//...
    		@Builder
        public Comment(String content, String writer, Board board) {
            this.content = content;
            this.writer = writer;
            writtenBoard(board);
        }
    
    		private void writtenBoard(Board board) {
            this.board = board;
            board.getComments().add(this);
        }
    		//...
    }
    • @Builder를 통해 객체를 생성할 때 request를 통해 넘어온 댓글의 내용UserFindService를 통해 찾은 사용자의 정보를 사용했습니다.
      • 또한, Board - Comment 사이는 연관관계가 성립되어있기 때문에 그에 맞도록 메서드(writtenBoard())를 구현했습니다.
  • 사용자 정보를 통해 찾아온 사용자 객체에 댓글을 저장하도록 했습니다. (user.writeComment(savedComment))
    • User
      @Entity
      @Getter
      @NoArgsConstructor(access = AccessLevel.PROTECTED)
      public class User {
      
      		//...
      		public void writeComment(Comment comment) {
              this.comments.add(comment);
              comment.writeUser(this);
          }
      		//...
      }
    • Comment
      		//...
      		public void writeUser(User user) {
              this.user = user;
          }
      		//...
      }

CommentUpdateService

@Service
@RequiredArgsConstructor
public class CommentUpdateService {

    private final CommentFindService commentFindService;

    @Transactional
    public Long updateComment(Long userId, Long boardId, CommentUpdateRequest commentUpdateRequest) {
        Comment comment = commentFindService.findCommentByUserAndBoard(userId, boardId);
        return comment.update(
                commentUpdateRequest.getContent()
        );
    }
}
  • 댓글을 수정하는 것 또한, 댓글 엔티티가 갖는 고유 식별자가 아닌 사용자와 게시판의 식별자를 통해 수정하도록 구현했습니다.

CommentDeleteService

@Service
@RequiredArgsConstructor
public class CommentDeleteService {

    private final CommentFindService commentFindService;
    private final CommentRepository commentRepository;

    @Transactional
    public void deleteComment(Long userId, Long boardId) {
        Comment comment = commentFindService.findCommentByUserAndBoard(userId, boardId);
        commentRepository.deleteById(comment.getComment_id());
    }
}
  • 수정과 마찬가지입니다.

참고 : 댓글이 삭제될 경우, 게시판과 사용자 측면에서 조회할 수 없게 되지만, 사용자나 게시판이 삭제될 경우 그에 해당하는 댓글은 저절로 삭제가 됩니다. ⇒ 연관관계를 맺을 때 옵션을 사용했기 때문입니다.


Controller


API 구현 부는 앞선 User, Board와 매우 유사한 구조이기 때문에 생략하겠습니다.
특히, 필요한 정보는 @PathVariable@RequestBody + XXXRequest클래스의 파라미터를 통해 얻을 수 있도록 구현했습니다.

profile
열심히 해보자9999

1개의 댓글

comment-user-thumbnail
2023년 11월 30일

You've come this far and I think that's great, keep chasing your dreams and one day you'll reach the top and then you'll see how great you've become . bitlife game

답글 달기