Spring Boot

[JPA] @MappedSuperclass vs Embedded Type (@Embeddable + @Embedded)

기매_ 2022. 12. 18. 20:19
package com.fastcampus.projectboard.domain;

@Getter
@ToString
@EntityListeners(AuditingEntityListener.class)
@MappedSuperclass
public class AuditingFields {

    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
    @CreatedDate
    @Column(nullable = false, updatable = false)
    private LocalDateTime createdAt; // 생성일시

    @CreatedBy
    @Column(nullable = false, updatable = false, length = 100)
    private String createdBy; // 생성자

    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
    @LastModifiedDate
    @Column(nullable = false)
    private LocalDateTime modifiedAt; // 수정일시

    @LastModifiedBy
    @Column(nullable = false, length = 100)
    private String modifiedBy; // 수정자

}
@Getter
@ToString
@Table(indexes = {
        @Index(columnList = "title"),
        @Index(columnList = "hashtag"),
        @Index(columnList = "createdAt"),
        @Index(columnList = "createdBy")
})
@Entity
public class Article extends AuditingFields {
// 생략
@Getter
@ToString
@Table(indexes = {
        @Index(columnList = "content"),
        @Index(columnList = "createdAt"),
        @Index(columnList = "createdBy")
})
@Entity
public class ArticleComment extends AuditingFields {

@MappedSuperclass

  • 엔티티가 아니고, 테이블과 직접적인 매핑되지 않는다.
  • 부모 클래스를 상속 받는 자식 클래스에 매핑 정보만 제공한다.
  • 직접 생성해서 사용할 일이 없으므로 추상 클래스로 사용하는 것이 좋다.
  • 테이블과 관계 없고 단순히 엔티티가 공통으로 사용하는 매핑 정보를 모으는 역할이다.
  • 주로 등록일, 수정일, 등록한 사람, 수정한 사람 같은 전체 엔티티에서 공통으로 적용하는 정보를 모을 때 사용한다.

=> @MappedSuperclass로 정의하면 Entity로 정의하는 것이고, 여러 Entity에 공통적으로 적용해야 할 때 사용함 !

테이블과 관계 없이 단순히 엔티티가 공통으로 사용하는 매핑 정보를 모아주는 역할을 담당.

 

참고로 @Entity 클래스엔티티나 @MappedSuperclass로 지정한 클래스만 상속이 가능하다.

 

@Getter
@Setter
@MappedSuperclass
public abstract class Timestamped {

    private LocalDateTime createdDate;  // 생성일
    private LocalDateTime modifiedDate; // 수정일
}
@Entity
public class User extends Timestamped {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "USER_ID")
    private Long id;
    
    @Column(nullable = false)
    private String username;
	...
}

Embedded Type (@Embeddable + @Embedded)

  • 새로운 값 타입을 직접 정의해서 사용할 수 있다.
  • JPA에서는 임베디드 타입이라고 한다.
  • 보다 더 객체지향적인 코드 작성을 위해 사용
  • 주로 기본 값 타입을 모아서 만들기 때문에 복합 값 타입이라고도 한다.
  • 임베디드 타입은 엔티티가 아니다. 임베디드 타입은 단순히 값들을 하나로 묶어 놓은 것이다.

엔티티를 구현하다보면 공통적인 부분을 따로 클래스로 만들어 사용하고 싶은 경우가 있다.

분리한 클래스의 멤버변수를 테이블에 매핑하고 싶을 때 @Embeddable+@Embedded을 사용한다.

  • @Embeddable : 값 타입을 정의하는 곳에 표시한다.
  • @Embedded : 값 타입을 사용하는 곳에 표시한다.
  • 기본 생성자 필수

=> @Embeddable으로 정의하면 Value 타입으로 정의하는 것이고,

      비슷한 속성을 가지는 애트리뷰트를 하나의 값으로 만들어 재사용성을 높일 수 있는 방법임

 

@Getter
@Setter
@Embeddable
public class Timestamped {

    private LocalDateTime createdDate;  // 생성일
    private LocalDateTime modifiedDate; // 수정일
}
@Entity
public class User { // extends 없음

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "USER_ID")
    private Long id;
    
    @Column(nullable = false)
    private String username;
    
    @Embedded
    private Timestamped timestamped;
	...
}

결론

결국 상속(@MappedSuperclass)을 사용하는 것과 위임(Embedded Type)을 사용하는 것의 차이다.

 

상속(@MappedSuperclass) :

  • 부모 타입과 자식 타입이 강한 결합성을 가짐으로써 캡슐화가 깨지고, 속한 객체들이 변경의 여파를 받게 된다.
  • 한 객체에서 정의된 상태나 행위에 대해서 관리하거나 확인해야 할 포인트(class)가 늘어난다.
  • 자식 타입에서 메서드를 재정의하는 경우에 잘못된 이해로 특정 로직만 추가하고 부모 타입의 메서드를 호출하고 있을 때 오동작할 수 있다.
    부모 타입의 메서드가 변경되어서 자식 타입의 메서드가 깨지거나 오동작할 수 있다.
  • 다중상속이 안되며, 객체지향 설계상 유연성이 떨어진다.

등의 문제점이 있다.

 

 

따라서 부모 타입의 기능 혹은 다른 객체의 기능을 사용할 것이라면 이를 상속받는 것이 아니라 상태 값, 즉 인스턴스 변수를 통해 기능을 사용하는 것을 추천함.

객체지향의 일반적인 법칙을 따르면 상속보다는 위임(조합)이 더 좋다 !

 

하지만 등록일, 수정일, 등록한 사람, 수정한 사람과 같이 운영상의 이유를 포함하는 컬럼을 공통으로 사용할 때는 상속을 사용하는게 더욱 편리하다. (대부분의 엔티티들이 공통으로 사용하는 속성을 다룰 때)

예를 들어, 이런 경우에서 JPQL (테이블이 아닌 엔티티 객체를 대상으로 검색하는 객체지향 쿼리)을 사용하면,

항상 아래 코드의 timestamped와 같이 임베디드 타입을 적어야 한다.

select u from User u where u.timestamped.createdDate > ?

 

 

상속을 사용하면 다음과 같이 간단하고 쉽게 바뀐다.

select u from User u where u.createdDate > ?

결국 두 가지 중 선택이지만, 편리함과 직관성 때문에, 이 경우에는 상속(@MappedSuperclass)을 사용한다고 한다.


참고 :

1. https://velog.io/@rudwnd33/JPA-MappedSuperclass-vs-Embedded-Type

2. https://blog.naver.com/PostView.nhn?blogId=qjawnswkd&logNo=222074957987 

3. https://www.inflearn.com/questions/18578/%EC%9E%84%EB%B2%A0%EB%94%94%EB%93%9C-%ED%83%80%EC%9E%85%EA%B3%BC-mappedsuperclass-%EC%B0%A8%EC%9D%B4

4. https://hyeonic.github.io/jpa/basic/mapped-superclass-vs-embedded.html

5. https://lob-dev.tistory.com/entry/Base-Column%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%A0-%EB%95%8C-MappedSuperclass-%EC%99%80-embedded-Type-%EC%A4%91-%EB%AC%B4%EC%97%87%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%B4%EC%95%BC-%ED%95%A0%EA%B9%8C