Spring Data JPA GROUP BY sorgusundan özel bir nesne nasıl döndürülür


115

Spring Data JPA ile bir Spring Boot uygulaması geliştiriyorum. Bir alana göre gruplamak ve sayımı almak için özel bir JPQL sorgusu kullanıyorum. Depo yöntemim aşağıdadır.

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

Çalışır ve sonuç şu şekilde alınır:

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

Bunun gibi bir şey almak istiyorum:

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

Bunu nasıl başarabilirim?

Yanıtlar:


252

JPQL sorguları için çözüm

Bu, JPA spesifikasyonu dahilindeki JPQL sorguları için desteklenir .

1. Adım : Basit bir fasulye sınıfı bildirin

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. Adım : Kod deposu yönteminden fasulye örneklerini iade edin

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

Önemli notlar

  1. Paket adı dahil olmak üzere, fasulye sınıfının tam yolunu sağladığınızdan emin olun. Örneğin, fasulye sınıfı çağrılırsa MyBeanve com.path.topaketteyse, çekirdeğe giden tam nitelikli yol olacaktır com.path.to.MyBean. Basitçe sağlamak MyBeanişe yaramayacaktır (bean sınıfı varsayılan pakette değilse).
  2. newAnahtar kelimeyi kullanarak bean sınıfı yapıcısını çağırdığınızdan emin olun . SELECT new com.path.to.MyBean(...)çalışacak, oysa SELECT com.path.to.MyBean(...)olmayacak.
  3. Öznitelikleri, bean yapıcısında beklenen sırayla tam olarak aynı sırada geçirdiğinizden emin olun. Öznitelikleri farklı bir sırayla geçirmeye çalışmak bir istisnaya yol açar.
  4. Sorgunun geçerli bir JPA sorgusu olduğundan, yani yerel bir sorgu olmadığından emin olun. @Query("SELECT ..."), veya @Query(value = "SELECT ...")veya @Query(value = "SELECT ...", nativeQuery = false)çalışacak, oysa @Query(value = "SELECT ...", nativeQuery = true)çalışmayacak. Bunun nedeni, yerel sorguların JPA sağlayıcısında değişiklik yapılmadan geçirilmesi ve bu şekilde temeldeki RDBMS'ye karşı yürütülmesidir. Yana newve com.path.to.MyBeangeçerli bir SQL anahtar olmayan, RDBMS sonra bir istisna atar.

Yerel sorgular için çözüm

Yukarıda belirtildiği gibi new ...sözdizimi, JPA destekli bir mekanizmadır ve tüm JPA sağlayıcılarıyla çalışır. Bununla birlikte, sorgunun kendisi bir JPA sorgusu değilse, yani yerel bir sorgu ise, new ...sözdizimi, sorgu doğrudan temeldeki RDBMS'ye iletildiği için çalışmayacaktır, bu da newanahtar kelimeyi anlamıyor çünkü SQL standardı.

Bu gibi durumlarda, fasulye sınıflarının Spring Data Projection arabirimleriyle değiştirilmesi gerekir .

1. Adım : Bir projeksiyon arayüzü bildirin

package com.path.to;

public interface SurveyAnswerStatistics {
  String getAnswer();

  int getCnt();
}

Adım 2 : Sorgudan öngörülen özellikleri iade edin

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

ASKesin haritalama için sonuç alanlarını projeksiyon özellikleriyle eşlemek için SQL anahtar sözcüğünü kullanın .


1
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..........
Çalışmıyor

SurveyAnswerReport Çıktınızda bu nedir ? SurveyAnswerStatistics Kendi sınıfınızla değiştirdiğinizi varsayıyorum SurveyAnswerReport. Tam nitelikli sınıf adını belirtmeniz gerekir.
Bunti

8
Fasulye sınıfı tam olarak nitelenmelidir, yani tam paket adını içermelidir. Gibi bir şey com.domain.dto.SurveyAnswerReport.
manish

