일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
Tags
- 교육기획팀원
- Spring
- JPA
- delayed message plugin
- 교육기획팀
- reactive operaton
- jdbc
- RESTClient
- ddd
- GitHub Actions
- scheduling messages with rabbitmq
- 30기
- 밋업프로젝트
- kusitms
- springboot
- java
- 객체지향 쿼리 언어
- 최범균
- 한국대학생it경영학회
- 도메인 주도 개발 시작하기
- cicd
- 자동처리
- 큐시즘
- Domain Driven Design
- JPQL
- 자바 ORM 표준 JPA 프로그래밍
- 영속성
- 이펙티브자바
- rabbitmq-delayed-message-exchange
- Spring Batch
Archives
- Today
- Total
코딩은 마라톤
[자바 ORM 표준 JPA 프로그래밍] 7장. 고급 매핑 본문
7.1 상속 관계 매핑
7.1.1 조인 전략 _ 각각의 테이블로 변환
- 엔티티 각각을 모두 테이블로 만들고 자식 테이블이 부모 테이블의 기본 키를 받아서 기본 키 + 외래 키로 사용하는 전략
- 조회할 때 조인을 자주 사용한다.
- 타입을 구분하는 컬럼을 추가해야한다. (객체는 타입으로 구분할 수 있지만 테이블은 타입의 개념이 없기 때문)
// 부모 클래스
@Entity
// 부모 클래스에 @Inheritance를 사용한다.
// 조인 전략을 사용하므로 InheritanceType.JOINED를 사용한다.
@Inheritance(strategy = InheritanceType.JOINED)
// 부모 클래스에 구분 컬럼을 지정한다.
// 이 컬럼(DTYPE)을 통해 저장된 자식 테이블을 구분할 수 있다.
@DiscriminatorColumn(name = "DTYPE")
public abstract class Item {
@Id @GeneratedValue
@Column(name = "ITEM_ID")
private Long id;
private String name; // 이름
...
}
// 자식 클래스
@Entity
@DiscriminatorValue("A")
public class Album extends Item {
private String artist;
...
}
- 기본값으로 자식 테이블은 부모 테이블의 ID 컬럼명을 그대로 사용한다.
- 만약 자식 테이블의 기본 키 컬럼명을 변경하고 싶으면 @PrimaryKeyJoinColumn을 사용한다.
// 자식 클래스
@Entity
@DiscriminatorValue("A")
@PrimaryKeyJoinColumn(name = "ALBUM_ID")
public class Album extends Item {
private String artist;
...
}
- 장점
- 테이블이 정규화된다.
- 외래 키 참조 무결성 제약조건을 활용할 수 있다.
- 저장공간을 효율적으로 사용한다.
- 단점
- 조회할 때 조인이 많이 사용되므로 성능 저하가 발생할 수 있다.
- 조회 쿼리가 복잡하다.
- 데이터를 등록할 INSERT SQL을 두 번 실행한다.
- 관련 어노테이션
- @PrimaryKeyJoinColumn, @DiscriminatorColumn, @DiscriminatorValue
7.1.2 단일 테이블 전략 _ 통합 테이블로 변환
- 이름 그대로 테이블을 하나만 사용한다.
- 구분 컬럼으로 어떤 자식 데이터가 저장되었는지 구분한다.
- 조회할 때 조인을 사용하지 않으므로 일반적으로 가장 빠르다.
// 부모 클래스
@Entity
// 부모 클래스에 @Inheritance를 사용한다.
// 단일 테이블 전략을 사용하므로 InheritanceType.SINGLE_TABLE를 사용한다.
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
// 부모 클래스에 구분 컬럼을 지정한다.
// 이 컬럼(DTYPE)을 통해 저장된 자식 테이블을 구분할 수 있다.
@DiscriminatorColumn(name = "DTYPE")
public abstract class Item {
@Id @GeneratedValue
@Column(name = "ITEM_ID")
private Long id;
private String name; // 이름
private String artist;
... 이외 자식 엔티티가 매핑한 모든 컬럼
}
// 자식 클래스
@Entity
@DiscriminatorValue("A")
public class Album extends Item {}
...
- 장점
- 조인이 필요없으므로 조회 성능이 빠르다.
- 조회 쿼리가 단순하다.
- 단점
- 자식 엔티티가 매핑한 컬럼은 모두 null을 허용해야 한다.
- 단일 테이블에 모든 것을 저장하므로 테이블이 커질 수 있다. -> 조회 성능이 오히려 느려질 수 있다.
- 특징
- 구분 컬럼을 꼭 사용해야 한다. (@DiscriminatorColumn)
- @DiscriminatorValue를 지정하지 않으면 기본으로 엔티티 이름을 사용한다.
7.2 @MappedSuperclass
- 부모 클래스는 테이블과 매핑하지 않고 부모 클래스를 상속받는 자식 클래스에게 매핑 정보만 제공하고 싶을 때 사용한다.
- 실제 테이블과는 매핑되지 않는다.
@MappedSuperclass
public abstract BaseEntity {
@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;
...
}
- 부모로부터 물려받은 매핑 정보를 재정의하려면 @AttributeOverrides 나 @AttributeOverride 를 사용한다.
// 1개 재정의
@Entity
@AttributeOverride(name = "id", column = @Column(name = "MEMBER_ID"))
public class Member extends BaseEntity { ... }
// 2개 이상 재정의
@Entity
@AttributeOverrides({
@AttributeOverride(name = "id", column = @Column(name = "MEMBER_ID")),
...
})
public class Member extends BaseEntity { ... }
- 특징
- 테이블과 매핑되지 않고 자식 클래스에 엔티티의 매핑 정보를 상속하기 위해 사용한다.
- @MappedSuperclass로 지정한 클래스는 엔티티가 아니므로 em.find() 나 JPQL에서 사용할 수 없다.
- 이 클래스를 직접 생성해서 사용할 일은 거의 없으므로 추상 클래스로 만드는 것을 권장한다.
7.3 복합 키와 식별 관계 매핑
7.3.1 식별 관계 vs 비식별 관계
- 식별 관계
- 부모 테이블의 기본 키를 내려받아서 자식 테이블의 기본 키 + 외래 키로 사용하는 관계
- 비식별 관계
- 부모 테이블의 기본 키를 받아서 자식 테이블의 외래 키로만 사용하는 관계 (자식 테이블의 독립된 기본 키가 존재)
- 필수적 비식별 관계 : 외래 키(부모 테이블)에 NULL을 허용하지 않는다. (필수로 연관관계 맺어야 함)
- 선택적 비식별 관계 : 외래 키에 NULL을 허용한다. (연관관계 또한 선택할 수 있다.)
- 최근에는 비식별 관계를 주로 사용하고 꼭 필요한 곳에만 식별관계를 사용한다.
7.3.2 복합 키 : 비식별 관계 매핑
@Entity
public class Hello {
@Id
private String id1;
@Id
private String id2; // 실행 시점에 매핑 예외 발생
}
- 위와 같이 복합키를 매핑할 경우 오류가 발생한다.
- @IdClass와 @EmbeddedId 2가지 방법을 이용하여 복합 키를 매핑한다.
@IdClass - 데이터베이스에 맞춘 방법
- PARENT 테이블을 보면 기본 키를 PARENT_ID1, PARENT_ID2로 묶은 복합 키로 구성한다.
@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;
this.id2 = id2;
}
@Override
public boolean equals(Object o) {...}
@Override
public int hashCode() {...}
}
- @IdClass를 사용할 때 만족해야 할 조건
- 식별자 클래스의 속성명과 엔티티에서 사용하는 식별자의 속성명이 같아야 한다.
- Serializable 인터페이스를 구현해야 한다.
- equals, hashCode를 구현해야 한다.
- 기본 생성자가 있어야 한다. (lombok : @NoArgsConstructor 이용)
- 식별자 클래스는 public이어야 한다.
@Entity
public class Child {
@Id
private String id;
@ManyToOne
@JoinColumns({
@JoinColumn(name = "PARENT_ID1",
referencedColumnName = "PARENT_ID1"),
@JoinColumn(name = "PARENT_ID2",
referencedColumnName = "PARENT_ID2")
})
private Parent parent;
...
}
- 부모 테이블의 기본 키 컬럼이 복합 키이므로 자식 테이블의 외래 키도 복합 키다.
- 따라서 외래 키 매핑 시 여러 컬럼을 매핑해야 하므로 @JoinColumns 어노테이션을 사용하고 각각의 외래 키 컬럼을 @JoinColumn으로 매핑한다.
- @JoinColumn의 name 속성과 referencedColumnName 속성의 값이 같으면 referencedColumnName은 생략해도 된다.
@IdClass - 객체지향적인 방법
@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 and hashCode 구현
...
}
- @EmbeddedId를 사용할 때 만족해야할 조건
- @Embeddable 어노테이션을 붙여줘야 한다.
- Serializable 인터페이스를 구현해야 한다.
- equals, hashCode를 구현해야 한다.
- 기본 생성자가 있어야 한다. (lombok : @NoArgsConstructor)
- 식별자 클래스는 public이어야 한다.
7.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 매핑
//equals and 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;
...
}
//손자 ID
public class GrandChildId implements Serializable {
private ChildId child; //GrandChild.child 매핑
private String id; //GrandChild.id 매핑
//equals and hashCode
...
}
@EmbeddedId와 식별 관계
//부모
@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;
...
}
//자식 ID
@Embeddable
public class ChildId implements Serializable {
private String parentId; //@MapsId("parentId")로 매핑
@Column(name = "CHILD_ID")
private String id;
//equals and 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;
...
}
//손자 ID
@Embeddable
public class GrandChildId implements Serializable {
private ChildId childId; //@MapsId("childId")로 매핑
@Column(name = "GRANDCHILD_ID")
private String id;
//equals and hashCode
...
}
7.3.4 비식별 관계로 구현
//부모
@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;
@ManyToOne
@JoinColumn(name = "PARENT_ID")
public Parent parent;
private String name;
...
}
//손자
@Entity
public class GrandChild {
@Id @GeneratedValue
@Column(name = "GRANDCHILD_ID")
private Long id;
@ManyToOne
@JoinColumn(name = "CHILD_ID")
private Child child;
private String name;
...
}
7.3.5 일대일 식별 관계
//부모
@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;
...
}
정리
- 비식별 관계를 사용하고 기본 키는 Long 타입의 대리 키를 사용한다.
이유 : 대리 키는 비즈니스와 아무 관련이 없어 비즈니스가 변경되어도 유연한 대처가 가능하다.
7.4 조인 테이블
데이터베이스 테이블의 연관관계를 설계하는 방법은 크게 2가지다.
- 조인 컬럼 사용(외래 키)
- 조인 테이블 사용(테이블 사용)
기본은 조인 컬럼을 사용하고 필요하다고 판단되면 조인 테이블을 사용하자 !
7.4.1 일대일 조인 테이블
//부모
@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;
...
}
- @JoinTable의 속성
- name : 매핑할 조인 테이블 이름
- joinColumns : 현재 엔티티를 참조하는 외래 키
- inverseJoinColumns : 반대 방향 엔티티를 참조하는 외래키
7.4.2 일대다 조인 테이블
//부모
@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;
...
}
7.4.3 다대일 조인 테이블
//부모
@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;
}
- 일대다 조인 테이블과 방향만 반대이다.
7.4.4 다대다 조인 테이블
//부모
@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;
...
}
'Backend > JPA' 카테고리의 다른 글
[자바 ORM 표준 JPA 프로그래밍] 9장. 값 타입 (0) | 2024.03.17 |
---|---|
[자바 ORM 표준 JPA 프로그래밍] 8장. 프록시와 연관관계 관리 (0) | 2024.02.25 |
[자바 ORM 표준 JPA 프로그래밍] 6장. 다양한 연관관계 매핑 (1) | 2024.02.12 |
[자바 ORM 표준 JPA 프로그래밍] 5장. 연관관계 매핑 기초 (0) | 2024.02.04 |
[자바 ORM 표준 JPA 프로그래밍] 4장. 엔티티 매핑 (0) | 2024.02.02 |