[SPRING] JPA - N + 1 Issue

RuiN·2022년 8월 7일
0

Spring

목록 보기
7/8
post-thumbnail

N + 1 Issue ?

N+1 문제란 만약 1번의 쿼리를 날렸을 때 필요하지 않은 쿼리가 N번 만큼 추가적으로 실행된다는것을 의미합니다.
우리가 단순한 프로젝트를 할때는 많아봤자 10개정도의 쿼리가 실행되겠지만
만약 대형서비스를 진행할때 100번 아니 천번 만번 불필요한 쿼리가 실행된다면
끔찍한 상황일 것이죠?!

이러한 N+1 의 상황은 OneToMany 관계나 ManyToOne 관계에서 엔티티를 조회할 때 발생합니다.

EAGER 전략으로 데이터를 조회하거나, LAZY 전략으로 데이터를 조회하여 하위 엔티티를 사용할때 다시 조회하면서 발생합니다.


EAGER / LAZY

  • EAGER ( 즉시 로딩 )
    • JPQL 에서 만든 SQL을 통해서 조회
    • EAGER 전략으로 조회하면서 하위엔티티들을 추가조회 하게 되면서 N +1 이슈 발생
  • LAZY ( 지연 로딩 )
    • JPQL 에서 만든 SQL을 통해서 조회
    • LAZY 전략의 경우 추가조회는 발생하지 않습니다.
    • 그러나 하위 엔티티들을 데리고 작업하다 보면 추가조회로 인해서 N+1 이슈 발생

Problem

먼저 ManyToOne 의 경우 기본전략은 EAGER
OneToMany 의 경우에는 LAZY가 Default 값이다.

@Entity
@NoArgsConstructor
@Data
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class Comment extends BaseEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String comment;

    @ManyToOne
    @ToString.Exclude
    private Review review;
}

위와 같은 Comment 라는 Entity를 만든다.

@Entity
@NoArgsConstructor
@Data
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class Review extends BaseEntity{

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String title;

    private String content;

    private float score;

    @ManyToOne
    private User user;

    @ManyToOne
    private Book book;

    @OneToMany
    @JoinColumn(name = "review_id")
    private List<Comment> comments;

}

그다음 Review 라는 Entity도 생성한다.

insert into review(`id`,`title`,`content`,`score`,`user_id`,`book_id`) values (1,'내 인생을 바꾼책','너무너무 좋았어요',5.0 ,1,1);

insert into review(`id`,`title`,`content`,`score`,`user_id`,`book_id`) values (2,'조금 속도가 빨라요','별로였던거 같기도하고?',3.0,2,2);

insert into comment(`id`,`comment`,`review_id`) values (1, '저도 좋았어요',1);

insert into comment(`id`,`comment`,`review_id`) values (2, '저는 그냥 그랬어요',1);
insert into comment(`id`,`comment`,`review_id`) values (3, '아니 왜만든거에요???',2);

Test를 위해서 Data.sql 에 위의 쿼리문장을 추가해준다.
( 나머지의 Value 들은 JPA 포스팅을 천천히 따라오다보면 된다 )


Test

    @Test
    @Transactional
    void reviewTest() {
        List<Review> reviews = reviewRepository.findAll();

        reviews.forEach(System.out::println);
    }

그냥 단순하게 위와 같은 테스트를 진행한다

Hibernate: 
    select
        review0_.id as id1_6_,
        review0_.created_at as created_2_6_,
        review0_.updated_at as updated_3_6_,
        review0_.book_id as book_id7_6_,
        review0_.content as content4_6_,
        review0_.score as score5_6_,
        review0_.title as title6_6_,
        review0_.user_id as user_id8_6_ 
    from
        review review0_
