Spring/강의

[📘 심화 Spring] 2-2. JPA 고급 매핑

가지코딩 2025. 6. 9. 17:18

📘 목차

  1. 프록시 (Proxy)
  2. 지연 로딩 (Lazy) vs 즉시 로딩 (Eager)
  3. 영속성 전이 (Cascade)
  4. 고아 객체 (Orphan Removal)

1. 프록시 (Proxy)

JPA는 엔티티를 실제로 조회하지 않고도 프록시 객체를 통해 지연 조회(Lazy Loading)를 가능하게 한다.

 

Entity 조회 방식

  • em.find(): 실제 객체를 즉시 조회한다.
  • em.getReference(): 프록시 객체를 반환하며, 실제 객체는 필요 시 조회한다. (지연 로딩)
Item item = em.getReference(Item.class, 1L); // 프록시 반환

 

 

프록시 특징

  • 실제 클래스를 상속받은 가짜 객체
  • 실제 데이터를 사용하는 시점에 쿼리 실행
  • instanceof는 사용할 수 있으나, 프록시 초기화 여부는 주의 필요

2. 지연 로딩 (Lazy) vs 즉시 로딩 (Eager)

JPA는 연관된 엔티티를 로딩할 때 두 가지 방식을 제공한다.

 

Lazy Loading (지연 로딩)

  • 기본 설정
  • 연관된 엔티티를 조회 시점에 로딩하지 않음
  • 실제 사용하는 시점에 쿼리를 실행한다.
  • 장점: 성능 최적화
  • 단점: 프록시 객체 사용 시 영속성 컨텍스트 밖이면 LazyInitializationException
@ManyToOne(fetch = FetchType.LAZY)
private Team team;

 

 

 

Eager Loading (즉시 로딩)

  • 연관된 엔티티를 즉시 조회
  • fetch = FetchType.EAGER
  • 단점: 연관된 모든 데이터를 즉시 JOIN 하므로 성능 문제가 발생할 수 있음
@ManyToOne(fetch = FetchType.EAGER)
private Team team;

 

 

즉시 로딩 주의점

  • 즉시 로딩은 JPQL에서 N+1 문제를 유발할 수 있다.
  • 항상 필요한 데이터가 아니면 Lazy를 기본으로 두고, 필요 시 JOIN FETCH로 해결하는 것이 권장됨.

3. 영속성 전이 (Cascade)

Cascade

  • 부모 엔티티의 영속성 상태 변화가 자식에게 전이되는 설정
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
private List<OrderItem> orderItems = new ArrayList<>();

 

 

주요 옵션

  • PERSIST: 부모 저장 시 자식도 함께 저장된다.
  • MERGE: 부모 병합 시 자식도 함께 병합된다.
  • REMOVE: 부모 삭제 시 자식도 함께 삭제된다.
  • REFRESH: 부모 새로고침 시 자식도 함께 새로고침된다.
  • DETACH: 부모 분리 시 자식도 함께 영속성 컨텍스트에서 분리된다.
  • ALL: 위 모든 옵션을 포함한다.

 

사용 시 주의점

  • 연관된 엔티티의 생명주기를 완전히 부모가 관리할 때만 사용
  • 복잡한 도메인에서는 과도한 Cascade 사용 지양

4. 고아 객체 (Orphan Removal)

고아 객체

  • 부모 엔티티와의 연관관계가 끊어진 자식 엔티티를 자동으로 삭제하는 설정
  • 연관관계만 끊으면 DELETE 쿼리 자동 실행
  • CascadeType.REMOVE와 함께 자주 사용됨
@OneToMany(mappedBy = "order", orphanRemoval = true)
private List<OrderItem> orderItems = new ArrayList<>();

5. JPA와 트랜잭션 전파

트랜잭션 전파

  • 스프링에서 JPA를 사용할 때 트랜잭션 전파는 메서드 호출 간 트랜잭션 유지 전략

 

트랜잭션 전파 옵션

  • REQUIRED (기본값): 기존 트랜잭션 있으면 참여, 없으면 새로 시작
  • REQUIRES_NEW: 항상 새 트랜잭션 생성 (기존 트랜잭션 중단)
  • NESTED: 기존 트랜잭션 내에 중첩 트랜잭션 시작
  • SUPPORTS, MANDATORY 등: 특별한 상황에서 사용

 

사용 예시

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveLog() {
    logRepository.save(...);
}