OneToOne(일대일) 관계의 종류
- 주 테이블이 외래 키를 가지는 단방향
- 주 테이블이 외래 키를 가지는 양방향
- 대상 테이블이 외래 키를 가지는 양방향
- (주의) JPA는 대상 테이블이 외래 키를 가지는 단방향을 지원하지 않습니다!
이하부터 주 테이블은 User, 대상 테이블은 Inventory를 기준으로 합니다.
1. 주 테이블이 외래 키를 가지는 단방향
public class User { @OneToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_inventory_id", referencedColumnName = "inventory_id") //user_inventory_id는 외래키 이름, inventory_id는 Inventory 엔티티의 참조할 칼럼명 private Inventory inventory; }이 경우 User 객체를 통하여 연결된 Inventory 객체에 접근할 수 있습니다.
접근을 Lazy로 하기 때문에, Inventory는 프록시 형태로 조회되며, 접근시 쿼리를 날리게 됩니다.
이 때, 외래 키인 user_inventory_id는 null값이 허용되기 때문에 DB에 치명적인 문제점을 가질 수 있습니다.
따라서 이러한 문제점을 해결하기 위해서는 nullable이 허용되지 않도록 항상 주 테이블 객체와 연결된 대상 테이블 객체가 생성되어 있도록 하여야 합니다.2. 주 테이블이 외래 키를 가지는 양방향
1번의 경우에서 대상 테이블이 되는 Inventory 엔티티에 아래와 같은 코드만 추가하면 됩니다.
public class Inventory { @OneToOne(mappedBy = "inventory") private User user; }mappedBy를 선언하는 테이블은 양방향 관계에서 대상 테이블이라고 보시면 됩니다!
하지만 1대1 양방향 관계는 언제나 fetch가 Lazy가 적용되지 않고 Eager로 적용된다는 단점이 있습니다.
이는 아래에 제가 공부한 내용을 자세히 서술하여 보겠습니다.3. 대상 테이블이 외래 키를 가지는 양방향
User 테이블
public class User { @OneToOne(mappedBy = "user") private Inventory inventory; }Inventory 테이블
public class Inventory { @OneToOne(fetch = FetchType.LAZY) @JoinColumn(name = "inventoty_user_id", referencedColumnName = "user_id") private User user; }언제나 외래 키를 가지고 있는 테이블이 연관 관계의 주인이 되기 때문에, 이 관계는 대상 테이블이 주인인 관계라고 보시면 됩니다.
이 또한 1대1 양방향 관계이기 때문에 fetch를 Lazy로 설정하여도 Eager로 작동한다는 단점이 있습니다.4. fetch가 Lazy로 적용되지 않는 이유
먼저, Lazy(지연 로딩)과 Eager(즉시 로딩)에 관한 공부내용은 다음의 링크에 있습니다. [JPA] Lazy와 Eager
결과부터 말씀드리면, 일대일 양방향 관계에서 연관 관계의 주인이 아닌 쪽 엔티티를 조회할 때, Lazy로 동작할 수 없습니다.
Lazy로 조회하려면 JPA에서 프록시를 만들어 줘야 하는데, JPA 구현체는 조회하려는 엔티티의 객체가 null이거나 프록시여야 하기 때문에 외래 키를 통하여 연결된 테이블의 객체가 존재하는지 안하는지 알 수 있는 주인 관계의 테이블과 달리, 주인이 아닌 테이블은 연결된 테이블의 객체가 존재하는지 확인할 방법이 없어 존재 확인 쿼리가 실행됩니다.5. 외래 키의 Nullable 문제
위에서 설명한 이유와 근사하게 주 테이블이 주인 관계인 경우 외래 키는 Null값이 허용됩니다. (대상 테이블이 주인 관계인 경우 주 테이블 객체는 무조건 존재하기 때문에 Nullable 문제가 생기지 않습니다.)
따라서 이를 방지하기 위해서는 Nullable을 허용하지 않기 위해 주 테이블의 객체가 생성될 때 대상 테이블의 객체는 이미 생성되었거나, 같이 생성되어야 합니다.결론
결론은 제 생각일 뿐이지만, 외래 키의 Nullable 문제와 Lazy fetch가 적용되지 않는 문제를 모두 해결하려면 2가지의 조건이 필요하다고 생각합니다.
1. 주 테이블이 외래키를 가지는 단방향 관계
2. 주 테이블 객체에 연결되어 있는 대상 테이블의 객체는 항상 생성되어 있어 Nullable하지 않도록 유지
이외에 해결책으로 Fetch Join이 존재하는데, 이는 추후에 공부해 보도록 하겠습니다.
결론은 순전히 제 생각으로, 틀린 점이 있다면 댓글로 지적 부탁드리겠습니다!