다양한 연관관계 매핑
연관관계 매핑을 다시 한 번 정리해보려고 한다.
연관관계 매핑 시 고려할 3가지
다중성 결정하기
- 다대일
- 일대다
- 일대일
- 다대다
단방향, 양방향 정하기
- 테이블의 경우 : '방향'이라는 개념 없음, 그냥 외래키로 조인하면 두 테이블이 연관관계를 맺음
- 객체의 경우 : 연관관계 매핑을 객체 참조하는 방식으로 하여,참조가 2군데 있어야함 ( A->B, B->A )→ 연관관계의 주인을 만들어야함
- → 외래키를 뭐로 할지, 누가 외래키를 관리할 지 정해줘야 함
연관관계의 주인 정하기
양방향의 경우, 누가 주인( = 외래키를 관리하는 쪽)인지 정해주자
다대일 @ManyToOne
• 다대일 단방향 [N:1] : 주인인 '다'쪽에서는 '일'쪽 참조 가능하나, 반대는 조회도 불가능!
• 다대일 양방향 [N:1, 1:N] : 서로 참조하면 주인아닌 쪽에서는 조회 가능해짐, 연관관계 편의 메소드를 만들어주면 좋음 (무한 루프 주의!)
아래 코드는 Member에서 다대일로 단방향을 걸고 Team에서 Member로 일대다를 걸어 양방향을 완성한 코드이다.
@Entity
@NoArgsConstructor
@ToString(exclude = "team")
@Getter @Setter
public class Member {
@Id
@Column(name = "MEMBER_ID")
private String id;
// 아래코드만 있으면 단방향, 양방향은 team 쪽에서 양방향을 걸어줘야한다.
@ManyToOne //★ N 대 1 에서 N 쪽에 외래키를 둔다.
@JoinColumn(name = "TEAM_ID") //★ 외래키가 있는 주인 쪽에서 상대방한테 @JoinColumn을 건다
private Team team;
}
@Entity
@NoArgsConstructor
@Getter @Setter
public class Team {
@Id
@Column(name = "TEAM_ID")
private String id;
// 아래 코드가 있으면 양방향 성립
@OneToMany(mappedBy = "team") //★ 양방향 연관관계의 주인은 Member에 있는 team이다.
private List<Member> members = new ArrayList<>();
}
일대다 @OneToMany
• 일대다 단방향 [1:N] : '일'이 연관관계의 주인임, @JoinColumn을 넣어주어야함!
• 일대다 양방향 [1:N, N:1] : 이보다는 다대일 양방향('다'쪽이 주인)을 추천한다.
만약 '일'쪽에서 외래키를 관리(주인)하겠다면 헷갈리기 때문에 비추함!
→ 다대일 관계에서 주인은 항상 '다' 쪽에 있다. 하지만 일대다는 주인이 ‘일’ 이기 때문에 기존 DB의 구조와 반대된다.
→ 연관관계 관리를 위해 추가로 UPDATE SQL 실행 해야한다.
→ 다대일 양방향을 추천한다. '다'쪽에서 '일'쪽을 조회할 일이 없더라도, '다'를 연관관계의 주인으로 설정하자!
아래는 일대다 단방향 코드이다. 일대다 양방향은 다대일 양방향으로 작성하자
@Entity
@Getter @Setter
public class Team {
@Id @GeneratedValue
@Column(name = "TEAM_ID")
private String id;
private String name;
@OneToMany
@JoinColumn(name = "TEAM_ID")
private List<Member> members = new ArrayList<>();
}
@Entity
@Setter
@Getter
public class Member {
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private String id;
private String username;
}
일대일 @OneToOne
- 주 테이블이나 대상 테이블 중에 외래 키를 선택 가능하다.
- 일대일 양방향일때는 주인이 아닌쪽에 mappedBy 를 걸어주면 된다.
- 주 테이블에 외래 키
- 주 객체가 대상 객체의 참조를 가지는 것 처럼 주 테이블에 외래 키를 두고 대상 테이블을 찾음
- 객체지향 개발자 선호
- JPA 매핑 편리
- 장점: 주 테이블만 조회해도 대상 테이블에 데이터가 있는지 확인 가능
- 단점: 값이 없으면 외래 키에 null 허용
- 대상 테이블에 외래 키
- 대상 테이블에 외래 키가 존재
- 전통적인 데이터베이스 개발자 선호
- 장점: 주 테이블과 대상 테이블을 일대일에서 일대다 관계로 변경할 때 테이블 구조 유지
- 단점: 프록시 기능의 한계로 지연 로딩으로 설정해도 항상 즉시 로딩됨
아래는 주테이블에 외래키 일대일 단방향, 양방향 코드이다.
@Entity
@Getter
@Setter
public class User {
@Id @GeneratedValue
@Column(name = "user_id")
private String id;
@OneToOne // 단방향을 먼저 걸어준다.
@JoinColumn(name = "locker_id") // 주인 쪽에서 @JoinColumn을 사용한다.
private Locker locker;
}
@Entity
@Getter @Setter
public class Locker {
@Id @GeneratedValue
@Column(name = "locker_id")
private Long id;
// (이 부분이 없으면 단방향)
@OneToOne(mappedBy = "locker") // 이걸 걸어주고
private Member member; // 객체를 참조해주면 양방향임
}
아래는 대상테이블에 외래키 양방향 코드이다.(대상 테이블의 외래키는 단방향이 없다.)
@Entity
public class Member {
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
private String username;
@OneToOne(mappedBy = "member")
private Locker locker;
...
}
@Entity
public class Locker {
@Id @GeneratedValue
@Column(name = "LOCKER_ID")
private Long id;
private String name;
@OneToOne
@JoinColumn(name = "MEMBER_ID")
private Member member;
...
}
다대다 @ManyToMany
객체의 경우
- 객체의 경우 : 다대다 관계가 가능함
- 테이블의 경우 : 기존 테이블 두개만으로 다대다 관계를 만들수 없다.대신에 보통은 연결 테이블을 추가하여, 일대다, 다대일로 풀어내자.@ManyToMany → @OneToMany, @ManyToOne 이렇게 풀어내어 만들자.
다대다 매핑의 한계
- 편리해 보이지만 실무에서 사용 X
- 연결 테이블이 단순히 연결만 하고 끝나지 않음
- 주문 시간, 수량 같은 데이터가 들어올 수 있음
@ManyToMany 는 편리한 것 같지만, 중간 테이블에 컬럼을 추가할 수 없고, 세밀하게 쿼 리를 실행하기 어렵기 때문에 실무에서 사용하기에는 한계가 있다.
@ManyToMany → @OneToMany, @ManyToOne 이렇게 풀어내고 + 연결 테이블용 엔티티 추가 하자
아래는 추천하는 @ManyToMany → @OneToMany, @ManyToOne 이렇게 풀어내고 + 연결 테이블용 엔티티(MemberProduct) 추가 코드
참고로 아래 코드는 다대일 양방향 코드가 두 번 있다.
@Entity
public class Member {
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")
private String username;
// ...
@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;
@ManyToOne
@JoinColumn(name = "PRODUCT_ID")
private Product product;
}
@Entity
public class Product {
@Id @GeneratedValue
private Long id;
private String name;
@OneToMany(mappedBy = "product")
private List<MemberProduct> memberProducts = new ArrayList<>();
}