Hibernate: 
    select
        book0_.id as id1_1_0_,
        book0_.created_at as created_2_1_0_,
        book0_.updated_at as updated_3_1_0_,
        book0_.author_id as author_i4_1_0_,
        book0_.category as category5_1_0_,
        book0_.deleted as deleted6_1_0_,
        book0_.name as name7_1_0_,
        book0_.publisher_id as publishe9_1_0_,
        book0_.status as status8_1_0_,
        publisher1_.id as id1_5_1_,
        publisher1_.created_at as created_2_5_1_,
        publisher1_.updated_at as updated_3_5_1_,
        publisher1_.name as name4_5_1_,
        bookreview2_.id as id1_3_2_,
        bookreview2_.created_at as created_2_3_2_,
        bookreview2_.updated_at as updated_3_3_2_,
        bookreview2_.average_review_score as average_4_3_2_,
        bookreview2_.book_id as book_id6_3_2_,
        bookreview2_.review_count as review_c5_3_2_ 
    from
        book book0_ 
    left outer join
        publisher publisher1_ 
            on book0_.publisher_id=publisher1_.id 
    left outer join
        book_review_info bookreview2_ 
            on book0_.id=bookreview2_.book_id 
    where
        book0_.id=? 
        and (
            book0_.deleted = 0
        )
Hibernate: 
    select
        user0_.id as id1_7_0_,
        user0_.created_at as created_2_7_0_,
        user0_.updated_at as updated_3_7_0_,
        user0_.company_city as company_4_7_0_,
        user0_.company_address_detail as company_5_7_0_,
        user0_.company_district as company_6_7_0_,
        user0_.company_zip_code as company_7_7_0_,
        user0_.email as email8_7_0_,
        user0_.home_city as home_cit9_7_0_,
        user0_.home_address_detail as home_ad10_7_0_,
        user0_.home_district as home_di11_7_0_,
        user0_.home_zip_code as home_zi12_7_0_,
        user0_.name as name13_7_0_,
        userhistor1_.user_id as user_id14_8_1_,
        userhistor1_.id as id1_8_1_,
        userhistor1_.id as id1_8_2_,
        userhistor1_.created_at as created_2_8_2_,
        userhistor1_.updated_at as updated_3_8_2_,
        userhistor1_.company_city as company_4_8_2_,
        userhistor1_.company_address_detail as company_5_8_2_,
        userhistor1_.company_district as company_6_8_2_,
        userhistor1_.company_zip_code as company_7_8_2_,
        userhistor1_.email as email8_8_2_,
        userhistor1_.home_city as home_cit9_8_2_,
        userhistor1_.home_address_detail as home_ad10_8_2_,
        userhistor1_.home_district as home_di11_8_2_,
        userhistor1_.home_zip_code as home_zi12_8_2_,
        userhistor1_.name as name13_8_2_,
        userhistor1_.user_id as user_id14_8_2_ 
    from
        user user0_ 
    left outer join
        user_history userhistor1_ 
            on user0_.id=userhistor1_.user_id 
    where
        user0_.id=?
Hibernate: 
    select
        book0_.id as id1_1_0_,
        book0_.created_at as created_2_1_0_,
        book0_.updated_at as updated_3_1_0_,
        book0_.author_id as author_i4_1_0_,
        book0_.category as category5_1_0_,
        book0_.deleted as deleted6_1_0_,
        book0_.name as name7_1_0_,
        book0_.publisher_id as publishe9_1_0_,
        book0_.status as status8_1_0_,
        publisher1_.id as id1_5_1_,
        publisher1_.created_at as created_2_5_1_,
        publisher1_.updated_at as updated_3_5_1_,
        publisher1_.name as name4_5_1_,
        bookreview2_.id as id1_3_2_,
        bookreview2_.created_at as created_2_3_2_,
        bookreview2_.updated_at as updated_3_3_2_,
        bookreview2_.average_review_score as average_4_3_2_,
        bookreview2_.book_id as book_id6_3_2_,
        bookreview2_.review_count as review_c5_3_2_ 
    from
        book book0_ 
    left outer join
        publisher publisher1_ 
            on book0_.publisher_id=publisher1_.id 
    left outer join
        book_review_info bookreview2_ 
            on book0_.id=bookreview2_.book_id 
    where
        book0_.id=? 
        and (
            book0_.deleted = 0
        )
