簡體   English   中英

Hibernate:將 JPQL 查詢投影到 DTO 問題

[英]Hibernate: projection JPQL query to DTO issue

首先,我將列出我在查詢中使用的三個模型

產品實體:

@Entity
@Table(name = "product")
public class ProductEntity extends BaseEntity {
  //some fields

   @ManyToOne(fetch = FetchType.LAZY)
   @JoinColumn(name = "owner_id")
   private PartnerEntity owner;

   @OneToMany(
            mappedBy = "product",
            fetch = FetchType.LAZY
    )
    private List<StockProductInfoEntity> stocks;
 }

合作伙伴實體:

@Entity
@Table(name = "partner")
public class PartnerEntity extends AbstractDetails {
    //some fields

    @OneToMany(
            mappedBy = "owner",
            fetch = FetchType.LAZY
    )
    private List<ProductEntity> products;
 }

和 StockProductInfoEntity:

@Entity
@Table(name = "stock_product")
public class StockProductInfoEntity extends BaseEntity {
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "product_id")
    private ProductEntity product;

    //other fields
    @Column(name = "rest")
    private int rest;
}

我想與合作伙伴一起從數據庫產品中獲取 + 計算所有股票的數量。 為方便起見,我創建了一個簡單的 DTO:

@Getter
@AllArgsConstructor
public class ProductCountDTO {
    private ProductEntity productEntity;
    private int count;

    //hack for hibernate
    public ProductCountDTO(ProductEntity productEntity, long count) {
        this.productEntity = productEntity;
        this.count = (int) count;
    }
}

並在 JPA 存儲庫中編寫 JPQL 查詢:

@Query("select new ru.oral.market.persistence.entity.product.util.ProductCountDTO(p, sum(stocks.rest))"+ 
            " from ProductEntity p" +
            " join fetch p.owner owner" +
            " join p.stocks stocks" +
            " where p.id = :id" +
            " group by p, owner")
    Optional<ProductCountDTO> findProductWithCount(@Param("id") long id);

但是由於查詢驗證問題,我的應用程序甚至沒有啟動。 我收到這條消息:

引起:org.hibernate.QueryException: 查詢指定的連接獲取,但選擇列表中不存在獲取的關聯的所有者

很奇怪,但我嘗試替換 join fetch -> join。 我明白為什么我會收到這個錯誤,hibernate 對數據庫進行了這樣的查詢:

select
            productent0_.id as col_0_0_,
            sum(stocks2_.rest) as col_1_0_ 
        from
            product productent0_ 
        inner join
            partner partnerent1_ 
                on productent0_.owner_id=partnerent1_.user_id 
        inner join
            stock_product stocks2_ 
                on productent0_.id=stocks2_.product_id 
        where
            productent0_.id=? 
        group by
            productent0_.id ,
            partnerent1_.user_id

但是為什么他只需要產品 ID 而沒有其他東西呢? 此查詢與元組工作並從產品和合作伙伴獲取所有字段

  @Query("select p, sum(stocks.rest) from ProductEntity p" +
            " join fetch p.owner owner" +
            " join p.stocks stocks" +
            " where p.id = :id" +
            " group by p, owner")
    Optional<Tuple> findProductWithCount(@Param("id") long id);

這產生了我想要的本機查詢:

