-
Notifications
You must be signed in to change notification settings - Fork 13
Feat/week2 #20
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
The head ref may contain hidden characters: "\uBC15\uC7AC\uD64D"
Feat/week2 #20
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,29 +1,22 @@ | ||
| plugins { | ||
| id 'java' | ||
| id 'org.springframework.boot' version '3.5.6' | ||
| id 'io.spring.dependency-management' version '1.1.7' | ||
| id 'org.springframework.boot' version '3.5.6' | ||
| id 'io.spring.dependency-management' version '1.1.6' | ||
| id 'java' | ||
| } | ||
|
|
||
| group = 'com.example' | ||
| version = '0.0.1-SNAPSHOT' | ||
| description = 'Demo project for Spring Boot' | ||
| java { sourceCompatibility = '21' } | ||
|
|
||
| java { | ||
| toolchain { | ||
| languageVersion = JavaLanguageVersion.of(21) | ||
| } | ||
| } | ||
|
|
||
| repositories { | ||
| mavenCentral() | ||
| } | ||
| repositories { mavenCentral() } | ||
|
|
||
| dependencies { | ||
| implementation 'org.springframework.boot:spring-boot-starter-web' | ||
| testImplementation 'org.springframework.boot:spring-boot-starter-test' | ||
| testRuntimeOnly 'org.junit.platform:junit-platform-launcher' | ||
| implementation 'org.springframework.boot:spring-boot-starter-web' | ||
| implementation 'org.springframework.boot:spring-boot-starter-data-jpa' | ||
| implementation 'org.springframework.boot:spring-boot-starter-validation' | ||
| runtimeOnly 'com.h2database:h2' | ||
| compileOnly 'org.projectlombok:lombok' | ||
| annotationProcessor 'org.projectlombok:lombok' | ||
| testImplementation 'org.springframework.boot:spring-boot-starter-test' | ||
| } | ||
|
|
||
| tasks.named('test') { | ||
| useJUnitPlatform() | ||
| } | ||
| tasks.named('test') { useJUnitPlatform() } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| package com.example.devSns.domain; | ||
|
|
||
| import jakarta.persistence.*; | ||
| import jakarta.validation.constraints.NotBlank; | ||
| import lombok.*; | ||
|
|
||
| @Entity | ||
| @Getter | ||
| @NoArgsConstructor(access = AccessLevel.PROTECTED) | ||
| public class Comment { | ||
| @Id @GeneratedValue(strategy = GenerationType.IDENTITY) | ||
| private Long id; | ||
|
|
||
| @NotBlank | ||
| @Column(nullable = false) | ||
| private String content; | ||
|
|
||
| @ManyToOne(fetch = FetchType.LAZY, optional = false) | ||
| private Post post; | ||
|
|
||
| @Builder | ||
| private Comment(String content, Post post) { | ||
| this.content = content; | ||
| this.post = post; | ||
| } | ||
|
|
||
| public void update(String content) { | ||
| this.content = content; | ||
| } | ||
|
|
||
| /** Post 편의 메서드에서만 설정하도록 제한 */ | ||
| void setPostInternal(Post post) { | ||
| this.post = post; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| package com.example.devSns.domain; | ||
|
|
||
| import org.springframework.data.jpa.repository.JpaRepository; | ||
|
|
||
| import java.util.List; | ||
| import java.util.Optional; | ||
|
|
||
| public interface CommentRepository extends JpaRepository<Comment, Long> { | ||
| List<Comment> findByPostIdOrderByIdAsc(Long postId); | ||
| Optional<Comment> findByIdAndPostId(Long id, Long postId); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| package com.example.devSns.domain; | ||
|
|
||
| import jakarta.persistence.*; | ||
| import jakarta.validation.constraints.NotBlank; | ||
| import lombok.*; | ||
|
|
||
| import java.util.ArrayList; | ||
| import java.util.List; | ||
|
|
||
| @Entity | ||
| @Getter | ||
| @NoArgsConstructor(access = AccessLevel.PROTECTED) | ||
| public class Post { | ||
| @Id @GeneratedValue(strategy = GenerationType.IDENTITY) | ||
| private Long id; | ||
|
|
||
| @NotBlank | ||
| @Column(nullable = false) | ||
| private String title; | ||
|
|
||
| @Lob | ||
| private String content; | ||
|
|
||
| @OneToMany(mappedBy = "post", cascade = CascadeType.ALL, orphanRemoval = true) | ||
| private final List<Comment> comments = new ArrayList<>(); | ||
|
|
||
| @Builder | ||
| private Post(String title, String content) { | ||
| this.title = title; | ||
| this.content = content; | ||
| } | ||
|
|
||
| public void update(String title, String content) { | ||
| this.title = title; | ||
| this.content = content; | ||
| } | ||
|
|
||
| /** 양방향 편의 메서드 */ | ||
| void addComment(Comment c) { | ||
| comments.add(c); | ||
| c.setPostInternal(this); | ||
| } | ||
| void removeComment(Comment c) { | ||
| comments.remove(c); | ||
| c.setPostInternal(null); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| package com.example.devSns.domain; | ||
|
|
||
| import org.springframework.data.jpa.repository.JpaRepository; | ||
|
|
||
| public interface PostRepository extends JpaRepository<Post, Long> { } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| package com.example.devSns.service; | ||
|
|
||
| import com.example.devSns.domain.Comment; | ||
| import com.example.devSns.domain.CommentRepository; | ||
| import com.example.devSns.domain.Post; | ||
| import com.example.devSns.domain.PostRepository; | ||
| import org.springframework.http.HttpStatus; | ||
| import org.springframework.stereotype.Service; | ||
| import org.springframework.web.server.ResponseStatusException; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| @Service | ||
| public class CommentService { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. DB를 건드리는 코드이니 @transactional을 통하여 트랜잭션을 설정해두면 좋을 것 같아요!! |
||
| private final CommentRepository commentRepo; | ||
| private final PostRepository postRepo; | ||
|
|
||
| public CommentService(CommentRepository commentRepo, PostRepository postRepo) { | ||
| this.commentRepo = commentRepo; | ||
| this.postRepo = postRepo; | ||
| } | ||
|
|
||
| public List<Comment> list(Long postId) { | ||
| ensurePostExists(postId); | ||
| return commentRepo.findByPostIdOrderByIdAsc(postId); | ||
| } | ||
|
|
||
| public Comment get(Long postId, Long commentId) { | ||
| return commentRepo.findByIdAndPostId(commentId, postId) | ||
| .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Comment not found")); | ||
| } | ||
|
|
||
| public Comment create(Long postId, String content) { | ||
| Post post = postRepo.findById(postId) | ||
| .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Post not found")); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 해당 코드는 아래에 작성한 ensurePostExists와 어떤 차이점이 존재할까요? |
||
| Comment comment = Comment.builder().content(content).post(post).build(); | ||
| return commentRepo.save(comment); | ||
| } | ||
|
|
||
| public Comment update(Long postId, Long commentId, String content) { | ||
| Comment comment = get(postId, commentId); | ||
| comment.update(content); | ||
| return commentRepo.save(comment); | ||
| } | ||
|
|
||
| public void delete(Long postId, Long commentId) { | ||
| Comment comment = get(postId, commentId); | ||
| commentRepo.delete(comment); | ||
| } | ||
|
|
||
| private void ensurePostExists(Long postId) { | ||
| if (!postRepo.existsById(postId)) { | ||
| throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Post not found"); | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| package com.example.devSns.service; | ||
|
|
||
| import com.example.devSns.domain.Post; | ||
| import com.example.devSns.domain.PostRepository; | ||
| import org.springframework.http.HttpStatus; | ||
| import org.springframework.stereotype.Service; | ||
| import org.springframework.web.server.ResponseStatusException; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| @Service | ||
| public class PostService { | ||
|
|
||
| private final PostRepository repo; | ||
|
|
||
| public PostService(PostRepository repo) { | ||
| this.repo = repo; | ||
| } | ||
|
|
||
| public List<Post> findAll() { | ||
| return repo.findAll(); | ||
| } | ||
|
|
||
| public Post findById(Long id) { | ||
| return repo.findById(id) | ||
| .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Post not found")); | ||
| } | ||
|
|
||
| public Post create(String title, String content) { | ||
| Post post = Post.builder() | ||
| .title(title) | ||
| .content(content) | ||
| .build(); | ||
| return repo.save(post); | ||
| } | ||
|
|
||
| public Post update(Long id, String title, String content) { | ||
| Post post = findById(id); | ||
| post.update(title, content); | ||
| return repo.save(post); | ||
| } | ||
|
|
||
| public void delete(Long id) { | ||
| Post post = repo.findById(id) | ||
| .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Post not found")); | ||
|
Comment on lines
+44
to
+45
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. commentService에서도 같은 방식이 존재하는데, 이러한 코드 중복은 어떻게 처리하는 것이 좋을까요? |
||
| repo.delete(post); // 연관 댓글까지 안전하게 제거 | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| package com.example.devSns.web; | ||
|
|
||
| import com.example.devSns.service.CommentService; | ||
| import com.example.devSns.web.dto.CommentCreateRequest; | ||
| import com.example.devSns.web.dto.CommentUpdateRequest; | ||
| import com.example.devSns.web.dto.CommentResponse; | ||
| import jakarta.validation.Valid; | ||
| import org.springframework.http.HttpStatus; | ||
| import org.springframework.web.bind.annotation.*; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| @RestController | ||
| @RequestMapping("/posts/{postId}/comments") | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. comment를 설정하는 모든 코드에서 postId가 필수로 존재해야할까요? 그렇다면 왜 그렇게해야할까요? 아니라면 어떤식으로 코드를 수정할 수 있을까요? |
||
| public class CommentController { | ||
| private final CommentService svc; | ||
|
|
||
| public CommentController(CommentService svc) { | ||
| this.svc = svc; | ||
| } | ||
|
|
||
| @GetMapping | ||
| public List<CommentResponse> list(@PathVariable Long postId) { | ||
| return svc.list(postId).stream().map(CommentResponse::from).toList(); | ||
| } | ||
|
|
||
| @GetMapping("/{commentId}") | ||
| public CommentResponse get(@PathVariable Long postId, @PathVariable Long commentId) { | ||
| return CommentResponse.from(svc.get(postId, commentId)); | ||
| } | ||
|
|
||
| @ResponseStatus(HttpStatus.CREATED) | ||
| @PostMapping | ||
| public CommentResponse create(@PathVariable Long postId, | ||
| @Valid @RequestBody CommentCreateRequest req) { | ||
| return CommentResponse.from(svc.create(postId, req.content())); | ||
| } | ||
|
|
||
| @PutMapping("/{commentId}") | ||
| public CommentResponse update(@PathVariable Long postId, @PathVariable Long commentId, | ||
| @Valid @RequestBody CommentUpdateRequest req) { | ||
| return CommentResponse.from(svc.update(postId, commentId, req.content())); | ||
| } | ||
|
|
||
| @ResponseStatus(HttpStatus.NO_CONTENT) | ||
| @DeleteMapping("/{commentId}") | ||
| public void delete(@PathVariable Long postId, @PathVariable Long commentId) { | ||
| svc.delete(postId, commentId); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| package com.example.devSns.web; | ||
|
|
||
| import com.example.devSns.domain.Post; | ||
| import com.example.devSns.service.PostService; | ||
| import com.example.devSns.web.dto.PostCreateRequest; | ||
| import com.example.devSns.web.dto.PostUpdateRequest; | ||
| import com.example.devSns.web.dto.PostResponse; | ||
| import jakarta.validation.Valid; | ||
| import org.springframework.http.HttpStatus; | ||
| import org.springframework.web.bind.annotation.*; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| @RestController | ||
| @RequestMapping("/posts") | ||
| public class PostController { | ||
|
|
||
| private final PostService svc; | ||
|
|
||
| public PostController(PostService svc) { | ||
| this.svc = svc; | ||
| } | ||
|
|
||
| @GetMapping | ||
| public List<PostResponse> getAll() { | ||
| return svc.findAll().stream().map(PostResponse::from).toList(); | ||
| } | ||
|
|
||
| @GetMapping("/{id}") | ||
| public PostResponse getOne(@PathVariable Long id) { | ||
| Post p = svc.findById(id); | ||
| return PostResponse.from(p); | ||
| } | ||
|
|
||
| @ResponseStatus(HttpStatus.CREATED) | ||
| @PostMapping | ||
| public PostResponse create(@Valid @RequestBody PostCreateRequest req) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. valid를 사용하여 dto를 검증하는 방식 좋습니당 |
||
| Post saved = svc.create(req.title(), req.content()); | ||
| return PostResponse.from(saved); | ||
| } | ||
|
|
||
| @PutMapping("/{id}") | ||
| public PostResponse update(@PathVariable Long id, @Valid @RequestBody PostUpdateRequest req) { | ||
| Post updated = svc.update(id, req.title(), req.content()); | ||
| return PostResponse.from(updated); | ||
| } | ||
|
|
||
| @ResponseStatus(HttpStatus.NO_CONTENT) | ||
| @DeleteMapping("/{id}") | ||
| public void delete(@PathVariable Long id) { | ||
| svc.delete(id); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| package com.example.devSns.web.dto; | ||
|
|
||
| import jakarta.validation.constraints.NotBlank; | ||
|
|
||
| public record CommentCreateRequest( | ||
| @NotBlank String content | ||
| ) {} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| package com.example.devSns.web.dto; | ||
|
|
||
| import com.example.devSns.domain.Comment; | ||
|
|
||
| public record CommentResponse( | ||
| Long id, | ||
| String content | ||
| ) { | ||
| public static CommentResponse from(Comment c) { | ||
| return new CommentResponse(c.getId(), c.getContent()); | ||
| } | ||
| } | ||
|
Comment on lines
+5
to
+12
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 레코드를 아주 잘 쓰고 계시군요! |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| package com.example.devSns.web.dto; | ||
|
|
||
| import jakarta.validation.constraints.NotBlank; | ||
|
|
||
| public record CommentUpdateRequest( | ||
| @NotBlank String content | ||
| ) {} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| package com.example.devSns.web.dto; | ||
|
|
||
| import jakarta.validation.constraints.NotBlank; | ||
|
|
||
| public record PostCreateRequest( | ||
| @NotBlank String title, | ||
| String content | ||
| ) {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Setter가 아닌 Builder를 사용하는 방식 아주 좋습니다!