1. 기존
기존 브라우저
- GET : 요청정보를 URL에 담아서 HTML문서를 주세요.
- POST : 요청정보를 패킷에 담아서 HTML문서를 주세요.
VS
REST : 자원을 이름으로 구분하여 해당 자원의 상태를 주고 받으며 mustache가 필요없다.
- GET : 자원의 정보를 조회해주세요.
- POST : 자원을 생성해주세요.
- PUT, PATCH : 자원을 줄테니 업데이트해주세요. (PUT: 본문 전체, PATCH:일정 요소만)
- 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로 생성됨
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 |