๐ŸŒป JAVA/์ž๋ฐ” ORM ํ‘œ์ค€ JPA ํ”„๋กœ๊ทธ๋ž˜๋ฐ

[JPA] N+1 ๋ฌธ์ œ์™€ ํ•ด๊ฒฐ (feat. fetch join, EntityGraph)

iseunghan 2024. 2. 27. 10:50
๋ฐ˜์‘ํ˜•
๐Ÿ’ก ์•„๋ž˜ ์‹ค์Šต์— ์ง„ํ–‰ํ•œ ๋ชจ๋“  ์ฝ”๋“œ๋Š” Github์— ์žˆ์Šต๋‹ˆ๋‹ค.

JPA N+1์ด๋ž€?

์‹ค๋ฌด์—์„œ JPA๋ฅผ ์‚ฌ์šฉํ•˜๋‹ค๋ณด๋ฉด, N+1 ์ฟผ๋ฆฌ๋ฅผ ๋งŒ๋‚˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œ N+1์ด๋ž€ Team(1) ↔ Member(N) ์—ฐ๊ด€๊ด€๊ณ„๊ฐ€ ์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ–ˆ์„ ๋•Œ, ํ•˜๋‚˜์˜ ํŒ€์„ ์กฐํšŒํ–ˆ์ง€๋งŒ ํŒ€ ๋‚ด๋ถ€์— ์žˆ๋Š” ๋ชจ๋“  ๋ฉค๋ฒ„๋“ค์ด ํ•จ๊ป˜ ์กฐํšŒ๋˜๋ฉด์„œ 1+N ๊ฐœ์˜ ์ฟผ๋ฆฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. ์ง์ ‘ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ํ†ตํ•ด ์ด๋Ÿฌํ•œ ์ƒํ™ฉ๋“ค์„ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•๋“ค์„ ์‚ดํŽด๋ณด๊ณ  ๊ฐ ์ƒํ™ฉ์ด ๋˜ ์–ด๋–ค ์‚ฌ์ด๋“œ์ดํŽ™ํŠธ๊ฐ€ ์žˆ๋Š”์ง€๋„ ์•Œ์•„๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

Entity ๋ฐ Repository ์ฝ”๋“œ

์‹ค์Šต์— ์‚ฌ์šฉ๋  ์ฝ”๋“œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
public class Member {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "TEAM_ID")
    private Team team;

    @Builder
    private Member(String name) {
        this.name = name;
    }

    public void updateTeam(Team team) {
        this.team = team;
    }
}
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
public class Team {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;

    @OneToMany(mappedBy = "team", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    private List<Member> members = new ArrayList<>();

    @Builder
    private Team(String name) {
        this.name = name;
    }

    public void addMember(Member member) {
        this.members.add(member);
        member.updateTeam(this);
    }
}
public interface TeamRepository extends JpaRepository<Team, Long> {
    Optional<Team> findTeamByName(String name);
}
@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
public class Member_Lazy_Team_Lazy_Test {
    @Autowired private MemberRepository memberRepository;
    @Autowired private TeamRepository teamRepository;
    @PersistenceContext private EntityManager em;

    @BeforeEach
    void setup() {
        System.out.println("----------setup start-----------");
        Team team1 = Team.builder().name("team1").build();
        team1.addMember(Member.builder().name("member1-1").build());
        team1.addMember(Member.builder().name("member1-2").build());

        Team team2 = Team.builder().name("team2").build();
        team2.addMember(Member.builder().name("member2-1").build());
        team2.addMember(Member.builder().name("member2-2").build());

        Team team3 = Team.builder().name("team3").build();
        team3.addMember(Member.builder().name("member3-1").build());
        team3.addMember(Member.builder().name("member3-2").build());

        teamRepository.save(team1);
        teamRepository.save(team2);
        teamRepository.save(team3);

        clearPersistenceContext();
        System.out.println("----------setup end-----------");
    }

