NativeQuery support

- `NativeQuery#addAttributeResult`
This commit is contained in:
Steve Ebersole 2020-07-28 11:26:25 -05:00
parent 671250afa6
commit 5f7c139f7e
10 changed files with 324 additions and 8 deletions

View File

@ -15,6 +15,9 @@ Document in release-notes:
* `NativeQuery#addScalar(Class)`
* `NativeQuery#addScalar(Class,AttributeConverter)`
* `NativeQuery#addScalar(Class,Class<AttributeConverter>)`
* `NativeQuery#addAttributeResult(String,Class,String)`
* `NativeQuery#addAttributeResult(String,String,String)`
* `NativeQuery#addAttributeResult(String,Attribute)`
== Changes

View File

@ -536,7 +536,12 @@ public class MappingMetamodelImpl implements MappingMetamodel, MetamodelImplemen
@Override
public String getImportedName(String name) {
throw new NotYetImplementedFor6Exception( getClass() );
// we have to go back through TypeConfiguration / SessionFactory to get to the JpaMetamodel :(
final String qualifiedName = typeConfiguration.getSessionFactory()
.getRuntimeMetamodels()
.getJpaMetamodel()
.qualifyImportableName( name );
return qualifiedName == null ? name : qualifiedName;
}
@Override

View File

