다양한 연관관계 매핑
다대일(@ManyToOne)
단방향, 양방향
일대다(@OneToMany)
단방향, 양방향
일대일(@OneToOne)
주 테이블 단방향, 양방향 / 대상 테이블 양방향
다대다(@ManyToMany)
단방향, 양방향
단방향, 양방향
테이블: 외래키 하나로 양쪽 Join이 가능하다. 사실상 방향이라는 개념이 없다.
객체: 참조용 필드가 있는 쪽에서 참조 대상으로만 참조가 가능하다. 한 쪽만 참조하면 단방향, 양쪽이 서로 참조하면 양방향이다.
연관관계의 주인
테이블은 외래 키 하나로 두 테이블의 연관관계를 맺고 있다.
하지만, 객체의 양방향 관계는 A → B, B → A처럼 참조가 2군데에서 필요하다. 즉, 둘 중 테이블의 FK를 관리할 곳이 필요하다.
연관관계 주인: 외래 키를 관리하는 참조
주인의 반대편: 외래 키에 영향을 주지않고 단순 조회(참조)만 가능하다.
다대일[N:1] / @ManyToOne
다대일 단방향
가장 많이 사용하는 연관관계이며, 다대일의 반대는 일대다이다.
다대일 양방향
외래 키가 있는 쪽이 연관관계의 주인이다.
양쪽에서 서로 참조하도록 개발하면 된다.
연관관계가 주인이 아닌 쪽은 단순 조회만 가능하기 때문에 필드만 추가하면 된다.
@OneToMany(mappedBy = "team)
private List<Membr> members = new ArrayList<>();
일대다[1:N] / @OneToMany
일대다 단방향
1이 연관관계의 주인이 되는 방식으로, 권장하지 않는 방식이다.
테이블 일대다 관계는 항상 다(N) 쪽에 외래 키가 있다.
Team
의 members
를 수정해 주면 Team_ID
라는 다른 테이블에 있는 FK를 update 해줘야 한다.
일대다 단방향은 1:N에서 1의 입장에서 연관관계를 관리하겠다는 이야기이다. 즉, 다른 테이블에 있는 FK를 관리해야 한다.
객체와 테이블 차이 때문에 반대편 테이블의 외래키를 관리하는 특이한 구조를 나타낸다.
@Entity
public class Team {
@Id
@GeneratedValue
@Column(name = "TEAM_ID")
private Long id;
private String name;
@OneToMany
@JoinColumn(name = "TEAM_ID")
private List<Member> members = new ArrayList<Member>();
...
}
Team
의 members
필드에 JoinColum(name = “TEAM_ID”)
로 지정되어 있다.
Team
이 FK를 관리하게 되는 것이다.
이렇게 하면 저장할 때 멤버 저장 → 팀 저장 → 팀 안에 멤버 업데이트와 같이 쿼리가 더 나가게 된다.
하지만 이러한 성능차이보다 무엇보다 심간한 점은 실무에서는 수많은 쿼리들이 나가는 도중에 이러한 업데이트 쿼리가 들어가게 되면 큰 혼란을 야기할 수 있다.
권장하지 않는 이유
@JoinColum
을 꼭 사용해야 한다. 만약 그렇지 않으면 중간에 조인 테이블이 따로 생성된다.DB 테이블에서는 항상 N쪽에 FK가 있기 때문에 페러다임에 충돌이 있다.
→ 엔티티가 관리하는 FK가 다른 테이블에 있게 되는 것이다. 즉, update 쿼리 같은 상황이 발생한다.
실무 현장에서는 테이블이 1, 2개가 아닌 수십 개가 공존한다. → 관리가 어려워진다.
일대다 양방향
해당 매핑은 공식적으로는 존재하지 않는다.
하지만 @JoinColumn(insertable=false, updatable=false
)를 Team team
위에 넣으면 가능하다. 해당 방법은 읽기 전용 필드를 사용해서 양방향처럼 사용하는 방법이다.
위 @JoinColum
의 경우 Team
에서 List members
가 주인 행세를 하고, Member
에서 Team team
도 주인행세를 하지만, Team team
은 읽기전용으로 바뀌는 것 뿐이다.
결론은 다대일 양방향 매핑을 사용자!
일대일[1:1] / @OneToOne
일대일 단방향
일대일 관계는 반대도 일대일 관계이다.
주 테이블이나 대상 테이블 중에 외래 키 선택이 가능하다.
외래 키 데이터베이스에 unique 제약 조건 추가
다대일 연관관계와 동일하게 외래 키가 있는 곳이 연관관계의 주인이다.
연관관계의 주인이 아닌 곳에
mappedBy
를 넣어준다.
일대일 양방향
다대일 양방향 매핑과 같은 방식으로 외래키가 있는 곳이 연관관계의 주인이다.
마찬가지로 반대편에는 mappedBy
를 적용해야 한다.
일대일: 대상 테이블에 외래 키가 있는 단방향
위와 같은 모델은 불가능한 모델이다.
대신 대상 테이블에 외래 키가 있도록 설정하려면, 아래 처럼 양방향으로 만들어야 한다.
일대일: 대상 테이블에 외래 키가 있는 양방향
하지만 이 방법은 사실 맨 처음 일대일방식을 대칭으로 뒤집은 것 뿐이다.
정리하자면,
주테이블에 외래 키
주 객체가 대상 객체의 참조를 가지는 것처럼 주 테이블에 외래 키를 두고 대상 테이블을 찾는다.
객체지향 개발자 선호
JPA 매핑 편리
장점: 주 테이블만 조회해도 대상 테이블에 데이터가 있는지 확인 가능
단점: 값이 없으면 외래 키에 null 허용
대상 테이블의 외래 키
대상 테이블에 외래 키가 존재
전통적인 데이터베이스 개발자 선호
장점: 주 테이블과 대상 테이블을 일대일에서 일대다 관계로 변경할 때 테이블 구조 유지
→ unique 제약 조건만 없애도록 update 해주면 끝
단점: 프록시 기능의 한계로 지연 로딩으로 설정해도 항상 즉시 로딩
→ PK가 주 객체에게 있는 경우,
프록시 Locker 객체를 만들려면 JPA는 Member를 로딩할 때 Member에 Locker 객체 값이 있는지 없는지 알아야 한다. Member 테이블을 확인해서 FK가 있으면 값이 있다고 가정하고 프록시를 생성하고, 없으면 null을 삽입한다.
→ PK가 대상 테이블에 있는 경우,
대상 테이블인 Locker 테이블을 조회하여 값이 있는지를 확인해야 한다. 이때 값이 있어야 Member의 Locker가 값이 있다고 인정이 된다. 어짜피 Locker를 찾는 쿼리가 나가기 때문에 Locker를 프록시로 만드는 의미가 없어진다.
다대다[N:M] / @ManyToMany
관계형 데이터베이스는 정규화된 테이블 2개로 다대다 관계를 표현할 수가 없다.
따라서 연결 테이블을 추가해서 일대다, 다대일 관계로 만들어야 한다.
하지만 객체 입장은 다르다. 객체는 Member
에 products
라는 참조 변수를 Product
에 members
라는 참조 변수를 만들면 다대다가 가능하다.
다대다가 가능한 객체와 다대다가 불가능한 테이블을 매핑하기 위해 @ManyToMany
와 @JoinTable
로 연결 테이블을 지정해야 한다.
@Entity
public class Member {
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
private String username;
@ManyToMany
@JoinTable(name = "MEMBER_PRODUCT", joinColumns = @JoinColumn(name = "MEMBER_ID"),
inverseJoinColumns = @JoinColumn(name = "PRODUCT_ID"))
private List<Product> products = new ArrayList<Product>();
..
}
@JoinTable
의 name
속성으로 중간 테이블 이름을 지정한다.
@Entity
public class Product {
@Id
@Column(name = "PRODUCT_ID")
private String id;
private String name;
@ManyToMany(mappedBy = "products")
private List<Member> members = new ArrayList<>();
..
}
양방향으로 만들어 주려면, Product
쪽에 mappedBy
를 추가해주면 된다.
다대다 매핑의 한계
편리해 보이지만 실무에서는 사용하지 않는다.
연결 테이블(조인 테이블)이 단순히 연결만하고 끝나지 않는다.
중간 테이블에 추가적인 데이터를 넣을 수 없다는 한계점
→ 주문시간, 수량 같은 데이터가 들어올 수 있는데, 이를 반영하기 어렵다.
중간 테이블이 숨겨져 있어서 의도치 않은 쿼리가 생성이 될 수 있다.
다대다 한계 극복
연결 테이블용 엔티티를 따로 추가(연결 테이블을 엔티티로 승격)
→ 이전에는
@JoinTable
을 사용하여 연결 테이블이 생성되었지만, 이번에는 연결 테이블을 엔티티로 만들어 사용한다.@ManyToMany
→@OneToMany
,@ManyToOne
참고 자료
Last updated