Spring & JPA

[테코톡] JPA 연관관계 최적화

진진리 2025. 4. 3. 13:45

연관관계란?

  • JPA: Java 애플리케이션에서 관계형 데이터베이스의 데이터를 관리하는데 필요한 객체와 관계를 매핑하는 API
  • 엔티티: 데이터베이스 테이블에 대응되는 객체. 각 엔티티는 특정한 데이터 모델을 표현
  • JPA 연관관계: JPA 엔티티 간의 관계를 정의

 

왜 연관관계를 이해해야 할까?

  • 애플리케이션 내 데이터를 효율적으로 구성하기 위해
    • 성능 향상, 데이터 무결성, 데이터 관리, 유지 보수, 견고한 아키텍처 등

 

양방향 @OneToMany

  • 항상 부모 측에서 자식 측으로 전이 사용
  • @OneToMany(cascade = CascaseType.ALL)

 

단방향 @OneToMany

  • 부모 자식 연관관계를 관리하기 위한 연결 테이블 생성
    • 자식 엔티티에 부모의 외래키를 저장할 수 없어 별도의 연결 테이블 생성
  • 연결 테이블로 인한 비효율
    • 메모리 사용 증가 및 성능 저하 가능성
    • 두 개의 외래키 열에 대한 인덱싱, 연결 테이블 자체의 오버헤드, 3개 테이블로 인한 쿼리 복잡성 증가
  • INSERT, DELETE가 작동되는 방식
    • 새로운 크루를 삽입할 때 insert 3개, delete 1개 발생
    • 불필요한 삭제와 삽입 : 새로운 크루 추가를 위해 이미 존재하는 모든 관계를 삭제하고 다시 삽입
    • 부모 엔티티는 자식 엔티티와의 관계를 직접 관리하지 않아 자식 엔티티의 상태를 정확히 파악하기 위한 방법이 제한적
    • 데이터의 일관성을 유지하고 관계 변경을 정확히 감지하기 위해

 

단방향 @OneToMany + @JoinColumn

  • 새로운 크루를 삽입하면 update 호출
  • 단방향 @OneToMany보다 이점 제공. 추가 update문은 여전히 성능 저하를 가져옴

양방향 @ManyToMany

  • 두 테이블이 부모 테이블, 연결 테이블이 자식 테이블

 

항상 List가 아닌 Set 사용하자

  • 캠퍼스와 크루가 List를 통해 처리된다고 가정
    • 삭제 시 연결 테이블을 통해 관리됨
    • delete와 insert문 확인 가능
    • 연결 테이블에서 모든 연결 항목을 삭제한 후 삭제 대상이 아닌 연결 항목을 다시 삽입해 메모리 내의 내용을 반영
  • Set을 사용하는 경우
    • 하나의 delete문만 사용

 

CascadeType.ALL 및 CascadeType.REMOVE 사용하지 말자

  • 대부분의 경우 제거에 대한 전이는 좋은 생각이 아니다.
  • CascaseType.MERGE와 Cascade.PERSIST를 사용

 

연관관계 양측에서 지연 로딩 사용

  • 기본적으로 @ManyToMany는 지연 처리됨
  • 즉시 로딩이 성능저하와 비효율적인 데이터 로딩을 초래
    • N+1 문제

단방향 @OneToOne

  • 부모 엔티티가 자식 엔티티를 식별자를 조회할 수 없음
  • 부모 측이 지속적으로 자식 측을 필요로하면 쿼리 추가로 성능 저하 가능

 

양방향 @OneToOne

  • 지연 로딩 임에도 부모 조회 시 자식도 가져옴 -> 성능 저하 발생
  • One To One 관계 특성 상 자식 엔티티가 없는 경우와 있는 경우의 참조 상태가 중요하기 때문에 추가 검사가 필요
    • 자식 엔티티를 가져오지 않으면 자식 참조null로 할지, Object로 할지 알 수 없음

 

@OneToOne + @MapsId

  • 자식 측에 @MapsId를 추가
  • 자식 테이블은 부모 테이블과 기본키를 공유
  • 이점
    • 자식 엔티티를 추가젹으로 가져오는 부가 쿼리를 자동 호출하지 않음
    • 메모리 사용량이 줄어든다.


 

JPA 연관관계를 최적화한다는게 어렵게 느껴진다.

테스트 코드로 쿼리를 확인해보고 최적화해봐야겠다.