๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ

SpringBoot

[SpringBoot] EAGER๋Š” join ์ด ์•„๋‹ˆ๋‹ค

โœ๐Ÿป ๊ธ€ ์ž‘์„ฑ์˜ ๊ณ„๊ธฐ

์–ด๋А๋ง ๋ ˆ๋ฒจ3์˜ ํ”„๋กœ์ ํŠธ๋ฅผ ์ง„ํ–‰ํ•˜๋ฉฐ ํŒ€์›๋“ค์—๊ฒŒ fetchType ์€ LAZY ๋กœ ํ•˜๋Š” ๊ฒŒ ์ข‹๋‹ค๋Š” ๋ง์„ ํ–ˆ๋‹ค. ๊ทธ๋Ÿฌ์ž ํŒ€์›๋“ค์ด ์™œ๋ƒ๊ณ  ๋ฌผ์—ˆ๊ณ , ํ•œ๋งˆ๋””๋กœ ์„ ๋œป ์ •๋ฆฌํ•˜์ง€ ๋ชปํ•˜๋Š” ๋‚˜๋ฅผ ๋ฐœ๊ฒฌํ–ˆ๋‹ค. ๊ทธ์ € 'EAGER ๋Š” join ์ด ์•„๋‹ˆ์•ผ..' ๋ผ๋Š” ์• ๋งคํ•œ ๋ง๋งŒ ๋˜ํ’€์ดํ–ˆ๋‹ค.
 
์ดํ›„ ๋‚ด์šฉ์„ ์ •๋ฆฌํ•ด์„œ ํŒ€์›๋“ค์—๊ฒŒ ์ด๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ๊ธ€ ์ž‘์„ฑ๊นŒ์ง€ ํ•˜๊ฒŒ ๋๋‹ค.
 

๐Ÿ”Ž ์ด ๊ธ€์„ ์ด๋Ÿฐ ๋ถ„๋“ค๊ป˜ ์ถ”์ฒœํ•ฉ๋‹ˆ๋‹ค

  • LAZY ๋กœ ๋งค๋ฒˆ ์„ค์ •ํ•˜๊ธด ํ•˜๋Š”๋ฐ, ์™œ์ธ์ง€๋Š” ์†”์งํžˆ ์ž˜ ๋ชจ๋ฅด๊ฒ ๋‹ค ํ•˜์‹œ๋Š” ๋ถ„๋“ค
  • ๋งค๋ฒˆ EAGER ๋Š” join ์ด ๋˜๋Š” ๊ฒƒ์ด๋ผ๊ณ  ์ƒ๊ฐํ•˜๊ณ  ์žˆ๋Š” ๋ถ„๋“ค

EAGER ๊ฐ€ ๊ณ ๋ ค๋˜๋Š” ์ƒํ™ฉ

๋ธ”๋กœ๊ทธ ์„œ๋น„์Šค๋ฅผ ๊ตฌํ˜„ํ•˜๊ณ  ์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด๋ณด์ž. Blog ๋Š” ๊ฒŒ์‹œ๊ธ€, BlogMember ๋Š” ์ž‘์„ฑ์ž๋ฅผ ์˜๋ฏธํ•˜๋ฉฐ Post ๊ฐ€ N:1 ์˜ ๊ด€๊ณ„๋กœ Member ๋ฅผ ๊ฐ–๊ณ  ์žˆ๋‹ค. 
 
๐Ÿค” โ€œBlog๋ž‘ BlogMember๋Š” ํ•ญ์ƒ ๊ฐ™์ด ์“ฐ์ด๋Š”๋ฐ, ๊ทธ๋ƒฅ EAGER๋กœ ๋ฌถ์œผ๋ฉด ํŽธํ•˜์ง€ ์•Š์•„?โ€
 
