게시판 CRUD 만들어보기

이민기·2023년 11월 19일
0

3주차 스터디입니다.

이번 주는 게시판 CRUD를 만드는 것이 목표였습니다.

1. 폴더 구조

  • Service, Repository, DTO, Entity, Controller로 구성이 되어 있습니다.

  • Controller
    - 사용자의 요청이 진입하는 곳
    - 사용자의 요청을 처리하는 곳
    - 처리 후 서비스로 넘어갑니다.

  • service
    - repository와 controller 사이 미들웨어
    - controller에서 받은 데이터를 가공해서 DB를 보내거나 DB에서 가져온 데이터를 가공해서 사용자에게 보내줍니다.
    - 사용자의 요구사항을 처리하는 곳 (비즈니스 로직)

  • repository
    - 간단히 말하면 CRUD를 처리하는 클래스입니다.

  • dto
    - Data Transfer Object
    - 데이터의 저장을 담당합니다.
    - 계층간에 데이터를 교환할 때 주로 사용합니다.

  • Entity
    - DB에 접근하는 객체

  • Entity와 DTO를 분리하는 이유는?
    - 겹쳐서 작성하게 되면 변경된 값이 DB에 들어갈 수 있습니다.
    • 변경이 많은 경우에는 entity와 DTO를 변경하는 것이 좋습니다.

2. Entity

  • Post Entity입니다. 이전과 동일하나, created_at과 updated_at을 추가하여 날짜를 확인할 수 있도록 했습니다.

  • 추가로, CRUD에서 Update에 대응하는 메소드를 추가했습니다.

package com.example.gdsctuk.entity;

import jakarta.persistence.*;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;

import java.time.LocalDateTime;
import java.util.List;


@Entity
@Table
@NoArgsConstructor
@Getter
public class Post {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long postId;

    @Column(columnDefinition = "TEXT")
    private String title;

    @Column(columnDefinition = "TEXT")
    private String content;

    @Column
    private String postWriter;

    @Column
    private String postPasswd;

    @CreationTimestamp // INSERT 시 자동으로 값을 채워줌
    @Column(name = "created_at")
    private LocalDateTime createdAt;

    @Column(name = "updated_at")
    @UpdateTimestamp // UPDATE 시 자동으로 값을 채워줌
    private LocalDateTime updatedAt;

    @OneToMany(mappedBy = "post")
    private List<Comment> comments;


    @Builder
    public Post(Long postId, String title, String content, String postWriter, String postPasswd ) {
        this.postId = postId;
        this.title = title;
        this.content = content;
        this.postWriter = postWriter;
        this.postPasswd = postPasswd;
        this.createdAt = LocalDateTime.now();
        this.updatedAt = LocalDateTime.now();
    }

    public void update(String title, String content) {
        this.title = title;
        this.content = content;
        this.updatedAt = LocalDateTime.now();
    }

}

3. Repository

package com.example.gdsctuk.repository;

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

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

}
  • @Repository 어노테이션은 주로 데이터 액세스 객체(Data Access Object, DAO)를 구현하는 클래스에 적용됩니다.
  • 해당 클래스가 데이터베이스와의 상호작용을 위한 기능을 제공하고, 예외 변환 및 롤백 등의 데이터 액세스 예외 처리를 담당함을 나타냅니다.

4. DTO

PostRequest.java

package com.example.gdsctuk.dto;

import lombok.*;
import com.example.gdsctuk.entity.Post;

@Getter
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class PostRequest {

    private String title;
    private String content;
    private String postWriter;
    private String postPasswd;

    public Post toEntity() {
        return Post.builder()
                .title(title)
                .content(content)
                .postWriter(postWriter)
                .postPasswd(postPasswd)
                .build();
    }
}

PostResponse.java

package com.example.gdsctuk.dto;

import com.example.gdsctuk.entity.Post;
import lombok.*;

@Getter
@ToString
@NoArgsConstructor
public class PostResponse {
    private Long postId;
    private String title;
    private String content;
    private String postWriter;
    private String postPasswd;

