관계형 데이터베이스는 객체처럼 상속이라는 개념이 대부분 존재하지 않습니다. 그나마 슈퍼타입 서브타입 관계라는 모델링 기법이 객체 상속과 유사합니다. 상속관계 매핑이란 객체의 상속 구조와 데이터베이스의 슈퍼타입 서브타입 관계를 매핑하는 것입니다. 이번 포스팅을 통해 상속관계 매핑을 물리 모델로 구현하는 방식을 정리하고자 합니다.
슈퍼타입 서브타입 논리 모델
객체지향 프로그래밍에서는 모델링과 구현이 상속을 활용하여 일치시킬 수 있지만, 데이터베이스는 상속을 직접적으로 지원하지 않아서 논리적인 모델과 물리적인 모델 간의 차이가 발생합니다.
JPA(Java Persistence API)는 이러한 객체지향과 데이터베이스 간의 패러다임 불일치를 해결하기 위해 상속 관계를 물리적인 데이터베이스 모델로 매핑하는 기능을 제공합니다.
물리 모델로 구현하는 방식은 다음과 같이 3가지로 나뉩니다.
- 각각 테이블로 변환 -> 조인 전략
- 통합 테이블로 변환 -> 단일 테이블 전략
- 서브 타입 테이블로 변환 -> 구현 클래스마다 테이블 전략
실제 구현에 사용될 애노테이션은 다음과 같습니다.
- @Inheritance(strategy = InheritanceType.XXX)
- JOINED: 조인 전략
- SINGLE_TABLE: 단일 테이블 전략 (default)
- TABLE_PER_CLASS: 구현 클래스마다 테이블 전략
- @DiscriminatorColumn(name = "DTYPE")
- 상위 클래스에 선언하며, 하위 클래스를 구분하기 위해 사용됩니다.
- @DiscriminatorValue("XXX")
- 하위 클래스에 선언하며, 엔티티를 저장할 때 슈퍼타입의 구분 컬럼에 저장할 값을 지정합니다.
- 애노테이션을 선언하지 않은 경우 클래스명이 기본값으로 들어갑니다.
객체의 상속 관계 구현

위 모델링을 객체로 상속관계를 구현하면 다음과 같습니다.
@Entity
@Inheritance(strategy = InheritanceType.XXX) // 전략 설정
public abstract class Item {
@Id @GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "item_id")
private Long id;
private String name;
private int price;
}
@Entity
public class Album extends Item {
private String artist;
}
@Entity
public class Movie extends Item {
private String director;
private String actor;
}
@Entity
public class Book extends Item {
private String author;
private String isbn;
}
조인 전략
@Inheritance(strategy=InheritanceType.JOINED)

복잡한 서비스 구조를 설계할 때 용이하며, 가장 정규화된 방식입니다. 중복되는 NAME, PRICE를 ITEM 테이블에 저장하고, ALBUM, MOVIE, BOOK의 테이블을 각각 저장합니다.
조인 전략은 @DiscriminatorColumn을 따로 선언하지 않으면 DTYPE 컬럼이 생성되지 않습니다. DTYPE 컬럼이 없어도 조인을 하면서 어떤 테이블인지 알 수 있으나 DTYPE을 지정해 주는 것이 좋습니다.
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn // 하위 테이블의 구분 컬럼 생성(default = DTYPE)
public class Item {
@Id @GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
private int price;
}
위와 같이 설정하면 실제 생성되는 테이블은 4개이며, 하위 테이블(ALBUM, MOVIE, BOOK)에 외래 키 제약 조건이 생성됩니다.
Movie 객체를 저장한다면 쿼리는 어떻게 발생될지 확인해보겠습니다.
// 트랜잭션 시작 begin
Movie movie = new Movie();
movie.setDirector("A");
movie.setName("spring");
movie.setPrice(15000);
em.persist(movie);
// 트랜잭션 종료 commit
Hibernate:
/* insert advancedmapping.Movie
*/ insert
into
Item
(id, name, price, DTYPE)
values
(null, ?, ?, 'Movie')
Hibernate:
/* insert advancedmapping.Movie
*/ insert
into
Movie
(actor, director, id)
values
(?, ?, ?)
총 2번의 insert 쿼리가 발생되며, DTYPE는 기본값으로 설정했다면 클래스명이 들어가게 됩니다.
데이터베이스에 저장된 시점에서 다시 Movie 엔티티를 조회합니다.
em.find(Movie.class, movie.getId());
Hibernate:
select
movie0_.id as id2_2_0_,
movie0_1_.name as name3_2_0_,
movie0_1_.price as price4_2_0_,
movie0_.actor as actor1_3_0_,
movie0_.director as director2_3_0_
from
Movie movie0_
inner join
Item movie0_1_
on movie0_.id=movie0_1_.id
where
movie0_.id=?
Moive 엔티티를 조회하면 위와 같이 ITEM 테이블과 조인되어 select 쿼리가 발생하게 됩니다.
단일 테이블 전략
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)

