728x90
즉시 로딩 / 지연 로딩 / 일반 Join / Fetch Join
1. select / EAGER (즉시 로딩)
📌 Member
package prac.littleprac.domain;
@Entity
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Member {
@Id @GeneratedValue
private Long id;
private String name;
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "team_id")
private Team team;
// == 연관관계 메서드 ==//
public void changeTeam(Team team) {
team.getMembers().add(this);
this.team = team;
}
}
📌 Team
package prac.littleprac.domain;
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
public class Team {
@Id @GeneratedValue
private Long id;
private String name;
@Builder
private Team(String name, List<Member> members) {
this.name = name;
this.members = members;
}
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>();
}
📌 Test 코드
@Test
void 즉시로딩() {
Team teamA = Team.builder()
.name("teamA")
.build();
em.persist(teamA);
Team teamB = Team.builder()
.name("teamB")
.build();
em.persist(teamB);
for(int i=0; i<3; i++) {
Member member = Member.builder()
.team(i%2==0? teamA : teamB)
.name("member" + i)
.build();
em.persist(member);
}
em.flush();
em.clear();
String basicQuery = "select m from Member m";
List<Member> result = em.createQuery(basicQuery, Member.class)
.getResultList();
}
select
member0_.id as id1_2_,
member0_.name as name2_2_,
member0_.team_id as team_id3_2_
from
member member0_
select
team0_.id as id1_4_0_,
team0_.name as name2_4_0_
from
team team0_
where
team0_.id=?
select
team0_.id as id1_4_0_,
team0_.name as name2_4_0_
from
team team0_
where
team0_.id=?
Member를 즉시로딩 설정시 Member 조회 쿼리 1개와 연관관계인 Team 조회 쿼리 2개가 발생했다. (N+1 문제 발생)
2. select / LAZY (지연 로딩)
📌 Member
package prac.littleprac.domain;
@Entity
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Member {
@Id @GeneratedValue
private Long id;
private String name;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "team_id")
private Team team;
// == 연관관계 메서드 ==//
public void changeTeam(Team team) {
team.getMembers().add(this);
this.team = team;
}
}
📌 Team
package prac.littleprac.domain;
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
public class Team {
@Id @GeneratedValue
private Long id;
private String name;
@Builder
private Team(String name, List<Member> members) {
this.name = name;
this.members = members;
}
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>();
}
📌 Test 코드
@Test
void 지연로딩() {
Team teamA = Team.builder()
.name("teamA")
.build();
em.persist(teamA);
Team teamB = Team.builder()
.name("teamB")
.build();
em.persist(teamB);
for(int i=0; i<3; i++) {
Member member = Member.builder()
.team(i%2==0? teamA : teamB)
.name("member" + i)
.build();
em.persist(member);
}
em.flush();
em.clear();
String basicQuery = "select m from Member m";
List<Member> result = em.createQuery(basicQuery, Member.class)
.getResultList();
}
select
member0_.id as id1_2_,
member0_.name as name2_2_,
member0_.team_id as team_id3_2_
from
member member0_
지연 로딩 설정시 Member 조회시 Team 프록시 객체로 조회하기 때문에 Team 조회 쿼리가 따로 생성되지 않는다.
하지만 Member엔티티에 Team 엔티티를 조회할 떄 Team 조회 쿼리가 생성된다. 이 때 N+1 문제가 발생한다.
📌 Test 코드
@Test
void 지연로딩() {
Team teamA = Team.builder()
.name("teamA")
.build();
em.persist(teamA);
Team teamB = Team.builder()
.name("teamB")
.build();
em.persist(teamB);
for(int i=0; i<3; i++) {
Member member = Member.builder()
.team(i%2==0? teamA : teamB)
.name("member" + i)
.build();
em.persist(member);
}
em.flush();
em.clear();
String basicQuery = "select m from Member m";
List<Member> result = em.createQuery(basicQuery, Member.class)
.getResultList();
for (Member member : result) {
System.out.println("member.getTeam().getName() = " + member.getTeam().getName());
}
}
select
member0_.id as id1_2_,
member0_.name as name2_2_,
member0_.team_id as team_id3_2_
from
member member0_
select
team0_.id as id1_4_0_,
team0_.name as name2_4_0_
from
team team0_
where
team0_.id=?
select
team0_.id as id1_4_0_,
team0_.name as name2_4_0_
from
team team0_
where
team0_.id=?
Member 엔티티에서 Team 엔티티를 조회할 때 Team 조회 쿼리가 생성된다. (N+1 문제 발생)
3. 일반 Join (지연 로딩)
📌 Member
package prac.littleprac.domain;
@Entity
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Member {
@Id @GeneratedValue
private Long id;
private String name;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "team_id")
private Team team;
// == 연관관계 메서드 ==//
public void changeTeam(Team team) {
team.getMembers().add(this);
this.team = team;
}
}
📌 Team
package prac.littleprac.domain;
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
public class Team {
@Id @GeneratedValue
private Long id;
private String name;
@Builder
private Team(String name, List<Member> members) {
this.name = name;
this.members = members;
}
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>();
}
📌 테스트 코드
@Test
void 일반조인() {
Team teamA = Team.builder()
.name("teamA")
.build();
em.persist(teamA);
Team teamB = Team.builder()
.name("teamB")
.build();
em.persist(teamB);
for(int i=0; i<3; i++) {
Member member = Member.builder()
.team(i%2==0? teamA : teamB)
.name("member" + i)
.build();
em.persist(member);
}
em.flush();
em.clear();
String basicQuery = "select m from Member m join m.team t";
List<Member> result = em.createQuery(basicQuery, Member.class)
.getResultList();
}
select
member0_.id as id1_2_,
member0_.name as name2_2_,
member0_.team_id as team_id3_2_
from
member member0_
inner join
team team1_
on member0_.team_id=team1_.id
Member 엔티티만 호출시점에서는 Member 조회 쿼리만 생성된다.
📌 테스트 코드
@Test
void 일반조인() {
Team teamA = Team.builder()
.name("teamA")
.build();
em.persist(teamA);
Team teamB = Team.builder()
.name("teamB")
.build();
em.persist(teamB);
for(int i=0; i<3; i++) {
Member member = Member.builder()
.team(i%2==0? teamA : teamB)
.name("member" + i)
.build();
em.persist(member);
}
em.flush();
em.clear();
String basicQuery = "select m from Member m join m.team t";
List<Member> result = em.createQuery(basicQuery, Member.class)
.getResultList();
for (Member member : result) {
System.out.println("member.getTeam().getName() = " + member.getTeam().getName());
}
}
select
member0_.id as id1_2_,
member0_.name as name2_2_,
member0_.team_id as team_id3_2_
from
member member0_
inner join
team team1_
on member0_.team_id=team1_.id
select
team0_.id as id1_4_0_,
team0_.name as name2_4_0_
from
team team0_
where
team0_.id=?
member.getTeam().getName() = teamA
select
team0_.id as id1_4_0_,
team0_.name as name2_4_0_
from
team team0_
where
team0_.id=?
member.getTeam().getName() = teamB
member.getTeam().getName() = teamA
Member 엔티티에서 Team 엔티티를 사용 시점에서 Team 조회 쿼리가 생성되어 N+1개 문제가 발생했다.
4. Fetch Join (즉시 로딩)
📌 Member
package prac.littleprac.domain;
@Entity
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Member {
@Id @GeneratedValue
private Long id;
private String name;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "team_id")
private Team team;
// == 연관관계 메서드 ==//
public void changeTeam(Team team) {
team.getMembers().add(this);
this.team = team;
}
}
📌 Team
package prac.littleprac.domain;
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
public class Team {
@Id @GeneratedValue
private Long id;
private String name;
@Builder
private Team(String name, List<Member> members) {
this.name = name;
this.members = members;
}
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>();
}
📌 테스트 코드
@Test
void 페치조인() {
Team teamA = Team.builder()
.name("teamA")
.build();
em.persist(teamA);
Team teamB = Team.builder()
.name("teamB")
.build();
em.persist(teamB);
for(int i=0; i<3; i++) {
Member member = Member.builder()
.team(i%2==0? teamA : teamB)
.name("member" + i)
.build();
em.persist(member);
}
em.flush();
em.clear();
String basicQuery = "select m from Member m join fetch m.team t";
List<Member> result = em.createQuery(basicQuery, Member.class)
.getResultList();
for (Member member : result) {
System.out.println("member.getTeam().getName() = " + member.getTeam().getName());
}
}
select
member0_.id as id1_2_0_,
team1_.id as id1_4_1_,
member0_.name as name2_2_0_,
member0_.team_id as team_id3_2_0_,
team1_.name as name2_4_1_
from
member member0_
inner join
team team1_
on member0_.team_id=team1_.id
member.getTeam().getName() = teamA
member.getTeam().getName() = teamB
member.getTeam().getName() = teamA
페치 조인은 연관관계를 즉시 로딩처럼 쿼리 한 번에 다 끌어오기 때문에 N+1 문제를 해결할 수 있다.
✔ 정리
👀 참고 자료
https://www.inflearn.com/questions/30446
728x90
'[JPA] > JPA' 카테고리의 다른 글
[JPA] 프록시의 필드값을 조회 때는 get 방식으로 조회하자! (0) | 2022.06.07 |
---|---|
[JPA] @DataJpaTest + 테스트 DB 변경 (0) | 2022.05.15 |
[JPA] 엔티티의 필드 컬렉션을 생성과 동시에 초기화 하는 이유 (0) | 2022.04.06 |