양방향 @OneToOne 연관관계의 문제점
필자는 Spring Data JPA를 사용할 때, 양방향 @OneToOne 연관관계를 사용하는 걸 굉장히 불편해했다. 왜냐하면 부모 엔터티에서 조회하는 쿼리를 발생시키면, 자식 엔터티와 @OneToOne의 FetchType이 LAZY 연관관계 상태로 설정했음에도 불구하고, 자동으로 자식 엔터티를 SELECT 하는 추가 SQL문이 발생했기 때문이다.
해당 문제점 때문에 부모 엔터티만 필요한 경우에도 자식 엔터티를 가져오면서 리소스 낭비와 성능 저하를 초래했다. 그래서 어쩔 수 없이 양방향 @OneToOne 연관관계가 필요할 때, 비즈니스 로직을 고려해서 최대한 호출이 되지 않을 만한 쪽을 부모 엔터티로 설정하려고 했다.
원인
지연 로딩으로 설정해도 항상 즉시 로딩되는 이 문제는 하이버네이트에서 프록시 기능의 한계로 인해 발생한다.
지연 로딩으로 설정이 되어있는 엔티티를 조회할 때는 프록시로 감싸서 동작하게 되는데, 프록시는 null을 감쌀 수 없기 때문에 이와 같은 문제점이 발생하게 된다.
해결 방법
두 가지 방법이 있다. 하나는 부모 엔터티에 Bytecode Enhancement와 @LazyToOne(LazyToOneOption.NO_PROXY)를 사용하는 방법이고, 다른 한 가지 방법은 단방향 @OneToOne과 @MapsId를 사용하는 방법이다.
필자가 추천하는 방법은 '단방향 @OneToOne과 @MapsId를 사용'하는 방법이다.
@MapsId는 JPA에서 제공하는 어노테이션으로, @ManyToOne과 @OneToOne 관계에서 사용된다. @MapsId를 사용하면 다음과 같은 이점을 얻을 수 있다.
- 자식엔터티가 2차 캐시에 있으면 캐시에서 엔터티를 가져온다.
- 부모 엔터티를 조회할 때, 자식 엔터티를 추가적으로 조회하는 쿼리를 발생시키지 않는다.
- PK를 공유하기 때문에, PK와 FK를 모두 인덱싱할 필요가 없으므로 메모리 사용량이 감소한다.
@MapsId를 사용한 최적화 방법은 다음과 같다.
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
}
@Entity
public class Profile {
@Id
private Long id;
@MapsId
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private User user;
}
위의 예제에서 Profile 엔티티는 User 엔티티의 기본 키를 자신의 PK로 사용한다. @MapsId 어노테이션을 사용하여 Profile 엔티티의 id 필드를 User 엔티티의 id 필드와 매핑한다. 이를 통해 두 엔티티 간의 관계를 최적화할 수 있다.
이와 같이 @MapsId를 사용하면, 두 엔티티 간의 관계를 단순화하고, 데이터베이스의 성능을 향상시킬 수 있다. 또한, 데이터의 일관성을 유지하는 데 도움이 된다. 따라서 양방향 @OneToOne 관계에서 발생할 수 있는 문제점을 효과적으로 해결할 수 있다.
'Spring > JPA' 카테고리의 다른 글
@ManyToOne 연관관계에서 부모 엔터티를 등록할 때 프록시 호출을 통해 JPA 최적화 하기 (0) | 2024.10.22 |
---|---|
CascadeType.REMOVE, orphanRemoval=true 특징 (1) | 2024.10.20 |
JPA 효과적인 양방향 연관관계 구성방법 (0) | 2024.10.16 |
JPA Buddy 플러그인을 이용해서 엔티티에 equals와 hashcode를 명시적으로 구현해야 하는 이유 (0) | 2024.06.22 |
[Querydsl] @OneToMany에서 조건 절과 Fetch Join을 함께 사용할 때 서브쿼리 사용하기 (0) | 2024.05.12 |
댓글