- 참조: 자바 ORM 표준 JPA 프로그래밍
- 실전 예제
아래와 같은 요구사항이 추가되었다고 가정하자.
- 상품의 종류는 음반, 도서, 영화가 있고 이후 확장가능하다.
- 모든 데이터는 등록일과 수정일이 있다.
상품의 종류에 음반, 도서, 영화가 있다는것에 대한 요구사항을 UML 로 표현하면 아래와 같다.
Album, Book, Movie 객체는 Item 객체를 상속받는다. JPA 로 이러한 상속관계를 맵핑할 떄에는 @Inheritance 어노테이션을 사용한다. 실전이라면 맵핑 전략을 신중하게 고르겠지만 예제이기 때문에 간단하게 SINGLE_TABLE 전략을 이용해보자.
2 번째 요구사항인 모든 엔티티에 등록일과 수정일 컬럼을 추가할 때에는 컬럼을 모두 추가해주지 말고 맵핑 관계만 상속받는 @MappedSuperclass 를 이용하도록 한다.
- Java Example: @Inheritance
우선 Item 객체를 상속받는 Album, Book, Movie 를 @Inheritance 를 이용하여 슈퍼타입과 서브타입으로 맵핑시켜보자. Item 은 슈퍼타입이며, Album, Book, Movie 는 서브타입이 된다.
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "ITEM_TYPE")
public abstract class Item {
@Id
@GeneratedValue
@Column(name = "ITEM_ID")
private Long id;
private String name;
private int price;
private int stockQuantity;
@ManyToMany(mappedBy = "items")
private List<Category> categories = new ArrayList<>();
..............
@Entity
@DiscriminatorValue("ALBUM")
public class Album extends Item{
private String artist;
..............
@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;
슈퍼타입인 Item 에 @Inheritance 어노테이션을 붙여준다. @DiscriminatorColumn 의 값은 지정하지 않아도 상관없지만 ITEM_TYPE 으로 지정해보았다. 서브타입에 해당하는 엔티티에는 각각 @DiscriminatorValue 의 값을 지정해준다.
public static void save(EntityManager em) {
EntityTransaction tx = em.getTransaction();
tx.begin();
Album album = new Album();
album.setArtist("Artist#1");
album.setName("AlbumName#1");
album.setPrice(10000);
album.setStockQuantity(10000);
em.persist(album);
Book book = new Book();
book.setAuthor("BookAuthor#1");
book.setIsbn("BookIsbn#1");
book.setName("BookName#1");
book.setPrice(20000);
book.setStockQuantity(20000);
em.persist(book);
Movie movie = new Movie();
movie.setActor("MovieActor#1");
movie.setDirector("MovieDirector#1");
movie.setName("MovieName#1");
movie.setPrice(30000);
movie.setStockQuantity(30000);
em.persist(movie);
tx.commit();
em.close();
}
저장하는 테스트 코드에서 엔티티를 하나씩 저장해보았다. find 메소드를 작성하여 album 을 조회해보면 아래와 같은 SQL 을 수행한다. Album 엔티티에 지정된 @DiscriminatorValue 값을 where 절에서 구분자 컬럼 조건으로 활용하는것을 알 수 있다.
Hibernate:
select
album0_.item_id as item_id2_3_0_,
album0_.name as name3_3_0_,
album0_.price as price4_3_0_,
album0_.stock_quantity as stock_qu5_3_0_,
album0_.artist as artist6_3_0_
from
item album0_
where
album0_.item_id=?
and album0_.item_type='ALBUM'
데이터는 아래와 같이 조회된다.
Single 테이블 전략이라서 ITEM 단일 테이블에 모두 저장된다. ITEM_TYPE 에는 @DiscriminatorValue 로 지정한 값이 들어있다. 또한 각 엔티티에서 사용하지 않는 나머지 컬럼에는 null 이 할당되어있다.
책에는 Single 테이블 전략만 사용했지만 Joined 전략으로 하면 어떤 형태로 저장되는지도 살펴보자.
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
//@DiscriminatorColumn(name = "ITEM_TYPE")
public abstract class Item {
@Id
@GeneratedValue
@Column(name = "ITEM_ID")
private Long id;
InheritanceType 을 변경해주고, @DiscriminatorColumn 을 주석처리 한다. 또 서브타입 테이블과 맵핑되는 엔티티들의 @DiscriminatorValue 어노테이션도 주석처리를 해준다.
위와 같이 작성하고 console 에 출력된 sql 을 살펴보자. album 과 item 테이블을 inner join 하여 조회한다.
Hibernate:
select
album0_.item_id as item_id1_5_0_,
album0_1_.name as name2_5_0_,
album0_1_.price as price3_5_0_,
album0_1_.stock_quantity as stock_qu4_5_0_,
album0_.artist as artist1_0_0_
from
album album0_
inner join
item album0_1_
on album0_.item_id=album0_1_.item_id
where
album0_.item_id=?
- Java Example: @MappedSuperclass
모든 테이블에 등록일과 수정일을 추가해야한다고 할 때, 가장 단순한 방법은 각 엔티티마다 2 개의 컬럼을 모두 추가하는것이다. 하지만 JPA 에서는 고맙게도 @MappedSuperclass 를 제공한다. Mybatis 에서도 interface 로 선언해놓고 해당 인터페이스를 구현한 클래스를 Result 로 맵핑하여 비슷하게 구현가능하다.
우선 각 엔티티에 등록일과 수정일을 추가해줄 엔티티를 만들어보자.
@MappedSuperclass
public class DateMarkable {
private Date insertDatetime;
private Date updateDatetime;
public Date getInsertDatetime() {
return insertDatetime;
}
public void setInsertDatetime(Date insertDatetime) {
this.insertDatetime = insertDatetime;
}
public Date getUpdateDatetime() {
return updateDatetime;
}
public void setUpdateDatetime(Date updateDatetime) {
this.updateDatetime = updateDatetime;
}
}
위와 같이 @MappedSuperclass 어노테이션을 붙여주고 등록일과 수정일을 추가한다. 그리고 각 엔티티에서 위의 클래스를 extends 한다.
@Entity
@Table(name = "ORDERS")
public class Order extends DateMarkable{
..........
@Entity
public class Member extends DateMarkable{
모든 엔티티에 열심히 extends 를 하고 있는데 난관에 봉착한다. 앞에서 Item 을 슈퍼타입 그리고 Album, Book, Movie 를 서브타입으로 하여 SingleTable 전략으로 상속관계를 맵핑했었다. 그런데 Album 에 extends 를 해주려고 보니 Java 에서는 다중 상속을 허용하지 않는다. 어떻게 해야할까?
Item 을 상속하고 있으므로 Item 이 MappedSuperclass 를 상속받으면 된다.
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "ITEM_TYPE")
public abstract class Item extends DateMarkable {
한번 등록일을 set 하여 저장해보자. save 테스트코드에서 각 엔티티에 insertDatetime 을 set 하여 저장하자.
album.setInsertDatetime(new Date());
위처럼 컬럼이 추가되었고, INSERT_DATETIME 에도 값이 들어간다.
MappedSuperclass 는 Inheritance 와 기술적 구현상으로는 비슷할 수 있으나 의미가 전혀 다르다. Inheritance 를 사용하는 상속관계 맵핑은 엔티티이지만 MappedSuperclass 는 엔티티가 아니라 단순하게 맵핑관계만 정의할뿐이다. 이 두 가지를 잘 사용하여 적절하게 사용하자.
'Framework and Tool > JPA' 카테고리의 다른 글
JPA - 즉시 로딩과 지연 로딩 (0) | 2021.07.15 |
---|---|
JPA - 프록시 (0) | 2021.07.14 |
JPA - 고급맵핑 - Multi 테이블 맵핑 (0) | 2021.07.12 |
JPA - 고급맵핑 - 조인 테이블 (0) | 2021.07.12 |
JPA - 고급맵핑 - 복합키와 식별 관계 맵핑 (2) | 2021.07.11 |
댓글