当前位置: 首页 > 知识库问答 >
问题:

Hibernate:将JPQL查询投影到DTO问题

阎建德
2023-03-14

首先,我将列出我在查询中使用的三个模型

产品实体:

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

但我的应用程序甚至没有启动,因为查询验证有问题。我收到以下消息:

原因:组织。冬眠QueryException:查询指定了联接提取,但提取的关联的所有者不在选择列表中

很奇怪,但我试着换了-

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投影不能正常工作,但元组可以正常工作?

共有2个答案

潘嘉佑
2023-03-14

由于Vlad已经解释了原因,我将专注于另一种解决方案。必须在SELECT子句和GROUP BY子句中指定您真正感兴趣的所有属性是一项繁重的工作。如果您在Hibernate之上使用Blaze-持久性实体视图,这可能如下所示

@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数据或DeltaSpike数据集成,您甚至可以这样使用它

<代码>可选

它将生成如下所示的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中所期望的那样。

衡丰茂
2023-03-14

因为这就是Hibernate目前的实现方式。

因为您在DTO投影中使用了一个实体,顾名思义,它应该用于DTO,而不是实体,Hibernate将假定您要按标识符分组,因为它不应该按所有实体属性分组。

元组被破坏了,它只在MySQL中工作,而在Oracle或PostgreSQL中不工作,因为聚合查询选择了GROUP BY子句中不存在的列。

然而,根据JPA规范,这并不是必须的。尽管如此,您仍然应该提供一个复制测试用例并打开一个问题,以便这两种情况下的行为相同。

无论如何,一旦修复,它仍将按标识符分组。如果还想选择实体和分组依据,则必须使用本机SQL查询和Hibernate结果转换程序将结果集转换为对象图。

此外,获取实体和聚合是一种代码气味。很可能,您需要DTO投影或只读视图。

仅当您要修改实体时,才应获取实体。否则,DTO投影更有效,也更直接。

 类似资料:
  • 问题内容: 在我的DAO层中,我有一个类似的Find函数 因此,为了读取数据,我必须使用Loop(带有) 我的问题是:是否有任何api框架可以轻松地将其转换为对象列表(例如DQCategoryDTO),而无需使用任何循环,迭代器和调用setter / getter来填充值? 问题答案: 您可以使用ResultTransformer,它可以从别名转换为bean(DTO)属性。对于用法,您可以在此处的

  • 我正在使用JPA投影,但当我的查询包含子查询时,它就失败了。例如: 下面是投影的界面: 和存储库: 关于如何使用JPA投影的子查询有什么想法吗? 谢谢。

  • 只需面对N 1查询问题与这样的Spring数据存储库 我在日志中看到这样一个查询 Hibernate:选择todo0\u0。id为col_0_0_从todos todo0_其中todo0_。用户标识=?] 和N个这样的查询 Hibernate:选择todo0\u0。id为id1\u 0\u 0\u,todo0\u。描述为描述2\u 0\u 0\u,todo0\u。目标日期为目标日期第3天、第0天、

  • 我有一个以下JPQL查询 用于转换为以下SQL查询 过去一切都是魅力。但后来我不得不升级一些依赖项版本,以匹配其他模块的依赖项。这时这个查询中断了,现在它被转换成了这个SQL。 正如你所料,这是行不通的。我得到以下错误 我试图降低多个依赖项的级别,包括hibernate和spring数据jpa相关的依赖项,但没有起到任何作用。如果有人能帮我解决这个问题,我将不胜感激。 非常感谢。