본문 바로가기
Backend/JPA

[JPA] 상속 관계 매핑 전략 (조인 전략 / 단일 테이블 전략 / 구현 클래스마다 테이블 전략 / MappedSuperClass)

by 비전공자 기록광 2022. 12. 1.
반응형

객체의 상속 관계를 JPA에 적용시키는 3가지 전략을 정리해봤다.

부모 객체는 Employee고 id와 name을 갖는다.
자식 객체는 FullTimeEmployee와 PartTimeEmployee로 구성한다.

전략을 비교하기 전 기본적인 코드부터 작성하겠다

 

Employee

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
import lombok.Getter;
import lombok.Setter;
 
import javax.persistence.*;
 
@Getter
@Setter
@Entity
public abstract class Employee {
 
    @Id
    @GeneratedValue
    private Long id;
 
    private String name;
 
    protected Employee() {}
 
    public Employee(String name) {
        this.name = name;
    }
 
    @Override
    public String toString() {
        return String.format("Employee[%s]", name);
    }
}
 
cs

 

FullTimeEmployee

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import javax.persistence.Entity;
import java.math.BigDecimal;
 
@Entity
public class FullTimeEmployee extends Employee{
    protected FullTimeEmployee(){}
 
    public FullTimeEmployee(String name, BigDecimal salary) {
        super(name);
        this.salary = salary;
    }
 
    private BigDecimal salary;
}
 
cs

 

PartTimeEmployee

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import javax.persistence.Entity;
import java.math.BigDecimal;
 
@Entity
public class PartTimeEmployee extends Employee{
    protected PartTimeEmployee(){}
 
    public PartTimeEmployee(String name, BigDecimal hourlyWage) {
        super(name);
        this.hourlyWage = hourlyWage;
    }
 
    private BigDecimal hourlyWage;
}
 
cs

 

EmployeeRepository

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
31
import com.udemy.jpa_study.inheritance.domain.Employee;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
 
import javax.persistence.EntityManager;
import java.util.List;
 
@Repository
@Transactional
public class EmployeeRepository {
    private Logger logger = LoggerFactory.getLogger(this.getClass());
 
    @Autowired
    EntityManager em;
 
    public void insert(Employee employee){
        em.persist(employee);
    }
 
    public List<Employee> insertAllPartTimeEmployees(){
        return em.createQuery("select e from PartTimeEmployee e", Employee.class).getResultList();
    }
 
    public List<Employee> insertAllFullTimeEmployees(){
        return em.createQuery("select e from FullTimeEmployee e", Employee.class).getResultList();
    }
}
 
cs

 

InheritanceApplication

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
31
32
33
34
35
36
37
38
39
import com.udemy.jpa_study.inheritance.domain.Employee;
import com.udemy.jpa_study.inheritance.domain.FullTimeEmployee;
import com.udemy.jpa_study.inheritance.domain.PartTimeEmployee;
import com.udemy.jpa_study.inheritance.repository.EmployeeRepository;
import com.udemy.jpa_study.relationships.repository.CourseRepository;
import com.udemy.jpa_study.relationships.repository.StudentRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
 
import java.math.BigDecimal;
 
@SpringBootApplication
public class InheritanceApplication implements CommandLineRunner {
 
    private Logger logger = LoggerFactory.getLogger(this.getClass());
 
    @Autowired
    EmployeeRepository repository;
 
 
    public static void main(String[] args) {
        SpringApplication.run(InheritanceApplication.class, args);
    }
 
    @Override
    public void run(String... args) throws Exception {
        repository.insert(new PartTimeEmployee("김씨"new BigDecimal("50")));
        repository.insert(new FullTimeEmployee("박씨"new BigDecimal("1000")));
 
        logger.info("All FullTimeEmployees -> {}", repository.insertAllFullTimeEmployees());
        logger.info("All PartTimeEmployees -> {}", repository.insertAllPartTimeEmployees());
 
    }
}
 
cs

서버 실행과 함께 데이터도 넣어줘서 상속 관계를 확인하기 좋게 했다.

조인 전략 Joined Strategy

부모, 자식 엔티티를 각각 모두 테이블로 만들어 조인해서 조회하는 전략이다.
자식 객체는 Default DType 컬럼으로 구분된다.

@Inheritance 전략을 JOINED로 설정 하면 된다
여기에 자식 객체를 구분해부는 컬럼의 정의를 할 수 있다.
@DiscriminatorColumn 을 통해 이름을 정해준다.
Default는 DType이다.

이대로 서버를 실행해주면


부모와 상속받은 자식 객체가 테이블로 생성된걸 볼 수 있다.
그리고 부모가 되는 테이블에 DType이란 이름으로 자식을 구분 할 수 있는 컬럼도 확인된다.


