[SpringBoot-JPA 기본편] 자바 ORM 표준 JPA 프로그래밍 - 기본편: 7. 고급 매핑
강의 출처:
https://www.inflearn.com/course/ORM-JPA-Basic#curriculum
자바 ORM 표준 JPA 프로그래밍 - 기본편 강의 - 인프런
현업에서 실제로 JPA로 개발을 하고 있습니다. 그런 입장에서보면 지금 작성하고 있는 코드들이 어떻게 작동하는지 이해하는데 큰 도움을 주는 강의입니다. 다음은 제가 느낀 이 강의의 장점들
www.inflearn.com
해당 강의는 Inflearn에 등록된 김영한님의 Spring Boot 강의입니다.
이번시간에는 상속 관계 매핑에 대해서 공부하자.
1. 상속관계 매핑
관계형 데이터베이스(ex: H2,mysql 등)는 테이블, 행, 열로 구성되어 있기에 직접적으로 상속관계를 설명하기 어렵다. 다만 슈퍼타입, 서브타입이라는 개념을 도입하면 된다.
슈퍼타입(Supertype)
- 슈퍼타입은 보다 일반적인 엔티티를 말한다. 예를 들어, 차량을 슈퍼타입의 한 예로 들자. 모든 차량이 공통으로 가지는 속성(예: 차량번호, 제조사)을 포함하고 있다.
서브타입(Subtype)
- 서브타입은 특정 슈퍼타입에서 파생된 보다 구체적인 엔티티를 말한다. '차량'이라는 슈퍼타입을 기반으로 '승용차', '트럭' 같은 서브타입을 만들 수 있다. 이 서브타입들은 각각 승용차나 트럭만의 고유한 속성(예: 승용차의 좌석 수, 트럭의 적재량)을 가질 수 있다.
그러면 객체에서는 어떻게 상속 관계를 매핑할까? 지난 시간에 배운것을 토대로 하면 각 클래스마다 슈퍼타입 객체의 id를 FK로 갖고오고, 슈퍼타입 객체의 속성들을 일일이 매핑하는 방법도 있을 수 있다. 하지만 이런건 객체지향 스럽지도 않고, 상속이라고도 말하기도 애매하다.
JPA에서는 그러한 방법 말고 더 편한 방법인 3가지를 추천한다. 이러한 것을 상속 매핑 전략이라고 한다.
(저는 구현 클래스마다 테이블 전략은 설명은 배제했습니다. 왜냐하면 UNION 쿼리로 여러 아이템 조회시 성능이 매우 안좋아지기 때문입니다.)
1. 조인전략
우선 각각의 상품들(Album,Movie,Book)은 Item객체의 id,이름,가격을 공통 속성으로 가진다. 그리고 각각의 고유 속성들이 있다. 우선 각각 고유속성들을 생성하자.
Album
@Entity
public class Album extends Item{
private String artist;
//getter,setter
}
Book
@Entity
public class Book extends Item{
private String author;
private String isbn;
//getter,setter
}
Movie
@Entity
public class Movie extends Item{
private String director;
private String actor;
//getter,setter
}
위의 코드들의 각각의 객체들은 Item 클래스를 상속 받았다.
(Item 클래스는 상속을 줄 클래스이니 추상 클래스로 작성하는 것이 객체 지향 스러울 것이다.)
Item
@Entity
//★★★이 전략으로 상속 시키면 부모 테이블에서 조인전략으로 매핑 가능★★★
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn//★★Dtype(상속받은 객체의 타입)을 열에 추가됨. 어떤 애한테 받은건지 구분 가능
public abstract class Item {
@Id @GeneratedValue
private Long id;
private String name;
private int price;
//getter,setter
}
위와 같이 조인전략에는 부모 객체(엔티티)에 총 2가지 annotation을 추가하면 된다.
- @Inheritance(strategy= InheritanceType.전략): 상속 전략 선택
- @DiscriminatorColumn : DType-> 현재 어떤 자식 클래스의 데이터를 담고있는 지 알려준다.
우선 조인 전략으로 상속관계를 매핑 하면 제일 객체지향 스럽고도 상속관계가 확연히 눈에 보인다.
main함수
에 Movie
를 세팅
해보자.
Movie movie=new Movie();
movie.setDirector("봉준호");
movie.setActor("지창욱");
movie.setName("바람과 함께 사라지다");
movie.setPrice(10000);
em.persist(movie);
System.out.println();
System.out.println("================================");
Item findMoiveName= em.find(Item.class,movie.getId());
System.out.println("Movie의 이름 조회 = " + findMoiveName.getName());
System.out.println("================================");
결과
2. 단일 테이블 전략
단일 테이블 전략은 SINGLE_TABLE 이라고 명시하면 된다. 다음과 같이 Item annotation을 수정하자.
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
main함수
는 그대로 두고 실행해보자 .
장점으로는 성능이 제일 좋다. 한 테이블안에 속성들을 집어 넣어 조회시 조인같은게 필요없어 쿼리가 제일 덜 나가게 된다. (insert 문이 하나이다)
테이블이 커지는 단점과 자식 엔티티가 매핑하는 컬럼이 null을 허용한다. 위의 조인 전략은 setting한 값만 컬럼에 추가되어 좀 더 오류가 덜 나오게 할 수 있다.
이렇게 트레이드 오프 형식이므로 둘 중에 케이스를 맞춰 하나를 사용하면 된다. 전략 선택시에도 그냥 전략 타입만 바꾸면 되니까 여러개 해보고, 어떤게 제일 좋은지 선택하는게 제일 좋을 것 같다.
2. @MappedSuperClass
다음으로는 공통 속성이 존재 할때이다.
테이블과 관계 없고, 단순히 엔티티가 공통으로 사용하는 매핑 정보를 모으는 역할.
즉, 한 테이블에서 공통속성을 바꾼다고 해서 다른 테이블에 영향을 미치지 않고, 독립적임.
주로 등록일, 수정일, 등록자, 수정자 같은 전체 엔티티에서 공통 으로 적용하는 정보를 모을 때 사용.
예를 들어, BaseEntity
클래스에 createdAt
과 updatedAt
필드
가 있고, 이 클래스를 상속받는 EntityA
와 EntityB
두 엔티티가 있다고 해보자.
이 경우, EntityA
의 인스턴스가 생성되면 해당 인스턴스의 createdAt
과 updatedAt
필드는 그 인스턴스가 생성된 시점의 날짜와 시간으로 설정될 거다.
마찬가지로, EntityB
의 인스턴스가 생성되면, 그 인스턴스의 createdAt
과 updatedAt
필드도 해당 인스턴스가 생성된 시점의 날짜와 시간으로 독립적으로 설정된다.
그러면 이것을 왜 사용하냐면 각 엔티티 클래스마다 이 필드들을 반복해서 정의할 필요가 없게 되고, 공통 필드의 매핑 정보를 한 곳에서 관리할 수 있게 되어 유지보수성이 향상되기 때문이다.
참고: @Entity
클래스는 엔티티나 @MappedSuperclass
로 지 정한 클래스만 상속 가능.
BaseEntity
생성 및 공통속성 정의
@MappedSuperclass
public abstract class BaseEntity
{
@Column(name="INSERT_MEMBER")
private String createdBy;
private LocalDateTime createdDate;
private String lastModifiedBy;
private LocalDateTime lastModifiedDate;
//getter,setter
}
Member
@Entity
public class Member extends BaseEntity
Team
@Entity
public class Team extends BaseEntity
main
함수에서 member가 상속 받은 속성으로 값 setting
Member member = new Member();
member.setName("user1");
member.setCreatedBy("Cho");
member.setCreatedDate(LocalDateTime.now());
em.persist(member);
결과