From c06bf74c93eb221bee7b62a33ea645f7efa3ac73 Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Fri, 11 Apr 2025 12:04:52 +0200 Subject: [PATCH] WIP JPA Criteria aliasing fix --- .../jpa/spi/AliasInjectingTransformer.java | 30 ++++ .../spi/CriteriaQueryTupleTransformer.java | 8 ++ .../query/internal/AbstractProducedQuery.java | 18 ++- .../jpa/test/criteria/CriteriaAliasTest.java | 135 ++++++++++++++++++ 4 files changed, 190 insertions(+), 1 deletion(-) create mode 100644 hibernate-core/src/main/java/org/hibernate/jpa/spi/AliasInjectingTransformer.java create mode 100644 hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/CriteriaAliasTest.java diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/spi/AliasInjectingTransformer.java b/hibernate-core/src/main/java/org/hibernate/jpa/spi/AliasInjectingTransformer.java new file mode 100644 index 000000000000..768d198037dd --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/jpa/spi/AliasInjectingTransformer.java @@ -0,0 +1,30 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.jpa.spi; + +import org.hibernate.transform.BasicTransformerAdapter; +import org.hibernate.transform.ResultTransformer; + +public class AliasInjectingTransformer extends BasicTransformerAdapter { + + private final String[] aliases; + private final ResultTransformer resultTransformer; + + public AliasInjectingTransformer(String[] aliases, ResultTransformer resultTransformer) { + this.aliases = aliases; + this.resultTransformer = resultTransformer; + } + + public String[] getAliases() { + return aliases; + } + + @Override + public Object transformTuple(Object[] tuple, String[] aliases) { + return resultTransformer.transformTuple(tuple, this.aliases); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/spi/CriteriaQueryTupleTransformer.java b/hibernate-core/src/main/java/org/hibernate/jpa/spi/CriteriaQueryTupleTransformer.java index 41c3f1d9de5b..81dc0d6e003d 100644 --- a/hibernate-core/src/main/java/org/hibernate/jpa/spi/CriteriaQueryTupleTransformer.java +++ b/hibernate-core/src/main/java/org/hibernate/jpa/spi/CriteriaQueryTupleTransformer.java @@ -28,6 +28,14 @@ public CriteriaQueryTupleTransformer(List valu this.tupleElements = tupleElements; } + public String[] getAliases() { + final String[] aliases = new String[this.tupleElements.size()]; + for (int i = 0; i < aliases.length; i++) { + aliases[i] = ((TupleElement) this.tupleElements.get(i)).getAlias(); + } + return aliases; + } + @Override public Object transformTuple(Object[] tuple, String[] aliases) { final Object[] valueHandlerResult; diff --git a/hibernate-core/src/main/java/org/hibernate/query/internal/AbstractProducedQuery.java b/hibernate-core/src/main/java/org/hibernate/query/internal/AbstractProducedQuery.java index 81f3f209088a..a599e316135d 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/internal/AbstractProducedQuery.java +++ b/hibernate-core/src/main/java/org/hibernate/query/internal/AbstractProducedQuery.java @@ -69,6 +69,8 @@ import org.hibernate.jpa.internal.util.ConfigurationHelper; import org.hibernate.jpa.internal.util.FlushModeTypeHelper; import org.hibernate.jpa.internal.util.LockModeTypeHelper; +import org.hibernate.jpa.spi.AliasInjectingTransformer; +import org.hibernate.jpa.spi.CriteriaQueryTupleTransformer; import org.hibernate.property.access.spi.BuiltInPropertyAccessStrategies; import org.hibernate.property.access.spi.Getter; import org.hibernate.property.access.spi.PropertyAccess; @@ -929,7 +931,21 @@ else if ( retType.isArray() ) { @Override @SuppressWarnings("unchecked") public QueryImplementor setResultTransformer(ResultTransformer transformer) { - this.resultTransformer = transformer; + if ( this.resultTransformer instanceof CriteriaQueryTupleTransformer ) { + this.resultTransformer = new AliasInjectingTransformer( + ((CriteriaQueryTupleTransformer) this.resultTransformer).getAliases(), + transformer + ); + } + else if ( this.resultTransformer instanceof AliasInjectingTransformer ) { + this.resultTransformer = new AliasInjectingTransformer( + ((AliasInjectingTransformer) this.resultTransformer).getAliases(), + transformer + ); + } + else { + this.resultTransformer = transformer; + } return this; } diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/CriteriaAliasTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/CriteriaAliasTest.java new file mode 100644 index 000000000000..d5650803282a --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/CriteriaAliasTest.java @@ -0,0 +1,135 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.jpa.test.criteria; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutput; +import java.io.ObjectOutputStream; +import java.util.Arrays; +import java.util.List; +import javax.persistence.Tuple; +import javax.persistence.TypedQuery; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Root; +import javax.persistence.metamodel.EntityType; + +import org.hibernate.CacheMode; +import org.hibernate.ScrollMode; +import org.hibernate.ScrollableResults; +import org.hibernate.Session; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; +import org.hibernate.jpa.test.callbacks.RemoteControl; +import org.hibernate.jpa.test.callbacks.Television; +import org.hibernate.jpa.test.callbacks.VideoSystem; +import org.hibernate.jpa.test.inheritance.Fruit; +import org.hibernate.jpa.test.inheritance.Strawberry; +import org.hibernate.jpa.test.metamodel.Address; +import org.hibernate.jpa.test.metamodel.Alias; +import org.hibernate.jpa.test.metamodel.Country; +import org.hibernate.jpa.test.metamodel.CreditCard; +import org.hibernate.jpa.test.metamodel.Customer; +import org.hibernate.jpa.test.metamodel.Info; +import org.hibernate.jpa.test.metamodel.LineItem; +import org.hibernate.jpa.test.metamodel.Order; +import org.hibernate.jpa.test.metamodel.Phone; +import org.hibernate.jpa.test.metamodel.Product; +import org.hibernate.jpa.test.metamodel.ShelfLife; +import org.hibernate.jpa.test.metamodel.Spouse; +import org.hibernate.query.spi.QueryImplementor; +import org.hibernate.transform.ResultTransformer; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.transaction.TransactionUtil; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertThat; + +public class CriteriaAliasTest extends BaseEntityManagerFunctionalTestCase { + @Override + public Class[] getAnnotatedClasses() { + return new Class[] { + Customer.class, + Alias.class, + Phone.class, + Address.class, + Country.class, + CreditCard.class, + Info.class, + Spouse.class, + LineItem.class, + Order.class, + Product.class, + ShelfLife.class, + // @Inheritance + Fruit.class, + Strawberry.class, + // @MappedSuperclass + VideoSystem.class, + Television.class, + RemoteControl.class + }; + } + + @Before + public void setUp(){ + TransactionUtil.doInJPA( this::entityManagerFactory, entityManager -> { + Customer customer = new Customer(); + customer.setId( "id" ); + customer.setName( " David R. Vincent " ); + entityManager.persist( customer ); + } ); + } + + @Test + @TestForIssue(jiraKey = "HHH-") + public void testAliasJPACriteriaQuery() { + TransactionUtil.doInJPA( this::entityManagerFactory, entityManager -> { + final CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder(); + final CriteriaQuery query = criteriaBuilder.createTupleQuery(); + final Root from = query.from( Customer.class ); + query.multiselect( + from.get( "id" ), + from.get( "name" ).alias( "my.name" ), + from.get( "age" ).alias( "alter" ) + ); + + final MyResultTransformer transformer = new MyResultTransformer(); + entityManager.createQuery( query ) + .unwrap( QueryImplementor.class ) + .setResultTransformer( transformer ) + .getResultList(); + assertThat( transformer.aliases, is(notNullValue()) ); + assertThat( transformer.aliases.length, is( 3 ) ); + assertThat( transformer.aliases[0], is(nullValue()) ); + assertThat( transformer.aliases[1], is( "my.name" ) ); + assertThat( transformer.aliases[2], is( "alter" ) ); + } ); + } + + private static class MyResultTransformer implements ResultTransformer { + String[] aliases; + + @Override + public Object transformTuple(Object[] tuple, String[] aliases) { + this.aliases = aliases; + return tuple; + } + + @Override + public List transformList(List collection) { + return collection; + } + } +}