1. 상속 관계 매핑
1.1. 관계형 데이터베이스와 객체의 상속 차이
관계형 데이터베이스는 상속이라는 개념이 존재하지 않는다. 대신 데이터베이스 설계에서 슈퍼타입-서브타입 모델링이라는 기법이 객체의 상속과 유사한 역할을 한다.
객체의 상속
// 객체의 상속 구조
class Item {
private Long id;
private String name;
private int price;
}
class Album extends Item {
private String artist;
private String etc;
}
class Book extends Item {
private String author;
private String isbn;
}
class Movie extends Item {
private String director;
private String actor;
}
데이터베이스 슈퍼타입-서브타입 모델링
슈퍼타입: ITEM 테이블
├── 서브타입: ALBUM 테이블
├── 서브타입: BOOK 테이블
└── 서브타입: MOVIE 테이블
1.2. 상속 관계 매핑 전략
JPA는 객체의 상속 구조를 데이터베이스에 구현하는 세 가지 전략을 제공한다:
| 전략 | 설명 | 주요 어노테이션 |
| 조인 전략 | 각각 테이블로 변환 | @Inheritance(strategy = InheritanceType.JOINED) |
| 단일 테이블 전략 | 통합 테이블로 변환 | @Inheritance(strategy = InheritanceType.SINGLE_TABLE) |
| 구현 클래스마다 테이블 전략 | 서브타입 테이블로 변환 | @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) |
공통 어노테이션
- @DiscriminatorColumn: 엔티티 구분 컬럼 지정
- @DiscriminatorValue: 구분 컬럼에 저장할 값 지정
2. 조인 전략 (JOINED Strategy)
2.1. 개념과 구조
가장 정규화된 형태로, 부모 테이블과 자식 테이블을 각각 생성하고 조인을 통해 데이터를 조회한다.

