-
Notifications
You must be signed in to change notification settings - Fork 13
Feat/week 2 #21
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: 강준이
Are you sure you want to change the base?
Feat/week 2 #21
Changes from all commits
098a8c2
5200ce0
3d54a1f
2ded9e9
2592017
a869462
9346fa9
e227126
96b956f
845bb2d
eb68811
8000aaf
7437ff5
bbc2f3d
cc40f17
f955163
a06835e
f090e61
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 +1,3 @@ | ||
| # backend-study-sns | ||
|
|
||
| 강준이 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,4 +10,4 @@ public static void main(String[] args) { | |
| SpringApplication.run(DevSnsApplication.class, args); | ||
| } | ||
|
|
||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| package com.example.devSns.controller; | ||
|
|
||
| import com.example.devSns.dto.CommentRequest; | ||
| import com.example.devSns.entity.CommentEntity; | ||
| import com.example.devSns.service.CommentService; | ||
| import org.springframework.stereotype.Controller; | ||
| import org.springframework.web.bind.annotation.*; | ||
|
|
||
| @Controller | ||
| @RequestMapping("/comments") | ||
| public class CommentController { | ||
| private final CommentService commentService; | ||
|
|
||
| public CommentController(CommentService commentService) { | ||
| this.commentService = commentService; | ||
| } | ||
|
|
||
| // 댓글 생성 | ||
| @PostMapping("/{postId}") | ||
| public String createComment(@PathVariable Long postId, @ModelAttribute CommentRequest request) { | ||
| commentService.createComment(postId, request); | ||
| return "redirect:/posts/" + postId; | ||
| } | ||
|
|
||
| // 댓글 삭제 | ||
| @PostMapping("/{commentId}/delete") | ||
| public String deleteComment(@PathVariable Long commentId, @RequestParam Long postId) { | ||
| commentService.deleteComment(commentId); | ||
| return "redirect:/posts/" + postId; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| package com.example.devSns.controller; | ||
|
|
||
| import com.example.devSns.entity.PostEntity; | ||
| import com.example.devSns.service.PostService; | ||
| import org.springframework.stereotype.Controller; | ||
| import org.springframework.ui.Model; | ||
| import org.springframework.web.bind.annotation.*; | ||
|
|
||
| @Controller | ||
| @RequestMapping("/posts") | ||
| public class PostController { | ||
|
|
||
| private final PostService postService; | ||
|
|
||
| // 생성자 | ||
| public PostController(PostService postService) { | ||
| this.postService = postService; | ||
| } | ||
|
|
||
| // HTTP GET /posts | ||
| @GetMapping | ||
| public String list(Model model) { | ||
| model.addAttribute("posts", postService.getAllPosts()); | ||
| return "list"; | ||
| } | ||
|
|
||
| // HTTP GET /posts/new | ||
| @GetMapping("/new") | ||
| public String form(Model model) { | ||
| model.addAttribute("post", new PostEntity()); | ||
| return "form"; | ||
| } | ||
|
|
||
| // HTTP POST /posts | ||
| @PostMapping | ||
| public String create(@ModelAttribute PostEntity postEntity) { | ||
| postService.createPost(postEntity); | ||
| return "redirect:/posts"; // 글 생성 후 돌아올 주소 | ||
| } | ||
|
|
||
| // HTTP GET /posts/{id} | ||
| @GetMapping("/{id}") | ||
| public String detail(@PathVariable Long id, Model model) { | ||
| model.addAttribute("post", postService.getPost(id)); | ||
| return "detail"; | ||
| } | ||
|
|
||
| // HTTP GET /posts/{id}/edit | ||
| @GetMapping("/{id}/edit") | ||
| public String editForm(@PathVariable Long id, Model model) { | ||
| PostEntity postEntity = postService.getPost(id); | ||
| model.addAttribute("post", postEntity); | ||
| return "edit"; | ||
| } | ||
|
|
||
| // HTTP POST /posts/{id}/update | ||
| @PostMapping("/{id}/update") | ||
| public String update(@PathVariable Long id, @ModelAttribute PostEntity updatedPost) { | ||
| postService.updatePost(id, updatedPost); | ||
| return "redirect:/posts/" + id; // 수정 후 돌아올 주소 | ||
| } | ||
|
|
||
| // HTTP POST /posts/{id}/delete | ||
| @PostMapping("/{id}/delete") | ||
| public String delete(@PathVariable Long id) { | ||
| postService.deletePost(id); | ||
| return "redirect:/posts"; // 삭제 후 돌아올 주소 | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| 요청을 받는 곳 | ||
|
|
||
| PostController | ||
| Spring MVC 패턴에서 Controller 계층 역할 | ||
| - 사용자의 요청을 받고 Service 계층을 호출해서 처리한 후, 결과를 View에 전달 | ||
|
|
||
| @Controller | ||
| 이 클래스는 웹 요청을 처리하는 컨트롤러임을 알려줌 | ||
| - Model에 데이터를 담아 템플릿에 전달 (templates/list.html) | ||
|
|
||
| @RequestMapping | ||
| 클래스 전체의 기본 URL 경로를 지정 | ||
| - 이 컨트롤러 안의 모든 요청은 /posts로 시작 | ||
| - /posts (게시글 목록), /posts/new (새 글 작성 폼) | ||
|
|
||
| @GetMapping, @PostMapping | ||
| HTTP 메서드 별 라우팅 | ||
|
|
||
| @PathVariable, @ModelAttribute | ||
| URL 경로 변수 바인딩 / 폼 데이터를 객체에 바인딩 | ||
|
|
||
| @RestController | ||
| 문자열 반환이 뷰 이름이 아닌 JSON | ||
| - REST API 만들 때 사용 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| package com.example.devSns.dto; | ||
|
|
||
| import lombok.Getter; | ||
| import lombok.Setter; | ||
|
|
||
| @Getter | ||
| @Setter | ||
| public class CommentRequest { | ||
| private String username; | ||
| private String content; | ||
| } | ||
|
|
||
| // Controller는 CommentEntity를 직접 받지 않고 CommentRequest만 받게 됨 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| package com.example.devSns.dto; | ||
|
|
||
| import lombok.Getter; | ||
|
|
||
| @Getter | ||
| public class CommentResponse { | ||
| private final Long id; | ||
| private final String username; | ||
| private final String content; | ||
| private final String createdAt; | ||
|
|
||
| public CommentResponse(Long id, String username, String content, String createdAt) { | ||
| this.id = id; | ||
| this.username = username; | ||
| this.content = content; | ||
| this.createdAt = createdAt; | ||
| } | ||
| } | ||
|
|
||
| // Entity를 외부로 그대로 내보내지 않고 DTO로 가공해서 반환 | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| 계층간 데이터 교환에 사용 | ||
| - Entity 클래스를 보호 | ||
| - 필요한 데이터만 선택적으로 담을 수 있음 | ||
|
|
||
| 분리해서 사용하는 이유 | ||
| - Entity 객체의 변경을 피하기 위함 | ||
| - 클라이언트와 통신하는 ResponseDTO나 RequestDTO는 요구사항에 따라 자주 변경 | ||
| - 어떤 요청에서는 특정 값이 추가되거나 없을 수 있어서 분리해서 관리 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| package com.example.devSns.entity; | ||
|
|
||
| import jakarta.persistence.*; | ||
| import lombok.Getter; | ||
|
|
||
| import java.time.LocalDateTime; | ||
|
|
||
| // DTO를 사용하고 Setter를 삭제 (안전) | ||
| @Entity | ||
| @Getter | ||
| public class CommentEntity { | ||
| @Id | ||
| @GeneratedValue(strategy = GenerationType.IDENTITY) | ||
| private Long id; | ||
|
|
||
| private String content; | ||
| private String username; | ||
|
|
||
| private LocalDateTime createdAt; | ||
| private LocalDateTime updatedAt; | ||
|
|
||
| // Post와 N:1 관계 | ||
| @ManyToOne(fetch = FetchType.LAZY) | ||
| @JoinColumn(name = "post_id") | ||
| private PostEntity postEntity; | ||
|
Comment on lines
+23
to
+25
Contributor
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는 Post가 존재할 때만 만들어 질 수 있으니 @ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "post_id", nullable = false) |
||
|
|
||
| // JPA 기본 생성자 | ||
| protected CommentEntity() {} | ||
|
Contributor
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. 잘 작성해주셨네요! JPA의 지연로딩 때문에 빈 프록시 객체를 만들기 위한 protected 레벨의 기본 생성자가 필요합니다! 이 부분을 다음과 같은 어노테이션 추가해서 생략할 수도 있습니다 @NoArgsConstructor(access = AccessLevel.PROTECTED) |
||
|
|
||
| // 정적 생성 메서드 > 엔터티 변경은 오직 메서드로 | ||
| public static CommentEntity create(PostEntity postEntity, String username, String content) { | ||
| CommentEntity comment = new CommentEntity(); | ||
| comment.postEntity = postEntity; | ||
| comment.username = username; | ||
| comment.content = content; | ||
| return comment; | ||
| } | ||
|
|
||
| public void updateContent(String content) { | ||
| this.content = content; | ||
| } | ||
|
|
||
| @PrePersist | ||
| public void onCreate() { | ||
| this.createdAt = LocalDateTime.now(); | ||
| this.updatedAt = LocalDateTime.now(); | ||
| } | ||
|
|
||
| @PreUpdate | ||
| public void onUpdate() { | ||
| this.updatedAt = LocalDateTime.now(); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| package com.example.devSns.entity; | ||
|
|
||
| import jakarta.persistence.*; | ||
| import lombok.Getter; | ||
| import lombok.Setter; | ||
|
|
||
| import java.time.LocalDateTime; | ||
| import java.util.ArrayList; | ||
| import java.util.List; | ||
|
|
||
| @Entity | ||
| @Getter | ||
| @Setter | ||
| public class PostEntity { | ||
|
|
||
| @Id | ||
| @GeneratedValue(strategy = GenerationType.IDENTITY) | ||
| private Long id; | ||
|
|
||
| private String title; | ||
| private String content; | ||
| private String author; | ||
|
|
||
| private LocalDateTime createdAt; | ||
| private LocalDateTime updatedAt; | ||
|
|
||
| // post를 삭제하면 댓글도 같이 삭제 | ||
| @OneToMany(mappedBy = "postEntity", cascade = CascadeType.ALL, orphanRemoval = true) | ||
| private List<CommentEntity> comments = new ArrayList<>(); | ||
|
Comment on lines
+28
to
+29
Contributor
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.
|
||
|
|
||
| @PrePersist | ||
| protected void onCreate() { | ||
| this.createdAt = LocalDateTime.now(); | ||
| this.updatedAt = LocalDateTime.now(); | ||
| } | ||
|
|
||
| @PreUpdate | ||
| protected void onUpdated() { | ||
| this.updatedAt = LocalDateTime.now(); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| 데이터베이스와 직접적으로 맞닿는 핵심적인 클래스 | ||
| - entity를 기준으로 테이블 생성 | ||
| - Builder 패턴을 사용해서 필요한 값만 넣음 | ||
|
|
||
| PostEntity 객체가 곧 DB의 한 행(row) | ||
| -> 기본 키는 id이고, 나머지는 속성임을 명시하는 클래스 | ||
|
|
||
| @Entity | ||
| 데이터베이스 테이블과 매핑 | ||
| - JPA가 이 클래스를 테이블로 인식 | ||
| - 데이터베이스 테이블로 매핑할 수 있게 함 | ||
|
|
||
| @Id | ||
| 해당 필드가 기본 키임을 나타냄 | ||
| - JPA에서 이 필드는 각 행을 고유하게 식별하는 기준 | ||
|
|
||
| @GeneratedValue | ||
| 기본 키의 값을 자동 생성할 때 어떤 전략을 쓸지 지정 | ||
| GenerationType.IDENTITY는 자동 증가 | ||
|
|
||
| @Getter, @Setter (Lombok) | ||
| 보일러플레이트 제거 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| package com.example.devSns.repository; | ||
|
|
||
| import com.example.devSns.entity.CommentEntity; | ||
| import org.springframework.data.jpa.repository.JpaRepository; | ||
|
|
||
| public interface CommentRepository extends JpaRepository<CommentEntity, Long> { | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| package com.example.devSns.repository; | ||
|
|
||
| import com.example.devSns.entity.PostEntity; | ||
| import org.springframework.data.jpa.repository.JpaRepository; | ||
|
|
||
| public interface PostRepository extends JpaRepository<PostEntity, Long> { | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| 데이터베이스 접근 (JPA 인터페이스) | ||
|
|
||
| PostRepository<T, ID> | ||
| 상속만 하면 기본 CRUD가 다 생김 | ||
| - save, findAll, findById, deleteById |
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.
DTO 적용 좋습니다! 현재
@Getter를 통해 값을 조회하네요! 그런데 이런 경우에 class 대신 record를 쓴다면 코드를 아주 간결하게 작성할 수 있습니다. 불변객체라서 안정적이라는 장점도 있습니다. 저는 개인적으로 DTO는 항상 record로 작성하는 편입니다참고 : https://wikidocs.net/287476