Hibernate: 
    select
        user0_.id as id1_7_0_,
        user0_.created_at as created_2_7_0_,
        user0_.updated_at as updated_3_7_0_,
        user0_.company_city as company_4_7_0_,
        user0_.company_address_detail as company_5_7_0_,
        user0_.company_district as company_6_7_0_,
        user0_.company_zip_code as company_7_7_0_,
        user0_.email as email8_7_0_,
        user0_.home_city as home_cit9_7_0_,
        user0_.home_address_detail as home_ad10_7_0_,
        user0_.home_district as home_di11_7_0_,
        user0_.home_zip_code as home_zi12_7_0_,
        user0_.name as name13_7_0_,
        userhistor1_.user_id as user_id14_8_1_,
        userhistor1_.id as id1_8_1_,
        userhistor1_.id as id1_8_2_,
        userhistor1_.created_at as created_2_8_2_,
        userhistor1_.updated_at as updated_3_8_2_,
        userhistor1_.company_city as company_4_8_2_,
        userhistor1_.company_address_detail as company_5_8_2_,
        userhistor1_.company_district as company_6_8_2_,
        userhistor1_.company_zip_code as company_7_8_2_,
        userhistor1_.email as email8_8_2_,
        userhistor1_.home_city as home_cit9_8_2_,
        userhistor1_.home_address_detail as home_ad10_8_2_,
        userhistor1_.home_district as home_di11_8_2_,
        userhistor1_.home_zip_code as home_zi12_8_2_,
        userhistor1_.name as name13_8_2_,
        userhistor1_.user_id as user_id14_8_2_ 
    from
        user user0_ 
    left outer join
        user_history userhistor1_ 
            on user0_.id=userhistor1_.user_id 
    where
        user0_.id=?
Hibernate: 
    select
        comments0_.review_id as review_i5_4_0_,
        comments0_.id as id1_4_0_,
        comments0_.id as id1_4_1_,
        comments0_.created_at as created_2_4_1_,
        comments0_.updated_at as updated_3_4_1_,
        comments0_.comment as comment4_4_1_,
        comments0_.review_id as review_i5_4_1_ 
    from
        comment comments0_ 
    where
        comments0_.review_id=?
Review(super=BaseEntity(createdAt=2022-08-07T18:02:12.703757, updatedAt=2022-08-07T18:02:12.703757), id=1, title=내 인생을 바꾼책, content=너무너무 좋았어요, score=5.0, user=User(super=BaseEntity(createdAt=2022-08-07T18:02:12, updatedAt=2022-08-07T18:02:12), id=1, name=martin, email=martin@fastcampus.com, homeAddress=null, companyAddress=null), book=Book(super=BaseEntity(createdAt=2022-08-07T18:02:12.690928, updatedAt=2022-08-07T18:02:12.690928), id=1, name=JPA 초격차 패키지, category=null, authorId=null, deleted=false, status=BookStatus(code=100, description=판매종료)), comments=[Comment(super=BaseEntity(createdAt=2022-08-07T18:02:12.710414, updatedAt=2022-08-07T18:02:12.710414), id=1, comment=저도 좋았어요), Comment(super=BaseEntity(createdAt=2022-08-07T18:02:12.716371, updatedAt=2022-08-07T18:02:12.716371), id=2, comment=저는 그냥 그랬어요)])
Hibernate: 
    select
        comments0_.review_id as review_i5_4_0_,
        comments0_.id as id1_4_0_,
        comments0_.id as id1_4_1_,
        comments0_.created_at as created_2_4_1_,
        comments0_.updated_at as updated_3_4_1_,
        comments0_.comment as comment4_4_1_,
        comments0_.review_id as review_i5_4_1_ 
    from
        comment comments0_ 
    where
        comments0_.review_id=?
