[JPA] @MappedSuperclass로 중복 컬럼 상속화

2019. 12. 30. 15:16Spring/Basic

300x250
반응형

@MappedSuperclass로 중복 컬럼 상속화

  1. Entity별 공통 요소 상속의 필요성
  2. @MappedSuperclass를 이용하여 공통요소를 Super Class에 정의
  3. 매우 간단해진 기존 Entity
  4. 공통 컬럼명을 override 하고싶을 경우엔 @AttributeOverride로 재정의

1. Entity별 공통 요소 상속의 필요성

@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter @Setter
@Entity
@Table(name = "user")
@DynamicUpdate
@DynamicInsert
public class User implements Serializable {

	private static final long serialVersionUID = -563329217866858622L;

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	@Column(updatable = false, nullable = false, columnDefinition = "INT(11)")
	private Long id;

	@Column(nullable = false, length = 1, columnDefinition = "CHAR(1) DEFAULT '0'")
	private String type;

	@Column(nullable = false, unique = true, length = 100)
	private String email;

	@Column(nullable = false, length = 50)
	private String name;

	@Column(nullable = false, length = 1, columnDefinition = "CHAR(1) DEFAULT '1'")
	private String sex;

	@Column(nullable = false, length = 6)
	private String birthDate;

	@Column(nullable = false, length = 20)
	private String phoneNumber;

	@Column(nullable = false, length = 150)
	private String password;

	@Column(nullable = false, columnDefinition = "TINYINT(1) DEFAULT 0", length = 1)
	private boolean del;

	@Temporal(TemporalType.TIMESTAMP)
	@Column(updatable = false, columnDefinition = "TIMESTAMP DEFAULT CURRENT_TIMESTAMP")
	private Date createTimestamp;

	@Temporal(TemporalType.TIMESTAMP)
	@Column(nullable = true, columnDefinition = "TIMESTAMP DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP")
	protected Date updateTimestamp;

	@Builder
	public User(String type, String name, String email, String sex, String birthDate, String phoneNumber, String password) {
		this.type = type;
		this.name = name;
		this.email = email;
		this.sex = sex;
		this.birthDate = birthDate;
		this.phoneNumber = phoneNumber;
		this.password = password;
	}

	@PrePersist
	protected void onCreate() {
		createTimestamp = Timestamp.valueOf(LocalDateTime.now());
	}

	@PreUpdate
	protected void onUpdate() {
		updateTimestamp = Timestamp.valueOf(LocalDateTime.now());
	}

}
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter @Setter
@Entity(name = "store")
@DynamicUpdate
public class Store implements Serializable{

	private static final long serialVersionUID = 3321044622977739271L;

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	@Column(updatable = false, nullable = false, columnDefinition = "INT(11)")
	private Long id;

	@JsonBackReference
	@ManyToOne(fetch = FetchType.LAZY)
	@JoinColumn(name = "user_id", foreignKey = @ForeignKey(name = "FK_STORE_USER"))
	private User user;

	@Column(nullable=false, length = 100)
	private String name;

	@Column(length = 30)
	private String storeBusiness;

	@Column(nullable = false, columnDefinition = "TINYINT(1) DEFAULT 0", length = 1)
	private boolean del;

	@Temporal(TemporalType.TIMESTAMP)
	@Column(updatable = false, columnDefinition = "TIMESTAMP DEFAULT CURRENT_TIMESTAMP")
	private Date createTimestamp;

	@Temporal(TemporalType.TIMESTAMP)
	@Column(nullable = true, columnDefinition = "TIMESTAMP DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP")
	protected Date updateTimestamp;

	@Builder
	private Store(User user, String name, String storeBusiness) {
		this.user = user;
		this.name = name;
		this.storeBusiness = storeBusiness;
	}

	@PrePersist
	protected void onCreate() {
		createTimestamp = Timestamp.valueOf(LocalDateTime.now());
	}

	@PreUpdate
	protected void onUpdate() {
		updateTimestamp = Timestamp.valueOf(LocalDateTime.now());
	}
}

demo 프로젝트의 엔티티인 User와 Store에 중복적으로 이용되는 id, del, create_timestamp, update_timestamp 컬럼이 있다.

이 컬럼들은 User, Store 뿐만 아니라 이 후에 추가될 엔티티들에게도 기본적으로 필요한 컬럼들입니다.
이런 컬럼들은 따로 SuperClass로 정의하여 하위 엔티티들이 상속을 받는다면 중복 코드를 줄여 한눈에 보기에도 편할 뿐 아니라, 개발 중 실수로 누락하여 에러를 발생하는 상황을 방지할 수 있어 좋습니다.

저장 또는 수정 전에 자동으로 발생되는 create_timestamp 설정 및 update_timestamp 설정에 관한 기본 메서드 역시 Super Class에 정의하면 좋을 메서드들입니다.


2. @MappedSuperclass를 이용하여 공통요소를 Super Class에 정의

