<인프런의 김영한님의 강의를 보고 정리한 내용입니다>
Member를 조회할 때 Team도 같이 조회를 해야할까?
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;
@Entity
public class Member extends BaseEntity {
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")
private String username;
@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;
@ManyToMany
@JoinColumn(name = "MEMBER_PRODUCT")
private List<Product> products = new ArrayList<>();
public Team getTeam() {
return team;
}
// .. 이하 생략
}
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;
@Entity
public class Team extends BaseEntity {
@Id @GeneratedValue
@Column(name = "TEAM_ID")
private Long id;
private String name;
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>();
public List<Member> getMembers() {
return members;
}
// ... 이하 생략
}
이렇게 Member 엔티티와 Team 엔티티가 있을 때, Member에서 getUsername()을 호출하게 된다면 Team에 대한 쿼리도 나가게 될 것이다.
이를 해결하기 위한 것이 지연로딩이다.
@ManyToOne에 lazy를 추가하면 되는데
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;
@Entity
public class Member extends BaseEntity {
@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;
@ManyToMany
@JoinColumn(name = "MEMBER_PRODUCT")
private List<Product> products = new ArrayList<>();
public Team getTeam() {
return team;
}
// 이하 생략
}
보면
@ManyToOne(fetch = FetchType.LAZY)를 추가했다.
이렇게 해서 해결되는 이유는 https://yanoo.tistory.com/126 에서 언급했듯 프록시로 조회하는 법이 있는데, 페치타입을 Lazy로 하면 프록시로 조회하기 때문이다. 정확한 결과를 확인하기 위해서
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
Team team = new Team();
team.setName("teamA");
em.persist(team);
Member member = new Member();
member.setUsername("user1");
member.setTeam(team);
em.persist(member);
em.flush();
em.clear();
Member m = em.find(Member.class, member.getId());
System.out.println("m.getTeam().getClass() = " + m.getTeam().getClass());
tx.commit();
} catch (Exception e) {
tx.rollback();
e.printStackTrace();
} finally {
em.close();
}
emf.close();
}
}
이렇게 테스트를 했을 때 Team은 프록시로 조회되어야 하는 것이 맞다. 확인을 해보면,
프록시로 조회된 것을 알 수 있다.
이제 반대로 즉시로딩인데, 이것은 어차피 Team과 Member 모두를 자주 사용한다면 모두 같이 조회하는 것이다.
xxxxToOne은 기본값이 EAGER이기에 그대로 사용하면 되는데 똑같은 코드로 결과를 확인해 보면,
조인으로 한 번에 다 조회하고 Team 객체가 프록시가 아니라 본 객체임을 알 수 있다.
결론적으로 말하면, 지연로딩을 써야하는데 이유는,
일단 예상하지 못한 SQL이 실행될 수 있다.
그리고 JPQL에서 N+1문제를 일으키기 때문이다.
N+1 문제는 JPA에서 자주 등장하는 문제인데, 예를 봐보면
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import java.util.List;
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
Team team = new Team();
team.setName("teamA");
em.persist(team);
Team team1 = new Team();
team1.setName("teamB");
em.persist(team1);
Member member = new Member();
member.setUsername("user1");
member.setTeam(team);
em.persist(member);
Member member1 = new Member();
member1.setUsername("user2");
member1.setTeam(team1);
em.persist(member1);
em.flush();
em.clear();
List<Member> members = em.createQuery("select m from Member m", Member.class).getResultList();
tx.commit();
} catch (Exception e) {
tx.rollback();
e.printStackTrace();
} finally {
em.close();
}
emf.close();
}
}
보면 Team이 2개 Member가 2개이다. 그리고 각각 다른 팀에 속한다.
여기서 멤버들을 조회하는 쿼리문을 생성했을 때, SQL문은 어떻게 될까?
보면 member만 조회하는 select문만 나가야하는데, 보면 member 수가 2명 이기에 2명의 각 팀에 대한 쿼리 2개가 더 나갔다. 그래서 이를 N+1 문제라고 한다.
@ManyToOne(fetch = FetchType.LAZY)
이렇게 추가하게 되면
멤버를 조회하는 select문만 나가는 것을 확인할 수 있다.
사실 N+1문제가 생기는 이유가 더 있는데 그것을 해결하는 법은 패치조인, 어노테이션 사용법, 배치사이즈 사용법 등이 있다.
xxxxToOne은 기본이 EAGER라 LAZY로 바꾸는걸 추천한다.
[ JPA ] 임베디드 타입 (0) | 2022.01.26 |
---|---|
[ JPA ] CASCADE는 어디에 사용할까 (persist 한 번만 쓰고 싶을 때) (0) | 2022.01.22 |
[ JPA ] 프록시와 getReference() (0) | 2022.01.17 |
[ JPA ] @MappedSuperclass (0) | 2022.01.16 |
[ JPA ] 상속관계 매핑 (0) | 2022.01.15 |