2
Ben 'java.lang.IllegalArgumentException var: PersistentEntity null olmamalı `i ne biz dönüş özel türünü çalıştığınızda! JpaRepository? Kaçırdığım bazı yapılandırma var mı?
marioosh

1
Yerel sorgu istisnası kullanılırken şunu söylüyor: iç içe geçmiş istisna java.lang.IllegalArgumentException: Yönetilen bir tür değil: sınıf ... Bu neden gerçekleştirilmeli?
Mikheil Zhghenti

20

Bu SQL sorgusu, List <Object []> döndürür.

Bunu şu şekilde yapabilirsiniz:

 @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;
     }
 }

1
bu soruya cevabınız için teşekkürler. Keskin ve netti
Dheeraj R

@manish Teşekkürler, gece uykumu kurtardınız, yönteminiz bir cazibe gibi çalıştı !!!!!!!
Vineel

15

Bunun eski bir soru olduğunu ve zaten yanıtlanmış olduğunu biliyorum, ancak işte başka bir yaklaşım:

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

Cevabınızı beğendim çünkü beni yeni bir sınıf veya arayüz oluşturmaya zorlamıyor. Benim için çalıştı.
Yuri Hassle Araújo

İyi çalışıyor ancak Harita'nın? Yerine jeneriklerde kullanılmasını tercih ediyorum, çünkü Harita onlara anahtar (0) ve değer (1) olarak
erişmemize

10

Arayüzleri kullanarak daha basit kodlar elde edebilirsiniz. Oluşturucu oluşturmaya ve manuel olarak çağırmaya gerek yok

Adım 1 : Gerekli alanlarla birlikte intefrace bildirin:

public interface SurveyAnswerStatistics {

  String getAnswer();
  Long getCnt();

}

Adım 2 : Arayüzdeki alıcı ile aynı ada sahip sütunları seçin ve depo yönteminden intefrace'i döndürün:

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

}

Ne yazık ki Projeksiyonlar, GUI perspektifinden DTO nesneleri olarak kullanılamaz. Form gönderimi için DTO'ları yeniden kullanmak isteseydiniz, bunu yapamazsınız. Yine de alıcılar / ayarlayıcılar ile ayrı bir normal fasulyeye ihtiyacınız olacak. Yani bu iyi bir çözüm değil.
gen b.

Ayrıca, Anket Sınıfı eksik
Mikheil Zhghenti

6

özel bir pojo sınıfı tanımlayın sureveyQueryAnalytics deyin ve sorgu döndürülen değeri özel pojo sınıfınızda saklayın

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

1
Çözüm daha iyidir veya resmi belgedeki projeksiyonu
Ninja

3

Sorgu dizelerinde java türü isimlerini sevmiyorum ve bunu belirli bir kurucu ile ele alıyorum. Spring JPA, HashMap parametresinde sorgu sonucu olan yapıcıyı örtük olarak çağırır:

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

Kodun @ Getter çözümlemesi için Lombok'a ihtiyacı var


@Getter, nesne türü için olmadığı için kodu çalıştırmadan önce bir hata gösteriyor
user666

Lombok'a ihtiyaç var. Koda bir dipnot ekledim.
dwe

1

Bu sorunu az önce çözdüm:

  • Sınıf tabanlı Projeksiyonlar, sorgu native ( @Query(value = "SELECT ...", nativeQuery = true)) ile çalışmaz, bu nedenle arabirimi kullanarak özel DTO tanımlamanızı öneririm.
  • DTO kullanmadan önce sorgunun sözdizimsel olarak doğru olup olmadığını doğrulamalısınız

1

Yerel bir sorguyu eşlemek için özel DTO (arabirim) kullandım - en esnek yaklaşım ve yeniden düzenleme güvenli.

Bununla ilgili yaşadığım sorun - şaşırtıcı bir şekilde, arayüzdeki alanların sırası ve sorgudaki sütunların önemi. Arayüz alıcılarını alfabetik olarak sıralayarak ve ardından sorgudaki sütunları aynı şekilde sıralayarak çalıştırdım.


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

}

Yukarıdaki kod benim için çalıştı.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.