ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • TIL 자바 스프링부트 서버개발 올인원 : 30-32강
    Java 2024. 5. 8. 22:19

    테이블을 만들 때 varchar(255)로 쓴 이유

    book table 만드는 쿼리

    1. @Column의 기본값이 255이기 때문에 을 생략할 수 있게 된다.

    2. 문자열 필드는 최적화를 해야 하는 경우가 아닐 때 조금 여유롭게 설정하는 것이 좋다.(나중을 대비)


    Entity추가

    package com.group.libraryapp.domain.book;
    
    import javax.persistence.*;
    
    @Entity
    public class book {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id = null;
    
    //  @Column(nullable = false, length = 255, name="name")
    //  name = "name" DB의 이름과 일치하기 때문에 생략가능
    //  length = 255 DB의 크기를 255 기본값으로 맞췄기 때문에 생략가능
        @Column(nullable = false)
        private String name;
        
        //기본 생성자 필수
        protected Book() {
            
        }
        
        public Book(String name) {
            if(name == null || name.isBlank()) {
                throw new IllegalArgumentException(String.format("잘못된 name(%s)이 들어왔습니다.",name));
            }
            this.name = name;
        }
    }

     


    Repository추가

    JPA를 사용하기 위해서는 Repository인터페이스에 JpaRepository를 상속받아서 사용한다.

    package com.group.libraryapp.repository.book;
    
    import com.group.libraryapp.domain.book.Book;
    import org.springframework.data.jpa.repository.JpaRepository;
    
    public interface BookRepository extends JpaRepository<Book, Long> {
        
    }

    JpaRepository가 Book도메인(엔티티)와 Id값을 반환해야 하기 때무에 Book, Long타입으로 설정해준다.

     


    JPA의 경우 엔티티에 기본 생성자가 필수이다.

    All persistent classes must have a default constructor (which can be non-public) so that Hibernate can instantiate them using Constructor.newInstance(). It is recommended that you have a default constructor with at least package visibility for runtime proxy generation in Hibernate.

     

    이렇게 기본생성자가 꼭 필요한 과정은 @RequestBody를 통해서 DTO를 바인딩 하는 경우도 해당된다. 이는 @RequestBody 바인딩 방식이 기본 생성자를 통해서 객체를 생성한 후 자바 Reflection을 이용해 필드값을 넣어주는 방식이기 때문이다.

    Reflection은 클래스의 이름만 알면 생성자, 필드, 메서드등 클래스의 모든 정보에 접근이 가능하다.

    하지만, Reflection이 가져오지 못하는 것이 생성자의 매개변수 정보이다. 그렇기에 객체에 모든 필드를 받는 생성자가 있다고 하더라도 Reflection으로는 해당생성자를 호출할 수 없다.

    그래서 Reflection은 기본 생성자로 객체를 생성하고 필드 값을 강제로 맵핑하는 방식을 사용한다.

     

    JPA또한 데이터를 DB에서 조회해 온 뒤 객체를 생성할 때 Reflection을 사용한다. 때문에 기본 생성자로 객체를 생성한다.

     

    결론적으로 기본 생성자가 없다면 DB에서 데이터를 조회한 정보를 엔티티로 만들 때 객체 생성 자체를 실패하게 되기 때문에 기본 생성자가 꼭 필요하다.

     

    출처 : https://velog.io/@ohzzi/JPA%EC%9D%98-%EC%97%94%ED%8B%B0%ED%8B%B0%EC%97%90-protected-public-%EA%B8%B0%EB%B3%B8-%EC%83%9D%EC%84%B1%EC%9E%90%EA%B0%80-%ED%95%84%EC%9A%94%ED%95%9C-%EC%9D%B4%EC%9C%A0


    DB 데이터타입 tinyint를 사용해서 boolean값을 맵핑

    DB에 책 대출여부를 확인하는 isReturn 컬럼을 만들었는데, 데이터 타입을 tinyint(1)로 설정했다.

    책이 대출중이라면 0을 반환하고 대출가능하다면 1을 반환하기 위해서다.

    그렇다면 Entity객체에서는 isReturn값을 어떻게 맵핑할 수 있을까?

    tinyint값을 맵핑시 boolean타입으로 변수를 설정한다면 0이면 false, 1이면 true를 반환하게 된다.


    Repository 메서드 시그니처 추가

    대출기록 확인을 위한 메서드를 만들기 위한 메서드

    package com.group.libraryapp.repository.user.loanHistory;
    
    import com.group.libraryapp.domain.user.loanHistory.UserLoanHistory;
    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 existByBookNameAndIsReturn(String bookName, boolean isReturn);
    }

     

    대출기록 테이블에서 boolean값으로 현재 책이 대출중인지 확인하기 위해 exist를 사용한다.

    위와 같은 메서드 시그니처를 만들면

    "SELECT * FROM user_loan_history WHERE book_name = ? AND is_return = ?" 쿼리가 생성된다.

     

    BookService

        public void loanBook(BookLoanRequest request) {
            // 1. 책 정보를 가져온다,
            Book book = bookRepository.findByName(request.getBookName())
                    .orElseThrow(IllegalArgumentException::new);
            // 2.대출 기록 정보를 확인해서 대출중인지 확인한다.
            if(userLoanHistoryRepository.existsByBookNameAndIsReturn(book.getName(),false)) {
                throw new IllegalArgumentException("이미 대출중 입니다.");
            }
    
        }

    위의 코드에서 

    if(userLoanHistoryRepository.existsByBookNameAndIsReturn(book.getName(),false))

     

    해당 부분에서 false를 넘겨주는게 헷갈려서 기록 남긴다.

     

    위에 user_loan_history테이블을 설계할 때  is_return 컬럼에서 1 = 대여가능/  0 = 대여중 으로 설계했고(tinyInt값이 boolean값과 맵핑된다는 것을 이용), 리포지토리에서  WHERE book_name = ? AND is_return = ? 으로 조건을 만들었다.

    그리고 service 조건문에 true일 경우 이미 대출중인 책입니다. 라는 에러를 던지기 위해서 false(0)값을 넣어서 "이미 대출중이지?" 라는 질문을 하게 하는 것이다.


    같은 변수를 받는 작업은 DTO를 재활용하는게 좋을까?

    대출을 위한 DTO도 userName, bookName을 받고, 반납을 위한 DTO도 userName과 bookName을 받는 상황이다.

    즉, 스펙이 같으니까 DTO를 재활용해서 대출과 반납을 같은 DTO로 둬도 될까??

     

    강사님의 경우 개인적으로 새로 만드는 것을 선호한다고 한다.

    두개를 따로 만들어야만 두 기능중 한 기능의 변화가 생겼을 때, 유연하고 side-effect없이 대처할 수 있기 때문이다.

     

     

    강의:https://www.inflearn.com/course/%EC%9E%90%EB%B0%94-%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8-%EC%84%9C%EB%B2%84%EA%B0%9C%EB%B0%9C-%EC%98%AC%EC%9D%B8%EC%9B%90/dashboard

Designed by Tistory.