본문 바로가기
프로그래밍 공부/Spring-Boot

[lesson] Spring - REST

by Luna_lua 2023. 1. 10.
반응형

 REST (Representational State Teansfer) (직역 : 전송하기 위한 대표상태를 전달)

-> 하나의 URI는 하나의 고유한 리소스를 대표하도록 설계 된다.

 

이전의 URL = /board/list

REST 이후의 URI = /board/123 (경로가 아닌 하나의 대표를 의미 -> 123번의 게시물)

=> /board/123을 입력하면 이전에는 html이었겠지만 이제는 페이지가 아닌 정보가 나온다(Xml, Text. JSON)

 

 그로인해서 URI에 변수를 사용할 수 있다.

 

※ URL vs URI

URL = Location으로 경로를 의미하며, URI = Identify로 정보를 의미한다.

URL = '너가 요청한 경로의 파일을 보여줄게!'

URI = '너가 요청한 정보는 이거야!'

 

※ 데이터의 처리는 주로 JS를 사용한다. 자바도 사용할 수 있지만 그러기에는 너무 무겁다. (JAVA의 기능은 깊은 연산을 하기 위해서지만, 데이터를 요청하고 데이터 처리만 해주기 때문에 JS가 더욱 적합하다.)

 

Rest! => 타 서버간 데이터 전송이 원활하고 언어와 서버의 제약 없이 데이터를 교환할 수 있다.

 

 

일반클래스 + REST방식의 메소드 => REST API
REST방식의 클래스 + REST방식의 메소드 => RESTFUL

 

RESTFUL방식의 어노테이션은 @RestController

REST방식의 어노테이션은 @Controller를 선언해주고, REST방식의 메소드에 @ResponseBody라는 어노테이션을 달아준다. -> 원래 컨트롤러에서 일반메소드의 return타입은 페이지의 경로이지만 여기서는 데이터 정보가 나온다.

 

 

 

 

SpingBoot 실습!

0. 사용하기전 Gson을 사용하기 위해 pom.xml에 dependency를 추가하고 진행하자!

<dependency>
   <groupId>com.google.code.gson</groupId>
   <artifactId>gson</artifactId>
   <version>2.8.2</version>
</dependency>

 

1.  [사용방식 1] 브라우저에서 JSON타입으로 데이터를 전송하고 서버에서는 댓글의 처리 결과에 따라 문자열로 결과를 리턴한다.

+) 경로를 보면 /new로 되어있을때도 있고, get으로 /가 빠져있을수 있어서 혼선이 있을수 있으나,@RequestMapping에서 /로 끝나게 해주어 경로에 /로 시작하면 붙이지 않고, /로 시작하지 않는다면 /를 붙여 경로를 만들어준다.

 // 댓글 등록
 
 @PostMapping(value = "/new", consumes = "application/json", produces = "text/plain; charset=utf-8")
    public ResponseEntity<String> create(@RequestBody ReplyVO replyVO) throws UnsupportedEncodingException {

        int replyCount = replyService.register(replyVO);
        log.info("ReplyVO : " + replyVO);
        log.info("REPLY INSERT COUNT : " + replyCount);
        return replyCount == 1 ?
                new ResponseEntity<>(new String("댓글 등록 성공".getBytes(), "UTF-8"), HttpStatus.OK) :
                new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
    }

1-1. consumes : 전달 받을 데이터 타입, produces : 전달 할 데이터 타입 (2개 모두 전달할 내용이 없다면 생략해도 된다.)

1-2. 위에서 응답할 타입은 String 타입이다. 이것의 문자설정도 해서 보내주어야 한다. -> 그러나 String 객체에서 직접 문자타입을 세팅하면 에러나기 때문에 throws UnsupportedEncodingException을 적어주어야 한다.

1-3. ResponseEntity : 응답할 정보의 객체이며 <>(제네릭)에 들어가는 타입은 반환해주어야 하는 타입을 적어준다.

위의 예제로 댓글 하나면 ReplyVO, 댓글이 여러개면 LIST, 문자열이면 String 과 같이 적어주면 된다.

 

But! return "~~"을 사용하면 안된다. => 서버의 상태도 변경해서 보내주어야 하기 때문이다! Ajax에서는 성공과 실패에 대한 구분(Success[200]/error[500, 404, 403, 203...]의 속성)이 있기 때문에 그 성공/실패에 대한 정보도 담아서 보내주기 위함이다.

 

★ Point! 원래는 JS서버를 사용해서 JS를 사용하는것이 올바른 로직이나 이번에는 JAVA로 처리하겠다.

(복잡한 연산이 있을때는 JAVA가 훨씬 안정적이고 빠르기 때문이다. 페이지나 데이터가 간단하다면 굳이 JAVA를 사용할 필요가 없다. 여기서의 무겁다는 결제 기능에 대한 연산들이 있는 페이지를 말한다.)

