반응형
JPA에서 사용하는 프록시란 무엇인가?
JPA는 성능 최적화를 위해 지연 로딩(Lazy Loading) 기능을 지원한다.
이를 가능하게 만드는 핵심 기술이 바로 프록시(Proxy) 객체다.
왜 프록시가 필요한가?
예를 들어, Member 엔티티와 Team 엔티티가 연관관계에 있다고 하자.

- 어떤 경우에는 Member와 Team 정보를 모두 필요로 한다. → 이때는 fetch join이나 즉시 로딩(EAGER) 이 유리할 수 있다.
- 하지만 대부분의 경우에는 Member 정보만 필요하다. → Team까지 항상 불러오는 건 성능 낭비다.
그래서 JPA는 Team은 나중에 필요할 때만 조회하도록 설정할 수 있고, 이때 Team 대신 프록시 객체를 넣어준다.
em.find() vs em.getReference()
| find | 실제 엔티티 조회 | 즉시 |
| getReference | 프록시 객체 반환 | 필요한 시점까지 지연 |
Member m = em.find(Member.class, memeber.getId()); // 이때 조회 쿼리가 실제로 발생함
System.out.prinln("id = " + m.getId());
Member m = em.getReference(Member.class, member.getId()); // select 쿼리 안나감
System.out.prinln("id = " + m.getId()); // 이땐 쿼리 X 이미 ID값으로 조회했으니 ID는 있음
// name 출력전 조회 쿼리 발생
System.out.prinln("id = " + m.getName());
// 만약에 조회하는 id값이 없으면??
// javax.persistence.EntityNotFoundException: Unable to find com.example.Member with id 999 익셉션 발생
- 주의: 해당 ID가 실제 DB에 없으면 EntityNotFoundException 발생.
프록시의 정체
어떻게 이런식으로 구현이 가능할까?
하이버네이트가 실제 엔티티를 상속받은 프록시 객체를 구현하고 있기 때문이다

- 이 프록시는 진짜 Member 엔티티의 참조를 내부에 보관하고 있다가, 메서드가 호출되면 그 시점에 DB에서 데이터를 조회한다.
프록시의 동작 흐름
1. 프록시 객체는 초기엔 데이터가 없음
- MemberProxy 내부에 실제 Member 객체(target)가 비어 있음.
2. getName() 호출 시 프록시가 초기화 요청
- 클라이언트가 member.getName()을 호출하면 프록시가 실제 데이터를 가져오기 위해 초기화 요청.
3. 영속성 컨텍스트가 DB에서 데이터를 조회함
4. 조회된 결과로 실제 엔티티(Member)를 생성
5. 프록시 객체가 실제 엔티티(target)와 연결됨
6. 이후부터는 실제 엔티티의 메서드를 통해 값 반환

프록시의 특징 요약
- 실제 클래스를 상속받아 생성된다.
- 겉으로 보면 동일한 타입처럼 보인다.
- 프록시 객체는 실제 객체의 참조(target)을 보관한다
- 프록시를 통해 실제 엔티티를 조회하면, 내부적으로 프록시 → 타겟 연결 → 타겟 메서드 호출 순으로 진행된다.
- 프록시는 한 번만 초기화되며, 초기화되었다고 해서 객체가 진짜 엔티티로 바뀌는 것은 아니다.
프록시 사용 시 주의사항
1. 타입 비교 시 == 사용 금지
프록시 객체는 원본 엔티티를 상속받음, 따라서 타입 체크시 주의 해야함 (== 비교 실패, 대신 instance of 사용)
Member m1 = em.find(Member.class, 1L);
Member m2 = em.getReference(Member.class, 1L);
System.out.println(m1 == m2); // false
System.out.println(m1 instanceof Member); // true
2. 영속성 컨텍스트에 존재 여부에 따라 실제 객체 반환 가능
Member m = em.find(Member.class, 1);
System.out.println("m == " + m.getClass()); // 멤버 엔티티
Member reference = em.getReference(Member.class, 1);
System.out.prinln("m== " + m.getClass()); // 이또한 멤버 엔티티
같은 트랜잭션, 같은 영속성 컨텍스트에서는 JPA가 동일성(==) 보장을 위해 프록시 대신 실제 엔티티를 반환한다. (반대도 마찬가지) + 이미 영속성 컨텍스트에 있는데 프록시로 반환해도 이점이 없음
3. 준영속 상태일 때 프록시 초기화 하면 에러 발생
Member reference = em.getReference(Member.class, 1);
System.out.prinln("m== " + reference.getClass());
em.detach(reference); // clear(), close()도 동일함
System.out.println("refMember = " + reference.getUsername());
// 에러발생 LazyInitializationException 발생!
프록시 유틸리티
초기화 여부 확인
PersistenceUnitUtil util = emf.getPersistenceUnitUtil();
util.isLoaded(m); // true or false
클래스 확인
entity.getClass()
프록시 강제 초기화
Hibernate.initialize(refMember); // 쿼리가 나가도록 강제 초기화
반응형
'프로그래밍 > JPA' 카테고리의 다른 글
| [JPA] 영속성 전이 CASCADE (2) | 2025.07.17 |
|---|---|
| [JPA] 즉시 로딩과 지연 로딩 (2) | 2025.07.16 |
| [JPA] Mapped Superclass - 매핑 정보 상속 (0) | 2025.07.03 |
| [JPA] 상속관계 매핑 (2) | 2025.07.03 |
| [JPA] 다양한 연관관계 매핑 (2) | 2025.06.30 |