일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 게시글 목록 api
- 자바의 정석 기초편 ch11
- 자바의 정석 기초편 ch6
- 스프링 mvc1 - 스프링 mvc
- 자바의 정석 기초편 ch8
- 자바의 정석 기초편 ch14
- 자바의 정석 기초편 ch3
- 스프링 입문(무료)
- 코드로 시작하는 자바 첫걸음
- 스프링 mvc2 - 검증
- 자바의 정석 기초편 ch12
- 자바의 정석 기초편 ch13
- 스프링 db2 - 데이터 접근 기술
- 스프링 db1 - 스프링과 문제 해결
- @Aspect
- 스프링 mvc2 - 타임리프
- 자바의 정석 기초편 ch9
- 자바의 정석 기초편 ch5
- jpa - 객체지향 쿼리 언어
- 타임리프 - 기본기능
- 2024 정보처리기사 시나공 필기
- 자바의 정석 기초편 ch4
- 스프링 mvc1 - 서블릿
- 스프링 고급 - 스프링 aop
- 자바의 정석 기초편 ch2
- jpa 활용2 - api 개발 고급
- 자바의 정석 기초편 ch7
- 2024 정보처리기사 수제비 실기
- 스프링 mvc2 - 로그인 처리
- 자바의 정석 기초편 ch1
- Today
- Total
나구리의 개발공부기록
고급 매핑, 상속관계 매핑, Mapped Superclass - 매핑 정보 상속, 실전 예제 - 상속관계 매핑 본문
고급 매핑, 상속관계 매핑, Mapped Superclass - 매핑 정보 상속, 실전 예제 - 상속관계 매핑
소소한나구리 2024. 10. 11. 11:31출처 : 인프런 - 자바 ORM 표준 JPA 프로그래밍 - 기본편(유료) / 김영한님
유료 강의이므로 정리에 초점을 두고 코드는 일부만 인용
** 실전예제 전까지는 DB URL 설정을 test로 설정하여 실습
<property name="jakarta.persistence.jdbc.url" value="jdbc:h2:tcp://localhost/~/test"/>
** 실전예제는 jdbc.url 설정을 jpashop으로 변경해서 진행
<property name="jakarta.persistence.jdbc.url" value="jdbc:h2:tcp://localhost/~/jpashop"/>
1. 상속관계 매핑
1) 관계형 DB의 상속관계
- 관계형 데이터베이스는 상속관계가 없음
- 슈퍼타입 서브타입 관계라는 모델링 기법이 객체 상속과 유사함
- 객체의 상속 구조와 슈퍼타입 서브타입 관계를 매핑함
(1) 슈퍼타입 서브타입 논리 모델을 실제 물리 모델로 구현하는 방법
- 각각의 정규화가 잘 된 테이블로 변환 -> 조인 전략
- 통합 테이블로 변환 -> 단일 테이블 전략
- 서브타입 테이블로 변환 -> 구현 클래스마다 테이블 전략
2) 주요 애노테이션
(1) @Inheritance(strategy=InheritanceType.XXX)
- JOINED: 조인전략
- SINGLE_TABLE: 단일 테이블 전략 (기본값)
- TABLE_PER_CLASS: 구현 클래스마다 테이블 전략
(2) @DiscriminatorColumn(name="DTYPE")
- 상속관계 중 조상 엔터티에 적용
- 조상 테이블에 입력된 값이 어떤 자손 테이블의 값인지 구분하는 컬럼
- name 속성으로 컬럼명을 변경할 수 있음
- 항상 적용하는 것을 권장함
(3) @DiscriminatorValue("XXX")
- 상속관계 중 자손 엔터티에 적용
- DTYPE컬럼에 매핑되는 기본값은 자손타입의 엔터티명이 필드 값으로 입력됨
- 그 값에 입력되는값을 원하는 대로 변경할 수 있음
3) 코드 및 실행 결과
(1) Item 생성
- Item 클래스는 단독으로 사용할 일이 없으므로 추상클래스로 생성
package jpabook.jpashoptest.domain;
@Entity
public abstract class Item {
@Id @GeneratedValue
private Long id;
private String name;
private int price;
// ... getter, setter 생략
}
(2) Album 생성
package jpabook.jpashoptest.domain;
@Entity
public class Album extends Item {
private String artist;
// ... getter, setter 생략
}
(3) Movie 생성
package jpabook.jpashoptest.domain;
@Entity
public class Movie extends Item {
private String director;
private String actor;
// ... getter, setter 생략
}
(4) Book 생성
package jpabook.jpashoptest.domain;
@Entity
public class Book extends Item {
private String author;
private String isbn;
// ... getter, setter 생략
}
(5) @Inheritance 적용 - SINGLE_TABLE
- Item 테이블에 @Inheritance를 적용, 기본값이 SINGLE_TABLE 전략임
- Item 엔터티의 자손 엔터티들이 ITEM 테이블에 모두 통합되어 생성됨
- 한 테이블에 통합되어 있어 조인이 필요없어서 성능이 좋음
- 애플리케이션이 매우 단순할 때 적용
@Entity
@Inheritance
public abstract class Item {
// ... 코드 생략
}
// @Inheritance(strategy = InheritanceType.SINGLE_TABLE)
(6) @Inheritance 적용 - JOINED
- Item 테이블에 @Inheritance를 적용할 때 strategy 속성을 JOINED로 설정하면 엔터티의 상속관계가 각각의 정규화된 테이블로 생성됨
- JPA와 가장 유사한 모델링이며 조회 시 조인을 사용해야 함
- 테이블이 정규화 되어있고 외래키 참조 무결성 제약조건을 활용할 수 있음
- 복잡한 비즈니스 모델에서 적용
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public abstract class Item {
// ... 코드 생략
}
(7) @Inheritance 적용 - TABLE_PER_CLASS
- 상속관계 중 자손 엔터티에 조상클래스의 속성을 각각 적용시켜서 자손클래스의 엔터티만 테이블이 생성됨
- Item 클래스가 없으므로 DTYPE을 생성하는 애노테이션을 사용할 필요가 없음
- 값을 입력할 때는 쿼리도 한 번으로 입력할 수 있고 각 테이블의 값을 조회할 때에도 깔끔하게 조회할 수 있음
- 그러나 부모타입으로 조회할 때가 문제가 발생하는데 부모타입인 Item으로 값을 조회하게되면 상속관계의 모든 테이블을 union으로 전부 조회하는 굉장히 비효율적으로 동작할 수 밖에 없어 성능문제가 발생함
@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class Item {
// ... 코드 생략
}
(8) @DiscriminatorColumn, @DiscriminatorValue 적용
- Item 엔터티에 @DiscriminatorColumn 애노테이션을 적용하여 어떤 자손테이블의 값인지 구분할 수 있는 컬럼을 생성
- 컬럼명의 기본값은 DTYPE이며 name 설정으로 원하는 컬럼명으로 변경할 수 있음
- Book, Album, Movie에 값을 하나씩 입력해서 테이블을 확인해보면 Item 테이블에 DTYPE이라는 컬럼 생겨서 각각의 엔터티 명이 값으로 매핑되어 적용됨
- 자손 엔터티들에 @DiscriminatorValue를 적용하여 DTYPE 컬럼에 기본값으로 입력되는 것을 원하는 값으로 변경할 수 있음
- SINGLE_TABLE 전략으로 적용 시 ITEM 테이블에 자손 엔터티의 속성을 입력하여 null값이 발생함
- SINGLE_TABLE 전략은 한 테이블에 전부 자손 객체의 값이 들어오다보니 구분할 방법이 없기 때문에 @DiscriminatorColumn 애노테이션을 적용하지 않아도 DTYPE이 자동으로 생성됨
4) 전략별 장단점
(1) JOINED
장점 | 테이블이 정규화 되어있음 외래키 참조 무결성 제약 조건을 활용 가능함 저장공간의 효율성이 좋음 조인은 잘 사용하면 성능저하가 거의 없고 저장공간이 효율화 되기때문에 오히려 성능이 증가할 수 있음 |
단점 | 조회할 때 조인 사용이 많아져 쿼리가 복잡해짐 데이터 저장 시 insert 쿼리가 2번 호출 됨 SINGLE_TABLE전략에 비해 성능저하가 있을 뿐이지 그렇게 성능저하의 영향이 크지 않음 |
(2) SINGLE_TABLE
장점 | 조인이 필요 없어서 일반적으로 조회 성능이 빠름 조회 쿼리가 매우 단순함 |
단점 | 자식 엔터티가 매핑한 컬럼은 모두 null을 허용해야함 단일 테이블에 모든 것을 저장하므로 테이블이 커지기 때문에 상황에 따라 조회 성능이 오히려 느려질 수 있음 |
(3) TABLE_PER_CLASS - 사용하지 말 것
- 데이터베이스 설계자, ORM 전문가 둘 다 추천하지 않는 전략
장점 | 서브 타입을 명확하게 구분해서 처리할 때 효과적 not null 제약조건을 사용할 수 있음 |
단점 | 여러 자식 테이블을 함께 조회할 때 UNION SQL을 사용하기 때문에 성능이 느림(테이블 전체를 조회함) 자식 테이블을 통합해서 쿼리하기가 어려움 시스템의 변경이 일어나면 수정해야할 부분이 많아짐 |
** 결론적으로 JOINED, SINGLE_TABLE 전략 중에서 고민해야 하며 정말 단순한 경우에는 SINGLE_TABLE을 적용
2. @MappedSuperclass
1) 매핑 정보 상속 (상속관계 매핑이 아님 - 헷갈림 주의)
- 공통 매핑 정보가 필요할 때 사용함
- 즉, 상속관련 매핑과 관련이 있다기보다는 여러 테이블에서 공통적인 속성을 많이 사용할 때 객체입장에서 속성만 상속받은 것이라고 보면됨
- 객체 구성만 별도의 공통 속성이 존재하는 객체가 있는것이지 테이블은 상속받은 공통 속성과 본인의 속성으로 구성된 각각의 테이블이 생성됨
2) 예제 코드
- 애플리케이션의 모든 곳에 CRUD 정보가 항상 있어야한다는 룰이 추가되었다고 가정
- 기존의 코드는 유지한채로 위의 상황을 적용
(1) 단순한 적용 방법
- 1차원 적으로 생각해보면 정보를 담는 변수들이 모든 엔터티에 삽입하면 됨
- 그러나 이러한 방식은 개발자 입장에서는 조금 하드코딩 방식이고, 엔터티가 무수히 많으면 작업도 많아짐
private String createBy; // 작성자
private LocalDateTime createDate; // 작성일
private String lastModifiedBy; // 마지막 수정자
private LocalDateTime lastModifiedDate; // 마지막 수정일
(2) MappedSuperclass 적용
- 새로운 클래스를 작성하여 @MappedSuperclass라는 애노테이션을 적용하고 공통적으로 적용할 필드를 입력 후 게터,세터를 생성
- 공통 정보를 매핑할 클래스들에 extends로 생성한 클래스를 상속시키면 공통정보를 사용할 수 있음
** 참고
- 실무에서 이런 운영정보는 진짜 변경이 안되는 테이블을 제외하고는 항상 기본적으로 깔려있어야 함
- JPA는 이러한 기본정보들은 이벤트라는 기능을 통해서 이미 자동화 할 수 있고, 스프링 데이터 JPA함께 사용하면 애노테이션을 사용하여 심플하게 적용할 수 있음
@MappedSuperclass
public abstract class BaseEntity {
private String createBy; // 작성자
private LocalDateTime createDate; // 작성일
private String lastModifiedBy; // 마지막 수정자
private LocalDateTime lastModifiedDate; // 마지막 수정일
// getter, setter
}
// Member 클래스에 공통으로 사용해야할 필드가 정의된 BaseEntity를 상속시킴
@Entity
public class Member extends BaseEntity {
// 코드 생략
}
3) 정리
- 상속관계 매핑이 아니고, 엔터티도 아니여서 테이블과 매핑이 되지도 않음(테이블이 생성되지 않음)
- 부모 클래스를 상속 받은 자식 클래스에 매핑 정보만 제공함
- @MappedSuperclass는 조회, 검색이 불가능함 즉, em.find(BaseEntity)가 불가능
- 직접 생성해서 사용할 일이 없으므로 추상 클래스로 생성하는 것을 권장함
- 테이블과 관계 없고 단순히 엔터티가 공통으로 사용하는 매핑 정보를 모으는 역할을 하며 주로 등록일, 수정일, 등록인, 수정인과 같이 전체 엔터티에서 공통적으로 정보를 적용해야할 때 사용하며 실무에서도 자주 사용함
** 참고
- 엔터티(@Entity가 적용된 클래스)는 @Entity나 @MappedSuperclass가 적용된 클래스끼리만 상속이 가능함
3. 실전 예제 - 상속관계 매핑
1) 요구사항 추가
- 상품의 종류는 음반, 도서, 영화가 있고 이후 더 확장될 수 있음 - 상속관계매핑 적용
- 모든 데이터는 등록일과 수정일이 필수임 - @MappedSuperclass 적용
(1) 도메인 모델
- Item과 Album, Book, Movie의 엔터티들을 상속관계로 적용
(2) 테이블 설계
- 상속관계 매핑의 테이블 전략은 애플리케이션이 복잡하지 않으므로 싱글테이블 전략으로 설정
1) 상속관계 매핑 적용
(1) Book, Movie, Album 생성
- Item 엔터티를 상속받은 Book, Movie, Album 클래스를 @Entity로 설정
@Entity
public class Book extends Item {
private String author;
private String isbn;
// ...getter, setter 생략
}
@Entity
public class Album extends Item{
private String artist;
private String etc;
// ...getter, setter 생략
}
@Entity
public class Movie extends Item {
private String director;
private String actor;
// ...getter, setter 생략
}
(2) Item 수정
- Item 클래스는 단독으로 사용하지 않으므로 abstract를 붙혀서 추상클래스로 변환
- @Inheritance 애노테이션을 적용하여 상속관계 매핑
- 요구사항이 싱글테이블 전략인데, 기본값이 싱글테이블 전략이므로 따로 전략설정을 하지않아도 적용되며 싱글테이블전략은 DTYPE도 자동으로 생성되기 때문에 @DiscriminatorColumn 애노테이션을 적용하지 않아도 됨
@Entity
@Inheritance // 기본값이 싱글테이블 전략, 싱글테이블 전략은 DTYPE 속성이 자동으로 생성됨
public abstract class Item {
// 기존 코드 생략
}
** @MappedSuperclass 실습 생략
- @MappedSuperclass는 위에서 적용한것과 다른것이 없어 실습을 생략
- 각 엔터티에 적용할 공통 필드가 선언된 클래스를 하나 선언하여 @MappedSuperclass 애노테이션을 적용시키고, 적용하고싶은 모든 엔터티에 생성한 클래스를 상속 시키면, 해당클래스에 작성된 필드들이 모든 테이블에 들어가있음
** 참고
- 실무에서 상속관계를 적용하는 것은 장단점이 있으므로 트레이드오프가 필요함
- 애플리케이션이 작은경우는 상속관계가 대부분 잘 동작하지만 애플리케이션이 매우 커지고 사용자가 많아지면 정상적으로 동작하지 않는 경우가 발생하거나 너무 구조가 복잡해짐
- 애플리케이션이 커지는 경우에는 테이블을 최대한 단순하게 유지해야 기에 JSON으로 데이터를 묶어버리는 경우도 있음
- 초기 설계에서는 사용량이 많지 않으므로 객체지향적으로 설계한 후 절충을 해야하는 시점에 시스템을 보완하는 식으로 하는 것을 권장함