[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

4개의 댓글

comment-user-thumbnail
2024년 11월 6일

Enter the pixelated world of Retro Bowl College where football fans can enjoy a delightful blend of strategy and action. Manage your own team, make tactical decisions, and lead them to victory on the field. With its charming visuals and engaging gameplay, Retro Bowl is an ideal choice for casual players and sports enthusiasts alike.

답글 달기
comment-user-thumbnail
2024년 12월 26일

This is a solid implementation for a commenting system where users can add, update, and delete comments on boards. The use of JPA relationships PolyTrack and cascading operations simplifies the management of associated entities.

답글 달기
comment-user-thumbnail
2025년 1월 4일

Visiting the city and feeling like you’d enjoy some pleasant and friendly company? Call Girls in Crowne Plaza Hotel seems to be what you were searching for. While I do not offer such services in my scope, I will be able to offer an engaging conversation and an enjoyable experience.

답글 달기
comment-user-thumbnail
4일 전

The nightlife around Fabhotel Prime Sage is amazing, especially with the availability of Call Girls in Fabhotel Prime Sage. It makes for an exciting night with great company

답글 달기