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)
|
Object[].class)
|
||||||
.getResultList();
|
.getResultList();
|
||||||
//end::hql-relational-comparisons-example[]
|
//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 jakarta.persistence.TupleElement;
|
||||||
|
|
||||||
import org.hibernate.HibernateException;
|
import org.hibernate.HibernateException;
|
||||||
|
import org.hibernate.query.TypedTupleTransformer;
|
||||||
import org.hibernate.transform.ResultTransformer;
|
import org.hibernate.transform.ResultTransformer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -21,13 +22,18 @@ import org.hibernate.transform.ResultTransformer;
|
||||||
*
|
*
|
||||||
* @author Arnold Galovics
|
* @author Arnold Galovics
|
||||||
*/
|
*/
|
||||||
public class NativeQueryTupleTransformer implements ResultTransformer<Tuple> {
|
public class NativeQueryTupleTransformer implements ResultTransformer<Tuple>, TypedTupleTransformer<Tuple> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Tuple transformTuple(Object[] tuple, String[] aliases) {
|
public Tuple transformTuple(Object[] tuple, String[] aliases) {
|
||||||
return new NativeTupleImpl( tuple, aliases );
|
return new NativeTupleImpl( tuple, aliases );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<Tuple> getTransformedType() {
|
||||||
|
return Tuple.class;
|
||||||
|
}
|
||||||
|
|
||||||
private static class NativeTupleElementImpl<X> implements TupleElement<X> {
|
private static class NativeTupleElementImpl<X> implements TupleElement<X> {
|
||||||
|
|
||||||
private final Class<? extends X> javaType;
|
private final Class<? extends X> javaType;
|
||||||
|
|
|
@ -211,6 +211,11 @@ public class LoaderSqlAstCreationState
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Boolean isDeDuplicationEnabled() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Boolean isResultCachingEnabled() {
|
public Boolean isResultCachingEnabled() {
|
||||||
return false;
|
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.GraphSemantic;
|
||||||
import org.hibernate.graph.spi.AppliedGraph;
|
import org.hibernate.graph.spi.AppliedGraph;
|
||||||
import org.hibernate.graph.spi.RootGraphImplementor;
|
import org.hibernate.graph.spi.RootGraphImplementor;
|
||||||
import org.hibernate.query.spi.Limit;
|
|
||||||
import org.hibernate.query.ResultListTransformer;
|
import org.hibernate.query.ResultListTransformer;
|
||||||
import org.hibernate.query.TupleTransformer;
|
import org.hibernate.query.TupleTransformer;
|
||||||
|
import org.hibernate.query.spi.Limit;
|
||||||
import org.hibernate.query.spi.MutableQueryOptions;
|
import org.hibernate.query.spi.MutableQueryOptions;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -43,6 +43,7 @@ public class QueryOptionsImpl implements MutableQueryOptions, AppliedGraph {
|
||||||
|
|
||||||
private TupleTransformer tupleTransformer;
|
private TupleTransformer tupleTransformer;
|
||||||
private ResultListTransformer resultListTransformer;
|
private ResultListTransformer resultListTransformer;
|
||||||
|
private Boolean deDupEnabled;
|
||||||
|
|
||||||
private RootGraphImplementor<?> rootGraph;
|
private RootGraphImplementor<?> rootGraph;
|
||||||
private GraphSemantic graphSemantic;
|
private GraphSemantic graphSemantic;
|
||||||
|
@ -160,6 +161,15 @@ public class QueryOptionsImpl implements MutableQueryOptions, AppliedGraph {
|
||||||
return resultListTransformer;
|
return resultListTransformer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Boolean isDeDuplicationEnabled() {
|
||||||
|
return deDupEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDeDuplicationEnabled(boolean enabled) {
|
||||||
|
this.deDupEnabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
public void setResultCacheRegionName(String resultCacheRegionName) {
|
public void setResultCacheRegionName(String resultCacheRegionName) {
|
||||||
this.resultCacheRegionName = resultCacheRegionName;
|
this.resultCacheRegionName = resultCacheRegionName;
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,6 +58,11 @@ public class DelegatingQueryOptions implements QueryOptions {
|
||||||
return queryOptions.getResultListTransformer();
|
return queryOptions.getResultListTransformer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Boolean isDeDuplicationEnabled() {
|
||||||
|
return queryOptions.isDeDuplicationEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Boolean isResultCachingEnabled() {
|
public Boolean isResultCachingEnabled() {
|
||||||
return queryOptions.isResultCachingEnabled();
|
return queryOptions.isResultCachingEnabled();
|
||||||
|
|
|
@ -62,6 +62,13 @@ public interface QueryOptions {
|
||||||
*/
|
*/
|
||||||
ResultListTransformer<?> getResultListTransformer();
|
ResultListTransformer<?> getResultListTransformer();
|
||||||
|
|
||||||
|
Boolean isDeDuplicationEnabled();
|
||||||
|
|
||||||
|
default boolean shouldApplyDeDuplication() {
|
||||||
|
final Boolean setting = isDeDuplicationEnabled();
|
||||||
|
return setting != null && setting;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Should results from the query be cached?
|
* Should results from the query be cached?
|
||||||
*
|
*
|
||||||
|
|
|
@ -80,6 +80,11 @@ public abstract class QueryOptionsAdapter implements QueryOptions {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Boolean isDeDuplicationEnabled() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getResultCacheRegionName() {
|
public String getResultCacheRegionName() {
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -105,7 +105,7 @@ public class NativeSelectQueryPlanImpl<R> implements NativeSelectQueryPlan<R> {
|
||||||
jdbcParameterBindings,
|
jdbcParameterBindings,
|
||||||
SqmJdbcExecutionContextAdapter.usingLockingAndPaging( executionContext ),
|
SqmJdbcExecutionContextAdapter.usingLockingAndPaging( executionContext ),
|
||||||
null,
|
null,
|
||||||
ListResultsConsumer.UniqueSemantic.NONE
|
ListResultsConsumer.UniqueSemantic.NEVER
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -122,7 +122,9 @@ public class ConcreteSqmSelectQueryPlan<R> implements SelectQueryPlan<R> {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
rowTransformer,
|
rowTransformer,
|
||||||
ListResultsConsumer.UniqueSemantic.FILTER
|
queryOptions.shouldApplyDeDuplication()
|
||||||
|
? ListResultsConsumer.UniqueSemantic.FILTER
|
||||||
|
: ListResultsConsumer.UniqueSemantic.NONE
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
|
@ -205,13 +207,7 @@ public class ConcreteSqmSelectQueryPlan<R> implements SelectQueryPlan<R> {
|
||||||
|
|
||||||
// NOTE : if we get here we have a resultType of some kind
|
// NOTE : if we get here we have a resultType of some kind
|
||||||
|
|
||||||
if ( queryOptions.getTupleTransformer() != null ) {
|
if ( selections.size() > 1 ) {
|
||||||
// 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 ) {
|
|
||||||
throw new IllegalQueryOperationException( "Query defined multiple selections, return cannot be typed (other that Object[] or Tuple)" );
|
throw new IllegalQueryOperationException( "Query defined multiple selections, return cannot be typed (other that Object[] or Tuple)" );
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
|
@ -230,6 +230,7 @@ public class OutputsImpl implements Outputs {
|
||||||
executionContext,
|
executionContext,
|
||||||
null,
|
null,
|
||||||
RowTransformerStandardImpl.INSTANCE,
|
RowTransformerStandardImpl.INSTANCE,
|
||||||
|
null,
|
||||||
jdbcValues
|
jdbcValues
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -68,16 +68,12 @@ import org.hibernate.type.BasicType;
|
||||||
import org.hibernate.type.descriptor.java.JavaType;
|
import org.hibernate.type.descriptor.java.JavaType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Standard JdbcSelectExecutor implementation used by Hibernate,
|
||||||
|
* through {@link JdbcSelectExecutorStandardImpl#INSTANCE}
|
||||||
|
*
|
||||||
* @author Steve Ebersole
|
* @author Steve Ebersole
|
||||||
*/
|
*/
|
||||||
public class JdbcSelectExecutorStandardImpl implements JdbcSelectExecutor {
|
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
|
* Singleton access
|
||||||
*/
|
*/
|
||||||
|
@ -89,6 +85,7 @@ public class JdbcSelectExecutorStandardImpl implements JdbcSelectExecutor {
|
||||||
JdbcParameterBindings jdbcParameterBindings,
|
JdbcParameterBindings jdbcParameterBindings,
|
||||||
ExecutionContext executionContext,
|
ExecutionContext executionContext,
|
||||||
RowTransformer<R> rowTransformer,
|
RowTransformer<R> rowTransformer,
|
||||||
|
Class<R> domainResultType,
|
||||||
ListResultsConsumer.UniqueSemantic uniqueSemantic) {
|
ListResultsConsumer.UniqueSemantic uniqueSemantic) {
|
||||||
// Only do auto flushing for top level queries
|
// Only do auto flushing for top level queries
|
||||||
return executeQuery(
|
return executeQuery(
|
||||||
|
@ -96,6 +93,7 @@ public class JdbcSelectExecutorStandardImpl implements JdbcSelectExecutor {
|
||||||
jdbcParameterBindings,
|
jdbcParameterBindings,
|
||||||
executionContext,
|
executionContext,
|
||||||
rowTransformer,
|
rowTransformer,
|
||||||
|
domainResultType,
|
||||||
(sql) -> executionContext.getSession()
|
(sql) -> executionContext.getSession()
|
||||||
.getJdbcCoordinator()
|
.getJdbcCoordinator()
|
||||||
.getStatementPreparer()
|
.getStatementPreparer()
|
||||||
|
@ -118,6 +116,7 @@ public class JdbcSelectExecutorStandardImpl implements JdbcSelectExecutor {
|
||||||
jdbcParameterBindings,
|
jdbcParameterBindings,
|
||||||
executionContext,
|
executionContext,
|
||||||
rowTransformer,
|
rowTransformer,
|
||||||
|
null,
|
||||||
(sql) -> executionContext.getSession().getJdbcCoordinator().getStatementPreparer().prepareQueryStatement(
|
(sql) -> executionContext.getSession().getJdbcCoordinator().getStatementPreparer().prepareQueryStatement(
|
||||||
sql,
|
sql,
|
||||||
false,
|
false,
|
||||||
|
@ -152,6 +151,7 @@ public class JdbcSelectExecutorStandardImpl implements JdbcSelectExecutor {
|
||||||
JdbcParameterBindings jdbcParameterBindings,
|
JdbcParameterBindings jdbcParameterBindings,
|
||||||
ExecutionContext executionContext,
|
ExecutionContext executionContext,
|
||||||
RowTransformer<R> rowTransformer,
|
RowTransformer<R> rowTransformer,
|
||||||
|
Class<R> domainResultType,
|
||||||
Function<String, PreparedStatement> statementCreator,
|
Function<String, PreparedStatement> statementCreator,
|
||||||
ResultsConsumer<T, R> resultsConsumer) {
|
ResultsConsumer<T, R> resultsConsumer) {
|
||||||
final PersistenceContext persistenceContext = executionContext.getSession().getPersistenceContext();
|
final PersistenceContext persistenceContext = executionContext.getSession().getPersistenceContext();
|
||||||
|
@ -168,6 +168,7 @@ public class JdbcSelectExecutorStandardImpl implements JdbcSelectExecutor {
|
||||||
jdbcParameterBindings,
|
jdbcParameterBindings,
|
||||||
executionContext,
|
executionContext,
|
||||||
rowTransformer,
|
rowTransformer,
|
||||||
|
domainResultType,
|
||||||
statementCreator,
|
statementCreator,
|
||||||
resultsConsumer
|
resultsConsumer
|
||||||
);
|
);
|
||||||
|
@ -184,6 +185,7 @@ public class JdbcSelectExecutorStandardImpl implements JdbcSelectExecutor {
|
||||||
JdbcParameterBindings jdbcParameterBindings,
|
JdbcParameterBindings jdbcParameterBindings,
|
||||||
ExecutionContext executionContext,
|
ExecutionContext executionContext,
|
||||||
RowTransformer<R> rowTransformer,
|
RowTransformer<R> rowTransformer,
|
||||||
|
Class<R> domainResultType,
|
||||||
Function<String, PreparedStatement> statementCreator,
|
Function<String, PreparedStatement> statementCreator,
|
||||||
ResultsConsumer<T, R> resultsConsumer) {
|
ResultsConsumer<T, R> resultsConsumer) {
|
||||||
return doExecuteQuery(
|
return doExecuteQuery(
|
||||||
|
@ -191,6 +193,7 @@ public class JdbcSelectExecutorStandardImpl implements JdbcSelectExecutor {
|
||||||
jdbcParameterBindings,
|
jdbcParameterBindings,
|
||||||
getScrollContext( executionContext, executionContext.getSession().getPersistenceContext() ),
|
getScrollContext( executionContext, executionContext.getSession().getPersistenceContext() ),
|
||||||
rowTransformer,
|
rowTransformer,
|
||||||
|
domainResultType,
|
||||||
statementCreator,
|
statementCreator,
|
||||||
resultsConsumer
|
resultsConsumer
|
||||||
);
|
);
|
||||||
|
@ -214,6 +217,7 @@ public class JdbcSelectExecutorStandardImpl implements JdbcSelectExecutor {
|
||||||
final AppliedGraph appliedGraph = queryOptions.getAppliedGraph();
|
final AppliedGraph appliedGraph = queryOptions.getAppliedGraph();
|
||||||
final TupleTransformer<?> tupleTransformer = queryOptions.getTupleTransformer();
|
final TupleTransformer<?> tupleTransformer = queryOptions.getTupleTransformer();
|
||||||
final ResultListTransformer<?> resultListTransformer = queryOptions.getResultListTransformer();
|
final ResultListTransformer<?> resultListTransformer = queryOptions.getResultListTransformer();
|
||||||
|
final Boolean deDuplicationEnabled = queryOptions.isDeDuplicationEnabled();
|
||||||
final Boolean resultCachingEnabled = queryOptions.isResultCachingEnabled();
|
final Boolean resultCachingEnabled = queryOptions.isResultCachingEnabled();
|
||||||
final CacheRetrieveMode cacheRetrieveMode = queryOptions.getCacheRetrieveMode();
|
final CacheRetrieveMode cacheRetrieveMode = queryOptions.getCacheRetrieveMode();
|
||||||
final CacheStoreMode cacheStoreMode = queryOptions.getCacheStoreMode();
|
final CacheStoreMode cacheStoreMode = queryOptions.getCacheStoreMode();
|
||||||
|
@ -259,6 +263,11 @@ public class JdbcSelectExecutorStandardImpl implements JdbcSelectExecutor {
|
||||||
return resultListTransformer;
|
return resultListTransformer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Boolean isDeDuplicationEnabled() {
|
||||||
|
return deDuplicationEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Boolean isResultCachingEnabled() {
|
public Boolean isResultCachingEnabled() {
|
||||||
return resultCachingEnabled;
|
return resultCachingEnabled;
|
||||||
|
@ -322,11 +331,13 @@ public class JdbcSelectExecutorStandardImpl implements JdbcSelectExecutor {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private <T, R> T doExecuteQuery(
|
private <T, R> T doExecuteQuery(
|
||||||
JdbcSelect jdbcSelect,
|
JdbcSelect jdbcSelect,
|
||||||
JdbcParameterBindings jdbcParameterBindings,
|
JdbcParameterBindings jdbcParameterBindings,
|
||||||
ExecutionContext executionContext,
|
ExecutionContext executionContext,
|
||||||
RowTransformer<R> rowTransformer,
|
RowTransformer<R> rowTransformer,
|
||||||
|
Class<R> domainResultType,
|
||||||
Function<String, PreparedStatement> statementCreator,
|
Function<String, PreparedStatement> statementCreator,
|
||||||
ResultsConsumer<T, R> resultsConsumer) {
|
ResultsConsumer<T, R> resultsConsumer) {
|
||||||
|
|
||||||
|
@ -346,7 +357,10 @@ public class JdbcSelectExecutorStandardImpl implements JdbcSelectExecutor {
|
||||||
|
|
||||||
if ( rowTransformer == null ) {
|
if ( rowTransformer == null ) {
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
final TupleTransformer<R> tupleTransformer = (TupleTransformer<R>) executionContext.getQueryOptions().getTupleTransformer();
|
final TupleTransformer<R> tupleTransformer = (TupleTransformer<R>) executionContext
|
||||||
|
.getQueryOptions()
|
||||||
|
.getTupleTransformer();
|
||||||
|
|
||||||
if ( tupleTransformer == null ) {
|
if ( tupleTransformer == null ) {
|
||||||
rowTransformer = RowTransformerStandardImpl.instance();
|
rowTransformer = RowTransformerStandardImpl.instance();
|
||||||
}
|
}
|
||||||
|
@ -415,6 +429,7 @@ public class JdbcSelectExecutorStandardImpl implements JdbcSelectExecutor {
|
||||||
? LockOptions.NONE
|
? LockOptions.NONE
|
||||||
: executionContext.getQueryOptions().getLockOptions(),
|
: executionContext.getQueryOptions().getLockOptions(),
|
||||||
rowTransformer,
|
rowTransformer,
|
||||||
|
domainResultType,
|
||||||
jdbcValues
|
jdbcValues
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -22,11 +22,21 @@ import org.hibernate.sql.results.spi.RowTransformer;
|
||||||
*/
|
*/
|
||||||
@Incubating
|
@Incubating
|
||||||
public interface JdbcSelectExecutor {
|
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(
|
<R> List<R> list(
|
||||||
JdbcSelect jdbcSelect,
|
JdbcSelect jdbcSelect,
|
||||||
JdbcParameterBindings jdbcParameterBindings,
|
JdbcParameterBindings jdbcParameterBindings,
|
||||||
ExecutionContext executionContext,
|
ExecutionContext executionContext,
|
||||||
RowTransformer<R> rowTransformer,
|
RowTransformer<R> rowTransformer,
|
||||||
|
Class<R> requestedJavaType,
|
||||||
ListResultsConsumer.UniqueSemantic uniqueSemantic);
|
ListResultsConsumer.UniqueSemantic uniqueSemantic);
|
||||||
|
|
||||||
<R> ScrollableResultsImplementor<R> scroll(
|
<R> ScrollableResultsImplementor<R> scroll(
|
||||||
|
|
|
@ -57,10 +57,12 @@ public class ResultsHelper {
|
||||||
ExecutionContext executionContext,
|
ExecutionContext executionContext,
|
||||||
LockOptions lockOptions,
|
LockOptions lockOptions,
|
||||||
RowTransformer<R> rowTransformer,
|
RowTransformer<R> rowTransformer,
|
||||||
|
Class<R> transformedResultJavaType,
|
||||||
JdbcValues jdbcValues) {
|
JdbcValues jdbcValues) {
|
||||||
|
final SessionFactoryImplementor sessionFactory = executionContext.getSession().getFactory();
|
||||||
|
|
||||||
final Map<NavigablePath, Initializer> initializerMap = new LinkedHashMap<>();
|
final Map<NavigablePath, Initializer> initializerMap = new LinkedHashMap<>();
|
||||||
final List<Initializer> initializers = new ArrayList<>();
|
final List<Initializer> initializers = new ArrayList<>();
|
||||||
final SessionFactoryImplementor sessionFactory = executionContext.getSession().getFactory();
|
|
||||||
|
|
||||||
final List<DomainResultAssembler<?>> assemblers = jdbcValues.getValuesMapping().resolveAssemblers(
|
final List<DomainResultAssembler<?>> assemblers = jdbcValues.getValuesMapping().resolveAssemblers(
|
||||||
new AssemblerCreationState() {
|
new AssemblerCreationState() {
|
||||||
|
@ -107,7 +109,7 @@ public class ResultsHelper {
|
||||||
|
|
||||||
logInitializers( initializerMap );
|
logInitializers( initializerMap );
|
||||||
|
|
||||||
return new StandardRowReader<>( assemblers, initializers, rowTransformer );
|
return new StandardRowReader<>( assemblers, initializers, rowTransformer, transformedResultJavaType );
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void logInitializers(Map<NavigablePath, Initializer> initializerMap) {
|
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<DomainResultAssembler<?>> resultAssemblers;
|
||||||
private final List<Initializer> initializers;
|
private final List<Initializer> initializers;
|
||||||
private final RowTransformer<T> rowTransformer;
|
private final RowTransformer<T> rowTransformer;
|
||||||
|
private final Class<T> domainResultJavaType;
|
||||||
|
|
||||||
private final int assemblerCount;
|
private final int assemblerCount;
|
||||||
|
|
||||||
public StandardRowReader(
|
public StandardRowReader(
|
||||||
List<DomainResultAssembler<?>> resultAssemblers,
|
List<DomainResultAssembler<?>> resultAssemblers,
|
||||||
List<Initializer> initializers,
|
List<Initializer> initializers,
|
||||||
RowTransformer<T> rowTransformer) {
|
RowTransformer<T> rowTransformer,
|
||||||
|
Class<T> domainResultJavaType) {
|
||||||
this.resultAssemblers = resultAssemblers;
|
this.resultAssemblers = resultAssemblers;
|
||||||
this.initializers = initializers;
|
this.initializers = initializers;
|
||||||
this.rowTransformer = rowTransformer;
|
this.rowTransformer = rowTransformer;
|
||||||
|
|
||||||
this.assemblerCount = resultAssemblers.size();
|
this.assemblerCount = resultAssemblers.size();
|
||||||
|
this.domainResultJavaType = domainResultJavaType;
|
||||||
|
|
||||||
logDebugInfo();
|
logDebugInfo();
|
||||||
}
|
}
|
||||||
|
@ -61,18 +64,22 @@ public class StandardRowReader<T> implements RowReader<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@SuppressWarnings("unchecked")
|
public Class<T> getDomainResultResultJavaType() {
|
||||||
public Class<T> getResultJavaType() {
|
return domainResultJavaType;
|
||||||
if ( resultAssemblers.size() == 1 ) {
|
|
||||||
return (Class<T>) resultAssemblers.get( 0 ).getAssembledJavaType().getJavaTypeClass();
|
|
||||||
}
|
|
||||||
|
|
||||||
return (Class<T>) Object[].class;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<JavaType> getResultJavaTypes() {
|
public Class<?> getResultJavaType() {
|
||||||
List<JavaType> javaTypes = new ArrayList<>( resultAssemblers.size() );
|
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 ) {
|
for ( DomainResultAssembler resultAssembler : resultAssemblers ) {
|
||||||
javaTypes.add( resultAssembler.getAssembledJavaType() );
|
javaTypes.add( resultAssembler.getAssembledJavaType() );
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,50 +8,94 @@ package org.hibernate.sql.results.spi;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
import org.hibernate.HibernateException;
|
import org.hibernate.HibernateException;
|
||||||
import org.hibernate.engine.spi.PersistenceContext;
|
import org.hibernate.engine.spi.PersistenceContext;
|
||||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||||
import org.hibernate.query.ResultListTransformer;
|
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.JdbcValues;
|
||||||
import org.hibernate.sql.results.jdbc.spi.JdbcValuesSourceProcessingOptions;
|
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.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
|
* @author Steve Ebersole
|
||||||
*/
|
*/
|
||||||
public class ListResultsConsumer<R> implements ResultsConsumer<List<R>, R> {
|
public class ListResultsConsumer<R> implements ResultsConsumer<List<R>, R> {
|
||||||
/**
|
private static final ListResultsConsumer<?> NEVER_DE_DUP_CONSUMER = new ListResultsConsumer<>( UniqueSemantic.NEVER );
|
||||||
* Singleton access
|
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 UNIQUE_FILTER_INSTANCE = new ListResultsConsumer( UniqueSemantic.FILTER );
|
private static final ListResultsConsumer<?> ERROR_DUP_CONSUMER = new ListResultsConsumer<>( UniqueSemantic.ASSERT );
|
||||||
private static final ListResultsConsumer NORMAL_INSTANCE = new ListResultsConsumer( UniqueSemantic.NONE );
|
|
||||||
private static final ListResultsConsumer UNIQUE_INSTANCE = new ListResultsConsumer( UniqueSemantic.ASSERT );
|
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
public static <R> ListResultsConsumer<R> instance(UniqueSemantic uniqueSemantic) {
|
public static <R> ListResultsConsumer<R> instance(UniqueSemantic uniqueSemantic) {
|
||||||
switch ( uniqueSemantic ) {
|
switch ( uniqueSemantic ) {
|
||||||
case ASSERT:
|
case ASSERT: {
|
||||||
return UNIQUE_INSTANCE;
|
return (ListResultsConsumer<R>) ERROR_DUP_CONSUMER;
|
||||||
case FILTER:
|
}
|
||||||
return UNIQUE_FILTER_INSTANCE;
|
case FILTER: {
|
||||||
default:
|
return (ListResultsConsumer<R>) DE_DUP_CONSUMER;
|
||||||
return NORMAL_INSTANCE;
|
}
|
||||||
|
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 {
|
public enum UniqueSemantic {
|
||||||
|
/**
|
||||||
|
* Apply no in-memory de-duplication
|
||||||
|
*/
|
||||||
NONE,
|
NONE,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply in-memory de-duplication, removing rows already part of the results
|
||||||
|
*/
|
||||||
FILTER,
|
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 UniqueSemantic uniqueSemantic;
|
||||||
|
private final ResultHandler<R> resultHandler;
|
||||||
|
|
||||||
public ListResultsConsumer(UniqueSemantic uniqueSemantic) {
|
public ListResultsConsumer(UniqueSemantic uniqueSemantic) {
|
||||||
this.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
|
@Override
|
||||||
|
@ -63,61 +107,75 @@ public class ListResultsConsumer<R> implements ResultsConsumer<List<R>, R> {
|
||||||
RowProcessingStateStandardImpl rowProcessingState,
|
RowProcessingStateStandardImpl rowProcessingState,
|
||||||
RowReader<R> rowReader) {
|
RowReader<R> rowReader) {
|
||||||
final PersistenceContext persistenceContext = session.getPersistenceContext();
|
final PersistenceContext persistenceContext = session.getPersistenceContext();
|
||||||
|
final TypeConfiguration typeConfiguration = session.getTypeConfiguration();
|
||||||
|
final JavaTypeRegistry javaTypeRegistry = typeConfiguration.getJavaTypeRegistry();
|
||||||
|
final QueryOptions queryOptions = rowProcessingState.getQueryOptions();
|
||||||
|
|
||||||
RuntimeException ex = null;
|
RuntimeException ex = null;
|
||||||
try {
|
try {
|
||||||
persistenceContext.getLoadContexts().register( jdbcValuesSourceProcessingState );
|
persistenceContext.getLoadContexts().register( jdbcValuesSourceProcessingState );
|
||||||
|
|
||||||
final List<R> results = new ArrayList<>();
|
final List<R> results = new ArrayList<>();
|
||||||
|
|
||||||
boolean uniqueRows = false;
|
final JavaType<R> domainResultJavaType;
|
||||||
|
final ResultHandler<R> resultHandlerToUse;
|
||||||
|
|
||||||
if ( uniqueSemantic != UniqueSemantic.NONE ) {
|
final TupleTransformer<?> tupleTransformer = queryOptions.getTupleTransformer();
|
||||||
final Class<R> resultJavaType = rowReader.getResultJavaType();
|
if ( tupleTransformer instanceof TypedTupleTransformer ) {
|
||||||
if ( resultJavaType != null && !resultJavaType.isArray() ) {
|
//noinspection unchecked
|
||||||
final EntityPersister entityDescriptor = session.getFactory()
|
final TypedTupleTransformer<R> typedTupleTransformer = (TypedTupleTransformer<R>) tupleTransformer;
|
||||||
.getRuntimeMetamodels()
|
domainResultJavaType = javaTypeRegistry.resolveDescriptor( typedTupleTransformer.getTransformedType() );
|
||||||
.getMappingMetamodel()
|
|
||||||
.findEntityDescriptor( resultJavaType );
|
|
||||||
if ( entityDescriptor != null ) {
|
|
||||||
uniqueRows = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( uniqueRows ) {
|
if ( uniqueSemantic == UniqueSemantic.NEVER ) {
|
||||||
final List<JavaType> resultJavaTypes = rowReader.getResultJavaTypes();
|
resultHandlerToUse = this.resultHandler;
|
||||||
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;
|
else if ( queryOptions.shouldApplyDeDuplication() ) {
|
||||||
break;
|
resultHandlerToUse = ListResultsConsumer::deDuplicationHandling;
|
||||||
}
|
}
|
||||||
|
else if ( domainResultJavaType instanceof EntityJavaType ) {
|
||||||
|
resultHandlerToUse = ListResultsConsumer::deDuplicationHandling;
|
||||||
}
|
}
|
||||||
if ( add ) {
|
else {
|
||||||
results.add( row );
|
resultHandlerToUse = this.resultHandler;
|
||||||
}
|
|
||||||
rowProcessingState.finishRowProcessing();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
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() ) {
|
while ( rowProcessingState.next() ) {
|
||||||
results.add( rowReader.readRow( rowProcessingState, processingOptions ) );
|
final R row = rowReader.readRow( rowProcessingState, processingOptions );
|
||||||
|
resultHandlerToUse.handle( row, domainResultJavaType, results, rowProcessingState );
|
||||||
rowProcessingState.finishRowProcessing();
|
rowProcessingState.finishRowProcessing();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
jdbcValuesSourceProcessingState.finishUp();
|
jdbcValuesSourceProcessingState.finishUp();
|
||||||
}
|
}
|
||||||
|
@ -126,12 +184,11 @@ public class ListResultsConsumer<R> implements ResultsConsumer<List<R>, R> {
|
||||||
}
|
}
|
||||||
|
|
||||||
//noinspection unchecked
|
//noinspection unchecked
|
||||||
final ResultListTransformer<R> resultListTransformer = (ResultListTransformer<R>) jdbcValuesSourceProcessingState.getExecutionContext()
|
final ResultListTransformer<R> resultListTransformer = (ResultListTransformer<R>) queryOptions.getResultListTransformer();
|
||||||
.getQueryOptions()
|
|
||||||
.getResultListTransformer();
|
|
||||||
if ( resultListTransformer != null ) {
|
if ( resultListTransformer != null ) {
|
||||||
return resultListTransformer.transformList( results );
|
return resultListTransformer.transformList( results );
|
||||||
}
|
}
|
||||||
|
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
catch (RuntimeException e) {
|
catch (RuntimeException e) {
|
||||||
|
@ -160,8 +217,108 @@ public class ListResultsConsumer<R> implements ResultsConsumer<List<R>, R> {
|
||||||
throw new IllegalStateException( "Should not reach this!" );
|
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
|
@Override
|
||||||
public boolean canResultsBeCached() {
|
public boolean canResultsBeCached() {
|
||||||
return true;
|
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;
|
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
|
* @author Steve Ebersole
|
||||||
*/
|
*/
|
||||||
public interface ResultsConsumer<T, R> {
|
public interface ResultsConsumer<T, R> {
|
||||||
|
|
|
@ -23,27 +23,38 @@ import org.hibernate.type.descriptor.java.JavaType;
|
||||||
*/
|
*/
|
||||||
public interface RowReader<R> {
|
public interface RowReader<R> {
|
||||||
/**
|
/**
|
||||||
* The overall row result Java type. Might be a scalar type, an
|
* The type actually returned from this reader's {@link #readRow} call,
|
||||||
* entity type, etc. Might also be a `Object[].class` for multiple
|
* accounting for any transformers.
|
||||||
* results (domain selections).
|
* <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();
|
List<Initializer> getInitializers();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The actual coordination of reading a row
|
* 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);
|
R readRow(RowProcessingState processingState, JdbcValuesSourceProcessingOptions options);
|
||||||
|
|
||||||
|
|
|
@ -9,11 +9,12 @@ package org.hibernate.transform;
|
||||||
import java.lang.reflect.Constructor;
|
import java.lang.reflect.Constructor;
|
||||||
|
|
||||||
import org.hibernate.QueryException;
|
import org.hibernate.QueryException;
|
||||||
|
import org.hibernate.query.TypedTupleTransformer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wraps the tuples in a constructor call.
|
* 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;
|
private final Constructor<T> constructor;
|
||||||
|
|
||||||
|
@ -26,6 +27,11 @@ public class AliasToBeanConstructorResultTransformer<T> implements ResultTransfo
|
||||||
this.constructor = constructor;
|
this.constructor = constructor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<T> getTransformedType() {
|
||||||
|
return constructor.getDeclaringClass();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wrap the incoming tuples in a call to our configured constructor.
|
* 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.PropertyAccessStrategyFieldImpl;
|
||||||
import org.hibernate.property.access.internal.PropertyAccessStrategyMapImpl;
|
import org.hibernate.property.access.internal.PropertyAccessStrategyMapImpl;
|
||||||
import org.hibernate.property.access.spi.Setter;
|
import org.hibernate.property.access.spi.Setter;
|
||||||
|
import org.hibernate.query.TypedTupleTransformer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Result transformer that allows to transform a result to
|
* Result transformer that allows to transform a result to
|
||||||
|
@ -22,7 +23,7 @@ import org.hibernate.property.access.spi.Setter;
|
||||||
*
|
*
|
||||||
* @author max
|
* @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
|
// IMPL NOTE : due to the delayed population of setters (setters cached
|
||||||
// for performance), we really cannot properly define equality for
|
// for performance), we really cannot properly define equality for
|
||||||
|
@ -41,6 +42,11 @@ public class AliasToBeanResultTransformer<T> implements ResultTransformer<T> {
|
||||||
this.resultClass = resultClass;
|
this.resultClass = resultClass;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<T> getTransformedType() {
|
||||||
|
return resultClass;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public T transformTuple(Object[] tuple, String[] aliases) {
|
public T transformTuple(Object[] tuple, String[] aliases) {
|
||||||
T result;
|
T result;
|
||||||
|
|
|
@ -8,6 +8,7 @@ package org.hibernate.transform;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.hibernate.internal.util.collections.CollectionHelper;
|
import org.hibernate.internal.util.collections.CollectionHelper;
|
||||||
|
import org.hibernate.query.TypedTupleTransformer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link ResultTransformer} implementation which builds a map for each "row",
|
* {@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 Gavin King
|
||||||
* @author Steve Ebersole
|
* @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();
|
public static final AliasToEntityMapResultTransformer INSTANCE = new AliasToEntityMapResultTransformer();
|
||||||
|
|
||||||
|
@ -26,6 +27,12 @@ public class AliasToEntityMapResultTransformer implements ResultTransformer<Map<
|
||||||
private AliasToEntityMapResultTransformer() {
|
private AliasToEntityMapResultTransformer() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||||
|
@Override
|
||||||
|
public Class getTransformedType() {
|
||||||
|
return Map.class;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<String,Object> transformTuple(Object[] tuple, String[] aliases) {
|
public Map<String,Object> transformTuple(Object[] tuple, String[] aliases) {
|
||||||
Map<String,Object> result = CollectionHelper.mapOfSize( tuple.length );
|
Map<String,Object> result = CollectionHelper.mapOfSize( tuple.length );
|
||||||
|
|
|
@ -9,10 +9,12 @@ package org.hibernate.transform;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
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
|
* 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();
|
public static final ToListResultTransformer INSTANCE = new ToListResultTransformer();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -21,6 +23,12 @@ public class ToListResultTransformer implements ResultTransformer<List<Object>>
|
||||||
private ToListResultTransformer() {
|
private ToListResultTransformer() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||||
|
@Override
|
||||||
|
public Class<List<Object>> getTransformedType() {
|
||||||
|
return (Class) List.class;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Object> transformTuple(Object[] tuple, String[] aliases) {
|
public List<Object> transformTuple(Object[] tuple, String[] aliases) {
|
||||||
return Arrays.asList( tuple );
|
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.Product;
|
||||||
import org.hibernate.testing.orm.domain.retail.Vendor;
|
import org.hibernate.testing.orm.domain.retail.Vendor;
|
||||||
import org.hibernate.testing.orm.junit.DomainModel;
|
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.SessionFactory;
|
||||||
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||||
import org.junit.jupiter.api.AfterEach;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
@ -29,6 +30,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||||
*/
|
*/
|
||||||
@DomainModel( standardModels = StandardDomainModel.RETAIL )
|
@DomainModel( standardModels = StandardDomainModel.RETAIL )
|
||||||
@SessionFactory
|
@SessionFactory
|
||||||
|
@JiraKey( "HHH-15133" )
|
||||||
public class ImplicitSelectWithJoinTests {
|
public class ImplicitSelectWithJoinTests {
|
||||||
private static final String HQL = "from Product p join p.vendor v where v.name like '%Steve%'";
|
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 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;
|
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
|
@Id
|
||||||
public Integer getId() {
|
public Integer getId() {
|
||||||
return id;
|
return id;
|
||||||
|
|
|
@ -25,6 +25,20 @@ public class Order {
|
||||||
private Payment payment;
|
private Payment payment;
|
||||||
private SalesAssociate salesAssociate;
|
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
|
@Id
|
||||||
public Integer getId() {
|
public Integer getId() {
|
||||||
return id;
|
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