select
            productent0_.id as col_0_0_,
            sum(stocks2_.rest) as col_1_0_,
            partnerent1_.user_id as user_id31_12_1_,
            productent0_.id as id1_14_0_,
            productent0_.brand_id as brand_i17_14_0_,
            productent0_.commission_volume as commissi2_14_0_,
            productent0_.created as created3_14_0_,
            productent0_.description as descript4_14_0_,
            productent0_.height as height5_14_0_,
            productent0_.length as length6_14_0_,
            productent0_.long_description as long_des7_14_0_,
            productent0_.name as name8_14_0_,
            productent0_.old_price as old_pric9_14_0_,
            productent0_.owner_id as owner_i18_14_0_,
            productent0_.pitctures as pitctur10_14_0_,
            productent0_.price as price11_14_0_,
            productent0_.status as status12_14_0_,
            productent0_.updated as updated13_14_0_,
            productent0_.vendor_code as vendor_14_14_0_,
            productent0_.weight as weight15_14_0_,
            productent0_.width as width16_14_0_,
            partnerent1_.about_company as about_co1_12_1_,
            partnerent1_.bik as bik2_12_1_,
            partnerent1_.bank_inn as bank_inn3_12_1_,
            partnerent1_.bank_kpp as bank_kpp4_12_1_,
            partnerent1_.bank as bank5_12_1_,
            partnerent1_.bank_address as bank_add6_12_1_,
            partnerent1_.checking_account as checking7_12_1_,
            partnerent1_.correspondent_account as correspo8_12_1_,
            partnerent1_.company_name as company_9_12_1_,
            partnerent1_.company_inn as company10_12_1_,
            partnerent1_.company_kpp as company11_12_1_,
            partnerent1_.ogrn as ogrn12_12_1_,
            partnerent1_.okato as okato13_12_1_,
            partnerent1_.actual_address as actual_14_12_1_,
            partnerent1_.director as directo15_12_1_,
            partnerent1_.full_name as full_na16_12_1_,
            partnerent1_.legal_address as legal_a17_12_1_,
            partnerent1_.short_name as short_n18_12_1_,
            partnerent1_.country as country19_12_1_,
            partnerent1_.discount_conditions as discoun20_12_1_,
            partnerent1_.discounts as discoun21_12_1_,
            partnerent1_.logo as logo22_12_1_,
            partnerent1_.min_amount_order as min_amo23_12_1_,
            partnerent1_.min_shipment as min_shi24_12_1_,
            partnerent1_.min_sum_order as min_sum25_12_1_,
            partnerent1_.own_delivery as own_del26_12_1_,
            partnerent1_.own_production as own_pro27_12_1_,
            partnerent1_.phones as phones28_12_1_,
            partnerent1_.return_information as return_29_12_1_,
            partnerent1_.site as site30_12_1_ 
        from
            product productent0_ 
        inner join
            partner partnerent1_ 
                on productent0_.owner_id=partnerent1_.user_id 
        inner join
            stock_product stocks2_ 
                on productent0_.id=stocks2_.product_id 
        where
            productent0_.id=? 
        group by
            productent0_.id ,
            partnerent1_.user_id

但是不是很方便。 為什么 DTO 投影不能正常工作,但元組工作正常?

因為這就是 Hibernate 當前的實現方式。

因為您在 DTO 投影中使用了一個實體,顧名思義,它應該用於 DTO,而不是實體,Hibernate 將假設您希望按標識符進行 GROUP BY,因為它不應該對所有實體屬性進行 GROUP BY。

Tuple壞了,它只能在 MySQL 中工作,但不能在 Oracle 或 PostgreSQL 中工作,因為您的聚合查詢選擇了 GROUP BY 子句中不存在的列。

但是,根據 JPA 規范並不要求這樣做。 盡管如此,您仍然應該提供一個復制測試用例並打開一個問題,以便在這兩種情況下行為是相同的。

無論如何,一旦修復,它仍然是 GROUP BY 標識符。 如果您還想選擇實體和分組依據,則必須使用本機 SQL 查詢和Hibernate ResultTransformerResultSet轉換為對象圖。

此外,獲取實體和聚合是一種代碼味道。 很可能,您需要 DTO 投影或只讀視圖。

只有當您想修改它們時才應該獲取實體。 否則,DTO 投影也更高效、更直接。

由於 Vlad 已經解釋了原因,我將專注於替代解決方案。 必須在 SELECT 子句和 GROUP BY 子句中指定您真正感興趣的所有屬性是一項大量工作。 如果您在 Hibernate 之上使用了 Blaze-Persistence Entity Views,這可能如下所示

@EntityView(ProductEntity.class)
public interface ProductCountDTO {
    // Or map the ProductEntity itself if you like..
    @Mapping("this")
    ProductView getProduct();
    @Mapping("sum(stocks.rest)")
    int getCount();
}

@EntityView(ProductEntity.class)
public interface ProductView {
    // Whatever mappings you like
}

通過 Spring Data 或 DeltaSpike Data 集成,您甚至可以像這樣使用它

Optional<ProductCountDTO> findById(long id);

它將產生一個 JPQL 查詢,如下所示

SELECT
  p /* All the attributes you map in ProductView  */,
  sum(stocks_1.rest)
FROM
  ProductEntity p
LEFT JOIN
  p.stocks stocks_1
GROUP BY
  p /* All the attributes you map in ProductView */

也許試一試? https://github.com/Blazebit/blaze-persistence#entity-view-usage

神奇的是,如果至少使用了一個聚合函數,Blaze-Persistence 會在遇到聚合函數時自動處理 GROUP BY,將您使用的每個非聚合表達式放入 GROUP BY 子句中。 當使用實體視圖而不是直接使用實體時,您將不會面臨連接提取問題,因為實體視圖只會將您實際映射的字段放入 JPQL 和 SQL 的結果 SELECT 子句中。 即使您直接或通過 ProductCountDTO 使用實體,后台使用的查詢構建器也會優雅地處理實體類型的選擇,就像您對 Hibernate 所期望的那樣。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM