はじめに
Entityでよく使うカラムの共通化の仕方について、
考えたことをまとめておきます。
Javaで共通化
各テーブルに共通して存在するカラム(たとえば created_at
とか)は抽象クラスに切り出し。
- 抽象クラス
CommonEntity.java
/**
* 共通Entity(各テーブルで保持する項目)
*/
@MappedSuperclass
@Data
public abstract class CommonEntity {
@Column(updatable = false)
private String createdBy;
@Column(updatable = false)
private LocalDateTime createdAt;
private String updatedBy;
private LocalDateTime updatedAt;
// 登録前に共通的に実行されるメソッド(呼び出し不要)
@PrePersist
public void preInsert() {
LocalDateTime localDateTime = LocalDateTime.now();
setCreatedAt(localDateTime);
setUpdatedAt(localDateTime);
}
// 更新前に共通的に実行されるメソッド(呼び出し不要)
@PreUpdate
public void preUpdate() {
setUpdatedAt(LocalDateTime.now());
}
// 登録前に共通的に実行されるメソッド
public void initUserId(CustomRequestHeaderForm header) {
this.setCreatedBy(header.getLoginUserId());
this.setUpdatedBy(header.getLoginUserId());
}
// 更新前に共通的に実行されるメソッド
public void updateUserId(CustomRequestHeaderForm header) {
this.setUpdatedBy(header.getLoginUserId());
}
}
- エンティティ
MemoEntity.java
@Entity
@Getter
@Setter
@Builder
@Table(name = "memo")
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class MemoEntity extends CommonEntity implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
private String content;
@Builder.Default
private Boolean isDeleted = ApplicationConstant.IS_NOT_DELETED;
public void delete() {
isDeleted = ApplicationConstant.IS_DELETED;
}
}
なにがうれしいか
- 登録前や更新前に実行したいお決まりの処理を切り出すことができる
- エンティティを定義するたびに、同じことを書く必要がなくなる
- エンティティがスッキリする
- ドメインの実装に集中できる
Kotlinで共通化
上記と同じことを実装しようとすると、Kotlinでは以下のようになりました。
- 抽象クラス
ちなみに、data class
はスーパークラスになれないみたいです。
CommonEntity.kt
@MappedSuperclass
abstract class CommonEntity {
@Column(updatable = false)
lateinit var createdAt: LocalDateTime
lateinit var updatedAt: LocalDateTime
@Column(updatable = false)
lateinit var createdBy: String
lateinit var updatedBy: String
fun initUserId(header: CustomRequestHeaderForm) {
createdBy = header.loginUserId
updatedBy = header.loginUserId
}
fun updateUserId(header: CustomRequestHeaderForm) {
updatedBy = header.loginUserId
}
@PrePersist
fun preInsert() {
val now = LocalDateTime.now()
createdAt = now
updatedAt = now
}
@PreUpdate
fun preUpdate() {
updatedAt = LocalDateTime.now()
}
}
- エンティティ
MemoEntity.kt
@Entity
@Table(name = "memo")
data class MemoEntity(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long?,
val title: String,
val content: String,
var isDeleted: Boolean,
): CommonEntity() {
fun delete() {
isDeleted = ApplicationConstant.IS_DELETED
}
}
微妙だなと思ったこと
コンストラクタでまとめて初期化できない
抽象クラスのフィールドはコンストラクタ内で初期化できないのでスッキリしません。。
(Javaも同じではありますが、Kotlinではコンストラクタで完結できるとスッキリ書けるので・・)
- NG
本当はこんな感じに書きたい。けどエラる。
MemoRequestForm.kt
data class MemoRequestForm (
val title: String,
val content: String,
) {
fun toEntity(header: CustomRequestHeaderForm, id: Long? = null) =
MemoEntity(
id,
title,
content,
header.loginUserId // ←抽象クラスのフィールドなのでエラる!!
)
}
}
- OK
以下だとエラーにならない。
MemoRequestForm.kt
data class MemoRequestForm (
val title: String,
val content: String,
) {
fun toEntity(header: CustomRequestHeaderForm, id: Long? = null): MemoEntity {
val entity = MemoEntity(
id,
title,
content,
)
entity.createdBy = header.loginUserId // 別で代入してあげる必要がある。
return entity
}
}
}
抽象クラスでvar
を使ってしまっている
上記の通り、コンストラクタ内ですべてのフィールドを初期化できないがために、
以下のように抽象クラスを使わなければ全てval
で書けるのに、var
で書く必要が出てしまいます。
- good
抽象クラスで切り出さない場合は全てval
で書ける。
MemoEntity.kt
@Entity
@Table(name = "memo")
data class MemoEntity(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long?,
val title: String,
val content: String,
val isDeleted: Boolean,
@Column(updatable = false)
val createdAt: LocalDateTime,
val updatedAt: LocalDateTime,
@Column(updatable = false)
val createdBy: String,
val updatedBy: String,
)
まとめ
以上、JavaとKotlinでのEntityの共通カラムの切り出し方についてまとめてみました。
Kotlinで抽象クラスを使うのは、もしかすると微妙かもしれません・・(好みの問題)
私はどっちがよいのか2、3日考えましたが、答えは出せず。。
皆さんのJava / Kotlinでの共通化Tips、あればぜひ教えてください!!