// 부모 엔티티
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn(name = "DTYPE") // 구분 컬럼
public abstract class Item {
@Id @GeneratedValue
private Long id;
private String name;
private int price;
}
// 자식 엔티티 - Album
@Entity
@DiscriminatorValue("ALBUM") // DTYPE 컬럼 값
public class Album extends Item {
private String artist;
private String etc;
}
// 자식 엔티티 - Book
@Entity
@DiscriminatorValue("BOOK")
public class Book extends Item {
private String author;
private String isbn;
}
// 자식 엔티티 - Movie
@Entity
@DiscriminatorValue("MOVIE")
public class Movie extends Item {
private String director;
private String actor;
}
2.2. 테이블 구조
생성되는 테이블
-- 부모 테이블
CREATE TABLE ITEM (
ID BIGINT PRIMARY KEY,
NAME VARCHAR(255),
PRICE INTEGER,
DTYPE VARCHAR(255) -- 구분 컬럼
);
-- 자식 테이블들
CREATE TABLE ALBUM (
ID BIGINT PRIMARY KEY,
ARTIST VARCHAR(255),
ETC VARCHAR(255),
FOREIGN KEY (ID) REFERENCES ITEM(ID)
);
CREATE TABLE BOOK (
ID BIGINT PRIMARY KEY,
AUTHOR VARCHAR(255),
ISBN VARCHAR(255),
FOREIGN KEY (ID) REFERENCES ITEM(ID)
);
CREATE TABLE MOVIE (
ID BIGINT PRIMARY KEY,
DIRECTOR VARCHAR(255),
ACTOR VARCHAR(255),
FOREIGN KEY (ID) REFERENCES ITEM(ID)
);
2.3. 데이터 저장과 조회
저장 예시
// Album 저장
Album album = new Album();
album.setName("최고의 앨범");
album.setPrice(15000);
album.setArtist("아이유");
album.setEtc("특별 한정판");
em.persist(album);
// 실행되는 SQL
// 1. INSERT INTO ITEM (ID, NAME, PRICE, DTYPE) VALUES (?, ?, ?, 'ALBUM')
// 2. INSERT INTO ALBUM (ID, ARTIST, ETC) VALUES (?, ?, ?)
조회 예시
// 조인을 통한 조회
Album findAlbum = em.find(Album.class, albumId);
// 실행되는 SQL
// SELECT i.*, a.*
// FROM ITEM i
// INNER JOIN ALBUM a ON i.ID = a.ID
// WHERE i.ID = ? AND i.DTYPE = 'ALBUM'
// 부모 타입으로 조회 (다형성 쿼리)
Item item = em.find(Item.class, itemId);
if (item instanceof Album) {
Album album = (Album) item;
System.out.println("아티스트: " + album.getArtist());
}
2.4. 장단점 분석
장점
- 테이블 정규화: 데이터 중복 최소화
- 외래키 참조 무결성: ITEM 테이블의 ID를 참조 가능
@Entity public class OrderItem { @ManyToOne @JoinColumn(name = "ITEM_ID") // ITEM 테이블 참조 private Item item; } - 저장 공간 효율성: 중복 데이터 없음
단점
- 성능 저하: 조회 시 조인이 필요
- 복잡한 쿼리: 조인으로 인한 쿼리 복잡도 증가
- INSERT 2번: 저장 시 부모/자식 테이블 각각 INSERT
3. 단일 테이블 전략 (SINGLE_TABLE Strategy)
3.1. 개념과 구조
모든 자식 엔티티를 하나의 테이블에 통합하여 저장하는 전략이다.

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "DTYPE")
public abstract class Item {
@Id @GeneratedValue
private Long id;
private String name;
private int price;
}
@Entity
@DiscriminatorValue("ALBUM")
public class Album extends Item {
private String artist;
private String etc;
}
@Entity
@DiscriminatorValue("BOOK")
public class Book extends Item {
private String author;
private String isbn;
}
@Entity
@DiscriminatorValue("MOVIE")
public class Movie extends Item {
private String director;
private String actor;
}
3.2. 테이블 구조
생성되는 단일 테이블
CREATE TABLE ITEM (
ID BIGINT PRIMARY KEY,
NAME VARCHAR(255),
PRICE INTEGER,
ARTIST VARCHAR(255), -- Album 전용
ETC VARCHAR(255), -- Album 전용
AUTHOR VARCHAR(255), -- Book 전용
ISBN VARCHAR(255), -- Book 전용
DIRECTOR VARCHAR(255), -- Movie 전용
ACTOR VARCHAR(255), -- Movie 전용
DTYPE VARCHAR(255) NOT NULL -- 구분 컬럼 (NOT NULL)
);
3.3. 데이터 저장과 조회
저장 예시
Album album = new Album();
album.setName("최고의 앨범");
album.setPrice(15000);
album.setArtist("아이유");
album.setEtc("특별 한정판");
em.persist(album);
// 실행되는 SQL (한 번만 실행)
// INSERT INTO ITEM (ID, NAME, PRICE, ARTIST, ETC, DTYPE)
// VALUES (?, ?, ?, ?, ?, 'ALBUM')
조회 예시
// 단일 테이블 조회 (조인 없음)
Album findAlbum = em.find(Album.class, albumId);
// 실행되는 SQL
// SELECT * FROM ITEM WHERE ID = ? AND DTYPE = 'ALBUM'
// 전체 상품 조회
List<Item> items = em.createQuery(
"SELECT i FROM Item i", Item.class)
.getResultList();
// 실행되는 SQL
// SELECT * FROM ITEM
3.4. 장단점 분석
장점
- 빠른 조회 성능: 조인 불필요
- 단순한 쿼리: 단일 테이블 조회
- INSERT 1번: 저장 시 한 번의 INSERT
단점
- NULL 허용: 자식 전용 컬럼들은 NULL 허용해야 함
- 테이블 비대화: 모든 컬럼이 한 테이블에 모임
- 컬럼 수 제한: 데이터베이스별 컬럼 수 제한 존재
4. 구현 클래스마다 테이블 전략 (TABLE_PER_CLASS Strategy)
4.1. 개념과 구조 (비권장)
각각의 자식 엔티티마다 테이블을 생성하는 전략으로, JPA 표준 스펙에 포함되어 있지만 실제 사용은 권장되지 않는다.

