Table of Contents
엔티티의 연관관계를 매핑할 때는 다음 3가지를 고려해야 한다.
- 다중성
- 단방향, 양방향
- 연관관계의 주인
다대일
다대일 단반향
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Entity
public class Member {
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
private String username;
@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;
...
}
@Entity
public class Team {
@Id @GeneratedValue
@Column(name = "TEAM_ID")
private Long id;
private String name;
...
}
다대일 양방향
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
@Entity
public class Member {
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
private String username;
@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;
public void setTeam(Team team) {
this.team = team;
if (!team.getMembers().contains(this)) {
team.getMembers().add(this);
}
}
}
@Entity
public class Team {
@Id @GeneratedValue
@Column(name = "TEAM_ID")
private Long id;
private String name;
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<Member>();
public void addMember(Member member) {
this.members.add(member);
if (member.getTeam() != this){
member.setTeam(this);
}
}
}
- 양방향은 외래 키가 있는 쪽이 연관관계의 주인이다.
- 양방향 연관관계는 항상 서로를 참조해야 한다.
연관관계 편의 메소드가 무한루프에 빠질수 있으므로 주의하도록 해야 한다.
일대다
일대다 단방향
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Entity
public class Member {
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
private String username;
...
}
@Entity
public class Team {
@Id @GeneratedValue
@Column(name = "TEAM_ID")
private Long id;
private String name;
@OneToMany(mappedBy = "team")
@JoinColumn(name = "TEAM_ID")
private List<Member> members = new ArrayList<Member>();
...
}
- 일대다 단반향 매핑의 단점
매핑한 객체가 관리하는 외래 키가 다른 테이블에 있어서 연관관계 처리를 위한 UPDATE SQL을 추가로 실행 - 일대다 단반향 매핑보다는 다대일 양방향 매핑을 사용하자
엔티티를 매핑한 테이블이 아닌 다른 테이블의 외래 키를 관리해야 하는 부분은 성능 문제도 있지만 관리도 부담스럽다.
좋은 방법은 일대다 단방향 매핑 대신에 다대일 양방향 매핑을 사용하는 것
일대다 양방향
일대다 양방향 매핑은 존재하지 않는다. 대신 다대일 양방향 매핑을 사용해야 한다.
양방향 매핑에서 @OneToMany는 연관관계의 주인이 될 수 없다.
그렇다고 일대다 양방향 매핑이 완전히 불가능한 것은 아니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Entity
public class Member {
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
private String username;
@ManyToOne
@JoinColumn(name = "TEAM_ID", insertable = false, updatable = false)
private Team team;
...
}
@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>();
// Getter, Setter ...
}
위와 같은 방법은 다대일 단반향 매핑을 읽기 전용으로 추가해서 일대다 양방향처럼 보이도록 하는 방법
따라서 일대다 단반향 매핑이 가지는 단점을 그대로 가진다.
될 수 있다면 다대일 양방향 매핑을 사용하자
일대일
일대일 관계는 주 테이블이나 대상 테이블 중에 누가 외래 키를 가질지 선택해야 한다.
- 주 테이블에 외래키
객체지향 개발자들이 선호
장점은 주테이블이 외래 키를 가지고 있으므로 주 테이블만 확인해도 대상 테이블과 연관관계가 있는지 알 수 있다. - 대상 테이블에 외래키
전통적인 데이터베이스 개발자들은 보통 대상 테이블에 외래 키를 두는 것을 선호
장점은 테이블 관계를 일대일에서 일대다로 변경할 때 테이블 구조를 그대로 유지
주 테이블에 외래키
단방향
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Entity
public class Member {
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
private String username;
@OneToOne
@JoinColumn(name = "LOCKER_ID")
private Locker locker;
// Getter, Setter ...
}
@Entity
public class Locker {
@Id @GeneratedValue
@Column(name = "LOCKER_ID")
private Long id;
private String name;
// Getter, Setter ...
}
양방향
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Entity
public class Member {
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
private String username;
@OneToOne
@JoinColumn(name = "LOCKER_ID")
private Locker locker;
// Getter, Setter ...
}
@Entity
public class Locker {
@Id @GeneratedValue
@Column(name = "LOCKER_ID")
private Long id;
private String name;
@OneToOne(mappedBy = "locker")
private Member member;
// Getter, Setter ...
}
대상 테이블에 외래키
단방향
일대일 관계 중 대상 테이블에 외래 키가 있는 단방향 관계는 JPA에서 지원하지 않는다.
양방향
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Entity
public class Member {
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
private String username;
@OneToOne(mappedBy = "member")
private Locker locker;
// Getter, Setter ...
}
@Entity
public class Locker {
@Id @GeneratedValue
@Column(name = "LOCKER_ID")
private Long id;
private String name;
@OneToOne
@JoinColumn(name = "MEMBER_ID")
private Member member;
// Getter, Setter ...
}
다대다
단방향
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Entity
public class Member {
@Id
@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>();
// Getter, Setter ...
}
@Entity
public class Product {
@Id
@Column(name = "PRODUCT_ID")
private Long id;
private String name;
// Getter, Setter ...
}
@JoinTable의 속성
- @JoinTable.name : 연결 테이블 지정
- @JoinTable.joinColumns : 현재 방향인 회원과 매핑할 조인 컬럼 정보를 지정
- @JoinTable.inverseJoinColumns : 반대 방향인 상품과 매핑할 조인 컬럼 정보 지정
양방향
1
2
3
4
5
6
7
8
9
10
11
12
13
@Entity
public class Product {
@Id
@Column(name = "PRODUCT_ID")
private Long id;
private String name;
@ManyToMany(mappedBy = "products")
private List<Member> members;
// Getter, Setter ...
}
매핑의 한계와 극복, 연결 엔티티 사용
바로 이전에 확인한 방법은 단순하고 여러가지로 편하지만 한계가 있다.
보통 연결 테이블에 주문 수량이나 주문 날짜 같은 컬럼이 더 필요하다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
@Entity
public class Member {
@Id
@Column(name = "MEMBER_ID")
private Long id;
private String username;
@OneToMany(mappedBy = "member")
private List<MemberProduct> memberProducts;
...
}
@Entity
public class Product {
@Id
@Column(name = "PRODUCT_ID")
private Long id;
private String name;
...
}
@Entity
@IdClass(MemberProductId.class)
public class MemberProduct {
@Id
@ManyToOne
@JoinColumn(name = "MEMBER_ID")
private Member member;
@Id
@ManyToOne
@JoinColumn(name = "PRODUCT_ID")
private Product product;
private int orderAmount;
...
}
public class MemberProductId implements Serializable {
private String member;
private String product;
// hashCode and equals
}
@Id와 @JoinColumn을 동시에 사용해서 기본 키 + 외래 키를 한번에 매핑
@IdClass를 사용해서 복합 기본 키를 매핑
복합 기본키
복합키를 사용하려면 별도의 식별자 클래스를 만들어야 한다.
엔티티에 @IdClass를 사용해서 식별자 클래스를 지정하면 된다.
- 복합 키는 별도의 식별자 클래스로 만들어야 한다.
- Serializable을 구현해야 한다.
- equals와 hashCode 메소드를 구현해야 한다.
- 기본 생성자가 있어야 한다.
- 식별자 클래스는 public이여야 한다.
- @EmbeddedId도 존재
식별 관계
부모 테이블의 기본키를 받아서 자신의 기본 키 + 외래 키로 사용하는 것 을 식별 관계라고 한다.
새로운 기본키 사용
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
@Entity
public class Member {
@Id
@Column(name = "MEMBER_ID")
private Long id;
private String username;
@OneToMany(mappedBy = "member")
private List<Order> orders = new ArrayList<Order>;
...
}
@Entity
public class Product {
@Id
@Column(name = "PRODUCT_ID")
private Long id;
private String name;
...
}
@Entity
public class Order {
@Id
@GeneratedValue
@JoinColumn(name = "ORDER_ID")
private Long Id;
@ManyToOne
@JoinColumn(name = "MEMBER_ID")
private Member member;
@ManyToOne
@JoinColumn(name = "PRODUCT_ID")
private Product product;
private int orderAmount;
...
}
다대다 연관관계 정리
- 식별 관계 : 받아온 식별자를 기본 키 + 외래 키로 사용
- 비식별 관계 : 받아온 식별자는 외래 키로만 사용, 새로운 식별자를 추가
비식별 관계는 복합 키를 위한 식별자 클래스를 만들지 않아도 되므로 단순하고 편리하게 ORM을 매핑할 수 있다.