[게시판 + DB연동] 이민구 제출합니다.#41
Conversation
Donghwan814
left a comment
There was a problem hiding this comment.
안녕하세요 민구님!
우선 이번 주차 과제 제출하시느라 고생 많으셨습니다!
이번 과제는 저번 주 과제와 크게 다르지 않은 부분이 많았고, 코드도 전반적으로 잘 작성해주셔서 이번에는 코드 자체의 수정 사항보다는 왜 이렇게 작성했는지, 어떤 의도로 사용했는지에 대한 질문 위주로 피드백을 남겼습니다.
이번 주차는 특히 DB 연동이 중요한 부분이라고 생각됩니다.
다만 제출해주신 이미지를 확인해보니 현재는 Postman으로 API 테스트한 결과만 첨부되어 있는 것 같습니다.
MySQL과 연동해서 작업해주신 만큼, 실제로 게시글 생성, 조회, 수정, 삭제가 MySQL DB에 정상적으로 반영되는지도 함께 확인할 수 있도록 DB 조회 결과 화면도 같이 첨부해주시면 더 좋을 것 같습니다!
There was a problem hiding this comment.
현재 게시글 생성 API에서 200 OK를 사용해주신 것으로 보이는데, 200 OK도 요청 성공을 의미하기 때문에 완전히 틀린 것은 아니라고 생각합니다.
다만 게시글 생성처럼 새로운 리소스가 생성되는 경우에는 HTTP 표준상 201 Created를 사용하는 것이 더 의미가 명확합니다. 201 Created는 요청이 성공적으로 처리되었고, 그 결과 새로운 리소스가 생성되었음을 나타내는 상태 코드로 정의되어 있습니다.
혹시 200 OK를 사용하신 특별한 이유가 있으신지 궁금합니다!
There was a problem hiding this comment.
성공한 것에 대한 것은 전부 200OK라고 했습니다.
성공한 것도 분류 해서 작성해야하는 것을 망각했습니다.
201 Created로 수정하였습니다.
There was a problem hiding this comment.
파일명을 notFound로 작성해주신 부분을 봤을 때, 존재하지 않는 게시글을 조회했을 때의 예외 처리를 의도하신 것으로 보입니다!
다만 Postman 테스트 결과를 확인해보니 현재는 400 Bad Request로 응답이 내려오고 있는 것 같습니다.
존재하지 않는 게시글 ID를 조회하는 상황은 요청 형식이 잘못된 경우라기보다는, 요청한 리소스가 서버에 존재하지 않는 경우에 더 가깝기 때문에 404 Not Found 상태코드를 사용하는 것이 더 적절할 것 같습니다.
There was a problem hiding this comment.
요청 형식은 맞고 서버에 존재 하지 않기 때문에 404 Not Found가 맞습니다.
수정 완료했습니다.
| import org.springframework.web.bind.annotation.*; | ||
|
|
||
| @RestController | ||
| @RequestMapping("/post/{id}/comment") |
There was a problem hiding this comment.
@RequestMapping으로 공통 경로를 묶어주신 점은 매우 잘하셨습니다!
@RequestMapping을 사용함으로 현재 CommentController에 있는 모든 API 주소는 http://localhost:8080/post/{id}/comment 이렇게 공통 주소로 시작 될 것입니다!
보통 @RequestMapping을 사용할 때는 공통 경로를 묶어주는 어노테이션으로 사용하기에 {id} 같이 PathVariable 값을 사용하지 않고 공통 리소스 경로만 묶어서 사용하는 경우가 많습니다!
ex) http://localhost:8080/post
혹시 민구님께서 @RequestMapping("/post/{id}/comment") 이렇게 지정해주신 특별한 이유가 있으신지 의견을 들어보고 싶습니다!
There was a problem hiding this comment.
모든 댓글 api 경로가 /post/{id}/comment로 시작해서 이렇게 묶었습니다.
수정완료하였습니다.
| @PostMapping | ||
| public ResponseEntity<RsData<CommentResponse>> newComment(@RequestBody CommentRequest commentRequest, @PathVariable Long id) { | ||
| CommentResponse commentResponse = commentService.newComment(id, commentRequest); | ||
| RsData<CommentResponse> rsData = new RsData<>("200-1", "댓글이 등록되었습니다", commentResponse); |
There was a problem hiding this comment.
댓글 생성 응답값에 위에 이미지 업로드 부분에서 설명했던 것처럼, 댓글 생성 API는 새로운 댓글 리소스가 만들어지는 요청이기 때문에 200 OK도 동작은 하지만, 의미상으로는 201 Created를 사용하는 것이 더 명확해 보입니다!
| //댓글 단건 조회 | ||
| @GetMapping("/{commentId}") | ||
| public ResponseEntity<RsData<CommentResponse>> oneComment(@PathVariable Long commentId) { | ||
| CommentResponse commentResponse = commentService.findOneComment(commentId); | ||
| RsData<CommentResponse> rsData = new RsData<>("200-1", "해당 댓글 조회 완료되었습니다", commentResponse); | ||
| return ResponseEntity.status(rsData.statusCode()).body(rsData); | ||
| } | ||
|
|
||
| //댓글 수정 | ||
| @PutMapping("/{commentId}") | ||
| public ResponseEntity<RsData<CommentResponse>> updateCommet(@PathVariable Long commentId,@RequestBody CommentRequest request){ | ||
| CommentResponse commentResponse = commentService.updateComment(commentId,request); | ||
| RsData<CommentResponse> rsData = new RsData<>("200-1","댓글이 수정되었습니다",commentResponse); | ||
| return ResponseEntity.status(rsData.statusCode()).body(rsData); | ||
| } | ||
|
|
||
| // 댓글 전체 조회하기(slice) | ||
| @GetMapping | ||
| public ResponseEntity<RsData<Slice<CommentResponse>>> getComment( | ||
| @PathVariable Long id, | ||
| @RequestParam(defaultValue = "0") int page, | ||
| @RequestParam(defaultValue = "10") int size) { | ||
| Slice<CommentResponse> commentResponses = commentService.getCommentWitrhSlice(id, page, size); | ||
| RsData<Slice<CommentResponse>> rsData = new RsData<>("200-1", "댓글 조회가 완료되었습니다", commentResponses); | ||
| return ResponseEntity.status(rsData.statusCode()).body(rsData); | ||
| } | ||
|
|
||
| @DeleteMapping("/{commentId}") | ||
| public ResponseEntity<RsData<Void>> deleteComment(@PathVariable Long commentId){ | ||
| commentService.deleteComment(commentId); | ||
|
|
||
| RsData<Void> rsData = new RsData<>("200-1", "댓글이 정상적으로 삭제 되었습니다"); | ||
| return ResponseEntity.status(rsData.statusCode()).body(rsData); | ||
| } |
There was a problem hiding this comment.
댓글 조회, 수정, 삭제 API를 각각 역할별로 잘 분리해주신 점 좋았습니다!
특히 댓글 단건 조회/수정/삭제를 commentId 기준으로 처리하려고 하신 의도도 명확하게 보였습니다.
다만 현재 클래스 상단에서 @RequestMapping("/post/{id}/comment")로 {id} 값을 공통 경로에 포함해주셨기 때문에, 실제 API 주소에는 게시글 id와 댓글 id가 둘 다 들어가게 됩니다.
예를 들면 아래와 같은 형태가 됩니다.
/post/1/comment/3
여기서 1은 게시글 id, 3은 댓글 id라고 판단이 됩니다!
그런데 현재 조회, 수정, 삭제 메서드에서는 @PathVariable로 commentId만 가져오고 있어서, 스프링에서는 commentId 값만 메서드 파라미터로 전달하게 됩니다.
즉, URL에는 게시글 id가 포함되어 있지만 실제 로직에서는 해당 id 값을 사용하지 않기 때문에, “이 댓글이 정말 해당 게시글에 속한 댓글인지” 확인하는 검증 로직이 빠질 수 있습니다.
민구님께서 의도하신 구조가 특정 게시글 안에서 특정 댓글을 조회/수정/삭제하는 방식이라면, id와 commentId를 함께 받아서 Service 계층에서 검증해보면 더 안정적인 구조가 될 것 같습니다.
| @PathVariable Long id, | ||
| @RequestParam(defaultValue = "0") int page, | ||
| @RequestParam(defaultValue = "10") int size) { | ||
| Slice<CommentResponse> commentResponses = commentService.getCommentWitrhSlice(id, page, size); |
There was a problem hiding this comment.
getCommentWitrhSlice 이 부분 메서드명에서 오타가 발생한 것 같습니다.
.getCommentWithSlice를 의도 하셨을 것이라 생각이 듭니다! 이 부분 수정 해주시면 좋을 것 같습니다!
| // 댓글 삭제 | ||
| public void deleteComment(Long commentId){ | ||
| Comment comment = commentRepository.findById(commentId).orElseThrow(IllegalArgumentException::new); | ||
| commentRepository.delete(comment); | ||
| } |
There was a problem hiding this comment.
삭제 메서드에는 따로 @Transactional을 붙이지 않으신 이유가 있을까요?
현재는 삭제 메서드에 따로 @Transactional이 없기 때문에, 위에서 사용한 @Transactional(readOnly = true)가 그대로 적용될 수 있을 것 같습니다.
삭제는 데이터를 변경하는 작업인데 readOnly = true로 동작하게 되면 의도한 삭제 로직이 정상적으로 반영되지 않거나 문제가 발생할 가능성이 있어 보입니다.
이 부분은 @Transactional(readOnly = true)가 삭제 같은 변경 작업에서 어떤 영향을 주는지 한 번 찾아보면서 학습해보시면 좋을 것 같습니다!
There was a problem hiding this comment.
빼먹은거 같습니다!
readOnly = true로 하면 jpa기능을 사용하지 못해 DB반영이 되지 못하고 예외각 발생할 수 있을 거같습니다 .!
| @PostMapping | ||
| public ResponseEntity<RsData<PostResponse>> newPost(@Valid @RequestBody PostNewRequest requestDto){ | ||
| PostResponse postResponse = postService.newPost(requestDto); | ||
| RsData<PostResponse> rsData = new RsData<>("200-1","게시물이 등록되었습니다",postResponse); |
There was a problem hiding this comment.
이 부분도 201 Created를 사용해보면 조금 더 명확하게 전달할 수 있을 것 같습니다!
| // 단건 삭제 | ||
| public void deletePost(Long id){ | ||
| Post post = postRepository.findById(id).orElseThrow(IllegalArgumentException::new); | ||
| postRepository.delete(post); | ||
| } |
There was a problem hiding this comment.
이 부분도 위에서 설명한 내용처럼 @transactional 삭제 메서드 참고해서 적용해보면 좋을 것 같습니다!
There was a problem hiding this comment.
환경변수를 활용해서 DB 접속 정보를 분리해주신 부분 좋았습니다!
DATABASE_URL, DATABASE_USERNAME, DATABASE_PASSWORD처럼 민감한 정보들을 직접 코드에 작성하지 않고 외부 환경변수로 관리하도록 설정해주신 점이 좋았습니다.
.gitignore도 확인해봤을 때 .env 파일을 통해 민감한 정보를 따로 관리하고 계신 것으로 보여서, 보안 측면에서도 잘 적용해주신 것 같습니다.
|
안녕하세요 민구님! 이번 주차는 지난 과제에 DB 연동이 추가된 형태인데요. 지난주 리뷰에서 함께 이야기 나누었던 코드 개선 포인트들을 어떻게 고민하고 적용해 보셨는지 중심적으로 여쭤보고자 합니다! 편하게 답변해 주세요. |
There was a problem hiding this comment.
여기에 적힌 resultCode 값 중에 200-1이 눈에 띄는데요. 혹시 HTTP 상태 코드인 200을 기반으로 내부적인 세부 명세(예: 200번 성공 중 첫 번째 케이스)를 정의하려고 하신 걸까요? 어떤 의도로 작성하셨는지 궁금합니다!"
There was a problem hiding this comment.
같은 성공이여도 어떤 성공인지 세부적으로 분리해보기 위해서 200-1 같은 기능을 넣어보기 위해 연습해보았습니다.
하지만 지금 성공이 전부 200-1 로 되어있어 수정 하였습니다!
| spring.jpa.database-platform=org.hibernate.dialect.MySQLDialect | ||
| spring.jpa.open-in-view=false | ||
| spring.jpa.show-sql=true | ||
| spring.jpa.hibernate.ddl-auto=update |
There was a problem hiding this comment.
JPA 설정에서 ddl-auto 설정을 update로 지정해 주셨네요! 혹시 특별히 update 설정을 선택하신 이유나 의도가 있으신지 궁금합니다.
There was a problem hiding this comment.
엔티티 칼럼을 추가할때 테이블에 자동으로 반영되기 위해 update로 설정했습니다
| import org.springframework.data.jpa.repository.JpaRepository; | ||
|
|
||
| public interface CommentRepository extends JpaRepository<Comment, Long> { | ||
| Slice<Comment> findByPostId(Long Id, Pageable pageable); |
There was a problem hiding this comment.
지난 리뷰 내용을 바탕으로 Slice와 Page를 공부해 적용해 주셨군요! 피드백 반영이 아주 깔끔하게 잘 되었습니다. 수고하셨습니다.
과제명
게시판 + DB 연동
💡 작업 내용
🔗 참고 링크
김영한 스프링 DB2편
관계 매핑 https://mroh1226.tistory.com/211
🤔 느낀 점 / 어려웠던 점
DB 설계가 중요하다는 것을 느끼게 되었습니다.
또한 Jpa를 따로 배워야할 만큼 많은 기능들이 있다는 것을 느끼게 되었습니다 .