From ac845bca3156b587b5a85309a17f5d3cb7667a7c Mon Sep 17 00:00:00 2001 From: Gavin King Date: Wed, 15 Dec 2021 00:09:45 +0100 Subject: [PATCH] impl the new overload of createNativeQuery() - and add a second new overload - tolerate non-entity classes as arguments to these methods - the overloads accept a result class, and return a typed Query --- .../engine/spi/SessionDelegatorBaseImpl.java | 10 +++ .../AbstractSharedSessionContract.java | 68 +++++++++++-------- .../org/hibernate/query/QueryProducer.java | 20 ++++++ .../query/spi/QueryProducerImplementor.java | 6 ++ .../annotations/query/QueryAndSQLTest.java | 6 +- .../converted/converter/QueryTest.java | 13 ++++ .../formula/FormulaNativeQueryTest.java | 2 +- .../resultmapping/EntityResultTests.java | 22 ++++++ .../sql/hand/query/NativeSQLQueriesTest.java | 29 +++++++- 9 files changed, 143 insertions(+), 33 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionDelegatorBaseImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionDelegatorBaseImpl.java index d881329649..a7f0d51d2d 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionDelegatorBaseImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionDelegatorBaseImpl.java @@ -511,11 +511,21 @@ public class SessionDelegatorBaseImpl implements SessionImplementor { return queryDelegate().createNativeQuery( sqlString, resultClass ); } + @Override @SuppressWarnings({"rawtypes", "unchecked"}) + public NativeQueryImplementor createNativeQuery(String sqlString, Class resultClass, String tableAlias) { + return queryDelegate().createNativeQuery( sqlString, resultClass, tableAlias ); + } + @Override @SuppressWarnings("rawtypes") public NativeQueryImplementor createNativeQuery(String sqlString, String resultSetMappingName) { return queryDelegate().createNativeQuery( sqlString, resultSetMappingName ); } + @Override @SuppressWarnings({"rawtypes", "unchecked"}) + public NativeQueryImplementor createNativeQuery(String sqlString, String resultSetMappingName, Class resultClass) { + return queryDelegate().createNativeQuery( sqlString, resultSetMappingName, resultClass ); + } + @Override public ProcedureCall createNamedStoredProcedureQuery(String name) { return delegate.createNamedStoredProcedureQuery( name ); diff --git a/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java b/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java index e3cff92106..52fde00b5e 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java @@ -692,59 +692,69 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont } } - @Override @SuppressWarnings({"rawtypes", "unchecked"}) - //note: we're doing something a bit funny here to work around - // the classing signatures declared by the supertypes - public NativeQueryImplementor createNativeQuery(String sqlString, Class resultClass) { - checkOpen(); - pulseTransactionCoordinator(); - delayedAfterCompletion(); - - try { - NativeQueryImplementor query = createNativeQuery( sqlString ); - if ( Tuple.class.equals( resultClass ) ) { - query.setTupleTransformer( new NativeQueryTupleTransformer() ); - } - else { - query.addEntity( "alias1", resultClass.getName(), LockMode.READ ); - } - return query; - } - catch (RuntimeException he) { - throw getExceptionConverter().convert( he ); - } - } - @Override @SuppressWarnings("rawtypes") public NativeQueryImplementor createNativeQuery(String sqlString, String resultSetMappingName) { checkOpen(); pulseTransactionCoordinator(); delayedAfterCompletion(); - final NativeQueryImplementor query; try { - if ( StringHelper.isNotEmpty( resultSetMappingName ) ) { + if ( StringHelper.isNotEmpty(resultSetMappingName) ) { final NamedResultSetMappingMemento resultSetMappingMemento = getFactory().getQueryEngine() .getNamedObjectRepository() - .getResultSetMappingMemento( resultSetMappingName ); + .getResultSetMappingMemento(resultSetMappingName); if ( resultSetMappingMemento == null ) { - throw new HibernateException( "Could not resolve specified result-set mapping name : " + resultSetMappingName ); + throw new HibernateException( "Could not resolve specified result-set mapping name : " + + resultSetMappingName); } - query = new NativeQueryImpl<>( sqlString, resultSetMappingMemento, this ); + return new NativeQueryImpl<>(sqlString, resultSetMappingMemento, this); } else { - query = new NativeQueryImpl<>( sqlString, this ); + return new NativeQueryImpl<>(sqlString, this); } + //TODO: why no applyQuerySettingsAndHints( query ); ??? } catch (RuntimeException he) { throw getExceptionConverter().convert( he ); } + } + @Override @SuppressWarnings({"rawtypes", "unchecked"}) + //note: we're doing something a bit funny here to work around + // the clashing signatures declared by the supertypes + public NativeQueryImplementor createNativeQuery(String sqlString, Class resultClass) { + NativeQueryImplementor query = createNativeQuery( sqlString ); + if ( Tuple.class.equals(resultClass) ) { + query.setTupleTransformer( new NativeQueryTupleTransformer() ); + } + else if ( getFactory().getMetamodel().findEntityDescriptor(resultClass)!=null ) { + query.addEntity( "alias1", resultClass.getName(), LockMode.READ ); + } return query; } + @Override @SuppressWarnings({"rawtypes", "unchecked"}) + public NativeQueryImplementor createNativeQuery(String sqlString, Class resultClass, String tableAlias) { + NativeQueryImplementor query = createNativeQuery( sqlString ); + if ( Tuple.class.equals(resultClass) ) { + query.setTupleTransformer( new NativeQueryTupleTransformer() ); + } + else if ( getFactory().getMetamodel().findEntityDescriptor(resultClass)!=null ) { + query.addEntity( tableAlias, resultClass.getName(), LockMode.READ ); + } + return query; + } + + @Override @SuppressWarnings({"rawtypes", "unchecked"}) + public NativeQueryImplementor createNativeQuery(String sqlString, String resultSetMappingName, Class resultClass) { + final NativeQueryImplementor query = createNativeQuery( sqlString, resultSetMappingName ); + if ( Tuple.class.equals( resultClass ) ) { + query.setTupleTransformer( new NativeQueryTupleTransformer() ); + } + return query; + } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // named query handling diff --git a/hibernate-core/src/main/java/org/hibernate/query/QueryProducer.java b/hibernate-core/src/main/java/org/hibernate/query/QueryProducer.java index 22e6bbce7d..f37748997c 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/QueryProducer.java +++ b/hibernate-core/src/main/java/org/hibernate/query/QueryProducer.java @@ -98,6 +98,9 @@ public interface QueryProducer { /** * Create a NativeQuery instance for the given native (SQL) query using * implicit mapping to the specified Java type. + *

