RDB에서는 한 곳에서 외래키를 갖고 있는데 자바에서는 양방향 매핑시 양쪽 모두 서로의 Entity를 갖고 있음
RDB에서는 해당 필드(칼럼)이 없는데 자바에는 존재
관점이 다르기 때문에 발생하는 문제로 RDB 외래키를 관리해주는 Entity를 누구로 할지 결정해야한다.
(2)
자바는 단방향 매핑이면 참조타입이 없는 한쪽에서 다른쪽으로 접근하는 것이 불가능,
RDB는 외래키를 이용해서 조인하면 서로 접근이 가능
Q&A
무조건 외래 키가 있는 쪽이 연관 관계의 주인 ?
- 대부분의 경우엔 그렇다. 하지만 외래키가 없는 Entity가 관계의 주인이 될 수 있다.
외래키가 없는 곳을 굳이 연관 관계의 주인으로 지정하는 이유?
- 편하다. 외래 키가 없음에도 연관 관계 CRUD가 완벽 동작
원래 외래키가 있는 곳에서의 CRUD는?
- 양방향 매핑이라면 전부 정상 동작한다. 외래 키가 없는 곳에서 CRUD를 하기 위함이다.
단점은 없는지?
- 외래 키 설정을 위한 추가 update 쿼리가 동작한다. (성능 저하)
mappedBy로 외래 키가 있는 반대편을 지정하는 이유?
- 코드 가독성 측면에서 수많은 Entity를 볼 때 양방향 매핑이 돼있음을 알림,
JPA에게 반대편 Entity가 주인임을 알림(매핑 테이블 생성과 검색을 방지),
외래 키가 있는 곳에서만 외래 키를 관리하여 복잡도를 줄이기 위함
✔ 단방향 관계
한쪽만 매핑 어노테이션을 설정한 것
@ManyToOne, OneToMany 알맞게 설정하고
@JoinColumn으로 외래키를 갖고 있는 쪽을 가르킨다.
(1) 다대일
RDB와 JAVA 관점을 일치시키는 방식.
RDB 외래 키가 있는 곳을 연관 관계의 주인으로 설정
(2) 일대다
RBD와 JAVA 관점이 불일치되는 방식.
외래 키가 없는 곳에만 매핑 어노테이션 설정
외래키가 있는 Entity에는 매핑 어노테이션을 설정하지 않는다. 단순히 long boardId
RDB 외래키가 없는 곳을 연관 관계의 주인으로 설정
다쪽에 있는 외래 키를 수정하기 위해 추가적인 update쿼리가 동작한다.
이 방식보다는 차라리 양방향 매핑을 사용하므로, 많이 사용하는 방식은 아니다.
* 양방향에서의 관계 주인 (CRUD의 가능 여부)
✔ 양방향 관계
양쪽이 매핑 어노테이션을 설정한 것
모든 테이블을 양방향 관계로 설정하면 복잡성이 증가하므로 필요할 때만 양방향 관계 지정
사실 단방향 만으로도 Entity 간의 관계 설정은 완료된 것이다.
(RDB는 한쪽만 외래키를 갖는 식으로 완벽히 동작해왔다.)
✔ 외래 키가 없는 Entity에서 반대쪽 Entity의 외래 키를 인식하게 하는 방법은 두 가지
(1) mappedBy
외래 키는 반대쪽 Entity가 갖고 있음을 알려준다.
mappedBy가 없으면?
RDB에서 외래 키가 없는 테이블에 외래 키를 생성하게 된다. (조회 필드를 외래 키로 잘못 인식해버린다.)
JPA가 양방향인 것을 모르고 해당 필드가 외래키로 인식되어 매핑 테이블을 생성하고, 그곳에서 관계 정보를 찾으려고 한다.
JPA가 만든 양쪽 Entity id를 갖는 매핑 테이블에는 당연히 아무런 정보가 들어가 있지 않아 찾지 못한다.
(2) JoinColumn
반대쪽 Entity를 가리키면 연관 관계의 주인을 외래 키가 없는 곳으로 설정
이 방식을 권장하지 않는 이유는추가적인 쿼리를 소모하게 된다.
두 Entity의 데이터를 새로 추가시킨다고 했을 때 외래키가 없는 Entity로 save()하면
Entity1 insert ---> Entity2 insert ---> 외래키가 있는 Entity를 update
insert 만으로 끝날 동작을, 반대편 Entity 외래 키를 update 하며 추가적인 쿼리가 소모된다.
성능을 희생하고, 편의를 증가
두 Entity 모두 CRUD가 가능해진다.
양방향 매핑 두 가지 방법
(1) JoinColumn(외래 키가 있는 곳에 설정), mappedBy(외래키가 없는 곳에 설정)
-> mappedBy가 있는 Entity는 외래키를 관리하지 않는다.
연관 관계의 주인을 외래키가 있는 곳으로 설정, 권장되는 방식
외래키가 존재하는 Entity에서 연관 Create, Select, Update, Delete가 가능
mappedBy 쪽에서도 cascade 설정하면 Insert 빼고는 다 전이된다.
@Entity
public class Troop {
@OneToMany(mappedBy="troop")
public Set<Soldier> getSoldiers() {
...
}
@Entity
public class Soldier {
@ManyToOne
@JoinColumn(name="troop_fk")
public Troop getTroop() {
...
}
(2) JoinColumn(외래 키가 없는 곳에 설정)
연관 관계의 주인을 외래키가 없는 곳으로 설정, 편의를 위해 성능을 희생시키는 방식
양쪽 모두 Create, Select, Update, Delete가 가능 (복잡도 증가)
@Entity
public class Troop {
@OneToMany
@JoinColumn(name="troop_fk") //we need to duplicate the physical information
public Set<Soldier> getSoldiers() {
...
}
@Entity
public class Soldier {
@ManyToOne
@JoinColumn(name="troop_fk", insertable=false, updatable=false)
public Troop getTroop() {
...
}
양방향 매핑 시에는 순환 참조, 순환 삭제 등 신경 써줘야 할 사항들이 생겨 복잡도가 증가한다.
그래서 mappedBy로 조회만 하고, 나머지는 연관 관계의 주인으로 처리하는 방식이 권장된다.