    public PostResponse(Post post) {
        this.postId = post.getPostId();
        this.title = post.getTitle();
        this.content = post.getContent();
        this.postWriter = post.getPostWriter();
        this.postPasswd = post.getPostPasswd();
    }
}
  • 중간에서 데이터를 전송해주는 계층인 DTO 계층의 코드입니다.

  • PostRequest는 포스트와 관련된 정보를 받아 toEntity 메소드를 통해 전달합니다. (DTO to Entity)

  • PostResponse는 받아온 정보들을 Entity의 정보를 DTO로 매핑합니다. (Entity to DTO)

5. Controller

package com.example.gdsctuk.controller;

import com.example.gdsctuk.dto.PostResponse;
import com.example.gdsctuk.dto.PostRequest;
import com.example.gdsctuk.entity.Post;
import com.example.gdsctuk.service.PostService;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/post")
public class PostController {

    private final PostService postService;

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

    @GetMapping("/{id}")
    public PostResponse getPostById(@PathVariable Long id) {
        return postService.getPostById(id);
    }

    @PostMapping
    public Post createPost(@RequestBody PostRequest post) {
        return postService.createPost(post);
    }

    @PutMapping("/{id}")
    public Post updatePost(@PathVariable Long id, @RequestBody PostRequest updatedPost) {
        return postService.updatePost(id, updatedPost);
    }

    @DeleteMapping("/{id}")
    public void deletePost(@PathVariable Long id) {
        postService.deletePost(id);
    }
}
  • @GetMapping, @PostMapping, @PutMapping, @DeleteMapping은 CRUD와 관련된 각 http 메소드에 매핑됩니다.

  • @PathVariable 애너테이션은 매핑할 때 적힌 {id} 부분을 처리해줍니다.

  • @RequestBody 애너테이션은 HTTP 요청 body 내용인 XML, JSON 또는 기타 데이터 등을 자바 객체로 매핑하는 역할을 합니다.
  1.   getPostById 메소드는 Id 번호에 맞는 포스트를 찾습니다.

  2.   createPost 메소드는 새로운 포스트를 생성합니다.

  3.   updatePost 메소드는 이전에 작성한 포스트를 수정할 수 있습니다.

  4.   deletePost 메소드는 Id 번호에 맞는 포스트를 삭제합니다.

6. Service

package com.example.gdsctuk.service;

import com.example.gdsctuk.repository.PostRepository;
import com.example.gdsctuk.dto.PostRequest;
import com.example.gdsctuk.dto.PostResponse;
import com.example.gdsctuk.entity.Post;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Service
@Transactional(readOnly = true)
public class PostService {

    private final PostRepository postRepository;

    public PostService(PostRepository postRepository) {
        this.postRepository = postRepository;
    }

    @Transactional
    public List<Post> getAllPost() {
        return postRepository.findAll();
    }

    @Transactional
    public PostResponse getPostById(Long id) {
        Post post = postRepository.findById(id)
                .orElseThrow(() -> new IllegalArgumentException("잘못된 Post ID 입니다."));
        return new PostResponse(post);
    }

    @Transactional
    public Post createPost(PostRequest post) {
        return postRepository.save(post.toEntity());
    }

    @Transactional
    public Post updatePost(Long id, PostRequest updatedPost) {
        Post post = postRepository.findById(id).orElseThrow(() -> new IllegalArgumentException("잘못된 Post ID 입니다."));
        post.update(updatedPost.getTitle(), post.getContent());

        return postRepository.save(post);
    }

    @Transactional
    public void deletePost(Long id) {
        postRepository.deleteById(id);
    }
  • getAllPost()와 getPostById() 메소드는 단일 포스트, 혹은 포스트 전체의 목록을 가져오는 메소드입니다.

  • createPost는 post를 받아와서, Repository 쪽으로 넘겨 db에 저장을 할 수 있도록 합니다.

  • updatePost는 findById를 통해 받아온 포스트의 내용을 수정합니다.
    그 후 다시 저장합니다.

  • deletePost는 말 그대로 포스트 자체를 삭제합니다.
    -> 현재는 Soft delete가 아닌 Hard delete로 구현이 되어있어, 이 부분에 대한 변경이 필요합니다.

7. Postman으로 확인하기

Postman도 좋은데, 최근 Hoppscotch라는 툴을 알게 되어 테스트해보았습니다.

포스트의 생성 결과입니다.

포스트의 조회 결과입니다.

포스트의 수정 결과입니다.

포스트의 삭제 결과입니다.

profile
Michelangelo

0개의 댓글