@ManyToOne)@OneToMany)@OneToMany)@ManyToMany)이후 설명하는 다대일, 일대다 연관관계에서 회원(
Member)은 다(N), 팀(Team)은 일(1)로 서로 관계를 맺는다. 회원에서 팀 쪽으로 연관관계를 맺을 경우Member.team으로 객체 참조를 하고, 팀에서 회원쪽으로 연관관계를 맺을 경우Team.members로 컬렉션(List, Set 등)을 통한 참조를 한다.
Member.team으로 팀(1) 엔티티를 참조할 수 있음@JoinColumn 을 이용해 외래 키와 매핑Member.team필드로 회원 테이블의 TEAM_ID 외래 키를 관리Member.team으로 팀(1) 엔티티를 참조할 수 있음Team.members로 회원(N) 엔티티를 참조할 수 있음@JoinColumn, 반대쪽에는 mappedBy를 사용Team.members로 회원(N)을 참조하고 회원은 팀 참조 불가Team.members가 회원 테이블의 TEAM_ID 외래 키를 관리Member) 쪽에 있는데 관리는 일(Team) 쪽에서 함@JoinColumn을 명시해야 함Member의 외래 키를 사용하는게 아니라 조인 테이블 전략을 사용해서 매핑하게 됨@JoinColumn 생략은 @ManyToOne의 상황임에 주의INSERT문 한번으로 엔티티의 저장과 연관관계 처리를 끝내지 못하고 연관관계 처리를 위한 UPDATE문을 추가로 실행해야 함Member 엔티티는 Team엔티티를 모르므로 MEMBER 테이블의 TEAM_ID를 저장할 수 없음Team.members 참조를 확인해서 TEAM_ID를 업데이트 해주어야 함@ManyToOne에는 mappedBy가 없음@OneToMany는 연관관계의 주인이 될 수 없음Member와 Locker가 일대일로 연관관계를 맺는다고 가정했을 때, Member 쪽이 외래 키와 객체 참조를 모두 가지는 경우Member.locker에 @OneToOne으로 매핑@ManyToOne)과 거의 비슷Locker.member필드를 추가하고 @OneToOne(mappedBy = "locker")를 추가해주면 양방향 연관관계가 맺어짐Locker가 연관관계의 주인이 아님임을 명시@OneToOne(mappedBy = ), 대상 테이블의 엔티티에 @OneToOne과 @JoinColumn을 사용하여 매핑관계형 데이터베이스는 정규화된 테이블 2개로 다대다를 표현할 수 없음 → 연결 테이블 사용
MEMBER)과 상품(PRODUCT) 모두 다(N)에 해당하는 관계MEMBER_ID와 PRODUCT_ID를 컬럼으로 가지는 연결 테이블 MEMBER_PRODUCT를 만들어 다대다 관계를 일대다, 다대일 관계로 풀어내야 함MEMBER_PRODUCT에 있음MEMBER_PRODUCT가 MEMBER_ID와 PRODUCT_ID를 외래 키로 가지며, 또한 이 두 개가 기본 키가 됨@ManyToMany 사용INSERT 쿼리가 날아감회원(MEMBER)→상품(PRODUCT)으로만 연관관계가 있는 회원-상품 다대다 관계
@JoinColumn을 사용했다면 다대다에서는 @JoinTable 사용@JoinTable.name: 연결 테이블을 지정@JoinTable.joinColumns: 매핑할 조인 컬럼 정보를 지정MEMBER에 @JoinTable을 사용했으면 joinColumns속성은 MEMBER_ID로 지정@JoinTable.inverseJoinColumns: 반대 방향으로 매핑할 조인 컬럼 정보를 지정MEMBER에 @JoinTable을 사용했으면 PRODUCT_ID로 지정@ManyToMany 사용mappedBy 속성 사용@JoinTable 사용하면 됨편해 보이지만 실무에서 사용하기에 한계가 있음
@ManyToMany사용 불가능따라서 연결 테이블을 아예 엔티티로 만드는 방법이 있음(MemberProduct)
MemberProduct)엔티티와 회원, 상품을 각각 다대일 관계로 매핑mappedBy 사용하여 양방향을 만들거나 아예 연관관계를 맺어주지 않는 것을 선택할 수 있음MEMBER_ID와 PRODUCT_ID를 복합키로 하는 기본 키를 사용하려면 @IdClass를 사용해야 함@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;
...
}
public class MemberProductId implements Serializable {
private String member;
private String product;
// equals and hashcode...
}
@IdClass에는 복합키로 사용할 식별자 클래스를 만들어서 매핑해 줌@IdClass대신 @EmbeddedId를 사용할 수 있음MemberProduct)보다는 주문(Orders)이 더 적절한 네이밍이 됨관계형 데이터베이스에는 상속이라는 개념이 없지만 슈퍼타입 - 서브타입 관계라는 유사한 모델링 기법이 존재
슈퍼타입 - 서브타입 관계는 논리 모델로, 물리 모델인 테이블로 구현 시 전략을 선택
엔티티 각각을 모두 테이블로 만들고 자식 테이블이 부모 테이블의 기본 키를 받아서 기본 키 + 외래 키로 사용
@Inheritance(strategy = InheritanceType.JOINED 어노테이션과 @DiscriminatorColumn(name = {타입 구분 컬럼명}) 어노테이션을 사용DTYPE이므로 실제 테이블의 타입 구분 컬럼명이 DTYPE이면 name 생략 가능@DiscriminatorValue({구분 컬럼에 입력할 값})을 사용@PrimaryKeyJoinColumn 사용장점
단점
INSERT SQL이 두 번 실행테이블을 하나만 사용하며, 구분 컬럼(DTYPE)으로 어떤 자식 데이터가 저장되었는지 구분
NOT NULL일 수 없음BOOK에 해당하는 값을 저장할 때 ALBUM에 해당하는 값은 NULL이 들어갈 수 밖에 없음@Inheritance(strategy = InheritanceType.SINGLE_TABLE), @DiscriminatorColumn 사용@DiscriminatorVlalue를 지정하지 않으면 엔티티 이름을 사용장점
단점
NOT NULL 불가자식 엔티티마다 테이블을 만들고 각각에 필요한 컬럼이 모두 있는 전략
장점
NOT NULL 사용 가능단점
UNION을 사용해야 하므로 여러 자식 테이블을 함께 조회 시 성능 저하@MappedSuperclass부모 클래스는 테이블과 매핑하지 않고 자식 클래스에 상속 정보만 제공하고 싶을 때 사용
@MappedSuperclass를 사용하고 자식 클래스에서 상속@AttributeOverrides나 @AttributeOverride 사용@AssociationOverrides나 @AssociationOverride 사용@MappedSuperclass를 사용한 클래스는 엔티티가 아니므로 em.find나 JPQL에서 사용 x참고: 엔티티는 엔티티이거나 @MappedSuperclass로 지정한 클래스만 상속 가능
주의) 여기서 설명하는 부모 - 자식 테이블은 상속 관계가 아님. 기본 키를 내려받았음을 설명하기 위해 부모 - 자식으로 설명
둘 이상의 @Id를 사용하려면 별도의 식별자 클래스를 만들어야 함
@IdClass를 사용해서 식별자 클래스와 매핑 가능
@IdClass의 조건 설명 함em.persist를 호출하면 영속성 컨텍스트에 엔티티를 등록하기 전에 내부에서 식별자 클래스를 생성하고 영속성 컨텍스트의 키로 사용@JoinColumns 사용@JoinColumns 안에 @JoinColumn으로 각각의 외래 키 컬럼 매핑@JoinColumn 안에는 referencedColumnName 속성 사용(JoinColumn의 name과 같으면 생략 가능)@EmbeddedId도 사용 가능(조금 더 객체지향에 가까운 방법)
@EmbeddedId 어노테이션 붙여줌@Embeddable 어노테이션을 붙여주어야 함@IdClass보다 좋아보이지만 특정 상황에 JPQL이 좀 더 길어질 수 있음참고
@GenerateValue 사용 불가능@GenerateValue 사용 불가능equals hashCode를 필수적으로 오버라이딩 해야 하는데, 일반적으로 모든 필드를 사용부모, 자식, 손자까지 계속 기본 키를 전달하는 식별 관계에서 자식 테이블은 부모 테이블의 기본 키를 포함해 복합키를 구성해야 함
@IdClass 사용 시// 부모
@Entity
public class Parent {
@Id
@Column(name = "PARENT_ID")
private String id;
private String name;
...
}
// 자식
@Entity
@IdClass(ChildId.class)
public class Child {
@Id
@ManyToOne
@JoinColumn(name = "PARENT_ID")
public Parent parent;
@Id
@Column(name = "CHILD_ID")
private String childId;
private String name;
...
}
// 자식 ID
public class ChildId implements Serializable{
private String parent; // Child.parent 매핑
private String childId; // Child.childId 매핑
// equals, hashCode
...
}
// 손자
@Entity
@IdClass(GrandChildId.class)
public class GrandChild {
@Id
@ManyToOne
@JoinColumns({
@JoinColumn(name = "PARENT_ID"),
@JoinColumn(name = "CHILD_ID")
})
public Child child;
@Id @Column(name = "GRANDCHILD_ID")
private String id;
private String name;
}
// 손자 ID
public class GrandChildId implements Serializable{
private ChildId child; // GrandChild.child 매핑
private String id; // GrandChild.id 매핑
// equals, hashCode
...
}@Id와 @ManyToOne 같이 사용@EmbeddedId 사용 시@MapsId 사용해야 함
// 부모
@Entity
public class Parent {
@Id
@Column(name = "PARENT_ID")
private String id;
private String name;
...
}
// 자식
@Entity
public class Child {
@EmbeddedId
private ChildId childId;
@MapsId("parentId") // ChildId.parentId 매핑
@ManyToOne
@JoinColumn(name = "PARENT_ID")
public Parent parent;
private String name;
...
}
// 자식 ID
@Embeddable
public class ChildId implements Serializable {
private String parentId; // @MapsId("parentId")로 매핑
@Column(name = "CHILD_ID")
private String id;
//equals, hashCode
...
}
// 손자
@Entity
public class GrandChild {
@EmbeddedId
private GrandChildId id;
@MapsId("childId") // GrandChildId.childId 매핑
@ManyToOne
@JoinColumns({
@JoinColumn(name = "PARENT_ID"),
@JoinColumn(name = "CHILD_ID")
})
public Child child;
private String name;
...
}
// 손자 ID
@Embeddable
public class GrandChildId implements Serializable{
private ChildId childId; // @MapsId("childId")로 매핑
@Column(name = "GRANDCHILD_ID")
private String id; //
// equals, hashCode
...
}
식별 관계로 사용할 연관관계의 속성에 @Id 대신 @MapsId 사용
@MapsId는 외래 키와 매핑한 연관관계를 기본 키에도 매핑하겠다는 뜻
속성 값으로 @EmbeddedId를 사용한 식별자 클래스의 기본 키 필드 지정
일대일 식별 관계는 자식 테이블의 기본 키 값으로 부모 테이블의 기본 키 값 사용
@MapsId사용 시 속성 값을 비워둠일반적으로 식별 관계 보다는 비식별 관계를 선호
@GenerateValue 사용 불가능식별 관계가 비교적 가지는 장점도 있음
따라서 기본으로는 비식별 관계 + Long 타입의 대리 키를 사용하고 필요한 경우 식별 관계를 사용하는 것이 유리
선택적 비식별 관계는 NULL을 허용해서 OUTER JOIN을 사용해야 하므로 필수적 비식별 관계가 유리
조인 테이블이라는 별도의 테이블을 사용해서 연관관계를 관리하는 방식
@JoinColumn 자리에 @JoinTable사용@SecondaryTable을 사용해 한 엔티티에 여러 테이블을 매핑 가능
@SecondaryTable.name: 매핑할 다른 테이블의 이름@SecondaryTable.pkJoinColumns: 매핑 할 다른 테이블의 기본 키 컬럼 속성@Column(table = {다른 테이블 이름}) 지정해 주어야 함. 지정하지 않으면 기본 테이블에 매핑됨@SecondaryTables를 사용해 두 개 이상의 다른 테이블을 매핑 가능