본문 바로가기
TIL

[231113] Spring 입문 개인과제 해설

by 진진리 2023. 11. 13.
728x90

모범답안 깃허브 링크: https://github.com/camp-robbie/board-app

 

GitHub - camp-robbie/board-app: NB Camp Task Level1: Board APP

NB Camp Task Level1: Board APP. Contribute to camp-robbie/board-app development by creating an account on GitHub.

github.com

 

게시글 삭제 기능 - 비밀번호를 header에 담기

 

개인과제를 할 때 DELETE 메소드의 body에 내용을 담는 것은 지양한다는 말을 듣고 비밀번호를 어떻게 보내야하나 고민하다가 PUT 메소드를 사용했는데 http 헤더에 담는 방법이 있었고, 이에 대해 알게 되었다.

@RequestHeader로 헤더에 담긴 비밀번호를 받아온다.

데이터의 형식이 JSON이기 때문에 key값("password")와 같이 적어줘야 한다.

 

    @DeleteMapping("/{postId}")
    public ResponseEntity<Void> deletePost(
            @PathVariable Long postId,
            @RequestHeader("password") String password
    ) {
        postService.deletePost(postId, password);
        return ResponseEntity.noContent().build();
    }

 

 

http 메소드 PATCH 사용

 

http 메소드에서 PATCH를 사용하는 것은 처음 보게되어서 정리해두려고 한다.

전에는 일부만 수정하는 경우 주로 PATCH 메소드를 사용한다는 것을 보고

기존 Dto에 어떻게 맞추지?라는 생각이 들었다.

그런데 고민할 필요없이 PostUpdateRequestDto와 같은 클래스를 새로 만들어주면 되는 것이었다.

 

@PatchMapping("/{postId}")
    public ResponseEntity<PostResponseDto> updatePost(
            @PathVariable Long postId,
            @RequestBody PostUpdateRequestDto requestDto
    ) {
        PostResponseDto responseDto = postService.updatePost(postId, requestDto);
        return ResponseEntity.ok(responseDto);
    }

 

 

ResponseStatus로 HTTP 상태 코드 전달

 

발생한 오류 종류에 맞게 디테일한 http 상태 코드를 전달하고 싶은 경우

Controller의 해당 메소드 위에 @ResponseStatus(HttpStatus.CREATED)와 같이 애너테이션을 달아줄 수 있다.

상태 코드는 enum으로 제공되어 있어서 경우에 맞는 상태 코드를 지정할 수 있다.

 

    @ResponseStatus(HttpStatus.NO_CONTENT)
    @DeleteMapping("/{postId}")
    public void deletePost(
            @PathVariable Long postId,
            @RequestHeader("password") String password
    ) {
        postService.deletePost(postId, password);
    }

 

 

추가 요구사항 구현

 

선택한 게시글 수정 및 삭제 요청 시,비밀번호가 일치하지 않을 경우 API 요청 실패(예외상황)에 대해 판단할 수 있는 Status Code, Error 메시지등의 정보를 반환합니다.

 

처음 추가 요구사항을 읽었을 때 반환하는 타입이 ResponseDto 등과 같이 정해져있는데

어떻게 에러와 관련된 정보를 반환할 수 있는지 궁금했다.

 

과제 해설 강의에서는 Controller 내에서 에러 처리를 해서 상태 코드와 메세지를 전달하는 방식으로 구현하였다.

 

1. 먼저 처리하고자 하는 예외 상황에 맞는 예외 클래스를 직접 생성한다.

package com.sparta.boardapp.controller.exception;

public class PostNotFoundException extends RuntimeException {
    public PostNotFoundException(String message) {
        super(message);
    }
}

 

 

2. Controller의 모든 반환 타입을 ResponseEntity로 바꾼다.

  • ResponseEntity란?
    • Spring Boot에서 제공하는 클래스인 HttpEntity: HTTP 요청 또는 응답에 해당하는 HttpHeader와 HttpBody를 포함하는 클래스
    • 이를 상속하는 RequestEntity, ResponseEntity
    • HttpStatus, HttpHeaders, HttpBody를 포함
@PostMapping
    public ResponseEntity<PostResponseDto> addPost(
            @RequestBody PostAddRequestDto requestDto
    ) {
        PostResponseDto responseDto = postService.addPost(requestDto);
        return ResponseEntity.status(HttpStatus.CREATED).body(responseDto);
    }

    @GetMapping("/{postId}")
    public ResponseEntity<PostResponseDto> getPost(
            @PathVariable Long postId
    ) {
        PostResponseDto responseDto = postService.getPost(postId);
        return ResponseEntity.ok(responseDto);
    }

    @GetMapping
    public ResponseEntity<List<PostResponseDto>> getPosts() {
        List<PostResponseDto> responseDto = postService.getPosts();
        return ResponseEntity.ok(responseDto);
    }

    @PatchMapping("/{postId}")
    public ResponseEntity<PostResponseDto> updatePost(
            @PathVariable Long postId,
            @RequestBody PostUpdateRequestDto requestDto
    ) {
        PostResponseDto responseDto = postService.updatePost(postId, requestDto);
        return ResponseEntity.ok(responseDto);
    }

    @DeleteMapping("/{postId}")
    public ResponseEntity<Void> deletePost(
            @PathVariable Long postId,
            @RequestHeader("password") String password
    ) {
        postService.deletePost(postId, password);
        return ResponseEntity.noContent().build();
    }

 

 

3. 에러 발생 시 상태 코드와 메세지를 반환할 클래스 ErrorResponseDto를 만든다.

package com.sparta.boardapp.dto.exception;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import lombok.Getter;

@Getter
public class ErrorResponseDto{

    private final Error error;

    public ErrorResponseDto(int status, String msg) {
        this.error = new Error(status, msg);
    }

    @JsonPropertyOrder({"status", "message"})
    record Error(
            int status,
            @JsonProperty("message")
            String msg
    ){}
}

 

 

4. Controller 내에서 @ExceptionHandler를 통해 예외 처리를 한다.

 

ResponseEntity의 body에 앞에서 작성한 에러 클래스를 담아준다.

@ExceptionHandler(PostNotFoundException.class)
    public ResponseEntity<ErrorResponseDto> postNotFoundExceptionHandler(PostNotFoundException ex) {
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(
                new ErrorResponseDto(
                        HttpStatus.NOT_FOUND.value(),
                        ex.getMessage()
                )
        );
    }

 


  • 자바 레코드(Record)
    • 불변 데이터 객체를 쉽게 생성할 수 있도록 하는 새로운 유형의 클래스
    • 기존:
      • 모든 필드에 final 사용
      • 필드값을 모두 포함한 생성자
      • 모든 필드에 대한 접근자 메서드(getter)
      • 로깅 출력을 제공하기 위한 toString 재정의
      • 두 개의 인스턴스를 비교하기 위한 hashCode, equeals 재정의
    • 간결한 방식으로 불변 데이터 객체를 정의할 수 있음
    • 생성자를 작성하지 않아도 되고 toString, equals, hashCoode 메소드에 대한 구현을 자동으로 제공
    • 작성 예시
package com.sparta.boardapp.dto;

import com.sparta.boardapp.entity.PostEntity;

import java.time.LocalDateTime;

public record PostResponseDto(
     Long id,
     String title,
     String author,
     String content,
     LocalDateTime createdAt
) {
    public PostResponseDto(PostEntity savePost) {
        this(
                savePost.getId(),
                savePost.getTitle(),
                savePost.getAuthor(),
                savePost.getContents(),
                savePost.getCreatedAt()
        );
    }
}