Spring/Spring Data JPA

[자바 ORM 표준 JPA 프로그래밍] 7. 고급 매핑

noahkim_ 2023. 12. 28. 02:59

김영한 님의 "자바 ORM 표준 JPA 프로그래밍" 책을 정리한 포스팅 입니다.

 

1. 상속 관계 매핑

  • 데이터베이스의 슈퍼타입-서브타입 논리를 테이블로 구현

 

전략

특징 Joined Strategy Single Table Strategy
Table Per Class Strategy
테이블 구조 부모와 자식 클래스 각각의 테이블 모든 엔티티를 하나의 테이블에 저장
자식 클래스마다 별도의 테이블을 생성
쿼리 속도 느림 (조인 필요) 빠름 (조인 불필요)
느림 (조인 필요)
정규화 ✅ (테이블 정규화) ❌ (모든 컬럼이 하나의 테이블에 포함)
✅ (자식 테이블별로 별도 관리)
테이블 크기 작음 중간
컬럼 제약 외래키로 관리 자식 클래스마다 DTYPE 컬럼 사용
모든 컬럼 nullable
각 자식 테이블마다 not null 제약 가능
외래키 제약  

 

어노테이션

어노테이션 위치 설명
@Inheritance 부모 클래스 상속 관계 매핑 전략 지정
- JOINED (기본)
- SINGLE_TABLE
- TABLE_PER_CLASS
@DiscriminatorColumn 부모 클래스 자식 클래스들을 구분하는 컬럼 지정
- 자식 클래스에 어떤 타입의 데이터가 들어 있는지를 식별할 수 있습니다.
- 기본적으로 DTYPE 사용
@DiscriminatorValue 자식 클래스 자식 클래스의 구분 값 설정
- 각 자식 클래스가 식별될 수 있도록 합니다.
- @DiscriminatorColumn에서 설정한 컬럼에 들어갈 값
@MappedSuperclass 부모 클래스 공통 속성만 제공하고, 자식 클래스에서 해당 속성을 사용할 때 사용
- 부모 클래스가 테이블로 생성되지 않으며, 자식 클래스에 매핑 정보만 제공
@AttributeOverride 자식 클래스 부모 클래스의 속성 매핑을 자식 클래스에서 다르게 설정할 때 사용
- 부모 클래스에서 상속받은 속성을 자식 클래스에서 재정의할 때 사용

 

예제) 조인 전략

더보기
@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
@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
@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 {
    private String artist;
    ...
}
@Entity
public class Movie extends Item {
    private String director;
    private String actor;
    ...
}

 

예제) 상속 정보 제공

더보기
@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 {...}
@Entity
@AttributeOverrides({ // 둘 이상 재정의할 때 사용
    name = "id", column = @Column(name = "SELLER_ID")),
    name = "name", column = @Column(name = "SELLER_NAME"))
})
public class Seller extends BaseEntity {...}

 

2. 복합 키와 식별 관계 매핑

식별 관계 vs 비식별 관계

구분 식별 관계 비식별 관계
기본키 구성 부모 테이블 기본키 포함 ✅
부모 테이블 기본키 포함
외래키 NULLABLE ❌ (연관된 부모 객체가 반드시 존재해야 함) - 필수적 비식별 관계 : ❌
- 선택적 비식별 관계 : ✅ (연관관계를 맺을 지 선택 가능)

 

예시) 식별 관계

더보기
@Entity
public class Order {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id; // 주문 ID

    private String orderName;

    // Getters and Setters
}
@Entity
public class OrderItem {
    @Id
    @ManyToOne
    @JoinColumn(name = "order_id") // 부모 테이블(Order)의 기본키를 포함
    private Order order; // 주문 ID

    private String itemName;

    // Getters and Setters
}

 

예시) 비식별 관계

더보기
@Entity
public class Team {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id; // 팀 ID

    private String name;

    // Getters and Setters
}

 

필수적

@Entity
public class Member {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id; // 회원 ID

    private String name;

    @ManyToOne
    @JoinColumn(name = "team_id", nullable = false) // 부모 테이블(Team)의 외래키
    private Team team; // 팀에 대한 참조

    // Getters and Setters
}

 

선택적

@Entity
public class Member {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id; // 회원 ID

    private String name;

    @ManyToOne
    @JoinColumn(name = "team_id", nullable = true) // 부모 테이블(Team)의 외래키
    private Team team; // 팀에 대한 참조

    // Getters and Setters
}

 

복합 키: 비식별 관계 매핑

항목 설명
@IdClass 복합 기본 키가 정의된 식별자 클래스를 설정
@Embeddable
복합 키를 한 객체로 묶어서 사용하는 방식 (대안적)
@EmbeddedId
매핑할 식별자 클래스를 지정
@JoinColumns
복합 외래 키를 매핑할 때 사용

 

예제) @IdClass

더보기
@Entity
@IdClass(ParentId.class)
public class Parent {
    @Id
    @Column(name = "PARENT_ID1")
    private String id1; //Parentld.id1과연결

    @Id
    @Column(name = "PARENT_ID2")
    private String id2; //Parentld.id2와연결
}
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() {. ..}
}
  • 식별자 클래스를 외부에 분리
  • 필드명은 엔티티와 같아야 함

 

예제) @EmbeddedId & @Embeddable

더보기
@Entity
public class Parent {
    @EmbeddedId
    private ParentId id;
    private String name;
}
@Embeddable
public class ParentId implements Serializable {
    @Column(name = "PARENT_ID1")
    private String id1;

    @Coluinn (name = "PARENT_ID2")
    private String id2;

    //equals and hashCode 구현
}

 

예제) @JoinColumns

더보기
@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;
}

 

5. 엔티티 하나에 여러 테이블 매핑

  • 한 엔티티에 여러 테이블을 매핑할 수 있습니다.

 

@SecondaryTable

속성 설명
name 매핑할 다른 테이블의 이름
pkJoinColumns 매핑할 다른 테이블의 기본 키 컬럼 속성

 

예제

더보기
@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;
}