주 테이블에 외래키
주체 Entity(멤버)가 직접 FK를 관리하는 상태. 특징은 다음과 같다.
- 객체 지향 개발자가 선호한다.
- JPA 매핑이 편리하다.
- 주 테이블만 조회해도 연관 테이블의 데이터를 함께 확인할 수 있다.
- 단점으로는 외래키 값이 없으면 null을 허용한다.
- optional 설정으로 극복 가능(?)
일단 null을 허용하는 것이 DBA 입장에서 바라보았을 때 꽤나 치명적일 것이라고 영한님께서 말씀해주셨다.
그러면 애플리케이션 단에서 @NotNull 같은 Validation으로 null을 예방하면 되지 않을까? 라고 생각이 들었다.하지만 그렇다고 해서 DB 컬럼에 NOT NULL 제약이 생기는 것은 아니므로 여전히 잠재적인 위험이 있다고 보는 것 같다.
그리고 외래키에 UNIQUE 속성이 적용된다는데 내가 하는 테스트에서는 어째서 적용이 안 되는 건지 잘 모르겠다. 아무리 봐도 그냥 UNIQUE 속성이 기본으로 적용되는 것은 "절대" 아니다.
내가 진행한 테스트는 위에 멤버(를 Owner 클래스로 대체)와 Locker를 이용하여 다음과 같이 진행했다.
// Owner Class
public class Owner {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String name;
@OneToOne(fetch = FetchType.LAZY)
private Locker locker;
}
// TEST
Locker locker = lockerRepository.save(
Locker.builder()
.name("locker")
.build()
);
Owner owner = ownerRepository.save(
Owner.builder()
.name("member")
.locker(locker)
.build()
);
owner = ownerRepository.save(
Owner.builder()
.name("member")
.locker(locker)
.build()
);
만약 UNIQUE 속성이 기본으로 적용된다면 일단 두 번째 Owner를 저장할 때 Duplicate 문제가 발생했어야 하는데 발생하지 않았다.
그럼 언제 UNIQUE 속성이 적용되는가? 하고 확인해 본 결과 OneToOne의 Optional 값을 false로 설정하면 UNIQUE 제약이 자동으로 적용된다. (기본 값은 true이다) 그리고 테스트는 UNIQUE 제약 조건에 의해서 실패한다.
MySQL에 생성된 테이블도 확인해보았다.
왜 optional에 따라서 UNIQUE 제약사항이 다르게 적용되는 걸까? 일단 UNIQUE 제약 조건에 대한 SQL 표준을 찾아보았다.
SQL 표준에서는 DBMS가 선택적 기능인 ‘널 허용’을 구현하지 않았다면 UNIQUE 제약 조건에 종속되는 컬럼이나 컬럼 집합은 NOT NULL 제약 조건에 종속해야 한다고 명시한다. UNIQUE 제약 조건에 추가된 일부 선택적 기능은 다음과 같다.
• UNIQUE 제약 조건이 있는 컬럼에는 NOT NULL 제약 조건이 있어야 하지만, 꼭 그럴 필요는 없다.
• UNIQUE 제약 조건이 있는 컬럼에 NOT NULL 제약 조건이 없다면 이 컬럼은 널 값을 여러 개 가질 수 있다(NULL <> NULL이라는 사실의 논리적인 결과다).
그리고 인프런에 질문을 했다.
https://www.inflearn.com/questions/637633?re_comment_id=209562
답변해주신 분과 이야기를 나누어보니 JPA가 범용성 있는 설계를 위해서 이러한 선택을 했다는 것이 나름 합리적인 추측인 것 같다고 생각했다. 뭐 어쨌든 optional 여부에 따라서 나도 모르게 UNIQUE 제약이 적용되지 않을 수도 있기 때문에 이를 알고 사용하는 것이 좋을 거라고 생각한다.
그리고 OneToOne의 가장 큰 핵심은 Owner가 단 하나의 Locker를 가져야 한다는 것이다. 두 명의 Owner가 존재할 수 없고 그렇기 때문에 UNIQUE 제약이 추가되어야 하는 것이다. 이런 관계가 아니라면 OneToOne을 사용하지 않는 것이 맞다. 그렇기 때문에 정말 특별하지 않는 경우 OneToOne을 쓸 경우가 별로 없다고 한다.
대상 테이블에 외래키
대상 Entity(Locker)가 직접 FK를 관리하는 상태. 특징은 다음과 같다.
- 대상 테이블에 외래 키가 존재
- 이런 구조는 일대일 단방향 관계에서는 불가능하다. 양방향 관계만 가능하다.
- 전통적인 데이터베이스 개발자가 선호한다.
- 주 테이블과 대상 테이블을 일대일에서 일대다 관계로 변경할 때 테이블 구조를 유지할 수 있다.
- 사실 현재 테이블 구조가 일대다 단방향 관계와 매우 유사하다
- 단점으로는 프록시 기능의 한계 때문에 지연 로딩으로 설정해도 항상 즉시 로딩된다.
- 왜냐하면 Member 프록시 객체를 만들 때 Locker의 존재 유무를 알아야 null을 넣든 locker 프록시 객체를 넣든 할 텐데 현재 Member 테이블 구조에서는 Locker의 존재 유무를 알 수가 없다. 따라서 Locker를 찾는 쿼리가 무조건 발생하게 되고 지연 로딩의 의미가 없으므로 이런 경우 즉시 로딩만 가능하다.
'JPA' 카테고리의 다른 글
JPA 상속관계 매핑 (0) | 2022.09.01 |
---|---|
같은 클래스와 다대일 양방향 관계 맺기 (0) | 2022.09.01 |
JPA fetch join 대상의 별칭 사용과 주의사항 (0) | 2022.08.16 |
fetch join과 paging 시 OOM 문제 (OneToOne은 괜찮지) (0) | 2022.08.10 |
JPA에서 발생할 수 있는 문제들과 해결방법 정리 (N + 1, 무작위 JOIN..) (0) | 2022.08.09 |