HomeAboutMeBlogGuest
© 2025 Sejin Cha. All rights reserved.
Built with Next.js, deployed on Vercel
🌚
[New] 우기팀
/현지/
8주차 연관관계, 고급 매핑, 프록시 객체

8주차 연관관계, 고급 매핑, 프록시 객체

연관관계 매핑

키워드

  • 방향 (Direction) : 테이블 관계는 항상 외래키를 사용한 양방향이고 객체 관계는 참조를 통한 단방향
  • 연관관계의 주인 (Owner) : 객체를 양방향 연관관계로 만들면 연관관계의 주인을 정해야 한다.

객체 연관관계와 테이블 연관관계의 차이

notion image
  • 객체는 참조(주소 a.getB())로 연관관계를 맺는다. 참조를 사용하는 객체의 연관관계는 단방향이다.
  • 객체를 양방향으로 참조하려면 단방향 연관관계를 2개 만들어야 한다.
notion image
  • 테이블은 외래 키로 양방향이다 연관관계를 맺는다. 외래 키 하나로 양방향 조인할 수 있다.
    • A JOIN B, B JOIN A

양방향 연관관계

  • 단방향 매핑만으로 테이블과 객체의 연관관계 매핑은 완료
  • 단방향을 양방향으로 만들면 반대방향으로 객체 그래프 탐색 기능이 추가된다.
  • 양방향 연관관계를 매핑하려면 객체에서 양쪽 방향을 모두 관리해야 한다.
  • 연관관계 편의메서드에서 로직을 견고하게 작성할 것
  • 먼저 단방향 매핑을 사용하다가 반대 방향의 객체 그래프 탐색이 필요할 때 양방향을 사용하도록 코드를 추가해도 된다.
  • lombok toString() 등 호출시 무한루프에 빠지지 않도록 주의, lombok toString()→ exclude옵션
 
notion image

연관관계의 주인

  • 양방향 연관관계 매핑 시, 두 연관관계 중 하나를 연관관계의 주인으로 정해야 한다.
  • 연관관계의 주인만 데이터베이스 연관관계와 매핑되고 외래 키를 관리(등록, 수정, 삭제)할 수 있다. 반면에 주인이 아닌 쪽은 읽기만 할 수 있다.
  • 연관관계의 주인은 외래 키가 있는 곳으로 설정한다. 연관관계의 주인은 외래키의 위치와 관련해서 정해야지 비즈니스 중요도로 접근하면 안된다.
  • 데이터베이스 테이블의 다대일, 일대다 관계에서는 항상 다 쪽이 외래키를 갖는다. 다 쪽인 @ManyToOne은 항상 연관관계의 주인이 되므로 mappedBy를 설정할 수 없다. 따라서 @ManyToOne에는 mappedBy 속성이 없다.

다중성

@ManyToOne

속성
기능
기본값
optional
false로 설정하면 연관된 엔티티가 항상 있어야 한다.
true
fetch
글로벌 패치 전략 설정
FetchType.EAGER
cascade
영속성 전이 기능
ㅤ

@OneToOne

  • 주 테이블, 대상 테이블 둘 다 외래 키를 가질 수 있다.
  • 프록시를 사용할 때 외래키를 직접 관리하지 않는 일대일 관계는 지연 로딩으로 설정해도 즉시 로딩된다.
  • One-To-One 과 Lazy Loading 항목 참고
권남
@MapsId로 OneToOne 매핑시에 자식측에 @JoinColumn이 필요하다.
권남
https://kwonnam.pe.kr/wiki/java/jpa/one-to-one
권남

@OneToMany

@ManyToMany

고급 매핑

