Use named selection for projections to avoid AliasCollisionException.

We now use explicitly named selection items when selecting items from a projection selection to avoid name clashes through implicit naming.

Signed-off-by: oualid.bouh <oualid.bouh@kleegroup.com>
Closes #4135
Original pull request: #4136
This commit is contained in:
oualid.bouh
2025-12-29 15:47:24 +01:00
committed by Mark Paluch
parent 4a606f62ca
commit 12b9ebe01c
2 changed files with 47 additions and 1 deletions
@@ -69,6 +69,7 @@ import org.springframework.util.Assert;
* @author Greg Turnquist
* @author Christoph Strobl
* @author Jinmyeong Kim
* @author Oualid Bouh
*/
public class JpaQueryCreator extends AbstractQueryCreator<String, JpqlQueryBuilder.Predicate>
implements JpqlQueryCreator {
@@ -299,7 +300,7 @@ public class JpaQueryCreator extends AbstractQueryCreator<String, JpqlQueryBuild
List<JpqlQueryBuilder.Expression> paths = new ArrayList<>(requiredSelection.size());
for (String selection : requiredSelection) {
paths.add(JpqlUtils.toExpressionRecursively(metamodel, entity, entityType,
PropertyPath.from(selection, returnedType.getDomainType()), true));
PropertyPath.from(selection, returnedType.getDomainType()), true).as(selection));
}
JpqlQueryBuilder.Expression distance = null;
@@ -65,6 +65,7 @@ import org.springframework.data.util.Lazy;
*
* @author Christoph Strobl
* @author Mark Paluch
* @author Oualid Bouh
*/
class JpaQueryCreatorTests {
@@ -76,6 +77,9 @@ class JpaQueryCreatorTests {
static List<JpqlQueryTemplates> ignoreCaseTemplates = List.of(JpqlQueryTemplates.LOWER, JpqlQueryTemplates.UPPER);
private static final TestMetaModel ORDER_WITH_RELATIONS = TestMetaModel.hibernateModel(
OrderWithRelations.class, Customer.class, Supplier.class);
@Test // GH-3588
void simpleProperty() {
@@ -1131,4 +1135,45 @@ class JpaQueryCreatorTests {
return queryCreator(partTree, returnedType, metamodel, queryTemplates, parameterAccessor.get());
}
}
@Test // GH-4135
void interfaceProjectionWithMultipleJoinsShouldGenerateUniqueAliases() {
queryCreator(ORDER_WITH_RELATIONS)
.forTree(OrderWithRelations.class, "findProjectionById")
.returning(OrderSummaryProjection.class)
.withParameters(1L)
.as(QueryCreatorTester::create)
.expectJpql(
"SELECT o.id id, c.id customerId, s.id supplierId, c.name customerName, s.name supplierName FROM %s o LEFT JOIN o.customer c LEFT JOIN o.supplier s WHERE o.id = ?1",
DefaultJpaEntityMetadata.unqualify(OrderWithRelations.class))
.validateQuery();
}
@jakarta.persistence.Entity
static class OrderWithRelations {
@Id Long id;
@ManyToOne Customer customer;
@ManyToOne Supplier supplier;
}
@jakarta.persistence.Entity
static class Customer {
@Id Long id;
String name;
}
@jakarta.persistence.Entity
static class Supplier {
@Id Long id;
String name;
}
interface OrderSummaryProjection {
Long getId();
Long getCustomerId(); // → customer.id
Long getSupplierId(); // → supplier.id
String getCustomerName(); // → customer.name
String getSupplierName(); // → supplier.name
}
}