티스토리 뷰
상속 관계 매핑
객체의 상속 구조와 데이터베이스의 슈퍼타입 서브타입 관계를 매핑하는 것
조인 전략
엔티티를 각각을 모두 테이블로 만들고 자식테이블이 부모테이블의 기본키를 받아서 기본키 + 외래키로 사용
- 조회시 조인을 자주 사용
- 타입을 구분하는 컬럼을 추가(객체는 타입으로 구분 가능 하지만 테이블은 타입의 개념이 없음)
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn(name = "DTYPE")
public abstract class Item {
@Id @GeneratedValue
@Column(name = "ITEM_ID")
private Long id;
private String name;
private int price;
}
@Entity
@DiscriminatorValue("A")
public class Album extends Item {
private String artist;
}
@Entity
@DiscriminatorValue("M")
public class Movie extends Item {
private String director;
private String actor;
}
@Entity
@DiscriminatorValue("B")
@PrimaryKeyJoinColumn(name = "BOOK_ID")
public class Book extends Item {
private String author;
private String isbn;
}
@Inheritance(strategy = InheritanceType.JOINED)
상속 매핑은 부모 클래스에 이 어노테이션을 사용해야 함
@DiscriminatorColumn(name = "DTYPE")
부모 클래스에 구분 컬럼을 지정하여 저장된 자식 테이블 구분 가능
하이버네이트를 포함한 몇 구현체는 구분컬럼 없이도 동작
기본값 : DTYPE
@DiscriminatorValue
엔티티를 저장할 때 구분 컬럼에 입력할 값 저장
@PrimaryKeyJoinColumn
자식 테이블의 기본 키 컬럼 명을 부모 테이블의 ID 컬럼명이 아닌 다른 값으로 쓰고 싶을때 사용
장점
- 테이블이 정규화 됨
- 외래 키 참조 무결성 제약조건 활용 가능
- 외래키는 참조할 수 없는 값을 가질 수 없음, 외래키가 자신이 참조하는 릴레이션의 기본키와 상관없는 값을 가지게 되면 두 릴레이션을 연관시킬 수 없음
- 예를 들어 Order Table에서 외래키 참조로 ITEM을 봐야하면, ITEM_ID로 ITEM TABLE만 보면 된다.
- 출저 : https://kosaf04pyh.tistory.com/202
- 저장 공간을 효율적으로 사용
단점
- 조회 할 때 조인이 많이 사용 → 성능 저하
- 조회 쿼리 복잡
- 데이터 등록시 INSERT SQL 두 번 실행
- 서브 타입으로 조회시에는 필요한 서브타입을 조인해서 조회하지만 슈퍼 타입으로 조회시에는 전체 서브타입을 조인하여 그 테이블의 PK 값으로 조회하여 비효율적일 것으로 예상됨
단일 테이블 전략
테이블을 하나만 사용하고 구분 컬럼으로 어떤 자식 테이터가 저장되었는지 구분
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "DTYPE")
public abstract class Item {
@Id @GeneratedValue
@Column(name = "ITEM_ID")
private Long id;
private String name;
private int price;
}
@Entity
@DiscriminatorValue("A")
public class Album extends Item {
...
}
@Entity
@DiscriminatorValue("M")
public class Movie extends Item {
...
}
@Entity
@DiscriminatorValue("B")
@PrimaryKeyJoinColumn(name = "BOOK_ID")
public class Book extends Item {
...
}
장점
- 조인이 필요 없어 조회 성능이 빠름
- 조회 쿼리가 단순
단점
- 자식 엔티티가 매핑한 컬럼은 모두 null 허용
- 단일 테이블에 모든 것을 저장하여 테이블 커짐 → 상황에 따라 조회 성능 느려질 수 있음
특징
- 구분 컬럼을 꼭 사용해야 함
- @DiscriminatorColumn 필수
- @DiscriminatorValue 지정하지 않으면 기본값은 엔티티 이름
구현 클래스마다 테이블 전략(⇒ 비추천)
@InheritanceType.TABLE_PER_CLASS
장점
서브 타입을 구분해서 처리시 효과적이며 not null 제약조건 사용 가능
단점
여러 테이블 조회시 UNION을 사용해야해 성능이 느리며 자식 테이블을 통합해서 쿼리하기 어려움
특징
구분 컬럼을 사용하지 않음
@MappedSuperclass
@MappedSuperclass
public abstract class BaseEntity {
@Id @GeneratedValue
private Long id;
private String name;
}
@Entity
public class Member extends BaseEntity {
//id, name 상속
private String email;
}
@Entity
@AttributeOverride(name = "id", column = @Column(name = "MEMBER_ID"))
public class Rabbit extends BaseEntity {
//id, name 상속
...
}
@Entity
@AttributeOverrides({
@AttributeOverride(name = "id", column = @Column(name = "MEMBER_ID")),
@AttributeOverride(name = "name", column = @Column(name = "MEMBER_NAME"))
})
public class Dogg extends BaseEntity {
//id, name 상속
...
}
@AttributeOverrides, @AttributeOverride
부모에게 물려받은 매핑 정보를 재정의
@AssociationOverrides, @AssociationOverride
연관관계를 재정의
특징
- 부모 클래스는 테이블과 매핑하지 않고 조회(em.find()), JPQL 사용 불가능하다.
- (엔티티가 아님으로 테이블이 생성되지 않는다)
- 부모 클래스를 상속받는 자식 클래스에 공통으로 사용되는 매핑 정보를 제공하고 싶으면 사용한다.
- (등록일자, 수정일자, 등록자, 수정자 같은 여러 엔티티에서 공통으로 사용하는 속성 효과적 관리 가능)
- 직접 생성해서 사용할 일은 거의 없으므로 추상 클래스로 만들자
*엔티티(@Entity)는 엔티티(@Entity)이거나 @MappedSuperclass로 지정한 클래스만 상속 가능
식별 관계 vs 비식별 관계
식별관계
부모 테이블의 기본키를 내려받아서 자식테이블의 기본 키 + 외래키로 사용
비식별 관계
부모 테이블의 기본키를 받아서 자식 테이블의 외래 키로만 사용
필수적 비식별 관계(Mandatory)
외래 키에 NULL을 허용하지 않음, 연관관계를 필수적으로 맺어야 함
선택적 비식별 관계(Optional)
외래 키에 NULL을 허용, 연관관계를 맺을지 말지 선택 가능
복합 키 : 비식별 관계 매핑
JPA에서 식별자를 둘 이상 사용하려면 별도의 식별자 클래스를 만들어야 함
JPA는 영속성 컨텍스트에 엔티티를 보관할 때 엔티티의 식별자를 키로 사용, 이를 구분하기 위해 equals, hashcode를 사용해 동등성 비교를 한다.
식별자 필드가 하나 일때는 보통 자바의 기본 타입을 사용하므로 문제 없지만 2개 이상이면 별도의 식별자 클래스를 만들고 이곳에 equals와 hashcode를 구현해야 한다.
(자바의 모든 클래스는 Object 클래스를 상속 받고 이 클래스가 제공하는 equals()는 참조 값 비교 ==)
@IdClass
관계형 데이터베이스에 가까운 방법
@Entity
@IdClass(ParentId.class)
public class Parent {
@Id
@Column(name = "PARENT_ID1")
private String id1; //ParentId.id1과 연결
@Id
@Column(name = "PARENT_ID2")
private String id2; //ParentId.id2과 연결
private String name;
}
public class ParentId implements Serializable {
private String id1; //Parent.id1 매핑
private String id2; //Parent.id2 매핑
public ParentId() [
}
public ParentId(String id1, String id2) {
this.id1 = id1;
thid.id2 = id2;
}
@Override
public boolean equals(Object o) { ... }
@Override
public int hashCode() {...}
}
@IdClass를 사용할 때 식별자 클래스는 다음 조건을 만족해야 한다.
- 식별자 클래스의 속성명과 엔티티에서 사용하는 식별자의 속성명이 같아야 한다
- Parent.id1과 ParentId.id1, Parent.id2과 ParentId.id2
- Serializable 인터페이스를 구현해야 함
- equals, hashCode를 구현해야 함
- 기본 생성자가 있어야 함
- 식별자 클래스는 public이어야 함
저장
Parent parent = new Parant();
parent.setId1("myId1"); // 식별자
parent.setId2("myId2"); // 식별자
parent.setName("parentName");
em.persist(parent);
식별자 클래스는 ?
em.persist()를 호출하면 영속성 컨텍스트에 엔티티를 등록하기 직전에 내부에서 Parent.id1, Parent.id2 값을 사용해서 식별자 클래스인 ParentId를 생성하고 영속성 컨텍스트의 키로 사용한다.
조회
ParentId parentId = new ParentId("myId1","myId2");
Parent parent = em.find(Parent.class, parentId);
자식 클래스
@Entity
public class Child {
@Id
private String id;
@ManyToOne
@JoinColumns({
@JoinColumn(name = "PARENT_ID1",
referencedColumnName = "PARENT_ID1"),
//name과 referencedColumnName속성과 같으면 생략 가능
@JoinColumn(name = "PARENT_ID2")
})
private Parent parent;
}
부모 테이블의 기본 키 컬럼이 복합 키이므로 자식 테이블의 외래 키도 복합키
@EmbeddedId
객체지향적인 방법
@Entity
public class Parent {
@EmbeddedId
private ParentId id;
private String name;
}
@Embeddable
public class ParentId implements Serializable {
@Column(name = "PARENT_ID1")
private String id1;
@Column(name = "PARENT_ID2")
private String id2;
//equals와 hashcode를 구현
}
@EmbeddedId를 사용할 때 식별자 클래스는 다음 조건을 만족해야 한다.
- @Embeddable 어노테이션을 붙여야 함
- Serializable 인터페이스를 구현해야 함
- equals, hashCode를 구현해야 함
- 기본 생성자가 있어야 함
- 식별자 클래스는 public이어야 함
저장
Parent parent = new Parant();
ParentId parentId = new ParentId("myId1","myId2");
parent.setId(parentId);
parent.setName("parentName");
em.persist(parent);
조회
ParentId parentId = new ParentId("myId1","myId2");
Parent parent = em.find(Parent.class, parentId);
복합 키 : 식별 관계 매핑
@Entity
public class Parent {
@Id @Column(name = "PARENT_ID")
private String id;
private String name;
}
@Entity
@IdClass(ChildId.class)
//Parent는 복합키가 아님으로 여기에 씀
public class Child {
@Id
@ManyToOne
@JoinColumn(name = "PARENT_ID")
public Parent parent;
@Id @Column(name = "CHILD_ID")
private String childId;
private String name;
}
public class ChildId implements Serializable {
private String parent; //Child.parent 매핑
private String childId;
//equals, hashCode
}
@Entity
@IdClass(GrandChildId.class)
public class GrandChild {
@Id
@ManyToOne
@JoinColumns({
@JoinColumn(name = "PARENT_ID"),
@JoinColumn(name = "CHILD_ID")
})
private Child child;
@Id @Column(name = "GRANDCHILD_ID")
private String id;
private String name;
}
public class GrandChildId implements Serializable {
private ChildId child; //GrandChild.child 매핑
private String id; //GrandChild.id 매핑
}
식별 관계는 기본 키와 외래 키를 같이 매핑해야 한다.
따라서 식별자 매핑인 @Id와 연관관계 매핑인 @ManyToOne을 같이 사용하면 된다.
@Entity
public class Parent {
@Id @Column(name = "PARENT_ID")
private String id;
private String name;
}
@Entity
public class Child {
@EmbeddedId
private ChildId id;
@MapsId("parentId") //ChildId.parentId 매핑
@ManyToOne
@JoinColumn(name = "PARENT_ID")
public Parent parent;
private String name;
}
@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")
})
private Child child;
private String name;
}
@Embeddable
public class GrandChildId implements Serializable {
private ChildId childId; //@MapsId("childId")로 매핑
@Column(name = "GRANDCHILD_ID")
private String id;
//equals, hashCode
}
@MapsId
외래키와 매핑한 연관관계를 기본 키에도 매핑하겠다
속성값은 @EmbeddedId를 사용한 식별자 클래스의 기본 키 필드를 지정
복합 키 : 비식별 관계로 구현
// 부모
@Entity
public class Parent{
@Id @GeneratedValue
@Column(name = "PARENT_ID")
private Long id;
private String name;
...
}
// 자식
@Entity
public class Child{
@Id @GeneratedValue
@Column(name "CHILD_ID")
private Long id;
private String name;
@ManyToOne
@JoinColumn(name = "PARENT_ID")
private Parent parent;
...
}
// 손자
@Entity
public class GrandChild{
@Id @GeneratedValue
@Column(name = "GRANDCHILD_ID")
private Long id;
private String name;
@ManyToOne
@JoinColumn(name = "CHILD_ID")
private Child child;
...
}
일대일 식별 관계
일대일 식별 관계는 자식 테이블의 기본 키 값으로 부모 테이블의 기본 키 값만 사용한다. 그래서 부모 테이블의 기본 키가 복합 키가 아니면 자식 테이블의 기본 키는 복합 키로 구성하지 않아도 된다.
// 부모
@Entity
public class Board {
@Id @GeneratedValue
@Column(name = "BOARD_ID")
private Long id;
private String title;
@OneToOne(mappedBy = "board")
private BoardDetail boardDetail;
...
}
// 자식
@Entity
public class BoardDetail {
@Id
private Long boardId;
@MapsId // BoardDetail.boardId 매핑
@OneToOne
@JoinColumn(name = "BOARD_ID")
private Board board;
private String content;
...
}
ㄴ Id와 MapsId를 같이 쓴다는 점!
BoardDetail처럼 식별자가 단순히 컬럼 하나면 @MapsId를 사용하고 속성 값은 비우면 된다.
Board board = new Board();
board.setTitle("제목");
em.persist(board);
BoardDetail boardDetail = new BoardDetail();
boardDetail.setContent("내용");
boardDetail.setBoard(board);
em.persist(boardDetail);
데이터베이스 설계시 식별 관계보다는 비식별 관계를 선호하는 이유
- 식별 관계는 부모 테이블의 기본 키를 자식 테이블로 전파하면서 자식 테이블의 기본 키 컬럼이 점점 늘어남
- → 조인할 때 SQL이 복잡해지고 기본 키 인덱스가 불필요하게 커짐
- 식별 관계는 복합 기본 키를 만들어야 하는 경우가 많다
- 식별 관계는 자연 키 컬럼 조합하는 경우가 많음. 반면, 비식별 관계의 기본키는 대리키를 주로 사용
- 식별 관계는 부모 테이블의 기본 키를 자식 테이블의 기본 키로 사용하므로 비식별 관계보다 테이블 구조가 유연하지 못함
식별 관계 사용시 장점
키본 키 인덱스를 활용하기 좋음
상위 테이블들의 기본 키 컬럼을 자식, 손자 테이블이 가지고 있음으로 특정 상황에서 조인 없이 하위테이블 만으로 검색 완료 가능
예시 ) 부모 아이디가 A인 모든 자식 조회
SELECT * FROM CHILD WHERE PARENT_ID = 'A'
부모 아이디가 A이고 자식 아이디가 B인 자식 조회
SELECT * FROM CHILD WHERE PARENT_ID = 'A' AND CHILD_ID = 'B'
두 경우 모두 CHILD 테이블의 기본 키 인덱스를 PARENT_ID + CHILD_ID로 구성하면 별도의 인덱스를 생성할 필요 없이 기본 키 인덱스만 사용해도 된다.
비식별 관계를 사용하고 기본 키는 Long 타입의 대리 키를 사용하자. 선택적 보단 필수적 비식별 관계를 사용하자 (NOT NULL로 항상 관계가 있음으로 내부 조인 가능!)
조인 테이블
조인 컬럼 사용(외래키)
❗ 기본은 조인 컬럼을 사용하고 필요시 조인 테이블 사용
단점
- 선택적 비식별 관계 사용시 내부 조인을 사용하면 관계가 없는 테이블의 데이터는 조회가 되지 않음
- 아주 가끔 관계를 맺는다면 외래 키 값 대부분이 null로 저장
조인 테이블 사용(테이블 사용)
연관 관계를 관리하는 테이블을 추가하고 여기서 두 테이블의 외래 키를 가지고 연관 관계를 관리한다.
단점
- 테이블을 하나 추가함으로 관리해야 하는 테이블이 늘어남
- 조인 테이블까지 추가로 조인해야 함
일대일 조인 테이블
일대일 관계를 만드려면 조인 테이블의 외래키 컬럼 각각에 총 2개의 유니크 제약조건을 걸어야 한다.
(PARENT_ID는 기본키이므로 유니크 제약조건이 걸려 있음)
//부모
@Entity
public class Parent {
@Id @GeneratedValue
@Column(name = "PARENT_ID")
private Long id;
private String name;
@OneToOne
@JoinTable(name = "PARENT_CHILD",
joinColumns = @JoinColumn(name = "PARENT_ID"),
inverseJoinColumns = @JoinColumn(name = "CHILD_ID"))
private Child child;
...
}
//자식
@Entity
public claass Child {
@Id @GeneratedValue
@Column(name = "CHILD_ID")
//@OneToOne(mappedBy="child") 양방향으로 매핑하는 경우 사용
private Long id;
private String name;
...
}
@JoinTable의 속성
name
- 매핑할 조인 테이블의 이름
joinColumns
- 현재 엔티티를 참조하는 외래 키
inverseJoinColumns
- 반대방향 엔티티를 참조하는 외래키
일대다 조인 테이블
조인 테이블의 다(N)와 관련된 컬럼인 CHILD_ID에 유니크 제약조건을 걸어야 한다.
//부모
@Entity
public class Parent {
@Id @GeneratedValue
@Column(name = "PARENT_ID")
private Long id;
private String name;
@OneToMany
@JoinTable(name = "PARENT_CHILD",
joinColumns = @JoinColumn(name = "PARENT_ID"),
inverseJoinColumns = @JoinColumn(name = "CHILD_ID"))
private List<Child> child = new ArrayList<Child>();
...
}
//자식
@Entity
public class Child {
@Id @GeneratedValue
@Column(name = "CHILD_ID")
private Long id;
private String name;
}
다대일 조인 테이블
//부모
@Entity
public class Parent {
@Id @GeneratedValue
@Column(name = "PARENT_ID")
private Long id;
private String name;
@OneToMany(mappedBy = "parent")
private List<Child> child = new ArrayList<Child>();
...
}
//자식
@Entity
public class Child {
@Id @GeneratedValue
@Column(name = "CHILD_ID")
private Long id;
private String name;
//연관된 엔티티가 언제나 존재한다.
@ManyToOne(optional = false)
@JoinTable(name = "PARENT_CHILD",
joinColumns = @JoinColumn(name = "CHILD_ID"),
inverseJoinColumns = @JoinColumn(name = "PARENT_ID"))
private Parent parent;
...
}
다대다 조인 테이블
조인 테이블의 두 칼럼을 합해서 하나의 복합 유니크 제약조건을 걸어야 한다.
(PARENT_ID, CHILD_ID는 복합 기본키 임으로 유니크 제약조건이 걸려있다.)
//부모
@Entity
public class Parent {
@Id @GeneratedValue
@Column(name = "PARENT_ID")
private Long id;
private String name;
@ManyToMany
@JoinTable(name = "PARENT_CHILD",
joinColumns = @JoinColumn(name = "PARENT_ID"),
inverseJoinColumns = @JoinColumn(name = "CHILD_ID"))
private List<Child> child = new ArrayList<Child>();
...
}
//자식
@Entity
public class Child {
@Id @GeneratedValue
@Column(name = "CHILD_ID")
private Long id;
private String name;
}
엔티티 하나에 여러 테이블 매핑 (@SecondaryTable)
잘 사용하지 않음
@Entity
@Table(name = "BOARD")
@SecondaryTable(name = "BOARD_DETAIL", // 매핑할 테이블
pkJoinColumns = @PrimaryKeyJoinColumn(name = "BOARD_DETAIL_ID"))
//매핑할 다른 테이블의 테
public class Board {
@Id @GeneratedValue
@Column(name = "BOARD_ID")
private Long id;
private String title;
@Column(table = "BOARD_DETAIL")
private String content;
...
}
테이블당 엔티티를 각각 만들어 일대일 매핑하는 것을 권장, 항상 두 테이블 조회함으로 최적화 어려움
출저 : 김영한 저 , 자바 ORM 표준 JPA 프로그래밍 스프링 데이터 예제 프로젝트로 배우는 전자정부 표준 데이터베이스 프레임워크, 에이콘출판사 (2015) , 7장
- Total
- Today
- Yesterday
- 글또
- 누출 버킷 알고리즘
- 알고리즘
- 처리율제한
- 이동 윈도우 카운터 알고리즘
- 회고
- 처리율 제한 알고리즘
- 카카오프로젝트100
- 개발자
- 고정 윈도우 카운터 알고리즘
- 이동 윈도우 로깅 알고리즘
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |