본문 바로가기
Spring Boot Project/Plming

[Plming] 게시글 등록 / 수정 구현하기

by slchoi 2022. 3. 29.
728x90
SMALL
코드 설명은 추후에 추가하겠습니다!

 

이번에는 비즈니스 로직을 담당하는 Service Layer와 API 호출을 담당하는 Rest Controller를 처리해 볼 것이다.

1. Service Layer에서 사용할 Class 생성하기

Service Layer에서 API를 처리하기 위해 필요한 클래스는 총 3개이다.

  • 게시글의 생성과 수정을 처리할 요청(Request) DTO 클래스
  • 게시글 정보를 return 할 응답(Response) DTO 클래스
  • 트랜잭션(Transaction)을 처리할 Service 클래스

 

1.1. 요청(Request) DTO 클래스 생성하기

"plming.board" 패키지에 dto 패키지를 생성한 후 BoardRequestDto 클래스를 생성하고 아래 코드를 작성한다.

더보기
package plming.board.dto;

import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import plming.board.entity.Board;

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class BoardRequestDto { 
    
    private String user;    // 사용자
    private String category;    // 카테고리
    private String status;  // 모집 상태
    private String period;  // 진행 기간
    private String title;   // 제목
    private String content; // 내용
    
    public Board toEntity() {
        return Board.builder()
                .title(title)
                .user(user)
                .status(status)
                .category(category)
                .period(period)
                .content(content)
                .build();
    }
    
}

 

 

Entity 클래스는 Table 또는 Record 역할을 하는 그 자체로 볼 수 있고, 절대로 요청(Request)나 응답(Response)에 사용되서는 안 되기 때문에 반드시 Request, Response 클래스를 따로 생성해 주어야 한다.

toEntity() 메서드

  • BoardTests 클래스의 save() 메서드를 살펴보면 Entity 객체를 인자로 전달해서 게시글을 생성한다.
  • Entity 클래스는 절대로 Request에 사용되서는 안 되기 때문에, BoardRequestDto로 전달받은 데이터를 기준으로 Entity 객체를 생성한다.

 

1.2. Response DTO 클래스 생성하기

조금 전에 생성한 dto 패키지에 BoardResponseDto 클래스를 추가하고, 더보기 코드를 작성한다.

더보기
package plming.board.dto;

import lombok.Getter;
import plming.board.entity.Board;

import java.time.LocalDateTime;

@Getter
public class BoardResponseDto {

    private Long id;
    private String user;
    private String category;
    private String status;
    private String period;
    private String title;
    private String content;
    private Integer participantNum;
    private Long viewCnt;
    private LocalDateTime createDate;
    private LocalDateTime updateDate;
    private char deleteYn = 'N';

    public BoardResponseDto(Board entity) {
        this.id = entity.getId();
        this.user = entity.getUser();
        this.category = entity.getCategory();
        this.status = entity.getStatus();
        this.period = entity.getPeriod();
        this.title = entity.getTitle();
        this.content = entity.getContent();
        this.participantNum = entity.getParticipantNum();
        this.viewCnt = entity.getViewCnt();
        this.createDate = entity.getCreateDate();
        this.updateDate = entity.getUpdateDate();
        this.deleteYn = entity.getDeleteYn();
    }
}

응답(Response)도 Entity 클래스가 사용되서는 안 되기 때문에 클래스를 분리해야 한다. Response 객체 생성은 필수적으로 Entity 클래스를 필요로 한다.

 

1.3. Entity 클래스에 게시글 수정 기능 추가하기

entity 패키지의 Board 클래스에 게시글을 수정해주는 update 메서드를 추가한다.

public void update(String title, String content, String category, String status, String period) {
    this.title = title;
    this.content = content;
    this.category = category;
    this.status = status;
    this.period = period;
    this.updateDate = LocalDateTime.now();
}

 

1.4. Service 클래스 생성하기

"plming.board" 패키지에 model 패키지를 추가한 후 BoardService 클래스를 생성하고, 더보기 코드를 작성한다.

더보기
package plming.board.model;

import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import plming.board.dto.BoardRequestDto;
import plming.board.dto.BoardResponseDto;
import plming.board.entity.Board;
import plming.board.entity.BoardRepository;
import plming.board.exception.CustomException;
import plming.board.exception.ErrorCode;

import java.util.List;
import java.util.stream.Collectors;

import static org.springframework.data.domain.Sort.Direction.*;

@Service
@RequiredArgsConstructor
public class BoardService {

    private final BoardRepository boardRepository;

    /**
     * 게시글 생성
     */
    @Transactional
    public Long save(final BoardRequestDto params) {

        Board entity = boardRepository.save(params.toEntity());
        return entity.getId();
    }

    /**
     * 게시글 리스트 조회
     */
    public List<BoardResponseDto> findAll() {

        Sort sort = Sort.by(DESC, "id", "createdDate");
        List<Board> list = boardRepository.findAll(sort);
        return list.stream().map(BoardResponseDto::new).collect(Collectors.toList());
    }

    /**
     * 게시글 수정
     */
    @Transactional
    public Long update(final Long id, final BoardRequestDto params) {

        Board entity = boardRepository.findById(id).orElseThrow(() -> new CustomException(ErrorCode.POSTS_NOT_FOUND));
        entity.update(params.getTitle(), params.getContent(), params.getCategory(), params.getStatus(), params.getPeriod());
        return id;
    }
}

 