조인 전략과 다르게 한 테이블에 모든 컬럼을 저장하는 방식이며, 조인 전략처럼 테이블을 조회할 때 조인이 필요하지 않기 때문에 성능 면에서 훨씬 우월합니다. 서비스 로직의 규모가 별로 크지 않다면 단일 테이블 전략이 용이합니다.
단일 테이블 전략은 따로 @DiscriminatorColumn을 선언하지 않아도, 기본으로 DTYPE 컬럼이 추가됩니다. 이러한 이유는 한 테이블에 모든 컬럼을 저장하기 때문에 DTYPE이 있어야 구분을 할 수 있기 때문입니다.
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn // 생략 가능
public class Item {
@Id @GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
private int price;
}
싱글 테이블 전략은 말처럼 생성되는 테이블은 1개이며, 조인 전략처럼 Movie 엔티티를 데이터베이스에서 저장한 상황에서 조회를 하면 Movie 엔티티와 관련된 컬럼만 뽑아 select 쿼리가 발생하게 됩니다.
Hibernate:
select
movie0_.id as id2_0_0_,
movie0_.name as name3_0_0_,
movie0_.price as price4_0_0_,
movie0_.actor as actor8_0_0_,
movie0_.director as director9_0_0_
from
Item movie0_
where
movie0_.id=?
and movie0_.DTYPE='Movie'
구현 클래스마다 테이블 전략
@Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)

