[인프런 김영한] JPA - 즉시로딩 / 지연로딩

2021. 2. 11. 16:23프로그래밍 언어/Spring Framework

[인프런 김영한] JPA - 즉시로딩 / 지연로딩


해당 글은 인프런 김영한강사님의 영상을 보고 정리한 글입니다.

Spring Boot, Spring Data JPA를 사용해 실습하였습니다.

김영한 인프런 : www.inflearn.com/users/@yh

 

인프런 - 김영한의 강의들을 만나보세요.

우아한형제들 개발 팀장 (전: 카카오, SK플래닛) 저서: 자바 ORM 표준 JPA 프로그래밍

www.inflearn.com


▣ Member를 조회할 때 Team을 함께 조회해야 할까?

 

-> 지연로딩(Lazy)을 지원한다.

 

▣ Member와 Team중에서 Member만 자주 조회한다면 - Lazy / 지연로딩

Member

@Entity
public class Member{

    @Id
    @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;

    private String name;

    @ManyToOne
    @JoinColumn(name = "TEAM_ID")
    private Team team;
}

 

Team

@Entity
public class Team{

    @Id
    @GeneratedValue
    @Column(name = "TEAM_ID")
    private Long id;
    private String name;

    @OneToMany
    @JoinColumn(name = "TEAM_ID")
    List<Member> members = new ArrayList<>();
}

 

실행

Member member = new Member();
member.setName("user1");
memberRepository.save(member);

// Member를 조회하면 select에 Team까지 Join문 쿼리가 출력된다.
// Member를 조회하면 select에 Team까지 Join문 쿼리가 출력된다.
  //    @ManyToOne
  //    @JoinColumn(name = "TEAM_ID")
  //    private Team team;
Member findMember = memberRepository.findById(member.getId()).get();
System.out.println(findMember.getId());
System.out.println(findMember.getName());

// 실행결과
Hibernate: 
    select
        member0_.member_id as member_i1_0_0_,
        member0_.name as name2_0_0_,
        member0_.team_id as team_id3_0_0_,
        team1_.team_id as team_id1_1_1_,
        team1_.name as name2_1_1_ 
    from
        member member0_ 
    left outer join
        team team1_ 
            on member0_.team_id=team1_.team_id 
    where
        member0_.member_id=?

| Member만 조회하고 싶은데 Team까지 join해서 가지고 온다.

 

 

 

 

◈ 수정

Member

@Entity
public class Member{

    @Id
    @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;

    private String name;

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

| Team의 ManyToOne은 기본적으로 FetchType.EAGER를 사용하는데 이걸 FetchType.LAZY로 변경하게 되면

Member 클래스만 DB에서 조회한다.

 

 

Member member = new Member();
member.setName("user1");
memberRepository.save(member);

// FetchType.LAZY를 설정하면 Member만 조회한다.
//    @ManyToOne(fetch = FetchType.LAZY)
//    @JoinColumn(name = "TEAM_ID")
//    private Team team;
Member findMember = memberRepository.findById(member.getId()).get();
System.out.println(findMember.getId());
System.out.println(findMember.getName());


// 실행 결과
Hibernate: 
    select
        member0_.member_id as member_i1_0_0_,
        member0_.name as name2_0_0_,
        member0_.team_id as team_id3_0_0_ 
    from
        member member0_ 
    where
        member0_.member_id=?

| Select 쿼리를 보면 member만 가지고 오는것을 볼 수 있다.

 

 

 

 

 

◈ Lazy로 설정 했을 때, Team은 어떻게 가져올까?

Team team = new Team();
team.setName("team1");
teamRepository.save(team);

Member member = new Member();
member.setName("user1");
member.setTeam(team);
memberRepository.save(member);

//   @ManyToOne(fetch = FetchType.LAZY)
Member findMember = memberRepository.findById(member.getId()).get();
System.out.println(findMember.getId());
System.out.println(findMember.getName());

// 그러면 필요할 때만 가지고 오고 싶어할 때 프록시를 생각하면 된다.
System.out.println("findMember.class = " + findMember.getTeam().getClass());
System.out.println("=========================================");
System.out.println("getName : " + findMember.getTeam().getName());


// 실행결과 , 프록시로 가지고 오는것을 볼 수 있다.
findMember.class = class com.spring.jpa.Team$HibernateProxy$YcSewtkv

| 처음 member를 find()했을 때는 Team을 프록시로 조회하며 실제 member.getTeam()을 하는 시점에  DB조회

 


 

▣ Member와 Team을 자주 함께 사용한다면? - EAGER / 즉시로딩

 

Member

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


// 실행 결과
Hibernate: 
    select
        member0_.member_id as member_i1_0_0_,
        member0_.name as name2_0_0_,
        member0_.team_id as team_id3_0_0_,
        team1_.team_id as team_id1_1_1_,
        team1_.name as name2_1_1_ 
    from
        member member0_ 
    left outer join
        team team1_ 
            on member0_.team_id=team1_.team_id 
    where
        member0_.member_id=?

// 프록시 조회가 아니다.!!
findMember.class = class com.spring.jpa.Team

| 즉시로딩은 한번에 다 조회를 해서 가지고오기 때문에 프록시를 사용하지 않는다.

 

 

 

 

 


▣ 지연로딩, 즉시로딩에 대한 김영한님의 Point!

현제 예제는 2개의 테이블이여서 지금은 상관 없지만. 실무에서는 한번 조회할 때 다수의 테이블(대략 5개 이상)을 조인해서 가지고 오면 성능문제를 일으킨다

 

* 가급적 지연 로딩만 사용(특히나 실무에서는)

 - 즉시 로딩을 적용하면 예상하지 못한 SQL이 발생

 - 즉시로딩은 JPQL에서 N+1문제를 일으킨다.

em.creatQuery("select m from Member m", Member.class).getResultList()

// 실행결과 2개의 SQL문이 출력된다.

select문에서 Member를 조회하지만 Member안에 Team이 FK로 있기 때문에 Team도 조회하면서 총 2번의 쿼리 출력

 

* ManyToOne, @OneToOne은 기본이 즉시로딩. -> LAZY로 설정하기.

 -  @OneToMany, @ManyToMany는 기본이 지연로딩.