본문 바로가기
데이터 접근 기술

OneToOne 연관관계에 대한 고민.

by oncerun 2023. 2. 9.
반응형

1:1 관계를 풀어내기 위해 엔티티 간의 연관관계를 설정했을 때 보통 둘 중 하나의 엔티티가 연관관계의 주인이 된다.

 

하지만 이러한 매핑은 효율적이지 않다.

 

자식 테이블은 별도의 PK가 존재하고, FK 칼럼도 존재하게 된다.

 

엔티티는 부모와 자식이 단 하나의 관계만 갖기때문에, 부모의 PK를 미러링 하는 것이 더 나은 선택이 될 수 있다.

 

이 방법에는 자식 테이블의 PK 값이 곧 FK값이 된다. 그리고 두 개의 테이블은 PK를 공유하게 된다.

 

PK와 FK 칼럼은 자주 인덱스를 생성하게 됩니다. 위와 같은 설정은 저장공간을 절반으로 감소시킵니다. 이는 인덱스 스캐닝의 속도 향상을 하기 원한다면 매우 바람직한 방법입니다.

 

단방향의 @OneToOne 관계는 fetch 전략을 Lazy 하게 가져올 수 있습니다.

 

그렇지만 양방향의 관계에서 부모측은 그렇지 않습니다.

 

부모와 자식간의 연관관계가 선택적인 상황이라면 우리는 FetchType.Lazy를 설정하기 마련입니다.

 

@OneToOne관계에서 부모 측은 FetchType.Lazy 설정을 했지만 실제 동작은 FetchType.EAGER로 동작하게 됩니다.

FetchType.EAGER은 여러 가지 이유로 사용되지 않습니다.

JPA and Hibernate FetchType EAGER is a code smell - Vlad Mihalcea

 

JPA and Hibernate FetchType EAGER is a code smell - Vlad Mihalcea

Learn why using EAGER fetching associations is bad for performance when using JPA and Hibernate. Associations should be set to LAZY fetching by default.

vladmihalcea.com

대표적으로는 부모 엔티티 조회시 자식 엔티티를 가져오기 위해 2개의 SQL statement가 발생한다는 것입니다.

  • 연관관계 설정 시 optional이라는 속성이 존재합니다. 이 속성은 연관관계가 항상 존재해야 한다. (Not Null) 임을 나타냅니다. default 값은 true로 null을 허용합니다.

 

모든 엔티티를 관리하는 역할을 하는 Persistence Context는 다음의 속성들을 필요로 합니다.

 

엔티티의 Type, 식별자, 추가적으로 자식의 식별자까지 부모 엔티티를 로딩하는 시점에 알아야 합니다.

 

실제 JPA의 연관관계를 Lazy 하게 설정하고 해당 연관관계 객체를 검사해 보면 해당 객체는 프락시 객체입니다.

 

프락시 객체는 항상 null 아닌 객체 참조를 반환해야 합니다. 1:1 관계의 경우 이를 보완할 수 있는 수단이 없습니다. 다대일, 다대다 관계 경우에는 배열이라는 객체 참조를 반환할 수 있습니다.

 

배열 안에 객체 참조가 null 값을 가지든 엔티티가 있든 배열이라는 형태의 객체를 반환할 수 있기 때문에 일대일 관계와 다르게 동작하는 것입니다.

 

이를 모르고 OneToOne 관계를 LAZY 설정하고 부모 엔티티만을 사용해도 의도하지 않은 2개의 SQL select Statement가 발생합니다.

 

가장 효율적인 매핑 방법 소개.

 

가장 효과적으로 @OneToOne 관계를 사용하는 방법은 @MapsId를 사용하는 방법입니다.

 

이 방법은 부모 엔티티의 식별자를 사용하여 항상 자식 엔티티를 가져올 수 있기 때문에 양방향 연결이 필요하지 않습니다.

@Entity(name = "PostDetails")
@Table(name = "post_details")
public class PostDetails {

    @Id
    private Long id;

    @Column(name = "created_on")
    private Date createdOn;

    @Column(name = "created_by")
    private String createdBy;

    @OneToOne(fetch = FetchType.LAZY)
    @MapsId
    private Post post;

    public PostDetails() {}

    public PostDetails(String createdBy) {
        createdOn = new Date();
        this.createdBy = createdBy;
    }

