[Spring] 도서 관리 시스템 - 2

정석·2024년 3월 4일

Spring

목록 보기
12/21
post-thumbnail

이번엔 도서 대출 시스템을 구현해본다.

도서관에서 책을 대출하려면, 우선 책을 아무도 빌리지 않은 상태여야 하고, 빌렸다면 빌린 사람의 정보가 책의 정보와 함께 저장이 되어 있어야 한다.

  1. 그럼 일단, 이러한 정보를 저장하기 위한 새로운 테이블을 생성한다.
  • user_loan_history TABLE
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)
	);
  1. DB에서 테이블 생성을 하였으면, 자바 내에서도 테이블에 대응되는 Entitiy를 만들어 연결시켜야 한다.
  • UserLoanHistory.java
@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에서의 테이블 명") 이렇게 명시하면 대응시킬 수 있다.

  1. 그럼 이제 Controller와 데이터를 전달할 객체인 DTO 를 만들어보자.
  • BookController.java
@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);
    }
}
  • BookLoanRequest.java
public class BookLoanRequest {

    private String userName;
    private String bookName;

    public String getUserName() {
        return userName;
    }

    public String getBookName() {
        return bookName;
    }
}
  1. 이제 Service 단을 추가한다.
@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()));
    }
}
  1. Repository 부분 생성
public interface UserLoanHistoryRepository extends JpaRepository<UserLoanHistory, Long> {

    // SELECT * FROM user_loan_history WHERE book_name = ? AND is_return = ?
    boolean existsByBookNameAndIsReturn(String name, boolean isReturn);
}

0개의 댓글