얼핏 보면 조인 전략과 유사해 보이나, 슈퍼 타입의 컬럼을 서브 타입에 같이 저장한 형태입니다. 따라서 ITEM 테이블이 존재하지 않기 때문에 NAME, PRICE 컬럼이 중복되는 특징을 가집니다.
@GeneratedValue(strategy = GenerationType.AUTO)를 사용할 때, 구현 클래스마다 테이블 전략을 사용하는 경우 ITEM 엔티티는 실제로 데이터베이스에 생성되는 테이블이 아니므로 추상 클래스이어야 합니다. 따라서 데이터베이스는 상위 타입이 존재하지 않기 때문에 @DiscriminatorColumn 애노테이션은 필요하지 않습니다.
또한 @GeneratedValue(strategy = GenerationType.IDENTITY)와 구현 클래스마다 테이블 전략은 같이 사용할 수 없으며, 사용 시 예외가 발생하기 때문에 주의해야 합니다.
@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class Item {
@Id @GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
private int price;
}
구현 클래스마다 테이블 전략은 상위 타입 테이블이 따로 존재하지 않기 때문에 하위 테이블 3개가 생성됩니다. 타 전략처럼 Movie 엔티티를 저장하고, 조회 쿼리를 확인해 보겠습니다.
Hibernate:
/* insert advancedmapping.Movie
*/ insert
into
Movie
(name, price, actor, director, id)
values
(?, ?, ?, ?, ?)
Hibernate:
select
movie0_.id as id1_2_0_,
movie0_.name as name2_2_0_,
movie0_.price as price3_2_0_,
movie0_.actor as actor1_3_0_,
movie0_.director as director2_3_0_
from
Movie movie0_
where
movie0_.id=?
🤔 Movie, Album, Book을 Item으로 조회하면 어떻게 될까?
객체 관점에서 Movie, Album, Book은 Item으로 조회가 가능합니다. 이에 데이터베이스는 조회가 가능하긴 하나, union all로 전체 하위 테이블을 찾게 되며, 굉장히 복잡한 쿼리가 발생하게 됩니다. 실제 조회를 해보면 다음과 같습니다.
em.find(Item.class, movie.getId());
Hibernate:
select
item0_.id as id1_2_0_,
item0_.name as name2_2_0_,
item0_.price as price3_2_0_,
item0_.artist as artist1_0_0_,
item0_.author as author1_1_0_,
item0_.isbn as isbn2_1_0_,
item0_.actor as actor1_3_0_,
item0_.director as director2_3_0_,
item0_.clazz_ as clazz_0_
from
( select
id,
name,
price,
artist,
null as author,
null as isbn,
null as actor,
null as director,
1 as clazz_
from
Album
union
all select
id,
name,
price,
null as artist,
author,
isbn,
null as actor,
null as director,
2 as clazz_
from
Book
union
all select
id,
name,
price,
null as artist,
null as author,
null as isbn,
actor,
director,
3 as clazz_
from
Movie
) item0_
where
item0_.id=?
각각의 전략의 장단점
조인 전략
- 장점
- 테이블이 정규화가 되어 있어, 외래 키 참조를 통해 무결성 제약조건 활용이 가능합니다.
- 복잡할수록 저장 공간의 효율성이 증대합니다.
- 단점
- 조회 시 조인이 필수적으로 적용되기 때문에 단일 테이블 전략에 비해 성능이 떨어집니다.
- 데이터를 저장 시 상위 테이블과 하위 테이블에 쿼리가 2번 발생합니다.
단일 테이블 전략
- 장점
- 하나의 테이블에 몰아서 저장하는 방식이기 때문에 성능 면에서는 가장 우월합니다.
- 단점
- 사용되지 않은 컬럼은 모두 null을 허용해야 합니다.
- 단일 테이블에 모두 저장하기 때문에 데이터가 커질수록 성능이 느려질 수 있습니다.
구현 클래스마다 테이블 전략
- 장점
- 서브 타입을 명확하게 구분하여 처리할 때 효율적입니다.
- 단일 테이블 전략과 다르게 not null 제약 조건을 사용할 수 있습니다.
- 단점
- 일반적으로 사용되지 않는 전략입니다.
- 여러 자식 테이블을 한꺼번에 조회할 때 성능이 매우 느리며, 변경에 유연하지 못합니다.
@MappedSuperClass
위 상속관계 매핑과 다르게 상위 클래스는 테이블과 매핑하지 않고, 상위 클래스를 상속 받은 자식 클래스에게 부모 클래스가 가지는 컬럼만 매핑 정보로 제공하고 싶다면 @MappedSuperClass 애노테이션을 사용하면 됩니다.

