JPA 연관관계 살펴보기(1:1)

이종윤·2022년 2월 7일
0

Spring JPA

목록 보기
9/23

Bookmanager 관계도

😎1대1 연관관계 살펴보기.

먼저 테이블 하나 추가 하자.

위 테이블 대로 Entity 만들자.

@Entity
@NoArgsConstructor
@Data
@ToString(callSuper = true)                //상속받은 클래스에 대해 처리해줘야한다. ToString을 재정의 한다.
@EqualsAndHashCode(callSuper = true)       //EqualsAndHashCode를 재정의해준다.
//@EntityListeners(value = AuditingEntityListener.class)
public class Book extends BaseEntity {
    @Id
    @GeneratedValue
    private Long id;
    private String name;
    private String category;
    private Long authorId;
    private Long publisherId;
@Entity
@NoArgsConstructor
@Data
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class BookReviewInfo extends BaseEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private Long bookId;

    private float averageReviewScore; // 평균

    private int reviewCount; // 리뷰 수
}

BaseEntity에 created_at,updated_at 있다는거 있지 말자.

BookReviewInfoRepository 만들고 Test 해보자.

    @Test
    void crudTest2() {
        // 북 하나추가.
        Book book = new Book();
        book.setName("Jpa 초격차 패키지");
        book.setAuthorId(1L);
        book.setPublisherId(1L);
        bookRepository.save(book);
        // 북 목록
        System.out.println(">>>> " + bookRepository.findAll());

        // 북리뷰정보 출력
        BookReviewInfo bookReviewInfo = new BookReviewInfo();
        bookReviewInfo.setBookId(1L);
        bookReviewInfo.setAverageReviewScore(4.5f);
        bookReviewInfo.setReviewCount(2);
        bookReviewInfoRepository.save(bookReviewInfo);

        // 북리뷰정보 출력
        System.out.println(">>> " + bookReviewInfoRepository.findAll());

        // 북리뷰정보에 있는 Id가 1인 컬럼의 Id로 book 검색.
        Book result = bookRepository.findById(
                bookReviewInfoRepository
                        .findById(1L)
                        .orElseThrow(RuntimeException::new)
                        .getBookId()
                ).orElseThrow(RuntimeException::new);
        System.out.println("result >>>> " + result);
    }

😈 Test가 실패한다. ID 때문이다.

>>>> [Book(super=BaseEntity(createdAt=2022-02-07T20:42:57.189, updatedAt=2022-02-07T20:42:57.189), id=6, name=Jpa 초격차 패키지, category=null, authorId=1, publisherId=1)]

id가 6으로 시작하는 이유는 H2 DB는 GeneratedValue가 AUTO이면 시퀀스 전략을 가진다.
시퀀스는 id 값을 1을 높이는데 Entity관계 없이 올라간다. 즉 User Entity에서 5명의 유저가 생성되었기 때문에 Book에서는 id값이 6부터 시작하는 것이다.
Entity별 로 따로 설정 해 주려고 한다면 아래와 같이 하자.

모든 Entity의 ID 전략을 IDENTITY로 수정해주자.

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
data.sql의 hibernate_sequence는 주석 처리 해 주자.
-- call next value for hibernate_sequence;

자 그럼 잘 된다.

😎 위의 조건은 RDB의 PK,FK 조건을 표현한 것인데 Jpa에서는 좀 더 편하게 할 수 있다.

public class BookReviewInfo extends BaseEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

//    private Long bookId; 주석처리 하고

    @OneToOne				//1:1 어노테이션을 걸어주면된다.
    private Book book;		//Book타입 book을 만들고

Table에는 BookId가 존재하겠지만 Jpa에서는 Entity로 set,get을 하면 릴레이션을 자동으로 연결해준다.