boardRepository

  • JPA Repository 인터페이스
  • @Autowired로 Bean을 주입받는 방식을 사용했는데, 스프링은 생성자로 빈을 주입받는 방식을 권장한다고 한다.
  • 클래스 레벨에 선언된 @RequiredArgsContructor는 롬복에서 제공하는 어노테이션으로, 클래스 내에 final로 선언된 모든 멤버에 대한 생성자를 만들어준다.

@Transactional

  • JPA를 사용한다면, Service 클래스에서 필수적으로 사용되어야 하는 어노테이션이다.
  • 일반적으로 메서드 레벨에 선언하게 되며, 메서드의 실행, 종료, 예외를 기준으로 각각 실행(begin), 종료(commit), 예외(rollback)를 자동으로 처리해준다.

save()

  • boardRepository의 save() 메서드가 실행되면 새로운 게시글이 생성된다.
  • Entity는 절대로 Request에 사용되서는 안 되므로 BoardRequestDto의 toEntity() 메서드를 이용해서 boardRepository의 save() 메서드를 실행한다.
  • save() 메서드가 실행된 후 entity 객체에는 생성된 게시글 정보가 담기며, 메서드가 종료되면 생성된 게시글의 id를 리턴한다.

findAll()

  • boardRepository의 findAll() 메서드의 인자로 sort 객체를 전달해서 전체 게시글을 조회한다.
  • sort 객체는 ORDER BY id DESC, created_date DESC을 의미한다.
  • return문은 Java의 Stream API를 이용한다. list 변수에 게시글 Entity가 담겨 있고, 각각의 Entity를 BoardResponseDto 타입으로 변경해서 return 해준다.

update()

  • Board Entity 클래스에 update() 메서드를 추가했는데, 이 메서드에는 update 쿼리를 실행하는 로직이 없다. 하지만 메서드의 실행이 종료(commit)되면 update 쿼리가 자동으로 실행된다.
  • JPA에는 영속성 컨텍스트라는 개념이 있는데, 영속성 컨텍스트란 Entity를 영구히 저장하는 환경이라는 뜻이다. 애플리케이션과 데이터베이스 사이에 객체를 보관하는 가상의 영역으로, JPA의 Entity Manager가 Entity가 생성되거나, 조회하는 시점에 영속성 컨텍스트에 Entity를 보관 및 관리한다.
  • Entity를 조회하면 해당 Entity는 영속성 컨텍스트에 보관될 것이고, 영속성 컨텍스트에 포함된 Entity 객체의 값이 변경되면, Transaction이 종료(commit)되는 시점에 update 쿼리를 실행한다. (이렇게 자동으로 쿼리가 실행되는 개념을 더티 체킹(Dirty Checking)이라고 한다.)
  • 영속성 컨텍스트에 의해 Dirty Checking이 가능해진 것이다.

 

2. BoardApiController 클래스 수정하기

BoardApiController 클래스를 더보기 코드와 같이 수정한다.

더보기
package plming.board.controller;

import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import plming.board.dto.BoardRequestDto;
import plming.board.dto.BoardResponseDto;
import plming.board.exception.CustomException;
import plming.board.exception.ErrorCode;
import plming.board.model.BoardService;

import java.util.List;

@RestController
@RequestMapping("/board")
@RequiredArgsConstructor
public class BoardApiController {

    private final BoardService boardService;

    /**
     * 게시글 생성
     */
    @PostMapping
    public Long save(@RequestBody final BoardRequestDto post) {
        return boardService.save(post);
    }

    /**
     * 게시글 리스트 조회
     */
    @GetMapping
    public List<BoardResponseDto> findAll() {
        return boardService.findAll();
    }

    /**
     * 게시글 수정
     */
    @PatchMapping("/{id}")
    public Long save(@PathVariable final Long id, @RequestBody final BoardRequestDto post) {
        return boardService.update(id, post);
    }
}

 

3. API 테스트해보기

Postman을 사용해 게시글 등록/수정, 리스트 조회 관련 API를 테스트해볼 것이다.

3.1. 게시글 생성(Create) 테스트

create 테스트 결과

게시글 정보를 입력하고 post 요청을 보내면 응답으로 생성된 게시글의 id를 보내주는 것을 확인할 수 있다.

 

3.2. 게시글 리스트(Read) 테스트

findAll( ) 메서드의 경우, Request Method가 GET으로 매핑되어 있기 때문에, GET 방식의 API는 굳이 Content-Type을 설정하지 않아도 된다. 

Method를 변경하고, SEND를 클릭해 API를 호출해보면, 게시글 리스트를 JSON Format으로 내려주는 것을 확인할 수 있다.

read 테스트 결과

 

3.3. 게시글 수정(Update) 테스트

Method를 PATCH로 변경하고, Request URL 뒤에 "/{게시글 id}"를 붙여준 후 SEND를 클릭하면, 요청한 id에 해당하는 게시글을 수정한 뒤 해당 게시글의 id를 반환하는 것을 확인할 수 있다.

28번 게시글을 수정한 뒤 DB에서 수정되었는지 확인해보면, 수정된 내용이 잘 반영된 것을 확인할 수 있다.

update 테스트 결과
update 전 DB
update 후 DB

 

3.4. 게시글 에러 메시지 테스트

데이터베이스에 존재하지 않는 999번 게시글을 Path로 지정하고 API를 호출하면, 이전 글에서 ErrorCode에 정의한 POSTS_NOT_FOUND Exception을 throw 하는 것을 확인할 수 있다.

에러 메시지 테스트 결과

 

 

본 프로젝트는 아래 블로그를 참고해서 만들었습니다.
https://congsong.tistory.com/55?category=749196
728x90
LIST

댓글