Today
-
Yesterday
-
Total
-
  • Spring boot: JPA 연관관계 살펴보기 - 1:1
    Spring Boot 🍃 2023. 12. 5. 00:01

    1 대 1 연관관계

    어느 엔티티 쪽에서 상대 엔티티와 반드시 단 하나의 관계를 가지는 것을 말한다.

    1:1 연관관계는 생각보다 실무에서 많이 사용된다.

    실습 1: 단방향

    book 테이블과 bookReviewInfo 테이블이 1:1로 조인하는 경우에 대해 실습해본다.

    bookReviewInfo 엔티티에 book 엔티티를 조인해서 데이터 확인

    엔티티 작성

    Book 엔티티

    @ToString(callSuper = true)
    @Getter @Setter
    @SuperBuilder
    @NoArgsConstructor
    @AllArgsConstructor
    @Entity
    public class Book extends BaseEntity {
    
        private String name;
        private String category;
        private Long authorId;
        private Long publisherId;
    
    }

    BookReviewInfo 엔티티

    @ToString(callSuper = true)
    @Getter @Setter
    @SuperBuilder
    @AllArgsConstructor
    @NoArgsConstructor
    @Entity
    public class BookReviewInfo extends BaseEntity {
    
        @OneToOne(optional = false)
        private Book book;
        
        private float averageReviewScore;
        private int reviewCount;
    
    }

    BookReviewInfo 엔티티를 작성할 때 Book 객체를 필드로 작성하고, Book 필드에 연관관계 어노테이션을 붙여주면 JPA가 아래와 같이 해석해준다.

    create table book_review_info (
        average_review_score float(24) not null,
        review_count integer not null,
        book_id bigint not null unique, -- 이 부분이 Book 객체를 해석한 부분
        
        created_at timestamp(6),
        id bigint generated by default as identity,
        updated_at timestamp(6),
        
        primary key (id)
    )

    test 코드 실행: 쿼리 확인

    test 코드

    @Test
    void showQuery() {
    	// book 데이터 & bookReviewInfo 데이터 생성
        givenBookReviewInfo();
    
    	// bookReviewInfo 조회
        Book result = bookReviewInfoRepository
                        .findById(1L)
                        .orElseThrow(RuntimeException::new)
                        .getBook();
    }

    JPA query

    select
        b1_0.id,
        b1_0.average_review_score,
        b1_0.book_id,
        b2_0.id,
        b2_0.author_id,
        b2_0.category,
        b2_0.created_at,
        b2_0.name,
        b2_0.publisher_id,
        b2_0.updated_at,
        b1_0.created_at,
        b1_0.review_count,
        b1_0.updated_at 
    from
        book_review_info b1_0 
    join
        book b2_0 
            on b2_0.id=b1_0.book_id 
    where
        b1_0.id=?

    실습 2: 양방향

    1:1 연관관계를 맺는 경우,

    양방향으로 관계를 설정하는 경우는 극히 드물다.

    양방향 관계는 1:N , N:N의 관계에서 주로 맺게 된다.

    하지만 학습을 위해 실습!


    엔티티 작성

    Book 엔티티 수정

    book 엔티티에도 bookReviewInfo 엔티티 필드를 1:1관계로 추가해준다.

    이떄, 연관관계 어노테이션에 mappedBy="엔티티이름" 설정을 추가해주어, 연관키를 해당 엔티티에서 가지지 않도록 설정을 해주고 optional 설정을 기본값인 true 로 변경해주어야 한다.

    또한, ToString을 사용하는 엔티티라면, 순환참조가 발생하여 StackOverFlow 에러를 만나게 되므로 @ToString.exclude 설정을 주어야 한다.

    public class Book extends BaseEntity {
    
        @OneToOne(mappedBy = "book")
        @ToString.Exclude
        private BookReviewInfo bookReviewInfo;
    
        private String name;
        private String category;
        private Long authorId;
        private Long publisherId;
    
    }

    변환 된 sql 쿼리

    Hibernate: 
        create table book (
            author_id bigint,
            created_at timestamp(6),
            id bigint generated by default as identity,
            publisher_id bigint,
            updated_at timestamp(6),
            category varchar(255),
            name varchar(255),
            primary key (id)
        )

    mappedBy 설정으로 인해 create 쿼리에서는 BookReviewInfo_id 필드가 작성되어있지 않다.


    BookReviewInfo 엔티티는 그대로!

    public class BookReviewInfo extends BaseEntity {
    
        @OneToOne(optional = false)
        private Book book;
    
        private float averageReviewScore;
        private int reviewCount;
    
    }

    test 코드 실행: 쿼리 확인

    양방향 확인을 위해 Book 정보를 BookReviewInfoRepository에서 가져오고, BookReviewInfo 정보를 BookRepository에서 가져온다.

    test 코드

    @Test
    void crud() {
        givenBookReviewInfo();
    
        Book result = bookReviewInfoRepository
                        .findById(1L)
                        .orElseThrow(RuntimeException::new)
                        .getBook();
    
        System.out.println("[ review id=1 인 book 정보 ]___________🚩  " + result);
    
        BookReviewInfo result2 = bookRepository
                .findById(1L)
                .orElseThrow(RuntimeException::new)
                .getBookReviewInfo();
        System.out.println("[ book id=1 인 bookReviewInfo 정보 ]___________🚩  " +  result2);
    }

    jpa 작성 쿼리!

    Hibernate: 
        select
            b1_0.id,
            b1_0.average_review_score,
            b1_0.book_id,
            b2_0.id,
            b2_0.author_id,
            b2_0.category,
            b2_0.created_at,
            b2_0.name,
            b2_0.publisher_id,
            b2_0.updated_at,
            b1_0.created_at,
            b1_0.review_count,
            b1_0.updated_at 
        from
            book_review_info b1_0 
        join
            book b2_0 
                on b2_0.id=b1_0.book_id 
        where
            b1_0.id=?
    [ review id=1 인 book 정보 ]___________🚩  
    
    Hibernate: 
        select
            b1_0.id,
            b1_0.author_id,
            b2_0.id,
            b2_0.average_review_score,
            b2_0.book_id,
            b2_0.created_at,
            b2_0.review_count,
            b2_0.updated_at,
            b1_0.category,
            b1_0.created_at,
            b1_0.name,
            b1_0.publisher_id,
            b1_0.updated_at 
        from
            book b1_0 
        left join
            book_review_info b2_0 
                on b1_0.id=b2_0.book_id 
        where
            b1_0.id=?
    [ book id=1 인 bookReviewInfo 정보 ]___________🚩

    Book 엔티티의 테이블 생성문에서 BookReviewInfo_id 필드가 생성되지 않았지만,

    OneToOne 관계 설정을 해두었기 때문에, 두 엔티티의 공통 필드인 book_id로 Join이 가능해진다.

Designed by Tistory / Custom by 얼거스