์—”ํ‹ฐํ‹ฐ๋ฅผ ๊ตฌ์„ฑํ•˜๋ฉฐ ์œ„์™€ ๊ฐ™์€ ๊ณ ๋ฏผ์„ ํ•œ๋ฒˆ์ฏค์€ ํ•ด๋ดค์„ ๊ฒƒ์ด๋‹ค.
โ€œ๋งค๋ฒˆ fetch join ํ•˜๊ธฐ๋„ ๊ท€์ฐฎ๊ณ , ํ•ญ์ƒ ํ•จ๊ป˜ ์กฐํšŒ๋˜๋‹ˆ๊นŒ!โ€
 
๊ทธ๋Ÿฌ๋‚˜ ์‚ฌ์‹ค 'EAGER' ๋Š” ์šฐ๋ฆฌ๊ฐ€ ์ƒ๊ฐํ•˜๋Š” ๊ทธ โ€˜EAGERโ€™๋ž‘์€ ์ข€ ๋‹ค๋ฅด๋‹ค..
 

findById ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ƒํ™ฉ์—์„œ์˜ EAGER

@Test
void test1() {
    // given
    BlogMember hero = new BlogMember("ํžˆ๋กœ");
    BlogMember savedMember = blogMemberRepository.save(hero);

    Blog blog = new Blog(savedMember);
    Blog savedBlog = blogRepository.save(blog);

    // when
    Long id = savedBlog.getId();

    em.clear();

    System.out.println("======");
    Blog blog1 = blogRepository.findById(id).get();
    System.out.println(blog1.getMember().getName());
}
Hibernate: 
    select
        b1_0.id,
        m1_0.id,
        m1_0.name 
    from
        blog b1_0 
    left join
        blog_member m1_0 
            on m1_0.id=b1_0.member_id 
    where
        b1_0.id=?

 
findById ๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์กฐํšŒ๋ฅผ ํ•˜๊ฒŒ ๋˜๋ฉด, ์šฐ๋ฆฌ๊ฐ€ ์˜ˆ์ƒํ•œ ๋ฐ”์™€ ๊ฐ™์ด join ์ด ๋ผ์„œ ์ฟผ๋ฆฌ๊ฐ€ ๋‚˜๊ฐ€๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.
์ด ์ƒํ™ฉ์—์„œ๋Š” ์•„๋ฌด๋Ÿฐ ๋ถ€์ž‘์šฉ์ด ๋ฐœ์ƒํ•˜์ง€ ์•Š๋Š”๋‹ค.
 

Query Method ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ

@Test
void test2() {
    // given
    BlogMember hero = new BlogMember("ํžˆ๋กœ");
    BlogMember savedMember = blogMemberRepository.save(hero);

    Blog blog = new Blog(savedMember);
    Blog savedBlog = blogRepository.save(blog);

    // when
    Long id = savedBlog.getId();

    em.clear();

    System.out.println("======");
    Blog blog1 = blogRepository.findByMember(savedMember).get();
    System.out.println(blog1.getMember().getName());
}
Hibernate: 
    select
        b1_0.id,
        b1_0.member_id 
    from
        blog b1_0 
    where
        b1_0.member_id=?
Hibernate: 
    select
        bm1_0.id,
        bm1_0.name 
    from
        blog_member bm1_0 
    where
        bm1_0.id=?

 
 
๋งŒ์•ฝ findByMember ์™€ ๊ฐ™์ด ์ง์ ‘ ์ •์˜ํ•œ ์ฟผ๋ฆฌ ๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด ์กฐํšŒํ•œ๋‹ค๋ฉด ์ฟผ๋ฆฌ๊ฐ€ ์œ„์™€ ๊ฐ™์ด ๋‚˜๊ฐ„๋‹ค.
์šฐ๋ฆฌ๊ฐ€ ์ƒ์ƒํ•œ ๋ฐ”๋กœ๋Š”, EAGER ์ด๊ธฐ์— Blog ๋ฅผ ์กฐํšŒํ•ด์˜ฌ ๋•Œ join ์„ ํ•ด์„œ BlogMember ๋ฅผ ์กฐํšŒํ•ด์˜ฌ ๊ฒƒ์ด๋ผ๊ณ  ์ƒ๊ฐํ•œ๋‹ค.
๊ทธ๋Ÿฌ๋‚˜ ์‚ฌ์‹ค์€ select ์ฟผ๋ฆฌ๊ฐ€ ๋‘๋ฒˆ ๋‚˜๊ฐ”๋‹ค. ์™œ ์ด๋ ‡๊ฒŒ ๋‹ฌ๋ผ์ง€๋Š” ๊ฒƒ์ผ๊นŒ?

