관계형 데이터 테이블에서 외래키에 대해 알아봅시다.
외래키란 말 뜻 그대로 외부의, 다른 테이블의 키를 말합니다.
한 테이블의 필드가 다른 테이블의 기본 키를 참조할 때 이를 외래키라고 합니다.
예를 들어, 학교와 학생 테이블이 있다고 가정해봅시다.
학교 테이블의 기본 키는 학교ID(학교의 고유 번호)이고 학생이 이를 참조합니다.
즉 학생 테이블에 외래키로 학교ID가 들어가 있게 됩니다.
이를 통해 학생 정보를 보고 학교가 어디인지 알 수 있게 되죠.
CREATE TABLE School ( id INT AUTO_INCREMENT, name VARCHAR(255), address VARCHAR(255), phone VARCHAR(15), PRIMARY KEY (id) );
CREATE TABLE Student ( id INT AUTO_INCREMENT, name VARCHAR(255), school_id INT, PRIMARY KEY (id), FOREIGN KEY (school_id) REFERENCES School(id) // 외래키! );
외래키를 사용하면 데이터 연관 관계를 확실하게 설정할 수 있습니다. 그래서 보다 쉽게 데이터를 조회하거나 분석할 수 있습니다.
또한 존재하는 학교ID만 들어가게 되니 존재하지 않는 학교ID를 작성하는 등 데이터 무결성을 해치지 않게 됩니다
정리하면
한 학교에 여러명의 학생이 속해있고 이러한 관계를 1 : N 관계라고 하며 외래키는 N의 위치에 존재합니다. 그 1을 참조하기 위해서죠.
JPA에서 학교와 학생의 관계를 다시 한번 생각해봅시다.
[학교]
@Entity public class School{ @Id @Generated(strategy = GenerationType.IDENTITY) private Long id; private String name; }
[학생]
@Entity public class Student{ @Id @Generated(strategy = GenerationType.IDENTITY) private Long id; private String name; @ManyToOne @JoinColumn(name = "school_id) //외래키!! private School school }
학생 엔티티에 학교 필드를 추가해서 학교 엔티티를 참조하도록 해줍니다.
즉 RDBMS와 JPA는 서로 같은 목적을 가지지만 서로 다른 접근 방식을 가졌다고 생각할 수 있습니다.
엔티티 연관관계는 단방향과, 단방향이 양쪽으로 연결된 양뱡향 연관관계가 있습니다.
위와 같이 학교와 학생을 @ManyToOne으로만 연결한 관계는 단방향 연관관계
학교 엔티티에서 마찬가지로 @OneToMany로 연결해주면 양방향 연관관계가 됩니다.
양방향 연관관계일 때, 학교 - 학생 관계에서는 어느 엔티티가 주인일까요??
바로 학생이 주인이 됩니다.
뭔가 학교에 학생들이 다니니 학교가 더 큰 의미 아닌가? 싶을 수 있지만 아닙니다.
데이터베이스에서는 테이블간 관계를 표현하기 위해 외래키를 사용합니다. 이 경우, 학생 테이블은 학교 테이블의 Id를 외래키로 참조합니다. 객체 지향적인 프로그래밍에서도 이와 같은 관계가 유지되어야 일관성을 보장할 수 있습니다.
쉽게 말해, 외래키를 가지고 있는 곳이 주인으로 설정됩니다.
다른 예시를 들어 만약 학교가 주인이라고 생각해봅시다.
학교에 500명의 학생들이 다니고 있습니다. 그 중에 한 학생의 정보를 변경해야하는 상황이 발생하게 되면 500명을 모두 로드하여 학생을 찾아 변경해줘야 합니다. 이는 곧 성능의 큰 문제로 직결됩니다.
반면 학생이 주인이라면 학생 본인의 정보를 쉽게 변경하거나, 학교 정보가 변경되더라도 학생에게는 단 한개의 학교만이 연결되어있으니 학교를 쉽게 변경할 수 있습니다.
이러한 관계를 쉽게 나타내기 위해
@ManyToOne @JoinColumn(name = "school_id) private School school
를 통해 학교 엔티티의 school_id를 외래키로 참조한다는 JoinColumn 어노테이션을 사용하며
반대로 학교 엔티티에서는 이와 같이설정합니다.
@Entity public class School{ @Id @Generated(strategy = GenerationType.IDENTITY) private Long id; private String schoolName; @OneToMany(MappedBy = "School") private Student students }
처럼 MappedBy를 통해 주인이 student 이며 student는 school을 참조하고 있음을 알려주게 됩니다.
이처럼 양방향 연관관계에서는 생각보다 신경 쓸 일이 많습니다.
그래서 @ManyToOne만을 사용하고 반대의 경우는 따로 처리해주는 경우가 많습니다.
예를 들어,
학교에서 학생을 @OneToMany로 연관하지 말고
학교 저장소에서 학생을 findById를 통해 찾는 방식을 예로 들 수 있습니다.
이렇게 하면 복잡한 연관관계 대신 직접 학교에 속한 학생을 찾을 수 있지만 cascade 등 여러 처리 기능들을 jpa가 자동으로 처리해주지 않는 단점이 있습니다.