티스토리 뷰
[요구사항]
- 사용자가 책을 빌릴 수 있다.
- 다른 사람이 빌린 책은 또 다른 사람이 빌릴 수 없다.
[API 스펙]
- HTTP Method : POST
- HTTP Path : /book/loan
- HTTP Body(JSON) : {
"userName" : String,
"bookName" : String
}
- 결과 반환 X, HTTP Code 200이면 OK
1. Book Table
- 사용자의 대출 기록을 저장하는 새로운 테이블이 필요하다.
CREATE TABLE USER_LOAN_HISTORY (
ID BIGINT AUTO_INCREMENT, // 대출 기록 id
USER_ID BIGINT, // 책을 빌린 유저 id
BOOK_NAME VARCHAR(255), // 빌린 책 이름
IS_RETURN TINYINT(1), // 현재 대출(0) or 반납(1) 여부
PRIMARY KEY(ID)
);
cf) user table의 id 값을 다시 1부터 시작하기 위해 AUTO_INCREMENT 값을 초기화했다.
https://amaze9001.tistory.com/28
2. UserLoanHistory 객체 & UserLoanHistoryRepository
UserLoanHistory
package com.group.libraryapp.domain.user.loanhistory;
import javax.persistence.*;
@Entity
public class UserLoanHistory {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id = null;
@Column(nullable = false, name = "user_id")
private long userId;
@Column(nullable = false, name = "book_name")
private String bookName;
@Column(nullable = false, name = "is_return")
// is_return 컬럼은 자료형이 tinyint이기 때문에 DB에 0(false) 혹은 1(true)이 들어가도 된다.
// == boolean으로 처리해도 tinyint에 잘 매핑된다.
private boolean isReturn;
protected UserLoanHistory() {}
public UserLoanHistory(long userId, String bookName, boolean isReturn) {
this.userId = userId;
this.bookName = bookName;
this.isReturn = isReturn;
}
}
UserLoanHistoryRepository
package com.group.libraryapp.domain.user.loanhistory;
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserLoanHistoryRepository extends JpaRepository<UserLoanHistory, Long> {
}
cf) 현재 package 구조
3. DTO, Controller, Service
DTO
- DTO는 HTTP Body 스펙을 보고 그에 맞게 생성하면 된다.
package com.group.libraryapp.dto.book.request;
public class BookLoanRequest {
private String bookName;
private String userName;
public String getBookName() {
return bookName;
}
public String getUserName() {
return userName;
}
}
Controller(loanBook method 추가)
package com.group.libraryapp.controller.book;
import com.group.libraryapp.dto.book.request.BookCreateRequest;
import com.group.libraryapp.dto.book.request.BookLoanRequest;
import com.group.libraryapp.service.book.BookService;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class BookController {
private final BookService bookService;
// 생성자를 만들어 Spring bean을 주입받는다.
public BookController(BookService bookService) {
this.bookService = bookService;
}
@PostMapping("/book")
public void saveBook(@RequestBody BookCreateRequest request) {
bookService.saveBook(request);
}
@PostMapping("/book/loan")
// HTTP body를 파싱하기 위해 @RequestBody가 필요하다.
public void loanBook(@RequestBody BookLoanRequest request) {
bookService.loanBook(request);
}
}
Service(loanBook method 추가)
[By 앞에 들어갈 수 있는 구절]
- find : 1건을 가져온다. 반환 타입은 객체가 될 수도 있고 Optional<타입>이 될 수도 있다.
- findAll : 쿼리의 결과물이 N개인 경우 사용, List<타입> 반환
- exists : 쿼리 결과가 존재하는지 확인, 반환 타입은 boolean(존재 O = true / 존재 X = false)
- count : SQL의 결과 개수를 센다. 반환 타입은 long ex) long countByAge(Integer age);
package com.group.libraryapp.service.book;
import com.group.libraryapp.domain.book.Book;
import com.group.libraryapp.domain.book.BookRepository;
import com.group.libraryapp.domain.user.User;
import com.group.libraryapp.domain.user.UserRepository;
import com.group.libraryapp.domain.user.loanhistory.UserLoanHistory;
import com.group.libraryapp.domain.user.loanhistory.UserLoanHistoryRepository;
import com.group.libraryapp.dto.book.request.BookCreateRequest;
import com.group.libraryapp.dto.book.request.BookLoanRequest;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class BookService {
private final BookRepository bookRepository;
private final UserLoanHistoryRepository userLoanHistoryRepository;
private final UserRepository userRepository;
// 생성자를 만들어 Spring bean을 주입받는다.
public BookService(BookRepository bookRepository,
UserLoanHistoryRepository userLoanHistoryRepository,
UserRepository userRepository
) {
this.bookRepository = bookRepository;
this.userLoanHistoryRepository = userLoanHistoryRepository;
this.userRepository = userRepository;
}
@Transactional
public void saveBook(BookCreateRequest request) {
bookRepository.save(new Book(request.getName()));
}
@Transactional
public void loanBook(BookLoanRequest request) {
// 1. Book table에서 책 정보를 가져온다.
Book book = bookRepository.findByName(request.getBookName())
.orElseThrow(IllegalArgumentException::new);
// 2. 빌리려는 책이 현재 대출중인지 확인
if(userLoanHistoryRepository.existsByBookNameAndIsReturn(book.getName(), false)) {
// 대출 O
throw new IllegalArgumentException("이미 대출중인 책 입니다.");
} else {
// 대출 X
// 3. 사용자 정보를 가져온다.
User user = userRepository.findByName(request.getUserName())
.orElseThrow(IllegalArgumentException::new);
// 4. 사용자 정보 & 책 정보를 이용해 userLoanHistory 저장
userLoanHistoryRepository.save(new UserLoanHistory(user.getId(), book.getName(), false));
}
}
}
package com.group.libraryapp.domain.user;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
import java.util.Optional;
// JpaRepository를 상속하는 것만으로도 UserRepository는 Spring bean으로 등록된다.
public interface UserRepository extends JpaRepository<User, Long> {
// name을 기준으로 조회
// 데이터가 있으면 User 객체 반환, 없으면 null 반환
Optional<User> findByName(String name);
// 함수 이름이 중요하다.
// 반드시 findByName 이라고 적어줘야한다. 함수 이름에 맞춰서 알아서 SQL이 생성돼 날라간다.
// 'find'라고 작성하면 1개의 데이터만 가져온다.
// 'By' 뒤에 붙는 필드 이름으로 SELECT 쿼리의 WHERE 문이 작성된다.
// 결론 : findByName = SELECT * FROM USER WHERE NAME = ?;
// Optional<User> findByName(String name);
boolean existsByName(String name);
long countByAge(Integer age);
List<User> findAllByNameAndAge(String name, int age);
List<User> findAllByAgeBetween(int startAge, int endAge);
}
package com.group.libraryapp.domain.book;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface BookRepository extends JpaRepository<Book, Long> {
Optional<Book> findByName(String name);
}
package com.group.libraryapp.domain.user.loanhistory;
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserLoanHistoryRepository extends JpaRepository<UserLoanHistory, Long> {
// SELECT * FROM USER_LOAN_HISTORY WHERE BOOK_NAME = ? AND IS_RETURN = ?
boolean existsByBookNameAndIsReturn(String bookName, boolean isReturn);
}
2023.09.26 - [Spring] - #26 JPA를 이용해 SQL 날리기 with Spring Data JPA2
● 테스트
현재 user table과 book table
1. 정상 대출
2. 이미 빌린 책을 다른 사람이 빌리려고 할 때(못 빌림)
3. 없는 책을 빌리려고 할 때
4. 배주현 정상 대출
'Backend > Spring' 카테고리의 다른 글
#30 반납 기능 (0) | 2023.10.09 |
---|---|
#28 책 생성 API - INSERT (1) | 2023.10.02 |
#27 영속성 컨텍스트 (1) | 2023.09.28 |
#26 JPA를 이용해 SQL 날리기 with Spring Data JPA2 (0) | 2023.09.26 |
#25 트랜잭션 - 이론 (0) | 2023.09.25 |
공지사항
최근에 올라온 글
최근에 달린 댓글
- Total
- Today
- Yesterday
링크
TAG
- SpringBoot
- MongoDB
- Stream
- 빅데이터
- node.js
- nosql
- 자료구조
- Phaser
- 코딩테스트
- MySQL
- Spring Boot
- SQL
- DART
- 알고리즘
- 메모리
- 프로그래머스
- 프로세스
- API
- Phaser3
- 운영체제
- spring
- jpa
- Java8
- OS
- 코테
- Advanced Stream
- 빅데이터 분석기사
- git
- db
- java
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
글 보관함