์šฐ๋ฆฌ๋Š” ์ด์ œ๊นŒ์ง€ fetchType ์„ ์ž˜๋ชป ์ดํ•ดํ•˜๊ณ  ์žˆ์—ˆ๋‹ค

fetchType ์€ ์‚ฌ์‹ค ์—ฐ๊ด€๋œ ์—”ํ‹ฐํ‹ฐ๋ฅผ ์–ธ์ œ ๋กœ๋”ฉํ• ์ง€ ์— ๋Œ€ํ•œ ํžŒํŠธ์ผ ๋ฟ, ์–ด๋–ป๊ฒŒ ๋กœ๋”ฉํ• ์ง€(์ฟผ๋ฆฌ ๋ฐฉ์‹) ๋Š” ์ •์˜ํ•˜์ง€ ์•Š๋Š”๋‹ค.
 
์ฆ‰, FetchType.EAGER ์€ '๋‹น์žฅ ์ด ์—ฐ๊ด€ ์—”ํ‹ฐํ‹ฐ๋„ ๊ฐ™์ด ํ•„์š”ํ•˜๋‹ˆ๊นŒ ์ง€๊ธˆ ๋กœ๋”ฉํ•˜์ž' ๋Š” ์˜๋ฏธ์ง€๋งŒ, ์กฐ์ธ์œผ๋กœ ๊ฐ€์ ธ์˜ฌ์ง€, ์ถ”๊ฐ€ ์ฟผ๋ฆฌ๋ฅผ ๋‚ ๋ฆด์ง€ ๋Š” JPA ๊ตฌํ˜„์ฒด(Hibernate) ๊ฐ€ ์„ ํƒํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

Hibernate ์—์„œ์˜ fetchType ๋™์ž‘ ๋ฐฉ์‹

findById() (JPQL X)๋ณดํ†ต LEFT JOIN ์œผ๋กœ ์—ฐ๊ด€ ์—”ํ‹ฐํ‹ฐ๊นŒ์ง€ ํ•œ ๋ฒˆ์— ๊ฐ€์ ธ์˜ด
findByXXX() (Query Method)ํ”„๋ก์‹œ๋ฅผ ์šฐ์„  ๋ฐ˜ํ™˜ํ•˜๊ณ , ์‹ค์ œ ํ•„๋“œ๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ N๋ฒˆ์˜ select ๋กœ ์ถ”๊ฐ€ ์กฐํšŒ ๋ฐœ์ƒ (N+1 ๋ฌธ์ œ)
JPQL ์ž‘์„ฑ ์‹œ fetch join ๋ช…์‹œJOIN FETCH ๋กœ ๋ช…์‹œ์ ์œผ๋กœ ์กฐ์ธํ•˜์—ฌ ํ•œ ๋ฒˆ์— ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ์Œ

 
Hibernate ๋Š” ์œ„์™€ ๊ฐ™์€ ๋ฐฉ์‹์„ ๋”ฐ๋ฅด๊ณ  ์žˆ๋‹ค. ์œ„์™€ ๊ฐ™์€ ๊ฒฝ์šฐ findByMember ๋Š” ์ฟผ๋ฆฌ ๋ฉ”์„œ๋“œ์ด๊ธฐ์— ํ”„๋ก์‹œ๋กœ BlogMember ๊ฐ€ ์šฐ์„ ์ ์œผ๋กœ ๋ฐ˜ํ™˜๋œ ๊ฒƒ์ด๊ณ , EAGER ์ด๊ธฐ์— ๋ฐ”๋กœ BlogMember ๋ฅผ ๊ฐ€์ ธ์™€์•ผ ํ•ด์„œ ๋ฐ”๋กœ ์ฟผ๋ฆฌ๊ฐ€ ๋‚˜๊ฐ„ ๊ฒƒ์ด๋‹ค.
 
