[그림일기 서비스 보고 공부하기] 기본키 연관관계 매핑과 3정규형

오젼·2024년 10월 6일
0

그림일기 서비스에선 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가 아닌 값으로 연관관계 매핑을 하는 것이 정규화 관점에서 권장되지 않는다고 했다. 아래는 김영한님 답변

https://www.inflearn.com/community/questions/113969/joincolumn-의-name-referencedcolumnname-에-대해-질문-드립니다

아래는 클로드 답변

studentNumber를 사용하여 다른 엔티티들과 연관관계를 맺는 것이 반드시 잘못된 방식은 아닙니다. 하지만 일반적으로는 id (PK)를 사용하는 것이 더 권장되는 방식입니다. 그 이유를 몇 가지 설명드리겠습니다:

  • 성능: PK (id)는 데이터베이스에서 자동으로 인덱싱되어 있어 조회 성능이 우수합니다.
  • 불변성: studentNumber는 변경될 가능성이 있지만, id는 보통 변경되지 않습니다. 연관관계의 키가 변경되면 관련된 모든 테이블에서 업데이트가 필요하게 됩니다.

  • 유연성: 나중에 요구사항이 변경되어 studentNumber의 형식이나 의미가 바뀌어야 할 경우, id를 사용하면 영향을 최소화할 수 있습니다.

  • 정규화: id를 사용하면 3정규형을 쉽게 만족시킬 수 있습니다.

휴 유연성 부분에 대해선 지금 뼈저리게 공감되고 있다.. 왜냐면 원래 studentId라고 필드명을 해놨었는데 이게 다른 PK들의 이름과 비슷해서 studentNumber로 필드명을 바꾸려고 했더니 고칠 게 너무 많았기 때문이다..

그리고 정규형에 대해선 처음 알게 되어서 찾아봤다.

3정규형

3정규형은 데이터베이스 설계에서 중요한 개념으로, 데이터의 중복을 최소화하고 데이터 무결성을 보장하는 것을 목표로 한다.

3정규형의 조건은 다음과 같다:

  1. 1정규형을 만족해야 한다 (모든 속성이 원자값이어야 함).
  2. 2정규형을 만족해야 한다 (부분 함수적 종속성이 없어야 함).
  3. 이행적 함수적 종속성이 없어야 한다.

와우 부분 함수적 종속성..? 이행적 함수적 종속성....? ☠️ 여기서부터 무슨 말인지 모르겠어서 다시 정리해봤다.

1정규형 (1NF)

1정규형은 각 열이 원자값(더 이상 분해할 수 없는 값)을 가져야 한다는 규칙이다.

예시: 1정규형을 위반하는 테이블

학생ID이름과목
1김철수수학, 영어
2이영희국어, 과학, 미술

이 테이블은 '과목' 열이 여러 값을 포함하고 있어 1정규형을 위반한다.

1정규형을 만족하는 테이블

학생ID이름과목
1김철수수학
1김철수영어
2이영희국어
2이영희과학
2이영희미술

2정규형 (2NF)

2정규형은 1정규형을 만족하면서, 부분 함수적 종속성이 없는 것이다.

예시를 보면 쉽다. PK가 복합키일 때 생기는 문제를 해결하는 것이다. 복합 PK의 일부분에만 종속된 속성이 있을 때 이를 별도의 테이블로 분리하는 것이다.

예시: 2정규형을 위반하는 테이블

학생ID과목교수학과
1수학박교수공과대학
1영어김교수공과대학
2수학박교수인문대학

(학생ID, 과목)이 복합 키이다. 그런데 '학과'는 '학생ID'에만 종속되므로 부분 함수적 종속성이 존재한다.

2정규형을 만족하는 테이블

학생 테이블:

학생ID학과
1공과대학
2인문대학

수강 테이블:

학생ID과목교수
1수학박교수
1영어김교수
2수학박교수

3정규형 (3NF)

3정규형은 2정규형을 만족하면서, 이행적 함수적 종속성이 없어야 한다. 이행적 함수적 종속성이란 A → B, B → C 일 때 A → C가 성립하는 경우를 말한다.