    //Getters and setters omitted for brevity
}

추가적으로 양방향 연결관계를 끊기 위해 @MapsId 방법을 사용할 수 있지만 다른 방법으로는

 

부모 엔티티에 외래키를 가지는 구조에서 단방향 연관관계를 설정할 수도 있습니다.

 

하지만 이를 DB입장에서 생각해 보면 이미 설계된 테이블에서 자식 엔티티의 외래키 필드를 추가해야 합니다.

 

이러한 구조 변경이 불가능하고 신규 엔티티가 필요한 경우 @MapsId를 사용하는 방법이 가장 깔끔할 수 있습니다.

 

저도 이러한 요구사항이 들어와 있어 고민을 많이 했습니다.

 

우리가 OneToOne 관계를 고민한다면 이는 다양한 가능성을 염두에 둔 것이라고 생각합니다.

 

우선 OneToOne 관계가 OneToMany관계로 발전될 가능성을 체크해 보았습니다. 별도의 엔티티로 구성하는 것이 과연 올바른지 확인해 봅니다.

 

반드시 OneToOne관계가 필요할지 고민해 보았습니다.

 

그런데 역설이 존재합니다. 밸류 객체로 취급해도 정말 다양한 이슈가 발생할 수 있다는 것입니다.

밸류 객체로 인한 여러 가지 성능 이슈와 불변 객체를 놓치면 발생하는 이슈 등등..

 

저는 다음과 같이 선택을 했습니다.

 

@MapsId를 사용하기로 했습니다.

 

자식 엔티티의 외래키를 부모 엔티티에 두는 순간 실제 데이터베이스의 칼럼이 추가됩니다. 객체 입장에서는 속성이 추가됩니다.

 

이 경우 단순 기능이 추가되는 것이 아닌 기존의 것이 수정된 것이기 때문에 이에 영향받는 모든 코드와 테스트코드를 수정해야 합니다.

 

이 비용보다 자식 엔티티가 필요할 때 부모 PK 값으로 자식을 조회하는 것이 더 저렴한 비용이라고 생각했습니다.

 

또한 성능적인 부분에서도 이러한 쿼리와 네트워크 트래픽은 성능에 큰 영향을 미치지 않고 요청도 그리 많지 않을 것이라고 생각했습니다.

 

밸류 객체를 엔티티로 승격시키고 불변객체처럼 취급하여 사용할 것입니다.

 

확장 가능성이 없다고 판단했고 멀티 모듈로 분리되는 과정이 있다면 이를 부모 엔티티에 포함시켜서 리팩토링 하고 배포할 것입니다.

 

그리고 부모 엔티티에서 자식 엔티티를 포함해야 하는 요청이 많은지 확인해 보았습니다.

 

해당 요청은 매우 적을 것으로 예상되며 자식 엔티티만을 조회하는 경우가 많을 것 같아서 부모 PK를 받아 자식 엔티티를 반환하도록 구현할 생각입니다.

 

따라서 OneToOne의 연관관계를 지정할 것입니다.

 

아시다시피 엔티티 간의 연관관계를 효율적으로 구성하는 것은 애플리케이션 성능에 많은 영향을 미칩니다.

JPA의 구현체로 Hibernate를 사용한다면 OneToOne 연관관계에서는 항상 부모 테이블과 PK를 공유해야 합니다.

그렇기에 Hinbernate의 bytecode enhancement를 사용하지 않는다면 양방향 연관관계를 항상 피해야 합니다. (라고 추천하네요)

 

https://vladmihalcea.com/the-best-way-to-map-a-onetoone-relationship-with-jpa-and-hibern ate/

 

The best way to map a @OneToOne relationship with JPA and Hibernate - Vlad Mihalcea

Learn the best way to map a OneToOne association with JPA and Hibernate when using both unidirectional and bidirectional relationships.

vladmihalcea.com

 

반응형

'데이터 접근 기술' 카테고리의 다른 글

Kotlin, Querydsl 설정  (0) 2023.07.08
Querydsl Projections  (0) 2023.02.09
Querydsl 기본(3)  (0) 2023.02.07
Querydsl 기본(2)  (0) 2023.02.06
QueryDSL 적용 방법 알아보기.  (0) 2023.02.06

댓글