관계형 DB에서는 상속이라는 개념이 없음. 대신, 슈퍼타입 서브타입 관계라는 모델링 기법이 존재
슈퍼타입 서브타입 논리 모델을 테이블로 구현하는 방법 3가지
엔티티를 모두 테이블로 만들어, 자식 테이블이 부모 테이블의 기본키를 받아 기본키 + 외래키
로 사용하는 전략
이 때 객체는 타입이 있지만, 테이블은 타입의 개념이 없으므로 타입을 구분하는 컬럼이 필요
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@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;
}
@Entity
@DiscriminatorValue("M")
public class Movie extends Item {
private String director;
}
상속 매핑 Inheritance
사용,
부모클래스에 구분 컬럼 지정 @DiscriminatorColumn(name = "DTYPE")
자식 테이블을 구분할 수 있음
Movie 엔티티를 저장하면 부모인 Item DTYPE에 M 이 들어감
장점
단점
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
@MappedSuperclass
public abstract class BaseEntity {
@Id @GeneratedValue
private Long id;
private String name;
}
@Entity
public class Member extends BaseEntity {
private String email;
}
@AttributeOverride
@AssociationOverride
@MappedSuperclass
로 지정한 클래스는 엔티티가 아니여서 em.find 나 JPQL 에서 사용 불가데이터베이스에 맞춘 방법 (테이블에 맞게 엔티티 매핑)
@Entity
@IdClass(ParentId.class)
public class Parent {
@Id
@Column(name = "PARENT_ID1")
private String id1;
@Id
@Column(name = "PARENT_ID2")
private String id2;
}
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() { ... }
}
식별자 클래스의 속성명과 엔티티에서 사용하는 식별자의 속성명이 같아야 함
Serializable
인터페이스를 구현해야 함
equals, hashCode 를 구현해야 함
기본 생성자 있어야 함
식별자 클래스는 public
@Entity
public class Parent {
@EmbeddedId
private ParentId id;
}
@Embeddable
public class ParentId implements Serializable {
@Column(name = "PARENT_ID1")
private String id1;
@Column(name = "PARENT_ID2")
private String id2;
// equals, hashCode 구현
}
@MapsId
를 사용해야 함@MapsId
는 외래키와 매핑한 연관관계를 기본 키에도 매핑하겠다는 뜻@MapsId
@OneToOne
@JoinColumn(name = "BOARD_ID")
private Board board;
비식별관계를 더 선호
식별관계는 부모 테이블의 기본키를 자식 테이블로 전파하면서 자식 테이블의 기본 키 컬럼이 점점 늘어남
식별관계는 복합 기본키를 만들어야 하는 경우가 많음
비즈니스 요구사항이 변경되었을 때 식별관계는 비식별관계보다 테이블 구조가 유연하지 못하다
객체 관계 매핑 관점에서 식별관계는 복합 키 클래스가 따로 필요 비식별관계는 단순히 @GeneratedValue 사용
추천: 필수적 비식별 관계를 사용 + 기본키 타입 Long 인 대리키
기본은 조인 컬럼을 사용하고 필요하다면 조인 테이블을 쓰자
@Entity
public class Parent {
@Id @GeneratedValue
@Column(name = "PARENT_ID")
private Long id;
@OneToOne
@JoinTable(name = "PARENT_CHILD",
joinColumns = @JoinColumn(name = "PARENT_ID"),
inverseJoinColumns = @JoinColumn(name = "CHILD_ID")
)
}
@Entity
public class Child {
@Id @GeneratedValue
@Column(name = "CHILD_ID")
private Long id;
}
조인 테이블의 컬럼중 다 인 컬럼에 유니크 제약 조건을 걸어야 한다
조인 테이블의 두 컬럼을 합해서 하나의 복합 유니크 제약 조건
잘 사용하지 않음… 안볼래!!!!
회원 엔티티와 팀 엔티티가 서로 연관되어 있다고 가정했을 때, 회원의 정보만 알고 싶다면 굳이 팀 엔티티의 정보를 DB에서 조회할 필요가 없다. 결국 엔티티를 사용하는 시점에 DB에서 조회하는 것을 지연로딩 이라 한다
이 지연로딩을 할 때 실제 엔티티 객체 대신 DB 조회를 지연할 수 있는 가짜 객체가 필요한데 이를
프록시 객체 라고 한다.
JPA 에서 식별자로 엔티티 하나를 조회할 때 EntitiyManager.find()
를 사용한다. 이 메소드는 영속성 컨텍스트에 엔티티가 없으면 DB에서 조회한다.
엔티티를 직접 조회하면 사용 유무에 관계없이 DB를 조회한다. 엔티티를 실제 사용하는 시점까지 조회를 미루고 싶으면 EntitiyManager.getReference()
를 사용하자
이 메소드를 호출하면 JPA는 DB를 조회하지 않고, 엔티티 객체도 생성하지 않는다. 대신 프록시 객체를 반환한다.
프록시 클래스는 실제 클래스를 상속 받아 만들어진다.
프록시 객체는 실제 객체에 대한 참조를 보관하고 있고, 프록시 객체의 메소드를 호출하면 프록시 객체는 실제 객체의 메소드를 호출한다.
프록시 객체는 실제 사용될 때 DB를 조회해서 실제 엔티티 객체를 생성하는데, 이를 프록시 객체의 초기화라 한다.
MemberProxy가 있다 가정
프록시 객체는 연관된 엔티티를 지연로딩할 때 사용한다.
즉시로딩 : 엔티티를 조회할 때 연관된 엔티티도 함께 조회.
지연로딩 : 연관된 엔티티를 실제 사용할 때 조회한다.
@ManyToOne(fetch = FetchType.EAGER)
@ManyToOne(fetch = FetchType.LAZY)
@ManyToOne, @OneToOne : 즉시로딩
@OneToMany, @ManyToMany : 지연로딩 (컬렉션을 로딩하는 거은 비용이 많이 들고, 너무 많은 데이터를 로딩할 수 있기에)