핵심은 기본키가 아닌 속성이 다른 속성을 결정하는 경우, 그 속성들을 별도의 테이블로 분리하는 것.

후보키(기본키가 될 수 있는 속성)가 있을 때, 해당 속성에 종속되는 다른 속성들을 묶어 별도의 테이블로 분리하는 것.

이행적 함수적 종속성을 찾을 때 A → B → C 인 관계를 찾으면 된다.
A, B로 나누고 B, C로 나누면 B를 가지고 연관관계를 만들 수 있게 됨.

예시: 3정규형을 위반하는 테이블

대출번호책제목저자회원이름회원연락처대출일
001해리포터J.K. 롤링김철수010-1234-56782023-10-01
002반지의 제왕J.R.R. 톨킨이영희010-9876-54322023-10-02
003해리포터J.K. 롤링박지민010-1111-22222023-10-03

이 테이블의 문제점:
1. 책 정보(제목, 저자)가 중복된다.
2. 회원 정보도 매번 반복해서 기록된다.
3. 회원의 연락처가 바뀌면 모든 대출 기록을 수정해야 한다.

3정규형을 만족하는 테이블

책 정보 테이블:

책번호제목저자
B001해리포터J.K. 롤링
B002반지의 제왕J.R.R. 톨킨

회원 정보 테이블:

회원번호이름연락처
M001김철수010-1234-5678
M002이영희010-9876-5432
M003박지민010-1111-2222

대출 기록 테이블 (정규화 후):

대출번호책번호회원번호대출일
001B001M0012023-10-01
002B002M0022023-10-02
003B001M0032023-10-03

그럼 id를 사용하면 왜 3정규형을 쉽게 만족시킬 수 있을까?

id를 사용하여 3정규형을 쉽게 만족시킬 수 있는 이유는 다음과 같습니다:

  1. 유일성: id는 각 레코드를 유일하게 식별합니다. 이는 중복 데이터를 방지하고, 각 엔티티의 독립성을 보장합니다.

  2. 무의미성: id는 보통 자동 생성되는 숫자나 UUID로, 비즈니스 로직과 무관합니다. 이는 데이터 변경 시 발생할 수 있는 이상현상(anomaly)을 줄입니다.

  3. 불변성: id는 한 번 할당되면 변경되지 않습니다. 이는 데이터의 일관성을 유지하는 데 도움이 됩니다.

  4. 단일 컬럼 키: id는 대개 단일 컬럼으로 구성되어 있어, 복합 키를 사용할 때 발생할 수 있는 부분 종속성 문제를 방지합니다.

정리해보니 지금 우리 서비스에서 studentNumber가 수정될 일도 없고 유일하기 때문에 studentNumber를 외래키로 설정해주는 것도 괜찮아 보였다. 하지만! (However! 그러나! But..! Nevertheless...!) 다시 찾아보니 id로 하는 게 최선의 선택 같았다.

studentNumber보다 id를 사용해야 하는 이유

이 상황에서는 User 엔티티의 id (PK)를 참조 컬럼으로 사용하는 것이 더 나은 선택입니다. 그 이유는 다음과 같습니다:

  • 안정성: id는 시스템에 의해 자동 생성되고 변경되지 않는 값입니다. 반면 studentNumber는 비즈니스 로직에 의해 변경될 가능성이 있습니다.
    -> 비즈니스 로직과 분리시키는 것도 고려할 점이구나😮
  • 성능: Long 타입의 id는 인덱싱과 조인 연산에서 더 효율적입니다.
    -> 오.. 맞는 말이다.
  • 추상화: id를 사용하면 실제 비즈니스 데이터(studentNumber)와 시스템 식별자를 분리할 수 있습니다. 이는 시스템 변경 시 유연성을 제공합니다.
  • 일관성: 다른 엔티티들도 대부분 id를 PK로 사용할 것이므로, User 엔티티의 참조도 id로 하는 것이 일관성 있는 설계입니다.
    -> 인정.. 일관성 중요하지
  • JPA 최적화: JPA는 기본적으로 @Id 필드를 기반으로 최적화되어 있습니다.
    -> 아하 최적화 중요하지

0개의 댓글