티스토리 뷰
게시글 CRUD까지 구현했으니, 이제 댓글 CRUD도 구현해보겠다. 사실 댓글 로직은 게시글 로직과 거의 똑같다.
Entity 설계
댓글 Create(생성), Read(읽기), Update(업데이트), Delete(삭제)를 위해 필요한 엔티티 Comment를 만들어주었다.
Comment entity
package com.example.MyFreshmanCommunity.entity;
import com.example.MyFreshmanCommunity.dto.ArticleDto;
import com.example.MyFreshmanCommunity.dto.CommentDto;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OnDeleteAction;
import java.time.LocalDateTime;
@Entity
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class Comment {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column
private String content;
@Column
private LocalDateTime createDate;
@Column
private int likesCount;
@OnDelete(action = OnDeleteAction.SET_NULL)
@ManyToOne
@JoinColumn(name="member_id")
private Member member;
@ManyToOne
@JoinColumn(name="article_id")
private Article article;
public static Comment createComment(CommentDto commentDto, Member member, Article article) {
return new Comment(
null,
commentDto.getContent(),
LocalDateTime.now(),
0,
member,
article
);
}
public void patch(CommentDto commentDto) { //수정할 내용이 있는 경우에만 동작
if(commentDto.getContent() != null)
this.content = commentDto.getContent();
}
}
이번에도 외래키 제약조건 SET_NULL을 이용해서 member가 삭제되면, 해당 member와 연결된 comment의 member 필드는 null이 되도록 설정해주었다.
게시글과 마찬가지로 createComment 메서드는 새로운 Comment객체를 생성하고 반환하는 역할을 하고, patch 메서드는 수정할 내용이 담긴 commentDto 객체를 받아 댓글 내용을 수정하는 역할을 한다. null이 아닐 경우에만(수정할 내용이 있는 경우에만) 동작하도록 했다.
DTO
CommentDto
package com.example.MyFreshmanCommunity.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
//@NoArgsConstructor
@AllArgsConstructor
@NoArgsConstructor
public class CommentDto {
private String content;
}
댓글 생성 및 수정 시 클라이언트에서 서버로 데이터를 전송하는 데 사용되는 dto이다.
댓글 내용(content)을 담고있다.
CommentResponseDto
package com.example.MyFreshmanCommunity.dto;
import com.example.MyFreshmanCommunity.entity.Article;
import com.example.MyFreshmanCommunity.entity.Comment;
import com.example.MyFreshmanCommunity.entity.Major;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@Data
//@NoArgsConstructor
@AllArgsConstructor
@NoArgsConstructor
public class CommentResponseDto {
private Long id;
private String content;
private LocalDateTime createDate;
private int likesCount;
private MemberInfoDto memberInfo;
private LoginResponseDto member;
public static CommentResponseDto createCommentDto(Comment comment){
MemberInfoDto memberInfo = comment.getMember() != null ?
MemberInfoDto.createMemberDto(comment.getMember()) : null;
return new CommentResponseDto(
comment.getId(),
comment.getContent(),
comment.getCreateDate(),
comment.getLikesCount(),
memberInfo,
LoginResponseDto.createLoginDto(comment.getMember())
);
}
}
CommentResponseDto는 반대로 서버에서 클라이언트로 데이터를 전송하는 데 사용되는 dto이다.
게시글과 마찬가지로 MemberInfoDto를 사용해서 사용자 정보 중 민감한 데이터를 제외하고 필요한 정보만을 전달하도록 구현했다.
(자세한 MemberInfoDto는 게시글 CRUD 참고)
Repository
CommentRepository
package com.example.MyFreshmanCommunity.repository;
import com.example.MyFreshmanCommunity.dto.CommentDto;
import com.example.MyFreshmanCommunity.entity.Comment;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import java.util.List;
public interface CommentRepository extends JpaRepository<Comment, Long> {
//좋아요가 10개 이상이면 베스트댓글
@Query(value = "SELECT * FROM comment " +
"WHERE article_id = :articleId " +
"ORDER BY CASE WHEN likes_count >= 10 THEN 0 ELSE 1 END, " +
"CASE WHEN likes_count >= 10 THEN likes_count ELSE NULL END DESC, " +
"create_date ASC",
nativeQuery = true)
List<Comment> findByArticleId(Long articleId);
}
좋아요 수가 10개 이상인 댓글을 좋아요 수에 따라 내림차순으로 정렬하고, 같은 좋아요 수를 가진 댓글은 생성 날짜 순으로 오름차순 정렬하며, 좋아요 수가 10개 미만인 댓글은 생성 날짜 순으로 오름차순으로 정렬하는 쿼리를 작성해 주었다.
Service
CommentService
@Service
@Slf4j
@RequiredArgsConstructor
public class CommentService {
private final CommentRepository commentRepository;
private final ArticleRepository articleRepository;
//댓글 조회
public List<CommentResponseDto> showAll(Long articleId) {
List<Comment> comments = commentRepository.findByArticleId(articleId);
List<CommentResponseDto> dtos = new ArrayList<CommentResponseDto>();
for (Comment c : comments) {
CommentResponseDto dto = CommentResponseDto.createCommentDto(c);
dtos.add(dto);
}
return dtos;
}
댓글 조회 로직에서 위에서 만들었던 CommentResponseDto로 반환을 해주는 모습을 확인할 수 있다.
//댓글 생성
public CommentResponseDto create(Long articleId, CommentDto commentDto, HttpSession session) {
Member member = (Member) session.getAttribute("member");
Article article = articleRepository.findById(articleId).
orElseThrow(() -> new NotFoundException("대상 게시글이 없습니다."));
Comment comment = Comment.createComment(commentDto, member, article);
Comment save = commentRepository.save(comment);
return CommentResponseDto.createCommentDto(save);
}
//댓글 수정
public CommentResponseDto update(Long commentId, CommentDto commentDto, HttpSession session) {
Comment target = commentRepository.findById(commentId).
orElseThrow(() -> new NotFoundException("대상 댓글이 없습니다."));
Member member = (Member) session.getAttribute("member");
if(!target.getMember().getId().equals(member.getId())) {
throw new NotPermissionException("다른 사람의 댓글을 수정할 수 없습니다.");
}
target.patch(commentDto);
Comment updated = commentRepository.save(target);
return CommentResponseDto.createCommentDto(updated);
}
생성, 수직 로직에선 위에서 만든 CommentDto를 사용하고 있음을 볼 수 있다.
//댓글 삭제
public void delete(Long commentId, HttpSession session) {
Comment comment = commentRepository.findById(commentId).
orElseThrow(() -> new NotFoundException("대상 댓글이 없습니다."));
Member member = (Member) session.getAttribute("member");
if(!comment.getMember().getId().equals(member.getId())) {
throw new NotPermissionException("다른 사람의 댓글을 삭제할 수 없습니다.");
}
commentRepository.delete(comment);
}
댓글도 게시글과 마찬가지로 지우려는 댓글이 로그인한 유저가 아닌 경우 수정, 삭제 불가하도록 해주기 위하여 member의 고유한 값이 id로 비교하여 처리해주었다. (객체끼리만 비교하면 속성 값이 같아도 서로 다른 메모리 주소에 위치해서 false를 반환하는 오류 발생)
Controller
CommentApiController
@RestController
@RequiredArgsConstructor
public class CommentApiController {
private final CommentService commentService;
//댓글 조회
@GetMapping("/article/{articleId}/comment")
public ResponseEntity<List<CommentResponseDto>> showAll(@PathVariable Long articleId){
List<CommentResponseDto> comments = commentService.showAll(articleId);
return ResponseEntity.status(HttpStatus.OK).body(comments);
}
//댓글 생성
@PostMapping("/article/{articleId}/comment")
public ResponseEntity<CommentResponseDto> create(@PathVariable Long articleId, @RequestBody CommentDto commentDto,
HttpServletRequest request) {
HttpSession session = request.getSession();
CommentResponseDto comment = commentService.create(articleId, commentDto, session);
return ResponseEntity.status(HttpStatus.OK).body(comment);
}
//댓글 수정
@PatchMapping("/comment/{commentId}")
public ResponseEntity<CommentResponseDto> update(@PathVariable Long commentId, @RequestBody CommentDto commentDto,
HttpServletRequest request) {
HttpSession session = request.getSession();
CommentResponseDto comment = commentService.update(commentId, commentDto, session);
return ResponseEntity.status(HttpStatus.OK).body(comment);
}
//댓글 삭제
@DeleteMapping("/comment/{commentId}")
public ResponseEntity<String> delete(@PathVariable Long commentId, HttpServletRequest request) {
HttpSession session = request.getSession();
commentService.delete(commentId, session);
return ResponseEntity.status(HttpStatus.OK).body("댓글 삭제 완료");
}
@PathVariable을 사용하여 URL 경로의 일부를 메서드의 파라미터로 바인딩해주었다. ex)commentId, articleId
댓글 생성, 수정, 삭제 로직에서는 현재 로그인되어있는 멤버의 정보가 필요하므로 HttpServletRequest request, HttpSession session = request.getSession(); 을 통해 세션에 있는 객체를 가져와 articleService에 인자로 전달해주었다.
Postman을 통한 데이터 확인
postman을 통해 데이터가 잘 넘어가는지 확인해주었다.
'Spring' 카테고리의 다른 글
스프링 커뮤니티 만들기 #7 - 게시글 북마크 (0) | 2024.03.03 |
---|---|
스프링 커뮤니티 만들기 #6 - 댓글 좋아요 (2) | 2024.03.03 |
스프링부트 커뮤니티 만들기 # 회고록 (2) | 2024.02.29 |
스프링부트 커뮤니티 만들기 #4 - 게시글 CRUD (1) | 2024.02.28 |
스프링부트 커뮤니티 만들기 #3 - 로그아웃, 회원탈퇴 (1) | 2024.02.27 |
- Total
- Today
- Yesterday
- SQL
- 인텔리제이
- 자바 스프링
- DP
- elasticsearch
- 스프링 북마크
- 스프링부트
- 준영속
- 스프링
- 웹MVC
- 웹 MVC
- 프론트엔드
- EnumType.ORDINAL
- 커뮤니티
- 로그아웃
- 파이썬
- SQLD
- 회원탈퇴
- 지연로딩
- 자바
- SQL 레벨업
- 영속
- 스프링 커뮤니티
- 로깅
- 비영속
- JPA
- 백준 파이썬
- 다이나믹 프로그래밍
- 백준
- 북마크
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 |