@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
를 사용해 두 개 이상의 다른 테이블을 매핑 가능