ResultSet mapping

- support for dynamic instantiations of scalar values.  This is all JPA defines support for wrt `@ConstructorResult`
- support for mixed result mappings, including dynamic instantiations which JPA says is not legal.  We support this in HQL also
This commit is contained in:
Steve Ebersole 2020-07-30 14:18:57 -05:00
parent 8db9709408
commit bcf995f84f
7 changed files with 295 additions and 53 deletions

View File

@ -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<EntityResultMapping> entityResultMappings;
final List<ConstructorResultMapping> constructorResultMappings;
final List<JpaColumnResultMapping> 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<EntityResultMapping> entityResultMappings;
private final List<ConstructorResultMapping> constructorResultMappings;
private final List<JpaColumnResultMapping> columnResultMappings;
private SqlResultSetMappingDefinition(
String mappingName,
List<EntityResultMapping> entityResultMappings,
List<ConstructorResultMapping> constructorResultMappings,
List<JpaColumnResultMapping> 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<ScalarResultBuilder> scalarResultBuilders = new ArrayList<>();
final List<EntityResultBuilder> entityResultBuilders = new ArrayList<>();
for ( int i = 0; i < entityResultMappings.size(); i++ ) {
final EntityResultMapping resultMapping = entityResultMappings.get( i );
entityResultBuilders.add( resultMapping.resolve( factory ) );
}
final List<InstantiationResultBuilder> instantiationResultBuilders = new ArrayList<>();
for ( int i = 0; i < constructorResultMappings.size(); i++ ) {
final ConstructorResultMapping resultMapping = constructorResultMappings.get( i );
instantiationResultBuilders.add( resultMapping.resolve( factory ) );
}
final List<ScalarResultBuilder> 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<ResultMapping> 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<ResultMapping> argumentResultMappings;
public ConstructorResultMapping(
Class<?> targetJavaType,
List<ResultMapping> argumentResultMappings) {
this.targetJavaType = targetJavaType;
this.argumentResultMappings = argumentResultMappings;
}
@Override
public InstantiationResultBuilder resolve(SessionFactoryImplementor factory) {
final List<ResultBuilder> 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() );
}
}
}

View File

@ -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<ScalarResultBuilder> scalarResultBuilders;
private final List<ResultBuilder> resultBuilders;
public NamedResultSetMappingMementoImpl(
String name,
List<EntityResultBuilder> entityResultBuilders,
List<InstantiationResultBuilder> instantiationResultBuilders,
List<ScalarResultBuilder> 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<String> querySpaceConsumer,
SessionFactoryImplementor sessionFactory) {
scalarResultBuilders.forEach(
builder -> resultSetMapping.addResultBuilder(
(jdbcResultsMetadata, legacyFetchResolver, sqlSelectionConsumer, sessionFactory1) ->
builder.buildReturn(
jdbcResultsMetadata,
legacyFetchResolver,
sqlSelectionConsumer,
sessionFactory
)
)
);
resultBuilders.forEach( resultSetMapping::addResultBuilder );
}
}

View File

@ -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 {
}

View File

@ -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<ResultBuilder> argumentResultBuilders;
public StandardInstantiationResultBuilder(
JavaTypeDescriptor<?> javaTypeDescriptor,
List<ResultBuilder> argumentResultBuilders) {
this.javaTypeDescriptor = javaTypeDescriptor;
this.argumentResultBuilders = argumentResultBuilders;
}
@Override
public DomainResult<?> buildReturn(
JdbcValuesMetadata jdbcResultsMetadata,
BiFunction<String, String, LegacyFetchBuilder> legacyFetchResolver,
Consumer<SqlSelection> sqlSelectionConsumer,
SessionFactoryImplementor sessionFactory) {
final List<ArgumentDomainResult<?>> 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
);
}
}

View File

@ -155,10 +155,11 @@ public class NativeQueryImpl<R>
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(

View File

@ -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;
}
}
}

View File

@ -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<SimpleEntityWithNamedMappings.DropDownDto> 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();
}
);
}