ひだまりソケットは壊れない

ソフトウェア開発に関する話を書きます。 最近は主に Android アプリ、Windows アプリ (UWP アプリ)、Java 関係です。

まじめなことを書くつもりでやっています。 適当なことは 「一角獣は夜に啼く」 に書いています。

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) によるクエリを記述できる。 これを用いて、例えば FooRepositoryFoo に関する集計処理を記述できる。

    @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 JOINLEFT OUTER JOIN はシノニムである。 JPA においては、エンティティ定義でエンティティ間の関連を定義して、JPQL では 単純に下記のようにプロパティ名の記述だけで JOIN を記述できる。

        FROM Foo foo JOIN foo.bar bar

ただし、上記のコードでは foo.barnull となるような Foo は処理対象から外れてしまう。 foo.barnull でも処理対象とするために LEFT JOIN を使用する必要がある。 普通に SQL を書く場合には LEFT JOIN を使うのは自然だと思うが、JPQL だと JOIN の条件を書かなかったり、Foo のエンティティを普通に引っ張ってくるときは barnull でも取得できたりするので、LEFT JOIN が必要なことをうっかり忘れがちになりがちな気がする。 注意が必要。

GROUP BY (JPA 2.2 仕様の 4.7 節)

SQL と同様 GROUP BYHAVING を使用できる。 SELECT 句では集約関数を使用できる。 (上の例では COUNT 関数。)

参考

*1:テストは書いてあるのでまあ大丈夫かなという……。