티스토리 뷰

Backend/Spring

#29 대출 기능 API

RadderNepa 2023. 10. 7. 19:31
[요구사항]
- 사용자가 책을 빌릴 수 있다.

- 다른 사람이 빌린 책은 또 다른 사람이 빌릴 수 없다.
[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

 

[mySQL] AUTO_INCREMENT 값 초기화

오늘은 mySQL AUTO_INCREMENT 값을 초기화 하는 방법을 소개하려고 합니다.아래는 [heidiSQL] 화면인데요,선택할 수 있는 기본값의 종류입니다. 그 중, 맨 아래에 있는 아이가 오늘 소개할 AUTO_INCREMENT 에

amaze9001.tistory.com

ID 값 새로 1 부터 시작 됨

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

 

#26 JPA를 이용해 SQL 날리기 with Spring Data JPA2

1. 삭제 1. name을 기준으로 User가 있는지 확인 2. User가 있다면 DELETE 쿼리를 날린다. // JpaRepository를 상속하는 것만으로도 UserRepository는 Spring bean으로 등록된다. public interface UserRepository extends JpaReposi

radderveloper.tistory.com


● 테스트

현재 user table과 book table

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
링크
«   2024/12   »
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
글 보관함