Spring/Spring Data JPA

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

noahkim_ 2023. 12. 28. 16:02

김영한 님의 "자바 ORM 표준 JPA 프로그래밍" 책을 정리한 포스팅 입니다.

 

1. 프록시

  • 엔티티를 조회할 때, 연관된 엔티티를 항상 사용하는 것은 아닙니다.
  • 모든 연관관계를 한꺼번에 가져오게 될 경우, 성능이 떨어질 수 있습니다.

 

프록시

항목 설명
목적
연관 엔티티를 실제 사용할 때까지 DB 조회를 지연(Lazy Loading)
프록시 객체
실제 클래스를 상속받아 만들어진 가짜 객체
실제 객체의 참조를 보관
- 프록시 객체의 메소드를 호출하면 실제 객체의 메소드를 호출 (데이터베이스 조회 지연 목적)
초기화 영속성 컨텍스트에 의해 초기화됨
한 번만 초기화되고 재사용됨
내부에 실제 객체(target) 참조
식별자 활용
프록시는 식별자(ID)만 저장된 상태로 생성됨
식별자 접근은 초기화 없이 가능
ex) 상세 정보가 필요 없는 경우 ID만 참조해도 됨
→ 프록시로 비용 절감
지연 로딩 예외
준영속 상태에서 초기화 요청 시 LazyInitializationException 발생

 


예시) 프록시 객체 생성

더보기
Member member = em.getReference(Member.class, "id1");
member.getName();
  • 프록시 객체 반환 + 엔티티 객체 생성 X (DB 조회 X)
  • (엔티티가 영속성 컨텍스트에 존재 시) 실제 엔티티 반환

 

예시) 프록시 확인

더보기

PersistenceUnitUtil().isLoaded()

boolean isLoad = em.getEntityManagerFactory().getPersistenceUnitUtil().isLad(entity);
  • 프록시 인스턴스의 초기화 여부 확인

 

예제) 초기화 과정

더보기
class MemberProxy extends Member {
    private Member target;

    public String getName() {
        if (target == null) {
            // 2. 초기화 요청
            // 3. DB 조회
            // 4. 실제 엔티티 생성 및 참조 보관
            this.target = ...;
        }

        return target.getName();
    }
}
  1. 프록시 객체에 member.getName()을 호출하여 실제 데이터를 조회합니다.
  2. 실제 엔티티가 생성되어 있지 않으면, 영속성 컨텍스트에 실제 엔티티 생성을 요청합니다.
  3. 영속성 컨텍스트는 데이터베이스를 조회해서 실제 엔티티 객체를 생성합니다.
  4. 프록시 객체는 생성된 실제 엔티티 객체의 참조를 멤버변수에 저장합니다.

 

2. 즉시 로딩과 지연 로딩

항목 즉시 로딩 (FetchType.EAGER)
지연 로딩 (FetchType.LAZY)
기본 동작 연관 엔티티를 즉시 함께 조회
연관 엔티티를 프록시로 조회
DB 조회 시점 엔티티 로딩과 동시에 연관 객체도 조회
연관 객체를 사용할 때 쿼리 실행
조인 전략 ✅ (LEFT OUTER JOIN) ❌ 사용 안함
@JoinColumn(nullable=false) INNER JOIN ❌ 사용 안함
프록시 사용 여부 ❌ 사용 안함
컬렉션 즉시 로딩 LEFT OUTER JOIN
⚠️ 두 개 이상 즉시 로딩 금지 권장
→ 성능 급락 (N+1 문제 발생 위험)
❌ 사용 안함
LazyInitializationException 발생하지 않음
❗ 영속성 컨텍스트 종료 상태에서 접근 시 발생 가능
성능 관점 불필요한 로딩 위험 → 성능 저하 가능
성능 최적화 유리 (필요 시만 조회)

 

예제) 즉시 로딩

더보기
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "team_id")
private Team team;
// Member를 조회하면, Team도 함께 가져옴 (즉시 조인)
Member member = em.find(Member.class, 1L);

 

예제) 지연 로딩

더보기
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "team_id")
private Team team;
Member member = em.find(Member.class, 1L); // team은 아직 조회 안 됨

Team team = member.getTeam();             // 이때 DB 조회 발생 (프록시 초기화)

 

4. 영속성 전이 : CASCADE

  • 부모 엔티티를 영속 상태로 만들 때, 연관된 자식 엔티티도 함께 영속 상태로 만드는 기능
  • 연관된 모든 엔티티는 영속 상태여야 함
  • 수동으로 연관 엔티티까지 persist 하지 않아도 함께 영속화되는 편리함을 제공
항목 설명
적용 위치
@OneToMany, @ManyToOne, @OneToOne 등의 연관관계 매핑에 설정
대표 옵션
CascadeType.PERSIST: 부모를 영속화할 때, 연관된 자식들도 함께 영속화합니다.
CascadeType.REMOVE: 부모와 자식 엔티티 모두 제거하는 쿼리를 생성합니다.
주의사항
연관된 모든 객체의 상태를 명확히 파악한 뒤 사용해야 함 (무분별하게 사용 시 데이터 무결성 문제 발생 가능)

 

 

예제

더보기
@Entity
public class Parent {
    // ...

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

 

고아 객체

  • 부모 엔티티와 연관관계가 끊긴 자식 엔티티를 자동으로 삭제할 수 있습니다.

 

예제) @OneToMany의 orphanRemoval 속성

더보기
@Entity
public class Parent {
    @Id @GeneratedValue
    private Long id;
    
    @OneToMany(mappedBy = "parent", orphanRemoval = true)
    private List<Child> children = new ArrayList<Child>();
    ...
}