[JPA] 프록시와 즉시로딩, 지연로딩
JPA에서는 지연로딩 등에 쓰이는 프록시라는 개념이 있다.
프록시를 직접적으로 사용하는 방법은 em.getReference()를 이용하면된다.(EntityManager em)
em.find()의 경우 데이터베이스를 통해서 실제 엔티티 객체를 조회하지만,
em.getReference()를 사용하면 데이터가 실제로 필요할때까지 데이터베이스 조회를 미루는 프록시 객체를 가져온다.
프록시는 실제 엔티티 클래스를 상속 받아서 만들어졌고 따라서 실제 클래스와 겉 모양이 같다.
이론상 사용자는 진짜 객체인지 프록시 객체인지 구분하지 않고 사용하면 된다.
프록시와 초기화 과정을 그림과 같이 설명하면 다음과 같다.
프록시 객체가 처음 조회될때는 Entity target이 비어있는 상태였다가 실제로 데이터가 필요한 시점에 영속성 컨텍스트에 초기화 요청을 한다.
영속성 컨텍스트에서는 DB에 조회를 해서 데이터를 가져와 실제 엔티티를 생성한다.
최종적으로 프록시 객체는 영속성 컨텍스트를 통해 생성한 엔티티 객체를 target을 통해 참조한다.
프록시는 다음과 같은 특징들이 있다.
- 프록시 객체는 처음 데이터가 실질적으로 쓰일때 처음 한번만 초기화 된다.
- 프록시 객체의 초기화는 프록시 객체가 실제 엔티티로 바뀌는 것이 아니라 프록시 객체를 통해서 실제 엔티티에 접근 가능하게 되는것이다.
- 프록시 객체는 원본 엔티티를 상속받는다. 따라서 프록시와 엔티티의 타입을 비교할때는 == 대신 instance of를 사용해야한다.
- 영속성 컨텍스트에 찾는 엔티티가 이미 있으면 em.getReference()를 호출해도 실제 엔티티가 반환된다. (JPA는 같은 영속성 컨텍스트 안에서 동일한 pk를 갖는 객체를 참조하는 경우 자바의 컬렉션처럼 ==이 true인것을 보장하는 기본 메커니즘을 제공한다.)
- 영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태일 때, 프록시를 초기화하면 문제가 발생한다.(LazyInitializationException 터짐)
JPA에서는 즉시로딩과 프록시를 이용한 지연로딩이 있다.
즉시 로딩으로 객체를 조회할 때는 연관관계 매핑이 되어있는 객체의 정보까지 모두 가져오기 위해 연관된 객체 테이블까지 join한 쿼리를 db에 요청한다.
즉시로딩의 경우 연관관계를 맺은 객체를 사용하지 않을거라면 join을 한것이 성능측면에서 낭비가 될 수 있다.
따라서 어떤 객체를 조회할때 해당 객체와 연관된 또 다른 객체의 사용이 많지 않다면 지연로딩을 사용한다.
지연로딩이란 객체를 조회할때 해당 객체와 연관된 또 다른 객체의 정보를 즉시 가져오지 않고(join쿼리를 사용하지 않고) 해당 객체를 프록시로 대체한다.
즉, 조회한 객체(Member)와 연관되어 있는 객체(Member.team)의 실제 사용(Member.team.getName())이 일어날때 쿼리가 또 한번 나가는 형식으로 동작한다.
지연로딩을 적용한 코드는 다음과 같다.
@Entity
public class Member {
@Id
@GeneratedValue
private Long id;
@Column(name = "USERNAME")
private String name;
@ManyToOne(fetch = FetchType.LAZY) //**
@JoinColumn(name = "TEAM_ID")
private Team team;
..
}
그렇다면 연관된 객체의 사용이 자주 일어날때는 즉시로딩, 연관된 객체의 사용이 자주 일어나지 않을때는 지연로딩을 적용하면 될것 같다.
하지만 즉시로딩에는 다음과 같은 문제점들이 있어 실무에서는 거의 항상 지연로딩을 사용하는것이 바람직하다.
즉시로딩을 적용하면 자동으로 join이 일어나기 때문에 예상하지 못한 쿼리가 발생할 수 있다.
또한 JPQL로 해당 테이블에 있는 데이터만 가져올때 즉시로딩의 경우 연관된 객체들의 데이터를 가져오는 쿼리를 다시 데이터베이스에 요청한다.
즉, 즉시로딩은 JPQL에서 N+1문제를 일으킨다.
@ManyToOne, @OneToOne은 기본이 즉시로딩으로 설정 되어있기 때문에 fetch옵션값을 FetchType.Lazy로 설정해줘야 한다.
@OneToMany, @ManyToMany의 경우 기본값이 지연로딩으로 설정되어 있어 따로 설정할 필요는 없다.
참고: 자바 ORM 표준 JPA 프로그래밍 (https://www.inflearn.com/course/ORM-JPA-Basic#)