我正在将一个NamedQuery重写为hibernate-jpa-2.1中的CriteriaQuery。原始NamedQuery包含 order by 子句,该子句引用别名子查询。
select
new ItemDto (
item.id,
item.number,
(select count(*) from ClickEntity as click where click.item.id = item.id) as clickCount
)
from ItemEntity as item
order by clickCount desc
我找不到任何方法来使用别名来引用clickCount字段,所以我想我也可以在两个地方使用子查询:
public List<ItemDto> getItems() {
...
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<ItemDto> query = criteriaBuilder.createQuery(ItemDto.class);
Root<ItemEntity> item = query.from(ItemEntity.class);
query
.select(
cb.construct(ItemDto.class,
item.get("id"),
item.get("number"),
getClickCount(cb, query, item).getSelection()
)
)
.orderBy(cb.desc(getClickCount(cb, query, item).getSelection()))
TypedQuery<ItemDto> typedQuery = entityManager.createQuery(query);
return typedQuery.getResultList();
}
private Subquery<Long> getClickCount(CriteriaBuilder cb, CriteriaQuery<ItemDto> query, Root<ItemEntity> item) {
Subquery<Long> subquery = query.subquery(Long.class);
Root<ClickEntity> click = subquery.from(ClickEntity.class)
return subquery
.select(cb.count(click.get("id")))
.where(cb.equal(click.get("item").get("id"), item.get("id")));
}
但是,在调用getItems()时,Hibernate会在创建TypedQuery时抛出以下异常:
org.hibernate.hql.internal.ast.QuerySyntaxException: unexpected AST node: query [...]
解析后的查询如下所示:
select new ItemDto(
generatedAlias0.id,
generatedAlias0.number,
(select count(generatedAlias1.id) from ClickEntity as generatedAlias1 where( generatedAlias1.item.id=generatedAlias0.id ))
)
from ItemEntity as generatedAlias0
order by
(select count(generatedAlias2.id) from ClickEntity as generatedAlias2 where( generatedAlias2.item.id=generatedAlias0.id )) desc
尽管抛出了错误,但这个查询对我来说还不错。我已经在没有order by子句的情况下测试了它,然后它按预期工作,所以错误肯定是由该子句引起的。但是,由于Subquery显然有效,我很难弄清问题是什么。
我尝试/考虑的内容:
所以,我想知道,这种方法是否真的得到了Hibernate的支持,或者我错过了一个(可能更简单的)替代方法来使用Subquery的结果作为排序参数?
答案 0 :(得分:0)
我找到了解决这个问题的两个选项,两者都会产生不同的结果。请注意,由于在select子句中使用了聚合函数,因此对于未通过聚合选择的每个列,都需要 group by 子句。
为查询创建额外的根将导致交叉连接。结合where子句,这将导致内部联接,同时您仍然可以访问根目录中的字段。添加更多where子句允许进一步过滤。
y
由于这是一个内部联接,因此此方法仅选择单击表中的单击实体引用的项目实体。换句话说,未选中具有0次点击的项目。如果需要过滤没有点击的项目,这是一种有效的方法。
通过将@OneToMany字段添加到引用click实体的ItemEntity,可以创建左连接。首先,更新ItemEntity:
public List<ItemDto> getItems() {
...
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<ItemDto> query = criteriaBuilder.createQuery(ItemDto.class);
Root<ItemEntity> item = query.from(ItemEntity.class);
//Extra root here
Root<ClickEntity> click = query.from(ClickEntity.class);
query
.select(
cb.construct(ItemDto.class,
item.get("id"),
item.get("number"),
cb.count(click.get("id"))
)
)
//Required to make the cross join into an inner join
.where(cb.equal(item.get("id"), click.get("item").get("id")))
//Required because an aggregate function is used in the select clause
.groupBy(item.get("id"), item.get("number"))
//Possibility to refer to root
.orderBy(cb.count(click.get("id")));
...
}
现在,您可以让JPA为您执行连接,并使用连接来引用ClickEntity中的字段。此外,您可以使用join.on(...)为连接添加额外条件,并使用query.having()将允许您过滤掉没有点击的项目,如第一种方法。
@Entity
public class ItemEntity {
...
@OneToMany(cascade = CascadeType.ALL)
//The field in the click entity referring to the item
@JoinColumn(name="itemid")
private List<ClickEntity> clicks;
...
}
请注意不要内嵌点击变量,因为这会有效地加入项目表上的点击表。
最后,第二种方法对我的情况最有效,因为我想要没有点击的项目,并且找不到直接的方法将交叉连接变成左外连接。