programing

스프링 데이터 JPA GROUP BY 쿼리에서 사용자 지정 개체를 반환하는 방법

goodsources 2022. 8. 28. 09:58
반응형

스프링 데이터 JPA GROUP BY 쿼리에서 사용자 지정 개체를 반환하는 방법

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

이는 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.com.path.to는 '으로 간다' 입니다com.path.to.MyBean 「 」를 .MyBean는 동작하지 않습니다(기본 패키지에 bean 클래스가 없는 한).
  2. bean을 사용하여 해 주십시오.new키워드를 지정합니다. SELECT new com.path.to.MyBean(...)있는 , 작만만만만 만만만만 、SELECT com.path.to.MyBean(...)하지 않을 것이다.
  3. 속성을 bean constructor에서 예상한 것과 동일한 순서로 전달해야 합니다.다른 순서로 속성을 전달하려고 하면 예외가 발생합니다.
  4. 쿼리가 유효한 JPA 쿼리인지, 즉 네이티브 쿼리가 아닌지 확인하십시오. @Query("SELECT ...") , 「」@Query(value = "SELECT ...") , 「」@Query(value = "SELECT ...", nativeQuery = false)있는 , 작만만만만 만만만만 、@Query(value = "SELECT ...", nativeQuery = true)동작하지 않습니다.이는 네이티브 쿼리가 JPA 공급자에게 수정 없이 전달되고 기본 RDBMS에 대해 실행되기 때문입니다.★★new ★★★★★★★★★★★★★★★★★」com.path.to.MyBeanSQL을 사용합니다.RDBMS를 사용합니다.

네이티브 쿼리 솔루션

바와 같이 「」는, 「 」, 「 」, 「 。new ...구문은 JPA가 지원하는 메커니즘으로 모든 JPA 공급자와 연동됩니다.쿼리가 즉인 ", "JPA " " " " 입니다.new ...는 RDBMS의 RDBMS의 RDBMS를 하지 않습니다.new키워드는 SQL 표준에 속하지 않기 때문에 사용합니다.

이러한 상황에서는 빈 클래스를 스프링 데이터 투영 인터페이스로 대체해야 합니다.

순서 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();
}

을 합니다.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();

커스텀 pojo 클래스를 정의하고 sureveyQueryAnalysics라고 하며 쿼리가 반환된 값을 커스텀 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이 필요합니다.

커스텀 DTO(인터페이스)를 사용하여 네이티브 쿼리를 가장 유연한 접근법과 리팩터링 안전성에 매핑했습니다.

여기서 문제가 발생했습니다. 놀랍게도 인터페이스의 필드 순서와 쿼리의 열이 중요합니다.인터페이스 getters를 알파벳 순으로 정렬하고 쿼리의 컬럼을 같은 방식으로 정렬하여 작업을 수행했습니다.

@Repository
public interface ExpenseRepo extends JpaRepository<Expense,Long> {
    List<Expense> findByCategoryId(Long categoryId);

    @Query(value = "select category.name,SUM(expense.amount) from expense JOIN category ON expense.category_id=category.id GROUP BY expense.category_id",nativeQuery = true)
    List<?> getAmountByCategory();

}

위 코드는 나에게 효과가 있었다.

JDBC를 사용하여 열 이름과 해당 값(키-값 쌍)을 가진 데이터를 가져옵니다.

/*Template class with a basic set of JDBC operations, allowing the use
  of named parameters rather than traditional '?' placeholders.
 
  This class delegates to a wrapped {@link #getJdbcOperations() JdbcTemplate}
  once the substitution from named parameters to JDBC style '?' placeholders is
  done at execution time. It also allows for expanding a {@link java.util.List}
  of values to the appropriate number of placeholders.
 
  The underlying {@link org.springframework.jdbc.core.JdbcTemplate} is
  exposed to allow for convenient access to the traditional
  {@link org.springframework.jdbc.core.JdbcTemplate} methods.*/


@Autowired
protected  NamedParameterJdbcTemplate jdbc;


@GetMapping("/showDataUsingQuery/{Query}")
    public List<Map<String,Object>> ShowColumNameAndValue(@PathVariable("Query")String Query) throws SQLException {

      /* MapSqlParameterSource class is intended for passing in a simple Map of parameter values
        to the methods of the {@link NamedParameterJdbcTemplate} class*/

       MapSqlParameterSource msp = new MapSqlParameterSource();

       // this query used for show column name and columnvalues....
        List<Map<String,Object>> css = jdbc.queryForList(Query,msp);

        return css;
    }

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

  • 클래스 베이스 프로젝션은 쿼리 네이티브에서는 동작하지 않습니다.@Query(value = "SELECT ...", nativeQuery = true)의 경우 인터페이스를 사용하여 커스텀 DTO를 정의할 것을 권장합니다.
  • DTO를 사용하기 전에 쿼리가 구문적으로 올바른지 확인해야 합니다.
    //in Service      
      `
                public List<DevicesPerCustomer> findDevicesPerCustomer() {
                    LOGGER.info(TAG_NAME + " :: inside findDevicesPerCustomer : ");
                    List<Object[]> list = iDeviceRegistrationRepo.findDevicesPerCustomer();
                    List<DevicesPerCustomer> out = new ArrayList<>();
                    if (list != null && !list.isEmpty()) {
                        DevicesPerCustomer mDevicesPerCustomer = null;
                        for (Object[] object : list) {
                            mDevicesPerCustomer = new DevicesPerCustomer();
mDevicesPerCustomer.setCustomerId(object[0].toString());
                            mDevicesPerCustomer.setCount(Integer.parseInt(object[1].toString()));
                            
                            out.add(mDevicesPerCustomer);
                        }
                    }
            
                    return out;
                }`
        
    //In Repo
        `   @Query(value = "SELECT d.customerId,count(*) FROM senseer.DEVICE_REGISTRATION d  where d.customerId is not null group by d.customerId", nativeQuery=true)
            List<Object[]> findDevicesPerCustomer();`

언급URL : https://stackoverflow.com/questions/36328063/how-to-return-a-custom-object-from-a-spring-data-jpa-group-by-query

반응형