[TIL] JPA의 프록시 객체와 즉시로딩&지연로딩 편하게 쓰세요

wannabeing·2025년 5월 1일
0

SPARTA-TIL

목록 보기
18/22
post-thumbnail

프록시 객체의 특징

1. 프록시는 실제 객체로 바뀌지 않는다.

  • JPA에서 지연로딩을 사용하면, 실제 엔티티가 아닌 프록시 객체(proxy)가 반환된다.
  • 초기화되더라도 해당 객체가 실제 객체로 교체되는 것이 아니라,
    프록시 내부에서 실제 객체를 조회하여 위임 호출할 뿐이다.
즉, 프록시는 “껍데기”이고, 그 안에 실제 객체를 호출한다.

2. 비교할 때는 instance of를 사용하자.

  • 프록시 객체는 실제 객체의 상속 객체이기 때문에, ==.equals()로 비교하면 실패할 수 있다.
    File file = entityManager.getReference(File.class, 1L); // 프록시 객체
    file instanceof File; // ✅ true
    file.getClass() == File.class; // ❌ false

3. 영속성 컨텍스트에 이미 엔티티가 있다면 프록시가 아닌 실제 객체가 반환된다.

File file1 = entityManager.find(File.class, 1L); // 실제 객체

// 프록시 객체로 가져오기 위해 "getReference"를 사용했지만, 실제 객체가 반환됨
File file2 = entityManager.getReference(File.class, 1L); 
file1 == file2; // ✅ true

❓ find() vs getReference() 차이

  • .find()

    • 즉시 로딩(Eager Loading)
    • 호출 시점에 즉시 DB에서 실제 엔티티를 조회하여 반환한다.
  • .getReference()

    • 지연 로딩(Lazy Loading)
    • 실제 엔티티를 조회하지 않고, 프록시 객체(가짜 객체)를 반환한다.
    • 실제로 접근하는 순간(DB 접근 시점)에 실제 객체를 초기화한다.

✅ 프록시 객체를 코드로 확인해보자!

Member member = new Member();
member.setName("palmer");
entityManager.persist(member); // 영속 상태 및 Insert 쿼리 날림

Team team = new Team();
team.setName("chelsea");
entityManager.persist(team); // 영속 상태 및 Insert 쿼리 날림

entityManager.clear();  // 영속성 컨텍스트 초기화

Member proxy = entityManager.getReference(Member.class, member.getId());
System.out.println(proxy.getClass()); // 프록시 객체

✅ 프록시 객체로 진짜 객체를 호출해보자!

Member member = new Member();
member.setName("palmer");
entityManager.persist(member); // 영속 상태 및 Insert 쿼리 날림

Team team = new Team();
team.setName("chelsea");
entityManager.persist(team); // 영속 상태 및 Insert 쿼리 날림

entityManager.clear();  // 영속성 컨텍스트 초기화

Member proxyMember = entityManager.getReference(Member.class, member.getId());
proxyMember.getName(); // 이 때, 진짜 객체의 값을 조회하여 반환한다.
System.out.println(proxy.getClass()); // 프록시 객체는 바뀌지 않는다.

proxyMember 생성 → proxy.getName() 호출  
→ 프록시 초기화 과정에서 영속성 컨텍스트가 DB 접근  
→ 실제 Member 객체 로딩 및 영속성 컨텍스트 재등록  
→ 프록시가 실제 Member 객체를 참조하게 됨 (target) 
→ 실제 객체의 getName() 결과 반환

지연로딩(Lazy)과 즉시로딩(Eager)

지연로딩

지연로딩으로 세팅하면 연관관계를 프록시 객체로 가져온다.
실제로 사용하는 순간에 쿼리를 날려 프록시 객체에 진짜 객체를 연결해준다.

즉시로딩

연관관계가 자주 함께 사용되는 경우에 사용한다.
쿼리를 한번만 날려 한꺼번에 가져올 수 있기 때문에 성능상 유리할 수 있다.

JPA는 최대한 조인을 통해 한꺼번에 가져오려고 하는 경향이 있다.
@ManyToOne, @OneToOne의 기본 설정은 즉시로딩이다.

즉시로딩을 적용하면 예상치 못한 쿼리가 날라갈 수 있기 때문에
주의해서 사용해야 한다.


😇 즉시로딩 사용시, JPQL에서 N+1 문제를 일으킬 수 있다.

Team teamA = new Team();
teamA.setName("chelsea");
entityManager.persist(teamA); // teamA

Team teamB = new Team();
teamB.setName("liverpool");
entityManager.persist(teamB); // team B
		
Member memberA = new Member();
memberA.setName("palmer");
memberA.setTeam(teamA);
entityManager.persist(memberA); // memberA

Member memberB = new Member();
memberB.setName("salah");
memberB.setTeam(teamB);
entityManager.persist(memberB); // memberB

entityManager.clear();  // 영속성 컨텍스트 초기화

// N+1 문제 발생하는 쿼리
List<Member> result = entityManager
						.createQuery("select m from Member m", Member.class)
                        .getResultList();
  • Member(N):Team(1) : @ManyToOne 설정을 했다.
  • JPQL이 Member에 관한 SQL을 만들 때,
    Member객체에 Team이 즉시로딩 설정이 되어있는 것을 보고,
    Team을 조회하게 된다.
    우리는 1개의 select 쿼리를 날리고자 했는데, N개의 쿼리가 추가되어 날라간다.
  • 근본적인 N+1 문제는 @EntityGraph, @BatchSize, fetch join 등으로 해결할 수 있다.

🚀 따라서! 왠만하면 지연로딩(LAZY)로 설정하자!!


김영한 자바 ORM 표준 JPA 프로그래밍

profile
wannabe---ing

0개의 댓글