[JPA] 프록시

SIK407·2024년 8월 9일
0

spring

목록 보기
5/11
post-thumbnail

즉시 로딩과 지연 로딩 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 데이터는 가져올 필요가 없다.

프록시라는 것을 사용하면 필요한 데이터만 가져올 수 있게 된다.

JPA Proxy란?

실제 사용하는 시점까지 DB 조회를 미루고 싶으면 가짜(프록시) Entity를 조회하면 된다.
이 기능을 이용하기 위해선, em.getReference(Team.class, 1L) 메소드를 사용하면 된다.

저 Proxy는 조회할 실제 클래스를 상속받아 만들어진다.


Proxy 객체 초기화

일단 em.getReference(Team.class, 1L) 이걸 사용하게 되면 빈 덩어리만 있다.

Member refMember = em.getReference(Member.class, 1L)

로 저장을 했다고 가정하자.
refMember.getName(); 을 사용하면 초기화한다.

실제로 사용했을 경우, DB에서 조회가 된다는 뜻이다.
초기화는 영속성 컨텍스트에서 담당하게 된다.

그 다음, 영속성 컨텍스트가 DB에서 데이터를 가져오고,
실제 Member Entity를 생성하게 되고, refMember 객체가 타겟하게 된다.

Proxy 특징

1. 프록시 객체는 처음 사용할 때 한 번만 초기화

Member refMember = em.getReference(Member.class, 1L);

refMember.getName(); // 이때 한번 초기화하고 끝.

2. 초기화되면 프록시 객체를 통해서 실제 엔티티에 접근 가능

초기화가 된다고, 저 refMember가 직접 그 Entity가 되는것은 아니다.
접근만 가능하다고!!

3. 프록시 객체는 원본 엔티티를 상속받음, 따라서 타입 체크시 주의!

Member refMember = em.getReference(Member.class, 1L);
refMember.getName(); // 프록시 객체 초기화

Member findMember = em.find(Member.class, 1L); // 실제 엔티티

System.out.print(findMember.getClass() == refMember.getClass());

저 두개의 객체를 비교하게 되면, false가 출력된다.

저 둘은 엄연히 다른 객체다.

4. 영속성 컨텍스트에 찾는 엔티티가 이미 있으면 프록시도 실제 객체가 된다.

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가 출력이 된다.

5. 준영속 상태일 때, 프록시를 초기화하면 문제 발생

준영속 상태
영속성 컨텍스트가 관리하던 영속 상태의 엔티티를 더이상 관리하지 않는 상태

준영속 상태를 만드는 메소드가 있다.

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

이렇게 화를 낸다.

그러니까 영속성에서 함부러 분리하면 안된다....;;;

profile
Spring 백엔드!

0개의 댓글

관련 채용 정보