@ -19,6 +19,7 @@ import javax.persistence.FlushModeType;
import javax.persistence.LockModeType;
import javax.persistence.Parameter;
import javax.persistence.TemporalType;
import javax.persistence.metamodel.SingularAttribute;
import org.hibernate.CacheMode;
import org.hibernate.FlushMode;
@ -131,6 +132,41 @@ public interface NativeQuery<T> extends Query<T>, SynchronizeableQuery {
*/
<C> NativeQuery<T> addScalar(String columnAlias, Class<C> relationalJavaType, Class<? extends AttributeConverter<?,C>> converter);
/**
* Defines a result based on a specified attribute. Differs from adding a scalar in that
* any conversions or other semantics defined on the attribute are automatically applied
* to the mapping
*
* @return {@code this}, for method chaining
*
* @since 6.0
*/
NativeQuery<T> addAttributeResult(String columnAlias, Class<?> entityJavaType, String attributePath);
/**
* Defines a result based on a specified attribute. Differs from adding a scalar in that
* any conversions or other semantics defined on the attribute are automatically applied
* to the mapping
*
* @return {@code this}, for method chaining
*
* @since 6.0
*/
NativeQuery<T> addAttributeResult(String columnAlias, String entityName, String attributePath);
/**
* Defines a result based on a specified attribute. Differs from adding a scalar in that
* any conversions or other semantics defined on the attribute are automatically applied
* to the mapping.
*
* This form accepts the JPA Attribute mapping describing the attribute
*
* @return {@code this}, for method chaining
*
* @since 6.0
*/
NativeQuery<T> addAttributeResult(String columnAlias, SingularAttribute<?,?> attribute);
/**
* Add a new root return mapping, returning a {@link RootReturn} to allow
* further definition.

View File

@ -0,0 +1,80 @@
/*
* 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.List;
import java.util.Locale;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.metamodel.mapping.SingularAttributeMapping;
import org.hibernate.metamodel.mapping.internal.BasicValuedSingularAttributeMapping;
import org.hibernate.metamodel.mapping.internal.EmbeddedAttributeMapping;
import org.hibernate.sql.ast.spi.SqlAstCreationState;
import org.hibernate.sql.ast.spi.SqlSelection;
import org.hibernate.sql.results.graph.DomainResult;
import org.hibernate.sql.results.graph.DomainResultCreationState;
import org.hibernate.sql.results.graph.Fetch;
import org.hibernate.sql.results.graph.FetchParent;
import org.hibernate.sql.results.graph.basic.BasicResult;
import org.hibernate.sql.results.jdbc.spi.JdbcValuesMetadata;
/**
* @author Steve Ebersole
*/
public class AttributeResultBuilder implements ResultBuilder {
private final BasicValuedSingularAttributeMapping attributeMapping;
private final String columnAlias;
private final String entityName;
private final String attributePath;
public AttributeResultBuilder(
SingularAttributeMapping attributeMapping,
String columnAlias,
String entityName,
String attributePath) {
final boolean allowable = attributeMapping instanceof BasicValuedSingularAttributeMapping;
if ( !allowable ) {
throw new IllegalArgumentException(
String.format(
Locale.ROOT,
"Specified attribute [%s.%s] must be basic: %s",
entityName,
attributePath,
attributeMapping
)
);
}
this.attributeMapping = (BasicValuedSingularAttributeMapping) attributeMapping;
this.columnAlias = columnAlias;
this.entityName = entityName;
this.attributePath = attributePath;
}
@Override
public DomainResult<?> buildReturn(
JdbcValuesMetadata jdbcResultsMetadata,
BiFunction<String, String, LegacyFetchBuilder> legacyFetchResolver,
Consumer<SqlSelection> sqlSelectionConsumer,
SessionFactoryImplementor sessionFactory) {
final int resultSetPosition = jdbcResultsMetadata.resolveColumnPosition( columnAlias );
final int valuesArrayPosition = resultSetPosition - 1;
final SqlSelectionImpl sqlSelection = new SqlSelectionImpl( valuesArrayPosition, attributeMapping );
sqlSelectionConsumer.accept( sqlSelection );
return new BasicResult<>(
valuesArrayPosition,
columnAlias,
attributeMapping.getJavaTypeDescriptor(),
attributeMapping.getValueConverter()
);
}
}

View File

@ -6,10 +6,18 @@
*/
package org.hibernate.query.results;
import java.util.Locale;
import javax.persistence.AttributeConverter;
import javax.persistence.metamodel.EntityType;
import javax.persistence.metamodel.SingularAttribute;
import org.hibernate.LockMode;
import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.metamodel.RuntimeMetamodels;
import org.hibernate.metamodel.mapping.AttributeMapping;
import org.hibernate.metamodel.mapping.SingularAttributeMapping;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.type.BasicType;
import org.hibernate.type.descriptor.java.JavaTypeDescriptor;
@ -64,10 +72,92 @@ public class Builders {
throw new NotYetImplementedFor6Exception();
}
public static ResultBuilder attributeResult(
String columnAlias,
String entityName,
String attributePath,
SessionFactoryImplementor sessionFactory) {
if ( attributePath.contains( "." ) ) {
throw new NotYetImplementedFor6Exception(
"Support for defining a NativeQuery attribute result based on a composite path is not yet implemented"
);
}
final RuntimeMetamodels runtimeMetamodels = sessionFactory.getRuntimeMetamodels();
final String fullEntityName = runtimeMetamodels.getMappingMetamodel().getImportedName( entityName );
final EntityPersister entityMapping = runtimeMetamodels.getMappingMetamodel().findEntityDescriptor( fullEntityName );
if ( entityMapping == null ) {
throw new IllegalArgumentException( "Could not locate entity mapping : " + fullEntityName );
}
final AttributeMapping attributeMapping = entityMapping.findAttributeMapping( attributePath );
if ( attributeMapping == null ) {
throw new IllegalArgumentException( "Could not locate attribute mapping : " + fullEntityName + "." + attributePath );
}
if ( attributeMapping instanceof SingularAttributeMapping ) {
final SingularAttributeMapping singularAttributeMapping = (SingularAttributeMapping) attributeMapping;
return new AttributeResultBuilder( singularAttributeMapping, columnAlias, fullEntityName, attributePath );
}
throw new IllegalArgumentException(
String.format(
Locale.ROOT,
"Specified attribute mapping [%s.%s] not a basic attribute: %s",
fullEntityName,
attributePath,
attributeMapping
)
);
}
public static ResultBuilder attributeResult(String columnAlias, SingularAttribute<?, ?> attribute) {
if ( ! ( attribute.getDeclaringType() instanceof EntityType ) ) {
throw new NotYetImplementedFor6Exception(
"Support for defining a NativeQuery attribute result based on a composite path is not yet implemented"
);
}
throw new NotYetImplementedFor6Exception();
}
/**
* Creates a EntityResultBuilder allowing for further configuring of the mapping.
*
* @param tableAlias
* @param entityName
* @return
*/
public static EntityResultBuilder entity(String tableAlias, String entityName) {
throw new NotYetImplementedFor6Exception( );
}
/**
* Creates a EntityResultBuilder that does not allow any further configuring of the mapping.
*
* @see org.hibernate.query.NativeQuery#addEntity(Class)
* @see org.hibernate.query.NativeQuery#addEntity(String)
* @see org.hibernate.query.NativeQuery#addEntity(String, Class)
* @see org.hibernate.query.NativeQuery#addEntity(String, String)
*/
public static CalculatedEntityResultBuilder entityCalculated(String tableAlias, String entityName) {
return entityCalculated( tableAlias, entityName, null );
}
/**
* Creates a EntityResultBuilder that does not allow any further configuring of the mapping.
*
* @see #entityCalculated(String, String)
* @see org.hibernate.query.NativeQuery#addEntity(String, Class, LockMode)
* @see org.hibernate.query.NativeQuery#addEntity(String, String, LockMode)
*/
public static CalculatedEntityResultBuilder entityCalculated(
String tableAlias,
String entityName,
LockMode explicitLockMode) {
return new CalculatedEntityResultBuilder( tableAlias, entityName, explicitLockMode );
}
public static LegacyFetchBuilder fetch(String tableAlias, String ownerTableAlias, String joinPropertyName) {
throw new NotYetImplementedFor6Exception( );
}

View File

@ -0,0 +1,46 @@
/*
* 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.function.BiFunction;
import java.util.function.Consumer;
import org.hibernate.LockMode;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.sql.ast.spi.SqlSelection;
import org.hibernate.sql.results.graph.DomainResult;
import org.hibernate.sql.results.jdbc.spi.JdbcValuesMetadata;
/**
* An entity DomainResult builder for cases when Hibernate implicitly
* calculates the mapping
*
* @author Steve Ebersole
*/
public class CalculatedEntityResultBuilder implements ResultBuilder {
private final String tableAlias;
private final String entityName;
private final LockMode explicitLockMode;
public CalculatedEntityResultBuilder(
String tableAlias,
String entityName,
LockMode explicitLockMode) {
this.tableAlias = tableAlias;
this.entityName = entityName;
this.explicitLockMode = explicitLockMode;
}
@Override
public DomainResult<?> buildReturn(
JdbcValuesMetadata jdbcResultsMetadata,
BiFunction<String, String, LegacyFetchBuilder> legacyFetchResolver,
Consumer<SqlSelection> sqlSelectionConsumer,
SessionFactoryImplementor sessionFactory) {
return null;
}
}

View File

@ -27,6 +27,7 @@ import javax.persistence.LockModeType;
import javax.persistence.Parameter;
import javax.persistence.PersistenceException;
import javax.persistence.TemporalType;
import javax.persistence.metamodel.SingularAttribute;
import org.hibernate.CacheMode;
import org.hibernate.FlushMode;
@ -511,6 +512,30 @@ public class NativeQueryImpl<R>
return registerBuilder( Builders.scalar( columnAlias, relationalJavaType, converter, getSessionFactory() ) );
}
@Override
public NativeQueryImplementor<R> addAttributeResult(
String columnAlias,
Class<?> entityJavaType,
String attributePath) {
return addAttributeResult( columnAlias, entityJavaType.getName(), attributePath );
}
@Override
public NativeQueryImplementor<R> addAttributeResult(
String columnAlias,
String entityName,
String attributePath) {
registerBuilder( Builders.attributeResult( columnAlias, entityName, attributePath, getSessionFactory() ) );
return this;
}
@Override
public NativeQueryImplementor<R> addAttributeResult(
String columnAlias,
SingularAttribute<?, ?> attribute) {
registerBuilder( Builders.attributeResult( columnAlias, attribute ) );
return this;
}
@Override
public EntityResultBuilder addRoot(String tableAlias, String entityName) {
@ -534,13 +559,13 @@ public class NativeQueryImpl<R>
@Override
public NativeQueryImplementor<R> addEntity(String tableAlias, String entityName) {
addRoot( tableAlias, entityName );
registerBuilder( Builders.entityCalculated( tableAlias, entityName ) );
return this;
}
@Override
public NativeQueryImplementor<R> addEntity(String tableAlias, String entityName, LockMode lockMode) {
addRoot( tableAlias, entityName ).setLockMode( lockMode );
registerBuilder( Builders.entityCalculated( tableAlias, entityName, lockMode ) );
return this;
}

View File

@ -19,6 +19,7 @@ import javax.persistence.FlushModeType;
import javax.persistence.LockModeType;
import javax.persistence.Parameter;
import javax.persistence.TemporalType;
import javax.persistence.metamodel.SingularAttribute;
import org.hibernate.CacheMode;
import org.hibernate.FlushMode;
@ -65,6 +66,15 @@ public interface NativeQueryImplementor<R> extends QueryImplementor<R>, NativeQu
@Override
<C> NativeQueryImplementor<R> addScalar(String columnAlias, Class<C> relationalJavaType, Class<? extends AttributeConverter<?,C>> converter);
@Override
NativeQueryImplementor<R> addAttributeResult(String columnAlias, Class<?> entityJavaType, String attributePath);
@Override
NativeQueryImplementor<R> addAttributeResult(String columnAlias, String entityName, String attributePath);
@Override
NativeQueryImplementor<R> addAttributeResult(String columnAlias, SingularAttribute<?, ?> attribute);
@Override
EntityResultBuilder addRoot(String tableAlias, String entityName);

View File

@ -83,7 +83,6 @@ public class EntityGraphNativeQueryTest {
}
@Test
@FailureExpected( reason = "Uses an implicit entity/root return, which is not yet implemented" )
void testNativeQueryLoadGraph(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
@ -115,7 +114,6 @@ public class EntityGraphNativeQueryTest {
}
@Test
@FailureExpected( reason = "Uses an implicit entity/root return, which is not yet implemented" )
void testNativeQueryFetchGraph(SessionFactoryScope scope) {
scope.inTransaction(
session -> {

View File

@ -17,13 +17,11 @@ import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.internal.BasicValuedSingularAttributeMapping;
import org.hibernate.metamodel.model.convert.spi.BasicValueConverter;
import org.hibernate.metamodel.model.convert.spi.JpaAttributeConverter;
import org.hibernate.orm.test.metamodel.mapping.SmokeTests;
import org.hibernate.query.sql.spi.NativeQueryImplementor;
import org.hibernate.testing.orm.domain.StandardDomainModel;
import org.hibernate.testing.orm.domain.gambit.EntityOfBasics;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.DomainModelScope;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.junit.jupiter.api.AfterEach;
@ -47,7 +45,7 @@ import static org.hamcrest.MatcherAssert.assertThat;
standardModels = StandardDomainModel.GAMBIT
)
@SessionFactory
public class NativeQueryScalarTests {
public class NativeQueryResultBuilderTests {
public static final String STRING_VALUE = "a string value";
public static final String URL_STRING = "http://hibernate.org";
@ -220,6 +218,31 @@ public class NativeQueryScalarTests {
};
}
@Test
public void testConvertedAttributeBasedBuilder(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
final NativeQueryImplementor qry = session.createNativeQuery(
"select converted_gender from EntityOfBasics"
);
qry.addAttributeResult(
"converted_gender",
"EntityOfBasics",
"convertedGender"
);
final List results = qry.list();
assertThat( results.size(), is( 1 ) );
final Object result = results.get( 0 );
assertThat( result, instanceOf( EntityOfBasics.Gender.class ) );
assertThat( result, is( EntityOfBasics.Gender.OTHER ) );
}
);
}
@BeforeAll
public void verifyModel(SessionFactoryScope scope) {
final EntityMappingType entityDescriptor = scope.getSessionFactory()