그림일기 서비스에선 refrencedColumnName이 사용되지 않았다. 연관관계 매핑을 할 때 모두 PK로 연결을 해준 것이다.
하지만 우리 서비스에선 그렇지 않았다.
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class User extends BaseEntityWithUpdate {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotNull
@Column(nullable = false, unique = true)
private String studentNumber;
@NotNull
@Column(nullable = false)
private String password;
@Enumerated(EnumType.STRING)
private UserRole userRole;
private String refreshToken;
public User(String studentNumber, String password) {
this.studentNumber = studentNumber;
this.password = password;
this.userRole = UserRole.USER;
}
public void updateRefreshToken(String refreshToken) {
this.refreshToken = refreshToken;
}
}
User entity가 이런식으로 되어 있고 다른 곳에서
@ManyToOne
@JoinColumn(name = "student_number", referencedColumnName = "studentNumber")
private User user;
refrencedColumnName을 사용해서 PK인 id가 아닌 studentNumber를 가지고 연관관계 매핑을 해줬었다.
그런데 찾다보니 PK가 아닌 값으로 연관관계 매핑을 하는 것이 정규화 관점에서 권장되지 않는다고 했다. 아래는 김영한님 답변
아래는 클로드 답변
studentNumber를 사용하여 다른 엔티티들과 연관관계를 맺는 것이 반드시 잘못된 방식은 아닙니다. 하지만 일반적으로는 id (PK)를 사용하는 것이 더 권장되는 방식입니다. 그 이유를 몇 가지 설명드리겠습니다:
불변성: studentNumber는 변경될 가능성이 있지만, id는 보통 변경되지 않습니다. 연관관계의 키가 변경되면 관련된 모든 테이블에서 업데이트가 필요하게 됩니다.
유연성: 나중에 요구사항이 변경되어 studentNumber의 형식이나 의미가 바뀌어야 할 경우, id를 사용하면 영향을 최소화할 수 있습니다.
정규화: id를 사용하면 3정규형을 쉽게 만족시킬 수 있습니다.
휴 유연성 부분에 대해선 지금 뼈저리게 공감되고 있다.. 왜냐면 원래 studentId라고 필드명을 해놨었는데 이게 다른 PK들의 이름과 비슷해서 studentNumber로 필드명을 바꾸려고 했더니 고칠 게 너무 많았기 때문이다..
그리고 정규형에 대해선 처음 알게 되어서 찾아봤다.
3정규형은 데이터베이스 설계에서 중요한 개념으로, 데이터의 중복을 최소화하고 데이터 무결성을 보장하는 것을 목표로 한다.
3정규형의 조건은 다음과 같다:
와우 부분 함수적 종속성..? 이행적 함수적 종속성....? ☠️ 여기서부터 무슨 말인지 모르겠어서 다시 정리해봤다.
1정규형은 각 열이 원자값(더 이상 분해할 수 없는 값)을 가져야 한다는 규칙이다.
학생ID | 이름 | 과목 |
---|---|---|
1 | 김철수 | 수학, 영어 |
2 | 이영희 | 국어, 과학, 미술 |
이 테이블은 '과목' 열이 여러 값을 포함하고 있어 1정규형을 위반한다.
학생ID | 이름 | 과목 |
---|---|---|
1 | 김철수 | 수학 |
1 | 김철수 | 영어 |
2 | 이영희 | 국어 |
2 | 이영희 | 과학 |
2 | 이영희 | 미술 |
2정규형은 1정규형을 만족하면서, 부분 함수적 종속성
이 없는 것이다.
예시를 보면 쉽다. PK가 복합키일 때 생기는 문제
를 해결하는 것이다. 복합 PK의 일부분에만 종속된 속성이 있을 때
이를 별도의 테이블로 분리하는 것이다.
학생ID | 과목 | 교수 | 학과 |
---|---|---|---|
1 | 수학 | 박교수 | 공과대학 |
1 | 영어 | 김교수 | 공과대학 |
2 | 수학 | 박교수 | 인문대학 |
(학생ID, 과목)이 복합 키이다. 그런데 '학과'는 '학생ID'에만 종속되므로 부분 함수적 종속성이 존재한다.
학생 테이블:
학생ID | 학과 |
---|---|
1 | 공과대학 |
2 | 인문대학 |
수강 테이블:
학생ID | 과목 | 교수 |
---|---|---|
1 | 수학 | 박교수 |
1 | 영어 | 김교수 |
2 | 수학 | 박교수 |
3정규형은 2정규형을 만족하면서, 이행적 함수적 종속성
이 없어야 한다. 이행적 함수적 종속성이란 A → B, B → C 일 때 A → C가 성립하는 경우를 말한다.
핵심은 기본키가 아닌 속성이 다른 속성을 결정하는 경우
, 그 속성들을 별도의 테이블로 분리하는 것.
후보키(기본키가 될 수 있는 속성)가 있을 때
, 해당 속성에 종속되는 다른 속성들을 묶어 별도의 테이블로 분리하는 것.
이행적 함수적 종속성을 찾을 때 A → B → C 인 관계를 찾으면 된다.
A, B로 나누고 B, C로 나누면 B를 가지고 연관관계를 만들 수 있게 됨.
대출번호 | 책제목 | 저자 | 회원이름 | 회원연락처 | 대출일 |
---|---|---|---|---|---|
001 | 해리포터 | J.K. 롤링 | 김철수 | 010-1234-5678 | 2023-10-01 |
002 | 반지의 제왕 | J.R.R. 톨킨 | 이영희 | 010-9876-5432 | 2023-10-02 |
003 | 해리포터 | J.K. 롤링 | 박지민 | 010-1111-2222 | 2023-10-03 |
이 테이블의 문제점:
1. 책 정보(제목, 저자)가 중복된다.
2. 회원 정보도 매번 반복해서 기록된다.
3. 회원의 연락처가 바뀌면 모든 대출 기록을 수정해야 한다.
책 정보 테이블:
책번호 | 제목 | 저자 |
---|---|---|
B001 | 해리포터 | J.K. 롤링 |
B002 | 반지의 제왕 | J.R.R. 톨킨 |
회원 정보 테이블:
회원번호 | 이름 | 연락처 |
---|---|---|
M001 | 김철수 | 010-1234-5678 |
M002 | 이영희 | 010-9876-5432 |
M003 | 박지민 | 010-1111-2222 |
대출 기록 테이블 (정규화 후):
대출번호 | 책번호 | 회원번호 | 대출일 |
---|---|---|---|
001 | B001 | M001 | 2023-10-01 |
002 | B002 | M002 | 2023-10-02 |
003 | B001 | M003 | 2023-10-03 |
id를 사용하여 3정규형을 쉽게 만족시킬 수 있는 이유는 다음과 같습니다:
유일성: id는 각 레코드를 유일하게 식별합니다. 이는 중복 데이터를 방지하고, 각 엔티티의 독립성을 보장합니다.
무의미성: id는 보통 자동 생성되는 숫자나 UUID로, 비즈니스 로직과 무관합니다. 이는 데이터 변경 시 발생할 수 있는 이상현상(anomaly)을 줄입니다.
불변성: id는 한 번 할당되면 변경되지 않습니다. 이는 데이터의 일관성을 유지하는 데 도움이 됩니다.
단일 컬럼 키: id는 대개 단일 컬럼으로 구성되어 있어, 복합 키를 사용할 때 발생할 수 있는 부분 종속성 문제를 방지합니다.
정리해보니 지금 우리 서비스에서 studentNumber가 수정될 일도 없고 유일하기 때문에 studentNumber를 외래키로 설정해주는 것도 괜찮아 보였다. 하지만! (However! 그러나! But..! Nevertheless...!) 다시 찾아보니 id로 하는 게 최선의 선택 같았다.
이 상황에서는 User 엔티티의 id (PK)를 참조 컬럼으로 사용하는 것이 더 나은 선택입니다. 그 이유는 다음과 같습니다: