はじめに
Spring BootでCRUD APIを作成しているときに、エンティティクラスでenumを使う場面があります。
たとえば、案件管理システムで「案件ステータス」を表す場合、以下のような値を定義したくなります。
public enum ProjectStatus {
NOT_STARTED,
IN_PROGRESS,
COMPLETED
}
そして、エンティティクラスでは次のように使います。
@Entity
public class Project {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private ProjectStatus status;
}
一見これでも問題なさそうに見えますが、JPAでenumを扱う場合は、基本的に@Enumeratedアノテーションを付けた方がよいです。
この記事では、エンティティクラスのenumフィールドに@Enumeratedを付ける理由と、実装例を整理します。
結論
エンティティクラスでenumを使う場合は、以下のように書くのがおすすめです。
@Enumerated(EnumType.STRING)
private ProjectStatus status;
つまり、エンティティクラスのenumフィールドには、@Enumerated(EnumType.STRING)を付けます。
@Enumeratedとは
@Enumeratedは、JPAでenumをデータベースに保存するときの形式を指定するためのアノテーションです。
enumはJava上では定数として扱えますが、データベースにはそのまま保存できません。
そのため、JPAに対して、「このenumをどのような形式でDBに保存するか」を指定する必要があります。
@Enumeratedには、主に以下の2種類があります。
@Enumerated(EnumType.ORDINAL)
@Enumerated(EnumType.STRING)
EnumType.ORDINAL
EnumType.ORDINALは、enumの定義順を数値として保存します。
たとえば、以下のようなenumがあるとします。
public enum ProjectStatus {
NOT_STARTED,
IN_PROGRESS,
COMPLETED
}
この場合、DBには以下のような数値で保存されます。
| enum | 保存される値 |
|---|---|
| NOT_STARTED | 0 |
| IN_PROGRESS | 1 |
| COMPLETED | 2 |
一見シンプルですが、注意点があります。
後からenumの定義順を変更したり、途中に値を追加したりすると、DBに保存されている数値の意味が変わってしまう可能性があります。
たとえば、以下のように途中に値を追加した場合です。
public enum ProjectStatus {
NOT_STARTED,
ON_HOLD, // 追加
IN_PROGRESS,
COMPLETED
}
この場合、もともと1として保存されていたIN_PROGRESSが、変更後はON_HOLDを表すようになってしまいます。
これは非常に危険です。
EnumType.STRING
EnumType.STRINGは、enumの名前を文字列として保存します。
@Enumerated(EnumType.STRING)
private ProjectStatus status;
この場合、DBには以下のような文字列で保存されます。
| enum | 保存される値 |
|---|---|
| NOT_STARTED | NOT_STARTED |
| IN_PROGRESS | IN_PROGRESS |
| COMPLETED | COMPLETED |
文字列として保存されるため、enumの定義順を変更してもDB上の意味は変わりません。
そのため、基本的にはEnumType.STRINGを使う方が安全です。
実装例
enumクラス
public enum ProjectStatus {
NOT_STARTED,
IN_PROGRESS,
COMPLETED
}
エンティティクラス
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
@Entity
public class Project {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@Enumerated(EnumType.STRING)
private ProjectStatus status;
protected Project() {
}
public Project(String name, ProjectStatus status) {
this.name = name;
this.status = status;
}
public Long getId() {
return id;
}
public String getName() {
return name;
}
public ProjectStatus getStatus() {
return status;
}
}
@Enumeratedを付けない場合
@Enumeratedを付けない場合、JPAではデフォルトでEnumType.ORDINALとして扱われます。
つまり、以下のように書いた場合、
private ProjectStatus status;
DBにはNOT_STARTEDやIN_PROGRESSという文字列ではなく、0や1といった数値で保存されます。
そのため、あとからenumの順番を変更したときに、不具合の原因になる可能性があります。
実際に起きやすい問題
開発中は、最初に以下のようなenumを作ることがあります。
public enum ProjectStatus {
TODO,
DOING,
DONE
}
しかし、開発が進むと、ステータスを追加したくなることがあります。
public enum ProjectStatus {
TODO,
WAITING,
DOING,
DONE
}
このとき、EnumType.ORDINALで保存していると、DB上の数値とJava側の意味がずれてしまう可能性があります。
開発初期は問題なく動いていても、仕様変更によって後からバグになる可能性があるため注意が必要です。
まとめ
エンティティクラスでenumを使う場合は、基本的に以下のように書くのが安全です。
@Enumerated(EnumType.STRING)
private ProjectStatus status;
ポイントは以下です。
-
@Enumeratedは、enumをDBに保存するときの形式を指定するアノテーション - 指定しない場合、デフォルトでは
EnumType.ORDINALになる -
ORDINALはenumの定義順を数値として保存する - enumの順番を変更すると、DB上の値の意味が変わる可能性がある
- 基本的には
EnumType.STRINGを使うのが安全
Spring Bootでエンティティクラスにenumを使う場合は、忘れずに@Enumerated(EnumType.STRING)を付けるようにしたいと思います。