Review(super=BaseEntity(createdAt=2022-08-07T18:02:12.707035, updatedAt=2022-08-07T18:02:12.707035), id=2, title=조금 속도가 빨라요, content=별로였던거 같기도하고?, score=3.0, user=User(super=BaseEntity(createdAt=2022-08-07T18:02:12, updatedAt=2022-08-07T18:02:12), id=2, name=dennis, email=denis@fastcampus.com, homeAddress=null, companyAddress=null), book=Book(super=BaseEntity(createdAt=2022-08-07T18:02:12.694357, updatedAt=2022-08-07T18:02:12.694357), id=2, name=Spring Security 초격차 패키지, category=null, authorId=null, deleted=false, status=BookStatus(code=200, description=판매중)), comments=[Comment(super=BaseEntity(createdAt=2022-08-07T18:02:12.719883, updatedAt=2022-08-07T18:02:12.719883), id=3, comment=아니 왜만든거에요???)])
2022-08-07 18:02:15.352  INFO 4928 --- [           main] o.s.t.c.transaction.TransactionContext   : Rolled back transaction for test: [DefaultTestContext@68c9133c testClass = ReviewRepositoryTest, testInstance = com.example.jpa.bookmanager.repository.ReviewRepositoryTest@24b38e8f, testMethod = reviewTest@ReviewRepositoryTest, testException = [null], mergedContextConfiguration = [WebMergedContextConfiguration@7a35b0f5 testClass = ReviewRepositoryTest, locations = '{}', classes = '{class com.example.jpa.bookmanager.BookmanagerApplication}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.springframework.boot.test.autoconfigure.actuate.metrics.MetricsExportContextCustomizerFactory$DisableMetricExportContextCustomizer@41fbdac4, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@5f058f00, org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@1ab3a8c8, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@479d31f3, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@6aba2b86, org.springframework.boot.test.context.SpringBootTestArgs@1, org.springframework.boot.test.context.SpringBootTestWebEnvironment@24273305], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.web.ServletTestExecutionListener.activateListener' -> true, 'org.springframework.test.context.web.ServletTestExecutionListener.populatedRequestContextHolder' -> true, 'org.springframework.test.context.web.ServletTestExecutionListener.resetRequestContextHolder' -> true, 'org.springframework.test.context.event.ApplicationEventsTestExecutionListener.recordApplicationEvents' -> false]]

위의 결과처럼 아주 난리부르스가 난다....

그렇다면 어떻게 수정을 해주어야 불필요한 쿼리들과 비용을 줄일 수 있을까...???


Solution

@Entity
@NoArgsConstructor
@Data
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class Review extends BaseEntity{

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String title;

    private String content;

    private float score;

    @ManyToOne(fetch = FetchType.LAZY)
    @ToString.Exclude
    private User user;

    @ManyToOne(fetch = FetchType.LAZY)
    @ToString.Exclude
    private Book book;

    @OneToMany(fetch = FetchType.LAZY)
    @JoinColumn(name = "review_id")
    private List<Comment> comments;

}

불필요한 ToString 의 중복을 제거하고 ( @ ToString.Exclude )
조회 순간마다 엔티티들을 조회하는것이아닌 한번의 조회로 모든결과를 내준다
( LAZY 지연 로딩)

    @Query("select distinct r from Review r join fetch r.comments")
    List<Review>  findAllByFetchJoin();

    @EntityGraph(attributePaths = "comments")
    @Query("select r from Review r")
    List<Review> findAllByEntityGraph();

