Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,16 @@ repositories {
}

dependencies {
// 기본 웹 라이브러리
implementation 'org.springframework.boot:spring-boot-starter-web'
// 데이터베이스 연동(JPA) 라이브러리
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
// 테스트용 H2 인메모리 데이터베이스
runtimeOnly 'com.h2database:h2'
// Lombok 라이브러리 (코드 자동 생성)
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
// 기본 테스트 라이브러리
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
Expand Down
Binary file added data/devsns_db.mv.db
Binary file not shown.
39 changes: 39 additions & 0 deletions src/main/java/com/example/devSns/controller/CommentController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.example.devSns.controller;

import com.example.devSns.entity.Comment;
import com.example.devSns.service.CommentService;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/posts/{postId}/comments")
public class CommentController {

private final CommentService commentService;

public CommentController(CommentService commentService) {
this.commentService = commentService;
}

@PostMapping
public Comment createComment(@PathVariable Long postId, @RequestBody Comment comment) {
return commentService.createComment(postId, comment);
}
Comment on lines +19 to +22
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PostController처럼 responseEntity를 사용해서 응답 코드와 함께 반환하도록 구성해주시면 좋을 것 같아요!


@GetMapping
public List<Comment> getCommentsByPostId(@PathVariable Long postId) {
return commentService.getCommentsByPostId(postId);
}

@PutMapping("/{id}")
public Comment updateComment(@PathVariable Long id, @RequestBody Comment comment) {
return commentService.updateComment(id, comment);
}

@DeleteMapping("/{id}")
public void deleteComment(@PathVariable Long id) {
commentService.deleteComment(id);
}
}

51 changes: 51 additions & 0 deletions src/main/java/com/example/devSns/controller/PostController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.example.devSns.controller;

import com.example.devSns.entity.Post;
import com.example.devSns.service.PostService;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/posts")
public class PostController {
private final PostService postService;

public PostController(PostService postService) {
this.postService = postService;
}

@PostMapping
public ResponseEntity<Post> createPost(@RequestBody Post post) {
Post createdPost = postService.createPost(post);
return ResponseEntity.status(HttpStatus.CREATED).body(createdPost);
}

@GetMapping
public ResponseEntity<List<Post>> getAllPosts() {
List<Post> posts = postService.getAllPosts();
return ResponseEntity.ok(posts);
}

@GetMapping("/{id}")
public ResponseEntity<Post> getPostById(@PathVariable Long id) {
Post post = postService.getPostById(id);
return ResponseEntity.ok(post);
}

@PutMapping("/{id}")
public ResponseEntity<Post> updatePost(
@PathVariable Long id,
@RequestBody Post updatedPost) {
Post post = postService.updatePost(id, updatedPost);
return ResponseEntity.ok(post);
}

@DeleteMapping("/{id}")
public ResponseEntity<Void> deletePost(@PathVariable Long id) {
postService.deletePost(id);
return ResponseEntity.noContent().build();
}
}
43 changes: 43 additions & 0 deletions src/main/java/com/example/devSns/entity/Comment.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.example.devSns.entity;

import jakarta.persistence.*;
import lombok.*;
import java.time.LocalDateTime;

@Entity
@Table(name = "comments")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Comment {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY) // auto-incremented
private Long id;

@Column(nullable = false)
private String content;

@Column(nullable = false)
private String writer;

@ManyToOne(fetch = FetchType.LAZY) // 댓글 조회 시 Post는 필요할 때만 불러옴
@JoinColumn(name = "post_id", nullable = false)
private Post post;
Comment on lines +25 to +27
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@manytoone에도 optional=false 속성을 추가해서 더 안전하게 구성할 수 있습니다!


@Column(nullable = false, updatable = false)
private LocalDateTime createdAt;

private LocalDateTime updatedAt;

@PrePersist
public void prePersist() {
createdAt = LocalDateTime.now();
}

@PreUpdate
public void preUpdate() {
updatedAt = LocalDateTime.now();
}
}
49 changes: 49 additions & 0 deletions src/main/java/com/example/devSns/entity/Post.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package com.example.devSns.entity;

import jakarta.persistence.*;
import lombok.*;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

