1. ホーム
  2. java

[解決済み] Spring Data JPA GROUP BY クエリからカスタムオブジェクトを返す方法

2022-04-29 12:45:03

質問

Spring Data JPAを使用したSpring Bootアプリケーションを開発しています。カスタムJPQLクエリを使用して、いくつかのフィールドでグループ化し、カウントを取得しています。以下は私のリポジトリメソッドです。

@Query(value = "select count(v) as cnt, v.answer from Survey v group by v.answer")
public List<?> findSurveyCount();

正常に動作し、以下のような結果が得られました。

[
  [1, "a1"],
  [2, "a2"]
]

このようなものが欲しいのですが。

[
  { "cnt":1, "answer":"a1" },
  { "cnt":2, "answer":"a2" }
]

どうすれば実現できるのでしょうか?

どのように解決するのですか?

JPQLクエリーの解決方法

内のJPQLクエリに対応しています。 JPA仕様 .

ステップ1 : シンプルなビーンクラスを宣言する

package com.path.to;

public class SurveyAnswerStatistics {
  private String answer;
  private Long   cnt;

  public SurveyAnswerStatistics(String answer, Long cnt) {
    this.answer = answer;
    this.count  = cnt;
  }
}

ステップ2 : リポジトリメソッドからビーンインスタンスを返す

public interface SurveyRepository extends CrudRepository<Survey, Long> {
    @Query("SELECT " +
           "    new com.path.to.SurveyAnswerStatistics(v.answer, COUNT(v)) " +
           "FROM " +
           "    Survey v " +
           "GROUP BY " +
           "    v.answer")
    List<SurveyAnswerStatistics> findSurveyCount();
}

重要なお知らせ

  1. パッケージ名を含む、Bean クラスへの完全修飾パスを必ず指定してください。たとえば、ビーン・クラスの名前が MyBean で、パッケージ com.path.to の場合、ビーンへの完全修飾パスは次のようになります。 com.path.to.MyBean . 単に MyBean は動作しません (ビーンクラスがデフォルトパッケージ内にある場合を除く)。
  2. ビーンクラスのコンストラクタを呼び出すには、必ず new キーワードを使用します。 SELECT new com.path.to.MyBean(...) は動作しますが SELECT com.path.to.MyBean(...) となります。
  3. ビーンコンストラクタで予想される順序と全く同じ順序で属性を渡すことを確認してください。異なる順番で属性を渡そうとすると、例外が発生します。
  4. クエリが有効なJPAクエリであること、つまりネイティブクエリでないことを確認してください。 @Query("SELECT ...") または @Query(value = "SELECT ...") または @Query(value = "SELECT ...", nativeQuery = false) は動作しますが @Query(value = "SELECT ...", nativeQuery = true) は動作しません。これは、ネイティブクエリがJPAプロバイダに変更されずに渡され、そのまま基盤となるRDBMSに対して実行されるからです。したがって newcom.path.to.MyBean が有効な SQL キーワードでない場合、RDBMS は例外をスローします。

ネイティブクエリの解決策

上述したように new ... 構文はJPAがサポートするメカニズムであり、すべてのJPAプロバイダで機能します。しかし、クエリ自体がJPAクエリでない場合、つまりネイティブクエリである場合は new ... 構文が使えないのは、クエリが直接基盤となる RDBMS に渡されるからです。 new キーワードは標準SQLの一部ではないので、このキーワードを使用することはできません。

このような状況では、ビーンクラスを Springデータプロジェクション のインターフェイスを使用します。

ステップ1 : プロジェクション・インターフェースを宣言する

package com.path.to;

public interface SurveyAnswerStatistics {
  String getAnswer();

  int getCnt();
}

ステップ2 : クエリから投影されたプロパティを返す

public interface SurveyRepository extends CrudRepository<Survey, Long> {
    @Query(nativeQuery = true, value =
           "SELECT " +
           "    v.answer AS answer, COUNT(v) AS cnt " +
           "FROM " +
           "    Survey v " +
           "GROUP BY " +
           "    v.answer")
    List<SurveyAnswerStatistics> findSurveyCount();
}

SQLを使用する AS キーワードを使用して、結果フィールドをプロジェクションプロパティにマッピングし、曖昧さのないマッピングを実現します。