HHH-15133 - Use specified result-type to better infer "shape" of query results with implicit selections
This commit is contained in:
parent
cac18ae0c7
commit
bec32ebbc4
|
@ -2447,7 +2447,7 @@ public class HQLTest extends BaseEntityManagerFunctionalTestCase {
|
|||
Object[].class)
|
||||
.getResultList();
|
||||
//end::hql-relational-comparisons-example[]
|
||||
assertEquals(2, phonePayments.size());
|
||||
assertEquals(3, phonePayments.size());
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ import jakarta.persistence.Tuple;
|
|||
import jakarta.persistence.TupleElement;
|
||||
|
||||
import org.hibernate.HibernateException;
|
||||
import org.hibernate.query.TypedTupleTransformer;
|
||||
import org.hibernate.transform.ResultTransformer;
|
||||
|
||||
/**
|
||||
|
@ -21,13 +22,18 @@ import org.hibernate.transform.ResultTransformer;
|
|||
*
|
||||
* @author Arnold Galovics
|
||||
*/
|
||||
public class NativeQueryTupleTransformer implements ResultTransformer<Tuple> {
|
||||
public class NativeQueryTupleTransformer implements ResultTransformer<Tuple>, TypedTupleTransformer<Tuple> {
|
||||
|
||||
@Override
|
||||
public Tuple transformTuple(Object[] tuple, String[] aliases) {
|
||||
return new NativeTupleImpl( tuple, aliases );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<Tuple> getTransformedType() {
|
||||
return Tuple.class;
|
||||
}
|
||||
|
||||
private static class NativeTupleElementImpl<X> implements TupleElement<X> {
|
||||
|
||||
private final Class<? extends X> javaType;
|
||||
|
|
|
@ -211,6 +211,11 @@ public class LoaderSqlAstCreationState
|
|||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean isDeDuplicationEnabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean isResultCachingEnabled() {
|
||||
return false;
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Extension to TupleTransformer exposing the transformation target type.
|
||||
*
|
||||
* @apiNote This is mainly intended for use in equality checking while applying
|
||||
* result de-duplication for queries.
|
||||
*
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public interface TypedTupleTransformer<T> extends TupleTransformer<T> {
|
||||
/**
|
||||
* The type resulting from this transformation
|
||||
*/
|
||||
Class<T> getTransformedType();
|
||||
}
|
|
@ -17,9 +17,9 @@ import org.hibernate.LockOptions;
|
|||
import org.hibernate.graph.GraphSemantic;
|
||||
import org.hibernate.graph.spi.AppliedGraph;
|
||||
import org.hibernate.graph.spi.RootGraphImplementor;
|
||||
import org.hibernate.query.spi.Limit;
|
||||
import org.hibernate.query.ResultListTransformer;
|
||||
import org.hibernate.query.TupleTransformer;
|
||||
import org.hibernate.query.spi.Limit;
|
||||
import org.hibernate.query.spi.MutableQueryOptions;
|
||||
|
||||
/**
|
||||
|
@ -43,6 +43,7 @@ public class QueryOptionsImpl implements MutableQueryOptions, AppliedGraph {
|
|||
|
||||
private TupleTransformer tupleTransformer;
|
||||
private ResultListTransformer resultListTransformer;
|
||||
private Boolean deDupEnabled;
|
||||
|
||||
private RootGraphImplementor<?> rootGraph;
|
||||
private GraphSemantic graphSemantic;
|
||||
|
@ -160,6 +161,15 @@ public class QueryOptionsImpl implements MutableQueryOptions, AppliedGraph {
|
|||
return resultListTransformer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean isDeDuplicationEnabled() {
|
||||
return deDupEnabled;
|
||||
}
|
||||
|
||||
public void setDeDuplicationEnabled(boolean enabled) {
|
||||
this.deDupEnabled = enabled;
|
||||
}
|
||||
|
||||
public void setResultCacheRegionName(String resultCacheRegionName) {
|
||||
this.resultCacheRegionName = resultCacheRegionName;
|
||||
}
|
||||
|
|
|
@ -58,6 +58,11 @@ public class DelegatingQueryOptions implements QueryOptions {
|
|||
return queryOptions.getResultListTransformer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean isDeDuplicationEnabled() {
|
||||
return queryOptions.isDeDuplicationEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean isResultCachingEnabled() {
|
||||
return queryOptions.isResultCachingEnabled();
|
||||
|
|
|
@ -62,6 +62,13 @@ public interface QueryOptions {
|
|||
*/
|
||||
ResultListTransformer<?> getResultListTransformer();
|
||||
|
||||
Boolean isDeDuplicationEnabled();
|
||||
|
||||
default boolean shouldApplyDeDuplication() {
|
||||
final Boolean setting = isDeDuplicationEnabled();
|
||||
return setting != null && setting;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should results from the query be cached?
|
||||
*
|
||||
|
|
|
@ -80,6 +80,11 @@ public abstract class QueryOptionsAdapter implements QueryOptions {
|
|||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean isDeDuplicationEnabled() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getResultCacheRegionName() {
|
||||
return null;
|
||||
|
|
|
@ -105,7 +105,7 @@ public class NativeSelectQueryPlanImpl<R> implements NativeSelectQueryPlan<R> {
|
|||
jdbcParameterBindings,
|
||||
SqmJdbcExecutionContextAdapter.usingLockingAndPaging( executionContext ),
|
||||
null,
|
||||
ListResultsConsumer.UniqueSemantic.NONE
|
||||
ListResultsConsumer.UniqueSemantic.NEVER
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -122,7 +122,9 @@ public class ConcreteSqmSelectQueryPlan<R> implements SelectQueryPlan<R> {
|
|||
}
|
||||
},
|
||||
rowTransformer,
|
||||
ListResultsConsumer.UniqueSemantic.FILTER
|
||||
queryOptions.shouldApplyDeDuplication()
|
||||
? ListResultsConsumer.UniqueSemantic.FILTER
|
||||
: ListResultsConsumer.UniqueSemantic.NONE
|
||||
);
|
||||
}
|
||||
finally {
|
||||
|
@ -205,13 +207,7 @@ public class ConcreteSqmSelectQueryPlan<R> implements SelectQueryPlan<R> {
|
|||
|
||||
// NOTE : if we get here we have a resultType of some kind
|
||||
|
||||
if ( queryOptions.getTupleTransformer() != null ) {
|
||||
// aside from checking the type parameters for the given TupleTransformer
|
||||
// there is not a decent way to verify that the TupleTransformer returns
|
||||
// the same type. We rely on the API here and assume the best
|
||||
return makeRowTransformerTupleTransformerAdapter( sqm, queryOptions );
|
||||
}
|
||||
else if ( selections.size() > 1 ) {
|
||||
if ( selections.size() > 1 ) {
|
||||
throw new IllegalQueryOperationException( "Query defined multiple selections, return cannot be typed (other that Object[] or Tuple)" );
|
||||
}
|
||||
else {
|
||||
|
|
|
@ -230,6 +230,7 @@ public class OutputsImpl implements Outputs {
|
|||
executionContext,
|
||||
null,
|
||||
RowTransformerStandardImpl.INSTANCE,
|
||||
null,
|
||||
jdbcValues
|
||||
);
|
||||
|
||||
|
|
|
@ -68,16 +68,12 @@ import org.hibernate.type.BasicType;
|
|||
import org.hibernate.type.descriptor.java.JavaType;
|
||||
|
||||
/**
|
||||
* Standard JdbcSelectExecutor implementation used by Hibernate,
|
||||
* through {@link JdbcSelectExecutorStandardImpl#INSTANCE}
|
||||
*
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public class JdbcSelectExecutorStandardImpl implements JdbcSelectExecutor {
|
||||
// todo (6.0) : Make resolving these executors swappable - JdbcServices?
|
||||
// Since JdbcServices is just a "composition service", this is actually
|
||||
// a very good option...
|
||||
|
||||
// todo (6.0) : where do affected-table-names get checked for up-to-date?
|
||||
// who is responsible for that? Here?
|
||||
|
||||
/**
|
||||
* Singleton access
|
||||
*/
|
||||
|
@ -89,6 +85,7 @@ public class JdbcSelectExecutorStandardImpl implements JdbcSelectExecutor {
|
|||
JdbcParameterBindings jdbcParameterBindings,
|
||||
ExecutionContext executionContext,
|
||||
RowTransformer<R> rowTransformer,
|
||||
Class<R> domainResultType,
|
||||
ListResultsConsumer.UniqueSemantic uniqueSemantic) {
|
||||
// Only do auto flushing for top level queries
|
||||
return executeQuery(
|
||||
|
@ -96,6 +93,7 @@ public class JdbcSelectExecutorStandardImpl implements JdbcSelectExecutor {
|
|||
jdbcParameterBindings,
|
||||
executionContext,
|
||||
rowTransformer,
|
||||
domainResultType,
|
||||
(sql) -> executionContext.getSession()
|
||||
.getJdbcCoordinator()
|
||||
.getStatementPreparer()
|
||||
|
@ -118,6 +116,7 @@ public class JdbcSelectExecutorStandardImpl implements JdbcSelectExecutor {
|
|||
jdbcParameterBindings,
|
||||
executionContext,
|
||||
rowTransformer,
|
||||
null,
|
||||
(sql) -> executionContext.getSession().getJdbcCoordinator().getStatementPreparer().prepareQueryStatement(
|
||||
sql,
|
||||
false,
|
||||
|
@ -152,6 +151,7 @@ public class JdbcSelectExecutorStandardImpl implements JdbcSelectExecutor {
|
|||
JdbcParameterBindings jdbcParameterBindings,
|
||||
ExecutionContext executionContext,
|
||||
RowTransformer<R> rowTransformer,
|
||||
Class<R> domainResultType,
|
||||
Function<String, PreparedStatement> statementCreator,
|
||||
ResultsConsumer<T, R> resultsConsumer) {
|
||||
final PersistenceContext persistenceContext = executionContext.getSession().getPersistenceContext();
|
||||
|
@ -168,6 +168,7 @@ public class JdbcSelectExecutorStandardImpl implements JdbcSelectExecutor {
|
|||
jdbcParameterBindings,
|
||||
executionContext,
|
||||
rowTransformer,
|
||||
domainResultType,
|
||||
statementCreator,
|
||||
resultsConsumer
|
||||
);
|
||||
|
@ -184,6 +185,7 @@ public class JdbcSelectExecutorStandardImpl implements JdbcSelectExecutor {
|
|||
JdbcParameterBindings jdbcParameterBindings,
|
||||
ExecutionContext executionContext,
|
||||
RowTransformer<R> rowTransformer,
|
||||
Class<R> domainResultType,
|
||||
Function<String, PreparedStatement> statementCreator,
|
||||
ResultsConsumer<T, R> resultsConsumer) {
|
||||
return doExecuteQuery(
|
||||
|
@ -191,6 +193,7 @@ public class JdbcSelectExecutorStandardImpl implements JdbcSelectExecutor {
|
|||
jdbcParameterBindings,
|
||||
getScrollContext( executionContext, executionContext.getSession().getPersistenceContext() ),
|
||||
rowTransformer,
|
||||
domainResultType,
|
||||
statementCreator,
|
||||
resultsConsumer
|
||||
);
|
||||
|
@ -214,6 +217,7 @@ public class JdbcSelectExecutorStandardImpl implements JdbcSelectExecutor {
|
|||
final AppliedGraph appliedGraph = queryOptions.getAppliedGraph();
|
||||
final TupleTransformer<?> tupleTransformer = queryOptions.getTupleTransformer();
|
||||
final ResultListTransformer<?> resultListTransformer = queryOptions.getResultListTransformer();
|
||||
final Boolean deDuplicationEnabled = queryOptions.isDeDuplicationEnabled();
|
||||
final Boolean resultCachingEnabled = queryOptions.isResultCachingEnabled();
|
||||
final CacheRetrieveMode cacheRetrieveMode = queryOptions.getCacheRetrieveMode();
|
||||
final CacheStoreMode cacheStoreMode = queryOptions.getCacheStoreMode();
|
||||
|
@ -259,6 +263,11 @@ public class JdbcSelectExecutorStandardImpl implements JdbcSelectExecutor {
|
|||
return resultListTransformer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean isDeDuplicationEnabled() {
|
||||
return deDuplicationEnabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean isResultCachingEnabled() {
|
||||
return resultCachingEnabled;
|
||||
|
@ -322,11 +331,13 @@ public class JdbcSelectExecutorStandardImpl implements JdbcSelectExecutor {
|
|||
}
|
||||
};
|
||||
}
|
||||
|
||||
private <T, R> T doExecuteQuery(
|
||||
JdbcSelect jdbcSelect,
|
||||
JdbcParameterBindings jdbcParameterBindings,
|
||||
ExecutionContext executionContext,
|
||||
RowTransformer<R> rowTransformer,
|
||||
Class<R> domainResultType,
|
||||
Function<String, PreparedStatement> statementCreator,
|
||||
ResultsConsumer<T, R> resultsConsumer) {
|
||||
|
||||
|
@ -346,7 +357,10 @@ public class JdbcSelectExecutorStandardImpl implements JdbcSelectExecutor {
|
|||
|
||||
if ( rowTransformer == null ) {
|
||||
@SuppressWarnings("unchecked")
|
||||
final TupleTransformer<R> tupleTransformer = (TupleTransformer<R>) executionContext.getQueryOptions().getTupleTransformer();
|
||||
final TupleTransformer<R> tupleTransformer = (TupleTransformer<R>) executionContext
|
||||
.getQueryOptions()
|
||||
.getTupleTransformer();
|
||||
|
||||
if ( tupleTransformer == null ) {
|
||||
rowTransformer = RowTransformerStandardImpl.instance();
|
||||
}
|
||||
|
@ -415,6 +429,7 @@ public class JdbcSelectExecutorStandardImpl implements JdbcSelectExecutor {
|
|||
? LockOptions.NONE
|
||||
: executionContext.getQueryOptions().getLockOptions(),
|
||||
rowTransformer,
|
||||
domainResultType,
|
||||
jdbcValues
|
||||
);
|
||||
|
||||
|
|
|
@ -22,11 +22,21 @@ import org.hibernate.sql.results.spi.RowTransformer;
|
|||
*/
|
||||
@Incubating
|
||||
public interface JdbcSelectExecutor {
|
||||
default <R> List<R> list(
|
||||
JdbcSelect jdbcSelect,
|
||||
JdbcParameterBindings jdbcParameterBindings,
|
||||
ExecutionContext executionContext,
|
||||
RowTransformer<R> rowTransformer,
|
||||
ListResultsConsumer.UniqueSemantic uniqueSemantic) {
|
||||
return list( jdbcSelect, jdbcParameterBindings, executionContext, rowTransformer, null, uniqueSemantic );
|
||||
}
|
||||
|
||||
<R> List<R> list(
|
||||
JdbcSelect jdbcSelect,
|
||||
JdbcParameterBindings jdbcParameterBindings,
|
||||
ExecutionContext executionContext,
|
||||
RowTransformer<R> rowTransformer,
|
||||
Class<R> requestedJavaType,
|
||||
ListResultsConsumer.UniqueSemantic uniqueSemantic);
|
||||
|
||||
<R> ScrollableResultsImplementor<R> scroll(
|
||||
|
|
|
@ -57,10 +57,12 @@ public class ResultsHelper {
|
|||
ExecutionContext executionContext,
|
||||
LockOptions lockOptions,
|
||||
RowTransformer<R> rowTransformer,
|
||||
Class<R> transformedResultJavaType,
|
||||
JdbcValues jdbcValues) {
|
||||
final SessionFactoryImplementor sessionFactory = executionContext.getSession().getFactory();
|
||||
|
||||
final Map<NavigablePath, Initializer> initializerMap = new LinkedHashMap<>();
|
||||
final List<Initializer> initializers = new ArrayList<>();
|
||||
final SessionFactoryImplementor sessionFactory = executionContext.getSession().getFactory();
|
||||
|
||||
final List<DomainResultAssembler<?>> assemblers = jdbcValues.getValuesMapping().resolveAssemblers(
|
||||
new AssemblerCreationState() {
|
||||
|
@ -107,7 +109,7 @@ public class ResultsHelper {
|
|||
|
||||
logInitializers( initializerMap );
|
||||
|
||||
return new StandardRowReader<>( assemblers, initializers, rowTransformer );
|
||||
return new StandardRowReader<>( assemblers, initializers, rowTransformer, transformedResultJavaType );
|
||||
}
|
||||
|
||||
private static void logInitializers(Map<NavigablePath, Initializer> initializerMap) {
|
||||
|
|
|
@ -32,18 +32,21 @@ public class StandardRowReader<T> implements RowReader<T> {
|
|||
private final List<DomainResultAssembler<?>> resultAssemblers;
|
||||
private final List<Initializer> initializers;
|
||||
private final RowTransformer<T> rowTransformer;
|
||||
private final Class<T> domainResultJavaType;
|
||||
|
||||
private final int assemblerCount;
|
||||
|
||||
public StandardRowReader(
|
||||
List<DomainResultAssembler<?>> resultAssemblers,
|
||||
List<Initializer> initializers,
|
||||
RowTransformer<T> rowTransformer) {
|
||||
RowTransformer<T> rowTransformer,
|
||||
Class<T> domainResultJavaType) {
|
||||
this.resultAssemblers = resultAssemblers;
|
||||
this.initializers = initializers;
|
||||
this.rowTransformer = rowTransformer;
|
||||
|
||||
this.assemblerCount = resultAssemblers.size();
|
||||
this.domainResultJavaType = domainResultJavaType;
|
||||
|
||||
logDebugInfo();
|
||||
}
|
||||
|
@ -61,18 +64,22 @@ public class StandardRowReader<T> implements RowReader<T> {
|
|||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public Class<T> getResultJavaType() {
|
||||
if ( resultAssemblers.size() == 1 ) {
|
||||
return (Class<T>) resultAssemblers.get( 0 ).getAssembledJavaType().getJavaTypeClass();
|
||||
}
|
||||
|
||||
return (Class<T>) Object[].class;
|
||||
public Class<T> getDomainResultResultJavaType() {
|
||||
return domainResultJavaType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<JavaType> getResultJavaTypes() {
|
||||
List<JavaType> javaTypes = new ArrayList<>( resultAssemblers.size() );
|
||||
public Class<?> getResultJavaType() {
|
||||
if ( resultAssemblers.size() == 1 ) {
|
||||
return resultAssemblers.get( 0 ).getAssembledJavaType().getJavaTypeClass();
|
||||
}
|
||||
|
||||
return Object[].class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<JavaType<?>> getResultJavaTypes() {
|
||||
List<JavaType<?>> javaTypes = new ArrayList<>( resultAssemblers.size() );
|
||||
for ( DomainResultAssembler resultAssembler : resultAssemblers ) {
|
||||
javaTypes.add( resultAssembler.getAssembledJavaType() );
|
||||
}
|
||||
|
|
|
@ -8,50 +8,94 @@ package org.hibernate.sql.results.spi;
|
|||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.hibernate.HibernateException;
|
||||
import org.hibernate.engine.spi.PersistenceContext;
|
||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||
import org.hibernate.query.ResultListTransformer;
|
||||
import org.hibernate.query.TupleTransformer;
|
||||
import org.hibernate.query.TypedTupleTransformer;
|
||||
import org.hibernate.query.spi.QueryOptions;
|
||||
import org.hibernate.sql.results.internal.RowProcessingStateStandardImpl;
|
||||
import org.hibernate.sql.results.jdbc.internal.JdbcValuesSourceProcessingStateStandardImpl;
|
||||
import org.hibernate.sql.results.jdbc.spi.JdbcValues;
|
||||
import org.hibernate.sql.results.jdbc.spi.JdbcValuesSourceProcessingOptions;
|
||||
import org.hibernate.persister.entity.EntityPersister;
|
||||
import org.hibernate.sql.results.jdbc.internal.JdbcValuesSourceProcessingStateStandardImpl;
|
||||
import org.hibernate.sql.results.internal.RowProcessingStateStandardImpl;
|
||||
import org.hibernate.type.descriptor.java.JavaType;
|
||||
import org.hibernate.type.descriptor.java.spi.EntityJavaType;
|
||||
import org.hibernate.type.descriptor.java.spi.JavaTypeRegistry;
|
||||
import org.hibernate.type.spi.TypeConfiguration;
|
||||
|
||||
/**
|
||||
* ResultsConsumer for creating a List of results
|
||||
*
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public class ListResultsConsumer<R> implements ResultsConsumer<List<R>, R> {
|
||||
/**
|
||||
* Singleton access
|
||||
*/
|
||||
private static final ListResultsConsumer UNIQUE_FILTER_INSTANCE = new ListResultsConsumer( UniqueSemantic.FILTER );
|
||||
private static final ListResultsConsumer NORMAL_INSTANCE = new ListResultsConsumer( UniqueSemantic.NONE );
|
||||
private static final ListResultsConsumer UNIQUE_INSTANCE = new ListResultsConsumer( UniqueSemantic.ASSERT );
|
||||
private static final ListResultsConsumer<?> NEVER_DE_DUP_CONSUMER = new ListResultsConsumer<>( UniqueSemantic.NEVER );
|
||||
private static final ListResultsConsumer<?> IGNORE_DUP_CONSUMER = new ListResultsConsumer<>( UniqueSemantic.NONE );
|
||||
private static final ListResultsConsumer<?> DE_DUP_CONSUMER = new ListResultsConsumer<>( UniqueSemantic.FILTER );
|
||||
private static final ListResultsConsumer<?> ERROR_DUP_CONSUMER = new ListResultsConsumer<>( UniqueSemantic.ASSERT );
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <R> ListResultsConsumer<R> instance(UniqueSemantic uniqueSemantic) {
|
||||
switch ( uniqueSemantic ) {
|
||||
case ASSERT:
|
||||
return UNIQUE_INSTANCE;
|
||||
case FILTER:
|
||||
return UNIQUE_FILTER_INSTANCE;
|
||||
default:
|
||||
return NORMAL_INSTANCE;
|
||||
case ASSERT: {
|
||||
return (ListResultsConsumer<R>) ERROR_DUP_CONSUMER;
|
||||
}
|
||||
case FILTER: {
|
||||
return (ListResultsConsumer<R>) DE_DUP_CONSUMER;
|
||||
}
|
||||
case NEVER: {
|
||||
return (ListResultsConsumer<R>) NEVER_DE_DUP_CONSUMER;
|
||||
}
|
||||
default: {
|
||||
return (ListResultsConsumer<R>) IGNORE_DUP_CONSUMER;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ways this consumer can handle in-memory row de-duplication
|
||||
*/
|
||||
public enum UniqueSemantic {
|
||||
/**
|
||||
* Apply no in-memory de-duplication
|
||||
*/
|
||||
NONE,
|
||||
|
||||
/**
|
||||
* Apply in-memory de-duplication, removing rows already part of the results
|
||||
*/
|
||||
FILTER,
|
||||
ASSERT;
|
||||
|
||||
/**
|
||||
* Apply in-memory duplication checks, throwing a HibernateException when duplicates are found
|
||||
*/
|
||||
ASSERT,
|
||||
|
||||
/**
|
||||
* Never apply unique handling. E.g. for NativeQuery. Whereas {@link #NONE} can be adjusted,
|
||||
* NEVER will never apply unique handling
|
||||
*/
|
||||
NEVER
|
||||
}
|
||||
|
||||
private final UniqueSemantic uniqueSemantic;
|
||||
private final ResultHandler<R> resultHandler;
|
||||
|
||||
public ListResultsConsumer(UniqueSemantic uniqueSemantic) {
|
||||
this.uniqueSemantic = uniqueSemantic;
|
||||
|
||||
if ( uniqueSemantic == UniqueSemantic.FILTER ) {
|
||||
resultHandler = ListResultsConsumer::deDuplicationHandling;
|
||||
}
|
||||
else if ( uniqueSemantic == UniqueSemantic.ASSERT ) {
|
||||
resultHandler = ListResultsConsumer::duplicationErrorHandling;
|
||||
}
|
||||
else {
|
||||
resultHandler = ListResultsConsumer::applyAll;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -63,61 +107,75 @@ public class ListResultsConsumer<R> implements ResultsConsumer<List<R>, R> {
|
|||
RowProcessingStateStandardImpl rowProcessingState,
|
||||
RowReader<R> rowReader) {
|
||||
final PersistenceContext persistenceContext = session.getPersistenceContext();
|
||||
final TypeConfiguration typeConfiguration = session.getTypeConfiguration();
|
||||
final JavaTypeRegistry javaTypeRegistry = typeConfiguration.getJavaTypeRegistry();
|
||||
final QueryOptions queryOptions = rowProcessingState.getQueryOptions();
|
||||
|
||||
RuntimeException ex = null;
|
||||
try {
|
||||
persistenceContext.getLoadContexts().register( jdbcValuesSourceProcessingState );
|
||||
|
||||
final List<R> results = new ArrayList<>();
|
||||
|
||||
boolean uniqueRows = false;
|
||||
final JavaType<R> domainResultJavaType;
|
||||
final ResultHandler<R> resultHandlerToUse;
|
||||
|
||||
if ( uniqueSemantic != UniqueSemantic.NONE ) {
|
||||
final Class<R> resultJavaType = rowReader.getResultJavaType();
|
||||
if ( resultJavaType != null && !resultJavaType.isArray() ) {
|
||||
final EntityPersister entityDescriptor = session.getFactory()
|
||||
.getRuntimeMetamodels()
|
||||
.getMappingMetamodel()
|
||||
.findEntityDescriptor( resultJavaType );
|
||||
if ( entityDescriptor != null ) {
|
||||
uniqueRows = true;
|
||||
}
|
||||
final TupleTransformer<?> tupleTransformer = queryOptions.getTupleTransformer();
|
||||
if ( tupleTransformer instanceof TypedTupleTransformer ) {
|
||||
//noinspection unchecked
|
||||
final TypedTupleTransformer<R> typedTupleTransformer = (TypedTupleTransformer<R>) tupleTransformer;
|
||||
domainResultJavaType = javaTypeRegistry.resolveDescriptor( typedTupleTransformer.getTransformedType() );
|
||||
|
||||
if ( uniqueSemantic == UniqueSemantic.NEVER ) {
|
||||
resultHandlerToUse = this.resultHandler;
|
||||
}
|
||||
}
|
||||
|
||||
if ( uniqueRows ) {
|
||||
final List<JavaType> resultJavaTypes = rowReader.getResultJavaTypes();
|
||||
assert resultJavaTypes.size() == 1;
|
||||
final JavaType<R> resultJavaType = resultJavaTypes.get( 0 );
|
||||
while ( rowProcessingState.next() ) {
|
||||
final R row = rowReader.readRow( rowProcessingState, processingOptions );
|
||||
boolean add = true;
|
||||
for ( R existingRow : results ) {
|
||||
if ( resultJavaType.areEqual( existingRow, row ) ) {
|
||||
if ( uniqueSemantic == UniqueSemantic.ASSERT && !rowProcessingState.hasCollectionInitializers() ) {
|
||||
throw new HibernateException(
|
||||
"More than one row with the given identifier was found: " +
|
||||
jdbcValuesSourceProcessingState.getExecutionContext()
|
||||
.getEntityId() +
|
||||
", for class: " +
|
||||
rowReader.getResultJavaType().getName()
|
||||
);
|
||||
}
|
||||
add = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ( add ) {
|
||||
results.add( row );
|
||||
}
|
||||
rowProcessingState.finishRowProcessing();
|
||||
else if ( queryOptions.shouldApplyDeDuplication() ) {
|
||||
resultHandlerToUse = ListResultsConsumer::deDuplicationHandling;
|
||||
}
|
||||
else if ( domainResultJavaType instanceof EntityJavaType ) {
|
||||
resultHandlerToUse = ListResultsConsumer::deDuplicationHandling;
|
||||
}
|
||||
else {
|
||||
resultHandlerToUse = this.resultHandler;
|
||||
}
|
||||
}
|
||||
else {
|
||||
while ( rowProcessingState.next() ) {
|
||||
results.add( rowReader.readRow( rowProcessingState, processingOptions ) );
|
||||
rowProcessingState.finishRowProcessing();
|
||||
domainResultJavaType = resolveDomainResultJavaType(
|
||||
rowReader.getDomainResultResultJavaType(),
|
||||
rowReader.getResultJavaTypes(),
|
||||
typeConfiguration
|
||||
);
|
||||
|
||||
if ( uniqueSemantic == UniqueSemantic.NEVER ) {
|
||||
resultHandlerToUse = this.resultHandler;
|
||||
}
|
||||
else if ( queryOptions.shouldApplyDeDuplication() ) {
|
||||
resultHandlerToUse = ListResultsConsumer::deDuplicationHandling;
|
||||
}
|
||||
else {
|
||||
if ( uniqueSemantic == UniqueSemantic.ASSERT ) {
|
||||
if ( rowProcessingState.hasCollectionInitializers ) {
|
||||
resultHandlerToUse = ListResultsConsumer::deDuplicationHandling;
|
||||
}
|
||||
else {
|
||||
resultHandlerToUse = this.resultHandler;
|
||||
}
|
||||
}
|
||||
else if ( domainResultJavaType instanceof EntityJavaType ) {
|
||||
resultHandlerToUse = ListResultsConsumer::deDuplicationHandling;
|
||||
}
|
||||
else {
|
||||
resultHandlerToUse = this.resultHandler;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while ( rowProcessingState.next() ) {
|
||||
final R row = rowReader.readRow( rowProcessingState, processingOptions );
|
||||
resultHandlerToUse.handle( row, domainResultJavaType, results, rowProcessingState );
|
||||
rowProcessingState.finishRowProcessing();
|
||||
}
|
||||
|
||||
try {
|
||||
jdbcValuesSourceProcessingState.finishUp();
|
||||
}
|
||||
|
@ -126,12 +184,11 @@ public class ListResultsConsumer<R> implements ResultsConsumer<List<R>, R> {
|
|||
}
|
||||
|
||||
//noinspection unchecked
|
||||
final ResultListTransformer<R> resultListTransformer = (ResultListTransformer<R>) jdbcValuesSourceProcessingState.getExecutionContext()
|
||||
.getQueryOptions()
|
||||
.getResultListTransformer();
|
||||
final ResultListTransformer<R> resultListTransformer = (ResultListTransformer<R>) queryOptions.getResultListTransformer();
|
||||
if ( resultListTransformer != null ) {
|
||||
return resultListTransformer.transformList( results );
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
|
@ -160,8 +217,108 @@ public class ListResultsConsumer<R> implements ResultsConsumer<List<R>, R> {
|
|||
throw new IllegalStateException( "Should not reach this!" );
|
||||
}
|
||||
|
||||
/**
|
||||
* Essentially a tri-consumer for applying the different duplication strategies.
|
||||
*
|
||||
* @see UniqueSemantic
|
||||
*/
|
||||
@FunctionalInterface
|
||||
private interface ResultHandler<R> {
|
||||
void handle(R result, JavaType<R> transformedJavaType, List<R> results, RowProcessingStateStandardImpl rowProcessingState);
|
||||
}
|
||||
|
||||
public static <R> void deDuplicationHandling(
|
||||
R result,
|
||||
JavaType<R> transformedJavaType,
|
||||
List<R> results,
|
||||
RowProcessingStateStandardImpl rowProcessingState) {
|
||||
withDuplicationCheck(
|
||||
result,
|
||||
transformedJavaType,
|
||||
results,
|
||||
rowProcessingState,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
private static <R> void withDuplicationCheck(
|
||||
R result,
|
||||
JavaType<R> transformedJavaType,
|
||||
List<R> results,
|
||||
RowProcessingStateStandardImpl rowProcessingState,
|
||||
boolean throwException) {
|
||||
boolean addResult = true;
|
||||
|
||||
for ( int i = 0; i < results.size(); i++ ) {
|
||||
final R existingResult = results.get( i );
|
||||
if ( transformedJavaType.areEqual( result, existingResult ) ) {
|
||||
if ( throwException && ! rowProcessingState.hasCollectionInitializers ) {
|
||||
throw new HibernateException(
|
||||
String.format(
|
||||
Locale.ROOT,
|
||||
"Duplicate row was found and `%s` was specified",
|
||||
UniqueSemantic.ASSERT
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
addResult = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( addResult ) {
|
||||
results.add( result );
|
||||
}
|
||||
}
|
||||
|
||||
public static <R> void duplicationErrorHandling(
|
||||
R result,
|
||||
JavaType<R> transformedJavaType,
|
||||
List<R> results,
|
||||
RowProcessingStateStandardImpl rowProcessingState) {
|
||||
withDuplicationCheck(
|
||||
result,
|
||||
transformedJavaType,
|
||||
results,
|
||||
rowProcessingState,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
public static <R> void applyAll(
|
||||
R result,
|
||||
JavaType<R> transformedJavaType,
|
||||
List<R> results,
|
||||
RowProcessingStateStandardImpl rowProcessingState) {
|
||||
results.add( result );
|
||||
}
|
||||
|
||||
private JavaType<R> resolveDomainResultJavaType(
|
||||
Class<R> domainResultResultJavaType,
|
||||
List<JavaType<?>> resultJavaTypes,
|
||||
TypeConfiguration typeConfiguration) {
|
||||
final JavaTypeRegistry javaTypeRegistry = typeConfiguration.getJavaTypeRegistry();
|
||||
|
||||
if ( domainResultResultJavaType != null ) {
|
||||
return javaTypeRegistry.resolveDescriptor( domainResultResultJavaType );
|
||||
}
|
||||
|
||||
if ( resultJavaTypes.size() == 1 ) {
|
||||
//noinspection unchecked
|
||||
return (JavaType<R>) resultJavaTypes.get( 0 );
|
||||
}
|
||||
|
||||
return javaTypeRegistry.resolveDescriptor( Object[].class );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canResultsBeCached() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ListResultsConsumer(" + uniqueSemantic + ")";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,9 @@ import org.hibernate.sql.results.jdbc.spi.JdbcValues;
|
|||
import org.hibernate.sql.results.jdbc.spi.JdbcValuesSourceProcessingOptions;
|
||||
|
||||
/**
|
||||
* Consumes {@link JdbcValues} and returns the consumed values in whatever form this
|
||||
* consumer returns, generally a {@link java.util.List} or a {@link org.hibernate.ScrollableResults}
|
||||
*
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public interface ResultsConsumer<T, R> {
|
||||
|
|
|
@ -23,27 +23,38 @@ import org.hibernate.type.descriptor.java.JavaType;
|
|||
*/
|
||||
public interface RowReader<R> {
|
||||
/**
|
||||
* The overall row result Java type. Might be a scalar type, an
|
||||
* entity type, etc. Might also be a `Object[].class` for multiple
|
||||
* results (domain selections).
|
||||
* The type actually returned from this reader's {@link #readRow} call,
|
||||
* accounting for any transformers.
|
||||
* <p/>
|
||||
* May be null to indicate that no transformation is applied.
|
||||
* <p/>
|
||||
* Ultimately intended for use in comparing values are being de-duplicated
|
||||
*/
|
||||
Class<R> getResultJavaType();
|
||||
Class<R> getDomainResultResultJavaType();
|
||||
|
||||
/**
|
||||
* The JavaTypes of the result
|
||||
* The row result Java type, before any transformations.
|
||||
*
|
||||
* @apiNote along with {@link #getResultJavaTypes()}, describes the "raw"
|
||||
* values as determined from the {@link org.hibernate.sql.results.graph.DomainResult}
|
||||
* references associated with the JdbcValues being processed
|
||||
*/
|
||||
List<JavaType> getResultJavaTypes();
|
||||
Class<?> getResultJavaType();
|
||||
|
||||
/**
|
||||
* The initializers associated with this reader
|
||||
* The individual JavaType for each DomainResult
|
||||
*/
|
||||
List<JavaType<?>> getResultJavaTypes();
|
||||
|
||||
/**
|
||||
* The initializers associated with this reader.
|
||||
*
|
||||
* @see org.hibernate.sql.results.graph.DomainResult
|
||||
*/
|
||||
List<Initializer> getInitializers();
|
||||
|
||||
/**
|
||||
* The actual coordination of reading a row
|
||||
*
|
||||
* todo (6.0) : JdbcValuesSourceProcessingOptions is available through RowProcessingState - why pass it in separately
|
||||
* should use one approach or the other
|
||||
*/
|
||||
R readRow(RowProcessingState processingState, JdbcValuesSourceProcessingOptions options);
|
||||
|
||||
|
|
|
@ -9,11 +9,12 @@ package org.hibernate.transform;
|
|||
import java.lang.reflect.Constructor;
|
||||
|
||||
import org.hibernate.QueryException;
|
||||
import org.hibernate.query.TypedTupleTransformer;
|
||||
|
||||
/**
|
||||
* Wraps the tuples in a constructor call.
|
||||
*/
|
||||
public class AliasToBeanConstructorResultTransformer<T> implements ResultTransformer<T> {
|
||||
public class AliasToBeanConstructorResultTransformer<T> implements ResultTransformer<T>, TypedTupleTransformer<T> {
|
||||
|
||||
private final Constructor<T> constructor;
|
||||
|
||||
|
@ -26,6 +27,11 @@ public class AliasToBeanConstructorResultTransformer<T> implements ResultTransfo
|
|||
this.constructor = constructor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<T> getTransformedType() {
|
||||
return constructor.getDeclaringClass();
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap the incoming tuples in a call to our configured constructor.
|
||||
*/
|
||||
|
|
|
@ -14,6 +14,7 @@ import org.hibernate.property.access.internal.PropertyAccessStrategyChainedImpl;
|
|||
import org.hibernate.property.access.internal.PropertyAccessStrategyFieldImpl;
|
||||
import org.hibernate.property.access.internal.PropertyAccessStrategyMapImpl;
|
||||
import org.hibernate.property.access.spi.Setter;
|
||||
import org.hibernate.query.TypedTupleTransformer;
|
||||
|
||||
/**
|
||||
* Result transformer that allows to transform a result to
|
||||
|
@ -22,7 +23,7 @@ import org.hibernate.property.access.spi.Setter;
|
|||
*
|
||||
* @author max
|
||||
*/
|
||||
public class AliasToBeanResultTransformer<T> implements ResultTransformer<T> {
|
||||
public class AliasToBeanResultTransformer<T> implements ResultTransformer<T>, TypedTupleTransformer<T> {
|
||||
|
||||
// IMPL NOTE : due to the delayed population of setters (setters cached
|
||||
// for performance), we really cannot properly define equality for
|
||||
|
@ -41,6 +42,11 @@ public class AliasToBeanResultTransformer<T> implements ResultTransformer<T> {
|
|||
this.resultClass = resultClass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<T> getTransformedType() {
|
||||
return resultClass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T transformTuple(Object[] tuple, String[] aliases) {
|
||||
T result;
|
||||
|
|
|
@ -8,6 +8,7 @@ package org.hibernate.transform;
|
|||
import java.util.Map;
|
||||
|
||||
import org.hibernate.internal.util.collections.CollectionHelper;
|
||||
import org.hibernate.query.TypedTupleTransformer;
|
||||
|
||||
/**
|
||||
* {@link ResultTransformer} implementation which builds a map for each "row",
|
||||
|
@ -16,7 +17,7 @@ import org.hibernate.internal.util.collections.CollectionHelper;
|
|||
* @author Gavin King
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public class AliasToEntityMapResultTransformer implements ResultTransformer<Map<String,Object>> {
|
||||
public class AliasToEntityMapResultTransformer implements ResultTransformer<Map<String,Object>>, TypedTupleTransformer<Map<String,Object>> {
|
||||
|
||||
public static final AliasToEntityMapResultTransformer INSTANCE = new AliasToEntityMapResultTransformer();
|
||||
|
||||
|
@ -26,6 +27,12 @@ public class AliasToEntityMapResultTransformer implements ResultTransformer<Map<
|
|||
private AliasToEntityMapResultTransformer() {
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
@Override
|
||||
public Class getTransformedType() {
|
||||
return Map.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String,Object> transformTuple(Object[] tuple, String[] aliases) {
|
||||
Map<String,Object> result = CollectionHelper.mapOfSize( tuple.length );
|
||||
|
|
|
@ -9,10 +9,12 @@ package org.hibernate.transform;
|
|||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.hibernate.query.TypedTupleTransformer;
|
||||
|
||||
/**
|
||||
* Transforms each result row from a tuple into a {@link List} whose elements are each tuple value
|
||||
*/
|
||||
public class ToListResultTransformer implements ResultTransformer<List<Object>> {
|
||||
public class ToListResultTransformer implements ResultTransformer<List<Object>>, TypedTupleTransformer<List<Object>> {
|
||||
public static final ToListResultTransformer INSTANCE = new ToListResultTransformer();
|
||||
|
||||
/**
|
||||
|
@ -21,6 +23,12 @@ public class ToListResultTransformer implements ResultTransformer<List<Object>>
|
|||
private ToListResultTransformer() {
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
@Override
|
||||
public Class<List<Object>> getTransformedType() {
|
||||
return (Class) List.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Object> transformTuple(Object[] tuple, String[] aliases) {
|
||||
return Arrays.asList( tuple );
|
||||
|
|
|
@ -16,6 +16,7 @@ import org.hibernate.testing.orm.domain.StandardDomainModel;
|
|||
import org.hibernate.testing.orm.domain.retail.Product;
|
||||
import org.hibernate.testing.orm.domain.retail.Vendor;
|
||||
import org.hibernate.testing.orm.junit.DomainModel;
|
||||
import org.hibernate.testing.orm.junit.JiraKey;
|
||||
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
|
@ -29,6 +30,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
|||
*/
|
||||
@DomainModel( standardModels = StandardDomainModel.RETAIL )
|
||||
@SessionFactory
|
||||
@JiraKey( "HHH-15133" )
|
||||
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;
|
||||
|
|
|
@ -0,0 +1,279 @@
|
|||
/*
|
||||
* 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.orm.test.sql.results;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.money.Monetary;
|
||||
import javax.money.MonetaryAmount;
|
||||
|
||||
import jakarta.persistence.Tuple;
|
||||
|
||||
import org.hibernate.query.TypedTupleTransformer;
|
||||
|
||||
import org.hibernate.testing.orm.domain.StandardDomainModel;
|
||||
import org.hibernate.testing.orm.domain.retail.CardPayment;
|
||||
import org.hibernate.testing.orm.domain.retail.LineItem;
|
||||
import org.hibernate.testing.orm.domain.retail.Name;
|
||||
import org.hibernate.testing.orm.domain.retail.Order;
|
||||
import org.hibernate.testing.orm.domain.retail.Product;
|
||||
import org.hibernate.testing.orm.domain.retail.SalesAssociate;
|
||||
import org.hibernate.testing.orm.domain.retail.Vendor;
|
||||
import org.hibernate.testing.orm.junit.DomainModel;
|
||||
import org.hibernate.testing.orm.junit.Jira;
|
||||
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
@DomainModel( standardModels = StandardDomainModel.RETAIL )
|
||||
@SessionFactory
|
||||
@Jira( "https://hibernate.atlassian.net/browse/HHH-15133" )
|
||||
public class ResultsShapeTests {
|
||||
@Test
|
||||
public void testSimpleEntitySelection(SessionFactoryScope scope) {
|
||||
scope.inTransaction( (session) -> {
|
||||
final List<?> orders = session.createQuery( "select o from Order o" ).list();
|
||||
// only 2 orders
|
||||
assertThat( orders ).hasSize( 2 );
|
||||
assertThat( orders.get( 0 ) ).isInstanceOf( Order.class );
|
||||
assertThat( orders.get( 1 ) ).isInstanceOf( Order.class );
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTypedEntitySelection(SessionFactoryScope scope) {
|
||||
scope.inTransaction( (session) -> {
|
||||
final List<Order> orders = session.createQuery( "select o from Order o", Order.class ).list();
|
||||
// only 2 orders
|
||||
assertThat( orders ).hasSize( 2 );
|
||||
assertThat( orders.get( 0 ) ).isInstanceOf( Order.class );
|
||||
assertThat( orders.get( 1 ) ).isInstanceOf( Order.class );
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testArrayTypedEntitySelection(SessionFactoryScope scope) {
|
||||
scope.inTransaction( (session) -> {
|
||||
final List<Object[]> orders = session.createQuery( "select o from Order o", Object[].class ).list();
|
||||
// only 2 orders
|
||||
assertThat( orders ).hasSize( 2 );
|
||||
assertThat( orders.get( 0 ) ).isInstanceOf( Object[].class );
|
||||
assertThat( orders.get( 1 ) ).isInstanceOf( Object[].class );
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTupleTypedEntitySelection(SessionFactoryScope scope) {
|
||||
scope.inTransaction( (session) -> {
|
||||
final List<Tuple> orders = session.createQuery( "select o from Order o", Tuple.class ).list();
|
||||
// only 2 orders
|
||||
assertThat( orders ).hasSize( 2 );
|
||||
assertThat( orders.get( 0 ) ).isInstanceOf( Tuple.class );
|
||||
assertThat( orders.get( 1 ) ).isInstanceOf( Tuple.class );
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDuplicatedTypedEntitySelection(SessionFactoryScope scope) {
|
||||
scope.inTransaction( (session) -> {
|
||||
final String hql = "select o from LineItem i join i.order o";
|
||||
final List<Order> orders = session.createQuery( hql, Order.class ).list();
|
||||
// because we select the entity and specify it as the result type, the results are de-duped
|
||||
assertThat( orders ).hasSize( 2 );
|
||||
assertThat( orders.get( 0 ) ).isInstanceOf( Order.class );
|
||||
assertThat( orders.get( 1 ) ).isInstanceOf( Order.class );
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDuplicatedArrayTypedEntitySelection(SessionFactoryScope scope) {
|
||||
scope.inTransaction( (session) -> {
|
||||
final String hql = "select o from LineItem i join i.order o";
|
||||
final List<Object[]> orders = session.createQuery( hql, Object[].class ).list();
|
||||
// because we select the entity again, but here specify the full array as the result type - the results are not de-duped
|
||||
assertThat( orders ).hasSize( 3 );
|
||||
assertThat( orders.get( 0 ) ).isInstanceOf( Object[].class );
|
||||
assertThat( orders.get( 1 ) ).isInstanceOf( Object[].class );
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDuplicatedTupleTypedEntitySelection(SessionFactoryScope scope) {
|
||||
scope.inTransaction( (session) -> {
|
||||
final String hql = "select o from LineItem i join i.order o";
|
||||
final List<Tuple> orders = session.createQuery( hql, Tuple.class ).list();
|
||||
// Tuple is a special case or Object[] - not de-duped
|
||||
assertThat( orders ).hasSize( 3 );
|
||||
assertThat( orders.get( 0 ) ).isInstanceOf( Tuple.class );
|
||||
assertThat( orders.get( 1 ) ).isInstanceOf( Tuple.class );
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTupleTransformedEntitySelection(SessionFactoryScope scope) {
|
||||
scope.inTransaction( (session) -> {
|
||||
final String hql = "select o from Order o";
|
||||
final List<Order> orders = session.createQuery( hql, Order.class )
|
||||
.setTupleTransformer( (tuple, aliases) -> (Order) tuple[ 0 ] )
|
||||
.list();
|
||||
// only 2 orders
|
||||
assertThat( orders ).hasSize( 2 );
|
||||
assertThat( orders.get( 0 ) ).isInstanceOf( Order.class );
|
||||
assertThat( orders.get( 1 ) ).isInstanceOf( Order.class );
|
||||
} );
|
||||
|
||||
scope.inTransaction( (session) -> {
|
||||
final String hql = "select o from Order o";
|
||||
final List<Order> orders = session.createQuery( hql )
|
||||
.setTupleTransformer( (tuple, aliases) -> tuple[ 0 ] )
|
||||
.list();
|
||||
// only 2 orders
|
||||
assertThat( orders ).hasSize( 2 );
|
||||
assertThat( orders.get( 0 ) ).isInstanceOf( Order.class );
|
||||
assertThat( orders.get( 1 ) ).isInstanceOf( Order.class );
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDuplicatedTupleTransformedEntitySelection(SessionFactoryScope scope) {
|
||||
scope.inTransaction( (session) -> {
|
||||
final String hql = "select o from LineItem i join i.order o";
|
||||
final List<Order> orders = session.createQuery( hql, Order.class )
|
||||
.setTupleTransformer( (tuple, aliases) -> (Order) tuple[ 0 ] )
|
||||
.list();
|
||||
// only 2 orders
|
||||
assertThat( orders ).hasSize( 2 );
|
||||
assertThat( orders.get( 0 ) ).isInstanceOf( Order.class );
|
||||
assertThat( orders.get( 1 ) ).isInstanceOf( Order.class );
|
||||
} );
|
||||
|
||||
scope.inTransaction( (session) -> {
|
||||
final String hql = "select o from LineItem i join i.order o";
|
||||
final List<Order> orders = session.createQuery( hql )
|
||||
.setTupleTransformer( (tuple, aliases) -> tuple[ 0 ] )
|
||||
.list();
|
||||
// only 2 orders
|
||||
assertThat( orders ).hasSize( 2 );
|
||||
assertThat( orders.get( 0 ) ).isInstanceOf( Order.class );
|
||||
assertThat( orders.get( 1 ) ).isInstanceOf( Order.class );
|
||||
} );
|
||||
}
|
||||
|
||||
|
||||
private final TypedTupleTransformer<Order> ORDER_TUPLE_TRANSFORMER = new TypedTupleTransformer<>() {
|
||||
@Override
|
||||
public Class<Order> getTransformedType() {
|
||||
return Order.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Order transformTuple(Object[] tuple, String[] aliases) {
|
||||
return (Order) tuple[0];
|
||||
}
|
||||
};
|
||||
|
||||
@Test
|
||||
public void testTypedTupleTransformedEntitySelection(SessionFactoryScope scope) {
|
||||
scope.inTransaction( (session) -> {
|
||||
final String hql = "select o from Order o";
|
||||
final List<Order> orders = session.createQuery( hql, Order.class )
|
||||
.setTupleTransformer( ORDER_TUPLE_TRANSFORMER )
|
||||
.list();
|
||||
assertThat( orders ).hasSize( 2 );
|
||||
assertThat( orders.get( 0 ) ).isInstanceOf( Order.class );
|
||||
assertThat( orders.get( 1 ) ).isInstanceOf( Order.class );
|
||||
} );
|
||||
|
||||
scope.inTransaction( (session) -> {
|
||||
final String hql = "select o from Order o";
|
||||
final List<Order> orders = session.createQuery( hql )
|
||||
.setTupleTransformer( ORDER_TUPLE_TRANSFORMER )
|
||||
.list();
|
||||
assertThat( orders ).hasSize( 2 );
|
||||
assertThat( orders.get( 0 ) ).isInstanceOf( Order.class );
|
||||
assertThat( orders.get( 1 ) ).isInstanceOf( Order.class );
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDuplicatedTypedTupleTransformedEntitySelection(SessionFactoryScope scope) {
|
||||
final String hql = "select o from LineItem i join i.order o";
|
||||
|
||||
scope.inTransaction( (session) -> {
|
||||
final List<Order> orders = session.createQuery( hql, Order.class )
|
||||
.setTupleTransformer( (tuple, aliases) -> (Order) tuple[0] )
|
||||
.list();
|
||||
assertThat( orders ).hasSize( 2 );
|
||||
assertThat( orders.get( 0 ) ).isInstanceOf( Order.class );
|
||||
assertThat( orders.get( 1 ) ).isInstanceOf( Order.class );
|
||||
} );
|
||||
|
||||
scope.inTransaction( (session) -> {
|
||||
final List<Order> orders = session.createQuery( hql )
|
||||
.setTupleTransformer( (tuple, aliases) -> tuple[ 0 ] )
|
||||
.list();
|
||||
assertThat( orders ).hasSize( 2 );
|
||||
assertThat( orders.get( 0 ) ).isInstanceOf( Order.class );
|
||||
assertThat( orders.get( 1 ) ).isInstanceOf( Order.class );
|
||||
} );
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
public void prepareTestData(SessionFactoryScope scope) {
|
||||
scope.inTransaction( (session) -> {
|
||||
final Vendor acme = new Vendor( 1, "Acme, Inc.", null );
|
||||
session.persist( acme );
|
||||
|
||||
final Product widget = new Product( 1, UUID.randomUUID(), acme );
|
||||
session.persist( widget );
|
||||
|
||||
final SalesAssociate associate = new SalesAssociate( 1, new Name( "John", "Doe" ) );
|
||||
session.persist( associate );
|
||||
|
||||
final MonetaryAmount oneDollar = Monetary.getDefaultAmountFactory()
|
||||
.setNumber( 1 )
|
||||
.setCurrency( "USD" )
|
||||
.create();
|
||||
|
||||
final CardPayment payment1 = new CardPayment( 1, 123, oneDollar );
|
||||
session.persist( payment1 );
|
||||
final Order order1 = new Order( 1, payment1, associate );
|
||||
session.persist( order1 );
|
||||
final LineItem lineItem11 = new LineItem( 11, widget, 1, oneDollar, order1 );
|
||||
session.persist( lineItem11 );
|
||||
final LineItem lineItem12 = new LineItem( 12, widget, 1, oneDollar, order1 );
|
||||
session.persist( lineItem12 );
|
||||
|
||||
final CardPayment payment2 = new CardPayment( 2, 321, oneDollar );
|
||||
session.persist( payment2 );
|
||||
final Order order2 = new Order( 2, payment2, associate );
|
||||
session.persist( order2 );
|
||||
final LineItem lineItem2 = new LineItem( 2, widget, 1, oneDollar, order2 );
|
||||
session.persist( lineItem2 );
|
||||
} );
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void dropTestData(SessionFactoryScope scope) {
|
||||
scope.inTransaction( (session) -> {
|
||||
session.createMutationQuery( "delete LineItem" ).executeUpdate();
|
||||
session.createMutationQuery( "delete Order" ).executeUpdate();
|
||||
session.createMutationQuery( "delete Payment" ).executeUpdate();
|
||||
session.createMutationQuery( "delete Product" ).executeUpdate();
|
||||
session.createMutationQuery( "delete Vendor" ).executeUpdate();
|
||||
session.createMutationQuery( "delete SalesAssociate" ).executeUpdate();
|
||||
} );
|
||||
}
|
||||
}
|
|
@ -25,6 +25,22 @@ public class LineItem {
|
|||
|
||||
private Order order;
|
||||
|
||||
public LineItem() {
|
||||
}
|
||||
|
||||
public LineItem(
|
||||
Integer id,
|
||||
Product product,
|
||||
int quantity,
|
||||
MonetaryAmount subTotal,
|
||||
Order order) {
|
||||
this.id = id;
|
||||
this.product = product;
|
||||
this.quantity = quantity;
|
||||
this.subTotal = subTotal;
|
||||
this.order = order;
|
||||
}
|
||||
|
||||
@Id
|
||||
public Integer getId() {
|
||||
return id;
|
||||
|
|
|
@ -25,6 +25,20 @@ public class Order {
|
|||
private Payment payment;
|
||||
private SalesAssociate salesAssociate;
|
||||
|
||||
public Order() {
|
||||
}
|
||||
|
||||
public Order(Integer id, Payment payment, SalesAssociate salesAssociate) {
|
||||
this( id, Instant.now(), payment, salesAssociate );
|
||||
}
|
||||
|
||||
public Order(Integer id, Instant transacted, Payment payment, SalesAssociate salesAssociate) {
|
||||
this.id = id;
|
||||
this.transacted = transacted;
|
||||
this.payment = payment;
|
||||
this.salesAssociate = salesAssociate;
|
||||
}
|
||||
|
||||
@Id
|
||||
public Integer getId() {
|
||||
return id;
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* 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.testing.orm.junit;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Repeatable;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Specifies the URL to the Jira issue associated with a test.
|
||||
* Is repeatable, so multiple JIRA issues can be indicated.
|
||||
*
|
||||
* @see JiraGroup
|
||||
*
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
@Retention( RetentionPolicy.RUNTIME )
|
||||
@Target({ElementType.TYPE, ElementType.METHOD})
|
||||
@Repeatable( JiraGroup.class )
|
||||
public @interface Jira {
|
||||
/**
|
||||
* The URL to the Jira issue
|
||||
*/
|
||||
String value();
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* 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.testing.orm.junit;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Grouping annotation for `@Jira`
|
||||
*
|
||||
* @see Jira
|
||||
*
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
@Retention( RetentionPolicy.RUNTIME )
|
||||
@Target({ElementType.TYPE, ElementType.METHOD})
|
||||
public @interface JiraGroup {
|
||||
Jira[] value();
|
||||
}
|
Loading…
Reference in New Issue