-
[Spring] 고급 매핑(상속관계매핑/@MappedSuperclass/식별자클래스)Backend/Spring 2022. 8. 25. 15:56728x90
연관 관계 매핑
연관 관계 매핑은 단일 매핑의 데이터 중심 설계에서 벗어나 객체간의 참조를 설정하는 매핑 방식이다. 연관관계 매핑을 바탕으로 고급 매핑을 적용할 수 있다.
https://miraekwak.tistory.com/125
고급 매핑
RDB의 테이블과 매핑된 객체(Entity)를 객체답게 사용할 수 있도록 JPA에서 제공하는 매핑 전략이다.
- 테이블 상속 관계 매핑
- 상속을 통해서 자주 사용되는 필드를 사용하는 방법
- 단일 키가 아닌 복합키의 경우 식별자 설정 방법
상속 관계 매핑
- 다음과 같은 상속 구조에서 @Inheritance를 사용해서 매핑할 수 있다.
- InheritanceType의 JOINED와 SINGLE을 지정할 수 있다.
조인 테이블 전략
@Entity @Table(name = "item") @Inheritance(strategy = InheritanceType.JOINED) public abstract class Item { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private int price; private int stockQuantity; @ManyToOne @JoinColumn(name = "order_item_id", referencedColumnName = "id") private OrderItem orderItem; public void setOrderItem(OrderItem orderItem) { if (Objects.nonNull(this.orderItem)) { this.orderItem.getItems().remove(this); } this.orderItem = orderItem; orderItem.getItems().add(this); } } @Entity public class Food extends Item { private String chef; } @Entity public class Car extends Item { private long power; } @Entity public class Furniture extends Item { private long width; private long height; }
- 부모 클래스는 abstract class로 변경하고 Inheritance 애노테이션을 설정한다.
create table Car (power bigint not null, id bigint not null, primary key (id)) create table Food (chef varchar(255), id bigint not null, primary key (id)) create table Furniture (height bigint not null, width bigint not null, id bigint not null, primary key (id)) create table item (DTYPE varchar(31) not null, id bigint not null, price integer not null, stockQuantity integer not null, order_item_id bigint, primary key (id)) alter table Car add constraint FKge7by0el7y9941l8ssptf0hu9 foreign key (id) references item alter table Food add constraint FK9y4qp56rc3069yucfklyi6p64 foreign key (id) references item alter table Furniture add constraint FKfva03eyd5mv4v7uyj6ekjda81 foreign key (id) references item
싱글 테이블 전략
- @DiscreminatorColumn을 부모 클래스에 적용하면 DTYPE이라는 컬럼이 추가되면서 이 컬럼을 통해 각 자식 엔티티들이 어디에 매핑되는지 찾는다.
- @DiscreminatorValue를 자식 클래스에 적용한다.
@Entity @Table(name = "item") @Inheritance(strategy = InheritanceType.SINGLE_TABLE) @DiscriminatorColumn(name = "DTYPE") public abstract class Item { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private int price; private int stockQuantity; @ManyToOne @JoinColumn(name = "order_item_id", referencedColumnName = "id") private OrderItem orderItem; public void setOrderItem(OrderItem orderItem) { if (Objects.nonNull(this.orderItem)) { this.orderItem.getItems().remove(this); } this.orderItem = orderItem; orderItem.getItems().add(this); } } @Entity @DiscriminatorValue("FOOD") public class Food extends Item{ private String chef; } @Entity @DiscriminatorValue("CAR") public class Car extends Item{ private long power; } @Entity @DiscriminatorValue("FURNITURE") public class Furniture extends Item{ private long width; private long height; }
- DTYPE이라는 이름으로 ITEM테이블에 컬럼이 생성된다.
- DTYPE에는 각 item마다 정했던 value값을 가진다.
create table item (DTYPE varchar(31) not null, id bigint not null, price integer not null, stockQuantity integer not null, power bigint, chef varchar(255), height bigint, width bigint, order_item_id bigint, primary key (id))
@MappedSuperclass
- 실제 엔티티가 되는 클래스는 아니지만 상속을 받게되면 @MappedSuperclass의 컬럼값들을 자식들에게 추가로 테이블에 매핑이 된다.
- 공통된 필드들에 대해 공통 엔티티로 추출해서 사용한다.
@MappedSuperclass public class BaseEntity { @Column(name = "created_by") private String createdBy; @Column(name = "created_at", columnDefinition = "TIMESTAMP") private LocalDateTime cratedAt; } @Entity @Table(name = "orders") public class Order extends BaseEntity { @Id @Column(name = "id") private String uuid; @Column(name = "order_datetime", columnDefinition = "TIMESTAMP") private LocalDateTime orderDatetime; @Enumerated(EnumType.STRING) private OrderStatus orderStatus; @Lob private String memo; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "member_id", referencedColumnName = "id") private Member member; @OneToMany(mappedBy = "order") private List<OrderItem> orderItems; public void setMember(Member member) { if(Objects.nonNull(this.member)) { this.member.getOrders().remove(this); } this.member = member; member.getOrders().add(this); } public void addOrderItem(OrderItem orderItem) { orderItem.setOrder(this); } }
create table orders (id varchar(255) not null, created_at TIMESTAMP, created_by varchar(255), memo clob, order_datetime TIMESTAMP, orderStatus varchar(255), member_id bigint, primary key (id))
식별자 클래스
JPA에서 식별자를 둘 이상 사용하려면 별도의 식별자 클래스를 만들어야 한다.
- @IdClass -> 객체지향스럽지 않기 때문에 비추
- @EmbeddedId -> 객체지향스럽기 때문에 강추
만약 @Id를 통해 두 개 이상의 식별자를 정의하려고 한다면 문제가 생긴다. JPA는 영속성 컨텍스트에 엔티티를 보관할 때, equals & hashCode를 이용해서 동등성 비교를 하기 때문에 식별자가 두개일 경우 어떤 식별자로 해야할 지 알 수 없다.
public class Member { @Id private String id1; @Id private String id2; // runtime error }
@IdClass
@Getter @Setter @Entity @IdClass(ParentId.class) public class Parent { @Id private String id1; @Id private String id2; } @EqualsAndHashCode @NoArgsConstructor @AllArgsConstructor public class ParentId implements Serializable { private String id1; private String id2; }
- @IdClass의 경우 사용할 식별자가 정의된 클래스를 인자로 받는다.
- ParentId에서 정의한 식별자명을 똑같이 IdClass에서 사용해야 한다.
Parent parent = new Parent(); parent.setId1("id1"); parent.setId2("id2"); Parent parent1 = entityManager.find(Parent.class, new ParentId("id1", "id2")); log.info("{} {}", parent1.getId1(), parent1.getId2());
- 조회 시에는 ParentId 인스턴스를 통해 가능하다.
사용시 주의점!
- Serializable 인터페이스를 구현해야 한다.
- equals & hashCode를 구현해야 한다. @EqualsAndHashCode
- 기본 생성자가 있어야 한다. @NoArgsConstructor @AllArgsConstructor
- 식별자 클래스는 public이어야 한다.
@EmbeddedId
식별자 객체 자체를 명시하여 id를 1개만 사용한다.
@Getter @Setter @Entity public class Parent2 { @EmbeddedId private ParentId2 id; } @Getter @Setter @EqualsAndHashCode @NoArgsConstructor @AllArgsConstructor @Embeddable public class ParentId2 implements Serializable { private String id1; private String id2; }
- @Embeddable을 사용하여 식별자들을 정의
- @EmbeddedId를 통해 식별자 객체 정의
Parent parent = new Parent(); parent.setId(new ParentId("id1", "id2")); Parent parent1 = entityManager.find(Parent.class, new ParentId("id1", "id2")); log.info("{} {}", parent1.getId().getId1(), parent1.getId().getId2());
사용시 주의점!
- Serializable 인터페이스를 구현해야 한다.
- equals & hashCode를 구현해야 한다. @EqualsAndHashCode
- 기본 생성자가 있어야 한다. @NoArgsConstructor @AllArgsConstructor
- 식별자 클래스는 public이어야 한다.
- @Embeddable 애노테이션이 있어야 한다.
728x90'Backend > Spring' 카테고리의 다른 글
[Spring] SpringDataJPA (0) 2022.08.25 [Spring] 프록시와 연관관계 (0) 2022.08.25 [Spring] 연관 관계 매핑 (0) 2022.08.25 [Spring] 단일 엔티티 매핑 (0) 2022.08.24 [Spring] 영속성 컨텍스트 (0) 2022.08.22