๋งŒ์•ฝ LAZY ์˜€๋‹ค๋ฉด, ์‹ค์ œ๋กœ ์‚ฌ์šฉ๋˜๋Š” ์‹œ์ ์— ํ•ด๋‹น ๊ฐ์ฒด๊ฐ€ ํ”„๋ก์‹œ๊ฐ€ ์•„๋‹Œ ๊ฒƒ์œผ๋กœ ๋Œ€์ฒด๋˜๊ธฐ ๋•Œ๋ฌธ์— .getBlogMember() ์™€ ๊ฐ™์ด ์‹ค์ œ๋กœ ํ˜ธ์ถœ๋˜๋Š” ์‹œ์ ์— ์ฟผ๋ฆฌ๊ฐ€ ๋‚˜๊ฐ”์„ ๊ฒƒ์ด๋‹ค.
 
์ด๋Š” ๊ณต์‹ ๋ฌธ์„œ์—์„œ๋„ ์‹ค์ œ๋กœ ์–ธ๊ธ‰๋˜์–ด ์žˆ๋‹ค.
 
https://docs.jboss.org/hibernate/orm/current/userguide/html_single/Hibernate_User_Guide.html#fetching

Hibernate ORM User Guide

Starting in 6.0, Hibernate allows to configure the default semantics of List without @OrderColumn via the hibernate.mapping.default_list_semantics setting. To switch to the more natural LIST semantics with an implicit order-column, set the setting to LIST.

docs.jboss.org

์„ ํƒํ•ด์•ผ ํ•˜๋Š” ์ „๋žต

  • ๋ชจ๋“  ์—ฐ๊ด€ ๊ด€๊ณ„๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ LAZY๋กœ ์„ค์ •
  • ํ•„์š”ํ•œ ๊ฒฝ์šฐ์—๋งŒ JOIN FETCH ๋กœ ๋ช…์‹œ์  ๋กœ๋”ฉ ์ œ์–ด

EAGER ๋กœ ์„ค์ •ํ•˜๊ฒŒ ๋˜๋ฉด, ๋ฌด์—‡๋ณด๋‹ค ๊ฐœ๋ฐœ์ž๊ฐ€ ์˜ˆ์ธกํ•  ์ˆ˜ ์—†๋Š” ์ฟผ๋ฆฌ๊ฐ€ ๋‚˜๊ฐˆ ๊ฐ€๋Šฅ์„ฑ์ด ์กด์žฌํ•˜๊ธฐ ๋•Œ๋ฌธ์— LAZY ๋ฅผ ๊ธฐ๋ณธ์ ์œผ๋กœ ์„ ํƒํ•˜๊ณ  FETCH JOIN ์„ ํ†ตํ•ด ์ œ์–ดํ•  ์ˆ˜ ์žˆ๋Š” ์„ ์— ๋‘๋Š” ๊ฒƒ์ด ๊ฐ€์žฅ ์ ์ ˆํ•˜๋‹ค.

EAGER ๋Š” ๊ทธ๋Ÿผ ์™œ ์žˆ์„๊นŒ?