    @AfterEach
    void clear() {
        System.out.println("----------clear start-----------");
        memberRepository.deleteAll();
        teamRepository.deleteAll();
        clearPersistenceContext();
        System.out.println("----------clear end-----------");
    }

    private void clearPersistenceContext() {
        em.flush();
        em.clear();
    }

    @DisplayName("๋ชจ๋“  ํŒ€์„ ์กฐํšŒํ•˜๊ณ , ์ง€์—ฐ๋กœ๋”ฉ ๋œ ๋ฉค๋ฒ„๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ -> N+1์ด ๋ฐœ์ƒํ•œ๋‹ค.(team1,2,3 ์กฐํšŒํ•˜๋Š” ์ฟผ๋ฆฌ 1๊ฐœ, team1,2,3์— ๋Œ€ํ•œ ๋ฉค๋ฒ„ ์กฐํšŒํ•˜๋Š” ์ฟผ๋ฆฌ 3๊ฐœ)")
    @Test
    void team_findAll_test() {
        clearPersistenceContext();

        System.out.println("----------team_findAll_test start-----------");
        List<Team> teamList = teamRepository.findAll();
        assertThat(teamList).hasSize(3);
        System.out.println("----------team_findAll_test mid-----------");
        teamList.stream()
                .map(Team::getMembers)
                .map(List::stream)
                .forEach(memberStream -> memberStream
                        .map(Member::getName)
                        .forEach(System.out::println)
                );
        System.out.println("----------team_findAll_test end-----------");
    }