@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class Item {
@Id @GeneratedValue
private Long id;
private String name;
private int price;
}
@Entity
public class Album extends Item {
private String artist;
private String etc;
}
@Entity
public class Book extends Item {
private String author;
private String isbn;
}
@Entity
public class Movie extends Item {
private String director;
private String actor;
}
4.2. 테이블 구조
생성되는 테이블들
-- 부모 테이블 없음!
-- 각 자식 테이블에 모든 컬럼 포함
CREATE TABLE ALBUM (
ID BIGINT PRIMARY KEY,
NAME VARCHAR(255),
PRICE INTEGER,
ARTIST VARCHAR(255),
ETC VARCHAR(255)
);
CREATE TABLE BOOK (
ID BIGINT PRIMARY KEY,
NAME VARCHAR(255),
PRICE INTEGER,
AUTHOR VARCHAR(255),
ISBN VARCHAR(255)
);
CREATE TABLE MOVIE (
ID BIGINT PRIMARY KEY,
NAME VARCHAR(255),
PRICE INTEGER,
DIRECTOR VARCHAR(255),
ACTOR VARCHAR(255)
);
4.3. 데이터 저장과 조회
저장 예시
Album album = new Album();
album.setName("최고의 앨범");
album.setPrice(15000);
album.setArtist("아이유");
em.persist(album);
// INSERT INTO ALBUM (ID, NAME, PRICE, ARTIST, ETC) VALUES (?, ?, ?, ?, ?)
조회 예시 (문제점)
// 부모 타입으로 조회 시 UNION 사용
Item item = em.find(Item.class, itemId);
// 실행되는 SQL
// SELECT * FROM (
// SELECT ID, NAME, PRICE, ARTIST, ETC, 'ALBUM' as DTYPE FROM ALBUM
// UNION ALL
// SELECT ID, NAME, PRICE, AUTHOR, ISBN, 'BOOK' as DTYPE FROM BOOK
// UNION ALL
// SELECT ID, NAME, PRICE, DIRECTOR, ACTOR, 'MOVIE' as DTYPE FROM MOVIE
// ) WHERE ID = ?
// JPQL 사용 시
List<Item> items = em.createQuery(
"SELECT i FROM Item i", Item.class)
.getResultList();
// 모든 테이블을 UNION으로 조회 (성능 저하)
4.4. 장단점 분석
장점
- 서브타입 구분 명확: 테이블이 분리되어 있음
- NOT NULL 제약조건 사용 가능: 각 테이블별 제약조건 설정 가능
단점 (심각한 문제점)
- UNION 쿼리: 여러 테이블 조회 시 UNION 사용 (성능 저하)
- 통합 쿼리 어려움: 모든 자식 테이블 조회해야 함
- 외래키 참조 문제: ITEM 테이블이 없어 참조 불가능
- 확장성 문제: 새로운 자식 타입 추가 시 기존 쿼리 모두 영향
5. @MappedSuperclass
5.1. 개념과 목적
상속 관계 매핑이 아닌, 공통 매핑 정보를 상속하기 위한 기능이다. 엔티티가 아니므로 테이블과 매핑되지 않는다.

