상태코드는 크게 5가지 분류로 정리
JSON은 {Key : Value} 의 형태
Value 로 또다른 JSON이나 배열을 넣을 수 있다.
REST API의 주소설계
GET : 조회
POST : 생성
PATCH : 수정
DELETE : 삭제
request를 받아서 json으로 반환할 RestController와 적절한 상태 코드 반환을 위한 ResponseEntitiy 클래스
지금까지 만들었던 Article을 Rest API로 만들어보자
package com.example.firstproject.api;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController //일반 컨트롤러는 뷰페이저를 반환하지만 RestController는 json을 반환한다
public class FirstApiController {
@GetMapping("/api/hello")
public String hello(){
return "hello world!";
}
}
클래스의 어노테이션이 @RestController다.
이제까지 만든 컨트롤러는 어노테이션이 @Controller였다.
string으로 뷰페이저 (mustache 파일) 을 반환했었다.
하지만 RestController는 json을 반환한다. (다른것도 반환하긴함)
/api/hello 를 요청하면 hello world를 반환하게 해보자
이렇게 hello world! 문자열이 화면에 표시됨
전에 사용했던 Talend API를 이용해서도 확인해 볼 수 있다.
RestController와 Controller의 차이
@Controller
public class FirstController {
@GetMapping("/hi")
public String niceToMeetYou(Model model){
model.addAttribute("username","몽이");
return "greeting";
}
그냥 Controller는 greeting.mustache 즉 뷰페이저 반환
응답 바디를 보면 html 코드를 리턴하는 것을 확인할 수 있다.
반면에 RestController는 일반적으로 json 즉 데이터를 반환한다.
REST API CRUD 기능을 만들어보자
package com.example.firstproject.api;
import com.example.firstproject.entity.Article;
import com.example.firstproject.repository.ArticleRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController //Rest API용 컨트롤러, 데이터(JSON)를 반환한다
public class ArticleApiController {
@Autowired // DI : 의존성 주입, 외부에서 가져옴
private ArticleRepository articleRepository;
//GET
@GetMapping("/api/articles")
public List<Article> index(){
//리포지토리를 이용해서 모든 article을 가져옴
return articleRepository.findAll();
}
@GetMapping("/api/articles/{id}")
public Article show(@PathVariable Long id){
return articleRepository.findById(id).orElse(null);
}
//POST
//PATCH
//DELETE
}
먼저 GET을 구현해보자
index 메소드로 /api/articles 에 접속하면 전체 글 목록을 반환하고 싶다. 글은 db에 있으니까 리포지토리를 이용해서 접근해야겠다. 그리고 리포지토리에서 가져왔으니까 엔티티 객체인 Article일 것이다. articleRepository.findAll()을 이용하자. 이 때, ArticleRepository를 필드에 추가하고 @Autowired로 DI = 의존성 주입 (스프링부트가 알아서 객체를 만들어줌) 을 사용했다
그리고 show 메소드로 articles/id로 들어가면 해당 글을 반환하고 싶다. articleRepository의 findById를 이용해서 얻어온다. 이 때 PathVariable로 매개변수에 id를 전달하고 findById 뒤에는 orElse를 연이어서 호출해줘야 한다.
POST를 구현해보자
//POST
@PostMapping("/api/articles")
public Article create(@RequestBody ArticleForm dto){
//dto로(ArticleForm) 클라이언트가 보낸 json을 받아서 엔티티인 Article로 변환함
Article article = dto.toEntitiy();
return articleRepository.save(article);
}
api/articles에 POST 요청을 하면 받는 메소드를 만든다. POST 요청이니까 PostMapping 어노테이션을 사용한다.
ArticleForm을 매개변수로 받는데 dto는 원래 사용자가 폼에 입력한 데이터를 받는데 json으로 http request body에 넣은 데이터를 받기 위해 @RequestBody 어노테이션을 넣어준다.
dto를 DB에 저장해야 하므로 toEntitiy 메소드로 변환해주고 리포지토리를 이용해서 save 해준다.
//PATCH
@PatchMapping("/api/articles/{id}")
//ResponseEntitiy에 담아서 보내면 상태 코드를 같이 담을 수 있다
public ResponseEntity<Article> update(@PathVariable Long id, @RequestBody ArticleForm dto) {
//1. 수정용 엔티티 생성
Article article = dto.toEntitiy();
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)
Article updated = articleRepository.save(article);
return ResponseEntity.status(HttpStatus.OK).body(updated);
}
PATCH 요청은 수정을 위해 /articles/id로 PATCH 메소드로 요청을 보내는 경우다.
update 메소드를 만들고 반환형은 ResponsiEntitiy{Article} 형으로 해야 응답 메세지까지 같이 보낼 수 있다.
id가 요청 url에 있으므로 @PathVariable로 가져오고 요청할 때 받은 json 객체를 dto로 만들기 위해 @RequestBody로 매개변수로 받는다.
위는 올바른 요청을 보낸 경우
잘못된 요청을 보낸 경우
이렇게 작동하긴 하는데 문제점이 있다.
만약 request를 보낼때 json에서 title을 빼고 보내면 어떻게 될까?
title이 null로 수정된 것을 확인할 수 있다. 이 내용을 보완해보자.
//PATCH
@PatchMapping("/api/articles/{id}") //ResponseEntity에 담아서 보내면 상태 코드를 같이 담을 수 있다
public ResponseEntity<Article> update(@PathVariable Long id, @RequestBody ArticleForm dto) {
//1. 수정용 엔티티 생성
Article article = dto.toEntitiy();
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("Wrong Request! id : {}, article : {}",id,article.toString());
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null);
}
//4. 업데이트 및 정상 응답(200)
target.patch(article);
Article updated = articleRepository.save(target);
return ResponseEntity.status(HttpStatus.OK).body(updated);
}
update 메소드를 이렇게 바꿔주자.
수정하기 전에는 수정용 엔티티인 article을 만들어서 article을 리포지토리에 save 해줬는데,
article을 target 즉 원래 있던 엔티티에 patch 메소드로 추가해주고 추가된 target을 리포지토리에 save 하자.
이렇게 하기 위해서는 엔티티 클래스에 patch 메소드를 만들어야 한다.
package com.example.firstproject.entity;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import javax.persistence.*;
@Getter
@Entity // 이 어노테이션을 붙여야 DB가 이 객체를 인식한다
@AllArgsConstructor
@NoArgsConstructor //디폴트 생성자 추가
@ToString
public class Article {
@Id // 각 객체를 식별하기 위한 Id임 (주민번호 같은거)
@GeneratedValue(strategy = GenerationType.IDENTITY) // DB가 id를 자동생성함
private Long id;
@Column //DB가 필드를 인식할 수 있게 해줌
private String title;
@Column
private String content;
public void patch(Article article){
if (article.title!=null){
this.title = article.title;
}
if (article.content != null){
this.content = article.title;
}
}
}
Article 클래스에 patch 메소드를 추가해줬다. 단순히 새로 추가하려는 엔티티에 제목이 있으면 원래 엔티티에 제목 추가, 내용이 있으면 내용 추가하는 메소드다.
title을 생략하고 request 를 보내자 원래 있던 게시물의 title은 유지됨
//DELETE
@DeleteMapping("/api/articles/{id}")
public ResponseEntity<Article> delete(@PathVariable Long id){
// 대상 찾기
Article target = articleRepository.findById(id).orElse(null);
//잘못된 요청 처리
if (target==null){
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null);
}
// 대상 삭제
articleRepository.delete(target);
// 데이터 반환
return ResponseEntity.status(HttpStatus.OK).body(null);
}
delete 메소드는 DeleteMapping으로 매핑한다.
반환형은 ResponseEntitiy인데 응답 코드를 같이 반환해야 할 때는 이렇게 한다.
데이터 반환할 때 ResponseEntitiy.status(HttpStatus.OK).build()로 해도 된다.
(