REST API

2024. 12. 17. 16:04·웹공부/SPRING

1. 기존

기존 브라우저 

  • GET : 요청정보를 URL에 담아서 HTML문서를 주세요.
  • POST : 요청정보를 패킷에 담아서 HTML문서를 주세요.

VS

 

REST : 자원을 이름으로 구분하여 해당 자원의 상태를 주고 받으며 mustache가 필요없다.

  1. GET : 자원의 정보를 조회해주세요.
  2. POST : 자원을 생성해주세요.
  3. PUT, PATCH : 자원을 줄테니 업데이트해주세요. (PUT: 본문 전체, PATCH:일정 요소만) 
  4. DELETE : 자원을 삭제해주세요

 

- 장점 

  • API서버는 세션, 쿠키를 사용하지않고 API요청만 단순히 처리한다. 
  • 별도의 인프라 구축이 필요 없다.
  • HTTP 표준만 지키면 모든 플랫폼에서 사용이 가능하다.

 

- 단점

  • 표준 자체가 존재하지 않아 정의가 필요하다.
  • HTTP Method 형태가 제한적이다.(검색은 없음)
  • Header값 수정이 어려워 RestClient(Postman 등)으로 테스트를 진행한다.

 

- 특징

http://www.example.com/members/:id/orders

  • URI는 자원을 나타내야 한다. (소문자)
  • 컬렉션(colection) : 복수형의 자원 👉🏻 emails/:id
  • 고유한 객체 👉🏻  :id
  • 언더바 대신 하이픈 사용 👉🏻  /user-profile
  • URI에 파일 확장자를 포함하지 않음 .X
  • 행위를 포함하지 않는다.  👉🏻  POST를 앞에 붙임

 

 

1. 프로젝트 생성

 

2. 환경설정 - validation 추가

- application.yaml

spring:
  datasource:
    driver-class-name: org.h2.Driver
    url: jdbc:h2:~/library
    username: sa
    password: 1234
  h2:
    console:
      enabled: true
      path: /h2-console
  jpa:
    hibernate:
      ddl-auto: create
    show-sql: true

 

- build.gradle

//validation
implementation 'org.springframework.boot:spring-boot-starter-validation'

 

3. 앤티티 및 DTO

- domain/Book

package com.dream_flow.book.domain;

import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Entity
public class Book {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, length = 45)
    private String title;
    @Column(nullable = false, length = 45)
    private String subTitle;
    @Column(length = 45)
    private String author;
    @Column(length = 45)
    private String publisher;
    @Enumerated(value = EnumType.STRING)
    private Status status;

    public enum Status {
        BORROWED,
        AVAILABLE
    }
}

 

- dto/BookDTO

package com.dream_flow.book.dto;

import com.dream_flow.book.domain.Book;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

public class BookDTO {

    @Getter
    @Setter
    @AllArgsConstructor
    @NoArgsConstructor
    public static class Post {

        @NotBlank(message = "제목은 필수 입력 값입니다.")
        @NotNull(message = "제목은 반드시 입력하셔야 합니다.")
        @Size(min = 1, max = 45, message = "제목은 45자 이하여야 합니다.")
        private String title;

        @NotBlank
        @NotNull
        @Size(max = 45)
        private String subTitle;

        @Size(max = 45)
        private String author;

        @Size(max = 45)
        private String publisher;
    }

    @Getter
    @Setter
    @AllArgsConstructor
    @NoArgsConstructor
    public static class Put {
        @NotBlank(message = "제목은 필수 입력 값입니다.")
        @NotNull(message = "제목은 반드시 입력하셔야 합니다.")
        @Size(min = 1, max = 45, message = "제목은 45자 이하여야 합니다.")
        private String title;

        @NotBlank
        @NotNull
        @Size(max = 45)
        private String subTitle;

        @Size(max = 45)
        private String author;

        @Size(max = 45)
        private String publisher;

        private Book.Status status;
    }

    @Getter
    @Setter
    @NoArgsConstructor
    @AllArgsConstructor
    public static class Patch {
        private Book.Status status;
    }

    @Getter
    @Setter
    @NoArgsConstructor
    @AllArgsConstructor
    public static class Rollback {
        @NotBlank(message = "제목은 필수 입력 값입니다.")
        @NotNull(message = "제목은 반드시 입력하셔야 합니다.")
        @Size(min = 1, max = 45, message = "제목은 45자 이하여야 합니다.")
        private String title;
        @NotBlank
        @NotNull
        @Size(max = 45)
        private String subTitle;
        @NotBlank
        @NotNull
        private String publisher;
    }

    // 조회(GET)
    @Getter
    @Setter
    @NoArgsConstructor
    @AllArgsConstructor
    public static class Response {
        private Long id;
        private String title;
        private String author;
        private String publisher;
    }
}

 

4. Repository 및 Service 추가

- repository/BookRepository

package com.dream_flow.book.repository;

import com.dream_flow.book.domain.Book;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface BookRepository extends JpaRepository<Book, Long> {
}

 

- service/BookService

더보기
package com.dream_flow.book.service;

import com.dream_flow.book.domain.Book;
import com.dream_flow.book.repository.BookRepository;
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
@RequiredArgsConstructor
@Transactional
public class BookService {

    private final BookRepository bookRepository;