(과도한 요청에 대한 분기 처리등 필요한 기능들이 JS서버에 있기 때문이다. [주로 사용하는 서버 : Node express])

 

+ SPA (Single Page application)

하나의 페이지에 모든것을 비동기 방식으로 Body태그만 가져와 페이지를 바꾼것처럼 표출해준다. 중요한점은 페이지가 바뀐것이 아니기에 히스토리가 남지 않는다. 페이지가 하나!

 

★Point = 우리가 REST를 만들고 있기 때문에 ajax에서 정보를 주고 받고, 다른 페이지에 데이터를 넘겨줄 필요가 없다.

페이지 이동을 안할때 REST를 사용하며, 하나의 페이지에서 DB조회도 다 하고 싶을때 REST방식을 사용한다!

 

 

2. [사용 방식 2] URI에 변수를 선언하여 값을 받아올 수 있다.

//    게시글 댓글 전체 조회
    @GetMapping("pages/{bno}/{page}")
    public List<ReplyVO> getList(@PathVariable("bno") Long bno, @PathVariable("page") int page){
        log.info("getList..........................");
        Criteria criteria = new Criteria(page, 10);
        log.info(criteria.toString());
        return replyService.getList(bno, criteria);
    }

2-1 이제는 URI로 요청하는것이기 때문에 URI에 변수를 선언해서 받아올 수 있으며,

변수의 매칭을 할때는 @PathVariable 어노테이션을 사용하여 지정해준다.

또한 consumes로 받은 데이터를 매개변수의 객체에 지정을 해줄때는 @RequestBody를 사용해서 지정해준다.

 

2-2 1번의 실습을 보면 ResponseEntity로 보내야 하지만 스프링부트는 자동으로 변환(보내주는 데이터 세팅과 서버 상태)을 해주기 때문에 반환하는 객체 타입을 리턴타입에 작성해도 된다.

 

+ 리턴타입에 JSON을 입력하여 ResponseEntity타입으로 반환하려면 Jackson이라는 라이브러리가 필요하다.

(JSON이 아닌걸 JSON으로 바꿔주는 라이브러리)

 

+ 하지만 스프링부트는 자동으로 되니까 Jackson을 사용하지는 않겠다.

 

+ Get방식은 URL창에 지정해서 테스트 할 수 있지만 POST, PUT, PATCH, DELETE등의 방식은 안되기 때문에 이럴땐 구글 크롬에서 웹스토어에 Talend API를 설치하여 테스트를 하면된다.

 

 

3. 지금까지 GET, POST방식만 썼다면 PUT(수정), PATCH(수정), DELETE(삭제)를 사용하여 전달해보자!

전송방식에 따라 페이지처리를 할지, 데이터 처리를 할지 판단(메소드 맵핑)을 하기 위함이다.

 댓글 수정
//    PUT : 자원의 전체 수정, 자원 내 모든 필드를 전달해야 함, 일부만 전달할 경우 오류
//    PATCH : 자원의 일부 수정, 수정할 필드만 전송 (자동 주입이 아닌 부분만 수정하는 쿼리문에서 사용)
//    PATCH가 PUT을 담고 있기 때문에 전체를 전달 받아서 저네를 수정하는 상황, 전체 중 부분만 수정하는 상황 모두 PATCH를 사용하는것이 좋다.
    @RequestMapping(
            method = {RequestMethod.PUT, RequestMethod.PATCH},
            value = "{rno}", consumes = "application/json", produces = "text/plain; charset=UTF-8"
    )
    public ResponseEntity<String> modify(@RequestBody ReplyVO replyVO, @PathVariable("rno") Long rno) throws UnsupportedEncodingException{
        log.info("modify...............");
        replyVO.setRno(rno);
        return replyService.modify(replyVO) == 1 ?
                new ResponseEntity<>(new String("댓글 수정 성공".getBytes(), "UTF-8"), HttpStatus.OK) :
                new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
    }

3-1 PUT vs PATCH (쿼리문중 UPDATE를 보면 SET에 모든 컬럼을 수정하지 않는다! 이때 PATCH를 사용하고, 전체 컬럼을 수정할때 PUT 사용한다.)

PUT은 자원의 전체를 수정할 때 사용, 하나라도 빠져있다면 에러

PATCH는 자원의 일부를 수정할 때 사용, 그러나 PATCH안에 PUT의 기능이 들어있기 때문에 일부던, 전체던 주로 PATCH를 사용한다.


3-2 메소드를 실행하는것은 PUT방식과 PATCH방식 2가지이기 때문에 method에 2가지를 전부 넣어준다.

 

3-3 2번 실습은 URI로 데이터를 변수에 담아서 가져오지만 3번 실습은 기능에 필요한 객체 정보를 담아야 하기 때문에 consumes를 사용하여 json의 데이터를 받아와서 사용한다.

 

