首先,我将列出我在查询中使用的三个模型
ProductEntity:
@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;
}
PartnerEntity:
@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:查询指定的连接 获取,但获取的关联的所有者不存在 选择列表
很奇怪,但我尝试替换了连接提取 - &gt;加入。 我理解为什么我得到这个错误,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而没有别的? 这个查询与Tuple一起工作,并从产品和合作伙伴
获取所有字段 @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投影不能正常工作,但元组工作正常?
答案 0 :(得分:2)
因为这就是Hibernate目前的实现方式。
因为您在DTO Projection中使用了一个实体,顾名思义,它应该用于DTO,而不是实体,Hibernate会假设您希望GROUP BY由标识符,因为它不应该是GROUP BY all实体属性。
Tuple
已被破坏,它只能在MySQL中运行,但不能在Oracle或PostgreSQL中运行,因为您的聚合查询会选择GROUP BY子句中不存在的列。
然而,根据JPA规范,这不是要求工作。尽管如此,您仍然应该provide a replicating test case并打开一个问题,以便两种情况下的行为相同。
无论如何,一旦修复,它仍将是GROUP BY标识符。如果您还要选择实体和分组,则必须使用本机SQL查询和Hibernate ResultTransformer
将ResultSet
转换为对象图。
更多,获取实体和聚合是一种代码味道。最有可能的是,您需要一个只读视图的DTO投影。
在我的书High-Performance Java Persistence中,我解释说只有在你想修改它们时才能获取实体。否则,DTO投影也会更有效,更直接。
答案 1 :(得分:1)
由于弗拉德已经解释了原因,我将专注于另一种解决方案。必须在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一样。