티스토리 뷰
| 고급 매핑
:상속 관계 매핑
:@MappedSuperclass
:복합 키와 식별 관계 매핑
:조인 테이블
:엔티티 하나에 여러 테이블 매핑
|| 상속 관계 매핑
슈퍼타입 서브타입 논리 모델을 실제 물리 모델인 테이블로 구현 시 선택할 수 있는 방법
1. 각각의 테이블로 변환 : 각각을 모두 테이블로 만들고 조회 시 조인 사용
2. 통합 테이블로 변환 : 테이블을 하나만 사용해서 통합
3. 서브타입 테이블로 변환 : 서브 타입마다 하나의 테이블 생성
||| 조인 전략
엔티티 각각을 모두 테이블로 만들고
자식 테이블이 부모 테이블의 기본 키를 받아서 기본 키 + 외래 키로 사용
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | @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") // 부모 테이블의 ID 컬럼명 변경 (ITEM_ID -> BOOK_ID) public class Movie extends Item { private String director; private String actor; // ... } | cs |
> 장점
- 테이블이 정규화
- 외래 키 참조 무결성 제약조건 활용 가능
- 저장공간을 효율적으로 사용
> 단점
- 조회 시 조인이 많이 사용되어 성능 저하 가능성
- 조회 쿼리가 복잡
- 데이터 등록 시 INSERT SQL을 두 번 실행
> 관련 Annotation
@PrimaryKeyJoinColumn
@DiscriminatorColumn
@DiscriminatorValue
||| 단일 테이블 전략
- 테이블을 하나만 사용하고, 구분 컬럼(DTYPE)으로 어떤 자식 데이터가 저장되었는지 구분
- 조회 시 조인을 사용하지 않으므로 일반적으로 빠름
- 주의할 점은 자식 엔티티가 매핑한 컬럼은 모두 null 허용
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | @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 { private String artist; // ... } @Entity @DiscriminatorValue("M") public class Movie extends Item { private String director; private String actor; // ... } @Entity @DiscriminatorValue("B") @PrimaryKeyJoinColumn(name = "BOOK_ID") // 부모 테이블의 ID 컬럼명 변경 (ITEM_ID -> BOOK_ID) public class Movie extends Item { private String director; private String actor; // ... } | cs |
> 장점
- 조인이 필요 없으므로 일반적으로 조회 성능이 빠름
- 조회 쿼리가 단순
> 단점
- 자신 엔티티가 매핑한 컬럼은 모두 null 허용
- 단일 테이블에 모든 것을 저장하므로 테이블이 커질 수 있음
상황에 따라 조회 성능 저하
> 특징
- 구분 컬럼은 필수 (@DiscriminatorColumn)
- @DiscriminatorValue를 지정하지 않으면 기본으로 엔티티 이름을 사용
||| 구현 클래스마다 테이블 전략
자식 엔티티마다 테이블을 만듦
추천하지 않는 전략
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 | @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 Movie extends Item { // ... } | cs |
> 장점
- 서브 타입을 구분해서 처리 시 효과적
- not null 제약조건 사용 가능
> 단점
- 여러 자식 테이블을 함께 조회 시 성능이 느림 (UNION)
- 자식 테이블을 통합해서 쿼리하기 어려움
> 특징
- 구분 컬럼을 사용하지 않음
|| @MappedSuperclass
- 부모 클래스는 테이블과 매핑하지 않고 부모 클래스를 상속 받은 자식 클래스에게 매핑 정보만 제공
- 실제 테이블과는 매핑되지 않음, 단순히 매핑 정보를 상속할 목적으로 사용
- "등록일자, 수정일자, 등록자, 수정자" 같은 여러 엔티티에서 공통으로 사용하는 속성을 효과적으로 관리
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 | @MappedSuperclass public abstract class BaseEntity { @Id @GeneratedValue private Long id; private String name; // ... } @Entity // 매핑 정보 재정의 @AttributeOverride(name = "id", column = @Column(name = "MEMBER_ID")) public class Member extends BaseEntity { // id, name 상속 private String email; // ... } @Entity // 둘 이상 재정의 @AttributeOverrides({ @AttributeOverride(name = "id", column = @Column(name = "SELLER_ID")), @AttributeOverride(name = "name", column = @Column(name = "SELLER_NAME")) }) public class Seller extends BaseEntity { // id, name 상속 private String shopName; // ... } | cs |
* 테이블과 매핑되지 않고 자식 클래스에 엔티티의 매핑 정보를 상속하기 위해 사용
* @MappedSuperclass로 지정한 클래스는 엔티티가 아니므로 em.find()나 JPSQL 에서 사용 불가
* 이 클래스를 직접 생성해서 사용할 일은 거의 없으므로 추상 클래스로 만드는 것을 권장
* Entity는 Entity or @MappedSuperclass로 지정한 클래스만 상속 가능
|| 복합 키와 식별 관계 매핑
||| 식별 관계 vs 비식별 관계
외래 키가 기본 키에 포함되었는지 여부에 따라 구분
ㅇ 비식별 관계
- 부모 테이블의 기본 키를 받아서 자식 테이블의 외래 키로만 사용
- 외래 키에 NULL 허용 여부에 따라 나뉨
ㄴ 필수적 비식별 관계 : 외래 키에 NULL 허용 X, 연관관계를 필수적으로 맺어야 함
ㄴ 선택적 비식별 관계 : 외래 키에 NULL 허용, 연관관계를 맺을지 선택 가능
ㅇ 식별 관계
- 부모 테이블의 기본 키를 내려받아 자식 테이블의 기본 키 + 외래 키로 사용
* 데이터베이터 설계 관점에서의 "비식별 관계"를 선호
- 식별 관계는 조인 시 SQL문이 복잡, 기본 키 인덱스가 불필요하게 커질 수 있음
- 식별 관계는 복합키를 만들어야 하는 경우가 많음
- 비식별 관계는 대리 키를 사용 (테이블의 유연성)
* 객체 관계 매핑의 관점에서의 "비식별 관계"를 선호
- 식별 관계는 복합 기본 키를 사용, JPA에서 복합 키는 별도의 복합 키 클래스를 만들어 사용해야 함
- @GenerateValue로 대리 키를 편리하게 생성
- 식별 관계도 장점이 있는데 기본 키 인덱스 활용이 좋고,
특정 상황에 조인 없이 하위 테이블 만으로 검색을 할 수 있음.
- 식별자 데이터 타입은 Long (약 920경)을 추천
- 필수적 비식별 관계 선호
Not Null로 항상 관계가 있다는 것을 보장 (내부 조인만 사용)
선택적 비식별 관계 : 외래 키에 null 허용 (외부 조인(Outer Join) 사용)
||| 복합 키:: 비식별 관계 매핑
|||| @IdClass
(관계형 데이터베이스에 근접한 방법)
@IdClass 사용을 위한 식별자 클래스의 조건
- 복합 키는 별도의 식별자 클래스로 만들어야 함
- 식별자 클래스의 속성명과 엔티티에서 사용하는 식별자의 속성명이 같아야 함
- Serializable을 구현
- equals()와 hashCode() 메소드 구현
- 기본 생성자 필요
- 식별자 클래스는 public
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 32 33 34 35 36 | @Entity @IdClass(ParentId.class) public class Parent { @Id @Column(name = "PARENT_ID1") private ParentId id1; // ParentId.id1 과 연결 @Id @Column(name = "PARENT_ID2") private ParentId 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; this.id2 = id2; } @Override public boolean equals(Object o) { .. } @Override public int hashCode() { .. } } | cs |
자식 클래스 추가.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | @Entity public class Child { @Id private String id; @ManyToOne @JoinColumns({ @JoinColumn(name = "PARENT_ID1", referenceColumnName = "PARENT_ID1"), @JoinColumn(name = "PARENT_ID2", referenceColumnName = "PARENT_ID2"), }) /* @JoinColumn의 name 속성과 referenceColumnName 속성의 값이 같을 경우 referencedColumnName 생략 가능*/ private Parent parent; } | cs |
|||| @EmbeddedId
(객체지향에 근접한 방법)
@IdClass와 다르게 식별자 클래스에 기본 키를 직접 매핑
@EmbeddedId 사용을 위한 식별자 클래스의 조건
- @Embeddable 어노테이션을 붙여야 함
- Serializable 인터페이스 구현
- equals(), hashCode()를 구현
- 기본 생성자 필요
- 식별자 클래스는 public
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 | @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; public ParentId() {} public ParentId(String id1, String id2) { this.id1 = id1; this.id2 = id2; } @Override public boolean equals(Object o) { .. } @Override public int hashCode() { .. } } | cs |
||| 복합 키:: 식별 관계 매핑
|||| @IdClass
(관계형 데이터베이스에 근접한 방법)
식별 관계는 기본 키와 외래 키를 같이 매핑해야 함 !
@Id
@ManyToOne
@JoinColumn()
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 | // 부모 Class @Entity public class Parent { @Id @Column(name = "PARENT_ID") private String id; private String name; // ... } // 자식 class @Entity @IdClass(ChildId.class) public class Child { @Id @ManyToOne @JoinColumn(name = "PARENT_ID") public Parent parent; @Id @Column(name = "CHILD_ID") private String child; private String name; // ... } // 자식 ID Class public class ChildId implements Serializable { private String parent; // Child.parent 매핑 private String childId; // Child.childId 매핑 public ChildId() {} public ChildId(String parent, String childId) { this.parent = parent; this.childId = childId; } @Override public boolean equals(Object o) { .. } @Override public int hashCode() { .. } } // 손자 Class @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 Class public class GrandChildId implements Serializable { private ChildId child; // GrandChild.child 매핑 private String id; // GrandChild.id 매핑 public GrandChildId() {} public GrandChildId(ChildId child, String id) { this.child = child; this.id = id } @Override public boolean equals(Object o) { .. } @Override public int hashCode() { .. } } | cs |
|||| @EmbeddedId
(객체지향에 근접한 방법)
식별 관계로 사용할 연관관계의 속성에 @MapsId를 사용
@IdClass와 다른 점은 @Id 대신 @MapsId를 사용하는 점
@MapsId는 외래 키와 매핑한 연관관계를 기본 키에도 매핑하겠다는 뜻
@MapsId의 속성 값은 @EmbeddedId를 사용한 식별자 클래스의 기본 키 필드를 지정하면 됨.
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 | // 부모 Class @Entity public class Parent { @Id @Column(name = "PARENT_ID") private String id; private String name; // ... } // 자식 class @Entity public class Child { @EmbeddedId private ChildId id; @MapsId("parentId") // ChildId.parentId 매핑 @ManyToOne @JoinColumn(name = "PARENT_ID") public Parent parent; private String name; // ... } // 자식 ID Class @Embeddable public class ChildId implements Serializable { private String parentId; // @MapsId("parentId") 매핑 @Column(name = "CHILD_ID") private String id; public ChildId() {} public ChildId(String parent, String childId) { this.parent = parent; this.childId = childId; } @Override public boolean equals(Object o) { .. } @Override public int hashCode() { .. } } // 손자 Class @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 Class @Embeddable public class GrandChildId implements Serializable { private ChildId childId; // @MapsId("parentId") 매핑 @Column(name = "GRANDCHILD_ID") private String id; public GrandChildId() {} public GrandChildId(ChildId child, String id) { this.child = child; this.id = id } @Override public boolean equals(Object o) { .. } @Override public int hashCode() { .. } } | cs |
||| 비식별 관계 매핑
식별 관계의 복합 키를 사용한 방법과 비교하면
비식별 관계 매핑은
- 매핑이 쉽고 코드가 단순
- 복합 키가 없으므로 복합 키 클래스를 만들지 않아도 됨
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | // 부모 Class @Entity public class Parent { @Id @GeneratedValue @Column(name = "PARENT_ID") private Long id; private String name; // ... } // 자식 class @Entity public class Child { @Id @GeneratedValue @Column(name = "CHILD_ID") private Long id; private String name; @ManyToOne @JoinColumn(name = "PARENT_ID") private Parent parent; // ... } // 손자 Class @Entity public class GrandChild { @Id @GeneratedValue @Column(name = "GRANDCHILD_ID") private Long id; private String name; @ManyToOne @JoinColumn(name = "CHILD_ID") private Child child; // ... } | cs |
||| 일대일 식별 관계
자식 테이블의 기본 키 값으로 부모 테이블의 기본 키 값만 사용
식별자가 단순히 컬럼 하나면 @MapsId를 사용하고 속성 값은 비워두면 됨
@MapsId는 @Id를 사용해서 식별자로 지정한 BoardDetail.boardId와 매핑
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 | // 부모 Class @Entity public class Board { @Id @GeneratedValue @Column(name = "BOARD_ID") private Long id; private String title; @OneToOne(mappedBy = "board") private BoardDetail boardDetail; // .. } // 자식 Class @Entity public class BoardDetail { @Id private Long boardId; @MapsId // BoardDetail.boardId 매핑 @OneToOne @JoinColumn(name = "BOARD_ID") private Board board; private String content; // ... } | cs |
|| 조인 테이블
데이터베이스 테이블의 연관관계 설계 방법
1. 조인 컬럼 사용 (외래 키)
2. 조인 테이블 사용 (테이블 사용)
||| 조인 컬럼 사용
테이블 관에 관계는 주로 조인 컬럼이라 부르는 외래 키 컬럼을 사용해서 관리
@JoinColumn
||| 조인 테이블 사용
조인 테이블이라는 별도의 테이블을 사용해서 연관관계를 관리
@JoinTable
||| 일대일 조인 테이블
조인 테이블의 외래 키 컬럼 각각에 총 2개의 Unique 제약조건 필요
@JoinTable
> 속성
name : 매핑할 조인 테이블 이름
joinColumn : 현재 엔티티를 참조하는 외래 키
inverseJoinColumns : 반대방향 엔티티를 참조하는 외래 키
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 | // 부모 @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 class Child { @Id @GeneratedValue @Column(name = "CHILD_ID") private Long id; private String name; // .. } | cs |
||| 일대다 조인 테이블
조인 테이블의 컬럼 중 다(N)와 관련된 컬럼에 Unique 제약 필요
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 | // 부모 @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<>(); } // 자식 @Entity public class Child { @Id @GeneratedValue @Column(name = "CHILD_ID") private Long id; private String name; // .. } | cs |
||| 다대일 조인 테이블
일대다에서 방향만 반대
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 32 33 | // 부모 @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<>(); } // 자식 @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; // ... } | cs |
||| 다대다 조인 테이블
조인 테이블의 두 컬럼을 합해서 하나의 복합 Unique 제약조건 필요
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 | // 부모 @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<>(); //... } // 자식 @Entity public class Child { @Id @GeneratedValue @Column(name = "CHILD_ID") private Long id; private String name; // .. } | cs |
|| 엔티티 하나에 여러 테이블 매핑
@SecondaryTable
> 속성
name : 매핑할 다른 테이블의 이름
pkJoinColumns : 매핑할 다른 테이블의 기본 키 컬럼 속성
* 잘 사용하지 않음
* 테이블당 엔티티를 각각 만들어서 일대일 매핑하는 것을 권장
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | @Entity @Table(name = "BOARD") // BOARD Table과 매핑 @SecondaryTable(name = "BOARD_DETAIL", // BOARD_DETAIL Table과 추가 매핑 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; // ... } | cs |
출처 : 자바 ORM 표준 JPA 프로그래밍
'Books' 카테고리의 다른 글
[JPA] QueryDSL 정리 (0) | 2020.12.22 |
---|---|
[JPA] JPQL Query 정리 (0) | 2020.12.22 |
[JPA] 다양한 연관관계 매핑 (0) | 2020.12.21 |
[JPA] 연관관계 매핑이란.? (0) | 2020.12.19 |
[JPA] 엔티티와 매핑. @Entity, @Table, @Id, @Column.. (0) | 2020.12.18 |