예를 들어 각각의 엔티티는 등록자, 생성날짜, 수정자, 수정날짜를 공통으로 가져가야 하는 경우가 있습니다. 이러한 경우 @MappedSuperClass는 다음과 같이 활용할 수 있습니다.
@Getter @Setter
@MappedSuperclass
public abstract class BaseEntity {
private String createdBy;
private LocalDateTime createdDate;
private String lastModifiedBy;
private LocalDateTime lastModifiedDate;
}
추상 클래스 BaseEntity를 만들고 필요한 엔티티에 상속시켜주면 됩니다.
@Entity
public class Item extends BaseEntity {
...
}
객체 관점에서는 서로 구분되어 있으나, 데이터베이스에서는 하나의 테이블의 컬럼으로 생성됩니다.
@MappedSuperClass가 선언되어 있는 클래스는 엔티티가 아니며, 테이블과 매핑되지 않습니다. 오직 상위 클래스를 상속 받는 클래스에게 매핑 정보만 제공하는 역할을 합니다.
자바 ORM 표준 JPA 프로그래밍 - 기본편 | 김영한 - 인프런
김영한 | JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자신있게 JPA를 사용할 수 있습니다., 실무에서도
www.inflearn.com
'Spring > JPA' 카테고리의 다른 글
즉시 로딩(Eager)과 지연 로딩(Lazy) (0) | 2024.04.28 |
---|---|
프록시(Proxy) (0) | 2024.04.28 |
연관관계 매핑 정리 (0) | 2024.04.23 |
엔티티 매핑(Entity Mapping) - 기본 키 (1) | 2024.04.22 |
엔티티 매핑(Entity Mapping) - 객체와 테이블, 필드와 컬럼 (1) | 2024.04.21 |
관계형 데이터베이스는 객체처럼 상속이라는 개념이 대부분 존재하지 않습니다. 그나마 슈퍼타입 서브타입 관계라는 모델링 기법이 객체 상속과 유사합니다. 상속관계 매핑이란 객체의 상속 구조와 데이터베이스의 슈퍼타입 서브타입 관계를 매핑하는 것입니다. 이번 포스팅을 통해 상속관계 매핑을 물리 모델로 구현하는 방식을 정리하고자 합니다.
슈퍼타입 서브타입 논리 모델
객체지향 프로그래밍에서는 모델링과 구현이 상속을 활용하여 일치시킬 수 있지만, 데이터베이스는 상속을 직접적으로 지원하지 않아서 논리적인 모델과 물리적인 모델 간의 차이가 발생합니다.
JPA(Java Persistence API)는 이러한 객체지향과 데이터베이스 간의 패러다임 불일치를 해결하기 위해 상속 관계를 물리적인 데이터베이스 모델로 매핑하는 기능을 제공합니다.
물리 모델로 구현하는 방식은 다음과 같이 3가지로 나뉩니다.
- 각각 테이블로 변환 -> 조인 전략
- 통합 테이블로 변환 -> 단일 테이블 전략
- 서브 타입 테이블로 변환 -> 구현 클래스마다 테이블 전략
실제 구현에 사용될 애노테이션은 다음과 같습니다.
- @Inheritance(strategy = InheritanceType.XXX)
- JOINED: 조인 전략
- SINGLE_TABLE: 단일 테이블 전략 (default)
- TABLE_PER_CLASS: 구현 클래스마다 테이블 전략
- @DiscriminatorColumn(name = "DTYPE")
- 상위 클래스에 선언하며, 하위 클래스를 구분하기 위해 사용됩니다.
- @DiscriminatorValue("XXX")
- 하위 클래스에 선언하며, 엔티티를 저장할 때 슈퍼타입의 구분 컬럼에 저장할 값을 지정합니다.
- 애노테이션을 선언하지 않은 경우 클래스명이 기본값으로 들어갑니다.
객체의 상속 관계 구현

위 모델링을 객체로 상속관계를 구현하면 다음과 같습니다.
@Entity
@Inheritance(strategy = InheritanceType.XXX) // 전략 설정
public abstract class Item {
@Id @GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "item_id")
private Long id;
private String name;
private int price;
}
@Entity
public class Album extends Item {
private String artist;
}
@Entity
public class Movie extends Item {
private String director;
private String actor;
}
@Entity
public class Book extends Item {
private String author;
private String isbn;
}
조인 전략
@Inheritance(strategy=InheritanceType.JOINED)

복잡한 서비스 구조를 설계할 때 용이하며, 가장 정규화된 방식입니다. 중복되는 NAME, PRICE를 ITEM 테이블에 저장하고, ALBUM, MOVIE, BOOK의 테이블을 각각 저장합니다.
조인 전략은 @DiscriminatorColumn을 따로 선언하지 않으면 DTYPE 컬럼이 생성되지 않습니다. DTYPE 컬럼이 없어도 조인을 하면서 어떤 테이블인지 알 수 있으나 DTYPE을 지정해 주는 것이 좋습니다.
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn // 하위 테이블의 구분 컬럼 생성(default = DTYPE)
public class Item {
@Id @GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
private int price;
}
위와 같이 설정하면 실제 생성되는 테이블은 4개이며, 하위 테이블(ALBUM, MOVIE, BOOK)에 외래 키 제약 조건이 생성됩니다.
Movie 객체를 저장한다면 쿼리는 어떻게 발생될지 확인해보겠습니다.
// 트랜잭션 시작 begin
Movie movie = new Movie();
movie.setDirector("A");
movie.setName("spring");
movie.setPrice(15000);
em.persist(movie);
// 트랜잭션 종료 commit
Hibernate:
/* insert advancedmapping.Movie
*/ insert
into
Item
(id, name, price, DTYPE)
values
(null, ?, ?, 'Movie')
Hibernate:
/* insert advancedmapping.Movie
*/ insert
into
Movie
(actor, director, id)
values
(?, ?, ?)
총 2번의 insert 쿼리가 발생되며, DTYPE는 기본값으로 설정했다면 클래스명이 들어가게 됩니다.
데이터베이스에 저장된 시점에서 다시 Movie 엔티티를 조회합니다.
em.find(Movie.class, movie.getId());
Hibernate:
select
movie0_.id as id2_2_0_,
movie0_1_.name as name3_2_0_,
movie0_1_.price as price4_2_0_,
movie0_.actor as actor1_3_0_,
movie0_.director as director2_3_0_
from
Movie movie0_
inner join
Item movie0_1_
on movie0_.id=movie0_1_.id
where
movie0_.id=?
Moive 엔티티를 조회하면 위와 같이 ITEM 테이블과 조인되어 select 쿼리가 발생하게 됩니다.
단일 테이블 전략
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)