// 공통 속성을 모은 추상 클래스
@MappedSuperclass
public abstract class BaseEntity {
@Id @GeneratedValue
private Long id;
private String name;
@Column(updatable = false)
private LocalDateTime createdDate;
private LocalDateTime lastModifiedDate;
// 생성자, getter/setter
}
// BaseEntity 상속
@Entity
public class Member extends BaseEntity {
private String email;
private int age;
}
@Entity
public class Product extends BaseEntity {
private int price;
private int stockQuantity;
}
5.2. 테이블 구조
생성되는 테이블
-- 각 엔티티에 BaseEntity의 필드들이 포함됨
CREATE TABLE MEMBER (
ID BIGINT PRIMARY KEY,
NAME VARCHAR(255),
CREATED_DATE TIMESTAMP,
LAST_MODIFIED_DATE TIMESTAMP,
EMAIL VARCHAR(255),
AGE INTEGER
);
CREATE TABLE PRODUCT (
ID BIGINT PRIMARY KEY,
NAME VARCHAR(255),
CREATED_DATE TIMESTAMP,
LAST_MODIFIED_DATE TIMESTAMP,
PRICE INTEGER,
STOCK_QUANTITY INTEGER
);
5.3. 특징과 제약사항
주요 특징
- 엔티티가 아님: em.find(BaseEntity.class, id) 불가능
- 테이블과 매핑 안됨: BaseEntity 테이블 생성되지 않음
- 매핑 정보 제공만: 상속받는 엔티티에 매핑 정보 전달
- 추상 클래스 권장: 직접 생성/사용할 일이 없음
사용 예시
@MappedSuperclass
public abstract class BaseTimeEntity {
@Column(updatable = false)
private LocalDateTime createdDate;
@PrePersist
public void prePersist() {
this.createdDate = LocalDateTime.now();
}
@Column
private LocalDateTime updatedDate;
@PreUpdate
public void preUpdate() {
this.updatedDate = LocalDateTime.now();
}
}
// 감사 정보 추가
@MappedSuperclass
public abstract class BaseEntity extends BaseTimeEntity {
@CreatedBy
private String createdBy;
@LastModifiedBy
private String lastModifiedBy;
}
5.4. @MappedSuperclass vs @Inheritance
| 비교 항목 | @MappedSuperclass | @Inheritance |
| 목적 | 공통 매핑 정보 상속 | 객체 상속 구조 매핑 |
| 테이블 | 테이블 생성 안됨 | 테이블 생성됨 |
| 엔티티 | 엔티티 아님 | 엔티티임 |
| 조회 | 직접 조회 불가 | 다형성 조회 가능 |
| 참조 | 참조 관계 불가 | 참조 관계 가능 |
6. 실전 예제: 상속관계 매핑 적용
6.1. 요구사항 추가
기존 주문 시스템에 다음과 같은 요구사항이 추가되었다:
- 상품 종류: 음반(Album), 도서(Book), 영화(Movie) - 이후 확장 가능
- 모든 엔티티에 등록일과 수정일 필수
6.2. 도메인 모델 설계