์•„์ด๋Ÿฌ๋‹ˆํ•˜๊ฒŒ๋„, Hibernate ์˜ ๊ณต์‹ ๋ฌธ์„œ์—์„œ๋Š” LAZY ๋กœ๋”ฉ์„ ์‚ฌ์šฉํ•  ๊ฒƒ์„ ๊ฐ•์กฐํ•˜๋ฉด์„œ๋„ Jakarta ์—์„œ๋Š” ๋ชจ๋“  ๊ตฌํ˜„์ฒด๋“ค์ด EAGER ๋ฅผ ๊ธฐ๋ณธ์ ์œผ๋กœ ์ œ๊ณตํ•ด์•ผ ํ•  ๊ฒƒ์„ ๊ฐ•์š”ํ•˜๊ณ  ์žˆ๋‹ค.

 
JPA 1.0 ์—์„œ๋Š” ๋ชจ๋“  ๊ตฌํ˜„์ฒด๋“ค์ด ํ”„๋ก์‹œ๋ฅผ ์‚ฌ์šฉํ•  ๊ฒƒ์ด๋ผ๊ณ  ์ƒ๊ฐํ•˜์ง€ ์•Š์•˜๋‹ค. ๊ทธ๋ž˜์„œ @ManyToOne, @OneToOne ์˜ ๋ชจ๋“  fetchType ์ด ๊ธฐ๋ณธ์ ์œผ๋กœ EAGER ์˜€๋˜ ๊ฒƒ์ด๋‹ค.
 
์ฆ‰, ์ดˆ๊ธฐ JPA ํ‘œ์ค€์ด ๋ชจ๋“  ๊ตฌํ˜„์ฒด๊ฐ€ ํ”„๋ก์‹œ๋ฅผ ํ†ตํ•œ ์œ ์—ฐํ•œ ์ง€์—ฐ ๋กœ๋”ฉ์„ ์ œ๊ณตํ•  ๊ฒƒ์ด๋ผ๊ณ  ํ™•์‹ ํ•˜์ง€ ๋ชปํ–ˆ๊ธฐ ๋•Œ๋ฌธ์—, ๋ชจ๋“  ๊ณต๊ธ‰์ž๊ฐ€ ํ™•์‹คํžˆ ์ง€์›ํ•  ์ˆ˜ ์žˆ๋Š” '์ฆ‰์‹œ ๋กœ๋”ฉ'์„ ๊ณตํ†ต์˜ ๊ธฐ๋ณธ ์ž‘๋™ ๋ฐฉ์‹์œผ๋กœ ์‚ผ์•„ ํ˜ธํ™˜์„ฑ๊ณผ ๊ธฐ๋ณธ์ ์ธ ๋ฐ์ดํ„ฐ ๋กœ๋”ฉ์„ ๋ณด์žฅํ•˜๋ ค ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.
 
๋˜ํ•œ ๊ฐœ์ธ์ ์ธ ์ƒ๊ฐ์œผ๋กœ๋Š”, EAGER ์ž์ฒด๊ฐ€ ๊ฐ์ฒด์ง€ํ–ฅ์ ์ธ ๊ด€์ ์— ๋” ๊ฐ€๊น๋‹ค๊ณ  ๋ณด์—ฌ์ง„๋‹ค.
blog.getBlogMember() ๋ฅผ ํ–ˆ๋Š”๋ฐ ๊ทธ์ œ์•ผ ์ฟผ๋ฆฌ๊ฐ€ ๋‚˜๊ฐ€๊ณ  ํ”„๋ก์‹œ, ์‚ฌ์‹ค์ƒ ๊ฐ€์งœ ๊ฐ์ฒด๊ฐ€ ํ•„๋“œ์— ์กด์žฌํ•œ๋‹ค๋Š” ๊ฒƒ ์ž์ฒด๊ฐ€ ๊ฐ์ฒด์ง€ํ–ฅ์ ์ด์ง€๋Š” ์•Š์€ ๊ด€์ ์ด๋‹ค. ๊ทธ๋ ‡๊ธฐ์— ์–ด๋–ป๊ฒŒ ๋ณด๋ฉด ์ดˆ๊ธฐ์—๋Š” EAGER ์˜ ๊ธฐ๋ณธ์ ์ธ ์‚ฌ์šฉ์„ ๊ณ ๋ คํ–ˆ๋˜ ๊ฒŒ ๋‹น์—ฐํ•˜์ง€ ์•Š์„๊นŒ ์‹ถ๊ธฐ๋„ ํ•˜๋‹ค.