HHH-16747 nail down query result types and actually document the semantics
This commit is contained in:
parent
c5ecf5d41c
commit
4eee30550c
|
@ -861,7 +861,7 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont
|
|||
query.setTupleTransformer( NativeQueryListTransformer.INSTANCE );
|
||||
}
|
||||
else if ( getFactory().getMappingMetamodel().isEntityClass( resultClass ) ) {
|
||||
query.addEntity( "alias1", resultClass.getName(), LockMode.READ );
|
||||
query.addEntity( resultClass, LockMode.READ );
|
||||
}
|
||||
else if ( resultClass != Object.class && resultClass != Object[].class ) {
|
||||
if ( isClass( resultClass )
|
||||
|
|
|
@ -80,8 +80,8 @@ public interface JpaMetamodel extends Metamodel {
|
|||
|
||||
/**
|
||||
* Returns a map that gives access to the enum literal expressions that can be used in queries.
|
||||
* The key is the short-hand enum literal. The value is a map, from enum class to the actual enum value.
|
||||
* This is needed for parsing short-hand enum literals that don't use FQNs.
|
||||
* The key is the shorthand enum literal. The value is a map, from enum class to the actual enum value.
|
||||
* This is needed for parsing shorthand enum literals that don't use FQNs.
|
||||
*/
|
||||
Map<String, Map<Class<?>, Enum<?>>> getAllowedEnumLiteralTexts();
|
||||
|
||||
|
|
|
@ -29,11 +29,28 @@ public interface QueryProducer {
|
|||
/**
|
||||
* Create a {@link Query} instance for the given HQL query, or
|
||||
* HQL insert, update, or delete statement.
|
||||
* <p>
|
||||
* If a query has no explicit {@code select} list, the select list
|
||||
* is inferred:
|
||||
* <ul>
|
||||
* <li>if there is exactly one root entity in the {@code from}
|
||||
* clause, then that root entity is the only element of the
|
||||
* select list, or
|
||||
* <li>otherwise, if there are multiple root entities in the
|
||||
* {@code from} clause, then the select list contains every
|
||||
* root entity and every non-{@code fetch} joined entity.
|
||||
* </ul>
|
||||
*
|
||||
* @apiNote Returns a raw {@code Query} type instead of a wildcard
|
||||
* type {@code Query<?>}, to match the signature of the JPA method
|
||||
* {@link jakarta.persistence.EntityManager#createQuery(String)}.
|
||||
*
|
||||
* @implNote This method interprets some queries with an implicit
|
||||
* {@code select} list in a quite unintuitive way. In some future
|
||||
* release, this method will be modified to throw an exception
|
||||
* when passed a query with a missing {@code select}. For now, use
|
||||
* {@link #createQuery(String, Class)} to avoid ambiguity.
|
||||
*
|
||||
* @param queryString The HQL query
|
||||
*
|
||||
* @return The {@link Query} instance for manipulation and execution
|
||||
|
@ -41,21 +58,47 @@ public interface QueryProducer {
|
|||
* @see jakarta.persistence.EntityManager#createQuery(String)
|
||||
*
|
||||
* @deprecated use {@link #createQuery(String, Class)},
|
||||
* {@link #createSelectionQuery} or {@link #createMutationQuery(String)}
|
||||
* depending on intention
|
||||
* {@link #createSelectionQuery(String, Class)}, or
|
||||
* {@link #createMutationQuery(String)} depending on intention
|
||||
*/
|
||||
@Deprecated(since = "6.0") @SuppressWarnings("rawtypes")
|
||||
Query createQuery(String queryString);
|
||||
|
||||
/**
|
||||
* Create a typed {@link Query} instance for the given HQL query string.
|
||||
* Create a typed {@link Query} instance for the given HQL query
|
||||
* string and given query result type.
|
||||
* <ul>
|
||||
* <li>If the query has a single item in the {@code select} list,
|
||||
* then the select item must be assignable to the given result
|
||||
* type.
|
||||
* <li>Otherwise, if there are multiple select items, then the
|
||||
* select items will be packaged into an instance of the
|
||||
* result type. The result type must have an appropriate
|
||||
* constructor with parameter types matching the select items,
|
||||
* or it must be one of the types {@code Object[]},
|
||||
* {@link java.util.List}, {@link java.util.Map}, or
|
||||
* {@link jakarta.persistence.Tuple}.
|
||||
* </ul>
|
||||
* <p>
|
||||
* If a query has no explicit {@code select} list, the select list
|
||||
* is inferred from the given query result type:
|
||||
* <ul>
|
||||
* <li>if the result type is an entity type, the query must have
|
||||
* exactly one root entity in the {@code from} clause, it must
|
||||
* be assignable to the result type, and the inferred select
|
||||
* list will contain just that entity, or
|
||||
* <li>otherwise, the select list contains every root entity and
|
||||
* every non-{@code fetch} joined entity, and each query result
|
||||
* will be packaged into an instance of the result type, just
|
||||
* as specified above.
|
||||
* </ul>
|
||||
* <p>
|
||||
* The returned {@code Query} may be executed by calling
|
||||
* {@link Query#getResultList()} or {@link Query#getSingleResult()}.
|
||||
*
|
||||
* @param queryString The HQL query
|
||||
* @param resultClass The type of the query result
|
||||
* @return The Query instance for manipulation and execution
|
||||
* @return The {@link Query} instance for manipulation and execution
|
||||
*
|
||||
* @see jakarta.persistence.EntityManager#createQuery(String,Class)
|
||||
*/
|
||||
|
@ -98,15 +141,25 @@ public interface QueryProducer {
|
|||
|
||||
/**
|
||||
* Create a {@link NativeQuery} instance for the given native SQL query
|
||||
* using implicit mapping to the specified Java type.
|
||||
* <p>
|
||||
* If the given class is an entity class, this method is equivalent to
|
||||
* {@code createNativeQuery(sqlString).addEntity("alias1", resultClass)}.
|
||||
* using an implicit mapping to the specified Java type.
|
||||
* <ul>
|
||||
* <li>If the given class is an entity class, this method is equivalent
|
||||
* to {@code createNativeQuery(sqlString).addEntity(resultClass)}.
|
||||
* <li>If the given class has a registered
|
||||
* {@link org.hibernate.type.descriptor.java.JavaType}, then the
|
||||
* query must return a result set with a single column whose
|
||||
* {@code JdbcType} is compatible with that {@code JavaType}.
|
||||
* <li>Otherwise, the select items will be packaged into an instance of
|
||||
* the result type. The result type must have an appropriate
|
||||
* constructor with parameter types matching the select items, or it
|
||||
* must be one of the types {@code Object[]}, {@link java.util.List},
|
||||
* {@link java.util.Map}, or {@link jakarta.persistence.Tuple}.
|
||||
* </ul>
|
||||
*
|
||||
* @param sqlString The native (SQL) query string
|
||||
* @param resultClass The Java entity type to map results to
|
||||
* @param resultClass The Java type to map results to
|
||||
*
|
||||
* @return The NativeQuery instance for manipulation and execution
|
||||
* @return The {@link NativeQuery} instance for manipulation and execution
|
||||
*
|
||||
* @see jakarta.persistence.EntityManager#createNativeQuery(String,Class)
|
||||
*/
|
||||
|
@ -114,13 +167,13 @@ public interface QueryProducer {
|
|||
|
||||
/**
|
||||
* Create a {@link NativeQuery} instance for the given native SQL query
|
||||
* using implicit mapping to the specified Java type.
|
||||
* using an implicit mapping to the specified Java entity type.
|
||||
* <p>
|
||||
* If the given class is an entity class, this method is equivalent to
|
||||
* The given class must be 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 resultClass The Java entity class to map results to
|
||||
* @param tableAlias The table alias for columns in the result set
|
||||
*
|
||||
* @return The {@link NativeQuery} instance for manipulation and execution
|
||||
|
@ -133,13 +186,13 @@ public interface QueryProducer {
|
|||
* Create a {@link NativeQuery} instance for the given native SQL query
|
||||
* using an explicit mapping to the specified Java type.
|
||||
* <p>
|
||||
* The given result set mapping name must identify a mapping defined by a
|
||||
* {@link jakarta.persistence.SqlResultSetMapping} annotation.
|
||||
* The given result set mapping name must identify a mapping defined by
|
||||
* a {@link jakarta.persistence.SqlResultSetMapping} annotation.
|
||||
*
|
||||
* @param sqlString The native (SQL) query string
|
||||
* @param resultSetMappingName The explicit result mapping name
|
||||
*
|
||||
* @return The NativeQuery instance for manipulation and execution
|
||||
* @return The {@link NativeQuery} instance for manipulation and execution
|
||||
*
|
||||
* @see jakarta.persistence.EntityManager#createNativeQuery(String,Class)
|
||||
* @see jakarta.persistence.SqlResultSetMapping
|
||||
|
@ -153,8 +206,8 @@ public interface QueryProducer {
|
|||
* Create a {@link NativeQuery} instance for the given native SQL query
|
||||
* using an explicit mapping to the specified Java type.
|
||||
* <p>
|
||||
* The given result set mapping name must identify a mapping defined by a
|
||||
* {@link jakarta.persistence.SqlResultSetMapping} annotation.
|
||||
* The given result set mapping name must identify a mapping defined by
|
||||
* a {@link jakarta.persistence.SqlResultSetMapping} annotation.
|
||||
*
|
||||
* @param sqlString The native (SQL) query string
|
||||
* @param resultSetMappingName The explicit result mapping name
|
||||
|
@ -167,21 +220,69 @@ public interface QueryProducer {
|
|||
<R> NativeQuery<R> createNativeQuery(String sqlString, String resultSetMappingName, Class<R> resultClass);
|
||||
|
||||
/**
|
||||
* Create a {@link SelectionQuery} reference for the given HQL.
|
||||
* Create a {@link SelectionQuery} reference for the given HQL
|
||||
* {@code select} statement.
|
||||
* <p>
|
||||
* If the statement has no explicit {@code select} list, the
|
||||
* select list is inferred:
|
||||
* <ul>
|
||||
* <li>if there is exactly one root entity in the {@code from}
|
||||
* clause, then that root entity is the only element of the
|
||||
* select list, or
|
||||
* <li>otherwise, if there are multiple root entities in the
|
||||
* {@code from} clause, then the select list contains every
|
||||
* root entity and every non-{@code fetch} joined entity.
|
||||
* </ul>
|
||||
*
|
||||
* Only valid for select queries
|
||||
*
|
||||
* @see jakarta.persistence.EntityManager#createQuery(String)
|
||||
* @implNote This method interprets some queries with an implicit
|
||||
* {@code select} list in a quite unintuitive way. In some future
|
||||
* release, this method will be modified to throw an exception
|
||||
* when passed a query with a missing {@code select}. For now, use
|
||||
* {@link #createSelectionQuery(String, Class)} to avoid ambiguity.
|
||||
*
|
||||
* @throws IllegalSelectQueryException if the given HQL query
|
||||
* is an insert, update or delete query
|
||||
*
|
||||
* @deprecated Use {@link #createSelectionQuery(String, Class)}
|
||||
*/
|
||||
@Deprecated(since = "6.3")
|
||||
SelectionQuery<?> createSelectionQuery(String hqlString);
|
||||
|
||||
/**
|
||||
* Create a {@link SelectionQuery} reference for the given HQL.
|
||||
*
|
||||
* Only valid for select queries
|
||||
* Create a {@link SelectionQuery} instance for the given HQL query
|
||||
* string and given query result type.
|
||||
* <ul>
|
||||
* <li>If the query has a single item in the {@code select} list,
|
||||
* then the select item must be assignable to the given result
|
||||
* type.
|
||||
* <li>Otherwise, if there are multiple select items, then the
|
||||
* select items will be packaged into an instance of the
|
||||
* result type. The result type must have an appropriate
|
||||
* constructor with parameter types matching the select items,
|
||||
* or it must be one of the types {@code Object[]},
|
||||
* {@link java.util.List}, {@link java.util.Map}, or
|
||||
* {@link jakarta.persistence.Tuple}.
|
||||
* </ul>
|
||||
* <p>
|
||||
* If a query has no explicit {@code select} list, the select list
|
||||
* is inferred from the given query result type:
|
||||
* <ul>
|
||||
* <li>if the result type is an entity type, the query must have
|
||||
* exactly one root entity in the {@code from} clause, it must
|
||||
* be assignable to the result type, and the inferred select
|
||||
* list will contain just that entity, or
|
||||
* <li>otherwise, the select list contains every root entity and
|
||||
* every non-{@code fetch} joined entity, and each query result
|
||||
* will be packaged into an instance of the result type, just
|
||||
* as specified above.
|
||||
* </ul>
|
||||
* <p>
|
||||
* The returned {@code Query} may be executed by calling
|
||||
* {@link Query#getResultList()} or {@link Query#getSingleResult()}.
|
||||
|
||||
* @param hqlString The HQL query as a string
|
||||
* @param resultType The {@link Class} object representing the
|
||||
* query result type
|
||||
*
|
||||
* @see jakarta.persistence.EntityManager#createQuery(String)
|
||||
*
|
||||
|
@ -191,14 +292,15 @@ public interface QueryProducer {
|
|||
<R> SelectionQuery<R> createSelectionQuery(String hqlString, Class<R> resultType);
|
||||
|
||||
/**
|
||||
* Create a {@link SelectionQuery} reference for the given Criteria.
|
||||
* Create a {@link SelectionQuery} reference for the given
|
||||
* {@link CriteriaQuery}.
|
||||
*
|
||||
* @see jakarta.persistence.EntityManager#createQuery(CriteriaQuery)
|
||||
*/
|
||||
<R> SelectionQuery<R> createSelectionQuery(CriteriaQuery<R> criteria);
|
||||
|
||||
/**
|
||||
* Create a MutationQuery reference for the given HQL insert,
|
||||
* Create a {@link MutationQuery} reference for the given HQL insert,
|
||||
* update, or delete statement.
|
||||
*
|
||||
* @throws IllegalMutationQueryException if the given HQL query
|
||||
|
@ -234,7 +336,7 @@ public interface QueryProducer {
|
|||
* Create a typed {@link Query} instance for the given named query.
|
||||
* The named query might be defined in HQL or in native SQL.
|
||||
*
|
||||
* @param name the name of a pre-defined, named query
|
||||
* @param name the name of a predefined named query
|
||||
*
|
||||
* @return The {@link Query} instance for manipulation and execution
|
||||
*
|
||||
|
@ -244,8 +346,7 @@ public interface QueryProducer {
|
|||
*
|
||||
* @see jakarta.persistence.EntityManager#createNamedQuery(String)
|
||||
*
|
||||
* @deprecated use {@link #createNamedQuery(String, Class)} or
|
||||
* {@link #createNamedMutationQuery(String)}
|
||||
* @deprecated use {@link #createNamedQuery(String, Class)}
|
||||
*/
|
||||
@Deprecated(since = "6.0") @SuppressWarnings("rawtypes")
|
||||
Query createNamedQuery(String name);
|
||||
|
@ -269,20 +370,29 @@ public interface QueryProducer {
|
|||
<R> Query<R> createNamedQuery(String name, Class<R> resultClass);
|
||||
|
||||
/**
|
||||
* Create a {@link SelectionQuery} instance for the
|
||||
* named {@link jakarta.persistence.NamedQuery}
|
||||
* Create a {@link SelectionQuery} instance for the named
|
||||
* {@link jakarta.persistence.NamedQuery}.
|
||||
*
|
||||
* @throws IllegalSelectQueryException if the given HQL query is a select query
|
||||
* @implNote This method interprets some queries with an implicit
|
||||
* {@code select} list in a quite unintuitive way. In some future
|
||||
* release, this method will be modified to throw an exception
|
||||
* when passed a query with a missing {@code select}. For now, use
|
||||
* {@link #createNamedSelectionQuery(String, Class)} to avoid
|
||||
* ambiguity.
|
||||
*
|
||||
* @throws IllegalSelectQueryException if the given HQL query is not a select query
|
||||
* @throws UnknownNamedQueryException if no query has been defined with the given name
|
||||
*
|
||||
* @deprecated use {@link #createNamedSelectionQuery(String, Class)}
|
||||
*/
|
||||
@Deprecated(since = "6.3")
|
||||
SelectionQuery<?> createNamedSelectionQuery(String name);
|
||||
|
||||
/**
|
||||
* Create a {@link SelectionQuery} instance for the
|
||||
* named {@link jakarta.persistence.NamedQuery} with the expected
|
||||
* result-type
|
||||
* Create a {@link SelectionQuery} instance for the named
|
||||
* {@link jakarta.persistence.NamedQuery} with the given result type.
|
||||
*
|
||||
* @throws IllegalSelectQueryException if the given HQL query is a select query
|
||||
* @throws IllegalSelectQueryException if the given HQL query is not a select query
|
||||
* @throws UnknownNamedQueryException if no query has been defined with the given name
|
||||
*/
|
||||
<R> SelectionQuery<R> createNamedSelectionQuery(String name, Class<R> resultType);
|
||||
|
@ -301,7 +411,7 @@ public interface QueryProducer {
|
|||
/**
|
||||
* Create a {@link Query} instance for the named query.
|
||||
*
|
||||
* @param queryName the name of a pre-defined, named query
|
||||
* @param queryName the name of a predefined named query
|
||||
*
|
||||
* @return The {@link Query} instance for manipulation and execution
|
||||
*
|
||||
|
@ -317,7 +427,7 @@ public interface QueryProducer {
|
|||
/**
|
||||
* Get a {@link NativeQuery} instance for a named native SQL query
|
||||
*
|
||||
* @param name The name of the pre-defined query
|
||||
* @param name The name of the predefined query
|
||||
*
|
||||
* @return The {@link NativeQuery} instance for manipulation and execution
|
||||
*
|
||||
|
@ -329,7 +439,7 @@ public interface QueryProducer {
|
|||
/**
|
||||
* Get a {@link NativeQuery} instance for a named native SQL query
|
||||
*
|
||||
* @param name The name of the pre-defined query
|
||||
* @param name The name of the predefined query
|
||||
*
|
||||
* @return The {@link NativeQuery} instance for manipulation and execution
|
||||
*
|
||||
|
|
|
@ -31,6 +31,7 @@ import java.util.Locale;
|
|||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.hibernate.AssertionFailure;
|
||||
import org.hibernate.QueryException;
|
||||
import org.hibernate.boot.registry.classloading.spi.ClassLoaderService;
|
||||
import org.hibernate.boot.registry.classloading.spi.ClassLoadingException;
|
||||
|
@ -1157,31 +1158,56 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
|
|||
}
|
||||
|
||||
protected SqmSelectClause buildInferredSelectClause(SqmFromClause fromClause) {
|
||||
// for now, this is slightly different to the legacy behavior where
|
||||
// the root and each non-fetched-join was selected. For now, here, we simply
|
||||
// select the root
|
||||
final SqmSelectClause selectClause;
|
||||
if ( fromClause.getNumberOfRoots() == 0 ) {
|
||||
// should be impossible to get here
|
||||
throw new AssertionFailure( "query has no 'select' clause, and no root entities");
|
||||
}
|
||||
|
||||
final boolean expectingArray = expectedResultType != null && expectedResultType.isArray();
|
||||
if ( expectingArray ) {
|
||||
// triggers legacy interpretation of returning all roots
|
||||
// and non-fetched joins
|
||||
selectClause = new SqmSelectClause(
|
||||
false,
|
||||
creationContext.getNodeBuilder()
|
||||
);
|
||||
final NodeBuilder nodeBuilder = creationContext.getNodeBuilder();
|
||||
|
||||
final SqmSelectClause selectClause;
|
||||
final boolean singleEntityResult;
|
||||
if ( expectedResultType == null ) {
|
||||
// no result type was specified
|
||||
// - if there is a single root entity return the entity,
|
||||
// even if it has non-fetch joins (ugh!)
|
||||
// - otherwise, return all entities in an Object[] array,
|
||||
// including non-fetch joins
|
||||
selectClause = new SqmSelectClause( false, nodeBuilder );
|
||||
singleEntityResult = fromClause.getNumberOfRoots() == 1;
|
||||
}
|
||||
else {
|
||||
selectClause = new SqmSelectClause(
|
||||
false,
|
||||
fromClause.getNumberOfRoots(),
|
||||
creationContext.getNodeBuilder()
|
||||
);
|
||||
singleEntityResult = creationContext.getJpaMetamodel().findEntityType( expectedResultType ) != null;
|
||||
if ( singleEntityResult ) {
|
||||
// the result type is an entity class
|
||||
if ( fromClause.getNumberOfRoots() > 1 ) {
|
||||
// multiple root entities
|
||||
throw new SemanticException( "query has no 'select' clause, and multiple root entities, but query result type is an entity class"
|
||||
+ " (specify an explicit 'select' list, or a different result type, for example, 'Object[].class')");
|
||||
}
|
||||
else {
|
||||
final SqmRoot<?> sqmRoot = fromClause.getRoots().get(0);
|
||||
if ( sqmRoot instanceof SqmCteRoot ) {
|
||||
throw new SemanticException( "query has no 'select' clause, and the 'from' clause refers to a CTE, but query result type is an entity class"
|
||||
+ " (specify an explicit 'select' list)");
|
||||
}
|
||||
else {
|
||||
// exactly one root entity, return it
|
||||
// (joined entities are not returned)
|
||||
selectClause = new SqmSelectClause( false, 1, nodeBuilder );
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// the result type is not an entity class
|
||||
// return all root entities and non-fetch joins
|
||||
selectClause = new SqmSelectClause( false, nodeBuilder );
|
||||
}
|
||||
}
|
||||
|
||||
fromClause.visitRoots( (sqmRoot) -> {
|
||||
selectClause.addSelection( new SqmSelection<>( sqmRoot, sqmRoot.getAlias(), creationContext.getNodeBuilder() ) );
|
||||
if ( expectingArray ) {
|
||||
selectClause.addSelection( new SqmSelection<>( sqmRoot, sqmRoot.getAlias(), nodeBuilder) );
|
||||
if ( !singleEntityResult ) {
|
||||
applyJoinsToInferredSelectClause( sqmRoot, selectClause );
|
||||
}
|
||||
} );
|
||||
|
|
|
@ -63,7 +63,7 @@ public class QueryInterpretationCacheDisabledImpl implements QueryInterpretation
|
|||
public HqlInterpretation resolveHqlInterpretation(String queryString, Class<?> expectedResultType, Function<String, SqmStatement<?>> creator) {
|
||||
final StatisticsImplementor statistics = statisticsSupplier.get();
|
||||
final boolean stats = statistics.isStatisticsEnabled();
|
||||
final long startTime = ( stats ) ? System.nanoTime() : 0L;
|
||||
final long startTime = stats ? System.nanoTime() : 0L;
|
||||
final SqmStatement<?> sqmStatement = creator.apply( queryString );
|
||||
|
||||
final DomainParameterXref domainParameterXref;
|
||||
|
|
|
@ -9,7 +9,6 @@ package org.hibernate.query.internal;
|
|||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import jakarta.persistence.Tuple;
|
||||
|
||||
import org.hibernate.internal.util.collections.BoundedConcurrentHashMap;
|
||||
import org.hibernate.query.QueryLogging;
|
||||
|
@ -23,6 +22,7 @@ import org.hibernate.query.spi.SimpleHqlInterpretationImpl;
|
|||
import org.hibernate.query.sql.spi.ParameterInterpretation;
|
||||
import org.hibernate.query.sqm.internal.DomainParameterXref;
|
||||
import org.hibernate.query.sqm.tree.SqmStatement;
|
||||
import org.hibernate.query.sqm.tree.select.SqmSelectStatement;
|
||||
import org.hibernate.stat.spi.StatisticsImplementor;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
|
@ -99,6 +99,45 @@ public class QueryInterpretationCacheStandardImpl implements QueryInterpretation
|
|||
log.tracef( "QueryPlan#cacheNonSelectQueryPlan(%s)", key );
|
||||
}
|
||||
|
||||
private static String typedKey(String queryString, Class<?> expectedResultType) {
|
||||
return expectedResultType.getName() + ":" + queryString;
|
||||
}
|
||||
|
||||
private void putInCache(String queryString, Class<?> expectedResultType, HqlInterpretation hqlInterpretation) {
|
||||
final SqmStatement<?> statement = hqlInterpretation.getSqmStatement();
|
||||
if ( statement instanceof SqmSelectStatement && expectedResultType != null ) {
|
||||
hqlInterpretationCache.put( typedKey( queryString, expectedResultType ), hqlInterpretation);
|
||||
}
|
||||
else {
|
||||
hqlInterpretationCache.put( queryString, hqlInterpretation );
|
||||
}
|
||||
}
|
||||
|
||||
private HqlInterpretation getFromCache(String queryString, Class<?> expectedResultType) {
|
||||
if ( expectedResultType != null ) {
|
||||
final HqlInterpretation typedHqlInterpretation =
|
||||
hqlInterpretationCache.get( typedKey( queryString, expectedResultType ) );
|
||||
if ( typedHqlInterpretation != null ) {
|
||||
return typedHqlInterpretation;
|
||||
}
|
||||
}
|
||||
|
||||
final HqlInterpretation hqlInterpretation = hqlInterpretationCache.get( queryString );
|
||||
if ( hqlInterpretation != null ) {
|
||||
final SqmStatement<?> statement = hqlInterpretation.getSqmStatement();
|
||||
if ( statement instanceof SqmSelectStatement && expectedResultType != null ) {
|
||||
final Class<?> resultType = ((SqmSelectStatement<?>) statement).getSelection().getJavaType();
|
||||
return expectedResultType.equals( resultType ) ? hqlInterpretation : null;
|
||||
}
|
||||
else {
|
||||
return hqlInterpretation;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public HqlInterpretation resolveHqlInterpretation(
|
||||
String queryString,
|
||||
|
@ -106,17 +145,7 @@ public class QueryInterpretationCacheStandardImpl implements QueryInterpretation
|
|||
Function<String, SqmStatement<?>> creator) {
|
||||
log.tracef( "QueryPlan#resolveHqlInterpretation( `%s` )", queryString );
|
||||
|
||||
final String cacheKey;
|
||||
if ( expectedResultType != null
|
||||
&& ( expectedResultType.isArray() || Tuple.class.isAssignableFrom( expectedResultType ) ) ) {
|
||||
cacheKey = "multi_" + queryString;
|
||||
}
|
||||
else {
|
||||
cacheKey = queryString;
|
||||
}
|
||||
|
||||
|
||||
final HqlInterpretation existing = hqlInterpretationCache.get( cacheKey );
|
||||
final HqlInterpretation existing = getFromCache( queryString, expectedResultType );
|
||||
if ( existing != null ) {
|
||||
final StatisticsImplementor statistics = statisticsSupplier.get();
|
||||
if ( statistics.isStatisticsEnabled() ) {
|
||||
|
@ -124,10 +153,12 @@ public class QueryInterpretationCacheStandardImpl implements QueryInterpretation
|
|||
}
|
||||
return existing;
|
||||
}
|
||||
|
||||
final HqlInterpretation hqlInterpretation = createHqlInterpretation( queryString, creator, statisticsSupplier );
|
||||
hqlInterpretationCache.put( cacheKey, hqlInterpretation );
|
||||
return hqlInterpretation;
|
||||
else {
|
||||
final HqlInterpretation hqlInterpretation =
|
||||
createHqlInterpretation( queryString, creator, statisticsSupplier );
|
||||
putInCache( queryString, expectedResultType, hqlInterpretation );
|
||||
return hqlInterpretation;
|
||||
}
|
||||
}
|
||||
|
||||
protected static HqlInterpretation createHqlInterpretation(
|
||||
|
@ -136,7 +167,7 @@ public class QueryInterpretationCacheStandardImpl implements QueryInterpretation
|
|||
Supplier<StatisticsImplementor> statisticsSupplier) {
|
||||
final StatisticsImplementor statistics = statisticsSupplier.get();
|
||||
final boolean stats = statistics.isStatisticsEnabled();
|
||||
final long startTime = ( stats ) ? System.nanoTime() : 0L;
|
||||
final long startTime = stats ? System.nanoTime() : 0L;
|
||||
|
||||
final SqmStatement<?> sqmStatement = creator.apply( queryString );
|
||||
final ParameterMetadataImplementor parameterMetadata;
|
||||
|
|
|
@ -106,7 +106,7 @@ public class Builders {
|
|||
Class<R> jdbcJavaType,
|
||||
AttributeConverter<O, R> converter,
|
||||
SessionFactoryImplementor sessionFactory) {
|
||||
return new DynamicResultBuilderBasicConverted( columnAlias, domainJavaType, jdbcJavaType, converter, sessionFactory );
|
||||
return new DynamicResultBuilderBasicConverted<>( columnAlias, domainJavaType, jdbcJavaType, converter, sessionFactory );
|
||||
}
|
||||
|
||||
public static <R> ResultBuilder converted(
|
||||
|
@ -123,7 +123,7 @@ public class Builders {
|
|||
Class<R> jdbcJavaType,
|
||||
Class<? extends AttributeConverter<O,R>> converterJavaType,
|
||||
SessionFactoryImplementor sessionFactory) {
|
||||
return new DynamicResultBuilderBasicConverted( columnAlias, domainJavaType, jdbcJavaType, converterJavaType, sessionFactory );
|
||||
return new DynamicResultBuilderBasicConverted<>( columnAlias, domainJavaType, jdbcJavaType, converterJavaType, sessionFactory );
|
||||
}
|
||||
|
||||
public static ResultBuilderBasicValued scalar(int position) {
|
||||
|
@ -233,10 +233,8 @@ public class Builders {
|
|||
String entityName,
|
||||
LockMode explicitLockMode,
|
||||
SessionFactoryImplementor sessionFactory) {
|
||||
final RuntimeMetamodels runtimeMetamodels = sessionFactory.getRuntimeMetamodels();
|
||||
final EntityMappingType entityMapping = runtimeMetamodels.getEntityMappingType( entityName );
|
||||
|
||||
return new DynamicResultBuilderEntityCalculated( entityMapping, tableAlias, explicitLockMode, sessionFactory );
|
||||
final EntityMappingType entityMapping = sessionFactory.getRuntimeMetamodels().getEntityMappingType( entityName );
|
||||
return new DynamicResultBuilderEntityCalculated( entityMapping, tableAlias, explicitLockMode );
|
||||
}
|
||||
|
||||
public static DynamicFetchBuilderLegacy fetch(String tableAlias, String ownerTableAlias, String joinPropertyName) {
|
||||
|
@ -252,9 +250,9 @@ public class Builders {
|
|||
public static ResultBuilder resultClassBuilder(
|
||||
Class<?> resultMappingClass,
|
||||
SessionFactoryImplementor sessionFactory) {
|
||||
final MappingMetamodelImplementor mappingMetamodel = sessionFactory
|
||||
.getRuntimeMetamodels()
|
||||
.getMappingMetamodel();
|
||||
final MappingMetamodelImplementor mappingMetamodel =
|
||||
sessionFactory.getRuntimeMetamodels()
|
||||
.getMappingMetamodel();
|
||||
final EntityMappingType entityMappingType = mappingMetamodel.findEntityDescriptor( resultMappingClass );
|
||||
if ( entityMappingType != null ) {
|
||||
// the resultClass is an entity
|
||||
|
|
|
@ -9,7 +9,6 @@ package org.hibernate.query.results.dynamic;
|
|||
import java.util.function.BiFunction;
|
||||
|
||||
import org.hibernate.LockMode;
|
||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
import org.hibernate.metamodel.mapping.EntityMappingType;
|
||||
import org.hibernate.query.NativeQuery;
|
||||
import org.hibernate.query.results.DomainResultCreationStateImpl;
|
||||
|
@ -35,18 +34,14 @@ public class DynamicResultBuilderEntityCalculated implements DynamicResultBuilde
|
|||
private final String tableAlias;
|
||||
private final LockMode explicitLockMode;
|
||||
|
||||
private final SessionFactoryImplementor sessionFactory;
|
||||
|
||||
public DynamicResultBuilderEntityCalculated(
|
||||
EntityMappingType entityMapping,
|
||||
String tableAlias,
|
||||
LockMode explicitLockMode,
|
||||
SessionFactoryImplementor sessionFactory) {
|
||||
LockMode explicitLockMode) {
|
||||
this.entityMapping = entityMapping;
|
||||
this.navigablePath = new NavigablePath( entityMapping.getEntityName() );
|
||||
this.tableAlias = tableAlias;
|
||||
this.explicitLockMode = explicitLockMode;
|
||||
this.sessionFactory = sessionFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -45,11 +45,13 @@ public class ImplicitResultClassBuilder implements ResultBuilder {
|
|||
DomainResultCreationState domainResultCreationState) {
|
||||
assert resultPosition == 0;
|
||||
|
||||
final SessionFactoryImplementor sessionFactory = domainResultCreationState.getSqlAstCreationState()
|
||||
.getCreationContext()
|
||||
.getSessionFactory();
|
||||
final SessionFactoryImplementor sessionFactory =
|
||||
domainResultCreationState.getSqlAstCreationState()
|
||||
.getCreationContext()
|
||||
.getSessionFactory();
|
||||
final TypeConfiguration typeConfiguration = sessionFactory.getTypeConfiguration();
|
||||
final SqlExpressionResolver sqlExpressionResolver = domainResultCreationState.getSqlAstCreationState().getSqlExpressionResolver();
|
||||
final SqlExpressionResolver sqlExpressionResolver =
|
||||
domainResultCreationState.getSqlAstCreationState().getSqlExpressionResolver();
|
||||
|
||||
final int jdbcResultPosition = 1;
|
||||
|
||||
|
|
|
@ -1014,6 +1014,11 @@ public class NativeQueryImpl<R>
|
|||
return addEntity( entityType.getName() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public NativeQueryImplementor<R> addEntity(Class<R> entityType, LockMode lockMode) {
|
||||
return addEntity( StringHelper.unqualify( entityType.getName() ), entityType.getName(), lockMode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NativeQueryImplementor<R> addEntity(String tableAlias, @SuppressWarnings("rawtypes") Class entityClass) {
|
||||
return addEntity( tableAlias, entityClass.getName() );
|
||||
|
|
|
@ -112,6 +112,8 @@ public interface NativeQueryImplementor<R> extends QueryImplementor<R>, NativeQu
|
|||
@Override
|
||||
NativeQueryImplementor<R> addEntity(@SuppressWarnings("rawtypes") Class entityType);
|
||||
|
||||
NativeQueryImplementor<R> addEntity(Class<R> entityType, LockMode lockMode);
|
||||
|
||||
@Override
|
||||
NativeQueryImplementor<R> addEntity(String tableAlias, @SuppressWarnings("rawtypes") Class entityType);
|
||||
|
||||
|
|
|
@ -238,7 +238,7 @@ public abstract class AbstractSqmFrom<O,T> extends AbstractSqmPath<T> implements
|
|||
|
||||
@Override
|
||||
public boolean hasJoins() {
|
||||
return !( joins == null || joins.isEmpty() );
|
||||
return joins != null && !joins.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -330,7 +330,6 @@ public abstract class AbstractSqmFrom<O,T> extends AbstractSqmPath<T> implements
|
|||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <A> SqmSingularJoin<T, A> join(SingularAttribute<? super T, A> attribute, JoinType jt) {
|
||||
final SqmSingularJoin<T, A> join = buildSingularJoin( (SingularPersistentAttribute<? super T, A>) attribute, SqmJoinType.from( jt ), false );
|
||||
addSqmJoin( join );
|
||||
|
@ -646,7 +645,6 @@ public abstract class AbstractSqmFrom<O,T> extends AbstractSqmPath<T> implements
|
|||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <A> SqmSingularJoin<T, A> fetch(SingularAttribute<? super T, A> attribute, JoinType jt) {
|
||||
final SqmSingularJoin<T, A> join = buildSingularJoin(
|
||||
(SingularPersistentAttribute<? super T, A>) attribute,
|
||||
|
@ -663,7 +661,6 @@ public abstract class AbstractSqmFrom<O,T> extends AbstractSqmPath<T> implements
|
|||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <A> SqmAttributeJoin<T, A> fetch(PluralAttribute<? super T, ?, A> attribute, JoinType jt) {
|
||||
return buildJoin(
|
||||
(PluralPersistentAttribute<? super T, ?, A>) attribute,
|
||||
|
|
|
@ -24,6 +24,7 @@ import org.junit.jupiter.api.BeforeEach;
|
|||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
|
@ -34,6 +35,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
|||
public class ImplicitSelectWithJoinTests {
|
||||
private static final String HQL = "from Product p join p.vendor v where v.name like '%Steve%'";
|
||||
private static final String HQL2 = "select p " + HQL;
|
||||
private static final String HQL3 = "from Product q join q.vendor w, Product p join p.vendor v where v.name like '%Steve%' and w.name like '%Gavin%'";
|
||||
|
||||
@Test
|
||||
public void testNoExpectedType(SessionFactoryScope scope) {
|
||||
|
@ -79,8 +81,93 @@ public class ImplicitSelectWithJoinTests {
|
|||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testArrayResultNoResultType(SessionFactoryScope scope) {
|
||||
scope.inTransaction( (session) -> {
|
||||
final SelectionQuery<?> query = session.createSelectionQuery( HQL3 );
|
||||
|
||||
{
|
||||
final List<?> results = query.list();
|
||||
assertThat( results ).hasSize( 1 );
|
||||
final Object result = results.get( 0 );
|
||||
assertThat( result ).isNotNull();
|
||||
assertInstanceOf( Object[].class, result );
|
||||
assertThat( (Object[]) result ).hasSize(4);
|
||||
assertThat( (Object[]) result ).hasExactlyElementsOfTypes(Product.class, Vendor.class, Product.class, Vendor.class);
|
||||
}
|
||||
|
||||
{
|
||||
final ScrollableResults<?> results = query.scroll();
|
||||
assertThat( results.next() ).isTrue();
|
||||
final Object result = results.get();
|
||||
assertThat( result ).isNotNull();
|
||||
assertInstanceOf( Object[].class, result );
|
||||
assertThat( (Object[]) result ).hasSize(4);
|
||||
assertThat( (Object[]) result ).hasExactlyElementsOfTypes(Product.class, Vendor.class, Product.class, Vendor.class);
|
||||
assertThat( results.next() ).isFalse();
|
||||
}
|
||||
} );
|
||||
|
||||
// frankly, this would be more consistent and more backward-compatible
|
||||
// scope.inTransaction( (session) -> {
|
||||
// final SelectionQuery<?> query = session.createSelectionQuery( HQL );
|
||||
//
|
||||
// {
|
||||
// final List<?> results = query.list();
|
||||
// assertThat( results ).hasSize( 1 );
|
||||
// final Object result = results.get( 0 );
|
||||
// assertThat( result ).isNotNull();
|
||||
// assertInstanceOf( Object[].class, result );
|
||||
// assertThat( (Object[]) result ).hasSize(2);
|
||||
// assertThat( (Object[]) result ).hasExactlyElementsOfTypes(Product.class, Vendor.class);
|
||||
// }
|
||||
//
|
||||
// {
|
||||
// final ScrollableResults<?> results = query.scroll();
|
||||
// assertThat( results.next() ).isTrue();
|
||||
// final Object result = results.get();
|
||||
// assertThat( result ).isNotNull();
|
||||
// assertInstanceOf( Object[].class, result );
|
||||
// assertThat( (Object[]) result ).hasSize(2);
|
||||
// assertThat( (Object[]) result ).hasExactlyElementsOfTypes(Product.class, Vendor.class);
|
||||
// assertThat( results.next() ).isFalse();
|
||||
// }
|
||||
// } );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testArrayResult(SessionFactoryScope scope) {
|
||||
scope.inTransaction( (session) -> {
|
||||
final SelectionQuery<Object[]> query = session.createSelectionQuery( HQL3, Object[].class );
|
||||
|
||||
{
|
||||
final List<Object[]> results = query.list();
|
||||
assertThat( results ).hasSize( 1 );
|
||||
final Object[] result = results.get( 0 );
|
||||
assertThat( result ).isNotNull();
|
||||
assertThat( result ).hasSize( 4 );
|
||||
assertThat( result[ 0 ] ).isNotNull();
|
||||
assertThat( result[ 1 ] ).isNotNull();
|
||||
assertThat( result[ 2 ] ).isNotNull();
|
||||
assertThat( result[ 3 ] ).isNotNull();
|
||||
assertThat( result ).hasExactlyElementsOfTypes(Product.class, Vendor.class, Product.class, Vendor.class);
|
||||
}
|
||||
|
||||
{
|
||||
final ScrollableResults<Object[]> results = query.scroll();
|
||||
assertThat( results.next() ).isTrue();
|
||||
final Object[] result = results.get();
|
||||
assertThat( results.next() ).isFalse();
|
||||
assertThat( result ).isNotNull();
|
||||
assertThat( result ).hasSize( 4 );
|
||||
assertThat( result[ 0 ] ).isNotNull();
|
||||
assertThat( result[ 1 ] ).isNotNull();
|
||||
assertThat( result[ 2 ] ).isNotNull();
|
||||
assertThat( result[ 3 ] ).isNotNull();
|
||||
assertThat( result ).hasExactlyElementsOfTypes(Product.class, Vendor.class, Product.class, Vendor.class);
|
||||
}
|
||||
} );
|
||||
|
||||
scope.inTransaction( (session) -> {
|
||||
final SelectionQuery<Object[]> query = session.createSelectionQuery( HQL, Object[].class );
|
||||
|
||||
|
@ -92,6 +179,7 @@ public class ImplicitSelectWithJoinTests {
|
|||
assertThat( result ).hasSize( 2 );
|
||||
assertThat( result[ 0 ] ).isNotNull();
|
||||
assertThat( result[ 1 ] ).isNotNull();
|
||||
assertThat( result ).hasExactlyElementsOfTypes(Product.class, Vendor.class);
|
||||
}
|
||||
|
||||
{
|
||||
|
@ -103,6 +191,7 @@ public class ImplicitSelectWithJoinTests {
|
|||
assertThat( result ).hasSize( 2 );
|
||||
assertThat( result[ 0 ] ).isNotNull();
|
||||
assertThat( result[ 1 ] ).isNotNull();
|
||||
assertThat( result ).hasExactlyElementsOfTypes(Product.class, Vendor.class);
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
@ -161,10 +250,14 @@ public class ImplicitSelectWithJoinTests {
|
|||
@BeforeEach
|
||||
public void prepareTestData(SessionFactoryScope scope) {
|
||||
scope.inTransaction( (session) -> {
|
||||
final Vendor vendor = new Vendor( 1, "Steve's Curios", "Acme Corp." );
|
||||
final Product product = new Product( 10, UUID.fromString( "53886a8a-7082-4879-b430-25cb94415be8" ), vendor );
|
||||
session.persist( vendor );
|
||||
session.persist( product );
|
||||
final Vendor steve = new Vendor( 1, "Steve's Curios", "Acme Corp." );
|
||||
final Product product1 = new Product( 10, UUID.fromString( "53886a8a-7082-4879-b430-25cb94415be8" ), steve );
|
||||
final Vendor gavin = new Vendor( 2, "Gavin & Associates", "Acme Corp." );
|
||||
final Product product2 = new Product( 11, UUID.fromString( "53886a8b-3083-4879-b431-25cb95515be9" ), gavin );
|
||||
session.persist( steve );
|
||||
session.persist( product1 );
|
||||
session.persist( gavin );
|
||||
session.persist( product2 );
|
||||
} );
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue