[JPA]프록시를 사용하는 이유(즉시로딩, 지연로딩)
프로그래밍 언어/JPA

[JPA]프록시를 사용하는 이유(즉시로딩, 지연로딩)

반응형

위와 같이 Member에 Team이 들어있는 경우, Member를 조회할 때 Team도 조회해야 할까?

-> Member와 Team을 같이 사용하는 경우엔 괜찮지만, Member만 필요한 경우엔 Team까지 호출이 되면 리소스 낭비이다.

(즉시로딩, 지연로딩의 차이점)

 

프록시

Hibernate가 내부에서 만든 가짜 엔티티 객체

 

em.find() : 데이터베이스를 통해서 실제 엔티티 객체 조회

em.getRefetence() : 데이터베이스 조회를 미루는 가짜(프록시) 엔티티 객체 조회

 

em.getReference()를 호출하는 시점에는 데이터베이스에 query를 호출하지 않는다.

 

프록시 객체 초기화
1. em.getReference()를 호출해서 Proxy객체를 생성

2. target에 값이 없으면 영속성 컨텍스트를 통해서 초기화 요청

3. DB를 조회해서 실제 Entity를 target에 연결한다.

 

프록시는 실제 엔티티를 상속받아서 만들어지므로, 실제 클래스와 겉모양이 같지만 타입 체크 시 == 비교는 실패하며, instanceof를 사용하여 타입 체크를 해야 한다.

사용하는 입장에서는 진짜 객체인지 프록시 객체인지 구분하지 않고 사용하며, 프록시 객체는 실제 객체의 참조(target)를 보관한다.

즉, 프록시 객체를 호출하면 프록시 객체는 실제 객체의 메소드를 호출한다.

 

jpa에서는 하나의 트랜잭션에서 == 비교는 true여야 하는 조건이 있기 때문에 영속성 컨텍스트에 찾는 엔티티가 이미 있으면 em.getReference()를 호출해도 실제 엔티티가 반환된다.

 

그렇다면 프록시를 사용하는 이유는 무엇일까?

-> 지연 로딩과 성능최적화 때문에 사용한다.

 

지연로딩,  즉시로딩

@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를 사용하면 Member조회 시 Team은 Proxy객체로 조회한다.

(Member만 DB에서 조회)

실제로 비즈니스 로직에서 Team을 사용하게 될 때 프록시 초기화가 발생하면서 실제 DB에서 Team을 조회하는 query가 실행된다.

 

지연로딩은 하위 엔티티를 Proxy객체를 사용하며 실제 하위 엔티티가 필요한 시점에 초기화가 일어난다.

즉시로딩은 필요한 시점에 조인을 사용해서 한 번에 함께 조회가 일어난다.

 

만약 내가 Member와 Team을 항상 같이 사용한다면 즉시로딩을 통해서 같이 조회를 하면 되지만, 예상하지 못한 sql이 발생하며, JPQL에서는 N+1 문제를 일으키기 때문에 가급적 지연 로딩만 사용하는 것을 권장한다.

@ManyToOne, @OneToOne은 기본이 즉시 로딩 → LAZY로 설정

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

 

N+1

즉시로딩으로 설정되어 있는 경우 모든 Member를 출력하는 경우

Member에 team의 객체에 값을 넣기 위해서 Member의 결괏값이 10개면 10개만큼 Team team을 가져오기 위해 쿼리가 실행된다.

  1. SQL : SELECT * FROM MEMBER;
  2. SQL : SELECT * FROM TEAM where TEAM_ID = xxx..

이 문제를 방지하기 위해서 3가지 방법이 있다.

  1. fetch join
  2. @EntitiyGraph
  3. BatchSize

 

참고 : 인프런 - 자바 ORM 표준 JPA 프로그래밍

 

 

 

 

반응형