✌🏻 0. 들어가기 전
JPA 강의 중에 @JoinColumn을 마스터하면 JPA 연관관계를 어느정도는 다 알 수 있다고 들었다.
그래서, 여러 JPA 개념 중에 @JoinColumn에 대해 파헤쳐보면서 JPA 연관관계를 이해해보려고 한다!
앞으로 설명할 연관관계에서 연관관계별로 다음과 같은 예제로 설명하겠다!
1:1 - student <-> lecture
1:N / N:1 - Line(1) <-> Station(N)
🎯 1. @JoinColumn 이란?
JoinColumn 어노테이션 클래스를 번역해보면서 이해해보자.
JoinColumn의 최상단 주석에는 JoinColumn에 대한 설명이 나와있다.
맨 위의 내용을 요약하면 다음과 같다.
Entity 연관관계 또는 Element Collection을 연결하기 위한 Column을 지정한다.
JoinColumn이 사용되는 용도는 주로, Entity의 연관관계에서 외래 키를 매핑하기 위해 사용된다.
* Element Collection (자바의 컬렉션)의 경우에는 컬렉션의 정보로 테이블이 새로 생성된다.
Entity의 연관관계에 집중하기 위해 앞으로의 설명에서는 Element Collection 관련 설명은 생략하도록 한다.
🎯 2. @JoinColumn 의 속성: name
중요한 속성은 name과 referencedColumnName 이므로 해당 속성을 알아보자.
✅ 2-1. name : 매핑할 외래 키 이름 지정
name 속성은 매핑할 외래 키의 이름을 지정하는 속성이다.
지정하지 않았을 때 기본값은 다음과 같다.
* 참조하는 Entity가 존재할 경우
* 참조하는 Entity가 존재할 경우
: 참조하는 Entity의 필드명 + "_" + 참조된 기본 키 열의 이름
public class Station {
...
@ManyToOne
@JoinColumn
private Line line;
}
: 참조하는 Entity(Station)의 필드명(line) + "_" + 참조된 Entity(Line)의 기본 키 열의 이름(id)
: line + "_" + id = "line_id"
쉽게 요약하자면, 참조하는 Entity가 존재할 경우
@JoinColumn으로 FK를 매핑하는 필드의 이름과 해당 필드의 타입(대상 Entity)의 PK로 FK 이름이 지정된다!
일반적으로 테이블의 DDL을 만들 때도 FK는 대상 테이블의 PK로 이름을 짓는 것으로 통용되고 있다.
예를 들면, station 테이블에서 line 테이블의 FK를 가질 때, 해당 FK의 이름은 당연하게 line_id로 생각할 것이다.
테이블 입장에서 대상 테이블의 이름 + PK로 FK의 이름이 지어지는 것이 자연스러우므로
나도 그랬고, JPA에서 Entity를 매핑할 때 다음과 같은 2가지를 관습적으로 사용하는 것 같다.
1. @JoinColumn을 사용하는 FK 필드의 이름을 대상 Entity(대상 Table)의 이름으로 설정
ex) private Line line;
2. @JoinColumn을 사용할 때 name 속성으로 대상 Entity(대상 Table)의 이름 + PK로 하드하게 지정
ex)
@ManyToOne
@JoinColumn(name = "line_id")
private Line line;
1번을 잘 지킨다면 2번에서 name 속성을 생략해도 line_id로 만들어지지만,
필드의 이름이 대상 Entity의 이름으로 설정이 안 될수도 있으므로 보수적으로 name까지 지정해주는 것 같다.
(+ 유지 보수 시에 필드명이 바뀔 수도 있으므로)
✅ 2-2. JPA 연관관계 살펴보기 with name 주석
name의 역할, 기본값만 보면 단순히 FK의 이름을 지정하는 속성임을 알 수 있다.
그렇다면, 어떻게 JoinColumn을 파헤치면 JPA 연관관계를 파악할 수 있다고 했을까?
바로, 주석에 연관관계에 대한 내용이 담겨있었다.
주석에서는 OneToOne, ManyToOne, OneToMany, Embeddable, ManyToMany, ElementCollection 등
JPA의 연관관계를 다루는 모든 개념들이 담겨있었다.
첫 문장에서 다음과 같이 설명하고 있다.
FK의 Column이 발견되는 테이블은 JPA 연관관계(context)에 따라 다르다.
바로 다음부터 여러 연관관계에 따른 FK의 Column이 발견되는 테이블을 설명하고 있다.
즉, FK를 누가 가지는가 -> 연관관계의 주인이 누구인가? 외래키 관리자가 누구인가? 를 설명하는 것이다.
살펴보기 전에, source entity와 target entity에 대해 알아보자.
※ Source Entity / Target Entity
해당 주석에서 설명하는 Source Entity와 Target Entity는 다음과 같다.
Source Entity : @JoinColumn을 사용한 Entity
Target Entity : @JoinColumn을 사용한 Entity와 연관관계를 맺은 Entity
✅ 2-2-1. OneToOne / ManyToOne : Source Entity(Table)에 FK 위치
OneToOne과 ManyToOne에서 @JoinColumn을 사용하면,
Source Entity인 해당 Entity에 FK가 위치한다는 것이고,
해당 Entity가 연관관계의 주인이라는 의미이다.
OneToOne, ManyToOne의 예시를 살펴보자.
※ OneToOne @JoinColumn 예시 (단방향)
student <-> lecture 가 1:1 연관관계를 가지고 있다고 해보자. (학생이 1개의 강의만 수강할 수 있다.)
여기서는 student가 주 테이블(둘 중 많이 사용하는 테이블), lecture가 대상 테이블이라고 가정한다.
1:1 관계(@OneToOne)에서는 연관관계 주인이 누구든 될 수가 있다.
@OneToOne과 @JoinColumn이 함께 있다면,
해당 Entity를 연관관계 주인으로 판단하고 해당 필드를 FK로 매핑한다.
(물론 양방향일 경우 Target Entity의 @OneToOne에 mappedBy가 제대로 지정되어야 제대로 매핑된다.)
public class Student {
...
@OneToOne
@JoinColumn(name = "lecture_id")
// FK 매핑
private Lecture lecture;
}
※ ManyToOne @JoinColumn 예시 (단방향)
1:N 관계에서는 @ManyToOne과 @JoinColumn이 함께 있다면,
해당 Entity를 연관관계 주인으로 판단하고 해당 필드를 FK로 매핑한다.
public class Station {
...
@ManyToOne
@JoinColumn("line_id")
private Line line;
}
✅ 2-2-2. OneToMany 단방향 : Target Entity(Table)에 FK 위치
public class Line {
...
@OneToMany
@JoinColumn
private List<Station> stations = new ArrayList<>();
}
이렇게 @OneToMany 단방향에 @JoinColumn이 붙어있으면,
FK는 Target Entity(Station)에 위치한다.
이때, 조심해야할 점은 @JoinColumn에서 FK의 name을 지정해주지 않으면 기본값이 설정되는데
기본 값으로 참조하는 엔티티(Line)의 필드명(stations) + "_" + 참조된 엔티티(Station)의 PK(id)로 지정되므로
위의 예시에서는 FK의 이름으로 stations_id가 지정된다.
따라서 @OneToMany 단방향에 @JoinColumn을 사용한다면, name 속성으로 올바른 FK 이름을 지정해야한다.
(성능 이슈 및 불편함이 있어서 @OneToMany 단방향은 잘 사용하지 않는다.)
✅ 2-2-3. ManyToMany / OneToOne / 양방향 OneToMany / ManyToOne With JoinTable 전략 : JoinTable에 위치
JoinTable 전략이란, 두 연관관계의 조인 테이블 하나를 생성하여 FK를 저장하는 전략이다.
ManyToMany나 OneToOne 관계에서 @JoinTable 어노테이션을 사용하거나
양방향 OneToMany, ManyToOne 관계에서 mappedBy가 지정되지 않았을 때 자동으로 JoinTable 전략으로 실행된다.
이때는, Join Table에 FK가 위치한다.
public class Line {
...
@OneToMany
private List<Station> stations = new ArrayList<>();
}
public class Station {
...
@ManyToOne
@JoinColumn("line_id")
private Line line;
}
-> 이렇게 mappedBy로 FK 필드를 지정하지 않으면 Join Table 전략이 실행되어
Join한 테이블인 line_stations 조인 테이블에 FK가 위치한다.
🎯 3. @JoinColumn 의 속성: referencedColumnName
referencedColumnName은 조인할 대상 테이블의 컬럼을 의미한다.
즉 Join 절의 ON에 해당하는 대상 테이블의 컬럼을 의미하는 것이다.
기본값은 다음과 같이 설명이 나와있다.
참조된 테이블(대상 테이블)의 PK를 기본값으로 한다.
그래서 보통 referencedColumnName 속성은 생략해서 사용한다.
왜냐하면 조인 절의 컬럼은 주 테이블의 FK, 대상 테이블의 PK를 주로 사용하기 때문이다.
그래서 생략해서 조인할 대상 테이블의 컬럼을 기본값인 PK로 지정하는 것이다.
Entity 연관관계에서 referencedColumnName가 사용될 때는 FK는 Target Entity에 존재한다.
💻 4. 결론
위에서 JoinColumn의 개념 및 name, refercenedColumnName 속성을 살펴보고
관련된 JPA 연관관계를 살펴보았다.
🎯 JoinColumn의 개념
Entity 연관관계 또는 Element Collection을 연결하기 위한 Column을 지정
🎯 JoinColumn의 속성
Line(1) <-> Station(N)
1. name : Source Entity(Table) FK의 Column의 이름 지정
ex : station의 FK 이름 : "line_id"
2. referencedColumnName : FK로 참조하는 Column(조인할 대상 테이블의 컬럼)의 이름 지정
ex : station에서 FK로 참조하는 Line의 PK : "id"
다음으로, @JoinColumn에 따른 JPA 연관관계인데
연관관계별로 언제 @JoinColumn을 붙여야할지 정리해보는게 좋을 것 같다.
🎯 연관관계별 @JoinColumn 사용
※ @OneToOne : @JoinColumn을 사용하는 Entity가 연관관계의 주인, 즉 FK를 가진다.
※ @ManyToOne : @JoinColumn을 사용하는 Entity가 연관관계의 주인, 즉 FK를 가진다.
※ @OneToMany : @JoinColumn을 사용하는 Entity의 반대 Entity가 연관관계의 주인, 즉 FK를 가진다.
※ @ManyToMany : @JoinColumn을 사용하는 Join Table(Entity)가 연관관계의 주인, 즉 FK를 가진다.
@JoinColumn의 주석들을 보면서, JPA의 연관관계를 살펴보려고 했는데
주객전도가 된 느낌이라서 JPA의 연관관계를 살펴보려면 핵심 어노테이션인
@OneToOne, @ManyToOne, @OneToMany, @ManyToMany를 파보는 것이 나을 것 같다!
'Spring > JPA' 카테고리의 다른 글
[JPA] JPA, Spring Data JPA의 내부 동작 원리 알아보기 (0) | 2024.08.05 |
---|---|
테스트 시 @ElementCollection 테이블 동적으로 가져오기(테스트 데이터 초기화) (0) | 2024.05.09 |
[JPA] JPA N+1 문제 및 근본적인 원인에 대한 개인적인 고찰 (6) | 2024.04.05 |
[JPA] JPA 1:N 관계에서 연관관계 주인을 1 대신 N에 두는 이유 (4) | 2023.08.06 |