엔티티 설계를 하다보면 공통된 컬럼을 가진 엔티티들을 만들어야 할 때가 종종 있다.
똑같은 내용을 두 번 세 번 작업하지 않도록 공통된 부분을 상속받아서 땡겨 쓸 수 있도록 만들어보자.
JPA에는 @MappedSuperclass라는 어노테이션이 존재한다.
공식 주석에는 아래와 같이 설명하고 있다.
즉, 실체는 없으면서 공통으로 상속받아서 쓸 수 있는 테이블 포맷을 만들어둘 수 있다는 의미로 해석된다.
예를 들면, 현재 프로젝트에서 Q&A, 공지사항 등 게시판의 여러 형태가 나올 수 있는데 먼저 공통된 부분을 아래와 같이 상위 클래스로 구현해준다.
@Getter
@Setter
@ToString
@SuperBuilder
@MappedSuperclass
public class BaseBBSEntity {
public static final int NOTICE_TITLE = 200;
@ManyToOne
@JoinColumn(name = "user")
private User user;
@Column(name = "title", nullable = false, length = NOTICE_TITLE)
private String title;
@Column(name = "contents", nullable = false, columnDefinition = "TEXT")
private String contents;
@Column(name = "registered_date")
private LocalDateTime registeredDate;
@Column(name = "fixed")
@ColumnDefault("false")
private Boolean fixed;
@Column(name = "hits")
@ColumnDefault("0")
private Long hits;
}
그러면 공통된 컬럼을 가진 엔티티를 위 클래스를 상속받아 하위 클래스로 구현하면 된다.
@Getter
@Setter
@ToString
@SuperBuilder
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Table(name = "nt_qna")
public class Qna extends BaseBBSEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "qna_id")
private Long rentQnaId;
@Column(name = "qna_password")
private String rentQnaPassword;
@OneToMany(mappedBy = "qna")
private List<QnaFile> qnaFiles;
}
이제는 공통된 컬럼 뿐만 아니라 공통된 값, 공통된 동작을 수행하고 싶을 때 구현하는 방법을 알아보자.
@EntityListeners 는 엔티티에 속성값으로 받은 콜백 리스너를 추가할 수 있는 어노테이션이라고 설명되어 있다. 간단하게 설명하면 콜백 리스너란 어떤 상태가 되거나 특정 동작이 실행되면 호출되는 함수라고 할 수 있다. 이는 엔티티 클래스나 @MappedSuperclass에 사용될 수 있다고 한다.
예를 들어 보통의 데이터를 관리할 때 저장된 시간, 수정된 시간을 활용하는 경우가 많다. 그래서 대중적으로 사용되는 코드가 아래의 BaseTimeEntity이다.
@Getter
@Setter
@ToString
@SuperBuilder
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public class BaseTimeEntity {
@CreatedDate
@Column(name = "created_date")
private LocalDateTime createdDate;
@LastModifiedDate
@Column(name = "modified_date")
private LocalDateTime modifiedDate;
}
여기서 사용된 AuditingEntityListener.class에 대해 설명하자면
Audit은 감시라는 의미로 엔티티의 특정 동작을 감시하겠다는 의미로 해석할 수 있다. 이 클래스의 경우 persisting과 updating을 감시하겠다는 클래스. 위 코드의 경우 Spring Data의 @CreatedDate, @LastModifiedDate를 이용해 해당 내용을 구현하고 있다. 따라서 엔티티가 저장될 때 생성 일자를, 수정될 때 마지막 수정 일자를 저장하게 된다.
상위 클래스의 필드를 재정의해야 할 필요가 있을 때 위의 두 컬럼을 사용하면 된다.
@AttrivuteOverride의 경우 컬럼의 이름을 재정의할 때 사용한다. 상위 클래스의 필드 이름을 name 속성에 넣고, 재정의할 컬럼의 이름을 column 속성에 넣는다.
@AttrivuteOverrides를 사용해 여러개의 컬럼을 재정의할 수 있다.
@Table(name = "nt_qna")
@AttributeOverride(name = "title", column = @Column(name = "qna_title"))
public class Qna extends BaseBBSEntity {
...
}
@AssociationOverride의 경우 연관관계 조인컬럼의 이름을 재정의할 때 사용한다. 상위 클래스의 필드 이름을 name 속성에 넣고, 재 정의할 컬럼의 이름을 column 속성에 넣는다.
@AssociationOverrides를 사용해 여러개의 컬럼을 재정의할 수 있다.
@Table(name = "nt_qna")
@AssociationOverride(name="user", joinColumns=@JoinColumn(name="writer_id"))
public class Qna extends BaseBBSEntity {
...
}