이후 설명하는 다대일, 일대다 연관관계에서 회원(Member)은 다(N), 팀(Team)은 일(1)로 서로 관계를 맺는다. 회원에서 팀 쪽으로 연관관계를 맺을 경우 Member.team으로 객체 참조를 하고, 팀에서 회원쪽으로 연관관계를 맺을 경우 Team.members로 컬렉션(List, Set 등)을 통한 참조를 한다.
다대일
다대일 단방향 [N:1]
회원(N)은 Member.team으로 팀(1) 엔티티를 참조할 수 있음
팀에는 회원을 참조하는 필드가 없는 연관관계
N쪽의 필드를 @JoinColumn 을 이용해 외래 키와 매핑
Member.team필드로 회원 테이블의 TEAM_ID 외래 키를 관리
다대일 양방향 [N:1, 1:N]
회원(N)은 Member.team으로 팀(1) 엔티티를 참조할 수 있음
팀(1)도 Team.members로 회원(N) 엔티티를 참조할 수 있음
항상 다(N) 쪽에 외래 키가 있음
다 쪽이 연관관계의 주인이 되어야 함
주인이 아닌 연관관계는 조회를 위한 JPQL이나 객체 그래프 탐색에 사용
연관관계의 주인 쪽에는 @JoinColumn, 반대쪽에는 mappedBy를 사용
항상 서로를 참조해야 함
한 쪽만 참조하면 양방향 연관관계 성립 x
한 쪽 엔티티에 연관관계 편의 메서드를 작성해서 사용
일대다
일대다 단방향 [1:N]
팀(1)이 Team.members로 회원(N)을 참조하고 회원은 팀 참조 불가
Team.members가 회원 테이블의 TEAM_ID 외래 키를 관리
외래 키는 다(Member) 쪽에 있는데 관리는 일(Team) 쪽에서 함
@JoinColumn을 명시해야 함
명시하지 않으면 Member의 외래 키를 사용하는게 아니라 조인 테이블 전략을 사용해서 매핑하게 됨
현구막 기술 블로그에 나온 @JoinColumn 생략은 @ManyToOne의 상황임에 주의
매핑한 객체가 관리하는 외래 키가 다른 테이블에 있는 점이 단점
INSERT문 한번으로 엔티티의 저장과 연관관계 처리를 끝내지 못하고 연관관계 처리를 위한 UPDATE문을 추가로 실행해야 함
why? Member 엔티티는 Team엔티티를 모르므로 MEMBER 테이블의 TEAM_ID를 저장할 수 없음
이후에 Team.members 참조를 확인해서 TEAM_ID를 업데이트 해주어야 함
따라서 일대다 단방향 매핑 대신 다대일 단방향 매핑 사용을 권장
일대다 양방향 [1:N, N:1]
그런 거 없다.
양방향 연관관계를 맺으려면 연관관계의 주인이 아닌 쪽에 mappedBy를 사용해야 함
@ManyToOne에는 mappedBy가 없음
따라서 @OneToMany는 연관관계의 주인이 될 수 없음
일대다 단방향 매핑 반대편에 같은 외래 키를 사용하는 다대일 단방향 매핑을 읽기 전용으로 하나 추가하는 방법으로 우회는 가능하나, 이는 꼼수에 불과하며 일대다 단방향 매핑의 단점을 그대로 가짐 → 쓰지 말자
결론적으로 양방향 매핑을 하게 되면 거의 대부분 다대일 양방향 매핑을 사용하게 됨
일대일 [1:1]
일대일 관계의 특징
일대일 관계는 그 반대도 일대일 관계
주 테이블이나 대상 테이블 둘 중 어느 곳이나 외래 키를 가질 수 있음
주 테이블에 외래 키
외래 키를 객체 참조와 비슷하게 사용 가능
주 테이블만 확인해도 대상 테이블과 연관관계가 있는지 확인 가능
객체지향 개발자들이 선호
Member와 Locker가 일대일로 연관관계를 맺는다고 가정했을 때, Member 쪽이 외래 키와 객체 참조를 모두 가지는 경우
섹션 6. 다양한 연관관계 매핑
연관관계 매핑 시 고려할 것
다중성
@ManyToOne
)@OneToMany
)@OneToMany
)@ManyToMany
)단방향과 양방향
연관관계의 주인
다대일
다대일 단방향 [N:1]
Member.team
으로 팀(1) 엔티티를 참조할 수 있음@JoinColumn
을 이용해 외래 키와 매핑Member.team
필드로 회원 테이블의TEAM_ID
외래 키를 관리다대일 양방향 [N:1, 1:N]
Member.team
으로 팀(1) 엔티티를 참조할 수 있음Team.members
로 회원(N) 엔티티를 참조할 수 있음@JoinColumn
, 반대쪽에는mappedBy
를 사용일대다
일대다 단방향 [1:N]
Team.members
로 회원(N)을 참조하고 회원은 팀 참조 불가Team.members
가 회원 테이블의TEAM_ID
외래 키를 관리Member
) 쪽에 있는데 관리는 일(Team
) 쪽에서 함@JoinColumn
을 명시해야 함Member
의 외래 키를 사용하는게 아니라 조인 테이블 전략을 사용해서 매핑하게 됨@JoinColumn
생략은@ManyToOne
의 상황임에 주의INSERT
문 한번으로 엔티티의 저장과 연관관계 처리를 끝내지 못하고 연관관계 처리를 위한UPDATE
문을 추가로 실행해야 함Member
엔티티는Team
엔티티를 모르므로MEMBER
테이블의TEAM_ID
를 저장할 수 없음Team.members
참조를 확인해서TEAM_ID
를 업데이트 해주어야 함일대다 양방향 [1:N, N:1]
@ManyToOne
에는 mappedBy가 없음@OneToMany
는 연관관계의 주인이 될 수 없음일대일 [1:1]
일대일 관계의 특징
주 테이블에 외래 키
Member
와Locker
가 일대일로 연관관계를 맺는다고 가정했을 때,Member
쪽이 외래 키와 객체 참조를 모두 가지는 경우Member.locker
에@OneToOne
으로 매핑@ManyToOne
)과 거의 비슷Locker.member
필드를 추가하고@OneToOne(mappedBy = "locker")
를 추가해주면 양방향 연관관계가 맺어짐Locker
가 연관관계의 주인이 아님임을 명시대상 테이블에 외래 키
@OneToOne(mappedBy = )
, 대상 테이블의 엔티티에@OneToOne
과@JoinColumn
을 사용하여 매핑다대다 [N:N]
관계형 데이터베이스는 정규화된 테이블 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
를 사용해야 함@IdClass
에는 복합키로 사용할 식별자 클래스를 만들어서 매핑해 줌@IdClass
대신@EmbeddedId
를 사용할 수 있음MemberProduct
)보다는 주문(Orders
)이 더 적절한 네이밍이 됨섹션 7. 고급 매핑
상속 관계 매핑
관계형 데이터베이스에는 상속이라는 개념이 없지만 슈퍼타입 - 서브타입 관계라는 유사한 모델링 기법이 존재
슈퍼타입 - 서브타입 관계는 논리 모델로, 물리 모델인 테이블로 구현 시 전략을 선택
조인 전략
엔티티 각각을 모두 테이블로 만들고 자식 테이블이 부모 테이블의 기본 키를 받아서 기본 키 + 외래 키로 사용
@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
로 지정한 클래스만 상속 가능복합 키와 식별 관계 매핑
주의) 여기서 설명하는 부모 - 자식 테이블은 상속 관계가 아님. 기본 키를 내려받았음을 설명하기 위해 부모 - 자식으로 설명
식별 관계 vs 비식별 관계
복합 키 - 비식별 관계
둘 이상의
@Id
를 사용하려면 별도의 식별자 클래스를 만들어야 함@IdClass
를 사용해서 식별자 클래스와 매핑 가능@IdClass
의 조건 설명 함em.persist
를 호출하면 영속성 컨텍스트에 엔티티를 등록하기 전에 내부에서 식별자 클래스를 생성하고 영속성 컨텍스트의 키로 사용@JoinColumns
사용@JoinColumns
안에@JoinColumn
으로 각각의 외래 키 컬럼 매핑@JoinColumn
안에는referencedColumnName
속성 사용(JoinColumn
의 name과 같으면 생략 가능)@EmbeddedId
도 사용 가능(조금 더 객체지향에 가까운 방법)@EmbeddedId
어노테이션 붙여줌@Embeddable
어노테이션을 붙여주어야 함@IdClass
보다 좋아보이지만 특정 상황에 JPQL이 좀 더 길어질 수 있음참고
@GenerateValue
사용 불가능@GenerateValue
사용 불가능equals
hashCode
를 필수적으로 오버라이딩 해야 하는데, 일반적으로 모든 필드를 사용복합 키 - 식별 관계
부모, 자식, 손자까지 계속 기본 키를 전달하는 식별 관계에서 자식 테이블은 부모 테이블의 기본 키를 포함해 복합키를 구성해야 함
@IdClass
사용 시@Id
와@ManyToOne
같이 사용@EmbeddedId
사용 시@MapsId
사용해야 함@Id
대신@MapsId
사용@MapsId
는 외래 키와 매핑한 연관관계를 기본 키에도 매핑하겠다는 뜻@EmbeddedId
를 사용한 식별자 클래스의 기본 키 필드 지정일대일 식별 관계
일대일 식별 관계는 자식 테이블의 기본 키 값으로 부모 테이블의 기본 키 값 사용
@MapsId
사용 시 속성 값을 비워둠식별, 비식별 관계의 장단점
일반적으로 식별 관계 보다는 비식별 관계를 선호
@GenerateValue
사용 불가능식별 관계가 비교적 가지는 장점도 있음
따라서 기본으로는 비식별 관계 + Long 타입의 대리 키를 사용하고 필요한 경우 식별 관계를 사용하는 것이 유리
선택적 비식별 관계는 NULL을 허용해서 OUTER JOIN을 사용해야 하므로 필수적 비식별 관계가 유리
조인 테이블
조인 테이블이라는 별도의 테이블을 사용해서 연관관계를 관리하는 방식
@JoinColumn
자리에@JoinTable
사용엔티티 하나에 여러 테이블 매핑
@SecondaryTable
을 사용해 한 엔티티에 여러 테이블을 매핑 가능@SecondaryTable.name
: 매핑할 다른 테이블의 이름@SecondaryTable.pkJoinColumns
: 매핑 할 다른 테이블의 기본 키 컬럼 속성@Column(table = {다른 테이블 이름})
지정해 주어야 함. 지정하지 않으면 기본 테이블에 매핑됨@SecondaryTables
를 사용해 두 개 이상의 다른 테이블을 매핑 가능