@Entity
@Table(name = "posts")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Post {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(nullable = false)
private String title;

@Column(nullable = false, length = 1000)
private String content;

@Column(nullable = false)
private String writer;

private int likeCount = 0;

@Column(nullable = false, updatable = false)
private LocalDateTime createdAt;

private LocalDateTime updatedAt;

@OneToMany(mappedBy = "post", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Comment> comments = new ArrayList<>();
Comment on lines +37 to +38
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

양방향 연관관계(ManyToOne, OneToMany 모두 적용)의 경우 객체 연관관계 탐색에서 더 유연하다는 장점은 있지만 java 코드상에서 두 연관 객체를 모두 관리해야 한다는 단점이 생깁니다. 그래서 저는 단방향 연관관계만(ManyToOne) 적용하고 추후 필요하다면 양방향 연관관계까지 적용하는 걸 추천드립니다. 어떤 연관관계를 설정하더라도 DB 테이블 구조는 같습니다.


@PrePersist
public void prePersist() {
createdAt = LocalDateTime.now();
}

@PreUpdate
public void preUpdate() {
updatedAt = LocalDateTime.now();
}
}
12 changes: 12 additions & 0 deletions src/main/java/com/example/devSns/repository/CommentRepository.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.example.devSns.repository;

import com.example.devSns.entity.Comment;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;

@Repository
public interface CommentRepository extends JpaRepository<Comment, Long> {
// Comment에서 연관된 Post 객체의 id 기준
List<Comment> findByPost_Id(Long postId);
}
10 changes: 10 additions & 0 deletions src/main/java/com/example/devSns/repository/PostRepository.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.example.devSns.repository;

import com.example.devSns.entity.Post;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface PostRepository extends JpaRepository<Post, Long> {

}
55 changes: 55 additions & 0 deletions src/main/java/com/example/devSns/service/CommentService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.example.devSns.service;

import com.example.devSns.entity.Comment;
import com.example.devSns.entity.Post;
import com.example.devSns.repository.CommentRepository;
import com.example.devSns.repository.PostRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Service
@Transactional
public class CommentService {

private final CommentRepository commentRepository;
private final PostRepository postRepository;

public CommentService(CommentRepository commentRepository,
PostRepository postRepository) {
this.commentRepository = commentRepository;
this.postRepository = postRepository;
}

public Comment createComment(Long postId, Comment comment) {
Post post = postRepository.findById(postId)
.orElseThrow(() -> new RuntimeException("Post not found with id: " + postId));
comment.setPost(post);
return commentRepository.save(comment);
}

@Transactional(readOnly = true)
public List<Comment> getCommentsByPostId(Long postId) {
return commentRepository.findByPost_Id(postId);
}

public Comment updateComment(Long id, Comment updatedComment) {
Comment foundComment = commentRepository.findById(id)
.orElseThrow(() -> new RuntimeException("Comment not found with id: " + id));

foundComment.setContent(updatedComment.getContent());
foundComment.setWriter(updatedComment.getWriter());
Comment on lines +41 to +42
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

해당 부분들도 setter 없이 엔티티 객체 내부 헬퍼 메소드로 분리할 수 있을 것 같습니다!


return commentRepository.save(foundComment);
}

public void deleteComment(Long id) {
if (!commentRepository.existsById(id)) {
throw new RuntimeException("Comment not found with id: " + id);
}
commentRepository.deleteById(id);
}
}


52 changes: 52 additions & 0 deletions src/main/java/com/example/devSns/service/PostService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.example.devSns.service;

import com.example.devSns.entity.Post;
import com.example.devSns.repository.PostRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Service
@Transactional
public class PostService {
private final PostRepository postRepository;

public PostService(PostRepository postRepository) {
this.postRepository = postRepository;
}
Comment on lines +15 to +17
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@RequiredArgsConstuctor를 사용하는 걸 추천드립니다!


public Post createPost(Post post) {
return postRepository.save(post);
}

@Transactional(readOnly = true)
public List<Post> getAllPosts() {
return postRepository.findAll();
}

@Transactional(readOnly = true)
public Post getPostById(Long id) {
return postRepository.findById(id)
.orElseThrow(() -> new RuntimeException("Post not found with id: " + id));
}

public Post updatePost(Long id, Post updatedPost) {
Post foundPost = postRepository.findById(id)
.orElseThrow(() -> new RuntimeException("Post not found with id: " + id));

foundPost.setTitle(updatedPost.getTitle());
foundPost.setContent(updatedPost.getContent());
foundPost.setWriter(updatedPost.getWriter());

return postRepository.save(foundPost);
}
Comment on lines +34 to +43
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

update로직을 post 엔티티 내부에 구성해서 setter 사용 없이 update 로직의 책임을 post 객체에 위임하면 더 객체지향적으로 코드를 구성할 수 있습니다! 그리고 JPA의 변경 감지 기능 때문에 save를 호출하지 않아도 transaction 커밋 시 업데이트 쿼리가 자동으로 수행됩니다!


public void deletePost(Long id) {
if (!postRepository.existsById(id)) {
throw new RuntimeException("Post not found with id: " + id);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RuntimeException의 경우 추상적이라 다른 구체적인 예외 타입(e.g. InvalidInputException)을 사용하거나 필요하다면 커스텀 예외 타입을 사용해보시는 걸 추천드립니다!

}
postRepository.deleteById(id);
}
}