본문 바로가기
카테고리 없음

프록시 ) 즉시 로딩과 지연 로딩

by _비니_ 2024. 7. 26.
지연로딩

Q ) 단순히 member 정보만 사용하는 비즈니스 로직이라고 하면,

연관관계가 걸려있다고 해도, Member를 조회할 때 Team도 함께 join해 조회해야 할까?

 

⇒ JPA는 지연로딩이라는 옵션을 제공 fetch = FetchType.LAZY

@Entity
public class Member extends BaseEntity{
    @Id @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;
    @Column(name = "USERNAME")
    private String name;

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

 

이렇게 fetch = FetchType.LAZY 를 걸어주면, 프록시로 조회를 함.

Member 클래스만 DB에서 조회를 한다는 의미.

 

Member member1 = new Member();
            member1.setName("member1");
            em.persist(member1);

            em.flush();
            em.clear();

            Member m = em.find(Member.class, member1.getId());

            tx.commit();

Member 테이블만 가져오는 것을 볼 수 있음.

 

Team team = new Team();
team.setName("teamA");
em.persist(team);

Member member1 = new Member();
member1.setName("member1");
member1.setTeam(team);
em.persist(member1);

em.flush();
em.clear();

Member m = em.find(Member.class, member1.getId());
System.out.println("m = " + m.getTeam().getClass());

⇒ 팀은 프록시 로 나오는 것을 확인할 수 있음!!

 

Team team = new Team();
team.setName("teamA");
em.persist(team);

Member member1 = new Member();
member1.setName("member1");
member1.setTeam(team);
em.persist(member1);

em.flush();
em.clear();

Member m = em.find(Member.class, member1.getId());
System.out.println("m = " + m.getTeam().getClass());

System.out.println("==============================");
//Team을 가져오려는 순간, 프록시 초기화 & DB에서 값을 가져옴
m.getTeam().getName();
System.out.println("==============================");

 

========= 사이에 TEAM관련 쿼리를 확인할 수 있음.!!!

 

getName을 호출하는 순간 초기화

 

지연로딩으로 세팅하면, 연관된 것을 Proxy로 가져옴!!!⭐

 

즉시로딩

Q ) Member와 Team을 자주 함께 사용하는 경우에는??

 

fetch = FetchType.EAGER

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

 

쿼리가 MEMBER와 TEAM이 한 번에 같이 조인해서 나옴

즉, 프록시가 필요 없음. (둘 다 실제 객체 조회)

 

But ⇒ 실무에서는 즉시로딩 사용하지 말자! (가급적 지연로딩만 사용)

  • 즉시 로딩을 적용하면 예상하지 못한 SQL이 발생
  • 즉시 로딩은 JPQL에서 N+1 문제를 일으킨다.
  • @ManyToOne, @OneToOne은 기본이 즉시 로딩 -> LAZY로 설정
  • @OneToMany, @ManyToMany는 기본이 지연 로딩

 

//Member m = em.find(Member.class, member1.getId());

//JPQL
List<Member> members = em.createQuery("select m from Member m", Member.class).getResultList();

//SQL : select * from Member
//SQL : select * from TEAM where TEAM_ID = xxx

 

  • em.find
    • em.find() 메서드를 사용하면 내부적으로 한 번의 쿼리로 데이터를 가져옴. 예를 들어, 특정 Member를 조회할 때 연관된 Team도 함께 조회됨
  • JPQL
    • JPQL 쿼리를 사용하면, 해당 JPQL이 그대로 SQL로 변환되어 실행

 

🚨 JPQL에서의 N+1 문제 🚨

  • JPQL로 select m from Member m 쿼리를 실행하면, 먼저 Member 엔티티에 대한 쿼리가 실행. (select * from Member)
  • 이때, 각 Member에 연관된 Team을 EAGER로 설정했으면
    • 각 Member마다 추가로 Team을 조회하는 쿼리가 발생⭐⭐⭐(select * from TEAM where TEAM_ID = xxx)
  • Member 10명을 조회할 경우, 첫 번째 쿼리(select * from Member)가 한 번 실행되고,
  • 이후 각 Member마다 연관된 Team을 조회하기 위해 10번의 추가 쿼리(select * from TEAM where TEAM_ID = xxx)가 실행됨.
  • 총 11번의 쿼리가 실행되는 상황이 발생=⇒ 이게 N+1 문제!!!!!

IF) MEMBER와 TEAM을 같이 조회해야 한다면 JPQL에서 fetch join을 사용하면 됨

 

List<Member> members = em.createQuery(
    "SELECT m FROM Member m JOIN FETCH m.team", Member.class
).getResultList();
반응형