프록시란?
Proxy, 영어로 '대리인'이라는 뜻이 있다.
말 그대로 엔티티에 대한 대리인의 역할을 한다.
그 말대로, 프록시는 원본 객체가 아닌 원본에 대한 복제품을 가져오는 것이다.
다음과 같은 테이블이 있다고 하자.
만약 회원을 조회할 때, 팀도 함께 조회될까?
멤버 엔티티는 아래와 같은 값을 가지며, setter, getter를 포함한다고 하자.
@Entity
public class Member{
@Id @GeneratedValue
@Column(name = "member_id")
private Long id;
@Column(name = "username")
private String name;
@ManyToOne
private Team team;
}
팀 엔티티는 아래와 같은 값을 가지며, setter, getter를 포함한다고 하자.
@Entity
public class Team {
@Id @GeneratedValue
@Column(name = "team_id")
private Long id;
private String name;
}
결론부터 말하자면, em.find(Member.class, memberId)를 하면, 팀도 함께 조회된다.
만약 멤버를 찾을 때, 팀은 필요가 없는 순간에는 큰 낭비가 되고 있다는 것이다.
이를 해결하기 위해 프록시라는 개념이 사용된다.
em.find(): 데이터베이스를 통해서 실제 엔티티 객체 조회
em.getReference(): 데이터베이스 조회를 미루는 가짜(프록시) 엔티티 객체 조회
프록시 특징
- 실제 클래스를 상속받아서 만들어짐
- 실제 클래스와 겉모양이 같다.(속은 텅 비어있음)
- 사용하는 입장에서는 진짜 객체인지 프록시 객체인지 구분하지 않고 사용하면 됨(이론상)
- 클라이언트가 프록시에게 getName을 요청한다.
- 프록시 객체가 영속성 컨텍스트에게 엔티티 초기화를 요청한다.
- 영속성 컨텍스트가 DB에서 데이터를 조회한다.
- 데이터를 받아온 영속성 컨텍스트가 원본 엔티티를 만든다.
- 생성한 엔티티에서 getName메서드를 호출에 데이터를 반환받는다.
프록시 객체는 실제 객체의 참조(target)를 보관한다.
즉 실제 객체로 대체되는 것이 아닌, 프록시 객체를 호출하면 프록시 객체는 실제 객체의 메소드를 호출한다.
프록시의 특징
- 프록시 객체는 처음 사용할 때 한 번만 초기화
- 프록시 객체를 초기화 할 때, 프록시 객체가 실제 엔티티로 바뀌는 것이 아님
- 초기화되면 프록시 객체를 통해서 실제 엔티티에 접근 가능
- 프록시 객체는 원본 엔티티를 상속받음, 따라서 타입 체크 시 주의해야 함(==비교 실패, 대신 instance of 사용)
- 영속성 컨텍스트에 찾는 엔티티가 이미 있으면 em.getReference()를 호출해도 실제 엔티티 반환
- 영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태일 때, 프록시를 초기화하면 문제 발생
즉시 로딩 VS 지연 로딩
이제 프록시를 배웠으니, 맨 처음 의문이었던 멤버 조회에 대해 다시 보자.
맨 처음 코드는 즉시 로딩을 사용하게 된다.
따라서 멤버를 조회할 때, 팀을 같이 조회하게 되었다.
여기에 프록시를 적용하면, 아래와 같은 코드로 바뀌게 된다.
@Entity
public class Member{
@Id @GeneratedValue
@Column(name = "member_id")
private Long id;
@Column(name = "username")
private String name;
@ManyToOne(fetch = FetchType.LAZY) // 지연 로딩
private Team team;
}
단순히 @ManyToOne뒤에 fetch 조건을 LAZY를 걸어주면 된다.
팀을 단순히 한 번에 조회하는 것이 아닌, 팀을 사용하는 시점에 초기화된다.
그렇다면 멤버와 팀을 같이 사용하는 일이 많다면?
그렇다면 즉시로딩을 사용하는 것이 이론상으로는 좋다.
하지만 지연로딩을 사용하고, 즉시 로딩이 필요하다면, FetchJoin을 사용해야 한다.
이유는 아래와 같다.
프록시와 즉시로딩 주의
- 가급적 지연로딩만 사용
- 즉시 로딩을 적용하면 예상하지 못한 SQL이 발생
- 즉시 로딩은 JPQL에서 N+1 문제를 일으킨다
- @ManyToOne, @OneToOne은 기본이 즉시 로딩으로 설정되어 있다.
- @OneToMany, @ManyToMany는 기본이 지연 로딩
'JPA' 카테고리의 다른 글
JPQL 사용법 (3) | 2023.01.09 |
---|---|
JPA의 데이터 타입 (3) | 2023.01.07 |
엔티티 매핑 (0) | 2023.01.06 |
영속성 컨텍스트 (2) | 2023.01.05 |
JPA란 무엇인가? (7) | 2022.12.24 |