코딩 자율학습단/학습 일지

[DAY 11] REST API 구현

young604 2024. 8. 14. 16:23
728x90

REST API URI 설계 

조회 요청 (GET) : /api/articles or /api/articles/{id}

생성 요청 (POST) : /api/articles

수정 요청 (PATCH) : /api/articles/{id}

삭제 요청 (DELETE) : /api/artcles/{id}\

 

여태 만들었던 일반 컨트롤러와 REST 컨트롤러의 차이

일반 컨트롤러는 mustache 파일은 반환한다. 즉, 백엔드단에서 만들어둔 뷰페이지 파일(html)을 반환한다.

그러나 지금부터 만들 REST 컨트롤러는 JSON이나 텍스트와 같은 데이터를 반환한다.

조회 요청 (GET)

    // GET
    @GetMapping("/api/articles")
    public List<Article> index() {
        return articleRepository.findAll();
    }

index() : 모든 게시글 조회

1. @GetMapping()으로 /api/articles으로 오는 get url 요청을 받음

2. 모든 게시글을 조회하는 요청이므로 반환형이 List<Article>인 index() 선언.

3. findAll()으로 모든 게시글을 찾아와야하므로 @Autowired으로 의존성을 주입한 후, articleRepository를 선언, findAll()로 DB에 저장된 Article을 찾아와 return함

show() : 단일 게시글 조회

    @GetMapping("/api/articles/{id}")
    public Article show(@PathVariable Long id) {
        return articleRepository.findById(id).orElse(null);
    }

1. 해당 id에 대해 조회해야하므로 url을 /api/articles/{id}로 변경

2. 단일 게시글이므로 Article로 반환받음. DB에서 id로 검색하여 show()에 매개변수로 받아오기 위해 @PathVariable으로 id를 받아옴.

3. findById()을 이용하여 해당 id의 내용을 DB로부터 받아옴

 

생성 요청 (POST)

Create() : 게시글 생성

    // POST
    @PostMapping("/api/articles")
    public Article create(@RequestBody ArticleForm dto) {
        Article article = dto.toEntity();
        return articleRepository.save(article);
    }

1. @PostMapping()으로 /api/articles으로 오는 post url 요청을 받음

2. http의 body(본문)에 데이터를 실어보내야하므로 @RequestBody 어노테이션 주입해야함, @RequestBody로 dto를 매개변수로 받고, 해당 dto를 엔티티로 변환하여 article 변수에 넣음

3. articleRepository를 통해 DB에 저장한 후 return

수정 요청 (PATCH)

Update() : 게시글 수정

    // PATCH
    @PatchMapping("/api/articles/{id}")
    public ResponseEntity<Article> update(@PathVariable Long id,
                                 @RequestBody ArticleForm dto) {
        // 1. DTO -> 엔티티 변환
        Article article = dto.toEntity();
        log.info("id: {}, article: {}", id, article.toString());
        // 2. 타깃 조회
        Article target = articleRepository.findById(id).orElse(null);
        // 3. 잘못된 요청 처리
        if (target == null || id != article.getId()) {
            // 400, 잘못된 요청 응답
            log.info("잘못된 요청! id: {}, article: {}", id, article.toString());
            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null);
        }
        // 4. 업데이트 및 정상 응답(200)
        target.patch(article); // 기존 데이터에 새 데이터 붙임
        Article updated = articleRepository.save(target); // 수정 내용 db에 최종 저장
        return ResponseEntity.status(HttpStatus.OK).body(updated);
    }
    
    //entity/Article.java
    // 데이터 일부 수정할 경우 동작
    public void patch(Article article) {
        if (article.title != null)
            this.title = article.title;
        if (article.content != null)
            this.content = article.content;
    }

1. @PatchMapping()으로 /api/articles으로 오는 patch url 요청을 받음

2. 클라이언트에게 받은 수정 데이터를 담을 dto를 선언, dto를 엔티티로 변환하여 article 변수에 저장

