본문 바로가기
Java/ORM

JAVA ORM - JPA(상속을 매핑?)

by 티코딩 2024. 8. 5.

자바에는 상속이라는 개념이 있다. 하지만 관계형 DB에는 상속관계를 딱히 지원하지 않는다.

class Animal {
    void eat() {
        System.out.println("This animal eats food.");
    }
}

class Dog extends Animal {
    void bark() {
        System.out.println("The dog barks.");
    }
}

public class InheritanceExample {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.eat(); // 상위 클래스의 메서드
        dog.bark(); // 하위 클래스의 메서드
    }
}

 

상속이란 이런식으로 상위의 클래스나 인터페이스를 하위의 클래스가 상속받아서 메서드를 오버라이딩해서 구체화시키는 개념이다.

이런 상속의 개념을 JPA가 어떻게 상속관계 매핑을 할까?

 

슈퍼타입-서브타입 관계

객체지향 프로그래밍의 상속관계와 유사함. 공통의 슈퍼클래스를 가지는 여러 서브클래스들이 있을 때 사용된다.

데이터베이스 설계에서 이 개념을 반영해 엔티티간의 상속 구조를 테이블로 매핑한다.

슈퍼타입 - Animal

서브타입 - Elephant, Tiger..etc

이런 슈퍼타입-서브타입 관계를 매핑하기 위한 3가지 전략이 있다.

 

 

1. 단일테이블 전략

모든 서브타입을 하나의 테이블에 저장한다. 각각의 서브타입은 구분자 컬럼으로 식별함.

쿼리가 단순해 성능이 좋지만 테이블이 커지고, 종속된 컬럼이 많아질 경우 NULL값이 많이 발생한다.(자식클래스의 컬럼들은 모두 NULL허용해야 함)

CREATE TABLE vehicle (
    id BIGINT PRIMARY KEY,
    dtype VARCHAR(31), -- 구분자 컬럼
    color VARCHAR(31),
    speed INT,
    numberOfDoors INT, -- Car 속성
    hasCarrier BOOLEAN -- Bike 속성
);

JPA로 구현

import javax.persistence.*;

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "dtype") // <-- 구분자 컬럼
public abstract class Vehicle {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
	...
}

@Entity
@DiscriminatorValue("C") // <-- 서브타입 구분 값
public class Car extends Vehicle {
    private int seatingCapacity;
	...
}

@Entity
@DiscriminatorValue("B") // <-- 서브타입 구분 값
public class Bike extends Vehicle {
    private boolean hasCarrier;
	...
}

@DiscriminatorColumn(name = "dtype") 이렇게 구분자를 정의해주고, @DiscriminatorValue를 붙혀서 서브타입을 구분했다.

2. 조인 전략

슈퍼타입과 각 서브타입에 대해 별도의 테이블을 생성하고, 조인을 통해 관계를 맺는다.

테이블 구조가 정규화 되어 중복은 없지만, 쿼리시 조인으로 성능저하가 발생할 수도 있다.

CREATE TABLE vehicle (
    id BIGINT PRIMARY KEY,
    color VARCHAR(31),
    speed INT
);

CREATE TABLE car (
    id BIGINT PRIMARY KEY,
    numberOfDoors INT,
    FOREIGN KEY (id) REFERENCES vehicle(id)
);

CREATE TABLE bike (
    id BIGINT PRIMARY KEY,
    hasCarrier BOOLEAN,
    FOREIGN KEY (id) REFERENCES vehicle(id)
);

JPA로 구현

@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public abstract class Vehicle {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
	...
}

@Entity
public class Car extends Vehicle {
    private int seatingCapacity;
	...
}

@Entity
public class Bike extends Vehicle {
    private boolean hasCarrier;
	...
}

@Inheritance(strategy = InheritanceType.JOINED) --> 조인 전략을 사용해 각 클래스별 테이블을 생성한다는 뜻.

 

3. 각 클래스별 테이블 전략(쓰지말자)

슈퍼타입과 서브타입 각각에 대해 독립적인 테이블을 생성하고 각 테이블은 자신의 모든 속성을 가진다.

쿼리 시 조인이 필요없지만, 데이터 중복이 발생할 수 있다.

CREATE TABLE car (
    id BIGINT PRIMARY KEY,
    color VARCHAR(31),
    speed INT,
    numberOfDoors INT
);

CREATE TABLE bike (
    id BIGINT PRIMARY KEY,
    color VARCHAR(31),
    speed INT,
    hasCarrier BOOLEAN
);

 

JPA로 구현

@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class Vehicle {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
	...
}

@Entity
public class Car extends Vehicle {
    private int seatingCapacity;
	...
}

@Entity
public class Bike extends Vehicle {
    private boolean hasCarrier;
	...
}

@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) -> 각 클래스별 테이블 전략을 사용해 각각의 테이블 생성한다는 뜻.

그리고 추상클래스로 슈퍼테이블을 구현한다. 그러면 DB에 테이블은 Car, Bike만 생성된다.

 

1 or 2만 쓰자!