공부함
JPA에서 식별 관계, 비식별 관계 중 무엇을 사용해야 할까? 본문
최근 ERD를 설계하면서 외래 키를 구성할 때 해당 테이블이 외래 키 없이 식별될 수 있으면 비식별 관계로, 외래 키가 식별에 필요하다면 식별 관계로 설계했다. 이후 JPA를 위해 entity 매핑을 하면서 설계가 별로라고 느껴져서 식별 관계, 비식별 관계에 대해 알아보고, JPA에서는 어떻게 사용하는지 알아보았다.
외래 키 Foreign Key
다른 테이블의 PK를 참조하는 속성을 외래 키라 한다. 외래 키는 테이블 간 관계를 맺는 데 사용된다. 외래 키가 속한 테이블을 자식 테이블, 외래 키 값을 제공하는 테이블이 부모 테이블이다. 외래 키로 맺는 관계는 식별 관계, 비식별 관계가 있다.
식별 관계, 비식별 관계
식별 관계
식별관계는 ERD상에서 실선으로 표시한다. FK가 자식 테이블의 PK에 포함된다. player 테이블에서 PK는 복합 키 (id, team_id)가 된다. 또한 team_id는 FK이면서 PK가 된다. PK는 null값을 가질 수 없으므로 team이 없다면 player가 생성될 수 없다.
비식별 관계
비식별관계는 ERD상에서 점선으로 표시한다. FK가 자식 테이블에서 PK로 사용되지 않는다. 단순히 부모 테이블의 PK를 참조하는 것 외의 의미는 없는 것이다. player는 PK인 id로 식별된다. team_id가 nullable이라면 player는 team이 없더라도 team_id값을 null로 준다면 독립적으로 생성될 수 있고 이것은 선택적 비식별 관계다. team_id가 null이 될 수 없다면 필수적 비식별 관계가 된다.
장단점
위 예시에서는 player 테이블에 대리키를 사용해 와닿지 않지만 식별 관계를 사용하면 데이터 정합성을 DB에서 체크할 수 있다.
자연 키 vs 대리 키
기본 키로 자연 키와 대리 키 중 어느 것을 선택해야 할까?
자연 키
데이터 자체의 고유한 특성을 기반으로 레코드를 고유하게 식별하는 속성이다. 예를 들어 사람마다 고유한 값을 갖는 주민등록번호나 직원마다 고유한 값을 갖는 사원번호 등이 사람과 사원을 유일하게 식별하므로 자연 키로 쓰일 수 있다.
대리 키
자연 키와 달리 인위적으로 생성된 식별자다. 본질적인 의미가 없으며 주로 자동 증가 정수나 GUID로 구현된다.
대리 키의 장점
- 비즈니스 로직에서 분리된다. 비즈니스 요구사항의 변경으로 기본 키가 변경되지 않는다. 따라서 데이터 무결성이 향상된다. 자연 키 수정이나 중복으로 발생하는 문제가 없다.
- 성능이 향상된다. 이름, 주소와 같은 자연 키에 비해 자동 증가 정수인 대리 키는 빠른 데이터 검색 및 인덱스 생성이 가능하다. 쿼리 성능, 시스템 효율이 향상된다.
결론
대리 키는 자연 키가 수정되거나 중복 값이 존재하는 경우 발생하는 이상 현상을 예방할 수 있으며, 자연 키에 비해 성능과 가독성 측면에서도 뛰어나다!
예를 들어 player의 기본 키가 (back_number, team_name)이고 team_name이 FK이자 PK라고 해보자. 기본 키는 중복될 수 없기 때문에 (21, 컴공FC)인 데이터가 중복해서 INSERT 될 수 없다.
하지만 식별 관계에서는 요구사항이 변하면 수용하기 어렵다. 예를 들어 팀 내 등번호가 중복되는 선수가 존재할 수 있도록 요구사항이 변한다면 테이블의 데이터나 구조를 전부 변경해야 하는 문제가 발생한다. 따라서 테이블을 설계할 때는 비식별 관계로 설계하는 것이 권장된다. 비식별 관계로 변경하고 대리 키 player_id를 PK로 갖고 team_name을 FK로 갖는다면 UPDATE를 통해 back_number나 team_name을 변경할 수 있기 때문에 요구사항 변경에 비교적 유연하게 대처할 수 있다.
JPA에서 식별 관계, 비식별 관계
대리 키와 식별 관계를 함께 사용하게 되면 PK는 (대리 키, 외래 키1, 외래 키2, .. )의 형태가 되므로 복합 키가 된다. JPA에서 복합 키는 별도의 식별자 클래스를 만들어 사용할 수 있다. 영속성 컨텍스트는 기본 키로 엔티티를 관리하므로 식별자 클래스는 Serializable을 구현하고 equals와 hashCode를 오버라이딩 해야 한다. JPA에서는 복합 키를 지원하기 위해 데이터베이스에 맞춘 @IdClass와 객체지향적인 @EmbeddedId 2가지 방식을 제공한다. 식별 관계와 비식별 관계에서 복합 키를 매핑하는 자세한 방법은 2번째 출처를 확인하자. 아래는 @EmbeddedId를 활용한 간단한 예시이다.
@Embeddable
public class PlayerPositionKey implements Serializable {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "player_id")
private Player player;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "position_id")
private Position position;
// hashCode, equals 오버라이딩
}
@Entity(name = "player_position")
public class PlayerPosition {
@EmbeddedId
private PlayerPositionKey playerPositionKey;
}
비식별 관계를 사용해야 하는 이유
그래서 뭘 사용하냐고 묻는다면 주로 비식별 관계를 사용하고 필요한 경우에만 식별 관계를 사용한다.
- 식별관계를 사용하면 자식의 자식 테이블로 갈수록 복합 키 컬럼이 많아져 SQL 성능이 저하된다.
- 귀찮고 복잡하게 복합 키 클래스를 만들어야 한다.
- 비즈니스 요구사항과 관련 없는 대리 키만 PK로 사용하는 것이 좋다. JPA에서는
@GenereatedValue같은 편리한 대리 키 생성 방법을 제공한다. 심지어 PK가 복합 키가 아니라 단일 컬럼이어서 매핑하기 편리하다. - 비식별 관계를 사용한다면 NULL을 허용하지 않는 필수적 비식별 관계를 사용하는 것이 좋다. 왜냐하면 부모 테이블, 자식 테이블을 내부조인 할 수 있기 때문이다. (내부조인은 조인하는 대상 테이블 둘 다에 속성값이 있어야 한다)
결론
PK는 Long 타입 대리 키를 사용하면서 FK는 비식별 관계로 설계하자. 가능하면 필수적 비식별 관계로 설계해 내부조인을 사용할 수 있게 하자
나는 식별 관계 + 일부 테이블에서만 대리 키 사용하도록 설계해 엔티티 매핑이 매우 거지같은 설계를 했는데, 지금까지 왜 팀원이 Long 타입 대리키를 무조건 사용했는지 알게 되었다. 나두 db 설계를 바꿔 깔끔하고 이쁜 엔티티 매핑으로 수정해봐야겠다!!!
출처
https://deveric.tistory.com/108
https://bamdule.tistory.com/45
'데이터베이스' 카테고리의 다른 글
Data의 Scale Out (0) | 2024.04.28 |
---|---|
Java의 DB 접근 방법 (0) | 2023.12.11 |