그동안 배운 것들을 적용해 기존 만들었던 CRUD 게시판 처음부터 다시 만들어보기
<변경점>
1.Spring Security 적용 (JWT방식 커스텀 필터 사용)
2.Controller에 @Valid 어노테이션 적용 ( 간단해서 보류 )
2.5 기존 Article, Comment 등록/수정/삭제 기능 재구현 (추가)
3.JPA 연관관계 변경 (양방향 -> 단방향)
4.Custom Exception 처리하기
5.ERD, API 작성
<패키지 구조>
현재 있는 기능 : 회원기능 / 게시글 / 댓글(대댓글)
Controller // Service // Repository // Exception // Security // Handler // Jwt관련
=>묶어서 Utils
<게시글 관련 기능 구현>
Controller
게시글 전체목록 조회시에는 게시판의 형태대로 제목, 작성자만 가져오게 했다.
게시글 상세 조회(게시물클릭) 시에는 게시글의 필드를 모두 가져오게 했다.
둘을 구분짓기 위해 ResponseDto를 ListResponseDto // DetailResponseDto로 나누어 받게 함
Service
Controller에 맞게 구현했다. Article 조회 시 Comment를 어떻게 가져올지 아직 정하지 못했기 때문에, ArticleDetailResponseDto 작성을 보류했다. Comment에 대댓글 기능을 추가할 생각이기 때문에, 게시글에 달린 댓글을 모두 긁어와서 Article에 List로 나갈 댓글 목록을 만들어줄 생각이다. 자세한건 Comment에서
*추가 : 게시글 삭제 요청에 대해 DB에서 삭제하는 것이 아닌, Article의 isDeleted 필드를 true로 만들어주는 방식으로 하기로 했다. 따라서 게시글 긁어오는 부분을 findAllByIsDeletedFalse()로 대체하였다
게시글 상세조회 요청에 대해 가져온 게시글이 isDeleted면 ArticleDeletedException을 throw하도록 바꾸었다.
<Comment 관련 기능>
Comment 기능에 대댓글 기능을 구현할 생각이다. 구현 방법으로는 여러가지가 있을 것이다.
1. Comment 내부에 Comment필드를 가져 연관관계 맺어주기
- Comment와 Comment의 연관관계를 정의할 수가 없다. (댓글인지, 대댓글인지에 따라 관계가 달라지니까)
2. 대댓글 entity를 만들어 관리
- 대댓글이 한 depth로만 달리면 괜찮은 방법일 수 있지만, 대댓글에도 대댓글을 달 수 있는 경우 계속 Entity를 만들어주어야 하기 때문에 좋지 않다고 생각함
3. Comment에 commentType 필드를 가져서 게시글에 단 댓글인지 Comment에 단 댓글인지 분류
- Article을 조회했을 때 commentType이 ArticleComment인 댓글을 가져오고, 해당 댓글들에 대해서 commentType이 CommentComment 인 애들을 전부 조회한 뒤 Id를 비교해야 하기 때문에 쿼리가 불필요하게 많이 나가게 될 것 같다.
4. 댓글과 대댓글을 구분짓지 않고 Comment 엔티티로 관리하며, nullable한 rootComment 필드를 가지게 한다.
Article 조회 시, 해당 Article에 달린 댓글 전부를 긁어온다. CommentBuilder 메소드를 하나 만들어 Comment를 구분하 고, 댓글이 대댓글을 포함하도록 하는 로직을 구현한다.
==> 4번은 추가적인 로직을 구현해야 해서 복잡할 것 같지만, 이렇게 하는게 맞는 방법 같아서 이렇게 하기로 함.
일단, 댓글과 대댓글의 관계를 정리해주는 로직을 제외하고, Comment 등록/삭제/수정 기능을 구현하려고 한다.
또한, 댓글 삭제 시에도 대댓글은 유지하기 위해 댓글 삭제 요청에 대해 DB에서 댓글을 삭제하는 것이 아닌, isDeleted flag를 true로 바꿔주는 방식으로 구현하려고 한다. 이에 따라 CommentResponseDto의 생성자에는 isDeleted를 확인하고, 삭제된 댓글일 시에는 Dto의 content를 "삭제된 댓글"로 바꿔주어야한다.
추가로, 테스트를 위해 Article에서 Comment를 조회하는 부분을 일단 모든 Comment를 조회하도록 구현하려고 한다.
(Article상세조회부분)
(CommentController)
user의 고유ID만 넘겨주면 좋겠지만, 서비스에서 User의 권한을 확인하고 관리자일 시 요청을 처리해주도록 API가 설계되어있기 때문에 User객체 자체를 넘긴다
(CommentService)
delete요청에 대해 DB에서 삭제하는 것이 아니라, comment 엔티티의 isDeleted 값을 true로 바꾸는 메서드를 구현했다.
Comment의 생성자는 Article에 단 Comment인 경우와 대댓글의 경우를 나누어서 오버로딩 해주었다.
기존에는 Comment의 생성자에 CommentRequestDto를 받도록 하였는데, CommentRequest가 바뀔 가능성이 있다는 점을 생각해보면 이에 따라 도메인인 Comment 엔티티의 생성자가 바뀌어야 하는것은 좋지 않다고 생각했다.(의존성문제)
따라서 생성자로 필드값을 받게 한 뒤,
requestDto에서 Comment객체를 만드는 메서드를 통해 Comment를 생성하도록 하였다.
이렇게 하면 Comment요청을 받는 방식이 바뀐다 하더라도 도메인인 Comment 엔티티를 바꾸는 것이 아니라 Dto의 메서드만 바꾸면 된다.
(CommentService 내부 메서드 리팩토링)
Comment를 Optional로 불러오는 부분과, 해당 코멘트 삭제/수정 요청에 대해 작성자가 맞는지 혹은 관리자권한이 있는지를 검증하는 부분은 중복되기 때문에 따로 빼주었다.
<엔티티 연관관계 변경>
기존에는 한 객체에서 다른 객체를 조회할 일이 있으면 연관이 있다고 생각하여 @OneToMany나 @ManyToOne을 사용하여 연관관계를 맺었다. 전체적인 설계를 먼저 하고, 그에 맞게 연관관계를 맺어주는 것이 더 좋은 것 같다.
1. User와 Article의 관계 : 단방향 @ManyToOne
- 기존에는 User가 @OneToMany로 Article을, Article이 @ManyToOne으로 User를 바로보게 하였다.
이렇게 했을 때 장점은 user.getArticles로 작성 글 목록을 한번에 가져올 수 있다는 것이 있다.
회원 Entity가 모든 요청에 조회되는데 비해 비중이 굉장히 낮은 이점일 뿐더러, 이런식으로 설정 했을 때 fetch되어야하 는 시점이 언제인지, Article의 연관관계는 어떻게 되는지 등 생각할 것이 많아진다. 따라서 이번엔
Article이 User를 단방향 @ManyToOne으로 바라보는 관계로 설정하였다. 게시글이 조회될 땐 거의 무조건 작성자를 알 아야 하기 때문이다.
*Article의 User필드는 user_id가 아닌 User 객체로 한 이유는 Article을 조회할 때 username(작성자 닉네임)을 알아야하는데, Article의 필드에 가지고 있는 외래키는 고유id이다. 따라서 Article을 가지고 올 때 User객체를 같이 가져와야 작성자명을 찾기 위한 추가 쿼리가 나가지 않게 된다.
2. User와 Comment의 관계 : 단방향 @ManyToOne
- 1과 마찬가지의 이유로 Comment가 @ManyToOne으로 User를 바라보게 하였다.
3. Comment와 Article의 관계 : 연관관계없이 필드값으로 id 가지고있기
- Article의 경우 전체 글 목록을 보여줄 때는 Comment를 알 필요가 없다. 따라서 게시글 상세보기 시에만 Comment 엔티티에서 Article의 id로 검색하여 댓글을 가져오면 된다.
Comment에서 Article에 대해 알아야할 정보는 Article의 고유번호밖에 없다. Comment를 통해서 해당 댓글이 달린 Article의 내용이나 작성자를 조회할 일이 없기 때문이다. 따라서 따로 연관관계를 맺어주지 않고, Article의 고유id를 하나의 필드article_id로 가지게 하였다. Article에서 해당 글의 Comment를 가지고 올 때는 findby article_id로 쿼리를 날리면 된다.
저장할 때는 어차피 요청 쿼리스트링에서 해당 게시글의 id를 가지고 들어오기 때문에 Article 객체를 찾는 추가적인 쿼리가 나갈일이 없다.
'공부 > Spring' 카테고리의 다른 글
게시판 미니 프로젝트 // 연관관계에 대한 생각 (0) | 2023.05.11 |
---|---|
CRUD 게시판 리팩토링해보기 - <1> Spring Security, Jwt인증 적용 (0) | 2023.04.27 |
Spring Security (0) | 2023.04.25 |
ExceptionHandler // ResponseEntity // HTTP 상태 반환 (0) | 2023.04.23 |
간단한 게시판 CRUD // 로그인, 회원가입, JWT 추가 (0) | 2023.04.20 |