英文:
Joining CTEs in Hibernate 6.2
问题
Hibernate 6.2引入了CTE支持,但如何在单个HQL查询中使用多个CTE尚不清楚。
所以,假设我有一个名为City的实体以及以下的CTE查询:
TypedQuery<Integer> typedQuery = em.createQuery(
"""
with max_cities as (
SELECT id id from City c ORDER BY population DESC
LIMIT 20
), min_cities as (
SELECT id id from City c ORDER BY population ASC
LIMIT 20
)
SELECT m1.id from max_cities m1 join min_cities m2
on m1.id = m2.id
""",
Integer.class);
它包含两个CTE(min_cities和max_cities)。Hibernate对此没有抱怨。但错误消息在这里非常明确:
Caused by: java.lang.IllegalArgumentException: Could not resolve entity reference: min_cities
at org.hibernate.metamodel.model.domain.internal.JpaMetamodelImpl.resolveHqlEntityReference(JpaMetamodelImpl.java:166)
所以Hibernate允许在FROM子句中使用第一个CTE,但在JOIN子句中期望实体(而不是CTE)。
然而,MySQL文档表明连接两个CTE是合法的:
WITH
cte1 AS (SELECT a, b FROM table1),
cte2 AS (SELECT c, d FROM table2)
SELECT b, d FROM cte1 JOIN cte2
WHERE cte1.a = cte2.c;
所以,在Hibernate中支持连接CTE吗?
英文:
Hibernate 6.2 introduced CTE support but it's unclear how to use multiple CTEs in the single HQL query.
So let's say I have City entity and the following CTE query:
TypedQuery<Integer> typedQuery = em.createQuery(
"""
with max_cities as (
SELECT id id from City c ORDER BY population DESC
LIMIT 20
), min_cities as (
SELECT id id from City c ORDER BY population ASC
LIMIT 20
)
SELECT m1.id from max_cities m1 join min_cities m2
on m1.id = m2.id
""",
Integer.class);
It contains two CTEs (min_cities and max_cities). And Hibernate doesn't complain about that. But the error message is pretty clear here:
Caused by: java.lang.IllegalArgumentException: Could not resolve entity reference: min_cities
at org.hibernate.metamodel.model.domain.internal.JpaMetamodelImpl.resolveHqlEntityReference(JpaMetamodelImpl.java:166)
So Hibernate allows to use first CTE in FROM clause but expects entity (and not CTE) in the JOIN clause.
However MySQL documentation states that joining two CTEs is pretty legal:
WITH
cte1 AS (SELECT a, b FROM table1),
cte2 AS (SELECT c, d FROM table2)
SELECT b, d FROM cte1 JOIN cte2
WHERE cte1.a = cte2.c;
So is joining CTEs supported in Hibernate ?
答案1
得分: 2
这似乎是在 ExpectingEntityJoinDelegate#consumeIdentifier 中的一个错误:
@Override
public void consumeIdentifier(String identifier, boolean isTerminal, boolean allowReuse) {
if ( path.length() != 0 ) {
path.append( '.' );
}
path.append( identifier );
if ( isTerminal ) {
final String fullPath = path.toString();
final EntityDomainType<?> joinedEntityType = creationState.getCreationContext()
.getJpaMetamodel()
.resolveHqlEntityReference( fullPath );
// 这里的实现假设 resolveHqlEntityReference 可能返回 null,
// 但实际上它从不返回 null。最可能应该使用 getHqlEntityReference
if ( joinedEntityType == null ) {
final SqmCteStatement<?> cteStatement = creationState.findCteStatement( fullPath );
if ( cteStatement != null ) {
join = new SqmCteJoin<>( cteStatement, alias, joinType, sqmRoot );
creationState.getCurrentProcessingState().getPathRegistry().register( join );
return;
}
throw new SemanticException( "无法解析连接路径 - " + fullPath );
}
顺便说一下,交叉连接(cross join)的确按预期工作,即:
"""
with max_cities as (
SELECT id id from City c ORDER BY population DESC
LIMIT 20
), min_cities as (
SELECT id id from City c ORDER BY population ASC
LIMIT 20
)
SELECT m1.id from max_cities m1, min_cities m2
where m1.id = m2.id
"""
英文:
That looks like a bug in ExpectingEntityJoinDelegate#consumeIdentifier:
@Override
public void consumeIdentifier(String identifier, boolean isTerminal, boolean allowReuse) {
if ( path.length() != 0 ) {
path.append( '.' );
}
path.append( identifier );
if ( isTerminal ) {
final String fullPath = path.toString();
final EntityDomainType<?> joinedEntityType = creationState.getCreationContext()
.getJpaMetamodel()
.resolveHqlEntityReference( fullPath );
// here impl assumes that resolveHqlEntityReference may return nulls,
// however it never does that. Most probably getHqlEntityReference
// should be used instead
if ( joinedEntityType == null ) {
final SqmCteStatement<?> cteStatement = creationState.findCteStatement( fullPath );
if ( cteStatement != null ) {
join = new SqmCteJoin<>( cteStatement, alias, joinType, sqmRoot );
creationState.getCurrentProcessingState().getPathRegistry().register( join );
return;
}
throw new SemanticException( "Could not resolve join path - " + fullPath );
}
BTW, cross join does work as expected, i.e.:
"""
with max_cities as (
SELECT id id from City c ORDER BY population DESC
LIMIT 20
), min_cities as (
SELECT id id from City c ORDER BY population ASC
LIMIT 20
)
SELECT m1.id from max_cities m1, min_cities m2
where m1.id = m2.id
"""
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论