3. DB에서 수정할 엔티티를 findById()를 통해 조회

4. 대상 엔티티가 없거나 요청한 id가 본문의 id와 다를 시 잘못된 요청이므로 상태코드 400(BAD_REQUEST)를 반환

5. 잘못된 요청이 아닌 경우, 수정할 데이터를 업데이트하고, 상태코드 200(OK)를 반환

수정 요청 데이터는 article에 저장되어 있음, 수정할 기존 데이터는 target에 저장되어있으므로 일부 데이터만 수정할 경우그냥 덮어씌워버리면 수정하지 않을 데이터가 소실됨. patch()를 만들어서 article에서 null이 아닌 값만 갱신하도록 해야함!

삭제요청 (DELETE)

Delete() : 게시글 삭제

    // DELETE
    @DeleteMapping("/api/articles/{id}")
    public ResponseEntity<Article> delete(@PathVariable Long id) {
        // 1. 대상 찾기
        Article target = articleRepository.findById(id).orElse(null);
        // 2. 잘못된 요청 처리
        if (target == null) {
            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null);
        }
        // 3. 대상 삭제
        articleRepository.delete(target);
        return ResponseEntity.status(HttpStatus.OK).build();
    }

1. @DeleteMapping()으로 /api/articles으로 오는 delete url 요청을 받음

2. DB에서 findByid로 해당 id를 가진 엔티티가 있는지 조회

3. 해당 id를 가진 엔티티(데이터)가 없을 경우, 상태 코드 400(BAD_REQUEST)를 보냄

4. 해당 id를 가진 엔티티가 있다면, 대상 엔티티를 삭제함. 삭제 후 상태 코드 200(OK), 본문은 null로 보냄(body(null)과 build()는 동일)

Trouble Shooting

문제 상황

post 요청을 Talend API Tester로 테스트하던 중 해당 오류 로그와 500(internet Server error)로 실행되지 않음

fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of
com.example.firstproject.dto.ArticleForm
(no Creators, like default constructor, exist): cannot deserialize from Object value

해결 방법

// @allargsConstructor 사용하니 기본 생성자 없다고 오류 발생
// @all 지우고 @no 와 @setter 붙이니 오류 풀림
@Setter
@NoArgsConstructor
@ToString
public class ArticleForm {
    private Long id;
    private String title; // 제목 필드
    private String content; // 내용 필드

    public Article toEntity() {
        return new Article(id, title, content);
    }
}

(no Creators, like default constructor, exist)

ArticleForm에 기본 생성자가 없다는 뜻으로 ArticleForm.java로 가보니 @AllArgsConstructor 어노테이션이 주입되어 있었다.

찾아보니 @AllargsConstructor는 경우에 따라 심각한 에러가 발생할 수 있다고 한다...

@AllargsConstructor는 클래스 내부에 선언된 모든 field마다 하나의 parameter를 가진 생성자를 생성한다.

jackson이라는 JAVA에서 JSON 처리 라이브러리 중 하나인데, 주로 객체와 JSON간의 변환(직렬화 및 역직렬화)를 처리하기 위해 사용된다.

@AllargsConstructor는 모든 필드를 포함하는 생성자를 생성하지만, 기본 생성자를 만들어주지는 않는다. 컴파일러가 생성자가 없을때 자동으로 기본 생성자를 추가해주는데, Jackson이 ArticleForm 객체를 역직렬화할때 기본 생성자가 없어서 이 오류가 발생한다.

그리하여 ArticleForm.java에 @AllargsConstructor를 지우고 @NoArgsConstructor를 넣었지만 또 오류 발생

해당 필드가 private 라서 jackson이 접근할 수 없기 때문에 @Setter 추가하여 해결했다

질문게시판에 아무도 이에 대해 질문이 없는 걸 보면 내 코드 중에 뭔가 잘못된건가 싶기도 하고, 나중에 오류가 또 터지면 체크하도록 기록해둔당.

728x90