그리고 Repository 에 위와 같은 쿼리메소드를 생성해준다.
Fetch 와 Entity Graph를 통해 N+ 1 이슈를 해결해준다.
( Fetch 의 경우 Inner Join // Entity Graph 는 left Outer Join )

Fetch Join

 @Test
    @Transactional
    void reviewTest() {
        List<Review> reviews = reviewRepository.findAllByFetchJoin();
        reviews.forEach(System.out::println);
    }

위의 테스트를 실행후 결과

2022-08-07 18:08:30.396  INFO 10868 --- [           main] o.s.t.c.transaction.TransactionContext   : Began transaction (1) for test context [DefaultTestContext@68c9133c testClass = ReviewRepositoryTest, testInstance = com.example.jpa.bookmanager.repository.ReviewRepositoryTest@43e7f104, testMethod = reviewTest@ReviewRepositoryTest, testException = [null], mergedContextConfiguration = [WebMergedContextConfiguration@7a35b0f5 testClass = ReviewRepositoryTest, locations = '{}', classes = '{class com.example.jpa.bookmanager.BookmanagerApplication}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.springframework.boot.test.autoconfigure.actuate.metrics.MetricsExportContextCustomizerFactory$DisableMetricExportContextCustomizer@41fbdac4, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@5f058f00, org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@1ab3a8c8, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@479d31f3, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@6aba2b86, org.springframework.boot.test.context.SpringBootTestArgs@1, org.springframework.boot.test.context.SpringBootTestWebEnvironment@24273305], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.web.ServletTestExecutionListener.activateListener' -> true, 'org.springframework.test.context.web.ServletTestExecutionListener.populatedRequestContextHolder' -> true, 'org.springframework.test.context.web.ServletTestExecutionListener.resetRequestContextHolder' -> true, 'org.springframework.test.context.event.ApplicationEventsTestExecutionListener.recordApplicationEvents' -> false]]; transaction manager [org.springframework.orm.jpa.JpaTransactionManager@4c02899]; rollback [true]
Hibernate: 
   select
       distinct review0_.id as id1_6_0_,
       comments1_.id as id1_4_1_,
       review0_.created_at as created_2_6_0_,
       review0_.updated_at as updated_3_6_0_,
       review0_.book_id as book_id7_6_0_,
       review0_.content as content4_6_0_,
       review0_.score as score5_6_0_,
       review0_.title as title6_6_0_,
       review0_.user_id as user_id8_6_0_,
       comments1_.created_at as created_2_4_1_,
       comments1_.updated_at as updated_3_4_1_,
       comments1_.comment as comment4_4_1_,
       comments1_.review_id as review_i5_4_1_,
       comments1_.review_id as review_i5_4_0__,
       comments1_.id as id1_4_0__ 
   from
       review review0_ 
   inner join
       comment comments1_ 
           on review0_.id=comments1_.review_id
Review(super=BaseEntity(createdAt=2022-08-07T18:08:28.166819, updatedAt=2022-08-07T18:08:28.166819), id=1, title=내 인생을 바꾼책, content=너무너무 좋았어요, score=5.0, comments=[Comment(super=BaseEntity(createdAt=2022-08-07T18:08:28.171181, updatedAt=2022-08-07T18:08:28.171181), id=1, comment=저도 좋았어요), Comment(super=BaseEntity(createdAt=2022-08-07T18:08:28.176519, updatedAt=2022-08-07T18:08:28.176519), id=2, comment=저는 그냥 그랬어요)])
Review(super=BaseEntity(createdAt=2022-08-07T18:08:28.168033, updatedAt=2022-08-07T18:08:28.168033), id=2, title=조금 속도가 빨라요, content=별로였던거 같기도하고?, score=3.0, comments=[Comment(super=BaseEntity(createdAt=2022-08-07T18:08:28.179968, updatedAt=2022-08-07T18:08:28.179968), id=3, comment=아니 왜만든거에요???)])
2022-08-07 18:08:30.598  INFO 10868 --- [           main] o.s.t.c.transaction.TransactionContext   : Rolled back transaction for test: [DefaultTestContext@68c9133c testClass = ReviewRepositoryTest, testInstance = com.example.jpa.bookmanager.repository.ReviewRepositoryTest@43e7f104, testMethod = reviewTest@ReviewRepositoryTest, testException = [null], mergedContextConfiguration = [WebMergedContextConfiguration@7a35b0f5 testClass = ReviewRepositoryTest, locations = '{}', classes = '{class com.example.jpa.bookmanager.BookmanagerApplication}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.springframework.boot.test.autoconfigure.actuate.metrics.MetricsExportContextCustomizerFactory$DisableMetricExportContextCustomizer@41fbdac4, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@5f058f00, org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@1ab3a8c8, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@479d31f3, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@6aba2b86, org.springframework.boot.test.context.SpringBootTestArgs@1, org.springframework.boot.test.context.SpringBootTestWebEnvironment@24273305], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.web.ServletTestExecutionListener.activateListener' -> true, 'org.springframework.test.context.web.ServletTestExecutionListener.populatedRequestContextHolder' -> true, 'org.springframework.test.context.web.ServletTestExecutionListener.resetRequestContextHolder' -> true, 'org.springframework.test.context.event.ApplicationEventsTestExecutionListener.recordApplicationEvents' -> false]]

Entity Graph

 @Test
    @Transactional
    void reviewTest() {
        List<Review> reviews = reviewRepository.findAllByEntityGraph();
        reviews.forEach(System.out::println);
    }

위의 테스틑 실행후 결과

2022-08-07 18:10:49.099  INFO 9404 --- [           main] o.s.t.c.transaction.TransactionContext   : Began transaction (1) for test context [DefaultTestContext@68c9133c testClass = ReviewRepositoryTest, testInstance = com.example.jpa.bookmanager.repository.ReviewRepositoryTest@43e7f104, testMethod = reviewTest@ReviewRepositoryTest, testException = [null], mergedContextConfiguration = [WebMergedContextConfiguration@7a35b0f5 testClass = ReviewRepositoryTest, locations = '{}', classes = '{class com.example.jpa.bookmanager.BookmanagerApplication}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.springframework.boot.test.autoconfigure.actuate.metrics.MetricsExportContextCustomizerFactory$DisableMetricExportContextCustomizer@41fbdac4, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@5f058f00, org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@1ab3a8c8, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@479d31f3, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@6aba2b86, org.springframework.boot.test.context.SpringBootTestArgs@1, org.springframework.boot.test.context.SpringBootTestWebEnvironment@24273305], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.web.ServletTestExecutionListener.activateListener' -> true, 'org.springframework.test.context.web.ServletTestExecutionListener.populatedRequestContextHolder' -> true, 'org.springframework.test.context.web.ServletTestExecutionListener.resetRequestContextHolder' -> true, 'org.springframework.test.context.event.ApplicationEventsTestExecutionListener.recordApplicationEvents' -> false]]; transaction manager [org.springframework.orm.jpa.JpaTransactionManager@4c02899]; rollback [true]
Hibernate: 
   select
       review0_.id as id1_6_0_,
       comments1_.id as id1_4_1_,
       review0_.created_at as created_2_6_0_,
       review0_.updated_at as updated_3_6_0_,
       review0_.book_id as book_id7_6_0_,
       review0_.content as content4_6_0_,
       review0_.score as score5_6_0_,
       review0_.title as title6_6_0_,
       review0_.user_id as user_id8_6_0_,
       comments1_.created_at as created_2_4_1_,
       comments1_.updated_at as updated_3_4_1_,
       comments1_.comment as comment4_4_1_,
       comments1_.review_id as review_i5_4_1_,
       comments1_.review_id as review_i5_4_0__,
       comments1_.id as id1_4_0__ 
   from
       review review0_ 
   left outer join
       comment comments1_ 
           on review0_.id=comments1_.review_id
Review(super=BaseEntity(createdAt=2022-08-07T18:10:46.270307, updatedAt=2022-08-07T18:10:46.270307), id=1, title=내 인생을 바꾼책, content=너무너무 좋았어요, score=5.0, comments=[Comment(super=BaseEntity(createdAt=2022-08-07T18:10:46.272685, updatedAt=2022-08-07T18:10:46.272685), id=1, comment=저도 좋았어요), Comment(super=BaseEntity(createdAt=2022-08-07T18:10:46.274947, updatedAt=2022-08-07T18:10:46.274947), id=2, comment=저는 그냥 그랬어요)])
Review(super=BaseEntity(createdAt=2022-08-07T18:10:46.271508, updatedAt=2022-08-07T18:10:46.271508), id=2, title=조금 속도가 빨라요, content=별로였던거 같기도하고?, score=3.0, comments=[Comment(super=BaseEntity(createdAt=2022-08-07T18:10:46.281339, updatedAt=2022-08-07T18:10:46.281339), id=3, comment=아니 왜만든거에요???)])
2022-08-07 18:10:49.301  INFO 9404 --- [           main] o.s.t.c.transaction.TransactionContext   : Rolled back transaction for test: [DefaultTestContext@68c9133c testClass = ReviewRepositoryTest, testInstance = com.example.jpa.bookmanager.repository.ReviewRepositoryTest@43e7f104, testMethod = reviewTest@ReviewRepositoryTest, testException = [null], mergedContextConfiguration = [WebMergedContextConfiguration@7a35b0f5 testClass = ReviewRepositoryTest, locations = '{}', classes = '{class com.example.jpa.bookmanager.BookmanagerApplication}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.springframework.boot.test.autoconfigure.actuate.metrics.MetricsExportContextCustomizerFactory$DisableMetricExportContextCustomizer@41fbdac4, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@5f058f00, org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@1ab3a8c8, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@479d31f3, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@6aba2b86, org.springframework.boot.test.context.SpringBootTestArgs@1, org.springframework.boot.test.context.SpringBootTestWebEnvironment@24273305], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.web.ServletTestExecutionListener.activateListener' -> true, 'org.springframework.test.context.web.ServletTestExecutionListener.populatedRequestContextHolder' -> true, 'org.springframework.test.context.web.ServletTestExecutionListener.resetRequestContextHolder' -> true, 'org.springframework.test.context.event.ApplicationEventsTestExecutionListener.recordApplicationEvents' -> false]]

Feth Join 과 Entity Graph의 차이는 Inner Join 과 left Outer Join 말고는 없다.


+@

우리가 기본적으로 JPARepository 에서 findAll()을 재정의해서 사용할 수 있다.

 @EntityGraph(attributePaths = "comments")
    List<Review> findAll();

위와 같이 정의 해준후 테스트를 실행하면 동일한 결과가 나온다.

Hibernate: 
  select
      review0_.id as id1_6_0_,
      comments1_.id as id1_4_1_,
      review0_.created_at as created_2_6_0_,
      review0_.updated_at as updated_3_6_0_,
      review0_.book_id as book_id7_6_0_,
      review0_.content as content4_6_0_,
      review0_.score as score5_6_0_,
      review0_.title as title6_6_0_,
      review0_.user_id as user_id8_6_0_,
      comments1_.created_at as created_2_4_1_,
      comments1_.updated_at as updated_3_4_1_,
      comments1_.comment as comment4_4_1_,
      comments1_.review_id as review_i5_4_1_,
      comments1_.review_id as review_i5_4_0__,
      comments1_.id as id1_4_0__ 
  from
      review review0_ 
  left outer join
      comment comments1_ 
          on review0_.id=comments1_.review_id
Review(super=BaseEntity(createdAt=2022-08-07T18:13:06.779017, updatedAt=2022-08-07T18:13:06.779017), id=1, title=내 인생을 바꾼책, content=너무너무 좋았어요, score=5.0, comments=[Comment(super=BaseEntity(createdAt=2022-08-07T18:13:06.784070, updatedAt=2022-08-07T18:13:06.784070), id=1, comment=저도 좋았어요), Comment(super=BaseEntity(createdAt=2022-08-07T18:13:06.786952, updatedAt=2022-08-07T18:13:06.786952), id=2, comment=저는 그냥 그랬어요)])
Review(super=BaseEntity(createdAt=2022-08-07T18:13:06.782331, updatedAt=2022-08-07T18:13:06.782331), id=2, title=조금 속도가 빨라요, content=별로였던거 같기도하고?, score=3.0, comments=[Comment(super=BaseEntity(createdAt=2022-08-07T18:13:06.788297, updatedAt=2022-08-07T18:13:06.788297), id=3, comment=아니 왜만든거에요???)])
profile
어디까지 올라갈지 궁금한 하루

0개의 댓글