ChatGPT解决这个技术问题 Extra ChatGPT

如何从 Spring Data 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" }
]

我怎样才能做到这一点?


m
manish

JPQL 查询的解决方案

JPA specification 中的 JPQL 查询支持此功能。

第 1 步:声明一个简单的 bean 类

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 步:从存储库方法返回 bean 实例

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

重要笔记

确保提供 bean 类的完全限定路径,包括包名。例如,如果 bean 类名为 MyBean,并且它位于包 com.path.to 中,则 bean 的完全限定路径将是 com.path.to.MyBean。简单地提供 MyBean 是行不通的(除非 bean 类在默认包中)。确保使用 new 关键字调用 bean 类构造函数。 SELECT new com.path.to.MyBean(...) 会起作用,而 SELECT com.path.to.MyBean(...) 不会。确保以与 bean 构造函数中预期的顺序完全相同的顺序传递属性。尝试以不同的顺序传递属性将导致异常。确保查询是有效的 JPA 查询,也就是说,它不是本机查询。 @Query("SELECT ...") 或 @Query(value = "SELECT ...") 或 @Query(value = "SELECT ...", nativeQuery = false) 将起作用,而 @Query(value = "SELECT ...", nativeQuery = true) 将不起作用。这是因为本机查询无需修改即可传递给 JPA 提供程序,并且是针对底层 RDBMS 执行的。由于 new 和 com.path.to.MyBean 不是有效的 SQL 关键字,因此 RDBMS 会引发异常。

原生查询解决方案

如上所述,new ... 语法是 JPA 支持的机制,适用于所有 JPA 提供程序。但是,如果查询本身不是 JPA 查询,也就是说,它是本机查询,则 new ... 语法将不起作用,因为查询直接传递到底层 RDBMS,它不理解 new 关键字因为它不是 SQL 标准的一部分。

在这种情况下,需要将 bean 类替换为 Spring Data Projection 接口。

第一步:声明一个投影接口

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 关键字将结果字段映射到投影属性以进行明确映射。


它不起作用,触发错误:Caused by: java.lang.IllegalArgumentException: org.hibernate.hql.internal.ast.QuerySyntaxException: Unable to locate class [SurveyAnswerReport] [select new SurveyAnswerReport(v.answer,count(v.id)) from com.furniturepool.domain.Survey v group by v.answer] at org.hibernate.jpa.spi.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1750) at org.hibernate.jpa.spi.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1677) at org.hibernate.jpa.spi.AbstractEnti..........
您的输出中的 SurveyAnswerReport 是什么。我假设您将 SurveyAnswerStatistics 替换为您自己的类 SurveyAnswerReport。您需要指定完全限定的类名。
bean 类必须是完全限定的,即包含完整的包名。 com.domain.dto.SurveyAnswerReport 之类的东西。
当我尝试从 JpaRepository 返回自定义类型时,我得到了“java.lang.IllegalArgumentException:PersistentEntity 不能为空!”?我错过了一些配置吗?
使用本机查询异常时说:嵌套异常是 java.lang.IllegalArgumentException: Not a managed type: class ... 为什么应该发生这种情况?
o
ooozguuur

这个 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;
     }
 }

感谢您对这个问题的回答。它清脆清晰
@manish 谢谢你拯救了我的睡眠,你的方法就像一个魅力!!!!!!!
谢谢......我更喜欢这个解决方案而不是接受的答案本机查询解决方案,以避免一长串投影接口。
这仅在比较上述答案时有效。
r
rena

我知道这是一个老问题,并且已经得到解答,但这是另一种方法:

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

我喜欢你的回答,因为它不会强迫我创建一个新的类或接口。它对我有用。
工作正常,但我更喜欢在泛型中使用 Map 而不是?,因为 Map 会让我们将它们作为键 (0) 和值 (1) 访问
T
TanvirChowdhury

定义一个自定义 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();

解决方案更好。或使用the projection in the official document.
d
dwe

我不喜欢查询字符串中的 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();

代码需要 Lombok 来解析 @Getter


@Getter 在运行代码之前显示错误,因为它不适用于对象类型
需要龙目岛。刚刚在代码中添加了一个脚注。
它对我不起作用。构造函数没有被调用。对我来说,仅适用于 @Query 中的 new 的基于接口的投影或基于类的投影。如果没有 new(使用此构造函数 HashMap<String, Object>)的基于类的工作会很棒。但我得到org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [org.springframework.data.jpa.repository.query.AbstractJpaQuery$TupleConverter$TupleBackedMap] to type [package.MyClass]
似乎 spring 无法将 Map 对象注入构造函数,因为它们的类型不同。存储库返回类型不是 Map<String, Object>所以它不能调用专用的转换器。找到下面可能有帮助的帖子bytestree.com/spring/…
S
Senthuran
@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();

}

上面的代码对我有用。


a
adlerer

我使用自定义 DTO(接口)将本机查询映射到 - 最灵活且重构安全的方法。

我遇到的问题 - 令人惊讶的是,界面中的字段顺序和查询中的列很重要。我通过按字母顺序对接口 getter 进行排序,然后以相同的方式对查询中的列进行排序,从而使其工作。


m
marc_s

使用 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;
    }


Y
Yousra ADDALI

我刚刚解决了这个问题:

基于类的投影不适用于查询 native(@Query(value = "SELECT ...", nativeQuery = true)),因此我建议使用 interface 定义自定义 DTO。

在使用 DTO 之前应该验证查询语法正确与否


s
satish
    //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();`

虽然此代码可能会回答问题,但提供有关此代码为何和/或如何回答问题的额外上下文可提高其长期价值。