Java Persistence API (JPA) の JPQL で集計処理を書く (Spring Data JPA)
Spring Data JPA を使っていて、集計処理をどのように書くのが良いかわからずに調べた記録。 (例は Spring Data JPA 固有の話ではあるが、JPQL の知識は Spring Data JPA 以外での JPA でも使える。)
Spring Data JPA での集計の例
リポジトリにメソッドを定義して @Query
アノテーション使うことで、JPQL (Java Persistence query language) によるクエリを記述できる。 これを用いて、例えば FooRepository
に Foo
に関する集計処理を記述できる。
@Query(value = """ SELECT NEW com.example.AggregationResult( FUNCTION('year', foo.date), COUNT(foo) ) FROM Foo foo LEFT JOIN foo.bar bar LEFT JOIN foo.baz baz WHERE foo.targetId = ?1 AND (bar.content IS NOT NULL OR baz.content IS NOT NULL) GROUP BY FUNCTION('year', foo.date) """) fun aggregateCountPerYear(targetId: Long): List<AggregationResult>
上のコードは Kotlin で記述されたメソッド定義の例である。 (FUNCTION('year', foo.date)
が 2 箇所にあるのが微妙だし仕様上正しく動くものなのかわからないので何とかしたかったけど、どう書くのがいいかわからなかったのでこの形になっている *1。 誰か詳しい人が居たら教えてください><)
上記の例に含まれる JPQL の知識を下記に述べる。
集計に使える JPQL の知識
参照した JPA 仕様
学び
SELECT
句内のコンストラクタ式 (JPA 2.2 仕様の 4.8.2 節)
JPQL の SELECT
句の項目として Java クラスのコンストラクタを記述できる。 集計結果をオブジェクトで返したいときに便利。 ちなみに対象のクラス名は完全修飾名で指定する必要がある。
データベース関数の実行 (JPA 2.2 仕様の 4.6.17.3 節)
データベースの関数を FUNCTION(function_name, arg1, arg2, ...)
の形で記述できる。 JPQL で定義されていない集計関数を用いたい場合や、ユーザー定義の関数を利用したい場合などに便利。
LEFT JOIN
(JPA 2.2 仕様の 4.4.5.2 節)
LEFT JOIN
と LEFT OUTER JOIN
はシノニムである。 JPA においては、エンティティ定義でエンティティ間の関連を定義して、JPQL では 単純に下記のようにプロパティ名の記述だけで JOIN
を記述できる。
FROM Foo foo JOIN foo.bar bar
ただし、上記のコードでは foo.bar
が null
となるような Foo
は処理対象から外れてしまう。 foo.bar
が null
でも処理対象とするために LEFT JOIN
を使用する必要がある。 普通に SQL を書く場合には LEFT JOIN
を使うのは自然だと思うが、JPQL だと JOIN
の条件を書かなかったり、Foo
のエンティティを普通に引っ張ってくるときは bar
が null
でも取得できたりするので、LEFT JOIN
が必要なことをうっかり忘れがちになりがちな気がする。 注意が必要。
参考
- JavaEE7をはじめよう(4) - JPAクエリ(その1) JPQL - エンタープライズギークス (Enterprise Geeks) : JPQL の全体的な話。 参考になる。
- わかりやすい JPA(7)関数と集計クエリ : JPQL での集計の話。 参考になる。
- JPQLの集計関数に価値はあるか? - taediumの日記 : 集計には JPQL ではなく SQL を使った方が良いのではないかという話。 確かに……。 JPQL を使うとエンティティ定義を扱えるので、JPA の世界の中で集計クエリを書けるという利点はあるような気がする。 (多少メンテナンス性は良くなる気がするので、JPQL で困らないなら JPQL で書くと良さそうな気はしている。)
*1:テストは書いてあるのでまあ大丈夫かなという……。