조인 전략과 다르게 한 테이블에 모든 컬럼을 저장하는 방식이며, 조인 전략처럼 테이블을 조회할 때 조인이 필요하지 않기 때문에 성능 면에서 훨씬 우월합니다. 서비스 로직의 규모가 별로 크지 않다면 단일 테이블 전략이 용이합니다.
단일 테이블 전략은 따로 @DiscriminatorColumn을 선언하지 않아도, 기본으로 DTYPE 컬럼이 추가됩니다. 이러한 이유는 한 테이블에 모든 컬럼을 저장하기 때문에 DTYPE이 있어야 구분을 할 수 있기 때문입니다.
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn // 생략 가능
public class Item {
@Id @GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
private int price;
}
싱글 테이블 전략은 말처럼 생성되는 테이블은 1개이며, 조인 전략처럼 Movie 엔티티를 데이터베이스에서 저장한 상황에서 조회를 하면 Movie 엔티티와 관련된 컬럼만 뽑아 select 쿼리가 발생하게 됩니다.
Hibernate:
select
movie0_.id as id2_0_0_,
movie0_.name as name3_0_0_,
movie0_.price as price4_0_0_,
movie0_.actor as actor8_0_0_,
movie0_.director as director9_0_0_
from
Item movie0_
where
movie0_.id=?
and movie0_.DTYPE='Movie'
구현 클래스마다 테이블 전략
@Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)

