Springの@Queryに列挙型のフィールドの値を埋め込む

特定の値しか入らないテーブルのカラムに対応する列挙型を用意し、フィールドとして値を保持している。

ORMとしてはJPA Entityを使用しているが、かなり古いソースなのでEnumeratedConvertによる列挙型のフィールドマッピングなどは行われず、単純な int でフィールドが宣言されている。

データアクセスはSpring DataでJpaRepositoryを使っているが、QueryアノテーションでJPQLを書くときに、列挙型のフィールドを参照できないかと思い調べたのでメモ。

問題

特定の値をフィールドとして持った列挙型を用意している。

package example;

@Getter
@RequiredArgsConstructor
public enum ExampleEntityState {
    START(0), END(1);
    private final int value;
}

@Data
@Entity
public class ExampleEntity {
    private int state; // 0, 1 が入る
}

この値に対応するJPA Entityのフィールドは、Enumeratedアノテーションなどで列挙型に変換されておらず、 int で宣言されている。

Queryアノテーションのクエリ文字列内で、特定の値のデータを検索したいが、マジックナンバーは使いたくないので、列挙型を用いて値を参照したい。

だが、クエリ文字列は定数で記述する必要があるため、列挙型でもフィールド値の参照はできない。

// これはコンパイルエラーになる
@Query("SELECT e FROM ExampleEntity e WHERE e.state = " + ExampleEntityState.END.getValue())
List<ExampleEntity> findAllOfEndState();

対応

Query内でSpring Expression Language(SpEL)を使える。これを使えば、クエリ文字列内でもフィールドアクセスが可能。

spring.io

spring.pleiades.io

SpEL内で T(完全修飾クラス名).staticフィールドやメソッド と書くことで、staticフィールドにアクセスできる。列挙型の値にも、これでアクセスできた。また、フィールドアクセスは通常のEL式と同様、Getter経由であればフィールド名のみでアクセスできる。

クエリ文字列では先頭に : をつけ、今回の例だと以下のように記述できた。

// ?#{T...} でも動くが、Intellij上でシンタックスエラー扱いになる
@Query("SELECT e FROM ExampleEntity e WHERE e.state = :#{T(example.ExampleEntityState).END.value}")
List<ExampleEntity> findAllOfEndState();

振り返り

@Enumerated@Convert で列挙型をフィールドとしてマッピングしてあれば、列挙型の値を完全修飾クラス名付き(上記では example.ExampleEntityState.STARTexample.ExampleEntityState.END)をクエリ文字列に記述できるが、修正量が多くなることから今回はこの方法で対応した。

// 列挙型をフィールドとしてマッピングしている場合
@Query("SELECT e FROM ExampleEntity e WHERE e.state = example.ExampleEntityState.END")

また、JPQLではなくネイティブクエリでSQLを書きたいときにも、この方法は使えそう。

もうちょっと簡単な方法がありそうな気はする。