이번 편의 목표: 책을 메모리에 저장하고 목록, 단건 조회, 등록, 삭제를 한 API로 연결합니다.
10편까지는 1번 샘플과 항상 같은 id를 반환했습니다. 이제 요청마다 책을 보관합니다. 서버를 끄면 데이터는 사라집니다. 학습용 저장소이며, 동시에 많은 요청을 안전하게 처리하는 구조는 아닙니다.
저장소와 서비스
BookStore.java입니다.
package com.thinktree.library;
import java.util.ArrayList;
import java.util.List;
import org.springframework.stereotype.Component;
@Component
public class BookStore {
private final List<Book> books = new ArrayList<>();
private long nextId = 1;
public Book save(String title, int price) {
Book book = new Book(nextId, title, price);
nextId = nextId + 1;
books.add(book);
return book;
}
public List<Book> findAll() {
return new ArrayList<>(books);
}
public Book findById(long id) {
for (Book book : books) {
if (book.id() == id) {
return book;
}
}
return null;
}
public boolean delete(long id) {
for (int i = 0; i < books.size(); i++) {
if (books.get(i).id() == id) {
books.remove(i);
return true;
}
}
return false;
}
}BookService.java는 저장소를 사용하도록 파일 전체를 바꿉니다. 10편의 1번 샘플과 sample 메서드는 지웁니다.
package com.thinktree.library;
import java.util.List;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.web.server.ResponseStatusException;
@Service
public class BookService {
private final BookStore bookStore;
public BookService(BookStore bookStore) {
this.bookStore = bookStore;
}
public List<Book> findAll() {
return bookStore.findAll();
}
public Book find(Long id) {
if (id == null) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "책을 찾을 수 없습니다.");
}
Book book = bookStore.findById(id);
if (book == null) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "책을 찾을 수 없습니다.");
}
return book;
}
public Book create(BookRequest request) {
return bookStore.save(request.title(), request.price());
}
public void delete(Long id) {
if (id == null || !bookStore.delete(id)) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "책을 찾을 수 없습니다.");
}
}
}컨트롤러
BookController.java도 API 네 개로 파일 전체를 바꿉니다. 검색용 문자열 응답은 제거합니다.
package com.thinktree.library;
import java.util.List;
import jakarta.validation.Valid;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/books")
public class BookController {
private final BookService bookService;
public BookController(BookService bookService) {
this.bookService = bookService;
}
@GetMapping
public List<Book> list() {
return bookService.findAll();
}
@GetMapping("/{id}")
public Book find(@PathVariable Long id) {
return bookService.find(id);
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public Book create(@Valid @RequestBody BookRequest request) {
return bookService.create(request);
}
@DeleteMapping("/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void delete(@PathVariable Long id) {
bookService.delete(id);
}
}등록에 성공하면 201이 반환됩니다. 삭제가 성공하면 본문 없이 204가 반환됩니다. 없는 번호의 조회와 삭제는 404입니다.
개미를 등록한 뒤 GET /api/books 를 호출하면 배열에 한 권이 있습니다. 다시 공책을 등록하면 id는 2가 됩니다. DELETE /api/books/1 다음에 1번을 조회하면 404입니다.
FeePolicy는 이 저장 API에 더 이상 필요 없습니다. 04편의 /fee 컨트롤러를 남겨 두어도 서버 실행에는 문제가 없습니다.
직접 해 보기
책 두 권을 등록하고 목록의 길이가 2인지 확인한 뒤, 첫 권을 삭제하고 다시 목록을 조회하세요.
해설
등록을 두 번 하면 목록은 2권입니다. 삭제가 성공하면 남은 책은 한 권이고, 삭제한 번호는 조회할 수 없습니다. 서버를 재시작하면 목록은 다시 비어 있습니다.
COMMENTS
댓글 0
이 글에 대한 의견이나 질문을 남겨 주세요.
댓글은 구글이나 카카오로 시작한 뒤에 남길 수 있습니다.
등록된 댓글이 없습니다.