+ * If the given class is an entity class, this method is equivalent to + * {@code createNativeQuery(sqlString).addEntity("alias1", resultClass)}. * * @param sqlString Native (SQL) query string * @param resultClass The Java entity type to map results to @@ -108,6 +111,23 @@ public interface QueryProducer { */ NativeQuery createNativeQuery(String sqlString, Class resultClass); + /** + * Create a NativeQuery instance for the given native (SQL) query using + * implicit mapping to the specified Java type. + *

+ * If the given class is an entity class, this method is equivalent to + * {@code createNativeQuery(sqlString).addEntity(tableAlias, resultClass)}. + * + * @param sqlString Native (SQL) query string + * @param resultClass The Java entity type to map results to + * @param tableAlias The table alias for columns in the result set + * + * @return The NativeQuery instance for manipulation and execution + * + * @see jakarta.persistence.EntityManager#createNativeQuery(String,Class) + */ + NativeQuery createNativeQuery(String sqlString, Class resultClass, String tableAlias); + /** * Create a NativeQuery instance for the given native (SQL) query using * implicit mapping to the specified Java type. diff --git a/hibernate-core/src/main/java/org/hibernate/query/spi/QueryProducerImplementor.java b/hibernate-core/src/main/java/org/hibernate/query/spi/QueryProducerImplementor.java index e4c6aceee4..8142e9d22a 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/spi/QueryProducerImplementor.java +++ b/hibernate-core/src/main/java/org/hibernate/query/spi/QueryProducerImplementor.java @@ -50,9 +50,15 @@ public interface QueryProducerImplementor extends QueryProducer { @Override NativeQueryImplementor createNativeQuery(String sqlString, Class resultClass); + @Override + NativeQueryImplementor createNativeQuery(String sqlString, Class resultClass, String tableAlias); + @Override @SuppressWarnings("rawtypes") NativeQueryImplementor createNativeQuery(String sqlString, String resultSetMappingName); + @Override + NativeQueryImplementor createNativeQuery(String sqlString, String resultSetMappingName, Class resultClass); + @Override @SuppressWarnings("rawtypes") NativeQueryImplementor getNamedNativeQuery(String name); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/query/QueryAndSQLTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/query/QueryAndSQLTest.java index 188cc01848..bee358f9e1 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/query/QueryAndSQLTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/query/QueryAndSQLTest.java @@ -99,11 +99,11 @@ public class QueryAndSQLTest { .currentDate(); String sql = String.format( - "select t.TABLE_NAME as {t.tableName}, %s as {t.daysOld} from ALL_TABLES t where t.TABLE_NAME = 'AUDIT_ACTIONS' ", + "select t.TABLE_NAME as {t.tableName}, %s as {t.daysOld} from ALL_TABLES t where t.TABLE_NAME = 'AUDIT_ACTIONS' ", dateFunctionRendered ); String sql2 = String.format( - "select TABLE_NAME as t_name, %s as t_time from ALL_TABLES where TABLE_NAME = 'AUDIT_ACTIONS' ", + "select TABLE_NAME as t_name, %s as t_time from ALL_TABLES where TABLE_NAME = 'AUDIT_ACTIONS' ", dateFunctionRendered ); @@ -111,7 +111,9 @@ public class QueryAndSQLTest { scope.inTransaction( session -> { session.createNativeQuery( sql ).addEntity( "t", AllTables.class ).list(); + List allTables = session.createNativeQuery( sql, AllTables.class, "t" ).list(); session.createNativeQuery( sql2, "all" ).list(); + List allTableNames = session.createNativeQuery( sql2, "all", String.class ).list(); NativeQuery q = session.createNativeQuery( sql2 ); q.addRoot( "t", AllTables.class ).addProperty( "tableName", "t_name" ).addProperty( "daysOld", diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/converted/converter/QueryTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/converted/converter/QueryTest.java index c5f7c89bb2..e0462f56d6 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/converted/converter/QueryTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/converted/converter/QueryTest.java @@ -81,6 +81,19 @@ public class QueryTest extends BaseNonConfigCoreFunctionalTestCase { } ); } + public void testNativeQueryResultWithResultClass() { + inTransaction( (session) -> { + final NativeQueryImplementor query = session.createNativeQuery( "select id, salary from EMP", "emp_id_salary", Object[].class ); + + final List results = query.list(); + assertThat( results ).hasSize( 1 ); + + final Object[] values = results.get( 0 ); + assertThat( values[0] ).isEqualTo( 1 ); + assertThat( values[1] ).isEqualTo( SALARY ); + } ); + } + @Test @FailureExpected( jiraKey = "HHH-14975", message = "Not yet implemented" ) @NotImplementedYet diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/formula/FormulaNativeQueryTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/formula/FormulaNativeQueryTest.java index e1e133ad22..0cfd18ab0e 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/formula/FormulaNativeQueryTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/formula/FormulaNativeQueryTest.java @@ -75,7 +75,7 @@ public class FormulaNativeQueryTest { public void testNativeQueryWithAllFields(SessionFactoryScope scope) { scope.inTransaction( session -> { - Query query = session.createNativeQuery( + Query query = session.createNativeQuery( "SELECT ft.*, abs(locationEnd - locationStart) as distance FROM foo_table ft", Foo.class ); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/resultmapping/EntityResultTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/resultmapping/EntityResultTests.java index 213bae0306..c258171488 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/query/resultmapping/EntityResultTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/resultmapping/EntityResultTests.java @@ -171,6 +171,28 @@ public class EntityResultTests extends AbstractUsageTest { ); } + @Test + public void testExplicitDiscriminatedMappingWithResultClass(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + final String qryString = + "select id as id_alias," + + " type_code as type_code_alias," + + " root_name as root_name_alias," + + " subtype1_name as sub_type1_name_alias," + + " subtype2_name as sub_type2_name_alias" + + " from discriminated_entity"; + + final List results = session.createNativeQuery( qryString, "root-explicit", DiscriminatedRoot.class ).list(); + assertThat( results.size(), is( 4 ) ); + + final Set idsFound = new HashSet<>(); + results.forEach( result -> idsFound.add( result.getId() ) ); + assertThat( idsFound, containsInAnyOrder( 1, 2, 3, 4 ) ); + } + ); + } + @Test public void testConvertedAttributes(SessionFactoryScope scope) { scope.inTransaction( diff --git a/hibernate-core/src/test/java/org/hibernate/test/sql/hand/query/NativeSQLQueriesTest.java b/hibernate-core/src/test/java/org/hibernate/test/sql/hand/query/NativeSQLQueriesTest.java index 2311c472f0..839048654d 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/sql/hand/query/NativeSQLQueriesTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/sql/hand/query/NativeSQLQueriesTest.java @@ -49,7 +49,6 @@ import org.hibernate.testing.orm.junit.SessionFactory; import org.hibernate.testing.orm.junit.SessionFactoryScope; import org.hibernate.testing.orm.junit.Setting; import org.hibernate.testing.orm.junit.SkipForDialect; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import jakarta.persistence.PersistenceException; @@ -260,6 +259,34 @@ public class NativeSQLQueriesTest { ); } + @Test + public void testResultSetMappingDefinitionWithResultClass(SessionFactoryScope scope) { + Organization ifa = new Organization("IFA"); + Organization jboss = new Organization("JBoss"); + Person gavin = new Person("Gavin"); + Employment emp = new Employment(gavin, jboss, "AU"); + + scope.inTransaction( + session -> { + session.persist(ifa); + session.persist(jboss); + session.persist(gavin); + session.persist(emp); + + List l = session.createNativeQuery( getOrgEmpRegionSQL(), "org-emp-regionCode", Object[].class ).list(); + assertEquals( l.size(), 2 ); + + l = session.createNativeQuery( getOrgEmpPersonSQL(), "org-emp-person", Object[].class ).list(); + assertEquals( l.size(), 1 ); + + session.delete(emp); + session.delete(gavin); + session.delete(ifa); + session.delete(jboss); + } + ); + } + @Test public void testScalarValues(SessionFactoryScope scope) throws Exception { Organization ifa = new Organization( "IFA" );