티스토리 뷰

상속 관계 매핑

1. 조인전략 Joined Strategy: 엔티티 각각을 모두 테이블로 만들고 자식 테이블이 부모 테이블의 기본 키를 받아서 기본 키 + 외래 키로 사용하는 전략

@Entity
@Inheritance(strategy = InheritanceType.JOINED)  //조인 전략 사용
@DiscriminatorColumn  //컬럼명:dtype
public abstract class Item {
    
    @Id @GeneratedValue
    @Column(name = "ITEM_ID")
    private Long id;
    
    private String name;
    private int price;
    
}
@Entity
//@DiscriminatorValue("A")  //DiscriminatorValue를 지정하지 않으면 기본으로 엔티티 이름을 사용한다.
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")  //엔티티를 저장할 때 구분 컬럼에 입력할 값을 지정, 없으면 Entity 명으로 저장됨.
//@PrimaryKeyJoinColumn(name = "BOOK_ID")  //기본 키 컬럼명을 변경하고 싶은 경우 사용한다.
public class Book extends Item {
    private String author;
    private String isbn;
    ...
}

 

장점

  • 테이블이 정규화된다.
  • 외래 키 참조 무결성 제약조건을 활용할 수 있다.
  • 저장공간을 효율적으로 사용한다.

 

단점

  • 조회할 때 조인이 많이 사용되므로 성능이 저하될 수 있다.
  • 조회 쿼리가 복잡하다.
  • 데이터를 등록할 INSERT SQL을 두 번 실행한다.

 

2. 단일 테이블 전략 Single-Table Strategy: 구분 컬럼(DTYPE)으로 구분하는 데이터를 하나의 테이블에 저장한다.

@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")  //DiscriminatorValue를 지정하지 않으면 기본으로 엔티티 이름을 사용한다.
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")  //엔티티를 저장할 때 구분 컬럼에 입력할 값을 지정, 없으면 Entity 명으로 저장됨.
//@PrimaryKeyJoinColumn(name = "BOOK_ID")  //기본 키 컬럼명을 변경하고 싶은 경우 사용한다.
public class Book extends Item {
    private String author;
    private String isbn;
    ...
}

 

장점

  • 조인이 필요 없으므로 일반적으로 조회 성능이 빠르다.
  • 조회 쿼리가 단순하다.

단점

  • 자식 엔티티가 매핑한 컬럼은 모두 null을 허용해야 한다.
  • 단일 테이블에 모든 것을 저장하므로 테이블이 커질 수 있다. 그러므로 상황에 따라서는 조회 성능이 오히려 느려질 수 있다.

 

@MappedSuperclass

  • 실제 테이블과 매핑되지 않고 단순히 매핑 정보를 상속할 목적으로 사용한다.

@MappedSuperclass
public abstract class BaseEntity {
    @Id
    @GeneratedValue
    private Long id;
    private String name;
}
@Entity
public class Member extends BaseEntity {
    private String email;
}
@Entity
public class Seller extends BaseEntity {
    private String shopName;
}

 

복합 키와 식별 관계 매핑

  • 식별 관계
    • 부모 테이블의 기본 키를 내려받아서 자식 테이블의 기본 키 + 외래키로 사용하는 관계
  • 비식별 관계
    • 부모 테이블의 기본 키를 받아서 자식 테이블의 외래 키로만 사용하는 관계
    • 필수적 비식별 관계: 외래 키에 NULL을 허용하지 않는다.
    • 선택적 비식별 관계: 외래 키에 NULL을 허용한다.
  • 복합키
    • 둘 이상의 컬럼으로 구성된 복합 기본 키
    • JPA는 복합 키를 지원하기 위해 @IdClass와 @EmbeddedId 2가지 방법을 제공한다.
    • @IdClass는 관계형 데이터베이스에 가까운 방법
    • @EmbeddedId는 객체지향에 가까운 방법

 

1. 복합 키: 비식별 관계 매핑

  • @IdClass로 구현
@Entity
@IdClass(ParentId.class)  //ParentId 클래스를 식별자 클래스로 지정
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() {...}
    
}
@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;
    
}

 

사용

Parent parent = new Parent();
parent.setId1("myId1");
parent.setId2("myId2");
parent.setName("parentName");
em.persist(parent);
//영속성 컨텍스트에 엔티티를 등록하기 직전에 내부에서 Parent.id1, Parent.id2 값을 사용해서
//식별자 클래스인 ParentId를 생성하고 영속성 컨텍스트의 키로 사용한다.

//복합 키로 조회 예
ParentId parentId = new ParentId("myId1", "myId2");
Parent parent = em.find(Parent.class, parentId);

 

  • @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;
    
    public ParentId(){}
    
    @Override
    public boolean equals(Object o) {...}
    
    @Override
    public int hashCode() {...}
    
}

 

사용

Parent parent = new Parent();
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);

 

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

  • @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;
    ...
}
public class ChildId implements Serializable {
    private String 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;
    private String id;
    
    //equals, 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")
    @ManyToOne
    @JoinColumn(name = "PARENT_ID")
    public Parent parent;
    
    private String name;
    ...
}
@Embeddable
public class ChildId implements Serializable {
    
    private String parentId;
    
    @Column(name = "CHILD_ID")
    private String id;
    
    //equals, hashCode
    ...
}
@Entity
public class GrandChild {
   
   @EmbeddedId
   private GrandChildId id;
   
   @MapsId("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;
    
    @Column(name = "GRANDCHILD_ID")
    private String id;
    
    //equals, hashCode
    ...
}

 

3. 비식별 관계로 구현

@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;
    ...
}

 

식별 관계보다 비식별 관계를 선호하는 이유

  • 식별 관계는 부모 테이블의 기본 키를 자식 테이블로 전파하면서 자식 테이블의 기본 키 컬럼이 점점 늘어난다.
  • 식별 관계는 2개 이상의 컬럼을 합해서 복합 기본 키를 만들어야 하는 경우가 많다.
  • 식별 관계를 사용할 때 기본 키로 비즈니스 의미가 있는 자연 키 컬럼을 조합하는 경우가 많다. 식별 관계의 자연 키 컬럼들이 자식에 손자까지 전파되면 변경하기 힘들다.
  • 식별 관계는 부모 테이블의 기본 키를 자식 테이블의 기본 키로 사용하므로 비식별 관계보다 테이블 구조가 유연하지 못하다.

 

조인 테이블

조인 테이블이라는 별도의 테이블을 사용해서 연관관계를 관리한다.

조인하고자 하는 두 테이블의 외래 키를 가지고 연관관계를 관리한다.

 

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;
    
    /* 양방향 매핑 시 추가
    @OneToOne(mappedBy = "child")
    private Parent parent;
    */
    ...
}

 

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<Child>();
    ...
}
@Entity
public class Child {
    
    @Id @GeneratedValue
    @Column(name = "CHILD_ID")
    private Long id;
    private String name;
    ...
}

 

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<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;
    ...
}

 

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<Child>();
    ...
}
@Entity
public class Child {
    
    @Id @GeneratedValue
    @Column(name = "CHILD_ID")
    private Long id;
    private String name;
    ...
}

 

 

참고

김영한, 『자바 ORM 표준 JPA 프로그래밍』, 에이콘출판주식회사(2015)

댓글
Total
Today
Yesterday
링크
Apple 2023 맥북 프로 14 M3, 스페이스 그레이, M3 8코어, 10코어 GPU, 512GB, 8GB, 한글