다양한 연관관계 매핑
엔티티의 연관관계 매핑 시 고려사항
- 다중성
- 다대일
- 일대다
- 일대일
- 다대다
- 단방향, 양방향
- 연관관계의 주인
가능한 모든 연관관계
- 다대일 : 단방향, 양방향
- 일대다 : 단방향, 양방향
- 일대일 : 주 테이블 단방향, 양방향
- 일대일 : 대상 테이블 단방향, 양방향
- 다대다 : 단방향, 양방향
다대일
단방향
@Entity
public class Member {
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
private String username;
@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;
//Getter, Setter...
}
@Entity
public class Team {
@Id @GeneratedValue
@Column(name = "TEAM_ID")
private Long id;
private String name;
//Getter, Setter...
}
회원은 Member.team으로 팀 엔티티를 참조할 수 있지만, 팀에서는 불가
따라서 다대일 단방향 연관관계
@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;
@JoinColumn을 사용해서 TEAM_ID 외래 키와 매핑
양방향
@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);
}
}
}
- 양방향은 외래 키가 있는 쪽이 연관관계의 주인
- 일대다, 다대일 관계에서는 항상 다에 외래 키가 존재. 여기서는 Member
- 양방향 연관관계는 항상 서로를 참조해야 한다.
- 항상 서로 참조하게 하려면 연관관계 편의 메소드를 작성하는 것이 좋음
- setTeam(), addMember()가 해당. 한 곳에만 작성하거나 양쪽에 다 작성. 양쪽에 다 작성 시 무한루프에 빠지지 않도록 해야 함
일대다
단방향
Team.members로 회원 테이블의 TEAM_ID 외래 키를 관리
@Entity
public class Team {
@Id @GeneratedValue
@Column(name = "TEAM_ID")
private Long id;
private String name;
@OneToMany
@JoinColumn(name = "TEAM_ID") //MEMBER 테이블의 TEAM_ID
private List<Member> members = new ArrayList<Member>();
//Getter, Setter...
}
@Entity
public class Member {
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
private String username;
//Getter, Setter...
}
- 일대다 단방향 관계를 매핑할 때는 @JoinColumn을 명시해야 한다.
- 명시하지 않으면 연결 테이블을 중간에 두고 연관관계를 관리하는 조인 테이블 전략을 기본으로 사용하여 매핑
일대다 단방향 매피으이 단점은 매핑한 객체가 관리하는 외래 키가 다른 테이블에 있다는 점
이 때문에 INSERT SQL에 추가로 UPDATE SQL을 실행함
일대다 단방향 매핑보다는 다대일 양방향 매핑을 사용하는 것이 일반적으로 좋다.
양방향
일대다 양방향 매핑은 존재하지 않으며, 다대일 양방향 매핑을 사용한다.
하지만 완전히 불가능한 것은 아니며, 일대다 단방향 매핑 반대편에 같은 외래 키를 사용하는 다대일 단방향 매핑을 읽기 전용으로 추가하면 된다.
@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;
//Getter, Setter...
}
일대일
일대일 관계는 주 테이블이나 대상 테이블 중 어디서 외래 키를 가질지 선택해야 한다.
- 주 테이블에 외래 키
- 외래 키를 객체 참조와 비슷하게 사용할 수 있다.
- 주 테이블이 외래 키를 가지고 있으므로 주 테이블만 확인해도 대상 테이블과 연관관계가 있는지 알 수 있음
- 대상 테이블에 외래 키
- 테이블 관계를 일대일에서 일대다로 변경할 때 테이블 구조를 그대로 유지할 수 있음
주 테이블에 외래 키
단방향
주 테이블 : Member
대상 테이블 : Locker
@Entity
public class Member {
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
private String username;
@OneToOne
@JoinColumn(name = "LOCKER_ID")
private Locker locker;
}
@Entity
public class Locker {
@Id @GeneratedValue
@Column(name = "LOCKER_ID")
private Long id;
private String name;
}
양방향
@Entity
public class Member {
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
private String username;
@OneToOne
@JoinColumn(name = "LOCKER_ID")
private Locker locker;
}
@Entity
public class Locker {
@Id @GeneratedValue
@Column(name = "LOCKER_ID")
private Long id;
private String name;
@OneToOne(mappedBy = "locker")
private Member member;
}
대상 테이블에 외래 키
단방향
이런 관계는 불가능하다.
단방향 관계를 Locker에서 Member 방향으로 수정하거나, 양방향 관계로 만들고 Locker를 연관관계의 주인으로 설정해야 한다.
양방향
@Entity
public class Member {
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
private String username;
@OneToOne
@JoinColumn(name = "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;
}
다대다
관계형 데이터베이스는 정규화된 테이블 2개로 다대다 관계를 표현할 수 없다.
따라서, 일대다, 다대일 관계로 풀어내는 연결 테이블을 사용한다.
하지만, 객체는 객체 2개로 다대다 관계를 만들 수 있다.
단방향
@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>();
}
@Entity
public class Product {
@Id @Column(name = "PRODUCT_ID")
private String id;
private String name;
}
@ManyToMany와 @JoinTable을 사용해서 연결 테이블을 바로 매핑
- @JoinTable.name : 연결 테이블을 지정
- @JoinTable.joinColumns : 현재 방향인 회원과 매핑할 조인 컬럼 정보를 지정
- @JoinTable.inverseJoinColumns : 반대 방향인 상품과 매핑할 조인 컬럼 정보를 지정
양방향
역방향도 @ManyToMany를 사용하며, 원하는 곳에 mappedBy로 연관관계의 주인을 지정한다.(mappedBy가 없는 곳이 주인이다.)
@Entity
public class Product {
@Id
private String id;
@ManyToMany(mappedBy = "products")
private List<Member> members;
}
연결 엔티티 사용
보통은 연결 테이블에 추가 정보를 담는 컬럼이 사용되곤 한다. 이 경우에는 @ManyToMany를 사용할 수 없다.
이 경우 일대다, 다대일 관계로 풀어야 한다.
@Entity
public class Member {
@Id @Column(name = "MEMBER_ID")
private String id;
@OneToMany(mappedBy = "member")
private List<MemberProduct> memberproducts;
...
}
@Entity
public class Product {
@Id @Column(name = "PRODUCT_ID")
private String 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;
...
}
@Id와 @JoinColumn을 함께 사용해서 기본 키 + 외래 키를 한 번에 매핑
@IdClass를 사용하여 복합 기본 키를 매핑
회원 상품 엔티티는 기본 키가 MEMBER_ID와 PRODUCT_ID로 이루어진 복합 기본키이다.
JPA에서 복합 키를 사용하려면 별도의 식별자 클래스를 만들어야 한다.
//회원상품 식별자 클래스
public class MemberProductId implements Serializable {
private String member;
private String product;
//hashCode and equals
@Override
public boolean eqauls(Object o) { ... }
@Override
pubilc int hashCode() { ... }
}
식별자 클래스의 특징
- 복합 키는 별도의 식별자 클래스로 만들어야 함
- Serializable을 구현해야 함
- equals와 hashCode 메소드를 구현해야 함
- 기본 생성자가 있어야 함
- 식별자 클래스는 public이어야 함
- @IdClass 외에 @EmbeddedId를 사용하는 방법도 있음
새로운 기본 키 사용
새로운 기본 키를 만들고 MEMBER_ID와 PRODUCT_ID 컬럼은 외래 키로만 사용
@Entity
public class Order {
@Id @GeneratedValue
@Column(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;
...
}
@Entity
public class Member {
@Id @Column(name = "MEMBER_ID")
private String id;
private String username;
@OneToMany(mappedBy = "member")
private List<MemberProduct> memberproducts;
...
}
@Entity
public class Product {
@Id @Column(name = "PRODUCT_ID")
private String id;
private String name;
...
}
정리
다대다 관계를 일대다 다대일 관계로 풀기 위해 연결 테이블을 만들 때 식별자 구성을 선택해야 한다.
- 식별 관계 : 받아온 식별자를 기본 키 + 외래 키로 사용
- 비식별 관계 : 받아온 식별자는 외래 키로만 사용하고 새로운 식별자를 추가