즉시 로딩과 지연 로딩 feat (N + 1 문제)
이걸 위해서 일단 프록시라는 것을 알아보자
EntityManager를 em이라고 하겠다.
일단 여기에 테이블이 두가지가 있다고 가정하겠다.
@Entity
@Getter @Setter
public class Member {
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")
private String username;
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "TEAM_ID")
private Team team;
}
Id, username 그리고 team하고 N:1로 연결되어 있는 Member
@Entity
@Getter @Setter
public class Team {
@Id @GeneratedValue
@Column(name = "TEAM_ID")
private Long id;
private String name;
@OneToMany(mappedBy = "team", fetch = FetchType.LAZY)
private List<Member> members = new ArrayList<>();
}
그리고 팀 테이블이 있다.
일단 아시는 분들을 위해, fetch를 무시하자.
em.find(Member.class, 1L)
이렇게 하면, 1번 멤버가 나오게 된다.
근데... 그러면 강제로 연결된 Team 데이터도 가져오게 된다.
Member 데이터만 필요하다고 가정하면, Team 데이터는 가져올 필요가 없다.
프록시라는 것을 사용하면 필요한 데이터만 가져올 수 있게 된다.
실제 사용하는 시점까지 DB 조회를 미루고 싶으면 가짜(프록시) Entity를 조회하면 된다.
이 기능을 이용하기 위해선, em.getReference(Team.class, 1L)
메소드를 사용하면 된다.
저 Proxy는 조회할 실제 클래스를 상속받아 만들어진다.
일단 em.getReference(Team.class, 1L)
이걸 사용하게 되면 빈 덩어리만 있다.
Member refMember = em.getReference(Member.class, 1L)
로 저장을 했다고 가정하자.
refMember.getName();
을 사용하면 초기화한다.
즉 실제로 사용했을 경우, DB에서 조회가 된다는 뜻이다.
초기화는 영속성 컨텍스트에서 담당하게 된다.
그 다음, 영속성 컨텍스트가 DB에서 데이터를 가져오고,
실제 Member Entity를 생성하게 되고, refMember
객체가 타겟하게 된다.
Member refMember = em.getReference(Member.class, 1L);
refMember.getName(); // 이때 한번 초기화하고 끝.
초기화가 된다고, 저 refMember가 직접 그 Entity가 되는것은 아니다.
접근만 가능하다고!!
Member refMember = em.getReference(Member.class, 1L);
refMember.getName(); // 프록시 객체 초기화
Member findMember = em.find(Member.class, 1L); // 실제 엔티티
System.out.print(findMember.getClass() == refMember.getClass());
저 두개의 객체를 비교하게 되면, false가 출력된다.
저 둘은 엄연히 다른 객체다.
Member findMember = em.find(Member.class, 1L); // 실제 엔티티
Member refMember = em.getReference(Member.class, 1L);
refMember.getName(); // 프록시 객체 초기화
System.out.print(findMember.getClass() == refMember.getClass());
자 이번엔 순번만 바꿨다.
첫번째 줄에서 findMember
가 DB에서 데이터를 찾아와 실제 객체를 생성한다.
그럼 그 객체는 영속성 컨텍스트에 1차 캐시에 보관되어 관리되게 된다.
그럼 refMember
가 초기화를, 영속성 컨텍스트가 담당하기 때문에 그대로 실제 객체를 가져오게 된다.
그럼 당연히, 마지막 출력 문장은 true가 출력이 된다.
준영속 상태
영속성 컨텍스트가 관리하던 영속 상태의 엔티티를 더이상 관리하지 않는 상태
준영속 상태를 만드는 메소드가 있다.
em.detach(객체 명); // Entity를 영속성 컨텍스트에서 분리
em.claer(); // 영속성 콘텍스트 비우기
em.close(); // 영속성 콘텍스트 종료
얘네들을 선언하고 refMember.getName();
를 선언해 초기화하게 되면....
// em.detach(객체 명), em.claer();
could not initialize proxy [org.example.hellojpa.Member#1] - no Session
// em.close(); 사용
java.lang.IllegalStateException: Session/EntityManager is closed
이렇게 화를 낸다.
그러니까 영속성에서 함부러 분리하면 안된다....;;;