3-4 수정이 성공인지 실패인지 알리기 위해 produces를 사용해 text로 리턴한다. (String)

=> 3-3,4가 이해가 되지 않는다면 1번 실습을 다시 보자!

 

 

 

 

 

+. RESTFUL의 전체 소스

package com.example.board.controller;

import com.example.board.beans.vo.Criteria;
import com.example.board.beans.vo.ReplyVO;
import com.example.board.services.ReplyService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.io.UnsupportedEncodingException;
import java.util.List;

@RestController
@Slf4j
@RequiredArgsConstructor
@RequestMapping("/replies/*")
public class ReplyController {

    private final ReplyService replyService;

//  댓글 등록
//    consumes 전달받을 데이터 타입
//    produces 전달할 데이터 타입
//    브라우저에서 JSON타입으로 데이터를 전송하고 서버에서는 댓글의 처리결과에 따라 문자열로 결과를 리턴한다.
//    consumes : Ajax를 통해 전달받은 데이터 타입
//    Ajax의 success:function(result)에 있는 result로 전달할 데이터 타입
//    @ResponseBody : @Controller에서 Rest API를 구현하기 위해서 사용된다.

//    문자열을 전달할 때 한글이 깨지지 않게 하기 위해서는 text/plain; charset=utf-8을 작성한다.
//    ResponseEntity : 서버의 상태 코드, 응답 메세지 등을 담을 수 있는 타입이다.
//    @PostMapping(value = "/new", consumes = "application/json", produces = "text/plain; charset=utf-8")
//    public ResponseEntity<String> create(ReplyVO replyVO) throws UnsupportedEncodingException{
//        return replyService.register(replyVO) == 1?
//                new ResponseEntity<>(new String("댓글 등록 성공").getBytes(), "UTF-8"), HttpStatus.OK) :
//                new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
//    }

    @PostMapping(value = "/new", consumes = "application/json", produces = "text/plain; charset=utf-8")
    public ResponseEntity<String> create(@RequestBody ReplyVO replyVO) throws UnsupportedEncodingException {

        int replyCount = replyService.register(replyVO);
        log.info("ReplyVO : " + replyVO);
        log.info("REPLY INSERT COUNT : " + replyCount);
        return replyCount == 1 ?
                new ResponseEntity<>(new String("댓글 등록 성공".getBytes(), "UTF-8"), HttpStatus.OK) :
                new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
    }

//    게시글 댓글 전체 조회
    @GetMapping("pages/{bno}/{page}")
    public List<ReplyVO> getList(@PathVariable("bno") Long bno, @PathVariable("page") int page){
        log.info("getList..........................");
        Criteria criteria = new Criteria(page, 10);
        log.info(criteria.toString());
        return replyService.getList(bno, criteria);
    }

//    댓글 조회
//    URI에 댓글 번호만 작성한다.
//    전달 받은 tno를 JSON으로 리턴한다.
    @GetMapping("{rno}")
    public ReplyVO get(@PathVariable("rno") Long rno){
        log.info("get....................");
        return replyService.get(rno);
    }

//    댓글 수정
//    PUT : 자원의 전체 수정, 자원 내 모든 필드를 전달해야 함, 일부만 전달할 경우 오류
//    PATCH : 자원의 일부 수정, 수정할 필드만 전송 (자동 주입이 아닌 부분만 수정하는 쿼리문에서 사용)
//    PATCH가 PUT을 담고 있기 때문에 전체를 전달 받아서 저네를 수정하는 상황, 전체 중 부분만 수정하는 상황 모두 PATCH를 사용하는것이 좋다.
    @RequestMapping(
            method = {RequestMethod.PUT, RequestMethod.PATCH},
            value = "{rno}", consumes = "application/json", produces = "text/plain; charset=UTF-8"
    )
    public ResponseEntity<String> modify(@RequestBody ReplyVO replyVO, @PathVariable("rno") Long rno) throws UnsupportedEncodingException{
        log.info("modify...............");
        replyVO.setRno(rno);
        return replyService.modify(replyVO) == 1 ?
                new ResponseEntity<>(new String("댓글 수정 성공".getBytes(), "UTF-8"), HttpStatus.OK) :
                new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
    }

//    댓글 삭제
//    URI로 댓글 번호를 전달받은 후 성공 시 댓글 삭제 성공 전달
    @DeleteMapping(value = "{rno}", produces = "text/plain; charset=UTF-8")
    public ResponseEntity<String> remove(@PathVariable("rno") Long rno) throws UnsupportedEncodingException{
        log.info("remove....");
        return replyService.remove(rno) == 1?
                new ResponseEntity<>(new String("댓글 삭제 성공".getBytes(),"UTF-8"), HttpStatus.OK) :
                new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
    }

}
반응형