다음과 같이 N : 1로 연관관계 매핑된 2개의 엔티티가 있다고 할 때,
@Entity
public class Team {
@Id @GeneratedValue
@Column(name = "TEAM_ID")
private Long id;
private String name;
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>();
}
@Entity
public class Member {
@Id
@GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")
private String username;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "TEAM_ID")
private Team team;
}
서로 연관관계 매핑되는 칼럼에 다음과 같이
fetch = FetchType.LAZY/EAGER를 통해서 지연로딩/즉시로딩으로 설정할 수 있다.
지연 로딩
// Team 엔티티를 생성한다
Team team = new Team();
team.setName("teamA");
em.persist(team);
Member member = new Member();
member.setUsername("member");
member.setTeam(team);
em.persist(member);
em.flush();
em.clear();
Member m = em.find(Member.class, member1.getId());
System.out.println("m.getTeam().getClass() = " + m.getTeam().getClass());
System.out.println("===========================");
m.getTeam().getName();
System.out.println("===========================");
tx.commit();
/* 결과
insert
into
Team
(name, TEAM_ID)
values
(?, ?)
insert
into
Member
(TEAM_ID, USERNAME, MEMBER_ID)
values
(?, ?, ?)
// Member를 조회할 경우 member 조회에 대한 Query만 발생한다.
Hibernate:
select
member0_.MEMBER_ID as MEMBER_I1_3_0_,
member0_.TEAM_ID as TEAM_ID3_3_0_,
member0_.USERNAME as USERNAME2_3_0_
from
Member member0_
where
member0_.MEMBER_ID=?
// 다음과 같이 Team이 실제로 사용되지 않는 시점에서는 프록시 객체로 생성 된 것을 확인할 수 있다.
m.getTeam().getClass() = class hellojpa.Team$HibernateProxy$lBemz2Kd
// 다음과 같이 실제 Team 객체를 사용하는 시점에 조회 Query가 발생하는 것을 볼 수 있다.
===========================
Hibernate:
select
team0_.TEAM_ID as TEAM_ID1_5_0_,
team0_.name as name2_5_0_
from
Team team0_
where
team0_.TEAM_ID=?
===========================
*/
즉시 로딩
@Entity
public class Member {
@Id
@GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")
private String username;
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "TEAM_ID")
private Team team;
}
다음과 같이 즉시 로딩 전략으로 변경 후 결과를 확인해보면,
// 조회 시 join을 통해서 필요한 엔티티도 초기에 조회하여 채워진다.
Hibernate:
select
member0_.MEMBER_ID as MEMBER_I1_3_0_,
member0_.TEAM_ID as TEAM_ID3_3_0_,
member0_.USERNAME as USERNAME2_3_0_,
team1_.TEAM_ID as TEAM_ID1_5_1_,
team1_.name as name2_5_1_
from
Member member0_
left outer join
Team team1_
on member0_.TEAM_ID=team1_.TEAM_ID
where
member0_.MEMBER_ID=?
// 프록시 객체가 아닌 실제 엔티티인 것을 확인할 수 있다.
m.getTeam().getClass() = class hellojpa.Team
===========================
===========================
프록시와 즉시로딩 주의
1.실무에서는 가능하면 즉시로딩만 사용하자.
-> 즉시 로딩을 적용 시 예상치 못한 SQL이 발생한다.
->즉시 로딩은 JPQL 사용 시 N+1 문제를 일으킨다.
Team team = new Team();
team.setName("teamA");
em.persist(team);
Team team2 = new Team();
team2.setName("teamB");
em.persist(team2);
Member member1 = new Member();
member1.setUsername("member1");
member1.setTeam(team);
em.persist(member1);
Member member2 = new Member();
member2.setUsername("member2");
member2.setTeam(team2);
em.persist(member2);
em.flush();
em.clear();
// JPQL 사용
List<Member> members = em.createQuery("select m from Member m", Member.class)
.getResultList();
Hibernate:
/* select
m
from
Member m */ select
member0_.MEMBER_ID as MEMBER_I1_3_,
member0_.TEAM_ID as TEAM_ID3_3_,
member0_.USERNAME as USERNAME2_3_
from
Member member0_
// 다음과 같이 각각의 멤버에 대해서 Team을 조회하기 위한 별도의 쿼리가 발생한다.
// N + 1 : 하나의 쿼리를 수행할 때 부가적으로 N개만큼 쿼리가 발생하는 현상
Hibernate:
select
team0_.TEAM_ID as TEAM_ID1_5_0_,
team0_.name as name2_5_0_
from
Team team0_
where
team0_.TEAM_ID=?
Hibernate:
select
team0_.TEAM_ID as TEAM_ID1_5_0_,
team0_.name as name2_5_0_
from
Team team0_
where
team0_.TEAM_ID=?
2.ManyToOne, OneToOne은 기본이 즉시 로딩이다.
-> LAZY로 설정해주자
지연 로딩 활용
참조 자료
자바 ORM 표준 JPA 프로그래밍 - 기본편 (인프런 김영한님 강의)