1. 概要

Spring Data JPAは、特定の状況でデータベースからエンティティを取得するためのクエリの作成を抽象化できますが、集計関数を追加する場合など、クエリをカスタマイズする必要がある場合があります

このチュートリアルでは、これらのクエリの結果をオブジェクトに変換する方法に焦点を当てます。 2つの異なるソリューションを検討します。1つはJPA仕様とPOJOを使用し、もう1つはSpring Dataプロジェクションを使用します。

2. JPAクエリと集約の問題

JPAクエリは通常、マップされたエンティティのインスタンスとして結果を生成します。 ただし、集計関数を使用したクエリは、通常、結果をObject[]として返します。

問題を理解するために、投稿とコメントの関係に基づいてドメインモデルを定義しましょう。

@Entity
public class Post {
    @Id
    private Integer id;
    private String title;
    private String content;
    @OneToMany(mappedBy = "post")
    private List comments;

    // additional properties
    // standard constructors, getters, and setters
}

@Entity
public class Comment {
    @Id
    private Integer id;
    private Integer year;
    private boolean approved;
    private String content;
    @ManyToOne
    private Post post;

    // additional properties
    // standard constructors, getters, and setters
}

私たちのモデルでは、投稿には多くのコメントを含めることができ、各コメントは1つの投稿に属すると定義されています。 このモデルでSpring Dataリポジトリを使用してみましょう。

@Repository
public interface CommentRepository extends JpaRepository<Comment, Integer> {
    // query methods
}

それでは、年ごとにグループ化されたコメントを数えましょう。

@Query("SELECT c.year, COUNT(c.year) FROM Comment AS c GROUP BY c.year ORDER BY c.year DESC")
List<Object[]> countTotalCommentsByYear();

結果が異なる形状であるため、前のJPAクエリの結果をCommentのインスタンスにロードできません。クエリで指定されたyearおよびCOUNTエンティティオブジェクトと一致しません。

リストに返される汎用Object[] の結果にアクセスすることはできますが、そうすると、厄介でエラーが発生しやすいコードになります。

3. クラスコンストラクタを使用した結果のカスタマイズ

JPA仕様により、オブジェクト指向の方法で結果をカスタマイズできます。したがって、JPQLコンストラクター式を使用して結果を設定できます。

@Query("SELECT new com.baeldung.aggregation.model.custom.CommentCount(c.year, COUNT(c.year)) "
  + "FROM Comment AS c GROUP BY c.year ORDER BY c.year DESC")
List<CommentCount> countTotalCommentsByYearClass();

これにより、SELECTステートメントの出力がPOJOにバインドされます。 指定されたクラスには、投影された属性と完全に一致するコンストラクターが必要ですが、@Entityでアノテーションを付ける必要はありません。

また、JPQLで宣言されたコンストラクターには、完全修飾名が必要であることがわかります。

package com.baeldung.aggregation.model.custom;

public class CommentCount {
    private Integer year;
    private Long total;

    public CommentCount(Integer year, Long total) {
        this.year = year;
        this.total = total;
    }
    // getters and setters
}

4. SpringDataProjectionを使用した結果のカスタマイズ

もう1つの可能な解決策は、 Spring DataProjectionを使用してJPAクエリの結果をカスタマイズすることです。 この機能を使用すると、かなり少ないコードでクエリ結果を投影できます。

4.1. JPAクエリの結果のカスタマイズ

インターフェイスベースのプロジェクションを使用するには、プロジェクションされた属性名に一致するgetterメソッドで構成されるJavaインターフェイスを定義する必要があります。 クエリ結果のインターフェースを定義しましょう。

public interface ICommentCount {
    Integer getYearComment();
    Long getTotalComment();
}

それでは、次のように返される結果を使用してクエリを表現しましょう。 リスト

@Query("SELECT c.year AS yearComment, COUNT(c.year) AS totalComment "
  + "FROM Comment AS c GROUP BY c.year ORDER BY c.year DESC")
List<ICommentCount> countTotalCommentsByYearInterface();

Springが投影された値をインターフェイスにバインドできるようにするには、インターフェイスで見つかったプロパティ名を使用して、投影された各属性にエイリアスを指定する必要があります。

その後、Spring Dataはその場で結果を作成し、結果の各行に対してプロキシインスタンスを返します。

4.2. ネイティブクエリの結果のカスタマイズ

JPAクエリがネイティブSQLほど高速ではない、またはデータベースエンジンの特定の機能を使用できない状況に直面する可能性があります。 これを解決するために、ネイティブクエリを使用します。

インターフェイスベースのプロジェクションの利点の1つは、ネイティブクエリに使用できることです。 ICommentCount をもう一度使用して、SQLクエリにバインドしましょう。

@Query(value = "SELECT c.year AS yearComment, COUNT(c.*) AS totalComment "
  + "FROM comment AS c GROUP BY c.year ORDER BY c.year DESC", nativeQuery = true)
List<ICommentCount> countTotalCommentsByYearNative();

これは、JPQLクエリと同じように機能します。

5. 結論

この記事では、集計関数を使用したJPAクエリの結果のマッピングに対処するために2つの異なるソリューションを評価しました。最初に、POJOクラスを含むJPA標準を使用し、2番目のソリューションでは軽量を使用しました。インターフェイスを使用したSpringDataプロジェクション。

Spring Dataプロジェクションを使用すると、JavaとJPQLの両方で記述できるコードを減らすことができます。

いつものように、このチュートリアルのサンプルコードは、GitHubからで入手できます。