[java] SpringData JPA GROUP BY 쿼리에서 사용자 지정 개체를 반환하는 방법

SpringData 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 쿼리 솔루션

이것은 JPA 사양 내의 JPQL 쿼리에 대해 지원됩니다 .

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 클래스에 대한 완전한 경로를 제공해야합니다. 예를 들어, Bean 클래스가 호출 MyBean되고 package com.path.to에있는 경우 Bean에 대한 완전한 경로는입니다 com.path.to.MyBean. 단순히 제공하는 MyBean것은 작동하지 않습니다 (빈 클래스가 기본 패키지에 있지 않는 한).
  2. new키워드를 사용하여 Bean 클래스 생성자를 호출해야 합니다. SELECT new com.path.to.MyBean(...)작동하지만 작동 SELECT com.path.to.MyBean(...)하지 않습니다.
  3. Bean 생성자에서 예상 한 것과 정확히 동일한 순서로 속성을 전달해야합니다. 다른 순서로 속성을 전달하려고하면 예외가 발생합니다.
  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 표준.

이와 같은 상황에서는 빈 클래스를 SpringData Projection 인터페이스 로 대체해야합니다 .

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키워드를 사용하여 결과 필드를 명확한 매핑을 위해 프로젝션 속성에 매핑합니다.


답변

이 SQL 쿼리는 List <Object []>를 반환합니다.

다음과 같이 할 수 있습니다.

 @RestController
 @RequestMapping("/survey")
 public class SurveyController {

   @Autowired
   private SurveyRepository surveyRepository;

     @RequestMapping(value = "/find", method =  RequestMethod.GET)
     public Map<Long,String> findSurvey(){
       List<Object[]> result = surveyRepository.findSurveyCount();
       Map<Long,String> map = null;
       if(result != null && !result.isEmpty()){
          map = new HashMap<Long,String>();
          for (Object[] object : result) {
            map.put(((Long)object[0]),object[1]);
          }
       }
     return map;
     }
 }


답변

나는 이것이 오래된 질문이고 이미 답변을 받았음을 알고 있지만 여기에 또 다른 접근 방식이 있습니다.

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


답변

인터페이스를 사용하면 더 간단한 코드를 얻을 수 있습니다. 생성자를 만들고 수동으로 호출 할 필요가 없습니다.

1 단계 : 필수 필드를 사용하여 intefrace를 선언합니다.

public interface SurveyAnswerStatistics {

  String getAnswer();
  Long getCnt();

}

2 단계 : 인터페이스에서 getter와 이름이 같은 열을 선택하고 저장소 메서드에서 intefrace를 반환합니다.

public interface SurveyRepository extends CrudRepository<Survey, Long> {

    @Query("select v.answer as answer, count(v) as cnt " +
           "from Survey v " +
           "group by v.answer")
    List<SurveyAnswerStatistics> findSurveyCount();

}


답변

사용자 정의 pojo 클래스를 정의하고 sureveyQueryAnalytics라고 말하고 사용자 정의 pojo 클래스에 쿼리 반환 값을 저장하십시오.

@Query(value = "select new com.xxx.xxx.class.SureveyQueryAnalytics(s.answer, count(sv)) from Survey s group by s.answer")
List<SureveyQueryAnalytics> calculateSurveyCount();


답변

쿼리 문자열에서 Java 유형 이름을 좋아하지 않으며 특정 생성자로 처리합니다. Spring JPA는 HashMap 매개 변수의 쿼리 결과로 생성자를 암시 적으로 호출합니다.

@Getter
public class SurveyAnswerStatistics {
  public static final String PROP_ANSWER = "answer";
  public static final String PROP_CNT = "cnt";

  private String answer;
  private Long   cnt;

  public SurveyAnswerStatistics(HashMap<String, Object> values) {
    this.answer = (String) values.get(PROP_ANSWER);
    this.count  = (Long) values.get(PROP_CNT);
  }
}

@Query("SELECT v.answer as "+PROP_ANSWER+", count(v) as "+PROP_CNT+" FROM  Survey v GROUP BY v.answer")
List<SurveyAnswerStatistics> findSurveyCount();

@Getter를 해결하려면 코드에 Lombok이 필요합니다.


답변

방금이 문제를 해결했습니다.

  • 클래스 기반 프로젝션은 쿼리 네이티브 (@Query(value = "SELECT ...", nativeQuery = true )) interface를 사용하여 사용자 지정 DTO를 정의하는 것이 좋습니다.
  • DTO를 사용하기 전에 쿼리 구문이 올바른지 확인해야합니다.