HHH-15099 - Improve handling of associations marked with @NotFound
- database snapshot handling
This commit is contained in:
parent
ceb7df0c51
commit
c5ac528a24
|
@ -16,13 +16,11 @@ import org.hibernate.engine.jdbc.spi.JdbcServices;
|
||||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||||
import org.hibernate.internal.util.collections.ArrayHelper;
|
import org.hibernate.internal.util.collections.ArrayHelper;
|
||||||
import org.hibernate.metamodel.mapping.EntityAssociationMapping;
|
|
||||||
import org.hibernate.metamodel.mapping.EntityMappingType;
|
import org.hibernate.metamodel.mapping.EntityMappingType;
|
||||||
import org.hibernate.metamodel.mapping.SingularAttributeMapping;
|
|
||||||
import org.hibernate.query.sqm.ComparisonOperator;
|
|
||||||
import org.hibernate.query.spi.NavigablePath;
|
import org.hibernate.query.spi.NavigablePath;
|
||||||
import org.hibernate.query.spi.QueryOptions;
|
import org.hibernate.query.spi.QueryOptions;
|
||||||
import org.hibernate.query.spi.QueryParameterBindings;
|
import org.hibernate.query.spi.QueryParameterBindings;
|
||||||
|
import org.hibernate.query.sqm.ComparisonOperator;
|
||||||
import org.hibernate.query.sqm.sql.FromClauseIndex;
|
import org.hibernate.query.sqm.sql.FromClauseIndex;
|
||||||
import org.hibernate.sql.ast.Clause;
|
import org.hibernate.sql.ast.Clause;
|
||||||
import org.hibernate.sql.ast.SqlAstTranslatorFactory;
|
import org.hibernate.sql.ast.SqlAstTranslatorFactory;
|
||||||
|
@ -44,7 +42,6 @@ import org.hibernate.sql.exec.spi.ExecutionContext;
|
||||||
import org.hibernate.sql.exec.spi.JdbcParameterBindings;
|
import org.hibernate.sql.exec.spi.JdbcParameterBindings;
|
||||||
import org.hibernate.sql.exec.spi.JdbcSelect;
|
import org.hibernate.sql.exec.spi.JdbcSelect;
|
||||||
import org.hibernate.sql.results.graph.DomainResult;
|
import org.hibernate.sql.results.graph.DomainResult;
|
||||||
import org.hibernate.sql.results.graph.basic.BasicResult;
|
|
||||||
import org.hibernate.sql.results.internal.RowTransformerDatabaseSnapshotImpl;
|
import org.hibernate.sql.results.internal.RowTransformerDatabaseSnapshotImpl;
|
||||||
import org.hibernate.sql.results.spi.ListResultsConsumer;
|
import org.hibernate.sql.results.spi.ListResultsConsumer;
|
||||||
import org.hibernate.type.StandardBasicTypes;
|
import org.hibernate.type.StandardBasicTypes;
|
||||||
|
@ -145,44 +142,19 @@ class DatabaseSnapshotExecutor {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
entityDescriptor.visitStateArrayContributors(
|
|
||||||
contributorMapping -> {
|
entityDescriptor.visitStateArrayContributors( (contributorMapping) -> {
|
||||||
final NavigablePath navigablePath = rootPath.append( contributorMapping.getAttributeName() );
|
final NavigablePath navigablePath = rootPath.append( contributorMapping.getAttributeName() );
|
||||||
if ( contributorMapping instanceof SingularAttributeMapping ) {
|
domainResults.add(
|
||||||
if ( contributorMapping instanceof EntityAssociationMapping ) {
|
contributorMapping.createSnapshotDomainResult(
|
||||||
domainResults.add(
|
navigablePath,
|
||||||
( (EntityAssociationMapping) contributorMapping ).createDelayedDomainResult(
|
rootTableGroup,
|
||||||
navigablePath,
|
null,
|
||||||
rootTableGroup,
|
state
|
||||||
null,
|
)
|
||||||
state
|
);
|
||||||
)
|
} );
|
||||||
);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
domainResults.add(
|
|
||||||
contributorMapping.createDomainResult(
|
|
||||||
navigablePath,
|
|
||||||
rootTableGroup,
|
|
||||||
null,
|
|
||||||
state
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// TODO: Instead use a delayed collection result? Or will we remove this when redesigning this
|
|
||||||
//noinspection unchecked
|
|
||||||
domainResults.add(
|
|
||||||
new BasicResult(
|
|
||||||
0,
|
|
||||||
null,
|
|
||||||
contributorMapping.getJavaType()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
final SelectStatement selectStatement = new SelectStatement( rootQuerySpec, domainResults );
|
final SelectStatement selectStatement = new SelectStatement( rootQuerySpec, domainResults );
|
||||||
|
|
||||||
final JdbcServices jdbcServices = sessionFactory.getJdbcServices();
|
final JdbcServices jdbcServices = sessionFactory.getJdbcServices();
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
package org.hibernate.metamodel.mapping;
|
package org.hibernate.metamodel.mapping;
|
||||||
|
|
||||||
import org.hibernate.property.access.spi.PropertyAccess;
|
import org.hibernate.property.access.spi.PropertyAccess;
|
||||||
|
import org.hibernate.sql.results.graph.DatabaseSnapshotContributor;
|
||||||
import org.hibernate.sql.results.graph.Fetchable;
|
import org.hibernate.sql.results.graph.Fetchable;
|
||||||
import org.hibernate.tuple.ValueGeneration;
|
import org.hibernate.tuple.ValueGeneration;
|
||||||
import org.hibernate.type.descriptor.java.MutabilityPlan;
|
import org.hibernate.type.descriptor.java.MutabilityPlan;
|
||||||
|
@ -17,7 +18,8 @@ import org.hibernate.type.descriptor.java.MutabilityPlanExposer;
|
||||||
*
|
*
|
||||||
* @author Steve Ebersole
|
* @author Steve Ebersole
|
||||||
*/
|
*/
|
||||||
public interface AttributeMapping extends ModelPart, ValueMapping, Fetchable, PropertyBasedMapping, MutabilityPlanExposer {
|
public interface AttributeMapping
|
||||||
|
extends ModelPart, ValueMapping, Fetchable, DatabaseSnapshotContributor, PropertyBasedMapping, MutabilityPlanExposer {
|
||||||
/**
|
/**
|
||||||
* The name of the mapped attribute
|
* The name of the mapped attribute
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -6,11 +6,7 @@
|
||||||
*/
|
*/
|
||||||
package org.hibernate.metamodel.mapping;
|
package org.hibernate.metamodel.mapping;
|
||||||
|
|
||||||
import org.hibernate.query.spi.NavigablePath;
|
|
||||||
import org.hibernate.sql.ast.tree.from.TableGroup;
|
|
||||||
import org.hibernate.sql.ast.tree.from.TableGroupJoinProducer;
|
import org.hibernate.sql.ast.tree.from.TableGroupJoinProducer;
|
||||||
import org.hibernate.sql.results.graph.DomainResult;
|
|
||||||
import org.hibernate.sql.results.graph.DomainResultCreationState;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Commonality between `many-to-one`, `one-to-one` and `any`, as well as entity-valued collection elements and map-keys
|
* Commonality between `many-to-one`, `one-to-one` and `any`, as well as entity-valued collection elements and map-keys
|
||||||
|
@ -35,13 +31,4 @@ public interface EntityAssociationMapping extends ModelPart, Association, TableG
|
||||||
default boolean incrementFetchDepth(){
|
default boolean incrementFetchDepth(){
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a delayed DomainResult for a specific reference to this ModelPart.
|
|
||||||
*/
|
|
||||||
<T> DomainResult<T> createDelayedDomainResult(
|
|
||||||
NavigablePath navigablePath,
|
|
||||||
TableGroup tableGroup,
|
|
||||||
String resultVariable,
|
|
||||||
DomainResultCreationState creationState);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,8 +20,11 @@ import org.hibernate.sql.ast.spi.SqlAstCreationState;
|
||||||
import org.hibernate.sql.ast.tree.from.TableGroup;
|
import org.hibernate.sql.ast.tree.from.TableGroup;
|
||||||
import org.hibernate.sql.ast.tree.from.TableGroupJoinProducer;
|
import org.hibernate.sql.ast.tree.from.TableGroupJoinProducer;
|
||||||
import org.hibernate.sql.ast.tree.predicate.Predicate;
|
import org.hibernate.sql.ast.tree.predicate.Predicate;
|
||||||
|
import org.hibernate.sql.results.graph.DomainResult;
|
||||||
|
import org.hibernate.sql.results.graph.DomainResultCreationState;
|
||||||
import org.hibernate.sql.results.graph.Fetchable;
|
import org.hibernate.sql.results.graph.Fetchable;
|
||||||
import org.hibernate.sql.results.graph.FetchableContainer;
|
import org.hibernate.sql.results.graph.FetchableContainer;
|
||||||
|
import org.hibernate.sql.results.graph.basic.BasicResult;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mapping of a plural (collection-valued) attribute
|
* Mapping of a plural (collection-valued) attribute
|
||||||
|
@ -68,6 +71,16 @@ public interface PluralAttributeMapping
|
||||||
fetchableConsumer.accept( getElementDescriptor() );
|
fetchableConsumer.accept( getElementDescriptor() );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||||
|
@Override
|
||||||
|
default <T> DomainResult<T> createSnapshotDomainResult(
|
||||||
|
NavigablePath navigablePath,
|
||||||
|
TableGroup tableGroup,
|
||||||
|
String resultVariable,
|
||||||
|
DomainResultCreationState creationState) {
|
||||||
|
return new BasicResult( 0, null, getJavaType() );
|
||||||
|
}
|
||||||
|
|
||||||
String getSeparateCollectionTable();
|
String getSeparateCollectionTable();
|
||||||
|
|
||||||
boolean isBidirectionalAttributeName(NavigablePath fetchablePath, ToOneAttributeMapping modelPart);
|
boolean isBidirectionalAttributeName(NavigablePath fetchablePath, ToOneAttributeMapping modelPart);
|
||||||
|
|
|
@ -73,6 +73,8 @@ import org.hibernate.type.EntityType;
|
||||||
import org.hibernate.type.Type;
|
import org.hibernate.type.Type;
|
||||||
import org.hibernate.type.descriptor.java.JavaType;
|
import org.hibernate.type.descriptor.java.JavaType;
|
||||||
|
|
||||||
|
import static java.util.Objects.requireNonNullElse;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Steve Ebersole
|
* @author Steve Ebersole
|
||||||
*/
|
*/
|
||||||
|
@ -380,15 +382,6 @@ public class EntityCollectionPart
|
||||||
: fkTargetModelPart;
|
: fkTargetModelPart;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public <T> DomainResult<T> createDelayedDomainResult(
|
|
||||||
NavigablePath navigablePath,
|
|
||||||
TableGroup tableGroup,
|
|
||||||
String resultVariable,
|
|
||||||
DomainResultCreationState creationState) {
|
|
||||||
throw new NotYetImplementedFor6Exception( getClass() );
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public JavaType<?> getJavaType() {
|
public JavaType<?> getJavaType() {
|
||||||
return getEntityMappingType().getJavaType();
|
return getEntityMappingType().getJavaType();
|
||||||
|
@ -583,13 +576,8 @@ public class EntityCollectionPart
|
||||||
SqlExpressionResolver sqlExpressionResolver,
|
SqlExpressionResolver sqlExpressionResolver,
|
||||||
FromClauseAccess fromClauseAccess,
|
FromClauseAccess fromClauseAccess,
|
||||||
SqlAstCreationContext creationContext) {
|
SqlAstCreationContext creationContext) {
|
||||||
final SqlAstJoinType joinType;
|
final SqlAstJoinType joinType = requireNonNullElse( requestedJoinType, SqlAstJoinType.INNER );
|
||||||
if ( requestedJoinType == null ) {
|
|
||||||
joinType = SqlAstJoinType.INNER;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
joinType = requestedJoinType;
|
|
||||||
}
|
|
||||||
if ( collectionDescriptor.isOneToMany() && nature == Nature.ELEMENT ) {
|
if ( collectionDescriptor.isOneToMany() && nature == Nature.ELEMENT ) {
|
||||||
// If this is a one-to-many, the element part is already available, so we return a TableGroupJoin "hull"
|
// If this is a one-to-many, the element part is already available, so we return a TableGroupJoin "hull"
|
||||||
return new TableGroupJoin(
|
return new TableGroupJoin(
|
||||||
|
@ -645,15 +633,10 @@ public class EntityCollectionPart
|
||||||
SqlExpressionResolver sqlExpressionResolver,
|
SqlExpressionResolver sqlExpressionResolver,
|
||||||
FromClauseAccess fromClauseAccess,
|
FromClauseAccess fromClauseAccess,
|
||||||
SqlAstCreationContext creationContext) {
|
SqlAstCreationContext creationContext) {
|
||||||
final SqlAstJoinType joinType;
|
final SqlAstJoinType joinType = requireNonNullElse( requestedJoinType, SqlAstJoinType.INNER );
|
||||||
if ( requestedJoinType == null ) {
|
|
||||||
joinType = SqlAstJoinType.INNER;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
joinType = requestedJoinType;
|
|
||||||
}
|
|
||||||
final SqlAliasBase sqlAliasBase = aliasBaseGenerator.createSqlAliasBase( getSqlAliasStem() );
|
final SqlAliasBase sqlAliasBase = aliasBaseGenerator.createSqlAliasBase( getSqlAliasStem() );
|
||||||
final boolean canUseInnerJoin = joinType == SqlAstJoinType.INNER || lhs.canUseInnerJoins();
|
final boolean canUseInnerJoin = joinType == SqlAstJoinType.INNER || lhs.canUseInnerJoins();
|
||||||
|
|
||||||
final LazyTableGroup lazyTableGroup = new LazyTableGroup(
|
final LazyTableGroup lazyTableGroup = new LazyTableGroup(
|
||||||
canUseInnerJoin,
|
canUseInnerJoin,
|
||||||
navigablePath,
|
navigablePath,
|
||||||
|
@ -670,7 +653,7 @@ public class EntityCollectionPart
|
||||||
(np, tableExpression) -> {
|
(np, tableExpression) -> {
|
||||||
NavigablePath path = np.getParent();
|
NavigablePath path = np.getParent();
|
||||||
// Fast path
|
// Fast path
|
||||||
if ( path != null && navigablePath.equals( path ) ) {
|
if ( navigablePath.equals( path ) ) {
|
||||||
return targetKeyPropertyNames.contains( np.getUnaliasedLocalName() )
|
return targetKeyPropertyNames.contains( np.getUnaliasedLocalName() )
|
||||||
&& fkDescriptor.getKeyTable().equals( tableExpression );
|
&& fkDescriptor.getKeyTable().equals( tableExpression );
|
||||||
}
|
}
|
||||||
|
@ -681,7 +664,7 @@ public class EntityCollectionPart
|
||||||
sb.insert( 0, path.getUnaliasedLocalName() );
|
sb.insert( 0, path.getUnaliasedLocalName() );
|
||||||
path = path.getParent();
|
path = path.getParent();
|
||||||
}
|
}
|
||||||
return path != null && navigablePath.equals( path )
|
return navigablePath.equals( path )
|
||||||
&& targetKeyPropertyNames.contains( sb.toString() )
|
&& targetKeyPropertyNames.contains( sb.toString() )
|
||||||
&& fkDescriptor.getKeyTable().equals( tableExpression );
|
&& fkDescriptor.getKeyTable().equals( tableExpression );
|
||||||
},
|
},
|
||||||
|
|
|
@ -13,6 +13,7 @@ import java.util.Iterator;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
import org.hibernate.LockMode;
|
import org.hibernate.LockMode;
|
||||||
import org.hibernate.annotations.NotFoundAction;
|
import org.hibernate.annotations.NotFoundAction;
|
||||||
|
@ -90,6 +91,7 @@ import org.hibernate.sql.results.graph.entity.internal.EntityFetchJoinedImpl;
|
||||||
import org.hibernate.sql.results.graph.entity.internal.EntityFetchSelectImpl;
|
import org.hibernate.sql.results.graph.entity.internal.EntityFetchSelectImpl;
|
||||||
import org.hibernate.sql.results.graph.entity.internal.EntityResultImpl;
|
import org.hibernate.sql.results.graph.entity.internal.EntityResultImpl;
|
||||||
import org.hibernate.sql.results.graph.entity.internal.EntityResultJoinedSubclassImpl;
|
import org.hibernate.sql.results.graph.entity.internal.EntityResultJoinedSubclassImpl;
|
||||||
|
import org.hibernate.sql.results.graph.entity.internal.NotFoundSnapshotResult;
|
||||||
import org.hibernate.sql.results.internal.domain.CircularBiDirectionalFetchImpl;
|
import org.hibernate.sql.results.internal.domain.CircularBiDirectionalFetchImpl;
|
||||||
import org.hibernate.sql.results.internal.domain.CircularFetchImpl;
|
import org.hibernate.sql.results.internal.domain.CircularFetchImpl;
|
||||||
import org.hibernate.type.ComponentType;
|
import org.hibernate.type.ComponentType;
|
||||||
|
@ -227,12 +229,14 @@ public class ToOneAttributeMapping
|
||||||
&& join.getPropertySpan() == 1
|
&& join.getPropertySpan() == 1
|
||||||
&& join.getTable() == manyToOne.getTable()
|
&& join.getTable() == manyToOne.getTable()
|
||||||
&& equal( join.getKey(), manyToOne ) ) {
|
&& equal( join.getKey(), manyToOne ) ) {
|
||||||
|
//noinspection deprecation
|
||||||
bidirectionalAttributeName = join.getPropertyIterator().next().getName();
|
bidirectionalAttributeName = join.getPropertyIterator().next().getName();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Simple one-to-one mapped by cases
|
// Simple one-to-one mapped by cases
|
||||||
if ( bidirectionalAttributeName == null ) {
|
if ( bidirectionalAttributeName == null ) {
|
||||||
|
//noinspection deprecation
|
||||||
final Iterator<Property> propertyClosureIterator = entityBinding.getPropertyClosureIterator();
|
final Iterator<Property> propertyClosureIterator = entityBinding.getPropertyClosureIterator();
|
||||||
while ( propertyClosureIterator.hasNext() ) {
|
while ( propertyClosureIterator.hasNext() ) {
|
||||||
final Property property = propertyClosureIterator.next();
|
final Property property = propertyClosureIterator.next();
|
||||||
|
@ -247,6 +251,7 @@ public class ToOneAttributeMapping
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
//noinspection deprecation
|
||||||
final Iterator<Property> propertyClosureIterator = entityBinding.getPropertyClosureIterator();
|
final Iterator<Property> propertyClosureIterator = entityBinding.getPropertyClosureIterator();
|
||||||
while ( propertyClosureIterator.hasNext() ) {
|
while ( propertyClosureIterator.hasNext() ) {
|
||||||
final Property property = propertyClosureIterator.next();
|
final Property property = propertyClosureIterator.next();
|
||||||
|
@ -351,7 +356,7 @@ public class ToOneAttributeMapping
|
||||||
else {
|
else {
|
||||||
this.bidirectionalAttributeName = bidirectionalAttributeName;
|
this.bidirectionalAttributeName = bidirectionalAttributeName;
|
||||||
}
|
}
|
||||||
notFoundAction = isNullable() ? NotFoundAction.IGNORE : null;
|
notFoundAction = null;
|
||||||
isKeyTableNullable = isNullable();
|
isKeyTableNullable = isNullable();
|
||||||
isOptional = ! bootValue.isConstrained();
|
isOptional = ! bootValue.isConstrained();
|
||||||
}
|
}
|
||||||
|
@ -520,7 +525,9 @@ public class ToOneAttributeMapping
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean equal(Value lhsValue, Value rhsValue) {
|
private static boolean equal(Value lhsValue, Value rhsValue) {
|
||||||
|
//noinspection deprecation
|
||||||
Iterator<Selectable> lhsColumns = lhsValue.getColumnIterator();
|
Iterator<Selectable> lhsColumns = lhsValue.getColumnIterator();
|
||||||
|
//noinspection deprecation
|
||||||
Iterator<Selectable> rhsColumns = rhsValue.getColumnIterator();
|
Iterator<Selectable> rhsColumns = rhsValue.getColumnIterator();
|
||||||
boolean hasNext;
|
boolean hasNext;
|
||||||
do {
|
do {
|
||||||
|
@ -614,9 +621,13 @@ public class ToOneAttributeMapping
|
||||||
: ForeignKeyDescriptor.Nature.TARGET;
|
: ForeignKeyDescriptor.Nature.TARGET;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We can only use the parent table group if the FK is located there and ignoreNotFound is false
|
// We can only use the parent table group if
|
||||||
// If this is not the case, the FK is not constrained or on a join/secondary table, so we need a join
|
// * the FK is located there
|
||||||
this.canUseParentTableGroup = notFoundAction != NotFoundAction.IGNORE
|
// * the association does not force a join (`@NotFound`, nullable 1-1, ...)
|
||||||
|
// Otherwise we need to join to the associated entity table(s)
|
||||||
|
final boolean forceJoin = hasNotFoundAction()
|
||||||
|
|| ( cardinality == Cardinality.ONE_TO_ONE && isNullable() );
|
||||||
|
this.canUseParentTableGroup = ! forceJoin
|
||||||
&& sideNature == ForeignKeyDescriptor.Nature.KEY
|
&& sideNature == ForeignKeyDescriptor.Nature.KEY
|
||||||
&& declaringTableGroupProducer.containsTableReference( identifyingColumnsTableExpression );
|
&& declaringTableGroupProducer.containsTableReference( identifyingColumnsTableExpression );
|
||||||
}
|
}
|
||||||
|
@ -635,10 +646,6 @@ public class ToOneAttributeMapping
|
||||||
return sideNature;
|
return sideNature;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean canJoinForeignKey(EntityIdentifierMapping identifierMapping) {
|
|
||||||
return sideNature == ForeignKeyDescriptor.Nature.KEY && identifierMapping == getForeignKeyDescriptor().getTargetPart() && !isNullable;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getReferencedPropertyName() {
|
public String getReferencedPropertyName() {
|
||||||
return referencedPropertyName;
|
return referencedPropertyName;
|
||||||
}
|
}
|
||||||
|
@ -1033,93 +1040,50 @@ public class ToOneAttributeMapping
|
||||||
&& parentNavigablePath.equals( fetchParent.getNavigablePath().getRealParent() );
|
&& parentNavigablePath.equals( fetchParent.getNavigablePath().getRealParent() );
|
||||||
|
|
||||||
|
|
||||||
if ( fetchTiming == FetchTiming.IMMEDIATE && selected ) {
|
if ( hasNotFoundAction()
|
||||||
final TableGroup tableGroup;
|
|| ( fetchTiming == FetchTiming.IMMEDIATE && selected ) ) {
|
||||||
if ( fetchParent instanceof EntityResultJoinedSubclassImpl &&
|
final TableGroup tableGroup = determineTableGroup(
|
||||||
( (EntityPersister) fetchParent.getReferencedModePart() ).findDeclaredAttributeMapping( getPartName() ) == null ) {
|
|
||||||
final TableGroupJoin tableGroupJoin = createTableGroupJoin(
|
|
||||||
fetchablePath,
|
|
||||||
parentTableGroup,
|
|
||||||
resultVariable,
|
|
||||||
getJoinType( fetchablePath, parentTableGroup ),
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
creationState.getSqlAstCreationState()
|
|
||||||
);
|
|
||||||
parentTableGroup.addTableGroupJoin( tableGroupJoin );
|
|
||||||
tableGroup = tableGroupJoin.getJoinedGroup();
|
|
||||||
fromClauseAccess.registerTableGroup( fetchablePath, tableGroup );
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
tableGroup = fromClauseAccess.resolveTableGroup(
|
|
||||||
fetchablePath,
|
|
||||||
np -> {
|
|
||||||
final TableGroupJoin tableGroupJoin = createTableGroupJoin(
|
|
||||||
fetchablePath,
|
|
||||||
parentTableGroup,
|
|
||||||
resultVariable,
|
|
||||||
getDefaultSqlAstJoinType( parentTableGroup ),
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
creationState.getSqlAstCreationState()
|
|
||||||
);
|
|
||||||
parentTableGroup.addTableGroupJoin( tableGroupJoin );
|
|
||||||
return tableGroupJoin.getJoinedGroup();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
final boolean added = creationState.registerVisitedAssociationKey( foreignKeyDescriptor.getAssociationKey() );
|
|
||||||
AssociationKey additionalAssociationKey = null;
|
|
||||||
if ( cardinality == Cardinality.LOGICAL_ONE_TO_ONE && bidirectionalAttributeName != null ) {
|
|
||||||
final ModelPart bidirectionalModelPart = entityMappingType.findSubPart( bidirectionalAttributeName );
|
|
||||||
// Add the inverse association key side as well to be able to resolve to a CircularFetch
|
|
||||||
if ( bidirectionalModelPart instanceof ToOneAttributeMapping ) {
|
|
||||||
assert bidirectionalModelPart.getPartMappingType() == declaringTableGroupProducer;
|
|
||||||
final ToOneAttributeMapping bidirectionalAttribute = (ToOneAttributeMapping) bidirectionalModelPart;
|
|
||||||
final AssociationKey secondKey = bidirectionalAttribute.getForeignKeyDescriptor().getAssociationKey();
|
|
||||||
if ( creationState.registerVisitedAssociationKey( secondKey ) ) {
|
|
||||||
additionalAssociationKey = secondKey;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final DomainResult<?> keyResult;
|
|
||||||
if ( notFoundAction != null ) {
|
|
||||||
if ( sideNature == ForeignKeyDescriptor.Nature.KEY ) {
|
|
||||||
keyResult = foreignKeyDescriptor.createKeyDomainResult(
|
|
||||||
fetchablePath,
|
|
||||||
parentTableGroup,
|
|
||||||
creationState
|
|
||||||
);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
keyResult = foreignKeyDescriptor.createTargetDomainResult(
|
|
||||||
fetchablePath,
|
|
||||||
parentTableGroup,
|
|
||||||
creationState
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
keyResult = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
final EntityFetchJoinedImpl entityFetchJoined = new EntityFetchJoinedImpl(
|
|
||||||
fetchParent,
|
|
||||||
this,
|
|
||||||
tableGroup,
|
|
||||||
keyResult,
|
|
||||||
fetchablePath,
|
fetchablePath,
|
||||||
|
fetchParent,
|
||||||
|
parentTableGroup,
|
||||||
|
resultVariable,
|
||||||
|
fromClauseAccess,
|
||||||
|
creationState
|
||||||
|
);
|
||||||
|
|
||||||
|
return withRegisteredAssociationKeys(
|
||||||
|
() -> {
|
||||||
|
final DomainResult<?> keyResult;
|
||||||
|
if ( notFoundAction != null ) {
|
||||||
|
if ( sideNature == ForeignKeyDescriptor.Nature.KEY ) {
|
||||||
|
keyResult = foreignKeyDescriptor.createKeyDomainResult(
|
||||||
|
fetchablePath,
|
||||||
|
parentTableGroup,
|
||||||
|
creationState
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
keyResult = foreignKeyDescriptor.createTargetDomainResult(
|
||||||
|
fetchablePath,
|
||||||
|
parentTableGroup,
|
||||||
|
creationState
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
keyResult = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new EntityFetchJoinedImpl(
|
||||||
|
fetchParent,
|
||||||
|
this,
|
||||||
|
tableGroup,
|
||||||
|
keyResult,
|
||||||
|
fetchablePath,creationState
|
||||||
|
);
|
||||||
|
},
|
||||||
creationState
|
creationState
|
||||||
);
|
);
|
||||||
if ( added ) {
|
|
||||||
creationState.removeVisitedAssociationKey( foreignKeyDescriptor.getAssociationKey() );
|
|
||||||
}
|
|
||||||
if ( additionalAssociationKey != null ) {
|
|
||||||
creationState.removeVisitedAssociationKey( additionalAssociationKey );
|
|
||||||
}
|
|
||||||
return entityFetchJoined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -1184,6 +1148,44 @@ public class ToOneAttributeMapping
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private TableGroup determineTableGroup(NavigablePath fetchablePath, FetchParent fetchParent, TableGroup parentTableGroup, String resultVariable, FromClauseAccess fromClauseAccess, DomainResultCreationState creationState) {
|
||||||
|
final TableGroup tableGroup;
|
||||||
|
if ( fetchParent instanceof EntityResultJoinedSubclassImpl
|
||||||
|
&& ( (EntityPersister) fetchParent.getReferencedModePart() ).findDeclaredAttributeMapping( getPartName() ) == null ) {
|
||||||
|
final TableGroupJoin tableGroupJoin = createTableGroupJoin(
|
||||||
|
fetchablePath,
|
||||||
|
parentTableGroup,
|
||||||
|
resultVariable,
|
||||||
|
getJoinType( fetchablePath, parentTableGroup ),
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
creationState.getSqlAstCreationState()
|
||||||
|
);
|
||||||
|
parentTableGroup.addTableGroupJoin( tableGroupJoin );
|
||||||
|
tableGroup = tableGroupJoin.getJoinedGroup();
|
||||||
|
fromClauseAccess.registerTableGroup( fetchablePath, tableGroup );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
tableGroup = fromClauseAccess.resolveTableGroup(
|
||||||
|
fetchablePath,
|
||||||
|
np -> {
|
||||||
|
final TableGroupJoin tableGroupJoin = createTableGroupJoin(
|
||||||
|
fetchablePath,
|
||||||
|
parentTableGroup,
|
||||||
|
resultVariable,
|
||||||
|
getDefaultSqlAstJoinType( parentTableGroup ),
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
creationState.getSqlAstCreationState()
|
||||||
|
);
|
||||||
|
parentTableGroup.addTableGroupJoin( tableGroupJoin );
|
||||||
|
return tableGroupJoin.getJoinedGroup();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return tableGroup;
|
||||||
|
}
|
||||||
|
|
||||||
private boolean isSelectByUniqueKey(ForeignKeyDescriptor.Nature side) {
|
private boolean isSelectByUniqueKey(ForeignKeyDescriptor.Nature side) {
|
||||||
if ( side == ForeignKeyDescriptor.Nature.KEY ) {
|
if ( side == ForeignKeyDescriptor.Nature.KEY ) {
|
||||||
// case 1.2
|
// case 1.2
|
||||||
|
@ -1203,15 +1205,21 @@ public class ToOneAttributeMapping
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <T> DomainResult<T> createDelayedDomainResult(
|
public <T> DomainResult<T> createSnapshotDomainResult(
|
||||||
NavigablePath navigablePath,
|
NavigablePath navigablePath,
|
||||||
TableGroup tableGroup,
|
TableGroup tableGroup,
|
||||||
String resultVariable,
|
String resultVariable,
|
||||||
DomainResultCreationState creationState) {
|
DomainResultCreationState creationState) {
|
||||||
// We only need a join if the key is on the referring side i.e. this is an inverse to-one
|
// We need a join if either
|
||||||
// and if the FK refers to a non-PK, in which case we must load the whole entity
|
// - the association is mapped with `@NotFound`
|
||||||
if ( sideNature == ForeignKeyDescriptor.Nature.TARGET || referencedPropertyName != null ) {
|
// - the key is on the referring side i.e. this is an inverse to-one
|
||||||
creationState.getSqlAstCreationState().getFromClauseAccess().resolveTableGroup(
|
// and if the FK refers to a non-PK
|
||||||
|
final boolean forceJoin = hasNotFoundAction()
|
||||||
|
|| sideNature == ForeignKeyDescriptor.Nature.TARGET
|
||||||
|
|| referencedPropertyName != null;
|
||||||
|
final TableGroup tableGroupToUse;
|
||||||
|
if ( forceJoin ) {
|
||||||
|
tableGroupToUse = creationState.getSqlAstCreationState().getFromClauseAccess().resolveTableGroup(
|
||||||
navigablePath,
|
navigablePath,
|
||||||
np -> {
|
np -> {
|
||||||
final TableGroupJoin tableGroupJoin = createTableGroupJoin(
|
final TableGroupJoin tableGroupJoin = createTableGroupJoin(
|
||||||
|
@ -1228,11 +1236,27 @@ public class ToOneAttributeMapping
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
tableGroupToUse = tableGroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( hasNotFoundAction() ) {
|
||||||
|
assert tableGroupToUse != tableGroup;
|
||||||
|
//noinspection unchecked
|
||||||
|
return new NotFoundSnapshotResult(
|
||||||
|
navigablePath,
|
||||||
|
this,
|
||||||
|
tableGroupToUse,
|
||||||
|
tableGroup,
|
||||||
|
creationState
|
||||||
|
);
|
||||||
|
}
|
||||||
if ( referencedPropertyName == null ) {
|
if ( referencedPropertyName == null ) {
|
||||||
|
//noinspection unchecked
|
||||||
return new EntityDelayedResultImpl(
|
return new EntityDelayedResultImpl(
|
||||||
navigablePath.append( EntityIdentifierMapping.ROLE_LOCAL_NAME ),
|
navigablePath.append( EntityIdentifierMapping.ROLE_LOCAL_NAME ),
|
||||||
this,
|
this,
|
||||||
tableGroup,
|
tableGroupToUse,
|
||||||
creationState
|
creationState
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1241,7 +1265,8 @@ public class ToOneAttributeMapping
|
||||||
final EntityResultImpl entityResult = new EntityResultImpl(
|
final EntityResultImpl entityResult = new EntityResultImpl(
|
||||||
navigablePath,
|
navigablePath,
|
||||||
this,
|
this,
|
||||||
tableGroup, null,
|
tableGroupToUse,
|
||||||
|
null,
|
||||||
creationState
|
creationState
|
||||||
);
|
);
|
||||||
entityResult.afterInitialize( entityResult, creationState );
|
entityResult.afterInitialize( entityResult, creationState );
|
||||||
|
@ -1250,6 +1275,37 @@ public class ToOneAttributeMapping
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private EntityFetch withRegisteredAssociationKeys(
|
||||||
|
Supplier<EntityFetch> fetchCreator,
|
||||||
|
DomainResultCreationState creationState) {
|
||||||
|
final boolean added = creationState.registerVisitedAssociationKey( foreignKeyDescriptor.getAssociationKey() );
|
||||||
|
AssociationKey additionalAssociationKey = null;
|
||||||
|
if ( cardinality == Cardinality.LOGICAL_ONE_TO_ONE && bidirectionalAttributeName != null ) {
|
||||||
|
final ModelPart bidirectionalModelPart = entityMappingType.findSubPart( bidirectionalAttributeName );
|
||||||
|
// Add the inverse association key side as well to be able to resolve to a CircularFetch
|
||||||
|
if ( bidirectionalModelPart instanceof ToOneAttributeMapping ) {
|
||||||
|
assert bidirectionalModelPart.getPartMappingType() == declaringTableGroupProducer;
|
||||||
|
final ToOneAttributeMapping bidirectionalAttribute = (ToOneAttributeMapping) bidirectionalModelPart;
|
||||||
|
final AssociationKey secondKey = bidirectionalAttribute.getForeignKeyDescriptor().getAssociationKey();
|
||||||
|
if ( creationState.registerVisitedAssociationKey( secondKey ) ) {
|
||||||
|
additionalAssociationKey = secondKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return fetchCreator.get();
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
if ( added ) {
|
||||||
|
creationState.removeVisitedAssociationKey( foreignKeyDescriptor.getAssociationKey() );
|
||||||
|
}
|
||||||
|
if ( additionalAssociationKey != null ) {
|
||||||
|
creationState.removeVisitedAssociationKey( additionalAssociationKey );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SqlAstJoinType getDefaultSqlAstJoinType(TableGroup parentTableGroup) {
|
public SqlAstJoinType getDefaultSqlAstJoinType(TableGroup parentTableGroup) {
|
||||||
if ( isKeyTableNullable || isNullable ) {
|
if ( isKeyTableNullable || isNullable ) {
|
||||||
|
@ -1316,13 +1372,18 @@ public class ToOneAttributeMapping
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final SqlAstJoinType joinType;
|
final SqlAstJoinType joinType;
|
||||||
if ( requestedJoinType == null ) {
|
if ( requestedJoinType != null ) {
|
||||||
joinType = SqlAstJoinType.INNER;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
joinType = requestedJoinType;
|
joinType = requestedJoinType;
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
joinType = SqlAstJoinType.INNER;
|
||||||
|
// joinType = hasNotFoundAction()
|
||||||
|
// ? SqlAstJoinType.LEFT
|
||||||
|
// : SqlAstJoinType.INNER;
|
||||||
|
}
|
||||||
|
|
||||||
// If a parent is a collection part, there is no custom predicate and the join is INNER or LEFT
|
// If a parent is a collection part, there is no custom predicate and the join is INNER or LEFT
|
||||||
// we check if this attribute is the map key property to reuse the existing index table group
|
// we check if this attribute is the map key property to reuse the existing index table group
|
||||||
if ( CollectionPart.Nature.ELEMENT.getName().equals( parentTableGroup.getNavigablePath().getUnaliasedLocalName() )
|
if ( CollectionPart.Nature.ELEMENT.getName().equals( parentTableGroup.getNavigablePath().getUnaliasedLocalName() )
|
||||||
|
@ -1359,7 +1420,7 @@ public class ToOneAttributeMapping
|
||||||
}
|
}
|
||||||
NavigablePath path = np.getParent();
|
NavigablePath path = np.getParent();
|
||||||
// Fast path
|
// Fast path
|
||||||
if ( path != null && navigablePath.equals( path ) ) {
|
if ( navigablePath.equals( path ) ) {
|
||||||
return targetKeyPropertyNames.contains( np.getUnaliasedLocalName() )
|
return targetKeyPropertyNames.contains( np.getUnaliasedLocalName() )
|
||||||
&& identifyingColumnsTableExpression.equals( tableExpression );
|
&& identifyingColumnsTableExpression.equals( tableExpression );
|
||||||
}
|
}
|
||||||
|
@ -1379,6 +1440,7 @@ public class ToOneAttributeMapping
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final LazyTableGroup lazyTableGroup = createRootTableGroupJoin(
|
final LazyTableGroup lazyTableGroup = createRootTableGroupJoin(
|
||||||
navigablePath,
|
navigablePath,
|
||||||
lhs,
|
lhs,
|
||||||
|
@ -1400,16 +1462,23 @@ public class ToOneAttributeMapping
|
||||||
|
|
||||||
final TableReference lhsTableReference = lhs.resolveTableReference( navigablePath, identifyingColumnsTableExpression );
|
final TableReference lhsTableReference = lhs.resolveTableReference( navigablePath, identifyingColumnsTableExpression );
|
||||||
|
|
||||||
lazyTableGroup.setTableGroupInitializerCallback(
|
lazyTableGroup.setTableGroupInitializerCallback( (tableGroup) -> join.applyPredicate(
|
||||||
tableGroup -> join.applyPredicate(
|
foreignKeyDescriptor.generateJoinPredicate(
|
||||||
foreignKeyDescriptor.generateJoinPredicate(
|
sideNature == ForeignKeyDescriptor.Nature.TARGET ? lhsTableReference : tableGroup.getPrimaryTableReference(),
|
||||||
sideNature == ForeignKeyDescriptor.Nature.TARGET ? lhsTableReference : tableGroup.getPrimaryTableReference(),
|
sideNature == ForeignKeyDescriptor.Nature.TARGET ? tableGroup.getPrimaryTableReference() : lhsTableReference,
|
||||||
sideNature == ForeignKeyDescriptor.Nature.TARGET ? tableGroup.getPrimaryTableReference() : lhsTableReference,
|
sqlExpressionResolver,
|
||||||
sqlExpressionResolver,
|
creationContext
|
||||||
creationContext
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
);
|
) );
|
||||||
|
|
||||||
|
if ( hasNotFoundAction() ) {
|
||||||
|
getAssociatedEntityMappingType().applyWhereRestrictions(
|
||||||
|
join::applyPredicate,
|
||||||
|
lazyTableGroup.getTableGroup(),
|
||||||
|
true,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return join;
|
return join;
|
||||||
}
|
}
|
||||||
|
@ -1427,14 +1496,17 @@ public class ToOneAttributeMapping
|
||||||
FromClauseAccess fromClauseAccess,
|
FromClauseAccess fromClauseAccess,
|
||||||
SqlAstCreationContext creationContext) {
|
SqlAstCreationContext creationContext) {
|
||||||
final SqlAliasBase sqlAliasBase = aliasBaseGenerator.createSqlAliasBase( sqlAliasStem );
|
final SqlAliasBase sqlAliasBase = aliasBaseGenerator.createSqlAliasBase( sqlAliasStem );
|
||||||
final SqlAstJoinType joinType;
|
|
||||||
if ( requestedJoinType == null ) {
|
final boolean canUseInnerJoin;
|
||||||
joinType = SqlAstJoinType.INNER;
|
if ( ! lhs.canUseInnerJoins() ) {
|
||||||
|
canUseInnerJoin = false;
|
||||||
|
}
|
||||||
|
else if ( isNullable || hasNotFoundAction() ) {
|
||||||
|
canUseInnerJoin = false;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
joinType = requestedJoinType;
|
canUseInnerJoin = requestedJoinType == SqlAstJoinType.INNER;
|
||||||
}
|
}
|
||||||
final boolean canUseInnerJoin = joinType == SqlAstJoinType.INNER || lhs.canUseInnerJoins() && !isNullable;
|
|
||||||
|
|
||||||
TableGroup realParentTableGroup = lhs;
|
TableGroup realParentTableGroup = lhs;
|
||||||
while ( realParentTableGroup.getModelPart() instanceof EmbeddableValuedModelPart ) {
|
while ( realParentTableGroup.getModelPart() instanceof EmbeddableValuedModelPart ) {
|
||||||
|
@ -1469,7 +1541,7 @@ public class ToOneAttributeMapping
|
||||||
}
|
}
|
||||||
NavigablePath path = np.getParent();
|
NavigablePath path = np.getParent();
|
||||||
// Fast path
|
// Fast path
|
||||||
if ( path != null && navigablePath.equals( path ) ) {
|
if ( navigablePath.equals( path ) ) {
|
||||||
return targetKeyPropertyNames.contains( np.getUnaliasedLocalName() )
|
return targetKeyPropertyNames.contains( np.getUnaliasedLocalName() )
|
||||||
&& identifyingColumnsTableExpression.equals( tableExpression );
|
&& identifyingColumnsTableExpression.equals( tableExpression );
|
||||||
}
|
}
|
||||||
|
@ -1597,6 +1669,10 @@ public class ToOneAttributeMapping
|
||||||
return notFoundAction == NotFoundAction.IGNORE;
|
return notFoundAction == NotFoundAction.IGNORE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean hasNotFoundAction() {
|
||||||
|
return notFoundAction != null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isUnwrapProxy() {
|
public boolean isUnwrapProxy() {
|
||||||
return unwrapProxy;
|
return unwrapProxy;
|
||||||
|
|
|
@ -16,6 +16,8 @@ import java.util.concurrent.TimeUnit;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
import java.util.stream.StreamSupport;
|
import java.util.stream.StreamSupport;
|
||||||
|
import jakarta.persistence.CacheRetrieveMode;
|
||||||
|
import jakarta.persistence.CacheStoreMode;
|
||||||
|
|
||||||
import org.hibernate.CacheMode;
|
import org.hibernate.CacheMode;
|
||||||
import org.hibernate.FlushMode;
|
import org.hibernate.FlushMode;
|
||||||
|
@ -28,10 +30,10 @@ import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||||
import org.hibernate.graph.spi.AppliedGraph;
|
import org.hibernate.graph.spi.AppliedGraph;
|
||||||
import org.hibernate.internal.util.collections.ArrayHelper;
|
import org.hibernate.internal.util.collections.ArrayHelper;
|
||||||
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.internal.ScrollableResultsIterator;
|
import org.hibernate.query.internal.ScrollableResultsIterator;
|
||||||
|
import org.hibernate.query.spi.Limit;
|
||||||
import org.hibernate.query.spi.QueryOptions;
|
import org.hibernate.query.spi.QueryOptions;
|
||||||
import org.hibernate.query.spi.QueryParameterBindings;
|
import org.hibernate.query.spi.QueryParameterBindings;
|
||||||
import org.hibernate.query.spi.ScrollableResultsImplementor;
|
import org.hibernate.query.spi.ScrollableResultsImplementor;
|
||||||
|
@ -65,9 +67,6 @@ import org.hibernate.stat.spi.StatisticsImplementor;
|
||||||
import org.hibernate.type.BasicType;
|
import org.hibernate.type.BasicType;
|
||||||
import org.hibernate.type.descriptor.java.JavaType;
|
import org.hibernate.type.descriptor.java.JavaType;
|
||||||
|
|
||||||
import jakarta.persistence.CacheRetrieveMode;
|
|
||||||
import jakarta.persistence.CacheStoreMode;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Steve Ebersole
|
* @author Steve Ebersole
|
||||||
*/
|
*/
|
||||||
|
@ -478,21 +477,15 @@ public class JdbcSelectExecutorStandardImpl implements JdbcSelectExecutor {
|
||||||
SqlExecLogger.INSTANCE.debugf( "Reading Query result cache data per CacheMode#isGetEnabled [%s]", cacheMode.name() );
|
SqlExecLogger.INSTANCE.debugf( "Reading Query result cache data per CacheMode#isGetEnabled [%s]", cacheMode.name() );
|
||||||
final Set<String> querySpaces = jdbcSelect.getAffectedTableNames();
|
final Set<String> querySpaces = jdbcSelect.getAffectedTableNames();
|
||||||
if ( querySpaces == null || querySpaces.size() == 0 ) {
|
if ( querySpaces == null || querySpaces.size() == 0 ) {
|
||||||
SqlExecLogger.INSTANCE.tracev( "Unexpected querySpaces is {0}", ( querySpaces == null ? querySpaces : "empty" ) );
|
SqlExecLogger.INSTANCE.tracef( "Unexpected querySpaces is empty" );
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
SqlExecLogger.INSTANCE.tracev( "querySpaces is {0}", querySpaces );
|
SqlExecLogger.INSTANCE.tracef( "querySpaces is `%s`", querySpaces );
|
||||||
}
|
}
|
||||||
|
|
||||||
final QueryResultsCache queryCache = factory.getCache()
|
final QueryResultsCache queryCache = factory.getCache()
|
||||||
.getQueryResultsCache( executionContext.getQueryOptions().getResultCacheRegionName() );
|
.getQueryResultsCache( executionContext.getQueryOptions().getResultCacheRegionName() );
|
||||||
|
|
||||||
// todo (6.0) : not sure that it is at all important that we account for QueryResults
|
|
||||||
// these cached values are "lower level" than that, representing the
|
|
||||||
// "raw" JDBC values.
|
|
||||||
//
|
|
||||||
// todo (6.0) : relatedly ^^, pretty sure that SqlSelections are also irrelevant
|
|
||||||
|
|
||||||
queryResultsCacheKey = QueryKey.from(
|
queryResultsCacheKey = QueryKey.from(
|
||||||
jdbcSelect.getSql(),
|
jdbcSelect.getSql(),
|
||||||
executionContext.getQueryOptions().getLimit(),
|
executionContext.getQueryOptions().getLimit(),
|
||||||
|
@ -558,6 +551,7 @@ public class JdbcSelectExecutorStandardImpl implements JdbcSelectExecutor {
|
||||||
jdbcValuesMapping = mappingProducer.resolve( capturingMetadata, factory );
|
jdbcValuesMapping = mappingProducer.resolve( capturingMetadata, factory );
|
||||||
metadataForCache = capturingMetadata.resolveMetadataForCache();
|
metadataForCache = capturingMetadata.resolveMetadataForCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
return new JdbcValuesResultSetImpl(
|
return new JdbcValuesResultSetImpl(
|
||||||
resultSetAccess,
|
resultSetAccess,
|
||||||
queryResultsCacheKey,
|
queryResultsCacheKey,
|
||||||
|
|
|
@ -22,8 +22,6 @@ import org.hibernate.sql.results.spi.RowTransformer;
|
||||||
*/
|
*/
|
||||||
@Incubating
|
@Incubating
|
||||||
public interface JdbcSelectExecutor {
|
public interface JdbcSelectExecutor {
|
||||||
// todo (6.0) : Ideally we'd have a singular place (JdbcServices? ServiceRegistry?) to obtain these executors
|
|
||||||
|
|
||||||
<R> List<R> list(
|
<R> List<R> list(
|
||||||
JdbcSelect jdbcSelect,
|
JdbcSelect jdbcSelect,
|
||||||
JdbcParameterBindings jdbcParameterBindings,
|
JdbcParameterBindings jdbcParameterBindings,
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
* 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.sql.results.graph;
|
||||||
|
|
||||||
|
import org.hibernate.query.spi.NavigablePath;
|
||||||
|
import org.hibernate.sql.ast.tree.from.TableGroup;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contract for model-parts which contribute to their container's
|
||||||
|
* state array for database snapshots
|
||||||
|
*
|
||||||
|
* @author Steve Ebersole
|
||||||
|
*/
|
||||||
|
public interface DatabaseSnapshotContributor extends Fetchable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a DomainResult to be used when selecting snapshots from the database.
|
||||||
|
* <p/>
|
||||||
|
* By default, simply use {@link #createDomainResult}
|
||||||
|
*/
|
||||||
|
default <T> DomainResult<T> createSnapshotDomainResult(
|
||||||
|
NavigablePath navigablePath,
|
||||||
|
TableGroup tableGroup,
|
||||||
|
String resultVariable,
|
||||||
|
DomainResultCreationState creationState) {
|
||||||
|
return createDomainResult( navigablePath, tableGroup, null, creationState );
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,16 +22,19 @@ import org.hibernate.sql.results.graph.entity.EntityInitializer;
|
||||||
*/
|
*/
|
||||||
public class EntityDelayedFetchImpl extends AbstractNonJoinedEntityFetch {
|
public class EntityDelayedFetchImpl extends AbstractNonJoinedEntityFetch {
|
||||||
|
|
||||||
private final DomainResult keyResult;
|
private final DomainResult<?> keyResult;
|
||||||
private final boolean selectByUniqueKey;
|
private final boolean selectByUniqueKey;
|
||||||
|
|
||||||
public EntityDelayedFetchImpl(
|
public EntityDelayedFetchImpl(
|
||||||
FetchParent fetchParent,
|
FetchParent fetchParent,
|
||||||
ToOneAttributeMapping fetchedAttribute,
|
ToOneAttributeMapping fetchedAttribute,
|
||||||
NavigablePath navigablePath,
|
NavigablePath navigablePath,
|
||||||
DomainResult keyResult,
|
DomainResult<?> keyResult,
|
||||||
boolean selectByUniqueKey) {
|
boolean selectByUniqueKey) {
|
||||||
super( navigablePath, fetchedAttribute, fetchParent );
|
super( navigablePath, fetchedAttribute, fetchParent );
|
||||||
|
|
||||||
|
assert fetchedAttribute.getNotFoundAction() == null;
|
||||||
|
|
||||||
this.keyResult = keyResult;
|
this.keyResult = keyResult;
|
||||||
this.selectByUniqueKey = selectByUniqueKey;
|
this.selectByUniqueKey = selectByUniqueKey;
|
||||||
}
|
}
|
||||||
|
@ -47,7 +50,7 @@ public class EntityDelayedFetchImpl extends AbstractNonJoinedEntityFetch {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DomainResultAssembler createAssembler(
|
public DomainResultAssembler<?> createAssembler(
|
||||||
FetchParentAccess parentAccess,
|
FetchParentAccess parentAccess,
|
||||||
AssemblerCreationState creationState) {
|
AssemblerCreationState creationState) {
|
||||||
final NavigablePath navigablePath = getNavigablePath();
|
final NavigablePath navigablePath = getNavigablePath();
|
||||||
|
|
|
@ -40,7 +40,7 @@ public class EntityDelayedFetchInitializer extends AbstractFetchParentAccess imp
|
||||||
private final NavigablePath navigablePath;
|
private final NavigablePath navigablePath;
|
||||||
private final ToOneAttributeMapping referencedModelPart;
|
private final ToOneAttributeMapping referencedModelPart;
|
||||||
private final boolean selectByUniqueKey;
|
private final boolean selectByUniqueKey;
|
||||||
private final DomainResultAssembler identifierAssembler;
|
private final DomainResultAssembler<?> identifierAssembler;
|
||||||
|
|
||||||
private Object entityInstance;
|
private Object entityInstance;
|
||||||
private Object identifier;
|
private Object identifier;
|
||||||
|
@ -50,7 +50,10 @@ public class EntityDelayedFetchInitializer extends AbstractFetchParentAccess imp
|
||||||
NavigablePath fetchedNavigable,
|
NavigablePath fetchedNavigable,
|
||||||
ToOneAttributeMapping referencedModelPart,
|
ToOneAttributeMapping referencedModelPart,
|
||||||
boolean selectByUniqueKey,
|
boolean selectByUniqueKey,
|
||||||
DomainResultAssembler identifierAssembler) {
|
DomainResultAssembler<?> identifierAssembler) {
|
||||||
|
// associations marked with `@NotFound` are ALWAYS eagerly fetched
|
||||||
|
assert referencedModelPart.getNotFoundAction() == null;
|
||||||
|
|
||||||
this.parentAccess = parentAccess;
|
this.parentAccess = parentAccess;
|
||||||
this.navigablePath = fetchedNavigable;
|
this.navigablePath = fetchedNavigable;
|
||||||
this.referencedModelPart = referencedModelPart;
|
this.referencedModelPart = referencedModelPart;
|
||||||
|
|
|
@ -24,17 +24,20 @@ import org.hibernate.sql.results.graph.entity.EntityInitializer;
|
||||||
* @author Andrea Boriero
|
* @author Andrea Boriero
|
||||||
*/
|
*/
|
||||||
public class EntityFetchSelectImpl extends AbstractNonJoinedEntityFetch {
|
public class EntityFetchSelectImpl extends AbstractNonJoinedEntityFetch {
|
||||||
private final DomainResult keyResult;
|
private final DomainResult<?> keyResult;
|
||||||
private final boolean selectByUniqueKey;
|
private final boolean selectByUniqueKey;
|
||||||
|
|
||||||
public EntityFetchSelectImpl(
|
public EntityFetchSelectImpl(
|
||||||
FetchParent fetchParent,
|
FetchParent fetchParent,
|
||||||
ToOneAttributeMapping fetchedAttribute,
|
ToOneAttributeMapping fetchedAttribute,
|
||||||
NavigablePath navigablePath,
|
NavigablePath navigablePath,
|
||||||
DomainResult keyResult,
|
DomainResult<?> keyResult,
|
||||||
boolean selectByUniqueKey,
|
boolean selectByUniqueKey,
|
||||||
DomainResultCreationState creationState) {
|
@SuppressWarnings("unused") DomainResultCreationState creationState) {
|
||||||
super( navigablePath, fetchedAttribute, fetchParent );
|
super( navigablePath, fetchedAttribute, fetchParent );
|
||||||
|
|
||||||
|
assert fetchedAttribute.getNotFoundAction() == null;
|
||||||
|
|
||||||
this.keyResult = keyResult;
|
this.keyResult = keyResult;
|
||||||
this.selectByUniqueKey = selectByUniqueKey;
|
this.selectByUniqueKey = selectByUniqueKey;
|
||||||
}
|
}
|
||||||
|
@ -50,7 +53,7 @@ public class EntityFetchSelectImpl extends AbstractNonJoinedEntityFetch {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DomainResultAssembler createAssembler(FetchParentAccess parentAccess, AssemblerCreationState creationState) {
|
public DomainResultAssembler<?> createAssembler(FetchParentAccess parentAccess, AssemblerCreationState creationState) {
|
||||||
final EntityInitializer initializer = (EntityInitializer) creationState.resolveInitializer(
|
final EntityInitializer initializer = (EntityInitializer) creationState.resolveInitializer(
|
||||||
getNavigablePath(),
|
getNavigablePath(),
|
||||||
getFetchedMapping(),
|
getFetchedMapping(),
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
/*
|
||||||
|
* 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.sql.results.graph.entity.internal;
|
||||||
|
|
||||||
|
import org.hibernate.FetchNotFoundException;
|
||||||
|
import org.hibernate.annotations.NotFoundAction;
|
||||||
|
import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping;
|
||||||
|
import org.hibernate.query.spi.NavigablePath;
|
||||||
|
import org.hibernate.sql.results.graph.DomainResultAssembler;
|
||||||
|
import org.hibernate.sql.results.jdbc.spi.JdbcValuesSourceProcessingOptions;
|
||||||
|
import org.hibernate.sql.results.jdbc.spi.RowProcessingState;
|
||||||
|
import org.hibernate.type.descriptor.java.JavaType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specialized DomainResultAssembler for {@link org.hibernate.annotations.NotFound} associations
|
||||||
|
*
|
||||||
|
* @author Steve Ebersole
|
||||||
|
*/
|
||||||
|
public class NotFoundSnapshotAssembler implements DomainResultAssembler {
|
||||||
|
private final NavigablePath navigablePath;
|
||||||
|
private final ToOneAttributeMapping toOneMapping;
|
||||||
|
private final DomainResultAssembler<?> keyValueAssembler;
|
||||||
|
private final DomainResultAssembler<?> targetValueAssembler;
|
||||||
|
|
||||||
|
public NotFoundSnapshotAssembler(
|
||||||
|
NavigablePath navigablePath,
|
||||||
|
ToOneAttributeMapping toOneMapping,
|
||||||
|
DomainResultAssembler<?> keyValueAssembler,
|
||||||
|
DomainResultAssembler<?> targetValueAssembler) {
|
||||||
|
assert toOneMapping.hasNotFoundAction();
|
||||||
|
|
||||||
|
this.navigablePath = navigablePath;
|
||||||
|
this.toOneMapping = toOneMapping;
|
||||||
|
this.keyValueAssembler = keyValueAssembler;
|
||||||
|
this.targetValueAssembler = targetValueAssembler;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object assemble(RowProcessingState rowProcessingState, JdbcValuesSourceProcessingOptions options) {
|
||||||
|
final Object keyValue = keyValueAssembler.assemble( rowProcessingState );
|
||||||
|
final Object targetValue = targetValueAssembler.assemble( rowProcessingState );
|
||||||
|
|
||||||
|
// because of `@NotFound` these could be mismatched
|
||||||
|
if ( keyValue != null ) {
|
||||||
|
if ( targetValue != null ) {
|
||||||
|
if ( toOneMapping.getNotFoundAction() == NotFoundAction.IGNORE ) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new FetchNotFoundException(
|
||||||
|
toOneMapping.getAssociatedEntityMappingType().getEntityName(),
|
||||||
|
keyValue
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return targetValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JavaType<?> getAssembledJavaType() {
|
||||||
|
return toOneMapping.getJavaType();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
/*
|
||||||
|
* 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.sql.results.graph.entity.internal;
|
||||||
|
|
||||||
|
import org.hibernate.metamodel.mapping.ForeignKeyDescriptor;
|
||||||
|
import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping;
|
||||||
|
import org.hibernate.query.spi.NavigablePath;
|
||||||
|
import org.hibernate.sql.ast.tree.from.TableGroup;
|
||||||
|
import org.hibernate.sql.results.graph.AssemblerCreationState;
|
||||||
|
import org.hibernate.sql.results.graph.DomainResult;
|
||||||
|
import org.hibernate.sql.results.graph.DomainResultAssembler;
|
||||||
|
import org.hibernate.sql.results.graph.DomainResultCreationState;
|
||||||
|
import org.hibernate.sql.results.graph.FetchParentAccess;
|
||||||
|
import org.hibernate.type.descriptor.java.JavaType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Steve Ebersole
|
||||||
|
*/
|
||||||
|
public class NotFoundSnapshotResult implements DomainResult {
|
||||||
|
private final NavigablePath navigablePath;
|
||||||
|
private final ToOneAttributeMapping toOneMapping;
|
||||||
|
|
||||||
|
private final DomainResult<?> keyResult;
|
||||||
|
private final DomainResult<?> targetResult;
|
||||||
|
|
||||||
|
public NotFoundSnapshotResult(
|
||||||
|
NavigablePath navigablePath,
|
||||||
|
ToOneAttributeMapping toOneMapping,
|
||||||
|
TableGroup keyTableGroup,
|
||||||
|
TableGroup targetTableGroup,
|
||||||
|
DomainResultCreationState creationState) {
|
||||||
|
this.navigablePath = navigablePath;
|
||||||
|
this.toOneMapping = toOneMapping;
|
||||||
|
|
||||||
|
// NOTE: this currently assumes that only the key side can be
|
||||||
|
// defined with `@NotFound`. That feels like a reasonable
|
||||||
|
// assumption, though there is sme question whether to support
|
||||||
|
// this for the inverse side also when a join table is used.
|
||||||
|
//
|
||||||
|
// however, that would mean a 1-1 with a join-table which
|
||||||
|
// is pretty odd mapping
|
||||||
|
final ForeignKeyDescriptor fkDescriptor = toOneMapping.getForeignKeyDescriptor();
|
||||||
|
this.keyResult = fkDescriptor.createKeyDomainResult( navigablePath, keyTableGroup, creationState );
|
||||||
|
this.targetResult = fkDescriptor.createTargetDomainResult( navigablePath, targetTableGroup, creationState );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NavigablePath getNavigablePath() {
|
||||||
|
return navigablePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JavaType<?> getResultJavaType() {
|
||||||
|
return toOneMapping.getJavaType();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getResultVariable() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DomainResultAssembler<Object> createResultAssembler(
|
||||||
|
FetchParentAccess parentAccess,
|
||||||
|
AssemblerCreationState creationState) {
|
||||||
|
return new NotFoundSnapshotAssembler(
|
||||||
|
navigablePath,
|
||||||
|
toOneMapping,
|
||||||
|
keyResult.createResultAssembler( parentAccess, creationState ),
|
||||||
|
targetResult.createResultAssembler( parentAccess, creationState )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -6,88 +6,93 @@
|
||||||
*/
|
*/
|
||||||
package org.hibernate.orm.test.annotations.notfound;
|
package org.hibernate.orm.test.annotations.notfound;
|
||||||
|
|
||||||
import org.hibernate.annotations.NotFound;
|
|
||||||
import org.hibernate.annotations.NotFoundAction;
|
|
||||||
|
|
||||||
import org.hibernate.testing.TestForIssue;
|
|
||||||
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.BeforeEach;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
import jakarta.persistence.CascadeType;
|
import jakarta.persistence.CascadeType;
|
||||||
import jakarta.persistence.Column;
|
import jakarta.persistence.Column;
|
||||||
import jakarta.persistence.Entity;
|
import jakarta.persistence.Entity;
|
||||||
import jakarta.persistence.ForeignKey;
|
|
||||||
import jakarta.persistence.Id;
|
import jakarta.persistence.Id;
|
||||||
import jakarta.persistence.JoinColumn;
|
import jakarta.persistence.JoinColumn;
|
||||||
import jakarta.persistence.JoinTable;
|
import jakarta.persistence.JoinTable;
|
||||||
import jakarta.persistence.OneToOne;
|
import jakarta.persistence.OneToOne;
|
||||||
import jakarta.persistence.Table;
|
import jakarta.persistence.Table;
|
||||||
|
|
||||||
|
import org.hibernate.annotations.NotFound;
|
||||||
|
import org.hibernate.annotations.NotFoundAction;
|
||||||
|
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||||
|
import org.hibernate.metamodel.mapping.EntityMappingType;
|
||||||
|
import org.hibernate.metamodel.spi.RuntimeMetamodelsImplementor;
|
||||||
|
|
||||||
|
import org.hibernate.testing.orm.junit.DomainModel;
|
||||||
|
import org.hibernate.testing.orm.junit.FailureExpected;
|
||||||
|
import org.hibernate.testing.orm.junit.JiraKey;
|
||||||
|
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||||
|
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Andrea Boriero
|
* @author Andrea Boriero
|
||||||
*/
|
*/
|
||||||
@TestForIssue(jiraKey = "HHH-11591")
|
@JiraKey( "HHH-11591" )
|
||||||
@DomainModel(
|
@DomainModel(
|
||||||
annotatedClasses = { OneToOneNotFoundTest.Show.class, OneToOneNotFoundTest.ShowDescription.class }
|
annotatedClasses = { OneToOneNotFoundTest.Show.class, OneToOneNotFoundTest.ShowDescription.class }
|
||||||
)
|
)
|
||||||
@SessionFactory(
|
@SessionFactory
|
||||||
exportSchema = false
|
|
||||||
)
|
|
||||||
public class OneToOneNotFoundTest {
|
public class OneToOneNotFoundTest {
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
public void setUp(SessionFactoryScope scope) {
|
public void prepareTestData(SessionFactoryScope scope) {
|
||||||
scope.inTransaction(
|
scope.inTransaction( (session) -> {
|
||||||
session ->
|
// Show#1 will end up with a dangling foreign-key as the
|
||||||
session.doWork( connection -> {
|
// matching row on the Description table is deleted
|
||||||
connection.createStatement().execute(
|
{
|
||||||
"create table SHOW_DESCRIPTION ( ID integer not null, primary key (ID) )" );
|
Show show = new Show( 1, new ShowDescription( 10 ) );
|
||||||
connection.createStatement().execute(
|
session.persist( show );
|
||||||
"create table T_SHOW ( id integer not null, primary key (id) )" );
|
|
||||||
connection.createStatement().execute(
|
|
||||||
"create table TSHOW_SHOWDESCRIPTION ( DESCRIPTION_ID integer, SHOW_ID integer not null, primary key (SHOW_ID) )" );
|
|
||||||
|
|
||||||
} )
|
}
|
||||||
);
|
|
||||||
|
|
||||||
scope.inTransaction( session -> {
|
// Show#2 will end up with a dangling foreign-key as the
|
||||||
Show show = new Show();
|
// matching row on the join-table is deleted
|
||||||
show.setId( 1 );
|
{
|
||||||
ShowDescription showDescription = new ShowDescription();
|
Show show = new Show( 2, new ShowDescription( 20 ) );
|
||||||
showDescription.setId( 2 );
|
session.persist( show );
|
||||||
show.setDescription( showDescription );
|
}
|
||||||
session.save( showDescription );
|
|
||||||
session.save( show );
|
|
||||||
|
|
||||||
|
// Show#3 will end up as an inverse dangling foreign-key from
|
||||||
|
// Description because the matching row is deleted from the
|
||||||
|
// Show table
|
||||||
|
{
|
||||||
|
Show show = new Show( 3, new ShowDescription( 30 ) );
|
||||||
|
session.persist( show );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show#4 will end up as an inverse dangling foreign-key from
|
||||||
|
// Description because the matching row is deleted from the
|
||||||
|
// join-table
|
||||||
|
{
|
||||||
|
Show show = new Show( 4, new ShowDescription( 40 ) );
|
||||||
|
session.persist( show );
|
||||||
|
}
|
||||||
} );
|
} );
|
||||||
|
|
||||||
scope.inTransaction(
|
scope.inTransaction( (session) -> session.doWork( (connection) -> {
|
||||||
session ->
|
connection.createStatement().execute( "delete from descriptions where id = 10" );
|
||||||
session.doWork( connection ->
|
connection.createStatement().execute( "delete from show_descriptions where description_fk = 10" );
|
||||||
connection.createStatement()
|
connection.createStatement().execute( "delete from shows where id = 3" );
|
||||||
.execute( "delete from SHOW_DESCRIPTION where ID = 2" )
|
connection.createStatement().execute( "delete from show_descriptions where show_fk = 4" );
|
||||||
)
|
} ) );
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@AfterEach
|
@AfterEach
|
||||||
public void tearDow(SessionFactoryScope scope) {
|
public void dropTestData(SessionFactoryScope scope) {
|
||||||
scope.inTransaction(
|
scope.inTransaction( (session) -> {
|
||||||
session ->
|
session.createMutationQuery( "delete ShowDescription" ).executeUpdate();
|
||||||
session.doWork( connection -> {
|
session.createMutationQuery( "delete Show" ).executeUpdate();
|
||||||
connection.createStatement().execute( "drop table TSHOW_SHOWDESCRIPTION" );
|
} );
|
||||||
connection.createStatement().execute( "drop table SHOW_DESCRIPTION" );
|
|
||||||
connection.createStatement().execute( "drop table T_SHOW" );
|
|
||||||
|
|
||||||
} )
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -99,20 +104,57 @@ public class OneToOneNotFoundTest {
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void databaseSnapshotTest(SessionFactoryScope scope) throws Exception {
|
||||||
|
final SessionFactoryImplementor sessionFactory = scope.getSessionFactory();
|
||||||
|
final RuntimeMetamodelsImplementor runtimeMetamodels = sessionFactory.getRuntimeMetamodels();
|
||||||
|
|
||||||
|
// Check the Show side
|
||||||
|
scope.inTransaction( (session) -> {
|
||||||
|
final EntityMappingType showMapping = runtimeMetamodels.getEntityMappingType( Show.class );
|
||||||
|
final Object[] databaseSnapshot = showMapping.getEntityPersister().getDatabaseSnapshot( 1, session );
|
||||||
|
|
||||||
|
// `Show#description` is the only state-array-contributor for Show
|
||||||
|
assertThat( databaseSnapshot ).describedAs( "`Show` database-snapshot" ).hasSize( 1 );
|
||||||
|
// the snapshot value for `Show#description` should be null
|
||||||
|
assertThat( databaseSnapshot[0] ).describedAs( "`Show#description` database-snapshot value" ).isNull();
|
||||||
|
} );
|
||||||
|
|
||||||
|
// Check the ShowDescription side
|
||||||
|
scope.inTransaction( (session) -> {
|
||||||
|
final EntityMappingType descriptionMapping = runtimeMetamodels.getEntityMappingType( ShowDescription.class );
|
||||||
|
final Object[] databaseSnapshot = descriptionMapping.getEntityPersister().getDatabaseSnapshot( 2, session );
|
||||||
|
|
||||||
|
assertThat( databaseSnapshot ).describedAs( "`ShowDescription` database snapshot" ).isNull();
|
||||||
|
} );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@Entity(name = "Show")
|
@Entity(name = "Show")
|
||||||
@Table(name = "T_SHOW")
|
@Table(name = "shows")
|
||||||
public static class Show {
|
public static class Show {
|
||||||
|
|
||||||
@Id
|
@Id
|
||||||
private Integer id;
|
private Integer id;
|
||||||
|
|
||||||
@OneToOne
|
@OneToOne( cascade = { CascadeType.PERSIST, CascadeType.REMOVE } )
|
||||||
@NotFound(action = NotFoundAction.IGNORE)
|
@NotFound(action = NotFoundAction.IGNORE)
|
||||||
@JoinTable(name = "TSHOW_SHOWDESCRIPTION",
|
@JoinTable(name = "show_descriptions",
|
||||||
joinColumns = @JoinColumn(name = "SHOW_ID"),
|
joinColumns = @JoinColumn(name = "show_fk"),
|
||||||
inverseJoinColumns = @JoinColumn(name = "DESCRIPTION_ID"), foreignKey = @ForeignKey(name = "FK_DESC"))
|
inverseJoinColumns = @JoinColumn(name = "description_fk")
|
||||||
|
)
|
||||||
private ShowDescription description;
|
private ShowDescription description;
|
||||||
|
|
||||||
|
protected Show() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public Show(Integer id, ShowDescription description) {
|
||||||
|
this.id = id;
|
||||||
|
this.description = description;
|
||||||
|
if ( description != null ) {
|
||||||
|
description.setShow( this );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public Integer getId() {
|
public Integer getId() {
|
||||||
return id;
|
return id;
|
||||||
|
@ -133,17 +175,29 @@ public class OneToOneNotFoundTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Entity(name = "ShowDescription")
|
@Entity(name = "ShowDescription")
|
||||||
@Table(name = "SHOW_DESCRIPTION")
|
@Table(name = "descriptions")
|
||||||
public static class ShowDescription {
|
public static class ShowDescription {
|
||||||
|
|
||||||
@Id
|
@Id
|
||||||
@Column(name = "ID")
|
@Column(name = "id")
|
||||||
private Integer id;
|
private Integer id;
|
||||||
|
|
||||||
@NotFound(action = NotFoundAction.IGNORE)
|
@NotFound(action = NotFoundAction.IGNORE)
|
||||||
@OneToOne(mappedBy = "description", cascade = CascadeType.ALL)
|
@OneToOne(mappedBy = "description", cascade = CascadeType.ALL)
|
||||||
private Show show;
|
private Show show;
|
||||||
|
|
||||||
|
protected ShowDescription() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public ShowDescription(Integer id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ShowDescription(Integer id, Show show) {
|
||||||
|
this.id = id;
|
||||||
|
this.show = show;
|
||||||
|
}
|
||||||
|
|
||||||
public Integer getId() {
|
public Integer getId() {
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,8 +33,8 @@ import jakarta.persistence.Id;
|
||||||
import jakarta.persistence.JoinColumn;
|
import jakarta.persistence.JoinColumn;
|
||||||
import jakarta.persistence.OneToOne;
|
import jakarta.persistence.OneToOne;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.hamcrest.CoreMatchers.is;
|
import static org.hamcrest.CoreMatchers.is;
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
|
@ -115,33 +115,9 @@ public class BatchFetchNotFoundIgnoreDynamicStyleTest {
|
||||||
|
|
||||||
final List<Integer> paramterCounts = statementInspector.parameterCounts;
|
final List<Integer> paramterCounts = statementInspector.parameterCounts;
|
||||||
|
|
||||||
// there should be 4 SQL statements executed
|
// there should be 1 SQL statement with a join executed
|
||||||
assertEquals( 4, paramterCounts.size() );
|
assertThat( paramterCounts ).hasSize( 1 );
|
||||||
|
assertThat( paramterCounts.get( 0 ) ).isEqualTo( 0 );
|
||||||
// query loading Employee entities shouldn't have any parameters
|
|
||||||
assertEquals( 0, paramterCounts.get( 0 ).intValue() );
|
|
||||||
|
|
||||||
// query specifically for Task with ID == 0 will result in 1st batch;
|
|
||||||
// query should have 5 parameters for [0,1,2,3,4];
|
|
||||||
// Task with ID == 1 won't be found; the rest will be found.
|
|
||||||
assertEquals( 5, paramterCounts.get( 1 ).intValue() );
|
|
||||||
|
|
||||||
// query specifically for Task with ID == 1 will result in 2nd batch;
|
|
||||||
// query should have 4 parameters [1,5,6,7];
|
|
||||||
// Task with IDs == [1,7] won't be found; the rest will be found.
|
|
||||||
assertEquals( 4, paramterCounts.get( 2 ).intValue() );
|
|
||||||
|
|
||||||
// no extra queries required to load entities with IDs [2,3,4] because they
|
|
||||||
// were already loaded from 1st batch
|
|
||||||
|
|
||||||
// no extra queries required to load entities with IDs [5,6] because they
|
|
||||||
// were already loaded from 2nd batch
|
|
||||||
|
|
||||||
// query specifically for Task with ID == 7 will result in just querying
|
|
||||||
// Task with ID == 7 (because the batch is empty).
|
|
||||||
// query should have 1 parameter [7];
|
|
||||||
// Task with ID == 7 won't be found.
|
|
||||||
assertEquals( 1, paramterCounts.get( 3 ).intValue() );
|
|
||||||
|
|
||||||
assertEquals( NUMBER_OF_EMPLOYEES, employees.size() );
|
assertEquals( NUMBER_OF_EMPLOYEES, employees.size() );
|
||||||
for ( int i = 0; i < NUMBER_OF_EMPLOYEES; i++ ) {
|
for ( int i = 0; i < NUMBER_OF_EMPLOYEES; i++ ) {
|
||||||
|
@ -182,54 +158,9 @@ public class BatchFetchNotFoundIgnoreDynamicStyleTest {
|
||||||
|
|
||||||
final List<Integer> paramterCounts = statementInspector.parameterCounts;
|
final List<Integer> paramterCounts = statementInspector.parameterCounts;
|
||||||
|
|
||||||
// there should be 8 SQL statements executed
|
// there should be 1 SQL statement with a join executed
|
||||||
assertEquals( 8, paramterCounts.size() );
|
assertThat( paramterCounts ).hasSize( 1 );
|
||||||
|
assertThat( paramterCounts.get( 0 ) ).isEqualTo( 0 );
|
||||||
// query loading Employee entities shouldn't have any parameters
|
|
||||||
assertEquals( 0, paramterCounts.get( 0 ).intValue() );
|
|
||||||
|
|
||||||
// query specifically for Task with ID == 0 will result in 1st batch;
|
|
||||||
// query should have 5 parameters for [0,1,2,3,4];
|
|
||||||
// Task with IDs == [0,1,2,3,4] won't be found
|
|
||||||
assertEquals( 5, paramterCounts.get( 1 ).intValue() );
|
|
||||||
|
|
||||||
// query specifically for Task with ID == 1 will result in 2nd batch;
|
|
||||||
// query should have 4 parameters [1,5,6,7];
|
|
||||||
// Task with IDs == [1,5,6] won't be found; Task with ID == 7 will be found.
|
|
||||||
assertEquals( 4, paramterCounts.get( 2 ).intValue() );
|
|
||||||
|
|
||||||
// query specifically for Task with ID == 2 will result in just querying
|
|
||||||
// Task with ID == 2 (because the batch is empty).
|
|
||||||
// query should have 1 parameter [2];
|
|
||||||
// Task with ID == 2 won't be found.
|
|
||||||
assertEquals( 1, paramterCounts.get( 3 ).intValue() );
|
|
||||||
|
|
||||||
// query specifically for Task with ID == 3 will result in just querying
|
|
||||||
// Task with ID == 3 (because the batch is empty).
|
|
||||||
// query should have 1 parameter [3];
|
|
||||||
// Task with ID == 3 won't be found.
|
|
||||||
assertEquals( 1, paramterCounts.get( 4 ).intValue() );
|
|
||||||
|
|
||||||
// query specifically for Task with ID == 4 will result in just querying
|
|
||||||
// Task with ID == 4 (because the batch is empty).
|
|
||||||
// query should have 1 parameter [4];
|
|
||||||
// Task with ID == 4 won't be found.
|
|
||||||
assertEquals( 1, paramterCounts.get( 5 ).intValue() );
|
|
||||||
|
|
||||||
// query specifically for Task with ID == 5 will result in just querying
|
|
||||||
// Task with ID == 5 (because the batch is empty).
|
|
||||||
// query should have 1 parameter [5];
|
|
||||||
// Task with ID == 5 won't be found.
|
|
||||||
assertEquals( 1, paramterCounts.get( 6 ).intValue() );
|
|
||||||
|
|
||||||
// query specifically for Task with ID == 6 will result in just querying
|
|
||||||
// Task with ID == 6 (because the batch is empty).
|
|
||||||
// query should have 1 parameter [6];
|
|
||||||
// Task with ID == 6 won't be found.
|
|
||||||
assertEquals( 1, paramterCounts.get( 7 ).intValue() );
|
|
||||||
|
|
||||||
// no extra queries required to load entity with ID == 7 because it
|
|
||||||
// was already loaded from 2nd batch
|
|
||||||
|
|
||||||
assertEquals( NUMBER_OF_EMPLOYEES, employees.size() );
|
assertEquals( NUMBER_OF_EMPLOYEES, employees.size() );
|
||||||
|
|
||||||
|
@ -288,11 +219,9 @@ public class BatchFetchNotFoundIgnoreDynamicStyleTest {
|
||||||
.getEntityDescriptor( Task.class );
|
.getEntityDescriptor( Task.class );
|
||||||
final BatchFetchQueue batchFetchQueue =
|
final BatchFetchQueue batchFetchQueue =
|
||||||
sessionImplementor.getPersistenceContextInternal().getBatchFetchQueue();
|
sessionImplementor.getPersistenceContextInternal().getBatchFetchQueue();
|
||||||
assertThat(
|
assertThat( batchFetchQueue.containsEntityKey( new EntityKey( id, persister ) ) )
|
||||||
"Checking BatchFetchQueue for entry for Task#" + id,
|
.describedAs( "Checking BatchFetchQueue for entry for Task#" + id )
|
||||||
batchFetchQueue.containsEntityKey( new EntityKey( id, persister ) ),
|
.isEqualTo( expected );
|
||||||
is( expected )
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Entity(name = "Employee")
|
@Entity(name = "Employee")
|
||||||
|
|
|
@ -85,10 +85,10 @@ public class LazyNotFoundOneToOneTest extends BaseCoreFunctionalTestCase {
|
||||||
this::sessionFactory, session -> {
|
this::sessionFactory, session -> {
|
||||||
User user = session.find( User.class, ID );
|
User user = session.find( User.class, ID );
|
||||||
|
|
||||||
// per UserGuide (and simply correct behavior), `@NotFound` forces EAGER fetching
|
// `@NotFound` forces EAGER join fetching
|
||||||
assertThat( sqlInterceptor.getQueryCount() ).
|
assertThat( sqlInterceptor.getQueryCount() ).
|
||||||
describedAs( "Expecting 2 queries due to `@NotFound`" )
|
describedAs( "Expecting 1 query (w/ join) due to `@NotFound`" )
|
||||||
.isEqualTo( 2 );
|
.isEqualTo( 1 );
|
||||||
assertThat( Hibernate.isPropertyInitialized( user, "lazy" ) )
|
assertThat( Hibernate.isPropertyInitialized( user, "lazy" ) )
|
||||||
.describedAs( "Expecting `User#lazy` to be eagerly fetched due to `@NotFound`" )
|
.describedAs( "Expecting `User#lazy` to be eagerly fetched due to `@NotFound`" )
|
||||||
.isTrue();
|
.isTrue();
|
||||||
|
|
|
@ -26,8 +26,6 @@ import jakarta.persistence.ManyToOne;
|
||||||
|
|
||||||
import org.hibernate.Hibernate;
|
import org.hibernate.Hibernate;
|
||||||
import org.hibernate.annotations.BatchSize;
|
import org.hibernate.annotations.BatchSize;
|
||||||
import org.hibernate.annotations.LazyToOne;
|
|
||||||
import org.hibernate.annotations.LazyToOneOption;
|
|
||||||
import org.hibernate.annotations.NotFound;
|
import org.hibernate.annotations.NotFound;
|
||||||
import org.hibernate.annotations.NotFoundAction;
|
import org.hibernate.annotations.NotFoundAction;
|
||||||
import org.hibernate.boot.SessionFactoryBuilder;
|
import org.hibernate.boot.SessionFactoryBuilder;
|
||||||
|
@ -44,6 +42,7 @@ import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate;
|
import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertNull;
|
import static org.junit.Assert.assertNull;
|
||||||
|
@ -58,7 +57,7 @@ import static org.junit.Assert.assertTrue;
|
||||||
@EnhancementOptions(lazyLoading = true)
|
@EnhancementOptions(lazyLoading = true)
|
||||||
public class LoadANonExistingNotFoundBatchEntityTest extends BaseNonConfigCoreFunctionalTestCase {
|
public class LoadANonExistingNotFoundBatchEntityTest extends BaseNonConfigCoreFunctionalTestCase {
|
||||||
|
|
||||||
private static int NUMBER_OF_ENTITIES = 20;
|
private static final int NUMBER_OF_ENTITIES = 20;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@TestForIssue(jiraKey = "HHH-11147")
|
@TestForIssue(jiraKey = "HHH-11147")
|
||||||
|
@ -66,24 +65,20 @@ public class LoadANonExistingNotFoundBatchEntityTest extends BaseNonConfigCoreFu
|
||||||
final StatisticsImplementor statistics = sessionFactory().getStatistics();
|
final StatisticsImplementor statistics = sessionFactory().getStatistics();
|
||||||
statistics.clear();
|
statistics.clear();
|
||||||
|
|
||||||
doInHibernate(
|
inTransaction( (session) -> {
|
||||||
this::sessionFactory, session -> {
|
List<Employee> employees = new ArrayList<>( NUMBER_OF_ENTITIES );
|
||||||
List<Employee> employees = new ArrayList<>( NUMBER_OF_ENTITIES );
|
for ( int i = 0 ; i < NUMBER_OF_ENTITIES ; i++ ) {
|
||||||
for ( int i = 0 ; i < NUMBER_OF_ENTITIES ; i++ ) {
|
employees.add( session.load( Employee.class, i + 1 ) );
|
||||||
employees.add( session.load( Employee.class, i + 1 ) );
|
}
|
||||||
}
|
for ( int i = 0 ; i < NUMBER_OF_ENTITIES ; i++ ) {
|
||||||
for ( int i = 0 ; i < NUMBER_OF_ENTITIES ; i++ ) {
|
Hibernate.initialize( employees.get( i ) );
|
||||||
Hibernate.initialize( employees.get( i ) );
|
assertNull( employees.get( i ).employer );
|
||||||
assertNull( employees.get( i ).employer );
|
}
|
||||||
}
|
} );
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// A "not found" association cannot be batch fetched because
|
// not-found associations are always join-fetched, so we should
|
||||||
// Employee#employer must be initialized immediately.
|
// get `NUMBER_OF_ENTITIES` queries
|
||||||
// Enhanced proxies (and HibernateProxy objects) should never be created
|
assertEquals( NUMBER_OF_ENTITIES, statistics.getPrepareStatementCount() );
|
||||||
// for a "not found" association.
|
|
||||||
assertEquals( 2 * NUMBER_OF_ENTITIES, statistics.getPrepareStatementCount() );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -92,20 +87,16 @@ public class LoadANonExistingNotFoundBatchEntityTest extends BaseNonConfigCoreFu
|
||||||
final StatisticsImplementor statistics = sessionFactory().getStatistics();
|
final StatisticsImplementor statistics = sessionFactory().getStatistics();
|
||||||
statistics.clear();
|
statistics.clear();
|
||||||
|
|
||||||
doInHibernate(
|
inTransaction( (session) -> {
|
||||||
this::sessionFactory, session -> {
|
for ( int i = 0 ; i < NUMBER_OF_ENTITIES ; i++ ) {
|
||||||
for ( int i = 0 ; i < NUMBER_OF_ENTITIES ; i++ ) {
|
Employee employee = session.get( Employee.class, i + 1 );
|
||||||
Employee employee = session.get( Employee.class, i + 1 );
|
assertNull( employee.employer );
|
||||||
assertNull( employee.employer );
|
}
|
||||||
}
|
} );
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// A "not found" association cannot be batch fetched because
|
// not-found associations are always join-fetched, so we should
|
||||||
// Employee#employer must be initialized immediately.
|
// get `NUMBER_OF_ENTITIES` queries
|
||||||
// Enhanced proxies (and HibernateProxy objects) should never be created
|
assertThat( statistics.getPrepareStatementCount() ).isEqualTo( NUMBER_OF_ENTITIES );
|
||||||
// for a "not found" association.
|
|
||||||
assertEquals( 2 * NUMBER_OF_ENTITIES, statistics.getPrepareStatementCount() );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -114,28 +105,24 @@ public class LoadANonExistingNotFoundBatchEntityTest extends BaseNonConfigCoreFu
|
||||||
final StatisticsImplementor statistics = sessionFactory().getStatistics();
|
final StatisticsImplementor statistics = sessionFactory().getStatistics();
|
||||||
statistics.clear();
|
statistics.clear();
|
||||||
|
|
||||||
doInHibernate(
|
inTransaction( (session) -> {
|
||||||
this::sessionFactory, session -> {
|
for ( int i = 0; i < NUMBER_OF_ENTITIES; i++ ) {
|
||||||
for ( int i = 0; i < NUMBER_OF_ENTITIES; i++ ) {
|
Employee employee = session.get( Employee.class, i + 1 );
|
||||||
Employee employee = session.get( Employee.class, i + 1 );
|
Employer employer = new Employer();
|
||||||
Employer employer = new Employer();
|
employer.id = 2 * employee.id;
|
||||||
employer.id = 2 * employee.id;
|
employer.name = "Employer #" + employer.id;
|
||||||
employer.name = "Employer #" + employer.id;
|
employee.employer = employer;
|
||||||
employee.employer = employer;
|
}
|
||||||
}
|
} );
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
doInHibernate(
|
inTransaction( (session) -> {
|
||||||
this::sessionFactory, session -> {
|
for ( int i = 0; i < NUMBER_OF_ENTITIES; i++ ) {
|
||||||
for ( int i = 0; i < NUMBER_OF_ENTITIES; i++ ) {
|
Employee employee = session.get( Employee.class, i + 1 );
|
||||||
Employee employee = session.get( Employee.class, i + 1 );
|
assertTrue( Hibernate.isInitialized( employee.employer ) );
|
||||||
assertTrue( Hibernate.isInitialized( employee.employer ) );
|
assertEquals( employee.id * 2, employee.employer.id );
|
||||||
assertEquals( employee.id * 2, employee.employer.id );
|
assertEquals( "Employer #" + employee.employer.id, employee.employer.name );
|
||||||
assertEquals( "Employer #" + employee.employer.id, employee.employer.name );
|
}
|
||||||
}
|
} );
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -70,10 +70,9 @@ public class LoadANonExistingNotFoundEntityTest extends BaseNonConfigCoreFunctio
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// The Employee#employer must be initialized immediately because
|
// not-found associations are always join-fetched, so we should
|
||||||
// enhanced proxies (and HibernateProxy objects) should never be created
|
// get 1 query for the Employee with join
|
||||||
// for a "not found" association.
|
assertEquals( 1, statistics.getPrepareStatementCount() );
|
||||||
assertEquals( 2, statistics.getPrepareStatementCount() );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -89,10 +88,9 @@ public class LoadANonExistingNotFoundEntityTest extends BaseNonConfigCoreFunctio
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// The Employee#employer must be initialized immediately because
|
// not-found associations are always join-fetched, so we should
|
||||||
// enhanced proxies (and HibernateProxy objects) should never be created
|
// get 1 query for the Employee with join
|
||||||
// for a "not found" association.
|
assertEquals( 1, statistics.getPrepareStatementCount() );
|
||||||
assertEquals( 2, statistics.getPrepareStatementCount() );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -0,0 +1,193 @@
|
||||||
|
/*
|
||||||
|
* 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.notfound;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.OneToOne;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
|
||||||
|
import org.hibernate.annotations.NotFound;
|
||||||
|
import org.hibernate.annotations.NotFoundAction;
|
||||||
|
|
||||||
|
import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
|
||||||
|
public class IsNullAndNotFoundTest extends BaseNonConfigCoreFunctionalTestCase {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Class<?>[] getAnnotatedClasses() {
|
||||||
|
return new Class[] { Account.class, Person.class };
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
inTransaction(
|
||||||
|
session -> {
|
||||||
|
Account account1 = new Account( 1, null, null );
|
||||||
|
Account account2 = new Account( 2, "Fab", null );
|
||||||
|
|
||||||
|
Person person1 = new Person( 1, "Luigi", account1 );
|
||||||
|
Person person2 = new Person( 2, "Andrea", account2 );
|
||||||
|
Person person3 = new Person( 3, "Max", null );
|
||||||
|
|
||||||
|
session.persist( account1 );
|
||||||
|
session.persist( account2 );
|
||||||
|
session.persist( person1 );
|
||||||
|
session.persist( person2 );
|
||||||
|
session.persist( person3 );
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void tearDown() {
|
||||||
|
inTransaction(
|
||||||
|
session -> {
|
||||||
|
session.createQuery( "delete from Person" ).executeUpdate();
|
||||||
|
session.createQuery( "delete from Account" ).executeUpdate();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIsNullInWhereClause() {
|
||||||
|
inTransaction(
|
||||||
|
session -> {
|
||||||
|
final List<Integer> ids = session.createQuery(
|
||||||
|
"select p.id from Person p where p.account.code is null" ).getResultList();
|
||||||
|
|
||||||
|
assertEquals( 1, ids.size() );
|
||||||
|
assertEquals( 1, (int) ids.get( 0 ) );
|
||||||
|
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIsNullInWhereClause2() {
|
||||||
|
inTransaction(
|
||||||
|
session -> {
|
||||||
|
final List<Integer> ids = session.createQuery(
|
||||||
|
"select distinct p.id from Person p where p.account is null" ).getResultList();
|
||||||
|
|
||||||
|
assertEquals( 1, ids.size() );
|
||||||
|
assertEquals( 3, (int) ids.get( 0 ) );
|
||||||
|
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIsNullInWhereClause3() {
|
||||||
|
inTransaction(
|
||||||
|
session -> {
|
||||||
|
final List<Integer> ids = session.createQuery(
|
||||||
|
"select distinct p.id from Person p where p.account is null" ).getResultList();
|
||||||
|
|
||||||
|
assertEquals( 1, ids.size() );
|
||||||
|
assertEquals( 3, (int) ids.get( 0 ) );
|
||||||
|
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIsNullInWhereClause4() {
|
||||||
|
inTransaction(
|
||||||
|
session -> {
|
||||||
|
final List<Integer> ids = session.createQuery(
|
||||||
|
"select p.id from Person p where p.account.code is null or p.account.id is null" )
|
||||||
|
.getResultList();
|
||||||
|
|
||||||
|
assertEquals( 1, ids.size() );
|
||||||
|
assertEquals( 1, (int) ids.get( 0 ) );
|
||||||
|
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWhereClause() {
|
||||||
|
inTransaction(
|
||||||
|
session -> {
|
||||||
|
final List<Integer> ids = session.createQuery(
|
||||||
|
"select p.id from Person p where p.account.code = :code and p.account.id = :id" )
|
||||||
|
.setParameter( "code", "Fab" )
|
||||||
|
.setParameter( "id", 2 )
|
||||||
|
.getResultList();
|
||||||
|
|
||||||
|
assertEquals( 1, ids.size() );
|
||||||
|
assertEquals( 2, (int) ids.get( 0 ) );
|
||||||
|
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Entity(name = "Person")
|
||||||
|
public static class Person {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
private Integer id;
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@OneToOne
|
||||||
|
@NotFound(action = NotFoundAction.IGNORE)
|
||||||
|
private Account account;
|
||||||
|
|
||||||
|
Person() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public Person(Integer id, String name, Account account) {
|
||||||
|
this.id = id;
|
||||||
|
this.name = name;
|
||||||
|
this.account = account;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Account getAccount() {
|
||||||
|
return account;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity(name = "Account")
|
||||||
|
@Table(name = "ACCOUNT_TABLE")
|
||||||
|
public static class Account {
|
||||||
|
@Id
|
||||||
|
private Integer id;
|
||||||
|
|
||||||
|
private String code;
|
||||||
|
|
||||||
|
private Double amount;
|
||||||
|
|
||||||
|
public Account() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public Account(Integer id, String code, Double amount) {
|
||||||
|
this.id = id;
|
||||||
|
this.code = code;
|
||||||
|
this.amount = amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -50,8 +50,8 @@ import static org.junit.jupiter.api.Assertions.fail;
|
||||||
public class NotFoundExceptionLogicalOneToOneTest {
|
public class NotFoundExceptionLogicalOneToOneTest {
|
||||||
@Test
|
@Test
|
||||||
@JiraKey( "HHH-15060" )
|
@JiraKey( "HHH-15060" )
|
||||||
public void testProxy(SessionFactoryScope scope) {
|
public void testProxyCurrency(SessionFactoryScope scope) {
|
||||||
// test handling of a proxy for the missing Coin
|
// test handling of a proxy for the missing Currency
|
||||||
scope.inTransaction( (session) -> {
|
scope.inTransaction( (session) -> {
|
||||||
final Currency proxy = session.byId( Currency.class ).getReference( 1 );
|
final Currency proxy = session.byId( Currency.class ).getReference( 1 );
|
||||||
try {
|
try {
|
||||||
|
@ -65,6 +65,23 @@ public class NotFoundExceptionLogicalOneToOneTest {
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@JiraKey( "HHH-15060" )
|
||||||
|
public void testProxyCoin(SessionFactoryScope scope) {
|
||||||
|
// test handling of a proxy for the missing Coin
|
||||||
|
scope.inTransaction( (session) -> {
|
||||||
|
final Coin proxy = session.byId( Coin.class ).getReference( 1 );
|
||||||
|
try {
|
||||||
|
Hibernate.initialize( proxy );
|
||||||
|
Assertions.fail( "Expecting ObjectNotFoundException" );
|
||||||
|
}
|
||||||
|
catch (FetchNotFoundException expected) {
|
||||||
|
assertThat( expected.getEntityName() ).endsWith( "Currency" );
|
||||||
|
assertThat( expected.getIdentifier() ).isEqualTo( 1 );
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@JiraKey( "HHH-15060" )
|
@JiraKey( "HHH-15060" )
|
||||||
public void testGet(SessionFactoryScope scope) {
|
public void testGet(SessionFactoryScope scope) {
|
||||||
|
@ -74,22 +91,18 @@ public class NotFoundExceptionLogicalOneToOneTest {
|
||||||
scope.inTransaction( (session) -> {
|
scope.inTransaction( (session) -> {
|
||||||
session.get( Coin.class, 2 );
|
session.get( Coin.class, 2 );
|
||||||
|
|
||||||
// at the moment this is handled as SELECT fetch
|
assertThat( statementInspector.getSqlQueries() ).hasSize( 1 );
|
||||||
assertThat( statementInspector.getSqlQueries() ).hasSize( 2 );
|
|
||||||
|
|
||||||
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " Coin " );
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " Coin " );
|
||||||
assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " Currency " );
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " Currency " );
|
||||||
assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " join " );
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " );
|
||||||
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " inner " );
|
||||||
assertThat( statementInspector.getSqlQueries().get( 1 ) ).contains( " Currency " );
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " cross " );
|
||||||
assertThat( statementInspector.getSqlQueries().get( 1 ) ).doesNotContain( " Coin " );
|
|
||||||
assertThat( statementInspector.getSqlQueries().get( 1 ) ).doesNotContain( " join " );
|
|
||||||
} );
|
} );
|
||||||
|
|
||||||
scope.inTransaction( (session) -> {
|
scope.inTransaction( (session) -> {
|
||||||
try {
|
try {
|
||||||
final Coin coin = session.get( Coin.class, 1 );
|
final Coin coin = session.get( Coin.class, 1 );
|
||||||
fail( "Expecting ObjectNotFoundException, got - coin = " + coin + "; currency = " + coin.currency );
|
fail( "Expecting FetchNotFoundException, got - coin = " + coin + "; currency = " + coin.currency );
|
||||||
}
|
}
|
||||||
catch (FetchNotFoundException expected) {
|
catch (FetchNotFoundException expected) {
|
||||||
assertThat( expected.getEntityName() ).isEqualTo( Currency.class.getName() );
|
assertThat( expected.getEntityName() ).isEqualTo( Currency.class.getName() );
|
||||||
|
@ -98,9 +111,95 @@ public class NotFoundExceptionLogicalOneToOneTest {
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Baseline for {@link #testQueryImplicitPathDereferencePredicate}. Ultimately, we want
|
||||||
|
* SQL generated there to behave exactly the same as this query - specifically forcing the
|
||||||
|
* join
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
@JiraKey( "HHH-15060" )
|
||||||
|
public void testQueryImplicitPathDereferencePredicateBaseline(SessionFactoryScope scope) {
|
||||||
|
final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
|
||||||
|
statementInspector.clear();
|
||||||
|
|
||||||
|
scope.inTransaction( (session) -> {
|
||||||
|
final String hql = "select c from Coin c where c.currency.name = 'Euro'";
|
||||||
|
final List<Coin> coins = session.createQuery( hql, Coin.class ).getResultList();
|
||||||
|
assertThat( coins ).isEmpty();
|
||||||
|
} );
|
||||||
|
|
||||||
|
assertThat( statementInspector.getSqlQueries() ).hasSize( 1 );
|
||||||
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " Coin " );
|
||||||
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " Currency " );
|
||||||
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " );
|
||||||
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " inner " );
|
||||||
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " cross " );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Baseline for {@link #testQueryImplicitPathDereferencePredicate}. Ultimately, we want
|
||||||
|
* SQL generated there to behave exactly the same as this query - specifically forcing the
|
||||||
|
* join
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
@JiraKey( "HHH-15060" )
|
||||||
|
public void testQueryImplicitPathDereferencePredicateBaseline2(SessionFactoryScope scope) {
|
||||||
|
final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
|
||||||
|
statementInspector.clear();
|
||||||
|
|
||||||
|
scope.inTransaction( (session) -> {
|
||||||
|
final String hql = "select c from Coin c where c.currency.id = 2";
|
||||||
|
final List<Coin> coins = session.createQuery( hql, Coin.class ).getResultList();
|
||||||
|
assertThat( coins ).hasSize( 1 );
|
||||||
|
|
||||||
|
assertThat( statementInspector.getSqlQueries() ).hasSize( 1 );
|
||||||
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " Coin " );
|
||||||
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " Currency " );
|
||||||
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " );
|
||||||
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " inner " );
|
||||||
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " cross " );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Baseline for {@link #testQueryImplicitPathDereferencePredicate}. Ultimately, we want
|
||||||
|
* SQL generated there to behave exactly the same as this query
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
@JiraKey( "HHH-15060" )
|
||||||
|
public void testQueryImplicitPathDereferencePredicateBaseline3(SessionFactoryScope scope) {
|
||||||
|
final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
|
||||||
|
statementInspector.clear();
|
||||||
|
|
||||||
|
scope.inTransaction( (session) -> {
|
||||||
|
// NOTE : this query is conceptually the same as the one from
|
||||||
|
// `#testQueryImplicitPathDereferencePredicateBaseline` in that we want
|
||||||
|
// a join and we want to use the fk target column (here, `Currency.id`)
|
||||||
|
// rather than the normal perf-opt strategy of using the fk key column
|
||||||
|
// (here, `Coin.currency_fk`).
|
||||||
|
final String hql = "select c from Coin c join fetch c.currency c2 where c2.name = 'USD'";
|
||||||
|
final List<Coin> coins = session.createQuery( hql, Coin.class ).getResultList();
|
||||||
|
assertThat( coins ).hasSize( 1 );
|
||||||
|
assertThat( statementInspector.getSqlQueries() ).hasSize( 1 );
|
||||||
|
} );
|
||||||
|
|
||||||
|
statementInspector.clear();
|
||||||
|
|
||||||
|
scope.inTransaction( (session) -> {
|
||||||
|
// NOTE : this query is conceptually the same as the one from
|
||||||
|
// `#testQueryImplicitPathDereferencePredicateBaseline` in that we want
|
||||||
|
// a join and we want to use the fk target column (here, `Currency.id`)
|
||||||
|
// rather than the normal perf-opt strategy of using the fk key column
|
||||||
|
// (here, `Coin.currency_fk`).
|
||||||
|
final String hql = "select c from Coin c join fetch c.currency c2 where c2.name = 'Euro'";
|
||||||
|
final List<Coin> coins = session.createQuery( hql, Coin.class ).getResultList();
|
||||||
|
assertThat( coins ).hasSize( 0 );
|
||||||
|
assertThat( statementInspector.getSqlQueries() ).hasSize( 1 );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@JiraKey( "HHH-15060" )
|
@JiraKey( "HHH-15060" )
|
||||||
@FailureExpected( reason = "Join is not used in the SQL" )
|
|
||||||
public void testQueryImplicitPathDereferencePredicate(SessionFactoryScope scope) {
|
public void testQueryImplicitPathDereferencePredicate(SessionFactoryScope scope) {
|
||||||
final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
|
final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
|
||||||
statementInspector.clear();
|
statementInspector.clear();
|
||||||
|
@ -111,8 +210,11 @@ public class NotFoundExceptionLogicalOneToOneTest {
|
||||||
assertThat( coins ).isEmpty();
|
assertThat( coins ).isEmpty();
|
||||||
|
|
||||||
assertThat( statementInspector.getSqlQueries() ).hasSize( 1 );
|
assertThat( statementInspector.getSqlQueries() ).hasSize( 1 );
|
||||||
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " Coin " );
|
||||||
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " Currency " );
|
||||||
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " );
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " );
|
||||||
assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " inner " );
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " inner " );
|
||||||
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " cross " );
|
||||||
} );
|
} );
|
||||||
|
|
||||||
statementInspector.clear();
|
statementInspector.clear();
|
||||||
|
@ -123,6 +225,8 @@ public class NotFoundExceptionLogicalOneToOneTest {
|
||||||
assertThat( coins ).hasSize( 1 );
|
assertThat( coins ).hasSize( 1 );
|
||||||
|
|
||||||
assertThat( statementInspector.getSqlQueries() ).hasSize( 1 );
|
assertThat( statementInspector.getSqlQueries() ).hasSize( 1 );
|
||||||
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " Coin " );
|
||||||
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " Currency " );
|
||||||
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " );
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " );
|
||||||
assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " inner " );
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " inner " );
|
||||||
} );
|
} );
|
||||||
|
@ -152,6 +256,23 @@ public class NotFoundExceptionLogicalOneToOneTest {
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@JiraKey( "HHH-15060" )
|
||||||
|
public void testQueryAssociationSelection(SessionFactoryScope scope) {
|
||||||
|
final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
|
||||||
|
statementInspector.clear();
|
||||||
|
|
||||||
|
scope.inTransaction( (session) -> {
|
||||||
|
final String hql = "select c.currency from Coin c where c.id = 1";
|
||||||
|
final List<Currency> resultList = session.createQuery( hql, Currency.class ).getResultList();
|
||||||
|
assertThat( resultList ).hasSize( 0 );
|
||||||
|
|
||||||
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " );
|
||||||
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " left " );
|
||||||
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " cross " );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
public void prepareTestData(SessionFactoryScope scope) {
|
public void prepareTestData(SessionFactoryScope scope) {
|
||||||
scope.inTransaction( (session) -> {
|
scope.inTransaction( (session) -> {
|
||||||
|
|
|
@ -50,9 +50,8 @@ public class NotFoundExceptionManyToOneTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@JiraKey( "HHH-15060" )
|
@JiraKey( "HHH-15060" )
|
||||||
public void testProxy(SessionFactoryScope scope) {
|
public void testProxyCurrency(SessionFactoryScope scope) {
|
||||||
scope.inTransaction( (session) -> {
|
scope.inTransaction( (session) -> {
|
||||||
// the non-existent Child
|
|
||||||
final Currency proxy = session.byId( Currency.class ).getReference( 1 );
|
final Currency proxy = session.byId( Currency.class ).getReference( 1 );
|
||||||
try {
|
try {
|
||||||
Hibernate.initialize( proxy );
|
Hibernate.initialize( proxy );
|
||||||
|
@ -65,6 +64,22 @@ public class NotFoundExceptionManyToOneTest {
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@JiraKey( "HHH-15060" )
|
||||||
|
public void testProxyCoin(SessionFactoryScope scope) {
|
||||||
|
scope.inTransaction( (session) -> {
|
||||||
|
final Coin proxy = session.byId( Coin.class ).getReference( 1 );
|
||||||
|
try {
|
||||||
|
Hibernate.initialize( proxy );
|
||||||
|
Assertions.fail( "Expecting ObjectNotFoundException" );
|
||||||
|
}
|
||||||
|
catch (FetchNotFoundException expected) {
|
||||||
|
assertThat( expected.getEntityName() ).endsWith( "Currency" );
|
||||||
|
assertThat( expected.getIdentifier() ).isEqualTo( 1 );
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@JiraKey( "HHH-15060" )
|
@JiraKey( "HHH-15060" )
|
||||||
public void testGet(SessionFactoryScope scope) {
|
public void testGet(SessionFactoryScope scope) {
|
||||||
|
@ -78,8 +93,9 @@ public class NotFoundExceptionManyToOneTest {
|
||||||
fail( "Expecting ObjectNotFoundException - " + coin.getCurrency() );
|
fail( "Expecting ObjectNotFoundException - " + coin.getCurrency() );
|
||||||
}
|
}
|
||||||
catch (FetchNotFoundException expected) {
|
catch (FetchNotFoundException expected) {
|
||||||
// technically we could use a subsequent-select rather than a join...
|
|
||||||
assertThat( statementInspector.getSqlQueries() ).hasSize( 1 );
|
assertThat( statementInspector.getSqlQueries() ).hasSize( 1 );
|
||||||
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " Coin " );
|
||||||
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " Currency " );
|
||||||
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " );
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " );
|
||||||
assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " inner " );
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " inner " );
|
||||||
|
|
||||||
|
@ -89,32 +105,109 @@ public class NotFoundExceptionManyToOneTest {
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Baseline for {@link #testQueryImplicitPathDereferencePredicate}. Ultimately, we want
|
||||||
|
* SQL generated there to behave exactly the same as this query - specifically forcing the
|
||||||
|
* join
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
@JiraKey( "HHH-15060" )
|
||||||
|
public void testQueryImplicitPathDereferencePredicateBaseline(SessionFactoryScope scope) {
|
||||||
|
final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
|
||||||
|
statementInspector.clear();
|
||||||
|
|
||||||
|
scope.inTransaction( (session) -> {
|
||||||
|
final String hql = "select c from Coin c where c.currency.name = 'Euro'";
|
||||||
|
final List<Coin> coins = session.createQuery( hql, Coin.class ).getResultList();
|
||||||
|
assertThat( coins ).isEmpty();
|
||||||
|
} );
|
||||||
|
|
||||||
|
assertThat( statementInspector.getSqlQueries() ).hasSize( 1 );
|
||||||
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " Coin " );
|
||||||
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " Currency " );
|
||||||
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " );
|
||||||
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " inner " );
|
||||||
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " cross " );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Baseline for {@link #testQueryImplicitPathDereferencePredicate}. Ultimately, we want
|
||||||
|
* SQL generated there to behave exactly the same as this query - specifically forcing the
|
||||||
|
* join
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
@JiraKey( "HHH-15060" )
|
||||||
|
public void testQueryImplicitPathDereferencePredicateBaseline2(SessionFactoryScope scope) {
|
||||||
|
final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
|
||||||
|
statementInspector.clear();
|
||||||
|
|
||||||
|
scope.inTransaction( (session) -> {
|
||||||
|
final String hql = "select c from Coin c where c.currency.id = 2";
|
||||||
|
final List<Coin> coins = session.createQuery( hql, Coin.class ).getResultList();
|
||||||
|
assertThat( coins ).hasSize( 1 );
|
||||||
|
|
||||||
|
assertThat( statementInspector.getSqlQueries() ).hasSize( 1 );
|
||||||
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " Coin " );
|
||||||
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " Currency " );
|
||||||
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " );
|
||||||
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " inner " );
|
||||||
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " cross " );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Baseline for {@link #testQueryImplicitPathDereferencePredicate}. Ultimately, we want
|
||||||
|
* SQL generated there to behave exactly the same as this query
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
@JiraKey( "HHH-15060" )
|
||||||
|
public void testQueryImplicitPathDereferencePredicateBaseline3(SessionFactoryScope scope) {
|
||||||
|
final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
|
||||||
|
statementInspector.clear();
|
||||||
|
|
||||||
|
scope.inTransaction( (session) -> {
|
||||||
|
// NOTE : this query is conceptually the same as the one from
|
||||||
|
// `#testQueryImplicitPathDereferencePredicateBaseline` in that we want
|
||||||
|
// a join and we want to use the fk target column (here, `Currency.id`)
|
||||||
|
// rather than the normal perf-opt strategy of using the fk key column
|
||||||
|
// (here, `Coin.currency_fk`).
|
||||||
|
final String hql = "select c from Coin c join fetch c.currency c2 where c2.name = 'USD'";
|
||||||
|
final List<Coin> coins = session.createQuery( hql, Coin.class ).getResultList();
|
||||||
|
assertThat( coins ).hasSize( 1 );
|
||||||
|
assertThat( statementInspector.getSqlQueries() ).hasSize( 1 );
|
||||||
|
} );
|
||||||
|
|
||||||
|
statementInspector.clear();
|
||||||
|
|
||||||
|
scope.inTransaction( (session) -> {
|
||||||
|
// NOTE : this query is conceptually the same as the one from
|
||||||
|
// `#testQueryImplicitPathDereferencePredicateBaseline` in that we want
|
||||||
|
// a join and we want to use the fk target column (here, `Currency.id`)
|
||||||
|
// rather than the normal perf-opt strategy of using the fk key column
|
||||||
|
// (here, `Coin.currency_fk`).
|
||||||
|
final String hql = "select c from Coin c join fetch c.currency c2 where c2.name = 'Euro'";
|
||||||
|
final List<Coin> coins = session.createQuery( hql, Coin.class ).getResultList();
|
||||||
|
assertThat( coins ).hasSize( 0 );
|
||||||
|
assertThat( statementInspector.getSqlQueries() ).hasSize( 1 );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@JiraKey( "HHH-15060" )
|
@JiraKey( "HHH-15060" )
|
||||||
@FailureExpected(
|
|
||||||
reason = "Does not do the join. Instead selects the Coin based on `currency_id` and then " +
|
|
||||||
"subsequent-selects the Currency. Ultimately results in a `Coin#1` reference with a " +
|
|
||||||
"null Currency."
|
|
||||||
)
|
|
||||||
public void testQueryImplicitPathDereferencePredicate(SessionFactoryScope scope) {
|
public void testQueryImplicitPathDereferencePredicate(SessionFactoryScope scope) {
|
||||||
final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
|
final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
|
||||||
statementInspector.clear();
|
statementInspector.clear();
|
||||||
|
|
||||||
scope.inTransaction( (session) -> {
|
scope.inTransaction( (session) -> {
|
||||||
try {
|
final String hql = "select c from Coin c where c.currency.id = 1";
|
||||||
final String hql = "select c from Coin c where c.currency.id = 1";
|
final List<Coin> coins = session.createQuery( hql, Coin.class ).getResultList();
|
||||||
session.createQuery( hql, Coin.class ).getResultList();
|
assertThat( coins ).isEmpty();
|
||||||
|
|
||||||
fail( "Expecting ObjectNotFoundException for broken fk" );
|
assertThat( statementInspector.getSqlQueries() ).hasSize( 1 );
|
||||||
}
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " Coin " );
|
||||||
catch (ObjectNotFoundException expected) {
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " Currency " );
|
||||||
assertThat( expected.getEntityName() ).isEqualTo( Currency.class.getName() );
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " );
|
||||||
assertThat( expected.getIdentifier() ).isEqualTo( 1 );
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " inner " );
|
||||||
|
|
||||||
assertThat( statementInspector.getSqlQueries() ).hasSize( 1 );
|
|
||||||
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " );
|
|
||||||
assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " inner " );
|
|
||||||
}
|
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -140,13 +233,20 @@ public class NotFoundExceptionManyToOneTest {
|
||||||
@Test
|
@Test
|
||||||
@JiraKey( "HHH-15060" )
|
@JiraKey( "HHH-15060" )
|
||||||
public void testQueryAssociationSelection(SessionFactoryScope scope) {
|
public void testQueryAssociationSelection(SessionFactoryScope scope) {
|
||||||
|
final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
|
||||||
|
statementInspector.clear();
|
||||||
|
|
||||||
// NOTE: this one is not obvious
|
// NOTE: this one is not obvious
|
||||||
// - we are selecting the association so from that perspective, throwing the ObjectNotFoundException is nice
|
// - we are selecting the association so from that perspective, throwing the ObjectNotFoundException is nice
|
||||||
// - the other way to look at it is that there are simply no matching results, so nothing to return
|
// - the other way to look at it is that there are simply no matching results, so nothing to return
|
||||||
scope.inTransaction( (session) -> {
|
scope.inTransaction( (session) -> {
|
||||||
final String hql = "select c.currency from Coin c where c.id = 1";
|
final String hql = "select c.currency from Coin c where c.id = 1";
|
||||||
final List<Currency> resultList = session.createQuery( hql, Currency.class ).getResultList();
|
final List<Currency> resultList = session.createQuery( hql, Currency.class ).getResultList();
|
||||||
assertThat( resultList ).isEmpty();
|
assertThat( resultList ).hasSize( 0 );
|
||||||
|
|
||||||
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " );
|
||||||
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " left " );
|
||||||
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " cross " );
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -112,39 +112,32 @@ public class NotFoundIgnoreManyToOneTest {
|
||||||
assertThat( coins ).hasSize( 1 );
|
assertThat( coins ).hasSize( 1 );
|
||||||
assertThat( coins.get( 0 ).getCurrency() ).isNull();
|
assertThat( coins.get( 0 ).getCurrency() ).isNull();
|
||||||
|
|
||||||
// at the moment this uses a subsequent-select. on the bright side, it is at least eagerly fetched.
|
assertThat( statementInspector.getSqlQueries() ).hasSize( 1 );
|
||||||
assertThat( statementInspector.getSqlQueries() ).hasSize( 2 );
|
|
||||||
|
|
||||||
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " Coin " );
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " Coin " );
|
||||||
assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " Currency " );
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " Currency " );
|
||||||
assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " join " );
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " );
|
||||||
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " inner " );
|
||||||
assertThat( statementInspector.getSqlQueries().get( 1 ) ).contains( " Currency " );
|
|
||||||
assertThat( statementInspector.getSqlQueries().get( 1 ) ).doesNotContain( " Coin " );
|
|
||||||
assertThat( statementInspector.getSqlQueries().get( 1 ) ).doesNotContain( " join " );
|
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@JiraKey( "HHH-15060" )
|
@JiraKey( "HHH-15060" )
|
||||||
@FailureExpected(
|
// @FailureExpected( reason = "Has zero results because of bad join" )
|
||||||
reason = "Has zero results because of inner-join; & the select w/ inner-join is executed twice for some odd reason"
|
|
||||||
)
|
|
||||||
public void testQueryAssociationSelection(SessionFactoryScope scope) {
|
public void testQueryAssociationSelection(SessionFactoryScope scope) {
|
||||||
final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
|
final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
|
||||||
statementInspector.clear();
|
statementInspector.clear();
|
||||||
|
|
||||||
scope.inTransaction( (session) -> {
|
scope.inTransaction( (session) -> {
|
||||||
final String hql = "select c.id, c.currency from Coin c";
|
final String hql = "select c.id, c.currency from Coin c";
|
||||||
final List<Tuple> tuples = session.createSelectionQuery( hql, Tuple.class ).getResultList();
|
final List<Tuple> tuples = session.createQuery( hql, Tuple.class ).getResultList();
|
||||||
assertThat( tuples ).hasSize( 1 );
|
assertThat( tuples ).hasSize( 0 );
|
||||||
final Tuple tuple = tuples.get( 0 );
|
|
||||||
assertThat( tuple.get( 0 ) ).isEqualTo( 1 );
|
|
||||||
assertThat( tuple.get( 1 ) ).isNull();
|
|
||||||
|
|
||||||
assertThat( statementInspector.getSqlQueries() ).hasSize( 1 );
|
assertThat( statementInspector.getSqlQueries() ).hasSize( 1 );
|
||||||
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " Coin " );
|
||||||
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " Currency " );
|
||||||
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " );
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " );
|
||||||
assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " inner " );
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " left " );
|
||||||
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " cross " );
|
||||||
} );
|
} );
|
||||||
|
|
||||||
statementInspector.clear();
|
statementInspector.clear();
|
||||||
|
@ -152,14 +145,15 @@ public class NotFoundIgnoreManyToOneTest {
|
||||||
// I guess this one is somewhat debatable, but for consistency I think this makes the most sense
|
// I guess this one is somewhat debatable, but for consistency I think this makes the most sense
|
||||||
scope.inTransaction( (session) -> {
|
scope.inTransaction( (session) -> {
|
||||||
final String hql = "select c.currency from Coin c";
|
final String hql = "select c.currency from Coin c";
|
||||||
session.createQuery( hql, Currency.class ).getResultList();
|
|
||||||
final List<Currency> currencies = session.createSelectionQuery( hql, Currency.class ).getResultList();
|
final List<Currency> currencies = session.createSelectionQuery( hql, Currency.class ).getResultList();
|
||||||
assertThat( currencies ).hasSize( 1 );
|
assertThat( currencies ).hasSize( 0 );
|
||||||
assertThat( currencies.get( 0 ) ).isNull();
|
|
||||||
|
|
||||||
assertThat( statementInspector.getSqlQueries() ).hasSize( 1 );
|
assertThat( statementInspector.getSqlQueries() ).hasSize( 1 );
|
||||||
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " Coin " );
|
||||||
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " Currency " );
|
||||||
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " );
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " );
|
||||||
assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " inner " );
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " left " );
|
||||||
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " cross " );
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,6 @@ import org.hibernate.annotations.NotFoundAction;
|
||||||
|
|
||||||
import org.hibernate.testing.jdbc.SQLStatementInspector;
|
import org.hibernate.testing.jdbc.SQLStatementInspector;
|
||||||
import org.hibernate.testing.orm.junit.DomainModel;
|
import org.hibernate.testing.orm.junit.DomainModel;
|
||||||
import org.hibernate.testing.orm.junit.FailureExpected;
|
|
||||||
import org.hibernate.testing.orm.junit.JiraKey;
|
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;
|
||||||
|
@ -75,16 +74,17 @@ public class NotFoundIgnoreOneToOneTest {
|
||||||
final Coin coin = session.get( Coin.class, 1 );
|
final Coin coin = session.get( Coin.class, 1 );
|
||||||
assertThat( coin.getCurrency() ).isNull();
|
assertThat( coin.getCurrency() ).isNull();
|
||||||
|
|
||||||
// technically we could use a subsequent-select rather than a join...
|
|
||||||
assertThat( statementInspector.getSqlQueries() ).hasSize( 1 );
|
assertThat( statementInspector.getSqlQueries() ).hasSize( 1 );
|
||||||
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " Coin " );
|
||||||
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " Currency " );
|
||||||
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " );
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " );
|
||||||
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " left " );
|
||||||
assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " inner " );
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " inner " );
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@JiraKey( "HHH-15060" )
|
@JiraKey( "HHH-15060" )
|
||||||
@FailureExpected( reason = "Bad results due to join" )
|
|
||||||
public void testQueryImplicitPathDereferencePredicate(SessionFactoryScope scope) {
|
public void testQueryImplicitPathDereferencePredicate(SessionFactoryScope scope) {
|
||||||
final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
|
final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
|
||||||
statementInspector.clear();
|
statementInspector.clear();
|
||||||
|
@ -92,11 +92,11 @@ public class NotFoundIgnoreOneToOneTest {
|
||||||
scope.inTransaction( (session) -> {
|
scope.inTransaction( (session) -> {
|
||||||
final String hql = "select c from Coin c where c.currency.id = 1";
|
final String hql = "select c from Coin c where c.currency.id = 1";
|
||||||
final List<Coin> coins = session.createQuery( hql, Coin.class ).getResultList();
|
final List<Coin> coins = session.createQuery( hql, Coin.class ).getResultList();
|
||||||
assertThat( coins ).hasSize( 1 );
|
assertThat( coins ).isEmpty();
|
||||||
assertThat( coins.get( 0 ).getCurrency() ).isNull();
|
|
||||||
|
|
||||||
// technically we could use a subsequent-select rather than a join...
|
|
||||||
assertThat( statementInspector.getSqlQueries() ).hasSize( 1 );
|
assertThat( statementInspector.getSqlQueries() ).hasSize( 1 );
|
||||||
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " Coin " );
|
||||||
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " Currency " );
|
||||||
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " );
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " );
|
||||||
assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " inner " );
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " inner " );
|
||||||
} );
|
} );
|
||||||
|
@ -114,37 +114,42 @@ public class NotFoundIgnoreOneToOneTest {
|
||||||
assertThat( coins ).hasSize( 1 );
|
assertThat( coins ).hasSize( 1 );
|
||||||
assertThat( coins.get( 0 ).getCurrency() ).isNull();
|
assertThat( coins.get( 0 ).getCurrency() ).isNull();
|
||||||
|
|
||||||
// at the moment this uses a subsequent-select. on the bright side, it is at least eagerly fetched.
|
assertThat( statementInspector.getSqlQueries() ).hasSize( 1 );
|
||||||
assertThat( statementInspector.getSqlQueries() ).hasSize( 2 );
|
|
||||||
|
|
||||||
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " Coin " );
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " Coin " );
|
||||||
assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " Currency " );
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " Currency " );
|
||||||
assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " join " );
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " );
|
||||||
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " left " );
|
||||||
assertThat( statementInspector.getSqlQueries().get( 1 ) ).contains( " Currency " );
|
|
||||||
assertThat( statementInspector.getSqlQueries().get( 1 ) ).doesNotContain( " Coin " );
|
|
||||||
assertThat( statementInspector.getSqlQueries().get( 1 ) ).doesNotContain( " join " );
|
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@JiraKey( "HHH-15060" )
|
@JiraKey( "HHH-15060" )
|
||||||
@FailureExpected( reason = "Has zero results because of join; & the select w/ join is executed twice for some yet-unknown reason" )
|
|
||||||
public void testQueryAssociationSelection(SessionFactoryScope scope) {
|
public void testQueryAssociationSelection(SessionFactoryScope scope) {
|
||||||
|
final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
|
||||||
|
statementInspector.clear();
|
||||||
|
|
||||||
scope.inTransaction( (session) -> {
|
scope.inTransaction( (session) -> {
|
||||||
final String hql = "select c.id, c.currency from Coin c";
|
final String hql = "select c.id, c.currency from Coin c";
|
||||||
final List<Tuple> tuples = session.createQuery( hql, Tuple.class ).getResultList();
|
final List<Tuple> tuples = session.createQuery( hql, Tuple.class ).getResultList();
|
||||||
assertThat( tuples ).hasSize( 1 );
|
assertThat( tuples ).hasSize( 0 );
|
||||||
final Tuple tuple = tuples.get( 0 );
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " Coin " );
|
||||||
assertThat( tuple.get( 0 ) ).isEqualTo( 1 );
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " Currency " );
|
||||||
assertThat( tuple.get( 1 ) ).isNull();
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " );
|
||||||
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " left " );
|
||||||
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " cross " );
|
||||||
} );
|
} );
|
||||||
|
|
||||||
|
statementInspector.clear();
|
||||||
|
|
||||||
scope.inTransaction( (session) -> {
|
scope.inTransaction( (session) -> {
|
||||||
final String hql = "select c.currency from Coin c";
|
final String hql = "select c.currency from Coin c";
|
||||||
final List<Currency> currencies = session.createQuery( hql, Currency.class ).getResultList();
|
final List<Currency> currencies = session.createQuery( hql, Currency.class ).getResultList();
|
||||||
assertThat( currencies ).hasSize( 1 );
|
assertThat( currencies ).hasSize( 0 );
|
||||||
assertThat( currencies.get( 0 ) ).isNull();
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " Coin " );
|
||||||
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " Currency " );
|
||||||
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " );
|
||||||
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " left " );
|
||||||
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " cross " );
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue