일대일 방향에서는 외래 키의 위치가 자유롭다. 원하는 주 테이블에 외래 키를 놓고 테이블에서는 유니크 제약조건을 사용하면 된다.
양방향 매핑
@Entity public class Member { ... @OneToOne @JoinColumn(name = "LOCKER_ID") private Locker locker; ... }
@Entity public class Locker {
@Id @GeneratedValue private Long id; private String name;
@OneToOne(mappedBy = "locker") private Member member; }
반대로 Locker가 주인을 가져갈 수도 있다.
이 때 생각해봐야할 이슈가 생기는 데 만약 Member가 주인을 가지고 있다면 객체를 다루는 입장에서는 Member가 Locker를 가지고 있기 때문에 성능상의 이점과 개발 효율이 증가할 수 있는 반면 나중에 비즈니스가 여러 Member가 한개의 Locker를 사용할 수 있게끔 변경된다면 여러 코드를 변경해야하고 DBA 입장에서는 난감할 수 있는 상황이 발생한다.
물론, 반대의 입장도 똑같이 득과 실이 존재하기 때문에 생각을 해보는 것이 좋다.
Locker에 두는 경우에 JPA의 단점이 생기는데 프록시 기능의 한계로 LAZY로 설계하더라도 항상 EAGER 로딩이 된다. 풀어 설명하자면 프록시 객체를 만들기 위해서는 객체의 필드가 null인지 값이 있는지 알아야하는데 양방향일 경우 Member를 조회할 때 Locker의 값을 알기 위해서는 즉시 로딩을 통해 DB에 확인해야하는 문제점을 가지고 있다.
다대다 - @ManyToMany
관계형 데이터베이스는 다대다 관계를 정규화된 2개의 테이블로 구현이 불가능하다.
그러므로 중간에 연결 테이블을 만들어 `다대일 - 일대다 : 다대일 - 일대다` 구조로 해결해야한다.
하지만, 객체는 컬렉션을 사용하여 다대다가 가능하다.
단방향일 때는 Member에만 만들어주면 되고 양방향일 때는 똑같이 mappedBy 옵션을 사용해주면 된다.
양방향 매핑
@Entity public class Member { ... @ManyToMany @JoinTable(name = "MEMBER_PRODUCT") private List<Product> products = new ArrayList<>(); ... }
@Entity public class Product {
@Id @GeneratedValue private Long id; private String name;
@ManyToMany(mappedBy = "products") private List<Member> members = new ArrayList<>(); }
다대다 매핑은 편리해보이지만 치명적인 한계를 가지고 있는데 다대다 매핑으로 인해 만들어지는 중간 테이블(여기서는 MEMBER_PRODUCT)에 매핑을 위한 데이터 말고는 다른 컬럼 사용이 불가능하다. 예를 들어 주문 시간이나 수량과 같은 정보를 추가할 수 없다는 것이다. 또한 중간 테이블이 숨겨져 있기 때문에 생각지 못한 쿼리가 나갈 가능성이 농후하다.
다대다를 해결하기 위해서 결국 다대일 : 일대다 - 다대일 : 일대다로 풀어 내면 되는데 중간 연결 테이블(MEMBER_PRODUCT)을 Entity로 승격시켜 문제를 해결할 수 있다.
다대일 : 일대다 -다대일 : 일대다
@Entity public class Member { ... @OneToMany(mappedBy = "member") private List<MemberProduct> memberProducts = new ArrayList<>(); ... }
@Entity public class MemberProduct {
@Id @GeneratedValue private Long id;
@ManyToOne @JoinColumn(name = "MEMBER_ID") private Member member;