// 공통 시간 정보 (MappedSuperclass)
@MappedSuperclass
@Getter @Setter
public abstract class BaseTimeEntity {
@Column(name = "CREATED_DATE", updatable = false)
private LocalDateTime createdDate;
@Column(name = "MODIFIED_DATE")
private LocalDateTime modifiedDate;
@PrePersist
public void prePersist() {
this.createdDate = LocalDateTime.now();
this.modifiedDate = this.createdDate;
}
@PreUpdate
public void preUpdate() {
this.modifiedDate = LocalDateTime.now();
}
}
// 상품 상속 구조 (조인 전략 사용)
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn(name = "PRODUCT_TYPE")
@Table(name = "PRODUCT")
public abstract class Product extends BaseTimeEntity {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String name;
@Column(nullable = false)
private int price;
@Column(name = "STOCK_QUANTITY")
private int stockQuantity;
// 공통 비즈니스 메서드
public void addStock(int quantity) {
this.stockQuantity += quantity;
}
public void removeStock(int quantity) {
int restStock = this.stockQuantity - quantity;
if (restStock < 0) {
throw new NotEnoughStockException("재고가 부족합니다.");
}
this.stockQuantity = restStock;
}
}
// 앨범 상품
@Entity
@DiscriminatorValue("ALBUM")
@Table(name = "ALBUM")
public class Album extends Product {
@Column(name = "ARTIST")
private String artist;
@Column(name = "ETC")
private String etc;
@Column(name = "GENRE")
private String genre;
@Column(name = "RELEASE_DATE")
private LocalDate releaseDate;
}
// 도서 상품
@Entity
@DiscriminatorValue("BOOK")
@Table(name = "BOOK")
public class Book extends Product {
@Column(name = "AUTHOR")
private String author;
@Column(name = "ISBN", unique = true)
private String isbn;
@Column(name = "PUBLISHER")
private String publisher;
@Column(name = "PAGE_COUNT")
private int pageCount;
}
// 영화 상품
@Entity
@DiscriminatorValue("MOVIE")
@Table(name = "MOVIE")
public class Movie extends Product {
@Column(name = "DIRECTOR")
private String director;
@Column(name = "ACTOR")
private String actor;
@Column(name = "DURATION_MINUTES")
private int durationMinutes;
@Column(name = "RATING")
private String rating; // 12세, 15세, 19세 등
}
// 회원에도 BaseTimeEntity 적용
@Entity
@Table(name = "MEMBER")
public class Member extends BaseTimeEntity {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String name;
@Column(unique = true, nullable = false)
private String email;
@Embedded
private Address address;
}
// 주문에도 BaseTimeEntity 적용
@Entity
@Table(name = "ORDERS")
public class Order extends BaseTimeEntity {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "MEMBER_ID")
private Member member;
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
private List<OrderItem> orderItems = new ArrayList<>();
@OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@JoinColumn(name = "DELIVERY_ID")
private Delivery delivery;
@Enumerated(EnumType.STRING)
private OrderStatus status;
}
6.3. 테이블 설계
상품 관련 테이블
-- 공통 상품 테이블
CREATE TABLE PRODUCT (
ID BIGINT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(255) NOT NULL,
PRICE INTEGER NOT NULL,
STOCK_QUANTITY INTEGER,
CREATED_DATE TIMESTAMP,
MODIFIED_DATE TIMESTAMP,
PRODUCT_TYPE VARCHAR(255) NOT NULL
);
-- 앨범 테이블
CREATE TABLE ALBUM (
ID BIGINT PRIMARY KEY,
ARTIST VARCHAR(255),
ETC VARCHAR(255),
GENRE VARCHAR(255),
RELEASE_DATE DATE,
FOREIGN KEY (ID) REFERENCES PRODUCT(ID)
);
-- 도서 테이블
CREATE TABLE BOOK (
ID BIGINT PRIMARY KEY,
AUTHOR VARCHAR(255),
ISBN VARCHAR(255) UNIQUE,
PUBLISHER VARCHAR(255),
PAGE_COUNT INTEGER,
FOREIGN KEY (ID) REFERENCES PRODUCT(ID)
);
-- 영화 테이블
CREATE TABLE MOVIE (
ID BIGINT PRIMARY KEY,
DIRECTOR VARCHAR(255),
ACTOR VARCHAR(255),
DURATION_MINUTES INTEGER,
RATING VARCHAR(255),
FOREIGN KEY (ID) REFERENCES PRODUCT(ID)
);
공통 시간 컬럼이 추가된 테이블
-- 회원 테이블
CREATE TABLE MEMBER (
ID BIGINT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(255) NOT NULL,
EMAIL VARCHAR(255) NOT NULL UNIQUE,
CITY VARCHAR(255),
STREET VARCHAR(255),
ZIPCODE VARCHAR(255),
CREATED_DATE TIMESTAMP,
MODIFIED_DATE TIMESTAMP
);
-- 주문 테이블
CREATE TABLE ORDERS (
ID BIGINT PRIMARY KEY AUTO_INCREMENT,
MEMBER_ID BIGINT,
DELIVERY_ID BIGINT,
STATUS VARCHAR(255),
CREATED_DATE TIMESTAMP,
MODIFIED_DATE TIMESTAMP,
FOREIGN KEY (MEMBER_ID) REFERENCES MEMBER(ID),
FOREIGN KEY (DELIVERY_ID) REFERENCES DELIVERY(ID)
);
6.4. 비즈니스 로직 예시
@Service
@Transactional
public class ProductService {
@PersistenceContext
private EntityManager em;
// 앨범 등록
public Long registerAlbum(AlbumDto albumDto) {
Album album = new Album();
album.setName(albumDto.getName());
album.setPrice(albumDto.getPrice());
album.setArtist(albumDto.getArtist());
album.setGenre(albumDto.getGenre());
album.setReleaseDate(albumDto.getReleaseDate());
album.setStockQuantity(albumDto.getStockQuantity());
em.persist(album);
return album.getId();
}
// 상품 조회 (다형성 활용)
public ProductDto getProduct(Long productId) {
Product product = em.find(Product.class, productId);
if (product instanceof Album) {
Album album = (Album) product;
return new AlbumDto(album);
} else if (product instanceof Book) {
Book book = (Book) product;
return new BookDto(book);
} else if (product instanceof Movie) {
Movie movie = (Movie) product;
return new MovieDto(movie);
}
throw new ProductNotFoundException("상품을 찾을 수 없습니다.");
}
// 특정 타입의 상품들 조회
public List<Album> getAlbumsByArtist(String artist) {
return em.createQuery(
"SELECT a FROM Album a WHERE a.artist = :artist",
Album.class)
.setParameter("artist", artist)
.getResultList();
}
// 모든 상품 조회 (다형성 쿼리)
public List<Product> getAllProducts() {
return em.createQuery(
"SELECT p FROM Product p ORDER BY p.createdDate DESC",
Product.class)
.getResultList();
}
}
7. 전략 선택 가이드라인
7.1. 상속 전략 선택 기준
조인 전략 (JOINED) 사용 시
- 객체의 상속 구조를 정확히 표현해야 할 때
- 정규화된 데이터 구조가 필요할 때
- 다양한 타입의 확장이 예상될 때
- 외래키 참조 무결성이 중요할 때
단일 테이블 전략 (SINGLE_TABLE) 사용 시
- 조회 성능이 가장 중요할 때
- 자식 엔티티의 컬럼이 적을 때
- NULL 허용이 문제되지 않을 때
- 단순한 도메인 구조일 때
구현 클래스마다 테이블 전략 (TABLE_PER_CLASS)
- 사용하지 말 것
7.2. @MappedSuperclass 사용 시기
다음과 같은 경우에 @MappedSuperclass를 사용한다:
- 공통 속성이 많을 때
// 등록일, 수정일, 등록자, 수정자 등 @MappedSuperclass public abstract class AuditEntity { private LocalDateTime createdDate; private String createdBy; private LocalDateTime modifiedDate; private String modifiedBy; } - 엔티티가 아닌 공통 기능이 필요할 때
// 비즈니스 키를 가진 엔티티 @MappedSuperclass public abstract class BusinessKeyEntity { @Column(unique = true, nullable = false) private String businessKey; public abstract String generateBusinessKey(); } - 테이블 생성 없이 속성만 상속받고 싶을 때
7.3. 실무 권장사항
- 상속 관계가 단순할 때: 단일 테이블 전략 고려
- 상속 관계가 복잡하거나 확장 가능성 있을 때: 조인 전략 사용
- 공통 속성은 항상: @MappedSuperclass로 분리
- 기본값 설정: 조인 전략을 기본으로 고려
@Inheritance(strategy = InheritanceType.JOINED) // 기본 전략 @DiscriminatorColumn(name = "DTYPE")
8. 정리
고급 매핑 기능은 JPA의 강력한 기능이지만, 신중하게 사용해야 한다:
- 상속 매핑은 신중하게
- 객체 상속과 테이블 설계의 괴리를 이해할 것
- 프로젝트 특성에 맞는 전략 선택할 것
- 성능과 유지보수성 균형 고려할 것
- @MappedSuperclass는 적극 활용
- 공통 속성과 행위 추출할 것
- 코드 중복 제거와 일관성 유지할 것
- Audit 정보 같은 공통 관심사 분리할 것
- 확장성 고려
- 새로운 상품 타입 추가를 염두에 둘 것
- 다형성 쿼리와 타입 캐스팅 활용할 것
- DTO 변환 로직 효율적으로 설계할 것
상속 관계 매핑은 객체지향의 강력한 특성을 데이터베이스에 잘 반영할 수 있게 해주지만, 각 전략의 장단점을 충분히 이해하고 프로젝트의 요구사항에 맞게 선택하는 것이 중요하다. @MappedSuperclass는 코드 재사용과 유지보수성을 높이는 간단하면서도 효과적인 방법이다.
'Spring > JPA' 카테고리의 다른 글
| [Basic-8] 값 타입 (0) | 2026.01.06 |
|---|---|
| [Basic-7] 프록시와 연관관계 관리 (0) | 2026.01.06 |
| [Basic-5] 연관관계 매핑 심화 (0) | 2026.01.06 |
| [Basic-4] 연관관계 매핑 기초 (0) | 2026.01.06 |
| [Basic-3] 엔터티 매핑 (Entity Mapping) (0) | 2026.01.06 |