여기서 @DiscriminatorColumn의 이름과
각각 구분될때 들어가는 값들을 따로 정의할 수 있다

@DiscriminatorColumn에 이름을 부여하고
자식 엔티티에 @DiscriminatorValue 어노테이션을 추가해 정해주면 된다.

이대로 서버를 실행해 확인해보면



이렇게 자식 구분 컬럼의 이름과 값이 정의한 대로 잘 들어간 걸 볼 수 있다.

 

🛑 장점

  • 테이블의 정규화
  • 외래 키 참조 무결성 제약조건 활용 가능
  • 저장공간 효율성

🛑 단점

  • 조회시 많은 조인이 일어나 성능에 문제될 수 있음
  • 조회시 쿼리 복잡
  • INSERT SQL 두번 실행됨

 

 

단일 테이블 전략 Single-Table Strategy

이 전략은 모든 자식 엔티티가 부모 엔티티와 함께 단 하나의 테이블에 저장되는 전략이다.

@Inheritance 전략을 SINGLE_TABLE로 설정 하면 된다

 

이 전략에서는 위와 달리 @DiscriminatorColumn 이 필수로 들어가야 한다.

 

 

서버를 실행하면 이렇게 EMPLOYEE 하나의 테이블에 구분 컬럼과 자식 엔티티의 컬럼들이 모두 생성된 걸 볼 수 있다.

 

 

🛑 장점

  • 조회 성능 빠름 (조인 X)
  • 조회 쿼리 단순

🛑 단점

  • 자식 엔티티 컬럼에 null 허용됨
  • 단일 테이블이라 오히려 몸집이 커져 성능에 문제 생길 수 있음

 

 

구현 클래스마다 테이블 전략 Table-per-Concrete-Class Strategy

이 전략은 @Inheritance 전략을 TABLE_PER_CLASS로 설정 하면 된다

(캡쳐가 잘못되었는데 이 전략에선 구분 컬럼을 사용하지 않는다. @DiscriminatorColumn 사용 X)

서버를 실행하면 이렇게 부모는 없고 자식 엔티티의 테이블만 생성된 걸 볼 수 있다.

보기에는 상속받고 실사용되는 객체만 테이블로 생기니 좋을 것 같지만 일반적으로 추천하지 않는 전략이라고 한다. (자바 ORM 표준 JPA 프로그래밍 250p)

 

🛑 장점

  • 자식 테이블 컬럼에 not null을 설정할 수 있음
  • 서브 타입 구별 처리에 효과적

🛑 단점

  • 자식이 여럿일 때 조회 성능 떨어짐 (UNION 사용)
  • 자식을 통합해 쿼리하기 어려움



MappedSuperClass

@MappedSuperClass 는 추상 클래스와 비슷하다. 부모 클래스는 엔티티로 정의하지 않고 super class로 정의된다.

부모는 자식에게 공통된 매핑 정보만 제공한다. 부모의 컬럼을 상속받은 자식 엔티티들이 각각 생성이 된다.

 

여기서 부모의 속성을 자식 엔티티에서 상속받아 재정의할 수 있다.

@AttributeOverride를 이용해 custom해준다.

 

 

서버를 실행하면 이렇게 부모의 엔티티를 상속받은 자식 엔티티가 매핑된 테이블이 두개 생기고

또 custom한대로 부모의 속성이 변경되어 컬럼으로 생성된 걸 볼 수 있다.

 

@MappedSuperClass 를 사용해 등록일자, 수정일자, 등록자, 수정자 등 공통으로 사용하는 속성을 효과적으로 관리할 수 있다. 

 

 


코드

 

https://github.com/recordbuffer/TIL/tree/main/Spring_Boot/jpa_study/src/main/java/com/udemy/jpa_study/inheritance

 

GitHub - recordbuffer/TIL: Today I Learned

Today I Learned. Contribute to recordbuffer/TIL development by creating an account on GitHub.

github.com

 


참고

Master Hibernate and JPA with Spring Boot in 100 Steps | Udemy

 

Master Hibernate and JPA with Spring Boot in 100 Steps

Learn Hibernate, JPA (Java Persistence API) and Spring Data JPA using Spring and Spring Boot

www.udemy.com

 

자바 ORM 표준 JPA 프로그래밍 | 김영한 - 교보문고 (kyobobook.co.kr)

 

자바 ORM 표준 JPA 프로그래밍 | 김영한 - 교보문고

자바 ORM 표준 JPA 프로그래밍 | 자바 ORM 표준 JPA는 SQL 작성 없이 객체를 데이터베이스에 직접 저장할 수 있게 도와주고, 객체와 관계형 데이터베이스의 차이도 중간에서 해결해준다. 이 책은 JPA

product.kyobobook.co.kr

 

반응형

댓글