我正在使用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" }
]
我怎样才能做到这一点?
答案 0 :(得分:162)
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();
}
MyBean
并且它在包com.path.to
中,那么bean的完全限定路径将是com.path.to.MyBean
。简单地提供MyBean
将不起作用(除非bean类在默认包中)。new
关键字调用bean类构造函数。 SELECT new com.path.to.MyBean(...)
将有效,而SELECT com.path.to.MyBean(...)
则不会。@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,后者无法理解{{ 1}}关键字,因为它不是SQL标准的一部分。
在这种情况下,需要用Spring Data Projection接口替换bean类。
第1步:声明投影界面
new
第2步:从查询中返回投影属性
package com.path.to;
public interface SurveyAnswerStatistics {
String getAnswer();
int getCnt();
}
使用SQL 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();
}
关键字将结果字段映射到投影属性以进行明确映射。
答案 1 :(得分:16)
此SQL查询返回List&lt;对象[]&gt;会。
你可以这样做:
@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;
}
}
答案 2 :(得分:8)
我知道这是一个老问题而且已经得到了解答,但这是另一种方法:
@Query("select new map(count(v) as cnt, v.answer) from Survey v group by v.answer")
public List<?> findSurveyCount();
答案 3 :(得分:6)
使用接口,您可以获得更简单的代码。无需创建和手动调用构造函数
第1步:声明必填项并填写必填字段:
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();
}
答案 4 :(得分:4)
定义一个自定义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();
答案 5 :(得分:2)
我不喜欢查询字符串中的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
答案 6 :(得分:1)
我使用自定义DTO(接口)将本机查询映射到-最灵活的方法和重构安全的
我遇到的问题-令人惊讶的是,界面中字段的顺序和查询中列的顺序很重要。我通过按字母顺序对接口吸气剂进行排序,然后以相同的方式对查询中的列进行排序来使其工作。
答案 7 :(得分:1)
使用 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;
}
答案 8 :(得分:0)
我刚刚解决了这个问题:
@Query(value = "SELECT ...", nativeQuery = true
)),因此,我建议使用interface定义自定义DTO。 答案 9 :(得分:0)
@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();
}
上面的代码对我有用。
答案 10 :(得分:0)
//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();`