Board와 Post (1)

Minseo Kang·2023년 10월 4일
0

개요


멋쟁이사자처럼 11기 백엔드 자바 Spring 실습 영상 중 챕터 5. CRUD & Data (2) 의 미션 해설 영상을 보고 내가 이해한 대로 정리하고자 한다.




미션


미션 설명

  1. 게시판에 대한 CRUD를 우선 작성합니다.
    a. 게시판은 게시판 이름에 대한 정보를 가지고 있어야 합니다.
  2. 게시글에 대한 CRUD를 작성해 봅시다.
    a. 게시글에는 제목, 본문, 작성자, 비밀번호에 대한 정보가 존재합니다.
    b. 작성된 게시글은 게시판에 속해 있어야 합니다.
    c. 게시글을 삭제하기 위해서는 게시글의 비밀번호가 함깨 제공되어야 합니다.

세부 조건

  1. 관계형 데이터베이스의 Primary Key와 같은 정보 데이터에 포함하여, 각 자원을 쉽게 식별할 수 있도록 합시다.
  2. REST API와 URL의 구조를 잘 생각하여 @RequestMapping 구성을 할 수 있도록 합시다.
    a. 특히, postboard 의 관계가 요청하는 URL 상에 나타날 수 있도록 해봅시다.




프로젝트 생성


  • https://start.spring.io/ 에서 다음과 같은 설정을 한 후 프로젝트를 생성했다.
  • Project : Gradle - Groovy
  • Language : Java
  • Spring Boot : 2.7.16
  • Project Metadata : Packaging - Jar, Java - 11
  • Dependencies : Spring Web, Spring Data JPA, MySQL Driver

구현


Board와 Post의 관계

  • Board는 여러 Post를 가지기 때문에 Board와 Post는 일대다 관계이다.
  • https://dbdiagram.io/ 로 작성해보았다!

구현 순서

  1. 프로젝트 생성
  2. controller, model, repository 패키지 생성
  3. Board 구현
  4. Post 구현

의존관계가 없는 Board를 먼저 구현한다!




Board(게시판) 구현


1. model 패키지에 BoardDto 클래스를 작성한다.

  • BoardDto 클래스는 PK 역할을 하는 Long 타입의 id 필드, 게시판의 이름인 String 타입의 name 필드를 갖는다.
  • getter, setter, 기본 생성자, 모든 필드에 대한 생성자, toString 을 작성한다.

2. repository 패키지에 BoardRepository 인터페이스를 작성한다.

  • 인터페이스에서는 CRUD 메소드의 틀을 잡는다고 생각하면 된다. 실제 구현은 해당 인터페이스를 구현하는 클래스에서 한다.
  • 다음과 같이 작성하였다.
public interface BoardRepository {
    BoardDto create(BoardDto dto);
    BoardDto read(Long id);
    Collection<BoardDto> readAll();
    boolean update(Long id, BoardDto dto);
    boolean delete(Long id);
}

인터페이스를 통해 로직을 어느 정도 파악할 수 있다.

  • create : Controller에서 @RequestBody로 BoardDto가 들어오고 이것을 기반으로 Board를 생성하고 해당 객체를 반환한다.
  • read : Controller에서 @PathVariable로 id를 받고 이것을 기반으로 Board를 조회하고 해당 객체를 반환한다.
  • readAll : 모든 BoardDto를 Collection에 담아서 반환한다.
  • update : Controller에서 @PathVariable로 id를 받고 이것을 기반으로 BoardDto를 찾고 @RequestBody로 BoardDto가 들어와 해당 dto를 기반으로 업데이트 기능을 수행한다. 업데이트가 정상적으로 되었다면 true, 그렇지 않다면 false를 반환한다.
  • delete : Controller에서 @PathVariable로 id를 받고 이것을 기반으로 BoardDto를 찾고 삭제가 정상적으로 되었다면 true, 그렇지 않다면 false를 반환한다.

3. repository 패키지에 BoardRepository의 구현체인 InMemoryBoardRepository 클래스를 작성한다.

  • 지금은 실제 DB에 연결되어 프로젝트가 돌아가는 것이 아니라 로컬에서 실행되기 때문에 클래스 명에 InMemory를 붙였다.
  • 작성한 코드는 다음과 같다.
@Repository
public class InMemoryBoardRepository implements BoardRepository {

    private Long lastIndex = 0L;
    private final Map<Long, BoardDto> memory = new HashMap<>();

    @Override
    public BoardDto create(BoardDto dto) {
        lastIndex++;
        dto.setId(lastIndex);
        memory.put(lastIndex, dto);
        return memory.get(lastIndex);
    }

    @Override
    public BoardDto read(Long id) {
        return memory.getOrDefault(id, null);
    }

    @Override
    public Collection<BoardDto> readAll() {
        return memory.values();
    }

    @Override
    public boolean update(Long id, BoardDto dto) {
        if (memory.containsKey(id)) {
            dto.setId(id);
            memory.put(id, dto);
            return true;
        }
        return false;
    }