상속 관계 매핑

  • @Inheritance(strategy = InheritanceType.JOINED)
    • 각각의 테이블로 변환 - 모두 테이블로 만들고 조회할 때 조인을 사용하는 조인 전략
    • @DiscriminatorColumn(name=”DTYPE”), @DiscriminatorValue(”M”)
    • 장점
      • 테이블이 정규화된다
      • 외래 키 참조 무결성 제약조건 활용할 수 있다.
      • 저장공간을 효율적으로 사용
    • 단점
      • 조회할 때 조인이 많이 사용되므로 성능이 저하될 수 있다.
      • 조회 쿼리가 복잡하다
      • 데이터를 등록할 때 INSERT SQL을 두 번 실행한다.
  • @Inheritance(strategy = InheritanceType.SINGLE_TABLE)
    • 통합 테이블로 변환 - 테이블을 하나만 사용해서 통합, 단일 테이블 전략
    • 장점
      • 조인이 필요 없으므로 일반적으로 조회 성능이 빠르다
      • 조회 쿼리가 단순
    • 단점
      • 자식 엔티티가 매핑한 컬럼은 모두 null을 허용해야 한다.
      • 단일 테이블에 모든 것을 저장하므로 테이블이 커질 수 있다. 상황에 따라서 조회 성능이 오히려 느려질 수 있다.
    • 특징
      • 구분 컬럼을 꼭 사용해야 한다. @DiscriminatorColumn을 꼭 설정해야 한다.
      • @DiscriminatorValue를 지정하지 않으면 기본으로 엔티티 이름을 사용
  • @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
    • 서브타입 테이블로 변환 - 서브 타입마다 하나의 테이블 생성, 테이블 전략
    • 장점
      • 서브 타입을 구분해서 처리할 때 효과적
      • not null 제약 조건 사용 가능
    • 단점
      • 여러 자식 테이블을 함께 조회할 때 성능이 느리다
      • 자식 테이블을 통합해서 쿼리하기 어렵다
    • 특징
      • 구분 컬럼을 사용하지 않는다. (@Discriminator...)
      • 비추천 ( 조인이나 단일 테이블 전략을 사용할 것)
  • MappedSuperclass
    • 자식 클래스에 엔티티의 매핑 정보를 상속하기 위해 사용
    • 이 클래스를 직접 생성해서 사용할 일은 거의 없으므로 추상 클래스로 만드는 것을 권장
    • 테이블과는 관계가 없고 단순히 엔티티가 공통으로 사용하는 매핑 정보를 모아주는 역할
  • 복합 키와 식별 관계 매핑
 

프록시와 연관관계 관리

프록시

  • JPA 표준 명세는 지연 로딩의 구현 방법을 JPA 구현체에 위임했다. 구현체마다 동작이 다를 수 있다. 여기서는 하이버네이트 기준. 하이버네이트는 지연 로딩을 지원하기 위해 프록시를 사용하는 방법, 바이트 코드를 수정하는 방법 두 가지 방법을 제공.
  • 특징
    • 프록시 객체는 처음 사용할 때 한 번만 초기화
    • 프록시 객체를 초기화한다고 프록시 객체가 실제 엔티티로 바뀌는 것은 아님
    • 프록시 객체가 초기화되면 프록시 객체를 동해서 실제 엔티티에 접근가능
    • 초기화는 영속성 컨텍스트의 도움을 받아야 가능하다. 따라서 영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태의 프록시를 초기화하면 문제가 발생한다. → 하이버네이트의 LazyInitializationException
    • 엔티티를 프록시로 조회할 때 식별자 PK 값을 파라미터로 전달하는데 프록시 객체는 이 식별자 값을 보관한다.
    • 프록시 객체의 초기화 여부 확인 PersistenceUnitUtil.isLoaded(Object entity) 사용 가능

즉시 로딩

  • 엔티티를 조회할 때 연관된 엔티티도 함께 조회한다.
  • 대부분의 JPA 구현체는 즉시 로딩을 최적화하기 위해 가능하면 조인 쿼리를 사용

지연 로딩

  • 연관된 엔티티를 프록시로 조회, 프록시를 실제 사용할 때 초기화하면서 데이터베이스를 조회한다.

JPA 기본 페치 전략

  • 연관된 엔티티가 하나면 즉시 로딩, 컬렉션이면 지연로딩 사용
fetch 속성의 기본 설정값
  • ManyToOne, OneToOne : 즉시 로딩
  • OneToMany, ManyToMany : 지연 로딩
 
⇒ 일단 모든 연관관계에 지연 로딩하는 것을 추천, 개발 완료 단계에서 실제 사용하는 상황을 보고 꼭 필요한 곳에만 즉시 로딩을 사용하도록 최적화

CASCADE

  • 엔티티를 영속화할 때 연관된 엔티티도 같이 영속화하는 편리함 제공

고아 객체

  • 부모 엔티티의 컬렉션에서 자식 엔티티의 참조만 제거하면 자식 엔티티가 자동으로 삭제
  • 참조가 제거된 엔티티는 다른 곳에서 참조하지 않는 고아 객체로 보고 삭제하는 기능 → 참고하는 곳이 하나일 때만 사용해야 함. 만약 삭제한 엔티티를 다른 곳에서도 참조한다면 문제가 발생할 수 있다.
  • 부모를 제거할 때 자식은 고아가 되서 삭제됨. CascadeType.REMOVE
 
JPA Persistence Context Deep Dive
또한, 많은 국문자료에서 JPA를 다루면서 N+1 문제나 엔터티 매핑, 성능 최적화와 관련된 주제는 많이 다루었지만 본질적으로 Persistence Context가 어떻게 동작하는지에 대한 심도 있는 내용은 찾아볼 수 없었습니다.(제가 못 찾은 걸 수도 있습니다..) 그래서 저는 Persistence Context 동작에 대해 다양한 케이스를 구성하고...
https://msolo021015.medium.com/jpa-persistence-context-deep-dive-2f36f9bd6214
JPA Persistence Context Deep Dive