ORM에서 이야기하는 상속 관계 매핑은 객체의 상속 구조와 데이터베이스의 슈퍼타입 서브타입 관계를 매핑하는 것이다.
1.3 구현 클래스마다 테이블 전략
@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class Item {
@Id @GeneratedValue
@Column(name = "ITEM_ID")
private Long id;
private String name; // 이름
private int price; // 가격
...
}
@Entity
public class Album extends Item {...}
@Entity
public class Movie extends Item {...}
@Entity
public class Book extends Item {...}
이 전략은 DB설계자와 ORM 전문가 모두 추천하지 않는 전략이다.
조인전략이나 단일 테이블 전략을 고려하는게 좋다.
장점
서브 타입을 구분해서 처리할 때 효과적
not null 제약조건을 사용할 수 있다.
단점
여러 자식 테이블을 함꼐 조회할 때 성능이 느리다(UNION 사용)
자식 테이블을 통합해서 쿼리하기 어렵다.
특징
구분 컬럼을 사용하지 않는다.
2. MappedSuperclass
부모 클래스는 테이블과 매핑하지 않고 부모 클래스를 상속 받는 자식 클래스에게 매핑 정보만 제공하고 싶으면 사용하는 어노테이션
비유하자면 @MappedSuperclass는 추상클래스와 비슷하다.
@Entity는 실제 테이블과 매핑되지만 @MappedSuperclass는 실제테이블과 매핑되지 않는다.
단순히 매핑 정보를 상속할 목적으로만 사용된다.
@MappedSuperclass
public abstract class BasreEntity {
@Id @GeneratedValue
private Long id;
private String name;
...
}
@Entity
public class Member extends BaseEntity {
// ID 상속
// NAME 상속
private String email;
...
}
@Entity
public class Seller extends BaseEntity {
// ID 상속
// NAME 상속
private String shopName;
...
}
BaseEntity에는 객체들이 주로 사용하는 공통 매핑 정보를 정의한다,
부모로부터 물려받은 매핑 정보를 제정의하려면 @AttributeOverrides나 @AttributeOverride 사용하고 연관관계를 재정의하려면 @AssociationOverrides나 @AssociationOverride를 사용한다.
@Entity
@AttributeOverride(name = "id", column = @Column(name = "MEMBER_ID"))
public class Member extends BaseEntity {...}
둘 이상을 재정의하려면 다음처럼 @AttirubteOverrides를 사용하면 된다.
@Entity
@AttributeOverrides({
@AttributeOverride(name = "id", column = @Column(name = "MEMBER_ID")),
@AttributeOverride(name = "name", column = @Column(name = "MEMBER_ID"))
})
public class Member extends BaseEntity {...}
테이블과 매핑되지 않고 자식 클래스에 엔티티의 매핑 정보를 상속하기 위해 사용한다.
@MappedSuperclass로 지정한 클래스는 엔티티가 아니므로 em,.find() 나 JPQL에서 사용할 수 없다.
이 클래스를 직접 생성해서 사용할 일은 거의 없으므로 추상 클래스로 만드는 것을 권장한다.
@MappedSuperclass를 사용하면 등록일자, 수정일자, 등록자, 수정자 같은 여러 엔티티에서 공통으로 사용하는 속성을 효과적으로 관리할 수 있다.
3. 복합 키와 실벽 관계 매핑
3.1 식별관계 vs 비식별 관계
식별 관계
식별 관계는 부모 테이블의 기본 키를 내려받아서 자식 테이블의 기본 키 + 외래키로 사용하는 관계다.
PARENT 테이블의 기본 키 PARENT_ID를 받아서 CHILD 테이블의 기본키(PK) + 외래 키(FK)로 사용한다.
비식별 관계
비식별 관계는 부모 테이블의 기본 키를 받아서 자식 테이블의 외래 키로만 사용하는 관계다.
비식별 관계는 외래 키에 NULL을 허용하는지에 따라 필수적 비식별 관계와 선택적 비식별 관계로 나눈다.
필수적 비식별 관계(Mandatory) : 외래 키에 NULL을 허용하지 않는다. 연관관계를 필수적으로 맺어야 한다.
선택적 비식별 관계(Optional) : 외래 키에 NULL을 허용한다. 연관관계를 맺을지 말지 선택할 수 있다.
최근에는 비식별 관계를 주로 사용하고 꼭 필요한 곳에만 식별 관계를 사용하는 추세다. JPA는 두 관계를 모두 지원한다.
3.2 복합 키 : 비식별 관계 매핑
// 기본키 1개
@Entity
public class Hello {
@Id
private String id;
}
// 기본키 2개 이상이면 이렇게 하면 될거 같지만..
@Entity
public class Hello {
@Id
private String id1;
@Id
private String id2; // 실행 시점에 매핑 예외 발생
}
JPA는 영속성 컨텍스트에 엔티티를 보관할 때 엔티티의 식별자를 키로 사용한다. 그리고 식별자를 구분하기 위해 equals와 hashCode를 사용해서 동등성 비교를 한다.
따라서 식별자 필드가 2개 이상이면 별도의 식별자 클래스를 만들고 그곳에 equals와 hashCode를 구현해야 한다.
JPA는 복합 키를 지원하기 위해 @IdClass와 @EmbeddedId 2가지 방법을 제공하는데 @IdClass는 관계형 데이터베이스에 가까운 방법이고 @EmbeddedId는 좀 더 객체 지향에 가까운 방법이다.
@IdClass
// 부모 클래스
@Entity
@IdClass(ParentId.class)
public class Parent {
@Id
@Column(name = "PARENT_ID1")
private String id1;
@Id
@Column(name = "PARENT_ID2")
private String id2;
private String name;
...
}
// 식별자 클래스
public class ParentId implements Serializable {
private String id1;
private String id2;
public ParentId() {}
public ParentId(String id1, String id2) {
this.id1 = id1;
this.id2 = id2;
}
@Override
public boolean equals(Object o) {...}
@Override
public int hashCode() {...}
}
ParentId id1 = new ParentId();
id1.setId1("myId1");
id1.setId2("myId2");
ParentId id2 = new ParentId();
id2.setId1("myId1");
id2.setId2("myId2");
id1.equals(id2);
equals()를 적절히 오버라이딩했다면 참이지만 그렇지 않으면 거짓이다.
자바의 모든 클래스는 기본으로 Object 클래스를 상속받는데 이 클래스가 제공하는 기본 equals()는 인스턴스 참조 값 비교인 == 비교(동일성 비교)를 하기 때문이다.
영속성 컨텍스는 엔티티의 식별자를 키로 사용해서 엔티티를 관리한다. 그리고 식별자를 비교할 때 equals()와 hashCode()를 사용한다,.
식별자 객체의 동등성이 지켜지지 않으면 엔티티를 관리하는데 심각한 문제가 발생하므로 복합 키는 equals()와 hashCode()를 필수로 구현해야 한다.
IdClass vs @EmbeddedId
@IdClass와 @EmbeddedId는 장단점을 비교해 본인의 취향에 맞게 사용하되 일관성 있게 사용해야한다.
@EmbeddedId가 @IdClass에 비해 좀 더 객체지향적이고 중복도 없어 좋아보이지만 특정 상황에선 JPQL이 좀 더 길어 질수 있다.
em.createQuery("select p.id.id1, p.id.id2 from Parent p"); // @EmbeddedId
em.createQuery("select p.id1, p.id2 from Parent p"); // @IdClass
3.3 복합키: 식별 관계 매핑
@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 매핑
// equlas, hashCode
...
}
// 손자
@Entity
@IdClass(GrandChildId.class)
public class GrandChild {
@Id
@ManyToOne
@JoinColumn({@JoinColumn(name = "PARENT_ID"), @JoinColumn(name = "CHILD_ID")})
private 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 매핑
// equlas, hashCode
...
}
식별 관계는 기본 키와 외래 키를 같이 매핑해야 한다. 따라서 식별자 매핑인 @Id와 연관관계 매핑인 @ManyToOne을 같이 사용하면 된다.
Child 엔티티의 parent 필드를 보면 @Id로 기본 키를 매핑하면서 @ManyToOne과 @JoinColumn으로 외래 키를 같이 매핑한다.
@EmbeddedId와 식별 관계
@EmbeddedId는 식별 관계로 사용할 연관관계의 속성에 @MapsId를 사용하면 된다.
@IdClass와 다른 점은 @Id 대신에 @MapsId를 사용한 점이다.
@MapsId는 외래키와 매핑한 연관관계를 기본 키에도 매핑하겠다는 뜻이다.
@MapsId의 속성 값은 @EmbeddedId를 사용한 식별자 클래스의 기본 키 필드를 지정하면 된다.
3.4 비식별 관계로 구현
3.5 일대일 식별 관계
3.6 식별, 비식별 관계의 장단점
데이터베이스 설계 관점에서 보면 식별 관계보다 비식별 관계를 선호한다.
식별 관계는 기본 키가 자식 테이블로 전파되면서 자식 테이블의 기본 키 컬럼이 점점 늘어난다. 결국 조인할 때 SQL이 복잡해지고 기본 키 인덱스가 불필요하게 커질 수 있다.
식별 관계는 2개 이상의 컬럼을 합해서 복합 기본 키를 만들어야 하는 경우가 많다.
식별 관계를 사용할 때 기본 키로 비즈니스 의미가 있는 자연 키 컬럼을 조합하는 경우가 많다. 반면 비식별 관계의 기본 키는 비즈니스와 전혀 관계없는 대리키를 주로 사용하는데 비즈니스 요구사항은 시간이 지남에 따라 언젠간 변하는데 식별 관계의 자연 키 컬럼들이 자식에 손자까지 전파되면 변경하기가 힘들어진다.
식별 관계는 부모 테이블의 기본 키를 자식 테이블이 기본 키로 사용하므로 비식별 관계보다 테이블 구조가 유연하지 못하다.
객체 관계 매핑의 관점에서 보면 다음과 같은 이유로 비식별 관계를 선호한다.
일대일 관계를 제외하고 식별 관계는 2개 이상의 컬럼을 묶은 복합키를 사용하는데 JPA에서 복합 키는 별도의 복합 키 클래스를 만들어서 사용해야 한다. 따라서 컬럼이 하나인 기본 키를 매핑하는 것보다 좀 더 많은 노력이 필요하다.
비식별 관계의 기본 키는 주로 대리키를 사용하는데 JPA는 @GenerateValue처럼 대리 키를 생성하기 위한 편리한 방법을 제공한다.
물론 식별 관계가 가지는 장점도 있다.
기본키를 활용하기 좋다.
상위 테이블들의 기본 키 컬럼을 자식, 손자 테이블들이 가지고 있으므로 특정 상황에서 조인 없이 하위 테이블만으로 검색을 완료할 수 있다.
정리하자면 ORM 신규 프로젝트 진행시 추천하는 방법은 될 수 있으면 비식별 관계를 사용하고 기본 키는 Long 타입의 대리 키를 사용하는 것이다.(Integer 20억, Long 920경)
선택적인 비식별 관계보다는 필수적 비식별 관계를 사용하는 것이 좋은데, 선택적인 비식별 관계는 NULL을 허용하므로 조인할 때 외부 조인을 사용해야 하는 반면 필수적 관계는 NOT NULL로 항상 관계가 있다는 것을 보장하므로 내부 조인만 사용해도 된다.
4. 조인 테이블
조인 컬럼 사용(외래 키)
테이블 간에 관계는 주로 조인 컬럼이라 부르는 외래 키 컬럼을 사용해서 관리한다.
회원과 사물함이 있는데 각각 테이블에 데이터를 등록했다가 회원이 원할 때 사물함을 선택할 수 있다고 가정해보면 회원이 사물함을 사용하기 전까지는 아직 둘 사이에 관계가 없으므로 MEMBER 테이블의 LOCKER_ID 외래 키에 null을 입력해 두어야 한다. 이렇게 외래 키에 null을 허용하는 관계를 선택적 비식별 관계라 한다.
선택적 비식별 관계는 외래 키에 null을 허용하므로 회원과 사물함을 조인할 때 OUTTER JOIN을 사용해야 하며 실수로 내부 조인을 사용하면 사물함과 관계가 없는 회원은 조회되지 않는다.
조인 테이블 사용(테이블)
조인 테이블이라는 별도의 테이블을 사용해서 연관관계를 관리한다.
연관관계를 관리하는 조인 테이블을 추가하고 여기서 두 테이블의 외래 키를 가지고 연관관계를 관리한다. 따라서 MEMBER와 LOCKER에는 연관관계를 관리하기 위한 외래 키 컬럼이 없다.
회원과 사물함 테이터를 각각 등록했다가 회원이 원할 때 사물함을 선택하면 MEMBER_LOCKER 테이블에만 값을 추가하면 된다.
단점은 테이블을 하나 추가해 관리해야 하는 테이블이 늘어나고 두 테이블을 조인하려면 MEMBER_LOCKER 테이블까지 추가로 조인해야 한다.
기본은 조인 컬럼을 사용하고 필요가 판된되면 조인 테이블을 사용하는 것이 좋다.
주로 다대다 관계를 일대다, 다대일 관계로 풀어내기 위해 사용한다. 그렇지만 일대일, 일대다, 다대일 관계에서도 사용한다.
4.1 일대일 조인 테이블
일대일 조인테이블 관계를 만드려면 조인 테이블의 외래 키 컬럼 각각에 총 2개의 유니크 제약조건을 걸어야 한다.
부모 엔티티를 보면 @JoinColumn 대신에 @JoinTable을 사용한다.
@JoinTable의 속성
name: 매핑할 조인 테이블 이름
joinCloumns: 현재 엔티티를 참조하는 외래 키
inverseJoinColumns: 반대방향 엔티티를 참조하는 외래 키
4.2 일대다 조인 테이블
일대다 관계를 만들려면 조인 테이블의 컬럼 중 다(N)와 관련된 컬럼에 유니크 제약조건을 걸어야 한다.
4.3 다대일 조인 테이블
다대일은 일대다에서 방향만 반대이므로 조인 테이블 모양은 일대다와 같다.
4.4 다대다 조인 테이블
다대다 관계를 만들려면 조인 테이블의 두 컬럼을 합해서 하나의 복합 유니크 제약조건을 걸어야 한다.
7.5 엔티티 하나에 여러 테이블 매핑
잘 사용하지 않지만 @SecondaryTable을 사용하면 한 엔티티에 여러 테이블을 매핑할 수 있다.
7. 고급 매핑
1. 상속 관계 매핑
1.3 구현 클래스마다 테이블 전략
2.
MappedSuperclass
@MappedSuperclass
는 추상클래스와 비슷하다.@Entity
는 실제 테이블과 매핑되지만@MappedSuperclass
는 실제테이블과 매핑되지 않는다.@AttirubteOverrides
를 사용하면 된다.@MappedSuperclass
로 지정한 클래스는 엔티티가 아니므로 em,.find() 나 JPQL에서 사용할 수 없다.@MappedSuperclass
를 사용하면 등록일자, 수정일자, 등록자, 수정자 같은 여러 엔티티에서 공통으로 사용하는 속성을 효과적으로 관리할 수 있다.3. 복합 키와 실벽 관계 매핑
3.1 식별관계 vs 비식별 관계
식별 관계
비식별 관계
3.2 복합 키 : 비식별 관계 매핑
@IdClass
와@EmbeddedId
2가지 방법을 제공하는데 @IdClass는 관계형 데이터베이스에 가까운 방법이고 @EmbeddedId는 좀 더 객체 지향에 가까운 방법이다.@IdClass
@IdClass
를 사용하 때 식별자 클래스는 다음 조건을 만족해야 한다.@JoinColumns
어노테이션을 사용하여 각각의 외래 키 컬럼을@JoinColumn
으로 매핑한다.`@EmbeddedId
@IdClass
가 데이터베이스에 맞춘 방법이라면@EmbeddedId
는 좀 더 객체지향적인 방법이다.@IdClass
와 다르게@EmbeddedId
를 적용한 시겹ㄹ자 클래스는 식별자 클래스에 기본 키를 직접 매핑한다.@EmbeddedId
를 적용한 식별자 클래스는 다음 조건을 만족해야 한다.@Embeddable
어노테이션을 붙여주어야 한다.복합 키와 equals(), hashCode()
IdClass vs @EmbeddedId
@IdClass
와@EmbeddedId
는 장단점을 비교해 본인의 취향에 맞게 사용하되 일관성 있게 사용해야한다.@EmbeddedId
가@IdClass
에 비해 좀 더 객체지향적이고 중복도 없어 좋아보이지만 특정 상황에선 JPQL이 좀 더 길어 질수 있다.3.3 복합키: 식별 관계 매핑
@IdClass와 식별관계
@EmbeddedId와 식별 관계
@EmbeddedId
는 식별 관계로 사용할 연관관계의 속성에 @MapsId를 사용하면 된다.@IdClass
와 다른 점은@Id
대신에@MapsId
를 사용한 점이다.@MapsId
는 외래키와 매핑한 연관관계를 기본 키에도 매핑하겠다는 뜻이다.@MapsId
의 속성 값은@EmbeddedId
를 사용한 식별자 클래스의 기본 키 필드를 지정하면 된다.3.4 비식별 관계로 구현
3.5 일대일 식별 관계
3.6 식별, 비식별 관계의 장단점
4. 조인 테이블
조인 컬럼 사용(외래 키)
조인 테이블 사용(테이블)
4.1 일대일 조인 테이블
4.2 일대다 조인 테이블
4.3 다대일 조인 테이블
4.4 다대다 조인 테이블
7.5 엔티티 하나에 여러 테이블 매핑
@SecondaryTable.name
: 매핑할 다른 테이블의 이름@SecondaryTable.pkJoinColumns
: 매핑할 다른 테이블의 기본 키 컬럼 속성@SecondaryTable
을 사용해서 두 테이블을 하나의 엔티티에 매핑하는 방법보다는 테이블당 엔티티를 각각 만들어서 일대일 매핑하는 것을 권장한다.