Fix some more result set mapping issues and fix HHH-7525 as well as HHH-10504

This commit is contained in:
Christian Beikov 2021-08-06 13:26:54 +02:00
parent dd650705d0
commit b6683d2352
23 changed files with 357 additions and 167 deletions

View File

@ -182,8 +182,8 @@ include::{extrasdir}/sql-hibernate-entity-associations-query-many-to-one-join-ex
As seen in the associated SQL query, Hibernate manages to construct the entity hierarchy without requiring any extra database roundtrip.
====
By default, when using the `addJoin()` method, the result set will contain both entities that are joined.
To construct the entity hierarchy, you need to use a `ROOT_ENTITY` or `DISTINCT_ROOT_ENTITY` `ResultTransformer`.
Even when using the `addJoin()` method, the result list will only contain the root entity.
Joined entities will only be present for their respective association.
[[sql-hibernate-entity-associations-query-many-to-one-join-result-transformer-example]]
.Hibernate native query selecting entities with joined many-to-one association and `ResultTransformer`
@ -194,11 +194,6 @@ include::{sourcedir}/SQLTest.java[tags=sql-hibernate-entity-associations-query-m
----
====
[NOTE]
====
Because of the `ROOT_ENTITY` `ResultTransformer`, the query above will return the parent-side as root entities.
====
Notice that you added an alias name _pr_ to be able to specify the target property path of the join.
It is possible to do the same eager joining for collections (e.g. the `Phone#calls` `one-to-many` association).

View File

@ -15,7 +15,7 @@ import javax.persistence.PersistenceException;
import org.hibernate.Session;
import org.hibernate.dialect.H2Dialect;
import org.hibernate.dialect.Oracle8iDialect;
import org.hibernate.dialect.OracleDialect;
import org.hibernate.dialect.PostgreSQLDialect;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
import org.hibernate.loader.NonUniqueDiscoveredSqlAliasException;
@ -36,6 +36,7 @@ import org.hibernate.userguide.model.PhoneType;
import org.hibernate.userguide.model.WireTransferPayment;
import org.hibernate.testing.RequiresDialect;
import org.hibernate.testing.TestForIssue;
import org.junit.Before;
import org.junit.Test;
@ -335,17 +336,15 @@ public class SQLTest extends BaseEntityManagerFunctionalTestCase {
@Test
public void test_sql_jpa_entity_associations_query_many_to_one_join_example() {
doInJPA( this::entityManagerFactory, entityManager -> {
//tag::sql-jpa-entity-associations-query-many-to-one-join-example[]
List<Phone> phones = entityManager.createNativeQuery(
"SELECT * " +
"SELECT ph.* " +
"FROM Phone ph " +
"JOIN Person pr ON ph.person_id = pr.id", Phone.class )
.getResultList();
for(Phone phone : phones) {
for( Phone phone : phones ) {
assertNotNull( phone.getPerson().getName() );
}
//end::sql-jpa-entity-associations-query-many-to-one-join-example[]
assertEquals(3, phones.size());
});
}
@ -355,18 +354,16 @@ public class SQLTest extends BaseEntityManagerFunctionalTestCase {
doInJPA( this::entityManagerFactory, entityManager -> {
Session session = entityManager.unwrap( Session.class );
//tag::sql-hibernate-entity-associations-query-many-to-one-join-example[]
List<Object[]> tuples = session.createNativeQuery(
"SELECT * " +
List<Phone> tuples = session.createNativeQuery(
"SELECT {ph.*}, {pr.*} " +
"FROM Phone ph " +
"JOIN Person pr ON ph.person_id = pr.id" )
.addEntity("phone", Phone.class )
.addJoin( "pr", "phone.person")
.addEntity("ph", Phone.class )
.addJoin( "pr", "ph.person" )
.list();
for(Object[] tuple : tuples) {
Phone phone = (Phone) tuple[0];
Person person = (Person) tuple[1];
assertNotNull( person.getName() );
for ( Phone phone : tuples ) {
assertNotNull( phone.getPerson().getName() );
}
//end::sql-hibernate-entity-associations-query-many-to-one-join-example[]
assertEquals(3, tuples.size());
@ -378,37 +375,36 @@ public class SQLTest extends BaseEntityManagerFunctionalTestCase {
doInJPA( this::entityManagerFactory, entityManager -> {
Session session = entityManager.unwrap( Session.class );
//tag::sql-hibernate-entity-associations-query-many-to-one-join-result-transformer-example[]
List<Person> persons = session.createNativeQuery(
"SELECT * " +
List<Phone> phones = session.createNativeQuery(
"SELECT {ph.*}, {pr.*} " +
"FROM Phone ph " +
"JOIN Person pr ON ph.person_id = pr.id" )
.addEntity("phone", Phone.class )
.addJoin( "pr", "phone.person")
.setResultTransformer( RootEntityResultTransformer.INSTANCE )
.addEntity("ph", Phone.class )
.addJoin( "pr", "ph.person")
.list();
for(Person person : persons) {
person.getPhones();
for ( Phone person : phones ) {
person.getPerson();
}
//end::sql-hibernate-entity-associations-query-many-to-one-join-result-transformer-example[]
assertEquals(3, persons.size());
assertEquals(3, phones.size());
});
}
@Test
@RequiresDialect(H2Dialect.class)
@RequiresDialect(Oracle8iDialect.class)
@RequiresDialect(OracleDialect.class)
@RequiresDialect(PostgreSQLDialect.class)
public void test_sql_jpa_entity_associations_query_one_to_many_join_example() {
doInJPA( this::entityManagerFactory, entityManager -> {
//tag::sql-jpa-entity-associations-query-one-to-many-join-example[]
List<Phone> phones = entityManager.createNativeQuery(
"SELECT * " +
"SELECT ph.* " +
"FROM Phone ph " +
"JOIN phone_call c ON c.phone_id = ph.id", Phone.class )
.getResultList();
for(Phone phone : phones) {
for ( Phone phone : phones ) {
List<Call> calls = phone.getCalls();
}
//end::sql-jpa-entity-associations-query-one-to-many-join-example[]
@ -417,50 +413,43 @@ public class SQLTest extends BaseEntityManagerFunctionalTestCase {
}
@Test
@TestForIssue( jiraKey = "HHH-10504")
public void test_sql_hibernate_entity_associations_query_one_to_many_join_example_1() {
try {
doInJPA( this::entityManagerFactory, entityManager -> {
Session session = entityManager.unwrap( Session.class );
List<Phone> phones = session.createNativeQuery(
"SELECT * " +
"FROM Phone ph " +
"JOIN phone_call c ON c.phone_id = ph.id" )
.addEntity("phone", Phone.class )
.addJoin( "c", "phone.calls")
.setResultTransformer( DistinctRootEntityResultTransformer.INSTANCE)
.list();
doInJPA( this::entityManagerFactory, entityManager -> {
Session session = entityManager.unwrap( Session.class );
List<Phone> phones = session.createNativeQuery(
"SELECT {ph.*}, {c.*} " +
"FROM Phone ph " +
"JOIN phone_call c ON c.phone_id = ph.id" )
.addEntity("ph", Phone.class )
.addJoin( "c", "ph.calls" )
.list();
for(Phone phone : phones) {
List<Call> calls = phone.getCalls();
}
assertEquals(2, phones.size());
});
}
catch (Exception e) {
log.error( "HHH-10504", e );
//See issue https://hibernate.atlassian.net/browse/HHH-10504
}
for ( Phone phone : phones ) {
List<Call> calls = phone.getCalls();
}
assertEquals( 2, phones.size() );
});
}
@Test
@RequiresDialect(H2Dialect.class)
@RequiresDialect(Oracle8iDialect.class)
@RequiresDialect(OracleDialect.class)
@RequiresDialect(PostgreSQLDialect.class)
public void test_sql_hibernate_entity_associations_query_one_to_many_join_example_2() {
doInJPA( this::entityManagerFactory, entityManager -> {
Session session = entityManager.unwrap( Session.class );
//tag::sql-hibernate-entity-associations-query-one-to-many-join-example[]
List<Object[]> tuples = session.createNativeQuery(
"SELECT * " +
List<Phone> tuples = session.createNativeQuery(
"SELECT {ph.*}, {c.*} " +
"FROM Phone ph " +
"JOIN phone_call c ON c.phone_id = ph.id" )
.addEntity("phone", Phone.class )
.addJoin( "c", "phone.calls")
.addEntity("ph", Phone.class )
.addJoin( "c", "ph.calls")
.list();
for(Object[] tuple : tuples) {
Phone phone = (Phone) tuple[0];
Call call = (Call) tuple[1];
for ( Phone phone : tuples ) {
List<Call> calls = phone.getCalls();
}
//end::sql-hibernate-entity-associations-query-one-to-many-join-example[]
assertEquals(2, tuples.size());
@ -472,10 +461,10 @@ public class SQLTest extends BaseEntityManagerFunctionalTestCase {
try {
doInJPA( this::entityManagerFactory, entityManager -> {
//tag::sql-jpa-multi-entity-query-example[]
List<Object> entities = entityManager.createNativeQuery(
List<Person> entities = entityManager.createNativeQuery(
"SELECT * " +
"FROM Person pr, Partner pt " +
"WHERE pr.name = pt.name" )
"WHERE pr.name = pt.name", Person.class )
.getResultList();
//end::sql-jpa-multi-entity-query-example[]
assertEquals(2, entities.size());
@ -493,10 +482,10 @@ public class SQLTest extends BaseEntityManagerFunctionalTestCase {
doInJPA( this::entityManagerFactory, entityManager -> {
Session session = entityManager.unwrap( Session.class );
//tag::sql-hibernate-multi-entity-query-example[]
List<Object> entities = session.createNativeQuery(
List<Person> entities = session.createNativeQuery(
"SELECT * " +
"FROM Person pr, Partner pt " +
"WHERE pr.name = pt.name" )
"WHERE pr.name = pt.name", Person.class )
.list();
//end::sql-hibernate-multi-entity-query-example[]
assertEquals( 2, entities.size() );
@ -720,7 +709,7 @@ public class SQLTest extends BaseEntityManagerFunctionalTestCase {
@Test
@RequiresDialect(H2Dialect.class)
@RequiresDialect(Oracle8iDialect.class)
@RequiresDialect(OracleDialect.class)
@RequiresDialect(PostgreSQLDialect.class)
public void test_sql_jpa_entity_associations_named_query_example() {
doInJPA( this::entityManagerFactory, entityManager -> {
@ -741,7 +730,7 @@ public class SQLTest extends BaseEntityManagerFunctionalTestCase {
@Test
@RequiresDialect(H2Dialect.class)
@RequiresDialect(Oracle8iDialect.class)
@RequiresDialect(OracleDialect.class)
@RequiresDialect(PostgreSQLDialect.class)
public void test_sql_hibernate_entity_associations_named_query_example() {
doInJPA( this::entityManagerFactory, entityManager -> {

View File

@ -26,6 +26,7 @@ import org.hibernate.FlushMode;
import org.hibernate.Hibernate;
import org.hibernate.HibernateException;
import org.hibernate.Interceptor;
import org.hibernate.LockMode;
import org.hibernate.MultiTenancyStrategy;
import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.SessionEventListener;
@ -705,7 +706,7 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont
try {
NativeQueryImplementor query = createNativeQuery( sqlString );
// query.addEntity( "alias1", resultClass.getName(), LockMode.READ );
query.addEntity( "alias1", resultClass.getName(), LockMode.READ );
return query;
}
catch (RuntimeException he) {

View File

@ -13,6 +13,7 @@ import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.engine.spi.LoadQueryInfluencers;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.query.NavigablePath;
import org.hibernate.sql.ast.spi.SqlAliasBase;
import org.hibernate.sql.ast.spi.SqlAstCreationState;
import org.hibernate.sql.ast.spi.SqlAstCreationContext;
import org.hibernate.sql.ast.tree.from.RootTableGroupProducer;
@ -44,4 +45,15 @@ public interface Loadable extends ModelPart, RootTableGroupProducer {
SqlAstCreationState creationState, SqlAstCreationContext creationContext) {
throw new NotYetImplementedFor6Exception( getClass() );
}
@Override
default TableGroup createRootTableGroup(
boolean canUseInnerJoins,
NavigablePath navigablePath,
String explicitSourceAlias,
Supplier<Consumer<Predicate>> additionalPredicateCollectorAccess,
SqlAliasBase sqlAliasBase,
SqlAstCreationState creationState, SqlAstCreationContext creationContext) {
throw new NotYetImplementedFor6Exception( getClass() );
}
}

View File

@ -282,11 +282,32 @@ public interface EntityMappingType extends ManagedMappingType, EntityValuedModel
Supplier<Consumer<Predicate>> additionalPredicateCollectorAccess,
SqlAstCreationState creationState,
SqlAstCreationContext creationContext) {
return createRootTableGroup(
canUseInnerJoins,
navigablePath,
explicitSourceAlias,
additionalPredicateCollectorAccess,
creationState.getSqlAliasBaseGenerator().createSqlAliasBase( getSqlAliasStem() ),
creationState,
creationContext
);
}
@Override
default TableGroup createRootTableGroup(
boolean canUseInnerJoins,
NavigablePath navigablePath,
String explicitSourceAlias,
Supplier<Consumer<Predicate>> additionalPredicateCollectorAccess,
SqlAliasBase sqlAliasBase,
SqlAstCreationState creationState,
SqlAstCreationContext creationContext) {
return getEntityPersister().createRootTableGroup(
canUseInnerJoins,
navigablePath,
explicitSourceAlias,
additionalPredicateCollectorAccess,
sqlAliasBase,
creationState,
creationContext
);

View File

@ -1372,11 +1372,10 @@ public abstract class AbstractEntityPersister
NavigablePath navigablePath,
String explicitSourceAlias,
Supplier<Consumer<Predicate>> additionalPredicateCollectorAccess,
SqlAliasBase sqlAliasBase,
SqlAstCreationState creationState,
SqlAstCreationContext creationContext) {
final SqlExpressionResolver sqlExpressionResolver = creationState.getSqlExpressionResolver();
final SqlAliasBaseGenerator aliasBaseGenerator = creationState.getSqlAliasBaseGenerator();
final SqlAliasBase sqlAliasBase = aliasBaseGenerator.createSqlAliasBase( getSqlAliasStem() );
final TableReference primaryTableReference = createPrimaryTableReference(
sqlAliasBase,
@ -1411,7 +1410,9 @@ public abstract class AbstractEntityPersister
Collections.emptySet()
),
joinedTableReference,
generateJoinPredicate(
additionalPredicateCollectorAccess == null
? null
: generateJoinPredicate(
primaryTableReference,
joinedTableReference,
getSubclassTableKeyColumns( i ),

View File

@ -47,6 +47,7 @@ import org.hibernate.query.NavigablePath;
import org.hibernate.sql.InFragment;
import org.hibernate.sql.Insert;
import org.hibernate.sql.SelectFragment;
import org.hibernate.sql.ast.spi.SqlAliasBase;
import org.hibernate.sql.ast.spi.SqlAstCreationContext;
import org.hibernate.sql.ast.spi.SqlAstCreationState;
import org.hibernate.sql.ast.spi.SqlExpressionResolver;
@ -931,6 +932,7 @@ public class SingleTableEntityPersister extends AbstractEntityPersister {
NavigablePath navigablePath,
String explicitSourceAlias,
Supplier<Consumer<Predicate>> additionalPredicateCollectorAccess,
SqlAliasBase sqlAliasBase,
SqlAstCreationState creationState,
SqlAstCreationContext creationContext) {
final TableGroup tableGroup = super.createRootTableGroup(
@ -938,11 +940,12 @@ public class SingleTableEntityPersister extends AbstractEntityPersister {
navigablePath,
explicitSourceAlias,
additionalPredicateCollectorAccess,
sqlAliasBase,
creationState,
creationContext
);
if ( needsDiscriminator() ) {
if ( additionalPredicateCollectorAccess != null && needsDiscriminator() ) {
final Predicate discriminatorPredicate = createDiscriminatorPredicate(
tableGroup,
creationState.getSqlExpressionResolver(),

View File

@ -245,11 +245,10 @@ public class UnionSubclassEntityPersister extends AbstractEntityPersister {
NavigablePath navigablePath,
String explicitSourceAlias,
Supplier<Consumer<Predicate>> additionalPredicateCollectorAccess,
SqlAliasBase sqlAliasBase,
SqlAstCreationState creationState,
SqlAstCreationContext creationContext) {
final SqlAliasBase sqlAliasBase = creationState.getSqlAliasBaseGenerator().createSqlAliasBase( getSqlAliasStem() );
final TableReference tableReference = resolvePrimaryTableReference(sqlAliasBase);
final TableReference tableReference = resolvePrimaryTableReference( sqlAliasBase );
return new UnionTableGroup( canUseInnerJoins, navigablePath, tableReference, this, explicitSourceAlias );
}

View File

@ -12,6 +12,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
@ -19,15 +20,22 @@ import org.hibernate.Incubating;
import org.hibernate.Internal;
import org.hibernate.LockMode;
import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.engine.FetchTiming;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.internal.util.StringHelper;
import org.hibernate.loader.NonUniqueDiscoveredSqlAliasException;
import org.hibernate.metamodel.mapping.BasicValuedMapping;
import org.hibernate.persister.entity.AbstractEntityPersister;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.query.named.NamedResultSetMappingMemento;
import org.hibernate.query.results.dynamic.DynamicFetchBuilderLegacy;
import org.hibernate.sql.ast.spi.SqlSelection;
import org.hibernate.sql.results.graph.DomainResult;
import org.hibernate.sql.results.graph.Fetch;
import org.hibernate.sql.results.graph.basic.BasicFetch;
import org.hibernate.sql.results.graph.basic.BasicResult;
import org.hibernate.sql.results.graph.embeddable.EmbeddableResultGraphNode;
import org.hibernate.sql.results.graph.entity.EntityResult;
import org.hibernate.sql.results.jdbc.spi.JdbcValuesMapping;
import org.hibernate.sql.results.jdbc.spi.JdbcValuesMetadata;
import org.hibernate.type.BasicType;
@ -183,10 +191,92 @@ public class ResultSetMappingImpl implements ResultSetMapping {
domainResults.add( domainResult );
}
// We only need this check when we actually have result builders
// As people should be able to just run native queries and work with tuples
if ( resultBuilders != null ) {
final Set<String> knownDuplicateAliases = new TreeSet<>( String.CASE_INSENSITIVE_ORDER );
if ( resultBuilders.size() == 1 && domainResults.size() == 1 && domainResults.get( 0 ) instanceof EntityResult ) {
// Special case for result set mappings that just fetch a single polymorphic entity
final EntityResult entityResult = (EntityResult) domainResults.get( 0 );
final boolean polymorphic = entityResult.getReferencedMappingContainer()
.getEntityPersister()
.getEntityMetamodel()
.isPolymorphic();
// We only need to check for duplicate aliases if we have join fetches,
// otherwise we assume that even if there are duplicate aliases, the values are equivalent.
// If we don't do that, there is no way to fetch joined inheritance entities
if ( polymorphic && ( legacyFetchBuilders == null || legacyFetchBuilders.isEmpty() )
&& !hasJoinFetches( entityResult.getFetches() ) ) {
final Set<String> aliases = new TreeSet<>( String.CASE_INSENSITIVE_ORDER );
final AbstractEntityPersister entityPersister = (AbstractEntityPersister) entityResult.getReferencedMappingContainer()
.getEntityPersister();
for ( String[] columns : entityPersister.getContraintOrderedTableKeyColumnClosure() ) {
addColumns( aliases, knownDuplicateAliases, columns );
}
addColumn( aliases, knownDuplicateAliases, entityPersister.getDiscriminatorColumnName() );
addColumn( aliases, knownDuplicateAliases, entityPersister.getVersionColumnName() );
for ( int i = 0; i < entityPersister.countSubclassProperties(); i++ ) {
addColumns(
aliases,
knownDuplicateAliases,
entityPersister.getSubclassPropertyColumnNames( i )
);
}
}
}
final String[] aliases = new String[rowSize];
final Map<String, Boolean> aliasHasDuplicates = new HashMap<>( rowSize );
for ( int i = 0; i < rowSize; i++ ) {
aliasHasDuplicates.compute(
aliases[i] = jdbcResultsMetadata.resolveColumnName( i + 1 ),
(k, v) -> v == null ? Boolean.FALSE : Boolean.TRUE
);
}
// Only check for duplicates for the selections that we actually use
for ( SqlSelection sqlSelection : sqlSelections ) {
final String alias = aliases[sqlSelection.getValuesArrayPosition()];
if ( !knownDuplicateAliases.contains( alias ) && aliasHasDuplicates.get( alias ) == Boolean.TRUE ) {
throw new NonUniqueDiscoveredSqlAliasException(
"Encountered a duplicated sql alias [" + alias + "] during auto-discovery of a native-sql query"
);
}
}
}
final Map<String, LockMode> registeredLockModes = creationState.getRegisteredLockModes();
return new JdbcValuesMappingImpl( sqlSelections, domainResults, rowSize, registeredLockModes );
}
private static void addColumns(Set<String> aliases, Set<String> knownDuplicateAliases, String[] columns) {
for ( int i = 0; i < columns.length; i++ ) {
addColumn( aliases, knownDuplicateAliases, columns[i] );
}
}
private static void addColumn(Set<String> aliases, Set<String> knownDuplicateAliases, String column) {
if ( column != null && !aliases.add( column ) ) {
knownDuplicateAliases.add( column );
}
}
private static boolean hasJoinFetches(List<Fetch> fetches) {
for ( int i = 0; i < fetches.size(); i++ ) {
final Fetch fetch = fetches.get( i );
if ( fetch instanceof BasicFetch<?> || fetch.getTiming() == FetchTiming.DELAYED ) {
// That's fine
}
else if ( fetch instanceof EmbeddableResultGraphNode ) {
// Check all these fetches as well
if ( hasJoinFetches( ( (EmbeddableResultGraphNode) fetch ).getFetches() ) ) {
return true;
}
}
else {
return true;
}
}
return false;
}
private DomainResult<?> makeImplicitDomainResult(
int valuesArrayPosition,
Consumer<SqlSelection> sqlSelectionConsumer,

View File

@ -92,7 +92,7 @@ public class CompleteResultBuilderCollectionStandard implements CompleteResultBu
false,
navigablePath,
tableAlias,
() -> predicate -> {},
null,
creationStateImpl,
sessionFactory
);

View File

@ -84,7 +84,7 @@ public class CompleteResultBuilderEntityJpa implements CompleteResultBuilderEnti
true,
navigablePath,
null,
() -> predicate -> {},
null,
impl.getSqlAstCreationState(),
impl.getSqlAstCreationState().getCreationContext()
)

View File

@ -120,7 +120,7 @@ public class CompleteResultBuilderEntityStandard implements CompleteResultBuilde
true,
navigablePath,
null,
() -> predicate -> {},
null,
impl,
impl.getCreationContext()
)

View File

@ -12,6 +12,7 @@ import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import org.hibernate.LockMode;
import org.hibernate.engine.FetchTiming;
import org.hibernate.metamodel.mapping.AttributeMapping;
import org.hibernate.metamodel.mapping.ForeignKeyDescriptor;
import org.hibernate.metamodel.mapping.JdbcMapping;
@ -127,43 +128,55 @@ public class DynamicFetchBuilderLegacy implements DynamicFetchBuilder, NativeQue
tableGroup = ownerTableGroup;
}
final ForeignKeyDescriptor keyDescriptor;
if ( attributeMapping instanceof PluralAttributeMapping ) {
final PluralAttributeMapping pluralAttributeMapping = (PluralAttributeMapping) attributeMapping;
keyDescriptor = pluralAttributeMapping.getKeyDescriptor();
if ( columnNames != null ) {
final ForeignKeyDescriptor keyDescriptor;
if ( attributeMapping instanceof PluralAttributeMapping ) {
final PluralAttributeMapping pluralAttributeMapping = (PluralAttributeMapping) attributeMapping;
keyDescriptor = pluralAttributeMapping.getKeyDescriptor();
}
else {
// Not sure if this fetch builder can also be used with other attribute mappings
assert attributeMapping instanceof ToOneAttributeMapping;
final ToOneAttributeMapping toOneAttributeMapping = (ToOneAttributeMapping) attributeMapping;
keyDescriptor = toOneAttributeMapping.getForeignKeyDescriptor();
}
keyDescriptor.forEachSelectable(
(selectionIndex, selectableMapping) -> {
resolveSqlSelection(
columnNames.get( selectionIndex ),
createColumnReferenceKey(
tableGroup.getTableReference( selectableMapping.getContainingTableExpression() ),
selectableMapping.getSelectionExpression()
),
selectableMapping.getJdbcMapping(),
jdbcResultsMetadata,
domainResultCreationState
);
}
);
// We process the fetch builder such that it contains a resultBuilderEntity before calling this method in ResultSetMappingProcessor
assert resultBuilderEntity != null;
return resultBuilderEntity.buildFetch(
parent,
attributeMapping,
jdbcResultsMetadata,
creationState
);
}
else {
// Not sure if this fetch builder can also be used with other attribute mappings
assert attributeMapping instanceof ToOneAttributeMapping;
final ToOneAttributeMapping toOneAttributeMapping = (ToOneAttributeMapping) attributeMapping;
keyDescriptor = toOneAttributeMapping.getForeignKeyDescriptor();
return parent.generateFetchableFetch(
attributeMapping,
fetchPath,
FetchTiming.IMMEDIATE,
true,
null,
domainResultCreationState
);
}
keyDescriptor.forEachSelectable(
(selectionIndex, selectableMapping) -> {
resolveSqlSelection(
columnNames.get( selectionIndex ),
createColumnReferenceKey(
tableGroup.getTableReference( selectableMapping.getContainingTableExpression() ),
selectableMapping.getSelectionExpression()
),
selectableMapping.getJdbcMapping(),
jdbcResultsMetadata,
domainResultCreationState
);
}
);
// We process the fetch builder such that it contains a resultBuilderEntity before calling this method in ResultSetMappingProcessor
assert resultBuilderEntity != null;
return resultBuilderEntity.buildFetch(
parent,
attributeMapping,
jdbcResultsMetadata,
creationState
);
}
private void resolveSqlSelection(

View File

@ -15,9 +15,8 @@ import org.hibernate.query.NativeQuery;
import org.hibernate.query.NavigablePath;
import org.hibernate.query.results.DomainResultCreationStateImpl;
import org.hibernate.query.results.ResultsHelper;
import org.hibernate.query.results.TableGroupImpl;
import org.hibernate.sql.ast.spi.SqlAliasBaseConstant;
import org.hibernate.sql.ast.tree.from.TableReference;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.results.graph.DomainResultCreationState;
import org.hibernate.sql.results.graph.entity.EntityResult;
import org.hibernate.sql.results.jdbc.spi.JdbcValuesMetadata;
@ -103,18 +102,14 @@ public class DynamicResultBuilderEntityCalculated implements DynamicResultBuilde
DomainResultCreationState domainResultCreationState) {
final DomainResultCreationStateImpl creationStateImpl = ResultsHelper.impl( domainResultCreationState );
final TableReference tableReference = entityMapping.createPrimaryTableReference(
new SqlAliasBaseConstant( tableAlias ),
creationStateImpl.getSqlExpressionResolver(),
creationStateImpl.getCreationContext()
);
final TableGroupImpl tableGroup = new TableGroupImpl(
final TableGroup tableGroup = entityMapping.createRootTableGroup(
true,
navigablePath,
tableAlias,
tableReference,
entityMapping,
tableAlias
null,
new SqlAliasBaseConstant( tableAlias ),
creationStateImpl,
creationStateImpl.getCreationContext()
);
creationStateImpl.getFromClauseAccess().registerTableGroup( navigablePath, tableGroup );

View File

@ -53,11 +53,22 @@ public class ImplicitFetchBuilderBasic implements ImplicitFetchBuilder {
.getFromClauseAccess()
.getTableGroup( parent.getNavigablePath() );
final String column = fetchable.getSelectionExpression();
final String table = fetchable.getContainingTableExpression();
final String column;
// In case of a formula we look for a result set position with the fetchable name
if ( fetchable.isFormula() ) {
column = fetchable.getFetchableName();
}
else {
column = fetchable.getSelectionExpression();
}
final Expression expression = creationStateImpl.resolveSqlExpression(
createColumnReferenceKey( parentTableGroup.getTableReference( fetchPath, table ), column ),
createColumnReferenceKey(
parentTableGroup.getTableReference( fetchPath, table ),
fetchable.getSelectionExpression()
),
processingState -> {
final int jdbcPosition = jdbcResultsMetadata.resolveColumnPosition( column );
final int valuesArrayPosition = jdbcPositionToValuesArrayPosition( jdbcPosition );

View File

@ -61,8 +61,7 @@ public class ImplicitModelPartResultBuilderEntity
true,
navigablePath,
null,
() -> predicate -> {
},
null,
creationStateImpl,
creationStateImpl.getCreationContext()
);

View File

@ -238,7 +238,7 @@ public class ResultSetMappingProcessor implements SQLQueryParser.ParserContext {
final List<String> columnNames;
final String[] columnAliases = loadable.getSubclassPropertyColumnAliases(
fetchBuilder.getFetchableName(),
suffix
alias2Suffix.get( fetchBuilder.getOwnerAlias() )
);
if ( columnAliases.length == 0 ) {
final CollectionPersister collectionPersister = alias2CollectionPersister.get( fetchBuilder.getTableAlias() );

View File

@ -106,8 +106,9 @@ public class MultiTableSqmMutationConverter extends BaseSqmToSqlAstConverter<Sta
true,
navigablePath,
sourceAlias,
() -> predicate -> {
},
// We don't care about the discriminator predicate,
// but we pass non-null to ensure table reference join predicates are generated
() -> predicate -> {},
this,
creationContext.getSessionFactory() );

View File

@ -331,7 +331,7 @@ public class RestrictedDeleteExecutionDelegate implements TableBasedDeleteHandle
final Expression expression = sqlExpressionResolver.resolveSqlExpression(
SqlExpressionResolver.createColumnReferenceKey( targetTableReference, selection.getSelectionExpression() ),
sqlAstProcessingState -> new ColumnReference(
rootTableGroup.getPrimaryTableReference(),
targetTableReference,
selection,
sessionFactory
)

View File

@ -12,6 +12,7 @@ import java.util.function.Supplier;
import org.hibernate.LockMode;
import org.hibernate.metamodel.mapping.ModelPartContainer;
import org.hibernate.query.NavigablePath;
import org.hibernate.sql.ast.spi.SqlAliasBase;
import org.hibernate.sql.ast.spi.SqlAstCreationContext;
import org.hibernate.sql.ast.spi.SqlAstCreationState;
import org.hibernate.sql.ast.tree.predicate.Predicate;
@ -32,4 +33,12 @@ public interface RootTableGroupProducer extends TableGroupProducer, ModelPartCon
String explicitSourceAlias,
Supplier<Consumer<Predicate>> additionalPredicateCollectorAccess,
SqlAstCreationState creationState, SqlAstCreationContext creationContext);
TableGroup createRootTableGroup(
boolean canUseInnerJoins,
NavigablePath navigablePath,
String explicitSourceAlias,
Supplier<Consumer<Predicate>> additionalPredicateCollectorAccess,
SqlAliasBase sqlAliasBase,
SqlAstCreationState creationState, SqlAstCreationContext creationContext);
}

View File

@ -67,7 +67,7 @@ public abstract class AbstractResultSetAccess implements ResultSetAccess {
catch (SQLException e) {
throw getFactory().getJdbcServices().getJdbcEnvironment().getSqlExceptionHelper().convert(
e,
"Unable to find column position by name"
"Unable to find column position by name: " + columnName
);
}
}

View File

@ -6,6 +6,7 @@
*/
package org.hibernate.orm.test.mapping.formula;
import java.util.Collections;
import java.util.List;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
@ -13,6 +14,7 @@ import javax.persistence.Id;
import javax.persistence.Table;
import org.hibernate.annotations.Formula;
import org.hibernate.query.NativeQuery;
import org.hibernate.query.Query;
import org.hibernate.testing.TestForIssue;
@ -20,11 +22,13 @@ import org.hibernate.testing.orm.junit.DomainModel;
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.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.hibernate.testing.hamcrest.CollectionMatchers.hasSize;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
/**
* @author Алексей Макаров
@ -52,20 +56,75 @@ public class FormulaNativeQueryTest {
@Test
void testNativeQuery(SessionFactoryScope scope) {
scope.inTransaction( session -> {
final Query<Foo> query = session.createNativeQuery( "SELECT ft.* FROM foo_table ft", Foo.class );
final List<Foo> list = query.getResultList();
assertThat( list, hasSize( 3 ) );
} );
scope.inTransaction(
session -> {
final Query<Foo> query = session.createNativeQuery( "SELECT ft.* FROM foo_table ft", Foo.class );
try {
query.getResultList();
Assertions.fail(
"the native query result mapping should fail if the formula field is not returned from the query" );
}
catch (Exception ex) {
Assertions.assertTrue( ex.getMessage().contains( "distance" ) );
}
}
);
}
@Test
public void testNativeQueryWithAllFields(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
Query query = session.createNativeQuery(
"SELECT ft.*, abs(locationEnd - locationStart) as distance FROM foo_table ft",
Foo.class
);
List<Foo> list = query.getResultList();
assertThat( list, hasSize( 3 ) );
}
);
}
@Test
public void testNativeQueryWithAliasProperties(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
NativeQuery query = session.createNativeQuery(
"SELECT ft.*, abs(ft.locationEnd - locationStart) as d FROM foo_table ft" );
query.addRoot( "ft", Foo.class )
.addProperty( "id", "id" )
.addProperty( "locationStart", "locationStart" )
.addProperty( "locationEnd", "locationEnd" )
.addProperty( "distance", "d" );
List<Foo> list = query.getResultList();
assertThat( list, hasSize( 3 ) );
}
);
}
@Test
public void testNativeQueryWithAliasSyntax(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
NativeQuery query = session.createNativeQuery(
"SELECT ft.id as {ft.id}, ft.locationStart as {ft.locationStart}, ft.locationEnd as {ft.locationEnd}, abs(ft.locationEnd - locationStart) as {ft.distance} FROM foo_table ft" )
.addEntity( "ft", Foo.class );
query.setProperties( Collections.singletonMap( "distance", "distance" ) );
List<Foo> list = query.getResultList();
assertThat( list, hasSize( 3 ) );
}
);
}
@Test
void testHql(SessionFactoryScope scope) {
scope.inTransaction( session -> {
final Query<Foo> query = session.createQuery( "SELECT ft FROM Foo ft", Foo.class );
final List<Foo> list = query.getResultList();
assertThat( list, hasSize( 3 ) );
} );
scope.inTransaction(
session -> {
final Query<Foo> query = session.createQuery( "SELECT ft FROM Foo ft", Foo.class );
final List<Foo> list = query.getResultList();
assertThat( list, hasSize( 3 ) );
}
);
}
@Entity(name = "Foo")

View File

@ -6,7 +6,6 @@
*/
package org.hibernate.orm.test.sql.autodiscovery;
import javax.persistence.PersistenceException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
@ -19,7 +18,6 @@ import org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl;
import org.hibernate.cfg.Configuration;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.jdbc.Work;
import org.hibernate.loader.NonUniqueDiscoveredSqlAliasException;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import org.junit.Assert;
@ -59,31 +57,25 @@ public class AutoDiscoveryTest extends BaseCoreFunctionalTestCase {
session.close();
session = openSession();
try {
session.beginTransaction();
List results = session.createNativeQuery(
"select u.name, u2.name from t_user u, t_user u2 where u.name='steve'" ).list();
// this should result in a result set like:
// [0] steve, steve
// [1] steve, stliu
// although the rows could be reversed
assertEquals( 2, results.size() );
final Object[] row1 = (Object[]) results.get( 0 );
final Object[] row2 = (Object[]) results.get( 1 );
assertEquals( "steve", row1[0] );
assertEquals( "steve", row2[0] );
if ( "steve".equals( row1[1] ) ) {
assertEquals( "stliu", row2[1] );
}
else {
assertEquals( "stliu", row1[1] );
}
session.getTransaction().commit();
session.beginTransaction();
List results = session.createNativeQuery(
"select u.name, u2.name from t_user u, t_user u2 where u.name='steve'" ).list();
// this should result in a result set like:
// [0] steve, steve
// [1] steve, stliu
// although the rows could be reversed
assertEquals( 2, results.size() );
final Object[] row1 = (Object[]) results.get( 0 );
final Object[] row2 = (Object[]) results.get( 1 );
assertEquals( "steve", row1[0] );
assertEquals( "steve", row2[0] );
if ( "steve".equals( row1[1] ) ) {
assertEquals( "stliu", row2[1] );
}
catch (PersistenceException e) {
//expected
assertTyping( NonUniqueDiscoveredSqlAliasException.class, e.getCause() );
else {
assertEquals( "stliu", row1[1] );
}
session.getTransaction().commit();
session.close();
session = openSession();