    @Override
    public boolean delete(Long id) {
        if (memory.containsKey(id)) {
            memory.remove(id);
            return true;
        }
        return false;
    }

}
  • InMemoryBoardRepository 클래스를 루트 컨테이너에 빈(Bean) 객체로 생성해주기 위한 @Repository 어노테이션을 붙인다.
  • 인터페이스를 구현함을 명시하기 위해 implements BoardRepository 를 작성한다.
  • lastIndex 변수는 PK인 id를 위한 변수이다.
  • memory 변수는 Long을 key로, BoardDto를 value로 갖는다. Long 형의 데이터 타입(PK)로 BoardDto를 조회하는 등의 행위를 함을 알 수 있다.

구현된 메소드를 하나씩 살펴보자. (로직 자체는 그렇게 복잡하지 않지만 내가 Map을 사용하는 데에 익숙하지 않아서 문법 설명도 들어갈 수 있다.)

  • create : 인덱스 값을 증가시키고 들어온 dto의 id를 인덱스 값으로 준다. HashMap의 put 메소드로 저장한다. 인덱스 값으로 memory에서 dto를 가져와 리턴한다.

    public V put(K key, V value)
    key 값을 이용해 value를 넣는다.

  • read : Map의 getOrDefault 메소드를 이용해 BoardDto를 반환한다. 아래 설명에 따라 더 자세히 말하자면, 매개변수로 받은 id 값을 통해 BoardDto가 반환된다. id에 해당하는 dto가 없으면 null을 반환한다.

    getOrDefault(Object key, V DefaultValue)
    key는 값을 가져와야 하는 요소의 키이다. DefaultValue는 지정된 키로 매핑된 값이 없는 경우 반환되어야 하는 기본값이다. 반환값은 찾는 key가 존재하면 해당 key에 매핑되어 있는 값을 반환하고, 그렇지 않으면 디폴트 값이 반환된다.

  • readAll : HashMap의 values 메소드를 이용해 BoardDto를 Collection에 담아 반환한다.

    public Collection<V> values()
    해당 map의 value 목록을 Collection 형태로 리턴한다.

  • update : 매개변수로 전달받은 id 값을 기반으로 containsKey 메소드를 이용해 key 값으로 id가 존재한다면 id에 해당하는 value를 매개변수로 전달받은 dto로 세팅하고 true를 반환한다. 그렇지 않다면 false를 반환한다.

  • delete : 매개변수로 전달받은 id 값을 기반으로 containsKey 메소드를 이용해 key 값으로 id가 존재한다면 해당 id를 key 값으로 갖는 것을 제거한다.


4. controller 패키지에 BoardController를 작성한다.

  • 작성한 코드는 다음과 같다.
@RestController
@RequestMapping("board")
public class BoardController {

    private static final Logger logger = LoggerFactory.getLogger(BoardController.class);
    private final BoardRepository boardRepository;

    public BoardController(BoardRepository boardRepository){
        this.boardRepository = boardRepository;
    }

    @PostMapping
    public ResponseEntity<BoardDto> createBoard(@RequestBody BoardDto dto) {
        return ResponseEntity.ok(boardRepository.create(dto));
    }

    @GetMapping("{id}")
    public ResponseEntity<BoardDto> readBoard(@PathVariable("id") Long id) {
        BoardDto dto = boardRepository.read(id);
        if(dto == null) return ResponseEntity.notFound().build();
        return ResponseEntity.ok(dto);
    }

    @GetMapping
    public ResponseEntity<Collection<BoardDto>> readBoardAll() {
        return ResponseEntity.ok(this.boardRepository.readAll());
    }

    @PutMapping("{id}")
    public ResponseEntity<?> updateBoard(@PathVariable("id") Long id, @RequestBody BoardDto dto) {
        if(!boardRepository.update(id, dto)) return ResponseEntity.notFound().build();
        return ResponseEntity.noContent().build();
    }

    @DeleteMapping("{id}")
    public ResponseEntity<?> deleteBoard(@PathVariable("id") Long id) {
        if(!boardRepository.delete(id)) return ResponseEntity.notFound().build();
        return ResponseEntity.noContent().build();
    }


}
  • BoardRepository를 final로 선언하고 생성자에서 주입받아 사용한다.
  • 여기에서 중요하다고 생각되는 부분은 리턴값으로 ResponseEntity를 준다는 것이다.

    나는 처음 스프링부트로 API를 만드는 프로젝트를 했을 때, 반환값을 무조건 Dto 클래스로 했고 이런식으로 작업하면 Json 형태로 body에 담겨 응답하는 것을 볼 수 있었다.

  • 나는 이 부분에서 ResponseEntity를 통해 데이터를 단순히 제공하는데 그치지 않고 더 나아가 응답에 헤더 정보와 HTTP 상태 코드 등을 반환값으로 주는 방법을 배우게 되었다.



쓰다보니 꽤 길어져 Post에 관한 내용은 다음 글에 이어서 작성하도록 하겠다.

0개의 댓글