얼핏 보면 조인 전략과 유사해 보이나, 슈퍼 타입의 컬럼을 서브 타입에 같이 저장한 형태입니다. 따라서 ITEM 테이블이 존재하지 않기 때문에 NAME, PRICE 컬럼이 중복되는 특징을 가집니다.
@GeneratedValue(strategy = GenerationType.AUTO)를 사용할 때, 구현 클래스마다 테이블 전략을 사용하는 경우 ITEM 엔티티는 실제로 데이터베이스에 생성되는 테이블이 아니므로 추상 클래스이어야 합니다. 따라서 데이터베이스는 상위 타입이 존재하지 않기 때문에 @DiscriminatorColumn 애노테이션은 필요하지 않습니다.
또한 @GeneratedValue(strategy = GenerationType.IDENTITY)와 구현 클래스마다 테이블 전략은 같이 사용할 수 없으며, 사용 시 예외가 발생하기 때문에 주의해야 합니다.
@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class Item {
@Id @GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
private int price;
}
구현 클래스마다 테이블 전략은 상위 타입 테이블이 따로 존재하지 않기 때문에 하위 테이블 3개가 생성됩니다. 타 전략처럼 Movie 엔티티를 저장하고, 조회 쿼리를 확인해 보겠습니다.
Hibernate:
/* insert advancedmapping.Movie
*/ insert
into
Movie
(name, price, actor, director, id)
values
(?, ?, ?, ?, ?)
Hibernate:
select
movie0_.id as id1_2_0_,
movie0_.name as name2_2_0_,
movie0_.price as price3_2_0_,
movie0_.actor as actor1_3_0_,
movie0_.director as director2_3_0_
from
Movie movie0_
where
movie0_.id=?
🤔 Movie, Album, Book을 Item으로 조회하면 어떻게 될까?
객체 관점에서 Movie, Album, Book은 Item으로 조회가 가능합니다. 이에 데이터베이스는 조회가 가능하긴 하나, union all로 전체 하위 테이블을 찾게 되며, 굉장히 복잡한 쿼리가 발생하게 됩니다. 실제 조회를 해보면 다음과 같습니다.
em.find(Item.class, movie.getId());
Hibernate:
select
item0_.id as id1_2_0_,
item0_.name as name2_2_0_,
item0_.price as price3_2_0_,
item0_.artist as artist1_0_0_,
item0_.author as author1_1_0_,
item0_.isbn as isbn2_1_0_,
item0_.actor as actor1_3_0_,
item0_.director as director2_3_0_,
item0_.clazz_ as clazz_0_
from
( select
id,
name,
price,
artist,
null as author,
null as isbn,
null as actor,
null as director,
1 as clazz_
from
Album
union
all select
id,
name,
price,
null as artist,
author,
isbn,
null as actor,
null as director,
2 as clazz_
from
Book
union
all select
id,
name,
price,
null as artist,
null as author,
null as isbn,
actor,
director,
3 as clazz_
from
Movie
) item0_
where
item0_.id=?
각각의 전략의 장단점
조인 전략
- 장점
- 테이블이 정규화가 되어 있어, 외래 키 참조를 통해 무결성 제약조건 활용이 가능합니다.
- 복잡할수록 저장 공간의 효율성이 증대합니다.
- 단점
- 조회 시 조인이 필수적으로 적용되기 때문에 단일 테이블 전략에 비해 성능이 떨어집니다.
- 데이터를 저장 시 상위 테이블과 하위 테이블에 쿼리가 2번 발생합니다.
단일 테이블 전략
- 장점
- 하나의 테이블에 몰아서 저장하는 방식이기 때문에 성능 면에서는 가장 우월합니다.
- 단점
- 사용되지 않은 컬럼은 모두 null을 허용해야 합니다.
- 단일 테이블에 모두 저장하기 때문에 데이터가 커질수록 성능이 느려질 수 있습니다.
구현 클래스마다 테이블 전략
- 장점
- 서브 타입을 명확하게 구분하여 처리할 때 효율적입니다.
- 단일 테이블 전략과 다르게 not null 제약 조건을 사용할 수 있습니다.
- 단점
- 일반적으로 사용되지 않는 전략입니다.
- 여러 자식 테이블을 한꺼번에 조회할 때 성능이 매우 느리며, 변경에 유연하지 못합니다.
@MappedSuperClass
위 상속관계 매핑과 다르게 상위 클래스는 테이블과 매핑하지 않고, 상위 클래스를 상속 받은 자식 클래스에게 부모 클래스가 가지는 컬럼만 매핑 정보로 제공하고 싶다면 @MappedSuperClass 애노테이션을 사용하면 됩니다.

예를 들어 각각의 엔티티는 등록자, 생성날짜, 수정자, 수정날짜를 공통으로 가져가야 하는 경우가 있습니다. 이러한 경우 @MappedSuperClass는 다음과 같이 활용할 수 있습니다.
@Getter @Setter
@MappedSuperclass
public abstract class BaseEntity {
private String createdBy;
private LocalDateTime createdDate;
private String lastModifiedBy;
private LocalDateTime lastModifiedDate;
}
추상 클래스 BaseEntity를 만들고 필요한 엔티티에 상속시켜주면 됩니다.
@Entity
public class Item extends BaseEntity {
...
}
객체 관점에서는 서로 구분되어 있으나, 데이터베이스에서는 하나의 테이블의 컬럼으로 생성됩니다.
@MappedSuperClass가 선언되어 있는 클래스는 엔티티가 아니며, 테이블과 매핑되지 않습니다. 오직 상위 클래스를 상속 받는 클래스에게 매핑 정보만 제공하는 역할을 합니다.
자바 ORM 표준 JPA 프로그래밍 - 기본편 | 김영한 - 인프런
김영한 | JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자신있게 JPA를 사용할 수 있습니다., 실무에서도
www.inflearn.com
'Spring > JPA' 카테고리의 다른 글
즉시 로딩(Eager)과 지연 로딩(Lazy) (0) | 2024.04.28 |
---|---|
프록시(Proxy) (0) | 2024.04.28 |
연관관계 매핑 정리 (0) | 2024.04.23 |
엔티티 매핑(Entity Mapping) - 기본 키 (1) | 2024.04.22 |
엔티티 매핑(Entity Mapping) - 객체와 테이블, 필드와 컬럼 (1) | 2024.04.21 |