TEST도 수정해야겠지?

    @Test
    void crudTest2() {
        givenBookReviewInfo(givenBook());
    }

    private void givenBookReviewInfo(Book book) {
        // 북리뷰정보 출력
        BookReviewInfo bookReviewInfo = new BookReviewInfo();
//        bookReviewInfo.setBookId(1L);
        bookReviewInfo.setBook(book);
        bookReviewInfo.setAverageReviewScore(4.5f);
        bookReviewInfo.setReviewCount(2);
        bookReviewInfoRepository.save(bookReviewInfo);

        // 북리뷰정보 출력
        System.out.println(">>> " + bookReviewInfoRepository.findAll());

        // 북리뷰정보에 있는 Id가 1인 컬럼의 Id로 book 검색.
        Book result = bookReviewInfoRepository
                .findById(1L)
                .orElseThrow(RuntimeException::new)
                .getBook();
        System.out.println("result >>>> " + result);
    }

    private Book givenBook() {
        // 북 하나추가.
        Book book = new Book();
        book.setName("Jpa 초격차 패키지");
        book.setAuthorId(1L);
        book.setPublisherId(1L);
        // 북 목록
//        System.out.println(">>>> " + bookRepository.findAll());
        return bookRepository.save(book);
    }

🤔BookReviewInfo클래스에 bookId를 Book 타입으로 만들었는데 DDL에 변화는??

    create table book_review_info (
       id bigint generated by default as identity,
        created_at timestamp,
        updated_at timestamp,
        average_review_score float not null,
        book_id bigint,
        review_count integer not null,
        primary key (id)
    )

그대로 book_id bigint, 속성을 추가하는 걸 볼수있다.

🤔 쿼리는?

Test
...
...
        // 북리뷰정보에 있는 Id가 1인 컬럼의 Id로 book 검색.
        Book result = bookReviewInfoRepository
                .findById(1L)
                .orElseThrow(RuntimeException::new)
                .getBook();
        System.out.println("result >>>> " + result);
...
...
    select
        bookreview0_.id as id1_2_0_,
        bookreview0_.created_at as created_2_2_0_,
        bookreview0_.updated_at as updated_3_2_0_,
        bookreview0_.average_review_score as average_4_2_0_,
        bookreview0_.book_id as book_id6_2_0_,
        bookreview0_.review_count as review_c5_2_0_,
        book1_.id as id1_1_1_,
        book1_.created_at as created_2_1_1_,
        book1_.updated_at as updated_3_1_1_,
        book1_.author_id as author_i4_1_1_,
        book1_.category as category5_1_1_,
        book1_.name as name6_1_1_,
        book1_.publisher_id as publishe7_1_1_ 
    from
        book_review_info bookreview0_ 
    left outer join
        book book1_ 
            on bookreview0_.book_id=book1_.id 
    where
        bookreview0_.id=?

😮😮 left outer join을 쓰는걸 볼 수 있다.!!!!!
알아서 join을 해주다니!!!!!!!!!!!!!!!!!!!!!!!! 😮😮

    @OneToOne(optional = false)
    private Book book;

추가로 optional = false 해주면 Book은 절때로 null을 허용하지 않겠다는 뜻이다.

    from
        book_review_info bookreview0_ 
    inner join
        book book1_ 
            on bookreview0_.book_id=book1_.id 
    where
        bookreview0_.id=?

그럼 left outer join 에서 inner join 으로 바뀌는걸 볼 수 있다.
자 그럼 BookReviewInfo에서 Book정보를 받아 올 수 있게 되었다.

🤔🤔그럼 Book에서 BookReviewInfo 정보를 받아오려면?

먼저 그냥 Book에 @OneToOne 넣어보자

    @OneToOne
    private BookReviewInfo bookReviewInfo;

그럼

    from
        book book0_ 
    left outer join
        book_review_info bookreview1_ 
            on book0_.book_review_info_id=bookreview1_.id 
    left outer join
        book book2_ 
            on bookreview1_.book_id=book2_.id 
    where
        book0_.id=?

left outer join을 두번하는걸 볼 수 있다.
이걸 해결하기위해 mappedBy 옵션을 사용할수있다.
mappedBy : (선택사항) 관계를 소유하는 필드입니다. 이 요소는 연결의 역방향(비소유) 측에만 지정됩니다.

    @OneToOne(mappedBy = "book")
    @ToString.Exclude // ToString 순환참조 걸린다. 릴레이션은 단방향으로 걸고 ToString은 제외해야한다.
    private BookReviewInfo bookReviewInfo;

이렇게 하면 DDL 생성도 안하고, Book 과 BookReviewInfo의 1대1 관계가 형성된다.

profile
OK가자

0개의 댓글