[스프링 인 액션] 6장 REST 서비스 생성하기

    6장 REST 서비스 생성하기

    💻 실습 : https://github.com/cusbert/spring-in-action-5th

    🎯 이 장에서 배우는 내용

    • 스프링 MVC에서 REST 엔드포인트 정의하기
    • 하이퍼링크 REST 리소스 활성화 하기
    • 리퍼지터리 기반의 REST 엔드포인트 자동화

    6.1 REST 컨트롤러 작성하기

    • 스프링 MVC 의 HTTP 요청-처리 어노테이션
      HTTP 용도
    @GetMapping HTTP GET 요청 리소스 데이터 읽기
    @PostMapping HTTP POST 요청 리소스 생성
    @PutMapping HTTP PUT 요청 리소스 변경
    @PatchMapping HTTP PATCH 요청 리소스 변경
    @DeleteMapping HTTP DELETE 리소스 삭제
    @RequestMapping 다목적 요청 처리  
    @RestController
    @RequestMapping("/foos")
    class FooController {
    
        @Autowired
        private IFooService service;
    
        @GetMapping
        public List<Foo> findAll() {
            return service.findAll();
        }
    
        @GetMapping(value = "/{id}")
        public Foo findById(@PathVariable("id") Long id) {
            return RestPreconditions.checkFound(service.findById(id));
        }
    
        @PostMapping
        @ResponseStatus(HttpStatus.CREATED)
        public Long create(@RequestBody Foo resource) {
            Preconditions.checkNotNull(resource);
            return service.create(resource);
        }
    
        @PutMapping(value = "/{id}")
        @ResponseStatus(HttpStatus.OK)
        public void update(@PathVariable( "id" ) Long id, @RequestBody Foo resource) {
            Preconditions.checkNotNull(resource);
            RestPreconditions.checkNotNull(service.getById(resource.getId()));
            service.update(resource);
        }
    
        @DeleteMapping(value = "/{id}")
        @ResponseStatus(HttpStatus.OK)
        public void delete(@PathVariable("id") Long id) {
            service.deleteById(id);
        }
    
    }

    Rest API controller 기본 템플릿

    public class RestPreconditions {
        public static <T> T checkFound(T resource) {
            if (resource == null) {
                throw new MyResourceNotFoundException();
            }
            return resource;
        }
    }

    6.1.1 데이터 조회

    데이터를 조회 할 때는 @GetMapping를 사용한다.

    @RestController
    @RequestMapping(path = "/design", produces = "application/json")
    @CrossOrigin(origins = "*")
    public class DesignTacoController {
        private TacoRepository tacoRepo;
    
        public DesignTacoController(TacoRepository tacoRepo) {
            this.tacoRepo = tacoRepo;
        }
    
        // 페이지 목록
        @GetMapping("/recent")
        public Iterable<Taco> recentTacos() {
            PageRequest page = PageRequest.of(
                    0, 12, Sort.by("createdAt").descending());
            return tacoRepo.findAll(page).getContent();
        }
    
        // 아이템 1개 조회
        @GetMapping("/{id}")
        public ResponseEntity<Taco> tacoById(@PathVariable("id") Long id) {
            Optional<Taco> optTaco = tacoRepo.findById(id);
            if (optTaco.isPresent()) {
                return new ResponseEntity<>(optTaco.get(), HttpStatus.OK);
            }
            return new ResponseEntity<>(null, HttpStatus.NOT_FOUND);
        }
    }
    • @RestController : Controller의 모든 HTTP 요청 처리 메서드에서 HTTP 응답을 Response Body에 직접 반환한다. 따라서 반환값이 뷰를 통해 HTML 로 변환되지 않고 직접 HTTP 응답으로 브라우저에 전달되어 내려간다.

    6.1.2 데이터 전송

    데이터를 입력할 때는 클라이언트에서 @PostMapping 으로 데이터를 전송한다.

    @PostMapping(consumes="application/json") 
    @ResponseStatus(HttpStatus.CREATED) 
    public Order postOrder(@RequestBody Order order) {
      return repo.save(order);
    }
    • @RequestBody : Request Body에 Json 데이터가 Order 객체로 변환 되어 Order 매게 변수에 바인딩 된다.
    • @PostMapping(consumes="application/json") : application/json 만 처리
    • @ResponseStatus(HttpStatus.CREATED) : HTTP 201(CREATE) 전달, HTTP 200 OK 보다 명확하다.

    6.1.3 데이터 변경

    데이터를 변경하기 위한 HTTP 메소드로는 PUT 과 PATCH가 있다.

    • PUT 은 데이터 전체를 교체한다.
    • PATCH 는 데이터의 일부분을 변경하는 것이다.
    @PutMapping(path="/{orderId}", consumes="application/json")
    public Order putOrder(@RequestBody Order order) {
      return repo.save(order);
    }

    PUT은 해당 URL 에 이 데이터를 쓰라는 의미이므로 이미 존재하는 해당 데이터 전체를 교체한다. 만약 해당 주문의 속성이 생략되면 이 속성의 값은 null 이 된다.

    @PatchMapping(path="/{orderId}", consumes="application/json")
    public Order patchOrder(@PathVariable("orderId") Long orderId,
                            @RequestBody Order patch) {
    
      Order order = repo.findById(orderId).get();
      if (patch.getDeliveryName() != null) {
        order.setDeliveryName(patch.getDeliveryName());
      }
      if (patch.getDeliveryStreet() != null) {
        order.setDeliveryStreet(patch.getDeliveryStreet());
      }
      if (patch.getDeliveryCity() != null) {
        order.setDeliveryCity(patch.getDeliveryCity());
      }
      if (patch.getDeliveryState() != null) {
        order.setDeliveryState(patch.getDeliveryState());
      }
      if (patch.getDeliveryZip() != null) {
        order.setDeliveryZip(patch.getDeliveryState());
      }
      if (patch.getCcNumber() != null) {
        order.setCcNumber(patch.getCcNumber());
      }
      if (patch.getCcExpiration() != null) {
        order.setCcExpiration(patch.getCcExpiration());
      }
      if (patch.getCcCVV() != null) {
        order.setCcCVV(patch.getCcCVV());
      }
      return repo.save(order);
    }

    따라서 데이터의 일부만 변경하고자 할 때는 PATCH를 사용해야 한다.
    해당 주문 데이터를 전송된 Order 객체로 완전히 교체하는 대신 Order 객체의 각 필드 값이 null 이 아닌지 확인하고 기존 데이터를 변경해야한다. 이 방법을 사용하면 클라이언트에서 변경할 속성만 전송하면 된다. 그리고 서버에서는 클라이언트에서 지정하지 않은 속성의 기존 데이터를 그대로 보전할 수 있다.

    6.1.4 데이터 삭제

    데이터를 삭제할 때는 @DeleteMapping을 사용한다.

    @DeleteMapping("/{orderId}")
    @ResponseStatus(HttpStatus.NO_CONTENT) // 20
    public void deleteOrder(@PathVariable("orderId") Long orderId) {
      try {
        repo.deleteById(orderId);
      } catch (EmptyResultDataAccessException e) {}
    }
    • @ResponseStatus(HttpStatus.NO_CONTENT) : 삭제 성공시 HTTP 204 NO_CONTENT 전송한다. 대개의 경우 DELETE 요청 응답은 response body를 가지지 않는다.
    • EmptyResultDataAccessException : 정상적으로 삭제할 데이터가 없다면 EmptyResultDataAccessException를 발생 시킨다. 단 예외가 발생하여도 특별히 할 일은 없다.

    📌 요약

    • REST 엔드포인트는 스프링 MVC, 그리고 브라우저 지향의 컨트롤러와 동일한 프로그래밍 모델을 따르는 컨트롤러로 생성할 수 있다.
    • 모델과 뷰를 거치지 않고 요청 응답 몸체에 직접 데이터를 쓰기 위해 컨트롤러의 핸들러 메소드에는 @ResponseBody 어노테이션을 지정할 수 있으며, ResponseEntity 객체를 반환 할 수 있다.
    • @RestController 어노테이션을 컨트롤러에 지정하면 해당 컨트롤러의 각 핸들러 메서드에 @ResponseBody를 지정하지 않아도 되므로 컨트롤러를 단순화해 준다.
    • 스프링 HATEOAS는 스프링 MVC에서 반환되는 리소스의 하이퍼링크를 추가 할 수 있게 한다.
    • 스프링 데이터 리퍼지터리는 스프링 데이터 REST 를 사용하는 REST API 로 자동 노출 될 수 있다.

    참고

    예제 코드 수정할 때 참고

    반응형

    댓글

    Designed by JB FACTORY