[JPA] 상속 관계 매핑

dooboocookie·2022년 10월 31일
0

인프런 강의 - 김영한님의 자바 ORM 표준 JPA 프로그래밍 - 기본편(링크) 참고

상속 관계

  • JPA로 ORM설계를 하다보면, 엔티티를 상속하는 경우가 있을 것이다.
    • 하지만, 관계형 데이터 베이스는 테이블끼리의 상속관계는 없다.

엔티티

  • Member.java
    • 부모 엔티티
    • 추상 클래스로 선언
@Entity
public abstract class Member {
    
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private int age;
    
    private String address;
    
    private String phone;
    
    //getter, setter, ...
}
  • Student.java
@Entity
public class Student extends Member {
    
    private int grade;
    
    private int clazz;
    
    //getter, setter, 생성자, 생성 메소드, ...
}
  • Teacher.java
@Entity
public class Teacher extends Member{

    private String subject;

    private String ect;

    //getter, setter, 생성자, 생성 메소드, ...
}
  • Staff.java
@Entity
public class Staff extends Member {

    private int salary;

    private LocalDate hiredDate;
    
    //getter, setter, 생성자, 생성 메소드, ...
}

상속 전략

  • @Inheritance어노테이션을 사용하여 상속 전략을 지정
    • @Inheritance(strategy=InheritanceType.JOINED)
      • 조인 전략
    • @Inheritance(strategy=InheritanceType.SINGLE_TABLE)
      • 싱글 테이블 전략
    • @Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)
      • 구현 클래스만 테이블이 생성되는 전략

JOINED

  • 조인 전략
  • 부모 클래스의 공통된 부분은 부모클래스로 테이블이 만들어지고, 상속된 클래스들은 부모 클래스의 테이블을 참조하는 테이블이 각각 만들어짐

  • Member.java
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn(name = "D_TYPE")
public abstract class Member {
    
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
	//...
}

기타 속성

  • @DiscriminatorColumn(name=“D_TYPE”)
    • 구현 테이블 구분하는 목적의 컬럼
    • 기본값은 D_TYPE
    • 선언하지 않으면, 컬럼이 생성되지 않는다.
      • 해당 컬럼이 필수적으로 필요하진 않지만 MEMBER엔티티만 조회한다고 했을때, 조금 더 독립적으로 쓸 수 있다.
  • @DiscriminatorValue("Student2")
    • 구현 클래스에 주는 속성
    • D_TYPE 컬럼에 표시되는 값을 설정할 수 있다.

CREATE문

create table Member (
	D_TYPE varchar(31) not null,
    id bigint generated by default as identity,
    address varchar(255),
    age integer not null,
    phone varchar(255),
    primary key (id)
)
    
create table Student (
	clazz integer not null,
    grade integer not null,
    id bigint not null,
    primary key (id)
)
    
create table Teacher (
	ect varchar(255),
    subject varchar(255),
    id bigint not null,
    primary key (id)
)

create table Staff (
	hiredDate date,
    salary integer not null,
    id bigint not null,
    primary key (id)
)

저장

Student student = new Student(18, "서울", "010-1234-1234", 2, 10);
em.persist(student);

Teacher teacher = new Teacher(30, "경기도", "010-2345-2345", "수학", "");
em.persist(teacher);

Staff staff = new Staff(30, "서울", "010-3456-3456", 350, LocalDate.of(2010, 10, 31));
em.persist(staff);            
  • INSERT 결과

조회

em.find(Student.class, 1L);
  • SELECT문

select
	student0_.id as id2_6_0_,
    student0_1_.address as address3_6_0_,
    student0_1_.age as age4_6_0_,
    student0_1_.phone as phone5_6_0_,
    student0_.clazz as clazz1_12_0_,
    student0_.grade as grade2_12_0_ 
from
	Student student0_ 
inner join
	Member student0_1_ 
    on student0_.id=student0_1_.id 
where
	student0_.id=1
  • JOIN을 활용해서 부모 테이블과 자식 테이블을 둘 다 조회하여 정보를 가져옴

특징

  • 제일 정규화된 테이블이다.
    • 각각의 컬럼에 NULL을 허용하지 않고 무결성 제약조건을 추가할 수 있다.
  • NULL값으로 낭비되는 컬럼이 없기때문에 저장공간이 효율적이다.
  • 조회 시, 필수적으로 JOIN을 활용하기 때문에 성능에 저하가 생김
  • 데이터 저장 시, INSERT문에 2번 발생
    • JPA로는 학생엔티티만 저장해도 멤버까지 INSERT문이 날라감
    • 내가 저장한 엔티티에 대한 INSERT문 뿐아니라 부모 엔티티의 INSERT문이 날라가는 것에 주의를 해야된다.
      • 하지만 이것이 다른 전략에 비해 헷갈리는 구조는 아니다.

SINGLE_TABLE

  • 싱글 테이블 전략
  • 부모 클래스인 멤버테이블을 1개만들고 그 테이블에 모든 정보를 담고 있다.

  • Member.java
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "D_TYPE")
public abstract class Member {
    
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
	//...
}

CREATE 문

create table Member (
	DTYPE varchar(31) not null,
    id bigint generated by default as identity,
    address varchar(255),
    age integer not null,
    phone varchar(255),
    hiredDate date,
    salary integer,
    clazz integer,
    grade integer,
    ect varchar(255),
    subject varchar(255),
    primary key (id)
)