@Getter @Setter
@MappedSuperclass
public abstract class BaseEntity implements Serializable {

	private static final long serialVersionUID = 1146360965411496820L;

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	@Column(updatable = false, nullable = false, columnDefinition = "INT(11)")
	private Long id;

	@Temporal(TemporalType.TIMESTAMP)
	@Column(updatable = false, columnDefinition = "TIMESTAMP DEFAULT CURRENT_TIMESTAMP")
	private Date createTimestamp;

	@Temporal(TemporalType.TIMESTAMP)
	@Column(nullable = true, columnDefinition = "TIMESTAMP DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP")
	protected Date updateTimestamp;

	@Column(nullable = false, columnDefinition = "TINYINT(1) DEFAULT 0", length = 1)
	private boolean del;

	@PrePersist
	protected void onCreate() {
		createTimestamp = Timestamp.valueOf(LocalDateTime.now());
	}

	@PreUpdate
	protected void onUpdate() {
		updateTimestamp = Timestamp.valueOf(LocalDateTime.now());
	}
}

기본적으로 Entity에서 공통적으로 이용될 id, create_timestamp, update_timestamp, del 컬럼과 onCreate, onUpdate 함수를 BaseEntity에 작성했습니다.

이때, BaseEntity는 상속될 용도로만 이용될 뿐이며 단독적으로 생성자 생성 될 상황은 없기 때문에 추상 클래스로 정의했습니다.

javax.persistence.MappedSuperclass 애너테이션을 추상클래스 위에 붙이면 됩니다.


3. 매우 간단해진 기존 Entity

@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter @Setter
@Entity
@Table(name = "user")
@DynamicUpdate @DynamicInsert
public class User extends BaseEntity implements Serializable {

	private static final long serialVersionUID = -563329217866858622L;

	@Column(nullable = false, length = 1, columnDefinition = "CHAR(1) DEFAULT '0'")
	private String type;

	@Column(nullable = false, unique = true, length = 100)
	private String email;

	@Column(nullable = false, length = 50)
	private String name;

	@Column(nullable = false, length = 1, columnDefinition = "CHAR(1) DEFAULT '1'")
	private String sex;

	@Column(nullable = false, length = 6)
	private String birthDate;

	@Column(nullable = false, length = 20)
	private String phoneNumber;

	@Column(nullable = false, length = 150)
	private String password;

	@Builder
	public User(String type, String name, String email, String sex, String birthDate, String phoneNumber, String password) {
		this.type = type;
		this.name = name;
		this.email = email;
		this.sex = sex;
		this.birthDate = birthDate;
		this.phoneNumber = phoneNumber;
		this.password = password;
	}

}
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter @Setter
@Entity(name = "store")
@DynamicUpdate
public class Store extends BaseEntity implements Serializable {

	private static final long serialVersionUID = 3321044622977739271L;

	@JsonBackReference
	@ManyToOne(fetch = FetchType.LAZY)
	@JoinColumn(name = "user_id", foreignKey = @ForeignKey(name = "FK_STORE_USER"))
	private User user;

	@Column(nullable=false, length = 100)
	private String name;

	@Column(length = 30)
	private String storeBusiness;

	@Builder
	private Store(User user, String name, String storeBusiness) {
		this.user = user;
		this.name = name;
		this.storeBusiness = storeBusiness;
	}
}

BaseEntity에 작성한 코드를 제거한 후 BaseEntity를 상속받으면 끝!


3. 공통 컬럼명을 override 하고싶을 경우엔 @AttributeOverride로 재정의

PK로 이용하는 id 컬럼을 id가 아닌 다른 이름으로 사용할 경우엔 어떻게 해야할까
(예를 들면 Store 엔티티에서 PK를 store_id로 이용하는 경우)

이 경우에는, del, create_timestamp, update_timestamp를 공통적으로 이용하고 있음에도 BaseEntity를 상속할 수 없는걸까?

이 경우에는 @AttributeOverride 로 매칭할 컬럼명을 override 할 수 있습니다.


@AttributeOverride(name = "id", column = @Column(name = "store_id"))
@Entity(name = "store")
@DynamicUpdate
public class Store extends BaseEntity implements Serializable {
}

이렇게 store_id 컬럼을 @Column Long id에 매핑할 수 있으며


@AttributeOverride(name = "updateTimestamp", column = @Column(name = "reply_timestamp", columnDefinition = "TIMESTAMP DEFAULT NULL"))
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter @Setter
@ApiModel(description = "문의")
@Entity(name = "contact")
@DynamicUpdate
public class Contact extends BaseEntity implements Serializable {
	...
}

updateTimestamp에 contact 엔티티의 reply_timestamp 를 매칭시킬 수 있습니다.
이때, 기존에 @Column 애너테이션에 설정했었던 columnDefinition 속성도 정의할 수 있습니다.
(이 외의 다른 속성 역시 재정의 할 수 있습니다.)

300x250
반응형