이번엔 도서 대출 시스템을 구현해본다.
도서관에서 책을 대출하려면, 우선 책을 아무도 빌리지 않은 상태여야 하고, 빌렸다면 빌린 사람의 정보가 책의 정보와 함께 저장이 되어 있어야 한다.
CREATE TABLE user_loan_history (
id bigint auto_increment,
user_id bigint,
book_name varchar(255),
is_return tinyint(1), // 반납 상태면 0, 대출 상태면 1
primary key (id)
);
@Entity
public class UserLoanHistory {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id = null;
private long userId;
private String bookName;
private boolean isReturn;
protected UserLoanHistory() { // JPA 사용으로 기본생성자 필요
}
public UserLoanHistory(long userId, String bookName) {
this.userId = userId;
this.bookName = bookName;
this.isReturn = false;
}
}
여기서 궁금했던 점! DB 내의 테이블 이름은 snake_case로 명시되어 있고, JAVA의 클래스 이름은 CamelCase로 작성되었는데 Entity클래스와 DB가 어떻게 대응될까?
.
추가적으로 알아보니, JPA 구현체의 Hibernate에서 자동으로 처리되는 규칙으로 인해 대소문자 구분은 알아서 인식할 수 있단다. 근데 만약, 아예 이름조차 다르게 만들었다면 엔티티 어노테이션 아래에
@Table(name = "DB에서의 테이블 명")이렇게 명시하면 대응시킬 수 있다.
@RestController
public class BookController {
private final BookService bookService;
public BookController(BookService bookService) {
this.bookService = bookService;
}
@PostMapping("/book")
public void saveBook(@RequestBody BookCreateRequest request) {
bookService.saveBook(request);
}
@PostMapping("/book/loan") // loan 대출부분 POST API 를 생성했다.
public void loanBook(@RequestBody BookLoanRequest request) {
bookService.loanBook(request);
}
}
public class BookLoanRequest {
private String userName;
private String bookName;
public String getUserName() {
return userName;
}
public String getBookName() {
return bookName;
}
}
@Service
public class BookService {
private final BookRepository bookRepository;
private final UserLoanHistoryRepository userLoanHistoryRepository; // 대출기록 테이블 사용을 위한 저장소
private final UserRepository userRepository;
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 book = bookRepository.findByName(request.getBookName())
// 책이 없다면,
.orElseThrow(IllegalAccessError::new);
// 2. 대출기록 정보 확인해서 대출중인지 확인한다.
// 3. 만약에 확인했는데 대출 중이라면 예외를 발생시킨다.
if (userLoanHistoryRepository.existsByBookNameAndIsReturn(book.getName(), false)) {
throw new IllegalArgumentException("이미 대출되어 있는 책입니다.");
}
// 4. 유저 정보를 가져온다.
User user = userRepository.findByName(request.getUserName())
.orElseThrow(IllegalArgumentException::new);
// 5. 유저 정보와 책 정보를 기반으로 UserLoanHistory를 저장
userLoanHistoryRepository.save(new UserLoanHistory(user.getId(), book.getName()));
}
}
public interface UserLoanHistoryRepository extends JpaRepository<UserLoanHistory, Long> {
// SELECT * FROM user_loan_history WHERE book_name = ? AND is_return = ?
boolean existsByBookNameAndIsReturn(String name, boolean isReturn);
}