    @DisplayName("๋ชจ๋“  ๋ฉค๋ฒ„๋ฅผ ์กฐํšŒํ•˜๊ณ , ์ง€์—ฐ๋กœ๋”ฉ ๋œ ํŒ€์„ ์‚ฌ์šฉํ•  ๋•Œ -> N+1์ด ๋ฐœ์ƒํ•œ๋‹ค.(๋ชจ๋“  ๋ฉค๋ฒ„ ์กฐํšŒ 1๊ฐœ, ๊ฐ ํŒ€์„ ์กฐํšŒํ•˜๋Š” ์ฟผ๋ฆฌ 3๊ฐœ)")
    @Test
    void member_findAll_test() {
        clearPersistenceContext();

        System.out.println("----------member_findAll_test start-----------");
        List<Member> memberList = memberRepository.findAll();
        assertThat(memberList).hasSize(6);
        System.out.println("----------member_findAll_test mid-----------");
        memberList.stream()
                .map(Member::getTeam)
                .map(Team::getName)
                .forEach(System.out::println);
        System.out.println("----------member_findAll_test end-----------");
    }
}

์œ„ ํ…Œ์ŠคํŠธ์—์„œ ํ™•์ธํ•˜๊ณ ์ž ํ•˜๋Š” ๊ฒƒ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

1. ๋ชจ๋“  ํŒ€์„ ์กฐํšŒํ•˜๊ณ , ๊ฐ ํŒ€์— ์†ํ•œ ๋ฉค๋ฒ„๋“ค์˜ ์ด๋ฆ„์„ ์ถœ๋ ฅํ–ˆ์„ ๋•Œ, ์ฟผ๋ฆฌ๊ฐ€ ์–ด๋–ป๊ฒŒ ๋‚˜๊ฐ€๋Š” (team_findAll_test)ํŒ€ ์กฐํšŒ ํ›„, ๊ฐ ํŒ€์— ์†ํ•œ ๋ฉค๋ฒ„๋“ค์„ ์‚ฌ์šฉํ•  ๋•Œ ์ถ”๊ฐ€ ์ฟผ๋ฆฌ๊ฐ€ ๋‚˜๊ฐ€๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. (N+1) ์ด์ „์—๋Š” ๊ฐ ๋ฉค๋ฒ„์˜ ID๋กœ ์กฐํšŒํ•˜๋Š” ์ฟผ๋ฆฌ๊ฐ€ ๋‚˜๊ฐ€์„œ 4๊ฐœ์˜ ์ฟผ๋ฆฌ๊ฐ€ ์•„๋‹Œ ๊ฐ ๋ฉค๋ฒ„๋ฅผ ์กฐํšŒํ•˜๋Š” 7๊ฐœ์˜ ์ฟผ๋ฆฌ๊ฐ€ ๋‚˜๊ฐ”์—ˆ๋Š”๋ฐ ์–ธ์ œ๋ถ€ํ„ด๊ฐ€ JPA ์ชฝ์—์„œ ์ตœ์ ํ™”๋ฅผ ํ–ˆ๋Š”์ง€ team_id๋ฅผ ํ†ตํ•ด ์กฐํšŒํ•˜๋Š” ์ฟผ๋ฆฌ ๋•๋ถ„์— 4๊ฐœ๋กœ ์ค„์—ˆ์Šต๋‹ˆ๋‹ค. (์–ธ์ œ ์ตœ์ ํ™”๊ฐ€ ์ด๋ค„์กŒ๋Š”์ง€ ์•„์‹œ๋Š”๋ถ„ ๊ผญ ์ข€ ์•Œ๋ ค์ฃผ์„ธ์š”….)

----------team_findAll_test start-----------
Hibernate: 
    select
        t1_0.id,
        t1_0.name 
    from
        team t1_0
----------team_findAll_test mid-----------
Hibernate: 
    select
        m1_0.team_id,
        m1_0.id,
        m1_0.name 
    from
        member m1_0 
    where
        m1_0.team_id=?
member1-1
member1-2
Hibernate: 
    select
        m1_0.team_id,
        m1_0.id,
        m1_0.name 
    from
        member m1_0 
    where
        m1_0.team_id=?
member2-1
member2-2
Hibernate: 
    select
        m1_0.team_id,
        m1_0.id,
        m1_0.name 
    from
        member m1_0 
    where
        m1_0.team_id=?
member3-1
member3-2
----------team_findAll_test end-----------

2. ๋ชจ๋“  ๋ฉค๋ฒ„๋ฅผ ์กฐํšŒํ•˜๊ณ , ๊ฐ ๋ฉค๋ฒ„๋“ค์˜ ํŒ€์˜ ์ด๋ฆ„์„ ์ถœ๋ ฅํ–ˆ์„ ๋•Œ, ์ฟผ๋ฆฌ๊ฐ€ ์–ด๋–ป๊ฒŒ ๋‚˜๊ฐ€๋Š”์ง€ (member_findAll_test)Member์˜ Team์€ LAZY ์ „๋žต์œผ๋กœ ๋˜์–ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์— member๋ฅผ ์กฐํšŒํ•˜๋Š” ์‹œ์ ์—๋Š” member ์กฐํšŒ ์ฟผ๋ฆฌ๋งŒ ๋‚˜๊ฐ€๊ณ , ์‹ค์ œ Team์„ ์‚ฌ์šฉํ•  ๋•Œ Team์„ ์กฐํšŒํ•˜๋Š” ์ฟผ๋ฆฌ๊ฐ€ ๋‚˜๊ฐ‘๋‹ˆ๋‹ค.

----------member_findAll_test start-----------
Hibernate: 
    select
        m1_0.id,
        m1_0.name,
        m1_0.team_id 
    from
        member m1_0
----------member_findAll_test mid-----------
Hibernate: 
    select
        t1_0.id,
        t1_0.name 
    from
        team t1_0 
    where
        t1_0.id=?
team1
team1
Hibernate: 
    select
        t1_0.id,
        t1_0.name 
    from
        team t1_0 
    where
        t1_0.id=?
team2
team2
Hibernate: 
    select
        t1_0.id,
        t1_0.name 
    from
        team t1_0 
    where
        t1_0.id=?
team3
team3
----------member_findAll_test end-----------

์ด๋ ‡๋“ฏ ์‹ค๋ฌด์—์„œ N+1 ์ฟผ๋ฆฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด, ํ…Œ์ŠคํŠธ ์ˆ˜์ค€์ฒ˜๋Ÿผ ๋ช‡๊ฐœ๊ฐ€ ์•„๋‹Œ ๋ช‡์ฒœ๊ฐœ์—์„œ ๋ช‡๋งŒ๊ฐœ๊ฐ€ ์กฐํšŒ๋˜๋Š” ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜๊ณ  ์žฅ์• ๋กœ ์ด์–ด์งˆ ๊ฐ€๋Šฅ์„ฑ์ด ํฝ๋‹ˆ๋‹ค. ์–ด๋–ป๊ฒŒ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ๋ฐ”๋กœ ์•Œ์•„๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

ํ•ด๊ฒฐ๋ฐฉ๋ฒ•

1. FetchJoin ์‚ฌ์šฉ

public interface TeamRepository extends JpaRepository<Team, Long> {
	@Query(value = "select t from Team t join fetch t.members")
	List<Team> findTeamsFetchJoin();
}
@DisplayName("๋ชจ๋“  ํŒ€์„ ์กฐํšŒํ•˜๊ณ , ์ง€์—ฐ๋กœ๋”ฉ ๋œ ๋ฉค๋ฒ„๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ -> ๋‹จ 1๊ฐœ์˜ ์ฟผ๋ฆฌ๋งŒ ๋‚˜๊ฐ„๋‹ค")
@Test
void team_findAll_test() {
    clearPersistenceContext();

    System.out.println("----------team_findAll_test start-----------");
    List<Team> teamList = teamRepository.findTeamsFetchJoin();
    assertThat(teamList).hasSize(3);
    System.out.println("----------team_findAll_test mid-----------");
    teamList.stream()
            .map(Team::getMembers)
            .map(List::stream)
            .forEach(memberStream -> memberStream
                    .map(Member::getName)
                    .forEach(System.out::println)
            );
    System.out.println("----------team_findAll_test end-----------");
}

ํŒ€์„ ์กฐํšŒํ•  ๋•Œ, ํŒ€์— ์†ํ•œ ๋ฉค๋ฒ„๋“ค๊นŒ์ง€ ํ•œ๋ฐฉ์— Fetch Join ํ•˜๋Š” ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ๋˜๋ฉด ์—ฐ๊ด€๋œ ์—”ํ‹ฐํ‹ฐ ๋˜๋Š” ์ปฌ๋ ‰์…˜์„ ํ”„๋ก์‹œ ๊ฐ์ฒด๊ฐ€ ์•„๋‹Œ ์ฆ‰์‹œ๋กœ๋”ฉ์œผ๋กœ ๊ฐ€์ ธ์˜ค๊ธฐ ๋•Œ๋ฌธ์— ์ฟผ๋ฆฌ๋Š” ํ•˜๋‚˜๋งŒ ๋ฐœ์ƒํ•˜๊ฒŒ ๋  ๊ฒƒ ์ž…๋‹ˆ๋‹ค.

----------team_findAll_test start-----------
Hibernate: 
    select
        t1_0.id,
        m1_0.team_id,
        m1_0.id,
        m1_0.name,
        t1_0.name 
    from
        team t1_0 
    join
        member m1_0 
            on t1_0.id=m1_0.team_id
----------team_findAll_test mid-----------
member1-1
member1-2
member2-1
member2-2
member3-1
member3-2
----------team_findAll_test end-----------

์ด๋Ÿฐ์‹์œผ๋กœ Inner Join์„ ํ†ตํ•ด์„œ ์—ฐ๊ด€๋œ ๋ฉค๋ฒ„๋“ค์„ ๋ชจ๋‘ ์กฐํšŒํ•ด์„œ 1์ฐจ ์บ์‹œ์— ๋„ฃ์–ด๋‘๊ธฐ ๋•Œ๋ฌธ์—, ๋ฉค๋ฒ„์˜ ์ด๋ฆ„์„ ์ฐ๋Š” ์ˆœ๊ฐ„์—๋„ ์ถ”๊ฐ€ ์ฟผ๋ฆฌ๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

2. EntityGraph ์‚ฌ์šฉ

Fetch Join์ด ์ข‹์ง€๋งŒ ๋ฌธ์ž์—ด๋กœ ํ•˜๋“œ ์ฝ”๋”ฉ์„ ํ•ด์•ผํ•œ๋‹ค๋Š” ๋‹จ์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Ÿด ๋•Œ๋Š” EntityGraph๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

public interface TeamRepository extends JpaRepository<Team, Long> {
	@EntityGraph(attributePaths = {"members"})
	@Query(value = "select t from Team t")
	List<Team> findTeamsEntityGraph();
}
@DisplayName("๋ชจ๋“  ํŒ€์„ ์กฐํšŒํ•˜๊ณ , ์ง€์—ฐ๋กœ๋”ฉ ๋œ ๋ฉค๋ฒ„๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ -> ๋‹จ 1๊ฐœ์˜ ์ฟผ๋ฆฌ๋งŒ ๋‚˜๊ฐ„๋‹ค")
@Test
void team_findAll_test() {
    clearPersistenceContext();

    System.out.println("----------team_findAll_test start-----------");
    List<Team> teamList = teamRepository.findTeamsEntityGraph();
    assertThat(teamList).hasSize(3);
    System.out.println("----------team_findAll_test mid-----------");
    teamList.stream()
            .map(Team::getMembers)
            .map(List::stream)
            .forEach(memberStream -> memberStream
                    .map(Member::getName)
                    .forEach(System.out::println)
            );
    System.out.println("----------team_findAll_test end-----------");
}

์—”ํ‹ฐํ‹ฐ ๊ทธ๋ž˜ํ”„์˜ ์žฅ์ ์€ ๊ธฐ์กด ์ฟผ๋ฆฌ๋ฅผ ํ•ด์น˜์ง€ ์•Š๊ณ , ๋”ฐ๋กœ @EntityGraph๋ฅผ ํ†ตํ•ด ํŠน์ • ํ•„๋“œ๋ฅผ EAGER ๋กœ๋”ฉํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๋˜ํ•œ attributePaths๋Š” ์—ฌ๋Ÿฌ ๊ฐœ๋ฅผ ์„ ์–ธํ•ด์„œ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

----------team_findAll_test start-----------
Hibernate: 
    select
        t1_0.id,
        m1_0.team_id,
        m1_0.id,
        m1_0.name,
        t1_0.name 
    from
        team t1_0 
    left join
        member m1_0 
            on t1_0.id=m1_0.team_id
----------team_findAll_test mid-----------
member1-1
member1-2
member2-1
member2-2
member3-1
member3-2
----------team_findAll_test end-----------

Fetch Join๊ณผ ๋™์ผํ•˜๊ฒŒ EAGER๋กœ ๊ฐ€์ ธ์™€ member๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ ์ถ”๊ฐ€์ฟผ๋ฆฌ๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.

ํ•˜์ง€๋งŒ EntityGraph๋Š” Fetch Join๊ณผ ๋‹ค๋ฅธ์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

----------team_findAll_test start-----------
Hibernate: 
    select
        t1_0.id,
        m1_0.team_id,
        m1_0.id,
        m1_0.name,
        t1_0.name 
    from
        team t1_0 
    join
        member m1_0 
            on t1_0.id=m1_0.team_id
----------team_findAll_test mid-----------
member1-1
member1-2
member2-1
member2-2
member3-1
member3-2
----------team_findAll_test end-----------
----------team_findAll_test start-----------
Hibernate: 
    select
        t1_0.id,
        m1_0.team_id,
        m1_0.id,
        m1_0.name,
        t1_0.name 
    from
        team t1_0 
    left join
        member m1_0 
            on t1_0.id=m1_0.team_id
----------team_findAll_test mid-----------
member1-1
member1-2
member2-1
member2-2
member3-1
member3-2
----------team_findAll_test end-----------

EntityGraph๋Š” Joinํ•  ๋•Œ Outer Left Join์„ ํ•œ๋‹ค๋Š” ์ ์ž…๋‹ˆ๋‹ค. ์ด๋Š” ์นดํ…Œ์‹œ์•ˆ ๊ณฑ(**Cartesian Product)**์ด ๋ฐœ์ƒํ•œ๋‹ค๋Š” ๋‹จ์ ์ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๋˜๋„๋ก์ด๋ฉด Fetch Join์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์„ ์ถ”์ฒœ๋“œ๋ฆฝ๋‹ˆ๋‹ค.

๋งˆ์น˜๋ฉฐ

N+1 ํ•ด๊ฒฐ์„ ์œ„ํ•ด Fetch Join๊ณผ EntityGraph๋ฅผ ์ด์šฉํ•ด ํ•ด๊ฒฐํ–ˆ๋Š”๋ฐ์š” ๊ณผ์—ฐ ๋‘˜ ์ค‘ ๋ญ˜ ์จ์•ผ ํ• ๊นŒ์š”? ์ €๋Š” ๊ฐœ์ธ์ ์œผ๋กœ Fetch Join์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์„ ์ถ”์ฒœ๋“œ๋ฆฝ๋‹ˆ๋‹ค. ๊ทธ ์ด์œ ๋Š” EntityGraph๋Š” Outer Join์„ ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์„ฑ๋Šฅ์ƒ ๋” ์•ˆ์ข‹๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ๊ทธ๋ ‡๋‹ค๋ฉด Fetch Join์€ ๋งŒ๋Šฅ์ผ๊นŒ์š”? ๋‹ค์Œ ํฌ์ŠคํŒ…์—๋Š” Fetch Join์ด ๊ณผ์—ฐ ๋งŒ๋Šฅ์ธ๊ฐ€?์— ๋Œ€ํ•œ ์ฃผ์ œ๋กœ ์ฐพ์•„๋ต™๊ฒ ์Šต๋‹ˆ๋‹ค. ๋ถ€์กฑํ•œ ๊ธ€ ์ฝ์–ด์ฃผ์…”์„œ ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค!

REFERENCES

JPA N+1 ๋ฐœ์ƒ์›์ธ๊ณผ ํ•ด๊ฒฐ๋ฐฉ๋ฒ• - Yun Blog | ๊ธฐ์ˆ  ๋ธ”๋กœ๊ทธ

[JPA] N+1 ๋ฌธ์ œ ์›์ธ ๋ฐ ํ•ด๊ฒฐ๋ฐฉ๋ฒ•

JPA ๋ชจ๋“  N+1 ๋ฐœ์ƒ ์ผ€์ด์Šค๊ณผ ํ•ด๊ฒฐ์ฑ…

JPA N+1 ๋ฌธ์ œ ๋ฐ ํ•ด๊ฒฐ๋ฐฉ์•ˆ

JPA | JPA N+1 ๋ฌธ์ œ ๋ฐ ํ•ด๊ฒฐ๋ฐฉ์•ˆ - ๊ฐœ๋ฐœ ๋ธ”๋กœ๊ทธ

JPA ์„ฑ๋Šฅ N+1 ๋ฌธ์ œ์™€ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•

fetch ์กฐ์ธ, ์—”ํ‹ฐํ‹ฐ ๊ทธ๋ž˜ํ”„ ์งˆ๋ฌธ์ž…๋‹ˆ๋‹ค. - ์ธํ”„๋Ÿฐ | ์งˆ๋ฌธ & ๋‹ต๋ณ€

fetch join ๊ด€๋ จ ์งˆ๋ฌธ ๋“œ๋ฆฝ๋‹ˆ๋‹ค!! - ์ธํ”„๋Ÿฐ | ์งˆ๋ฌธ & ๋‹ต๋ณ€

๋ฐ˜์‘ํ˜•