em.find()와 em.getReference() 차이는?
em.find는 데이터베이스를 통해 실제 엔티티 객체를 조회한것 이고
em.getReference()는 프록시에서 값을 조회하는 것으로 프록시에 없는 값을 조회할 때까지 데이터베이스 조회를 미룬다.
프록시 특징
- 실제 클래스를 상속 받아서 만들어짐
- 실제 클래스와 겉 모양이 같음
- 사용하는 입장에서 진짜 객체인지 프록시 객체인지 구분하지 않고 사용 가능
- 프록시 객체는 현재 자신이 갖고 있지 않은 데이터에 접근해야 할 때, 처음 한 번만 초기화 한다.
- 프록시 객체를 초기화 할때, 프록시 객체가 실제 엔티티로 바뀌는 것은 아님
- 초기화되면 프록시 객체를 통해서 실제 엔티티에 접근 가능
- 프록시 객체는 원본 엔티티를 상속받음, 따라서 타입 체크시 주의해야함
(프록시를 통해 호출할 수도 있고 엔티티를 통해 호출할 수도 있기 때문에
== 와 같은 연산자 비교 대신, instance of 를 통해 타입 체크를 해야한다.)
- detach나 clear와 같은 메소드를 통해 준영속 상태가 되었을 경우 프록시를 초기화하면 문제가 발생한다.
1. getReference()(프록시)를 통해 생성한 객체에서 getName() 메소드 호출
2. getName() 메소드가 이전에 find와 같은 메소드를 통해 영속성 엔티티에서 사용되지 않은 상태라면,
프록시는 초기화를 위해 영속성 컨텍스트에 엔티티를 요청한다.
3. 요청한 값을 DB에서 조회
4. 조회한 값 반환
5. 프록시의 target 변수가 조회한 엔티티를 참조하여 값을 호출한다.
같은 클래스 객체를 member1을 먼저 getReference를 통해 조회했다가, 후에 find를 통해 member2를 조회하게 되면
member1은 프록시를 통해 호출되지만 member2는 엔티티를 통해 호출되어 member1.getClass!=member2.getClass가 된다.
하지만 find(~,getId())를 통해 member2를 먼저 조회하고 getReference(~,getID())를 통해 member1을 조회하게 되면,
member2에서 영속성 컨텍스트에 값을 미리 저장해 두었기 때문에 member1도 프록시를 초기화하지 않고 엔티티를 통해
호출되어 member1.getClass==member2.getClass가 된다.
단, 프록시를 위와 같이 영속성 컨텍스트에서 값을 가져왔을뿐 프록시를 초기화하지 않은 상태에서 member1.getName()과 같이 영속성 컨텍스트에 저장되어 있지 않은 값을 조회하면, 프록시 초기화를 위해 select 쿼리가 실행된다.
지연 로딩(fetch = FetchType.LAZY)
Member member = em.find(Member.class, 1L);
Team team = member.getTeam();
team.getName(); // 실제 team을 사용하는 시점에 초기화(DB 조회)
member 객체는 find를 통해 엔티티에서 조회하면서 Member에 대한 select 쿼리가 진행된다.
하지만 team은 아직 DB에 select하지 않은 상태로 조회를 뒤로 미루다가,
team.getName()이라는 메소드를 통해 값을 호출할 때 비로소 DB에 select 쿼리를 보낸다.
이처럼 특정 객체 내부에 객체가 필드로 있을 경우, 내부 객체 필드는 외부 객체가 호출될 때가 아니라
내부 객체의 필드값을 조회할 때 DB에 쿼리를 보내는 것을 지연 로딩(LAZY)이라고 한다.
즉시 로딩(fetch = FetchType.EAGER)
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@Column(name = "USERNAME")
private String name;
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "TEAM_ID")
private Team team;
}
fetch 속성을 사용하여 내부 객체가 즉시 로딩되도록 변경할 수도 있다.
즉시 로딩이란 지연 로딩과 반대로, 외부 객체가 조회될 경우 내부 객체도 함께 조회되는 것이다.
주의점
- 가급적 지연 로딩만 사용한다.
- 즉시 로딩은 JPAL에서 N+1 문제를 일으킨다.
( N+1 문제란, 하나의 쿼리를 요청했는데, 내부 객체 필드로 인해 관련된 N개의 쿼리가 더 발생하는 현상을 뜻한다.
이로 인해 프로그램 성능 저하가 발생하기 때문에 지연 로딩 사용이 바람직하다.)
- @ManyToOne, @OneoToOne은 Default가 즉시 로딩이기 때문에 LAZY로 수동 설정 해줘야 한다.
'JPA' 카테고리의 다른 글
[JPA] 임베디드 타입 (0) | 2023.07.21 |
---|---|
[JPA] 영속성 전이(CASCADE)와 고아 객체 (0) | 2023.07.17 |
[JPA] 연관 관계 매핑 (0) | 2023.07.17 |
[JPA] 필드와 컬럼 매핑 (0) | 2023.07.15 |
[JPA] 객체와 테이블 매핑 (0) | 2023.07.15 |