저장

Student student = new Student(18, "서울", "010-1234-1234", 2, 10);
em.persist(student);

Teacher teacher = new Teacher(30, "경기도", "010-2345-2345", "수학", "");
em.persist(teacher);

Staff staff = new Staff(30, "서울", "010-3456-3456", 350, LocalDate.of(2010, 10, 31));
em.persist(staff);            
  • INSERT 결과

조회

em.find(Student.class, 1L);
  • SELECT문
select
	student0_.id as id2_6_0_,
    student0_.address as address3_6_0_,
    student0_.age as age4_6_0_,
    student0_.phone as phone5_6_0_,
    student0_.clazz as clazz8_6_0_,
    student0_.grade as grade9_6_0_ 
from
	Member student0_ 
where
	student0_.id=? 
    and student0_.DTYPE='Student'
  • 단일 테이블이기 때문에 JOIN 없이 간단하게 테이블을 조회할 수 있음

특징

  • 조인이 없으므로 조회하는 SQL문이 간단하고 성능이 빠름
  • 구현 클래스에 있는 컬럼들은 모두 NULL을 강제로 허용해야된다.
    • 해당 컬럼들에 무결성 제약조건을 줄 수 없음
  • 한 테이블에 NULL일 수 있는 컬럼이 늘어나므로 데이터 공간 낭비가 심하다.
    • 특정 임계점을 넘으면 성능이 오히려 좋지 않을 수 있다.
  • JPA로는 학생을 저장했는데, 멤버의 INSERT문이 날라간다. 이는 혼란을 줄 수 있는 부분

TABLE_PER_CLASS

  • 구현클래스의 테이블을 각자 만드는 전략
  • 부모 클래스의 테이블은 만들어지지 않고, 별개의 엔티티 처럼 테이블이 만들어짐

  • Member.java
@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)

public abstract class Member {
    
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
	//...
}

기타 속성

  • @DiscriminatorColumn(name=“D_TYPE”)
    • 부모 테이블이 없어서 해당 속성은 적용이 불가능하다

CREATE문

create table Staff (
	id bigint not null,
    address varchar(255),
    age integer not null,
    phone varchar(255),
    hiredDate date,
    salary integer not null,
    primary key (id)
)
    
create table Student (
	id bigint not null,
    address varchar(255),
    age integer not null,
    phone varchar(255),
    clazz integer not null,
    grade integer not null,
    primary key (id)
)
    
create table Teacher (
	id bigint not null,
    address varchar(255),
    age integer not null,
    phone varchar(255),
    ect varchar(255),
    subject varchar(255),
    primary key (id)
)

저장

Student student = new Student(18, "서울", "010-1234-1234", 2, 10);
em.persist(student);

Teacher teacher = new Teacher(30, "경기도", "010-2345-2345", "수학", "");
em.persist(teacher);

Staff staff = new Staff(30, "서울", "010-3456-3456", 350, LocalDate.of(2010, 10, 31));
em.persist(staff);            
  • INSERT 결과

조회

em.find(Student.class, 1L);
  • SELECT문
select
	student0_.id as id1_6_0_,
	student0_.address as address2_6_0_,
    student0_.age as age3_6_0_,
    student0_.phone as phone4_6_0_,
    student0_.clazz as clazz1_12_0_,
    student0_.grade as grade2_12_0_ 
from
	Student student0_ 
where
	student0_.id=1
  • 단건 조회를 할때는 해당 테이블에 대해서 단순 조회를 한다.
em.createQuery("select m from Member m", Member.class).getResultList();
select
	member0_.id as id1_6_,
    member0_.address as address2_6_,
    member0_.age as age3_6_,
    member0_.phone as phone4_6_,
    member0_.hiredDate as hireddat1_11_,
    member0_.salary as salary2_11_,
    member0_.clazz as clazz1_12_,
    member0_.grade as grade2_12_,
    member0_.ect as ect1_13_,
    member0_.subject as subject2_13_,
    member0_.clazz_ as clazz_ 
from ( 
	select
    	id,
        address,
        age,
        phone,
        hiredDate,
        salary,
        null as clazz,
        null as grade,
        null as ect,
        null as subject,
        1 as clazz_ 
    from Staff 
    union all 
    select
    	id,
        address,
        age,
        phone,
        null as hiredDate,
        null as salary,
        clazz,
        grade,
        null as ect,
        null as subject,
        2 as clazz_ 
    from Student 
    union all 
    select
    	id,
        address,
        age,
        phone,
        null as hiredDate,
        null as salary,
        null as clazz,
        null as grade,
        ect,
        subject,
        3 as clazz_ 
    from Teacher) member0_
  • UNION ALL 을 사용 하여 매우 무겁고 복잡한 SQL문이 날라감

특징

  • 추천하지 않는 방법
  • 구현 클래스를 명확하게 표시하기에는 좋다.
  • 하지만, 자식 테이블을 조회할 때 매우 성능이 좋지 않고 어렵다.

결론

  • 기본으로는 제일 정규화된 구조인 JOINED를 사용하는 것이 좋아 보임
  • 구조가 매우 단순하여, 성능을 향상시키고 싶을 때는 SINGLE_TABLE 고려
profile
1일 1산책 1커밋

0개의 댓글