상속 관계 매핑은 부모 클래스와 자식 클래스 모두 데이터베이스 테이블과 매핑된다. 부모 클래스는 테이블과 매핑되지 않고, 자식 클래스에서 매핑 정보만 제공하고 싶으면 @MappedSuperclass
를 사용하면 된다. @MappedSuperclass
가 붙은 클래스는 실제 테이블과 매핑되지 않고, 자식 클래스에게 매핑 정보를 상속할 목적으로만 사용된다.
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
@Getter
public abstract class BaseEntity {
@CreatedDate
@Column(updatable = false)
private LocalDateTime createdDate;
@LastModifiedDate
private LocalDateTime updatedDate;
}
@MappedSuperclass
가 붙은 BaseEntity는 데이터베이스 테이블과 매핑되지 않고 자식 클래스 엔티티의 매핑 정보를 상속하기 위해 사용된다.
이 클래스를 인스턴스화 해서 사용할 일이 없으므로 추상 클래스로 만드는 것이 좋다.
Spring Data JPA는 Auditing이라는 기능을 제공한다. 이를 통해 엔티티가 생성되고 변경되는 시점을 감지하여 자동으로 생성시각, 수정시각 등을 기록할 수 있다.
Auditing 기능을 사용하기 위해 다음과 같이 Application 클래스에 @EnableJpaAuditing
어노테이션을 붙여 Auditing을 활성화해야 한다.
@SpringBootApplication
@EnableJpaAuditing
public class ShopApplication {
public static void main(String[] args) {
SpringApplication.run(ShopApplication.class, args);
}
}
Auditing을 적용할 클래스에 @EntityListeners(AuditingEntityListener.class)
어노테이션을 붙여 준다. 그러면 해당 어노테이션이 엔티티의 변화를 감지하여 값을 기록해준다.
생성일을 기록하기 위해 생성일을 나타내는 필드에 @CreateDate
을 붙여주고, 수정일을 기록하기 위해 수정일을 나타내는 필드에 @LastModifiedDate
을 붙여준다.
여러 필드 값을 묶어서 별도의 영역에 정의한다는 점에서 @MappedSuperclass
와 임베디드 타입의 용도가 비슷하다고 느껴진다. 그렇다면 둘의 차이는 무엇일까?
먼저 각각의 특징을 살펴보자.
extend
를 사용하여 클래스 간 상속관계가 생기지만, 데이터베이스 측면에서는 상속관계 매핑이 아니다.
엔티티가 아니며, 데이터베이스 테이블과 매핑되지 않는다. 단지 자식 클래스에 매핑 정보만 제공한다.
직접 생성해서 사용할 일이 없으므로 추상 클래스로 정의하는 것이 좋다.
테이블과 관계가 없고 단순히 여러 엔티티에서 공통으로 사용하는 매핑 정보를 모으는 역할이다. 따라서 주로 등록일, 수정일, 등록한 사람, 수정한 사람과 같은 전체 엔티티에서 공통으로 사용하는 필드를 모을 때 사용한다.
@MappedSuperclass
public abstract class BaseEntity {
private LocalDateTime createdDate;
private LocalDateTime updatedDate;
}
public class User extends BaseEntity {
@Id @GeneratedValue
private Long id;
private String username;
}
임베디드 타입은 새로운 값 타입이다.
주로 기본 값 타입들을 모아서 만들기 때문에 복합 값 타입이라고도 한다.
임베디드 타입은 엔티티가 아니며, 단순히 여러 값들을 하나로 묶어 놓은 것이다.
@Embeddable
public class Timestamped {
private LocalDateTime createdDate;
private LocalDateTime updatedDate;
}
public class User {
@Id @GeneratedValue
private Long id;
private String username;
@Embedded
private Timestamped timedtamped;
}
상속을 하냐, 조합(위임)을 하냐의 차이이다. 객체지향 관점에서는 상속보다는 조합을 사용하는 것이 좋다. 그 이유는 다음과 같다.
상속은 부모 클래스와 자식 클래스가 강한 결합성을 가져 캡슐화가 깨진다.
부모 클래스의 변경사항이 자식 클래스에 영향을 준다. 예를 들어 부모 클래스의 메서드가 변경되면, 자식 클래스에서 해당 메서드의 실행이 오작동할 수 있다.
그런데 임베디드 타입을 사용할 경우, JPQL 쿼리를 사용할 때 임베디드 타입을 적어주어야 한다.
select u from User u where u.timestamped.createdDate = ?
반면 상속을 사용할 경우, 비교적 더 간단하게 작성할 수 있다.
select u from User u where u.createdDate = ?
이번 경우, 임베디드 타입이 아닌 @MappedSuperclass
를 사용했다. 그 이유는 다음과 같다.
그리고 BaseEntity의 등록일, 수정일과 같은 변수는 향후 크게 변경될 거 같지 않고, 변경되더라도 하위 클래스에 미치는 영향이 크지 않을 것이라 생각한다.
또한 JPQL 사용시 @MappedSuperclass
가 임베디드 타입보다 편리하다.
Refrerence
https://hudi.blog/spring-data-jpa-auditing-create-update-date/
https://velog.io/@rudwnd33/JPA-MappedSuperclass-vs-Embedded-Type