🟩 데이터베이스 슈퍼타입 서브타입 관계
데이터베이스에서 슈퍼타입과 서브타입은 데이터간의 일반화와 특수화한 관계를 나타낸다.
다음과 같이 상품에 대한 상품 구분이 있을 때 다음과 같이 표현할 수 있다.
이때 상품을 슈퍼타입이라고 하고
상품 구분을 서브타입이라고 한다.
- 배타적 : 슈퍼타입은 많아야 단 하나의 서브타입과 관련된다.
- 포괄적 : 슈퍼타입은 하나 이상의 서브타입과 관련된다.
그리고 이러한 슈퍼타입 서브타입 모델을 테이블로 구현할 때 3가지 방법이 있다.
01. 각각의 테이블로 변환
- 상품_타입 컬럼을 통해서 어떤 상품을 나태는지를 확인하고 상품_식별자값으로 조인하여 나태낼 수 있다.
02. 통합 테이블로 변환
- 통합 테이블의 경우 모든 상품을 하나의 테이블로 관리하게 된다.
- 상품_타입을 통해서 상품을 구분하고 필요없는 컬럼이 생긴다.
03. 서브타입 테이블로 변환
- 슈퍼타입의 컬럼 정보를 각 상품마다 갖게하여 테이블을 만든다.
이러한 슈퍼타입 서브타입 관계는 객체지향에서는 상속과 매우 비슷하기 때문에
ORM에서 상속 관계 매핑은 데이터베이스 슈퍼타입 서브타입 관계를 매핑하는 것을 의미한다.
🟩 테이블 매핑 정보 상속 (@Inheritance, @DiscriminatorColumn)
@Inheritance 어노테이션
- 부모 클래스에 @Inheritance을 사용한다.
- 매핑 전략을 지정해야 한다.
@DiscriminatorColumn 어노테이션
- 부모 클래스에 구분 컬럼을 지정한다.
@DiscriminatorValue 어노테이션
- 엔티티를 저장할 때 구분 컬럼에 입력할 값을 지정한다.
@PrimaryKeyJoinColumn 어노테이션
- 자식 테이블의 기본키 컬러명을 변경하고 싶을 때 사용한다.
01. 조인 전략 (각각의 테이블로 변환)
- 엔티티 각각을 모두 테이블로 만든다.
- 자식 테이블이 부모 테이블의 기본키를 받아서 기본키 + 외래키로 사용한다.
- 타입을 구분하는 컬럼을 추가해야 한다. (상품_타입, DTYPE)
01-01. 조인 전략 매핑 코드
@DiscriminatorColumn(name = "DTYPE")
@Inheritance(strategy = InheritanceType.JOINED)
@Entity
public class Item {
@Id @GeneratedValue
private Long id;
private String name;
}
@DiscriminatorValue("M")
@Entity
public class Movie extends Item {
private String director;
}
@DiscriminatorValue("A")
@Entity
public class Album extends Item {
private String artist;
}
01-02. 조인 전략 장점
- 테이블 정규화
- 외래키 참조 무결성 제약조건 활용 가능
- 저장 공간 효율적
01-03. 조인 전략 단점
- 조인이 많이 사용되므로 성능 저하될 수 있다.
- 조인으로 인해 조회 쿼리가 복잡하다.
- 데이터를 삽입할 때 INSERT SQL을 두 번 실행한다.
02. 단일 테이블 전략 (통합 테이블로 변환)
- 테이블을 하나만 사용한다.
- 구분 컬럼(DTYPE)으로 어떤 자식 데이터가 저장되었는지 구분한다.
- 조인을 사용하지 않으므로 조회가 빠르다.
02-01. 단일 테이블 전략 매핑 코드
@DiscriminatorColumn(name = "DTYPE")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@Entity
public class Item {
@Id @GeneratedValue
private Long id;
private String name;
}
@DiscriminatorValue("M")
@Entity
public class Movie extends Item {
private String director;
}
@DiscriminatorValue("A")
@Entity
public class Album extends Item {
private String artist;
}
02-02. 단일 테이블 장점
- 조인이 필요 없으므로 조회가 빠르며 쿼리가 단순하다.
02-03. 단일 테이블 단점
- 자식 엔티티가 매핑한 컬럼은 모두 null을 허용해야 한다.
- 단일 테이블이기 때문에 테이블이 커질 수 있다.
03. 구현 클래스마다 테이블 전략 (서브타입 테이블로 변환)
- 자식 엔티티마다 테이블을 만든다.
- 자식 테이블 각각 모두 필요한 컬럼이 있다.
03-01. 서브타입 테이블 전략 매핑 코드
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
@Entity
public class Item {
@Id @GeneratedValue
private Long id;
private String name;
}
@Entity
public class Movie extends Item {
private String director;
}
@Entity
public class Album extends Item {
private String artist;
}
02-02. 서브타입 테이블 장점
- 서브 타입을 구분해야할 때 좋다.
- not null 제약 조건을 이용할 수 있다.
02-03. 서브타입 테이블 단점
- 여러 자식 테이블을 함께 조회할 경우 성능이 느리다.
- 자식 테이블을 통합해서 쿼리하기 어렵다.
- (추천하지 않는 전략이다.)
🟩 매핑 정보 상속 (@MappedSuperclass)
- @MappedSuperclass는 테이블과 관계가 없고 단순히 엔티티가 공통으로 사용하는 매핑 정보를 모아주는 역할이다.
01. @MappedSuperclass 특징
- 테이블과 매핑되지 않고 자식 클래스에 엔티티의 매핑 정보를 상속한다.
- @MappedSuperclass을 사용한 클래스는 엔티티가 아니므로 em.find()나 JPQL을 사용할 수 없다.
02. @MappedSuperclass 매핑 코드
- 등록일자, 수정일자, 삭제일자 같은 공통 속성을 관리하기 좋다.
@MappedSuperclass
public abstract class BaseEntity {
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
private LocalDateTime deletedAt;
}
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
private String name;
}
@Entity
public class Product {
@Id @GeneratedValue
private Long id;
private String title;
}
🟩 복합 키와 식별 관계 매핑 (@IdClass, @EmbeddableId)
- 복합키 : 데이터베이스에서 하나 이상의 컬럼을 조합하여 유일한 식별자로 사용하는 키이다.
01. 식별 관계와 비식별 관계
데이터베이스의 테이블 사이에서 외래키가 기본키의 포함 여부에 따라 식별/비식별 관계가 구분된다.
01-01. 식별 관계 (Identifying Relationship)
- 식별 관계 : 부모 테이블의 기본키(PK)를 "자식 테이블의 기본키(PK) + 외래키(FK)"로 사용하는 관계를 의미한다.
01-02. 비식별 관계 (Non-Identifying Relationship)
- 비식별 관계 : 부모 테이블의 기본키(PK)를 자식의 외래키(FK)로만 사용한다.
01-03. 필수적/선택적(Mandatory/Optional) 비식별 관계
- 필수적 비식별 관계 : 외래키(FK)에 null을 허용하지 않는다. (연관관계가 필수적이다)
- 선택적 비식별 관계 : 외래키(FK)에 null을 허용한다. (연관관계가 필수적이지 않다.)
02. 복합 키의 비식별 관계 매핑
- 복합 기본키(둘 이상의 컬럼으로 구성) : 별도의 식별자 클래스를 만들어야 한다.
- JPA에서는 복합키를 지원하기 위해 @IdClass(데이터베이스에 가까운 방법), @EmbeddedId(객체지향에 가까운 방법)를 제공하고 있다.
02-01. @IdClass와 비식별 관계 매핑
- 데이터베이스에 맞춘 방법이다.
- 복합키를 매핑하기 위해서 식별자 클래스를 만들어야 한다.
식별자 클래스를 만든다.
- 식별자 클래스는 속성명이 엔티티 클래스 식별자의 속성명과 같아야 한다.
- Serializable 인터페이스를 구현해야 한다.
- equals, hashcode를 재정의해야 한다.
- 기본 생성자가 있어야 한다.
public class ParentId implements Serializable {
private Long id1; // Parent.id1
private Long id2; // Parent.id2
public ParentId() {
}
public ParentId(Long id1, Long id2) {
this.id1 = id1;
this.id2 = id2;
}
@Override
public boolean equals(Object o) {...}
@Override
public int hashCode() {...}
}
부모 클래스를 만든다.
@IdClass(ParentId.class)
@Entity
public class Parent {
@Id
private Long id1; // ParentId.id1
@Id
private Long id2; // ParentId.id2
private String name;
}
자식 클래스를 만든다.
- 부모 테이블의 기본키가 복합키이다. 그러므로 자식 테이블의 외래키도 복합키다.
- 외래키 매핑시 기존에 @JoinColumn만 이용했었다. 현재는 여러 컬럼을 매핑하므로 @JoinColumns를 이용하면 된다.
@Entity
public class Child {
@Id
private Long id;
@ManyToOne
@JoinColumns({
@JoinColumn(name = "parent_id1", referencedColumnName = "parent_id1"),
@JoinColumn(name = "parent_id2", referencedColumnName = "parent_id2")
)}
private Parent parent;
}
@IdClass를 사용한 코드 예시
부모 엔티티의 속성에 값을 넣어두면 영속성 컨텍스트에 엔티티를 등록하기 전에
식별자 클래스인 ParentId를 생성하여 영속성 컨텍스트의 키로 사용한다.
Parent parent = new Parent();
parent.setId1(1L);
parent.setId2(2L);
parent.setName("헤나");
em.persist(parent);
02-02. @EmbeddedId와 비식별 관계 매핑
- 객체지향에 맞춘 방법이다.
식별자 클래스를 만든다.
- @IdClass와 다르게 @EmbeddedId를 적용한 식별자 클래스는 식별자 클래스에 기본키를 직접 매핑한다.
@Embeddable
public class ParentId implements Serializable {
@Column(name = "parent_id1");
private String id1;
@Column(name = "parent_id2");
private String id2;
// equals, hashcode ...
}
부모 클래스를 만든다.
@Entity
public class Parent {
@EmbeddedId
private ParentId id;
private String name;
}
@EmbeddedId를 사용한 코드 예시
Parent parent = new Parent();
parent.setId(new ParentId(1L, 2L));
parent.setName("헤나");
em.persist(parent);
02-03. @IdClass와 @EmbeddedId
@IdClass와 @EmbeddedId는 각각 장단점이 있다.
@EmbeddedId이 더 객체지향적이지만 JPQL을 이용할 때 길어질 수 도 있다.
// @EmbeddedId
SELECT p.id.id1, p.id.id2 FROM Parent p
// @IdClass
SELECT p.id1, p.id2 FROM Parent p
03. 복합 키의 식별 관계 매핑
- 식별 관계에서 자식 테이블은 부모 테이블의 기본키를 포함해서 복합키를 구성한다.
03-01. @IdClass와 식별 관계 매핑
- 식별 관계는 기본키와 외래키를 같이 매핑한다.
- 식별자 매핑 @Id + 연관관계 매핑 @ManyToOne을 같이 사용한다.
@Entity
public class Parent {
@Id
private Long id;
private String name;
}
@IdClass(ChildId.class)
@Entity
public class Child {
@ManyToOne
@JoinColumn(name = "parent_id")
@Id
public Parent parent;
@Id
private Sring childId;
private String name;
}
@IdClass(GrandChildId.class)
@Entity
public class GrandChild {
@ManyToOne
@JoinColumns({
@JoinColumn(name = "parent_id"),
@JoinColumn(name = "child_id")
})
@Id
private Child child;
@Id
private Long id;
private String name;
}
// 식별자 클래스
public class ChildId implements Serializable {
private Long parent; // Child.parent 매핑
private Long childId; // Child.childId 매핑
}
public class GrandChildId implements Serializable {
private ChildId child; // GrandChild.child 매핑
private Long id; // GrandChild.id 매핑
}
03-02. @EmbeddedId와 식별 관계 매핑
- @EmbeddedId로 식별 관계를 구성할 때 @MapsId 를 사용하면 된다.
- @MapsId : @EmbeddedId를 사용한 식별자 클래스의 기본키 필드를 지정
@Entity
public class Parent {
@Id
private Long id;
private String name;
}
@Entity
public class Child {
@EmbeddedId
private ChildId id;
@MapsId("parentId")
@ManyToOne
@JoinColumn(name = "parent_id")
private Parent parent;
private String name;
}
@Entity
public class GrandChild {
@EmbeddedId
private GrandChildId id;
@MapsId("childId")
@ManyToOne
@JoinColumns({
@JoinColumn(name = "parent_id"),
@JoinColumn(name = "child_id")
)}
private Child child;
private String name;
}
// 식별자 클래스
@Embeddable
public class ChildId implements Serializable {
private Long parentId;
@Column(name = "child_id)
private Long childId;
// equals, hashcode ...
}
@Embeddable
public class GrandChildId implements Serializable {
private ChildId child; // @MapsId("childId")로 매핑
@Column(name = "grand_child_id")
private Long id;
// equals, hashcode ...
}
🔗 참고
자바 ORM 표준 JPA 프로그래밍 - YES24
자바 ORM 표준 JPA는 SQL 작성 없이 객체를 데이터베이스에 직접 저장할 수 있게 도와주고, 객체와 관계형 데이터베이스의 차이도 중간에서 해결해준다. 이 책은 JPA 기초 이론과 핵심 원리, 그리고
www.yes24.com
'😋 JPA' 카테고리의 다른 글
[JPA] 영속성 전이, 고아 객체 (Cascade, Orphan) (0) | 2023.06.16 |
---|---|
[JPA] 프록시, 즉시 로딩, 지연 로딩 - (FetchType.EAGER, FetchType.LAZY) (0) | 2023.06.16 |
[JPA] 다양한 연관관계 매핑 - 다대일, 일대다, 일대일, 다대다 연관관계 (0) | 2023.06.15 |
[JPA] 연관관계 매핑 기초 - 단방향, 양방향, 객체 그래프 탐색, 연관 관계 주인, @JoinColumn, mappedBy (0) | 2023.06.15 |
[JPA] 필드와 컬럼 매핑 - @Column, @Enumerated, @Temporal, @Lob, @transient, @Access (0) | 2023.06.14 |