    // 책을 등록
    public Book inserBook(Book book){
//        Book b = bookRepository.save(book);
//        return b;
        return bookRepository.save(book); // 비영속 book을 영속으로
    }
    // 책을 업데이트(PUT)
    public Book updateBook(Long id, Book book){
        Book b = bookRepository.findById(id)
                .orElseThrow(()->new IllegalArgumentException("존재하지 않는 책입니다."));
        b.setTitle(book.getTitle());
        b.setSubTitle(book.getSubTitle());
        b.setAuthor(book.getAuthor());
        b.setPublisher(book.getPublisher());
        b.setStatus(book.getStatus());
        return bookRepository.save(b);
    }

    // 책을 업데이트(PATCH)
    public Book updateBook(Long id, Book.Status status){
        Book b = bookRepository.findById(id)
                .orElseThrow(()->new IllegalArgumentException("존재하지 않는 책입니다."));
        b.setStatus(status);
        return bookRepository.save(b);
    }

    // 책을 삭제
    public void deleteBook(Long id) {
        // 비즈니스 로직 (대출 중인 책 확인)
        Book b = bookRepository.findById(id)
                .orElseThrow(()->new IllegalArgumentException("존재하지 않는 책입니다."));
        if(b.getStatus() == Book.Status.BORROWED){
            throw new IllegalArgumentException("대출 중인 책은 삭제할 수 없습니다.");
        }
        // 지우는 코드
        bookRepository.delete(b);
    }

    // 책을 조회(단건)
    public Book findBook(Long id){
        return bookRepository.findById(id)
                .orElseThrow(()->new IllegalArgumentException("존재하지 않는 책입니다."));
    }

    // 책을 조회(다건)
    public List<Book> findBooks() {
        return bookRepository.findAll();
    }
}

 

5. 리팩토링

Method로 바꿔주기

- 드래그하고 실행하면 Method로 생성됨

    private Book getBook(Long id) {
        return bookRepository.findById(id)
                .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 책입니다."));
    }

 

- service/BookService.java 전체코드

package com.dream_flow.book.service;

import com.dream_flow.book.domain.Book;
import com.dream_flow.book.repository.BookRepository;
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
@RequiredArgsConstructor
@Transactional
public class BookService {

    private final BookRepository bookRepository;

    // 책을 등록
    public Book inserBook(Book book){
//        Book b = bookRepository.save(book);
//        return b;
        return bookRepository.save(book); // 비영속 book을 영속으로
    }
    // 책을 업데이트(PUT)
    public Book updateBook(Long id, Book book){
        Book b = getBook(id);
        b.setTitle(book.getTitle());
        b.setSubTitle(book.getSubTitle());
        b.setAuthor(book.getAuthor());
        b.setPublisher(book.getPublisher());
        b.setStatus(book.getStatus());
        return bookRepository.save(b);
    }

    private Book getBook(Long id) {
        return bookRepository.findById(id)
                .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 책입니다."));
    }

    // 책을 업데이트(PATCH)
    public Book updateBook(Long id, Book.Status status){
        Book b = getBook(id);
        b.setStatus(status);
        return bookRepository.save(b);
    }

    // 책을 삭제
    public void deleteBook(Long id) {
        // 비즈니스 로직 (대출 중인 책 확인)
        Book b = getBook(id);
        if(b.getStatus() == Book.Status.BORROWED){
            throw new IllegalArgumentException("대출 중인 책은 삭제할 수 없습니다.");
        }
        // 지우는 코드
        bookRepository.delete(b);
    }

    // 책을 조회(단건)
    public Book findBook(Long id){
        return getBook(id);
    }

    // 책을 조회(다건)
    public List<Book> findBooks() {
        return bookRepository.findAll();
    }
}

 

5. 테스트

post와 get으로 테스트 가능

{
"title":"제목",
"subTitle":"서브제목",
"author":"rabat",
"publisher":"spring",
"status":"AVAILABLE"
}

 

 

외) 스웨걸로 가능

http://localhost:8080/swagger-ui/index.html

implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0'

 

'웹공부 > SPRING' 카테고리의 다른 글

예외 처리  (0) 2024.12.17
Delete, Update  (0) 2024.12.17
페이지네이션  (0) 2024.12.17
BootStrap 실습 (Create, Read)  (0) 2024.12.16
SPRING 기본  (1) 2024.12.13
'웹공부/SPRING' 카테고리의 다른 글
  • 예외 처리
  • Delete, Update
  • 페이지네이션
  • BootStrap 실습 (Create, Read)
Rabet
Rabet
  • 블로그 메뉴

    • 관리자
    • 글쓰기
  • Rabet
    卯
    Rabet
  • 전체
    오늘
    어제
    • Root (139)
      • QA (1)
        • 오류사항 (1)
      • KT AIVLE School (0)
        • Start (4)
        • Python프로그래밍 & 라이브러리 (6)
        • 데이터 처리 및 분석 (7)
        • 데이터 분석 및 의미 찾기 (7)
        • 웹크롤링 (10)
        • 머신러닝 (10)
        • 딥러닝 (6)
        • 시각지능 딥러닝 (10)
        • 언어지능 딥러닝 (6)
        • JAVA (4)
        • SQL (2)
        • 가상화 클라우드 (5)
        • 프로젝트 (8)
      • 웹공부 (14)
        • SPRING (11)
        • React (1)
      • 코딩 알고리즘 스터디 (23)
      • 코딩테스트 (9)
        • JAVA (8)
        • HTML (1)
      • CS공부 (3)
      • 자격증공부 (3)
        • 정보처리기사 (1)
        • 컴퓨터활용능력 1급 (1)
        • AICE Associate (1)
  • 인기 글

  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.2
Rabet
REST API
상단으로

티스토리툴바