diff --git a/hibernate-core/src/main/java/org/hibernate/boot/query/SqlResultSetMappingDefinition.java b/hibernate-core/src/main/java/org/hibernate/boot/query/SqlResultSetMappingDefinition.java index 7be378a400..12cab69f6e 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/query/SqlResultSetMappingDefinition.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/query/SqlResultSetMappingDefinition.java @@ -7,8 +7,11 @@ package org.hibernate.boot.query; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import javax.persistence.ColumnResult; +import javax.persistence.ConstructorResult; +import javax.persistence.EntityResult; import javax.persistence.SqlResultSetMapping; import org.hibernate.NotYetImplementedFor6Exception; @@ -17,7 +20,11 @@ import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.query.internal.NamedResultSetMappingMementoImpl; import org.hibernate.query.named.NamedResultSetMappingMemento; +import org.hibernate.query.results.EntityResultBuilder; +import org.hibernate.query.results.InstantiationResultBuilder; +import org.hibernate.query.results.ResultBuilder; import org.hibernate.query.results.ScalarResultBuilder; +import org.hibernate.query.results.StandardInstantiationResultBuilder; import org.hibernate.query.results.StandardScalarResultBuilder; import org.hibernate.type.descriptor.java.JavaTypeDescriptor; @@ -43,38 +50,51 @@ public class SqlResultSetMappingDefinition implements NamedResultSetMappingDefin public static SqlResultSetMappingDefinition from( SqlResultSetMapping mappingAnnotation, MetadataBuildingContext context) { - if ( mappingAnnotation.classes().length > 0 ) { - throw new NotYetImplementedFor6Exception( - "Support for dynamic-instantiation result mappings not yet implemented" - ); - } - - if ( mappingAnnotation.entities().length > 0 ) { - throw new NotYetImplementedFor6Exception( - "Support for entity result mappings not yet implemented" - ); - } - - if ( mappingAnnotation.columns().length == 0 ) { - throw new NotYetImplementedFor6Exception( "Should never get here" ); - } + final List entityResultMappings; + final List constructorResultMappings; final List columnResultMappings; - if ( mappingAnnotation.columns().length == 0 ) { - columnResultMappings = null; + + final EntityResult[] entityResults = mappingAnnotation.entities(); + if ( entityResults.length > 0 ) { + entityResultMappings = Collections.emptyList(); } else { - columnResultMappings = new ArrayList<>( mappingAnnotation.columns().length ); - for ( int i = 0; i < mappingAnnotation.columns().length; i++ ) { - final ColumnResult columnMapping = mappingAnnotation.columns()[i]; - columnResultMappings.add( - new JpaColumnResultMapping( columnMapping.name(), columnMapping.type() ) - ); + entityResultMappings = new ArrayList<>( entityResults.length ); + for ( int i = 0; i < entityResults.length; i++ ) { + final EntityResult entityResult = entityResults[i]; + entityResultMappings.add( EntityResultMapping.from( entityResult, context ) ); + } + } + + final ConstructorResult[] constructorResults = mappingAnnotation.classes(); + if ( constructorResults.length == 0 ) { + constructorResultMappings = Collections.emptyList(); + } + else { + constructorResultMappings = new ArrayList<>( constructorResults.length ); + for ( int i = 0; i < constructorResults.length; i++ ) { + final ConstructorResult constructorResult = constructorResults[i]; + constructorResultMappings.add( ConstructorResultMapping.from( constructorResult, context ) ); + } + } + + final ColumnResult[] columnResults = mappingAnnotation.columns(); + if ( columnResults.length == 0 ) { + columnResultMappings = Collections.emptyList(); + } + else { + columnResultMappings = new ArrayList<>( columnResults.length ); + for ( int i = 0; i < columnResults.length; i++ ) { + final ColumnResult columnResult = columnResults[i]; + columnResultMappings.add( JpaColumnResultMapping.from( columnResult, context ) ); } } return new SqlResultSetMappingDefinition( mappingAnnotation.name(), + entityResultMappings, + constructorResultMappings, columnResultMappings, context ); @@ -82,13 +102,19 @@ public class SqlResultSetMappingDefinition implements NamedResultSetMappingDefin private final String mappingName; + private final List entityResultMappings; + private final List constructorResultMappings; private final List columnResultMappings; private SqlResultSetMappingDefinition( String mappingName, + List entityResultMappings, + List constructorResultMappings, List columnResultMappings, MetadataBuildingContext context) { this.mappingName = mappingName; + this.entityResultMappings = entityResultMappings; + this.constructorResultMappings = constructorResultMappings; this.columnResultMappings = columnResultMappings; } @@ -99,8 +125,19 @@ public class SqlResultSetMappingDefinition implements NamedResultSetMappingDefin @Override public NamedResultSetMappingMemento resolve(SessionFactoryImplementor factory) { - final List scalarResultBuilders = new ArrayList<>(); + final List entityResultBuilders = new ArrayList<>(); + for ( int i = 0; i < entityResultMappings.size(); i++ ) { + final EntityResultMapping resultMapping = entityResultMappings.get( i ); + entityResultBuilders.add( resultMapping.resolve( factory ) ); + } + final List instantiationResultBuilders = new ArrayList<>(); + for ( int i = 0; i < constructorResultMappings.size(); i++ ) { + final ConstructorResultMapping resultMapping = constructorResultMappings.get( i ); + instantiationResultBuilders.add( resultMapping.resolve( factory ) ); + } + + final List scalarResultBuilders = new ArrayList<>(); for ( int i = 0; i < columnResultMappings.size(); i++ ) { final JpaColumnResultMapping resultMapping = columnResultMappings.get( i ); scalarResultBuilders.add( resultMapping.resolve( factory ) ); @@ -108,6 +145,8 @@ public class SqlResultSetMappingDefinition implements NamedResultSetMappingDefin return new NamedResultSetMappingMementoImpl( mappingName, + entityResultBuilders, + instantiationResultBuilders, scalarResultBuilders, factory ); @@ -128,6 +167,10 @@ public class SqlResultSetMappingDefinition implements NamedResultSetMappingDefin : explicitJavaType; } + public static JpaColumnResultMapping from(ColumnResult columnResult, MetadataBuildingContext context) { + return new JpaColumnResultMapping( columnResult.name(), columnResult.type() ); + } + public String getColumnName() { return columnName; } @@ -151,4 +194,68 @@ public class SqlResultSetMappingDefinition implements NamedResultSetMappingDefin return new StandardScalarResultBuilder( columnName ); } } + + /** + * @see javax.persistence.ConstructorResult + */ + private static class ConstructorResultMapping implements ResultMapping { + + public static ConstructorResultMapping from( + ConstructorResult constructorResult, + MetadataBuildingContext context) { + final ColumnResult[] columnResults = constructorResult.columns(); + if ( columnResults.length == 0 ) { + throw new IllegalArgumentException( "ConstructorResult did not define any ColumnResults" ); + } + + final List argumentResultMappings = new ArrayList<>( columnResults.length ); + for ( int i = 0; i < columnResults.length; i++ ) { + final ColumnResult columnResult = columnResults[i]; + argumentResultMappings.add( JpaColumnResultMapping.from( columnResult, context ) ); + } + + return new ConstructorResultMapping( + constructorResult.targetClass(), + argumentResultMappings + ); + } + + private final Class targetJavaType; + private final List argumentResultMappings; + + public ConstructorResultMapping( + Class targetJavaType, + List argumentResultMappings) { + this.targetJavaType = targetJavaType; + this.argumentResultMappings = argumentResultMappings; + } + + @Override + public InstantiationResultBuilder resolve(SessionFactoryImplementor factory) { + final List argumentResultBuilders = new ArrayList<>( argumentResultMappings.size() ); + argumentResultMappings.forEach( mapping -> argumentResultBuilders.add( mapping.resolve( factory ) ) ); + + final JavaTypeDescriptor targetJtd = factory.getTypeConfiguration() + .getJavaTypeDescriptorRegistry() + .getDescriptor( targetJavaType ); + + return new StandardInstantiationResultBuilder( targetJtd, argumentResultBuilders ); + } + } + + /** + * @see javax.persistence.EntityResult + */ + private static class EntityResultMapping implements ResultMapping { + public static EntityResultMapping from( + EntityResult entityResult, + MetadataBuildingContext context) { + throw new NotYetImplementedFor6Exception( "Support for dynamic-instantiation results not yet implemented" ); + } + + @Override + public EntityResultBuilder resolve(SessionFactoryImplementor factory) { + throw new NotYetImplementedFor6Exception( getClass() ); + } + } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/internal/NamedResultSetMappingMementoImpl.java b/hibernate-core/src/main/java/org/hibernate/query/internal/NamedResultSetMappingMementoImpl.java index 51b6cf64b7..df979232e6 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/internal/NamedResultSetMappingMementoImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/internal/NamedResultSetMappingMementoImpl.java @@ -6,11 +6,15 @@ */ package org.hibernate.query.internal; +import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.query.named.NamedResultSetMappingMemento; +import org.hibernate.query.results.EntityResultBuilder; +import org.hibernate.query.results.InstantiationResultBuilder; +import org.hibernate.query.results.ResultBuilder; import org.hibernate.query.results.ResultSetMapping; import org.hibernate.query.results.ScalarResultBuilder; @@ -22,14 +26,24 @@ import org.hibernate.query.results.ScalarResultBuilder; public class NamedResultSetMappingMementoImpl implements NamedResultSetMappingMemento { private final String name; - private final List scalarResultBuilders; + private final List resultBuilders; public NamedResultSetMappingMementoImpl( String name, + List entityResultBuilders, + List instantiationResultBuilders, List scalarResultBuilders, SessionFactoryImplementor factory) { this.name = name; - this.scalarResultBuilders = scalarResultBuilders; + + final int totalNumberOfBuilders = entityResultBuilders.size() + + instantiationResultBuilders.size() + + scalarResultBuilders.size(); + this.resultBuilders = new ArrayList<>( totalNumberOfBuilders ); + + resultBuilders.addAll( entityResultBuilders ); + resultBuilders.addAll( instantiationResultBuilders ); + resultBuilders.addAll( scalarResultBuilders ); } @Override @@ -42,16 +56,6 @@ public class NamedResultSetMappingMementoImpl implements NamedResultSetMappingMe ResultSetMapping resultSetMapping, Consumer querySpaceConsumer, SessionFactoryImplementor sessionFactory) { - scalarResultBuilders.forEach( - builder -> resultSetMapping.addResultBuilder( - (jdbcResultsMetadata, legacyFetchResolver, sqlSelectionConsumer, sessionFactory1) -> - builder.buildReturn( - jdbcResultsMetadata, - legacyFetchResolver, - sqlSelectionConsumer, - sessionFactory - ) - ) - ); + resultBuilders.forEach( resultSetMapping::addResultBuilder ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/InstantiationResultBuilder.java b/hibernate-core/src/main/java/org/hibernate/query/results/InstantiationResultBuilder.java new file mode 100644 index 0000000000..0cbfd3b039 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/query/results/InstantiationResultBuilder.java @@ -0,0 +1,15 @@ +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.query.results; + +/** + * Nominal extension to ResultBuilder for cases involving dynamic-instantiation results + * + * @author Steve Ebersole + */ +public interface InstantiationResultBuilder extends ResultBuilder { +} diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/StandardInstantiationResultBuilder.java b/hibernate-core/src/main/java/org/hibernate/query/results/StandardInstantiationResultBuilder.java new file mode 100644 index 0000000000..b66b974079 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/query/results/StandardInstantiationResultBuilder.java @@ -0,0 +1,61 @@ +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.query.results; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.BiFunction; +import java.util.function.Consumer; + +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.query.DynamicInstantiationNature; +import org.hibernate.sql.ast.spi.SqlSelection; +import org.hibernate.sql.results.graph.DomainResult; +import org.hibernate.sql.results.graph.instantiation.internal.ArgumentDomainResult; +import org.hibernate.sql.results.graph.instantiation.internal.DynamicInstantiationResultImpl; +import org.hibernate.sql.results.jdbc.spi.JdbcValuesMetadata; +import org.hibernate.type.descriptor.java.JavaTypeDescriptor; + +/** + * @author Steve Ebersole + */ +public class StandardInstantiationResultBuilder implements InstantiationResultBuilder { + private final JavaTypeDescriptor javaTypeDescriptor; + private final List argumentResultBuilders; + + public StandardInstantiationResultBuilder( + JavaTypeDescriptor javaTypeDescriptor, + List argumentResultBuilders) { + this.javaTypeDescriptor = javaTypeDescriptor; + this.argumentResultBuilders = argumentResultBuilders; + } + + @Override + public DomainResult buildReturn( + JdbcValuesMetadata jdbcResultsMetadata, + BiFunction legacyFetchResolver, + Consumer sqlSelectionConsumer, + SessionFactoryImplementor sessionFactory) { + final List> argumentDomainResults = new ArrayList<>( argumentResultBuilders.size() ); + + argumentResultBuilders.forEach( + argumentResultBuilder -> argumentDomainResults.add( + new ArgumentDomainResult<>( + argumentResultBuilder.buildReturn( jdbcResultsMetadata, legacyFetchResolver, sqlSelectionConsumer, sessionFactory ) + ) + ) + ); + + //noinspection unchecked + return new DynamicInstantiationResultImpl( + null, + DynamicInstantiationNature.CLASS, + javaTypeDescriptor, + argumentDomainResults + ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/query/sql/internal/NativeQueryImpl.java b/hibernate-core/src/main/java/org/hibernate/query/sql/internal/NativeQueryImpl.java index 953094998a..dc15f32d45 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sql/internal/NativeQueryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sql/internal/NativeQueryImpl.java @@ -155,10 +155,11 @@ public class NativeQueryImpl SharedSessionContractImplementor session) { this( memento, session ); - // todo (6.0) : need to add handling for `javax.persistence.NamedNativeQuery#resultSetMapping` - // and `javax.persistence.NamedNativeQuery#resultClass` - - // todo (6.0) : relatedly, does `resultSetMappingName` come from `NamedNativeQuery#resultSetMapping`? + session.getFactory() + .getQueryEngine() + .getNamedQueryRepository() + .getResultSetMappingMemento( resultSetMappingName ) + .resolve( resultSetMapping, (s) -> {}, getSessionFactory() ); } public NativeQueryImpl( diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/named/resultmapping/SimpleEntityWithNamedMappings.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/named/resultmapping/SimpleEntityWithNamedMappings.java index 6cf1e157fd..9fc3a92011 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/query/named/resultmapping/SimpleEntityWithNamedMappings.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/named/resultmapping/SimpleEntityWithNamedMappings.java @@ -7,6 +7,7 @@ package org.hibernate.orm.test.query.named.resultmapping; import javax.persistence.ColumnResult; +import javax.persistence.ConstructorResult; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.SqlResultSetMapping; @@ -26,6 +27,16 @@ import javax.persistence.SqlResultSetMapping; @ColumnResult( name = "name" ) } ) +@SqlResultSetMapping( + name = "id_name_dto", + classes = @ConstructorResult( + targetClass = SimpleEntityWithNamedMappings.DropDownDto.class, + columns = { + @ColumnResult( name = "id" ), + @ColumnResult( name = "name" ) + } + ) +) public class SimpleEntityWithNamedMappings { @Id private Integer id; @@ -55,4 +66,22 @@ public class SimpleEntityWithNamedMappings { public void setName(String name) { this.name = name; } + + public static class DropDownDto { + private final Integer id; + private final String text; + + public DropDownDto(Integer id, String text) { + this.id = id; + this.text = text; + } + + public Integer getId() { + return id; + } + + public String getText() { + return text; + } + } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/named/resultmapping/UsageTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/named/resultmapping/UsageTests.java index 3dc720619c..06cbe7ed95 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/query/named/resultmapping/UsageTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/named/resultmapping/UsageTests.java @@ -6,13 +6,10 @@ */ package org.hibernate.orm.test.query.named.resultmapping; -import java.time.Instant; +import java.util.List; import org.hibernate.query.named.NamedResultSetMappingMemento; -import org.hibernate.query.sql.spi.NativeQueryImplementor; -import org.hibernate.testing.orm.domain.StandardDomainModel; -import org.hibernate.testing.orm.domain.helpdesk.Incident; import org.hibernate.testing.orm.junit.DomainModel; import org.hibernate.testing.orm.junit.SessionFactory; import org.hibernate.testing.orm.junit.SessionFactoryScope; @@ -20,32 +17,60 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.notNullValue; /** * @author Steve Ebersole */ -@DomainModel( standardModels = StandardDomainModel.HELPDESK ) +@DomainModel( annotatedClasses = SimpleEntityWithNamedMappings.class ) @SessionFactory public class UsageTests { @Test - public void testSimpleScalarMappings(SessionFactoryScope scope) { + public void testSimpleScalarMapping(SessionFactoryScope scope) { scope.inTransaction( session -> { // make sure it is in the repository final NamedResultSetMappingMemento mappingMemento = session.getSessionFactory() .getQueryEngine() .getNamedQueryRepository() - .getResultSetMappingMemento( "incident_summary" ); + .getResultSetMappingMemento( "id_name" ); assertThat( mappingMemento, notNullValue() ); // apply it to a native-query - final String qryString = "select id, description, reported from incident"; - session.createNativeQuery( qryString, "incident_summary" ).list(); + final String qryString = "select id, name from SimpleEntityWithNamedMappings"; + session.createNativeQuery( qryString, "id_name" ).list(); // todo (6.0) : should also try executing the ProcedureCall once that functionality is implemented - session.createStoredProcedureCall( "abc", "incident_summary" ); + session.createStoredProcedureCall( "abc", "id_name" ); + } + ); + } + + @Test + public void testSimpleInstantiationOfScalars(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + // make sure it is in the repository + final NamedResultSetMappingMemento mappingMemento = session.getSessionFactory() + .getQueryEngine() + .getNamedQueryRepository() + .getResultSetMappingMemento( "id_name_dto" ); + assertThat( mappingMemento, notNullValue() ); + + // apply it to a native-query + final String qryString = "select id, name from SimpleEntityWithNamedMappings"; + final List results + = session.createNativeQuery( qryString, "id_name_dto" ).list(); + assertThat( results.size(), is( 1 ) ); + + final SimpleEntityWithNamedMappings.DropDownDto dto = results.get( 0 ); + assertThat( dto.getId(), is( 1 ) ); + assertThat( dto.getText(), is( "test" ) ); + + // todo (6.0) : should also try executing the ProcedureCall once that functionality is implemented + session.createStoredProcedureCall( "abc", "id_name_dto" ); } ); } @@ -54,7 +79,7 @@ public class UsageTests { public void prepareData(SessionFactoryScope scope) { scope.inTransaction( session -> { - session.save( new Incident( 1, "test", Instant.now() ) ); + session.save( new SimpleEntityWithNamedMappings( 1, "test" ) ); } ); } @@ -63,7 +88,7 @@ public class UsageTests { public void cleanUpData(SessionFactoryScope scope) { scope.inTransaction( session -> { - session.createQuery( "delete Incident" ).executeUpdate(); + session.createQuery( "delete SimpleEntityWithNamedMappings" ).executeUpdate(); } ); }