Spring Security 가 적용된 프로젝트에 회원탈퇴, 대댓글, 좋아요 기능이 추가되었다.
이번엔 기능별로 코드를 정리해보고자 한다.
자세한 코드는 아래 링크에 있다.
Spring Lv5 과제의 깃허브 주소
@DeleteMapping("/signOut")
private ResponseEntity<MsgResponseDto> signOut(@RequestBody SignOutRequestDto signOutRequestDto, @AuthenticationPrincipal UserDetailsImpl userDetails) {
userService.signOut(signOutRequestDto, userDetails.getUser());
return ResponseEntity.ok(new MsgResponseDto("회원탈퇴 완료", HttpStatus.OK.value()));
}
@Getter
@NoArgsConstructor
public class SignOutRequestDto {
private String username;
private String password;
}
public void signOut(SignOutRequestDto signOutRequestDto, User user) {
// 사용자명 일치 여부 확인
user = userRepository.findByUsername(signOutRequestDto.getUsername()).orElseThrow (
() -> new CustomException(NOT_MATCH_INFORMATION)
);
// 비밀번호 일치 여부 확인
if (!passwordEncoder.matches(signOutRequestDto.getPassword(), user.getPassword())) {
throw new CustomException(NOT_MATCH_INFORMATION);
}
userRepository.deleteById(user.getId());
}
해당 회원이 게시글/댓글/대댓글 을 작성할 경우, 그 회원이 탈퇴하면 관련된 데이터들도 모두 삭제되도록 구현했다.
이를 위해서는 연관관계에서 설정이 필요하다.
@Entity
@Getter
@NoArgsConstructor
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true)
private String username;
@Column(nullable = false)
private String password;
@Column(nullable = false)
@Enumerated(value = EnumType.STRING)
private UserRoleEnum role;
@OneToMany(mappedBy = "user", cascade = CascadeType.REMOVE)
private List<Board> boardList = new ArrayList<>();
@OneToMany(mappedBy = "user", cascade = CascadeType.REMOVE)
private List<Comment> commentList = new ArrayList<>();
@OneToMany(mappedBy = "user", cascade = CascadeType.REMOVE)
private List<Reply> replyList = new ArrayList<>();
public User(String username, String password, UserRoleEnum role) {
this.username = username;
this.password = password;
this.role = role;
}
}
게시글 목록(boardList), 댓글 목록(commentList), 대댓글 목록(replyList) 필드
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id", nullable = false)
private User user;
Board, Comment, Reply 엔티티
기본적인 구성은 게시글, 댓글과 동일하다.
다만, 여기에 replyId 가 추가되었을 뿐이다.
// 대댓글 작성
@PostMapping("/{boardId}/{cmtId}")
public ResponseEntity<ReplyResponseDto> createReply(@PathVariable Long boardId, @PathVariable Long cmtId, @RequestBody ReplyRequestDto replyRequestDto, @AuthenticationPrincipal UserDetailsImpl userDetails) {
return ResponseEntity.ok(replyService.createReply(boardId, cmtId, replyRequestDto, userDetails.getUser()));
}
// 대댓글 수정
@PutMapping("/{boardId}/{cmtId}/{replyId}")
public ResponseEntity<ReplyResponseDto> updateReply(@PathVariable Long boardId, @PathVariable Long cmtId, @PathVariable Long replyId, @RequestBody ReplyRequestDto replyRequestDto, @AuthenticationPrincipal UserDetailsImpl userDetails) {
return ResponseEntity.ok(replyService.updateReply(boardId, cmtId, replyId, replyRequestDto, userDetails.getUser()));
}
// 대댓글 삭제
@DeleteMapping("/{boardId}/{cmtId}/{replyId}")
public ResponseEntity<MsgResponseDto> deleteReply(@PathVariable Long boardId, @PathVariable Long cmtId, @PathVariable Long replyId, @AuthenticationPrincipal UserDetailsImpl userDetails) {
return ResponseEntity.ok(replyService.deleteReply(boardId, cmtId, replyId, userDetails.getUser()));
}
@Getter
@NoArgsConstructor
public class ReplyRequestDto {
private String reply;
}
@Getter
public class ReplyResponseDto {
private Long id;
private String username;
private String reply;
private LocalDateTime createdAt;
private LocalDateTime modifiedAt;
// 게시글과 함께 조회되는 대댓글, 대댓글 작성/수정
public ReplyResponseDto(Reply reply) {
this.id = reply.getId();
this.username = reply.getUsername();
this.reply = reply.getReply();
this.createdAt = reply.getCreatedAt();
this.modifiedAt = reply.getModifiedAt();
}
}
@Entity
@Getter
@Table(name = "reply")
@NoArgsConstructor
public class Reply extends Timestamped {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "username", nullable = false)
private String username;
@Column(name = "reply", nullable = false)
private String reply;
@ManyToOne
@JoinColumn(name = "board_id", nullable = false)
private Board board;
@ManyToOne
@JoinColumn(name = "comment_id", nullable = false)
private Comment comment;
@ManyToOne
@JoinColumn(name = "user_id", nullable = false)
private User user;
// 대댓글 작성
public Reply(ReplyRequestDto replyRequestDto, Board board, Comment comment, User user) {
this.reply = replyRequestDto.getReply();
this.username = user.getUsername();
this.comment = comment;
this.board = board;
this.user = user;
}
// 대댓글 수정
public void update(ReplyRequestDto replyRequestDto) {
this.reply = replyRequestDto.getReply();
}
}
1) 대댓글 작성
public ReplyResponseDto createReply(Long boardId, Long cmtId, ReplyRequestDto replyRequestDto, User user) {
// 게시글이 있는지
Board board = boardRepository.findById(boardId).orElseThrow (
() -> new CustomException(NOT_FOUND_BOARD)
);
// 댓글이 있는지
Comment comment = commentRepository.findById(cmtId).orElseThrow(
() -> new CustomException(NOT_FOUND_COMMENT)
);
Reply reply = new Reply(replyRequestDto, board, comment, user);
Reply saveReply = replyRepository.save(reply);
return new ReplyResponseDto(saveReply, checkReplyLike(reply.getId(), user));
}
2) 대댓글 수정
@Transactional
public ReplyResponseDto updateReply(Long boardId, Long cmtId, Long replyId, ReplyRequestDto replyRequestDto, User user) {
// 게시글이 있는지
Board board = boardRepository.findById(boardId).orElseThrow (
() -> new CustomException(NOT_FOUND_BOARD)
);
// 댓글이 있는지
Comment comment = commentRepository.findById(cmtId).orElseThrow (
() -> new CustomException(NOT_FOUND_COMMENT)
);
// 대댓글이 있는지 & 사용자의 권한 확인
Reply reply = userService.findByReplyIdAndUser(replyId, user);
reply.update(replyRequestDto);
return new ReplyResponseDto(reply, checkReplyLike(reply.getId(), user));
}
3) 대댓글 삭제
public MsgResponseDto deleteReply(Long boardId, Long cmtId, Long replyId, User user) {
// 게시글이 있는지
Board board = boardRepository.findById(boardId).orElseThrow (
() -> new CustomException(NOT_FOUND_BOARD)
);
// 댓글이 있는지
Comment comment = commentRepository.findById(cmtId).orElseThrow (
() -> new CustomException(NOT_FOUND_COMMENT)
);
// 대댓글이 있는지 & 사용자의 권한 확인
Reply reply = userService.findByReplyIdAndUser(replyId, user);
replyRepository.delete(reply);
return new MsgResponseDto("대댓글을 삭제했습니다.", HttpStatus.OK.value());
}
좋아요 기능은 유사하므로, 게시글/댓글/대댓글 좋아요 中 게시글 좋아요만 다룰 것이다.
또한 그 중에서도 게시글 전체 조회만 작성할 것이다.
@PostMapping("/board/like/{boardId}")
public ResponseEntity<MsgResponseDto> saveBoardLike(@PathVariable Long boardId, @AuthenticationPrincipal UserDetailsImpl userDetails) {
return ResponseEntity.ok(boardService.saveBoardLike(boardId, userDetails.getUser()));
}
@Getter
@JsonInclude(JsonInclude.Include.NON_NULL)
public class BoardResponseDto {
private Long id;
private String title;
private String username;
private String contents;
private int boardLikeCnt;
private boolean boardLikeCheck;
private LocalDateTime createdAt;
private LocalDateTime modifiedAt;
private List<CommentResponseDto> commentList = new ArrayList<>();
// 게시글 작성
public BoardResponseDto(Board board) {
this.id = board.getId();
this.title = board.getTitle();
this.username = board.getUsername();
this.contents = board.getContents();
this.createdAt = board.getCreatedAt();
this.modifiedAt = board.getModifiedAt();
}
// 게시글 전체/선택 조회, 수정
public BoardResponseDto(Board board, List<CommentResponseDto> commentList, boolean boardLikeCheck) {
this.id = board.getId();
this.title = board.getTitle();
this.username = board.getUsername();
this.contents = board.getContents();
this.boardLikeCnt = board.getBoardLikeList().size();
this.boardLikeCheck = boardLikeCheck;
this.createdAt = board.getCreatedAt();
this.modifiedAt = board.getModifiedAt();
this.commentList = commentList;
}
}
좋아요 개수는 int 로, 해당 회원의 좋아요 유/무는 boolean 으로 구별한다.
좋아요 개수는 ArrayList 를 사용했으므로, size() 함수로 그 배열의 크기를 구할 수 있다.
@Entity
@Getter
@Table(name = "boardLike")
@NoArgsConstructor
public class BoardLike {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
@JoinColumn(name = "board_id", nullable = false)
private Board board;
@ManyToOne
@JoinColumn(name = "user_id", nullable = false)
private User user;
public BoardLike(Board board, User user) {
this.board = board;
this.user = user;
}
}
@Entity
@Getter
@Table(name = "board")
@NoArgsConstructor
public class Board extends Timestamped {
...
@OneToMany(mappedBy = "board", cascade = CascadeType.REMOVE)
private List<BoardLike> boardLikeList = new ArrayList<>();
...
}
게시글 좋아요 : 게시글 = N : 1 다대일의 관계다.
1) 게시글 좋아요 유/무 (false 면 좋아요 취소, true 면 좋아요)
@Transactional
public boolean checkBoardLike(Long boardId, User user) {
return boardLikeRepository.existsByBoardIdAndUserId(boardId, user.getId());
}
boolean 으로 유/무 를 구분해준다.
2) 게시글 좋아요 개수
@Transactional
public MsgResponseDto saveBoardLike(Long boardId, User user) {
// 게시글이 있는지
Board board = boardRepository.findById(boardId).orElseThrow (
() -> new CustomException(NOT_FOUND_BOARD)
);
if (!checkBoardLike(boardId, user)) {
BoardLike boardLike = new BoardLike(board, user);
boardLikeRepository.save(boardLike);
return new MsgResponseDto("게시글 좋아요", HttpStatus.OK.value());
} else {
boardLikeRepository.deleteByBoardIdAndUserId(boardId, user.getId());
return new MsgResponseDto("게시글 좋아요 취소", HttpStatus.OK.value());
}
}
'1)'에서 선언한 메서드를 이용하여, 해당 회원이 좋아요를 눌렀는지를 판단한 후 좋아요 개수를 누적해준다.
3) 게시글 전체 조회
@Transactional(readOnly = true)
public List<BoardResponseDto> getBoardList(User user) {
List<Board> boardList = boardRepository.findAllByOrderByCreatedAtDesc();
// 게시글
List<BoardResponseDto> boardResponseDtoList = new ArrayList<>();
for (Board board : boardList) {
// 댓글
List<CommentResponseDto> commentList = new ArrayList<>();
for (Comment comment : board.getCommentList()) {
// 대댓글
List<ReplyResponseDto> replyResponseDtoList = new ArrayList<>();
for (Reply reply : comment.getReplyList()) {
replyResponseDtoList.add(new ReplyResponseDto(reply, replyService.checkReplyLike(reply.getId(), user)));
}
commentList.add(new CommentResponseDto(comment, replyResponseDtoList, commentService.checkCommentLike(comment.getId(), user)));
}
boardResponseDtoList.add(new BoardResponseDto(board, commentList, checkBoardLike(board.getId(), user)));
}
return boardResponseDtoList;
}
ArrayList 를 이용하여 각각 게시글, 댓글, 대댓글을 담아줄 새로운 배열을 생성한 후
for문을 돌려 해당 배열에 데이터를 담아준다.