enginner_s2eojeong

[자바 ORM 표준 JPA 프로그래밍] 08. 프록시와 연관관계 관리 본문

Backend/JPA

[자바 ORM 표준 JPA 프로그래밍] 08. 프록시와 연관관계 관리

_danchu 2025. 5. 8. 14:51


1. 프록시란?

JPA에서 엔티티를 조회할 때 getReference() 같은 메서드를 사용하면 실제 엔티티 객체가 아니라 프록시 객체를 반환한다.

프록시 = 실제 객체를 상속받은 가짜 객체
→ 실제 객체는 사용될 때 로딩됨 (지연 로딩)

Member member = em.getReference(Member.class, "id1");
// 아직 DB 조회 X

String name = member.getName(); 
// 이 시점에 DB에서 조회 (초기화 발생)
em.find() em.getReference()
데이터베이스를 통한 실제 엔티티 객체 조회 데이터베이스 조회를 미루는 가짜(프록시) 엔티티 객체 조회

 

1.1 프록시 특징

 

  • 실제 클래스 상속 (자바의 다형성 이용)
  • 실제 객체와 겉보기엔 동일
  • 프록시 객체는 처음 사용할 번만 초기화
  • 프록시 객체를 초기화 , 프록시 객체가 실제 엔티티로 바뀌는 것은 아님. 기화되면 프록시 객체를 통해서 실제 엔티티에 접근 가능
  • 프록시 객체는 원본 엔티티를 상속받음. 따라서 타입 체크시 주의해야함. (== 실패, 대신 instance of 사용)
  • 영속성 컨텍스트에 찾는 엔티티가 이미 있으면 em.getReference() 호출해 실제 엔티티 반환
  • 영속성 컨텍스트의 도움을 받을 없는 준영속 상태일 , 프록시를 초기화하면 문제 (하이버네이트는 org.hibernate.LazyInitializationException 예외를 터트림)

1.2 프록시 주의사항 

1. 타입 비교는 ==이 아니라 instanceof 사용 !

// 잘못된 예
if (member.getClass() == Member.class) { ... }

// 올바른 예
if (member instanceof Member) { ... }

 

2. 영속성 컨텍스트가 닫히면 프록시 초기화 불가

  • 프록시는 실제 DB 접근이 필요할 수 있음
  • 영속성 컨텍스트 종료 후 필드 접근 → LazyInitializationException

 

2. 즉시 로딩과 지연 로딩

2.1 지연 로딩 (LAZY)

  • 연관 엔티티는 프록시로 로딩
  • 실제 사용 시 DB 조회
@Entity
public class Member {
	@Id
	@GeneratedValue
	private Long id;
    
	@Column(name = "USERNAME")
	private String name;
    
	@ManyToOne(fetch = FetchType.LAZY) // <- 여기
	@JoinColumn(name = "TEAM_ID")
	private Team team;
	..
}

2.2 즉시 로딩 (EAGER)

  • 연관 엔티티를 즉시 함께 조회 (조인 발생)
  • Member 조회 시 항상 Team 함께 조회
@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;
	..
}

2.3  프록시와 즉시 로딩 주의할 점 ⚠️

  • 가급적 지연 로딩(LAZY) 사용 권장
  • 즉시 로딩은 예상 못한 SQL 실행N+1 문제 유발
  • @ManyToOne, @OneToOne 기본이 즉시 로딩 -> LAZY 설정
  • @OneToMany, @ManyToMany 기본이 지연 로딩

 

3. 지연 로딩 활용

  • Member와 Team은 자주 함께 사용 -> 즉시 로딩
  • Member와 Order는 가끔 사용 -> 지연 로딩
  • Order와 Product는 자주 함께 사용 -> 즉시 로딩

 

3.1 실무에서 지연 로딩 활용하기

  1. 모든 연관관계에서 지연로딩 사용하기
  2. JPQL fetch 조인이나, 엔티티 그래프 기능을 사용하기
  3. 즉시 로딩은 상상하지 못한 쿼리가 나간다...!

 

4. 영속성 전이: CASCADE

특정 엔티티를 영속 상태로 만들 연관된 엔티티도 함께 영속 상태로 만들도 싶을

Ex) 부모 엔티티를 저장할 자식 엔티티도 함께 저장.

@OneToMany(mappedBy = "parent", cascade = CascadeType.PERSIST)
private List<Child> children = new ArrayList<>();

 

4.1 CASCADE 종류

타입 설명
ALL 모두 적용
PERSIST 영속
REMOVE 삭제
MERGE 병합
REFRESH 동기화
DETACH 분리

 

4.2 주의할 점⚠️ 
  • 연관관계 매핑과는 무관 (편의 기능일 뿐)
  • 실제 연관관계 설정은 mappedBy 등으로 별도 처리

 

5. 고아 객체

부모와의 관계가 끊어진 자식 엔티티를 자동 삭제

 

정의:

@OneToMany(mappedBy = "parent", orphanRemoval = true)
private List<Child> children = new ArrayList<>();

 

사용 방법: 

Parent parent1 = em.find(Parent.class, id);
parent1.getChildren().remove(0);
//자식 엔티티를 컬렉션에서 제거
DELETE FROM CHILD WHERE ID=1;

 

5.1 주의할 점

 

  • 참조하는 곳이 한 곳일 때 사용 가능
  • 특정 엔티티가 개인 소유할 때 사용
  • @OneToOne, @OneToMany만 적용 가능

 

6. 영속성 전이 + 고아 객체, 생명주기

사용 예시:

@OneToMany(mappedBy = "parent", 
           cascade = CascadeType.ALL, 
           orphanRemoval = true)
private List<Child> children = new ArrayList<>();

 

  • 부모 엔티티를 통해 자식의 생성과 삭제 모두 관리 가능
  • 도메인 주도 설계(DDD)에서 Aggregate Root 개념 구현에 활용
  • 스스로 생명주기를 관리하는 엔티티는 em.persist()로 영속화, em.remove()로 제거
  • CascadeType.ALL + orphanRemoval=true
    • 두 옵션을 모두 활성화 하면 부모 엔티티를 통해서 자식의 생명 주기를 관리할 수 있음