HHH-16759 When ComponentType is immutable, use instantiator instead of setting property values

This commit is contained in:
Christian Beikov 2023-07-26 16:37:26 +02:00
parent 130e05755a
commit 10baf4398a
14 changed files with 296 additions and 118 deletions

View File

@ -48,6 +48,7 @@ import org.hibernate.boot.model.internal.CreateKeySecondPass;
import org.hibernate.boot.model.internal.FkSecondPass; import org.hibernate.boot.model.internal.FkSecondPass;
import org.hibernate.boot.model.internal.IdGeneratorResolverSecondPass; import org.hibernate.boot.model.internal.IdGeneratorResolverSecondPass;
import org.hibernate.boot.model.internal.JPAIndexHolder; import org.hibernate.boot.model.internal.JPAIndexHolder;
import org.hibernate.boot.model.internal.OptionalDeterminationSecondPass;
import org.hibernate.boot.model.internal.QuerySecondPass; import org.hibernate.boot.model.internal.QuerySecondPass;
import org.hibernate.boot.model.internal.SecondaryTableFromAnnotationSecondPass; import org.hibernate.boot.model.internal.SecondaryTableFromAnnotationSecondPass;
import org.hibernate.boot.model.internal.SecondaryTableSecondPass; import org.hibernate.boot.model.internal.SecondaryTableSecondPass;
@ -1667,6 +1668,7 @@ public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector,
private ArrayList<ImplicitColumnNamingSecondPass> implicitColumnNamingSecondPassList; private ArrayList<ImplicitColumnNamingSecondPass> implicitColumnNamingSecondPassList;
private ArrayList<SecondPass> generalSecondPassList; private ArrayList<SecondPass> generalSecondPassList;
private ArrayList<OptionalDeterminationSecondPass> optionalDeterminationSecondPassList;
@Override @Override
public void addSecondPass(SecondPass secondPass) { public void addSecondPass(SecondPass secondPass) {
@ -1705,6 +1707,9 @@ public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector,
else if ( secondPass instanceof ImplicitColumnNamingSecondPass ) { else if ( secondPass instanceof ImplicitColumnNamingSecondPass ) {
addImplicitColumnNamingSecondPass( (ImplicitColumnNamingSecondPass) secondPass ); addImplicitColumnNamingSecondPass( (ImplicitColumnNamingSecondPass) secondPass );
} }
else if ( secondPass instanceof OptionalDeterminationSecondPass ) {
addOptionalDeterminationSecondPass( (OptionalDeterminationSecondPass) secondPass );
}
else { else {
// add to the general SecondPass list // add to the general SecondPass list
if ( generalSecondPassList == null ) { if ( generalSecondPassList == null ) {
@ -1786,6 +1791,13 @@ public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector,
implicitColumnNamingSecondPassList.add( secondPass ); implicitColumnNamingSecondPassList.add( secondPass );
} }
private void addOptionalDeterminationSecondPass(OptionalDeterminationSecondPass secondPass) {
if ( optionalDeterminationSecondPassList == null ) {
optionalDeterminationSecondPassList = new ArrayList<>();
}
optionalDeterminationSecondPassList.add( secondPass );
}
private boolean inSecondPass = false; private boolean inSecondPass = false;
@ -1812,6 +1824,7 @@ public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector,
processSecondPasses( querySecondPassList ); processSecondPasses( querySecondPassList );
processSecondPasses( generalSecondPassList ); processSecondPasses( generalSecondPassList );
processSecondPasses( optionalDeterminationSecondPassList );
processPropertyReferences(); processPropertyReferences();

View File

@ -119,6 +119,7 @@ public class AnyBinder {
} }
binder.setAccessType( inferredData.getDefaultAccess() ); binder.setAccessType( inferredData.getDefaultAccess() );
binder.setCascade( cascadeStrategy ); binder.setCascade( cascadeStrategy );
binder.setBuildingContext( context );
Property prop = binder.makeProperty(); Property prop = binder.makeProperty();
//composite FK columns are in the same table, so it's OK //composite FK columns are in the same table, so it's OK
propertyHolder.addProperty( prop, columns, inferredData.getDeclaringClass() ); propertyHolder.addProperty( prop, columns, inferredData.getDeclaringClass() );

View File

@ -1276,6 +1276,7 @@ public abstract class CollectionBinder {
binder.setProperty( property ); binder.setProperty( property );
binder.setInsertable( insertable ); binder.setInsertable( insertable );
binder.setUpdatable( updatable ); binder.setUpdatable( updatable );
binder.setBuildingContext( buildingContext );
Property prop = binder.makeProperty(); Property prop = binder.makeProperty();
//we don't care about the join stuffs because the column is on the association table. //we don't care about the join stuffs because the column is on the association table.
if ( !declaringClassSet ) { if ( !declaringClassSet ) {

View File

@ -192,7 +192,7 @@ public class EmbeddableBinder {
entityBinder, entityBinder,
isComponentEmbedded, isComponentEmbedded,
isIdentifierMapper, isIdentifierMapper,
false, context.getMetadataCollector().isInSecondPass(),
customInstantiatorImpl, customInstantiatorImpl,
compositeUserTypeClass, compositeUserTypeClass,
annotatedColumns, annotatedColumns,

View File

@ -112,6 +112,7 @@ public class OneToOneSecondPass implements SecondPass {
binder.setValue( value ); binder.setValue( value );
binder.setCascade( cascadeStrategy ); binder.setCascade( cascadeStrategy );
binder.setAccessType( inferredData.getDefaultAccess() ); binder.setAccessType( inferredData.getDefaultAccess() );
binder.setBuildingContext( buildingContext );
final LazyGroup lazyGroupAnnotation = property.getAnnotation( LazyGroup.class ); final LazyGroup lazyGroupAnnotation = property.getAnnotation( LazyGroup.class );
if ( lazyGroupAnnotation != null ) { if ( lazyGroupAnnotation != null ) {
@ -186,6 +187,7 @@ public class OneToOneSecondPass implements SecondPass {
manyToOne.setFetchMode( oneToOne.getFetchMode() ); manyToOne.setFetchMode( oneToOne.getFetchMode() );
manyToOne.setLazy( oneToOne.isLazy() ); manyToOne.setLazy( oneToOne.isLazy() );
manyToOne.setReferencedEntityName( oneToOne.getReferencedEntityName() ); manyToOne.setReferencedEntityName( oneToOne.getReferencedEntityName() );
manyToOne.setReferencedPropertyName( mappedBy );
manyToOne.setUnwrapProxy( oneToOne.isUnwrapProxy() ); manyToOne.setUnwrapProxy( oneToOne.isUnwrapProxy() );
manyToOne.markAsLogicalOneToOne(); manyToOne.markAsLogicalOneToOne();
property.setValue( manyToOne ); property.setValue( manyToOne );

View File

@ -0,0 +1,13 @@
/*
* 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.boot.model.internal;
import org.hibernate.boot.spi.SecondPass;
public interface OptionalDeterminationSecondPass extends SecondPass {
}

View File

@ -48,13 +48,16 @@ import org.hibernate.boot.model.IdentifierGeneratorDefinition;
import org.hibernate.boot.spi.AccessType; import org.hibernate.boot.spi.AccessType;
import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.boot.spi.MetadataBuildingContext;
import org.hibernate.boot.spi.PropertyData; import org.hibernate.boot.spi.PropertyData;
import org.hibernate.boot.spi.SecondPass;
import org.hibernate.engine.OptimisticLockStyle; import org.hibernate.engine.OptimisticLockStyle;
import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.mapping.Collection; import org.hibernate.mapping.Collection;
import org.hibernate.mapping.Component; import org.hibernate.mapping.Component;
import org.hibernate.mapping.GeneratorCreator; import org.hibernate.mapping.GeneratorCreator;
import org.hibernate.mapping.Join;
import org.hibernate.mapping.KeyValue; import org.hibernate.mapping.KeyValue;
import org.hibernate.mapping.MappedSuperclass; import org.hibernate.mapping.MappedSuperclass;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Property; import org.hibernate.mapping.Property;
import org.hibernate.mapping.RootClass; import org.hibernate.mapping.RootClass;
import org.hibernate.mapping.SimpleValue; import org.hibernate.mapping.SimpleValue;
@ -448,13 +451,32 @@ public class PropertyBinder {
private void handleOptional(Property property) { private void handleOptional(Property property) {
if ( this.property != null ) { if ( this.property != null ) {
property.setOptional( !isId && isOptional( this.property ) && isNullable( property ) ); property.setOptional( !isId && isOptional( this.property ) );
if ( property.isOptional() ) {
final OptionalDeterminationSecondPass secondPass = persistentClasses -> {
// Defer determining whether a property and its columns are nullable,
// as handleOptional might be called when the value is not yet fully initialized
if ( property.getPersistentClass() != null ) {
for ( Join join : property.getPersistentClass().getJoins() ) {
if ( join.getProperties().contains( property ) ) {
// If this property is part of a join it is inherently optional
return;
}
} }
} }
private static boolean isNullable(Property property) { if ( !property.getValue().isNullable() ) {
final Value value = property.getValue(); property.setOptional( false );
return value instanceof org.hibernate.mapping.OneToMany || value.isNullable(); }
};
// Always register this as second pass and never execute it directly,
// even if we are in a second pass already.
// If we are in a second pass, then we are currently processing the generalSecondPassList
// to which the following call will add the second pass to,
// so it will be executed within that second pass, just a bit later
buildingContext.getMetadataCollector().addSecondPass( secondPass );
}
}
} }
private void handleNaturalId(Property property) { private void handleNaturalId(Property property) {

View File

@ -143,4 +143,9 @@ public class ManyToOne extends ToOne {
public boolean isLogicalOneToOne() { public boolean isLogicalOneToOne() {
return isLogicalOneToOne; return isLogicalOneToOne;
} }
@Override
public boolean isNullable() {
return getReferencedPropertyName() != null || super.isNullable();
}
} }

View File

@ -9,11 +9,13 @@ package org.hibernate.metamodel.mapping.internal;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.List;
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 java.util.function.Supplier;
import org.hibernate.AssertionFailure;
import org.hibernate.LockMode; import org.hibernate.LockMode;
import org.hibernate.annotations.NotFoundAction; import org.hibernate.annotations.NotFoundAction;
import org.hibernate.cache.MutableCacheKeyBuilder; import org.hibernate.cache.MutableCacheKeyBuilder;
@ -77,6 +79,7 @@ import org.hibernate.sql.ast.tree.from.TableGroupJoin;
import org.hibernate.sql.ast.tree.from.TableGroupJoinProducer; import org.hibernate.sql.ast.tree.from.TableGroupJoinProducer;
import org.hibernate.sql.ast.tree.from.TableGroupProducer; import org.hibernate.sql.ast.tree.from.TableGroupProducer;
import org.hibernate.sql.ast.tree.from.TableReference; import org.hibernate.sql.ast.tree.from.TableReference;
import org.hibernate.sql.ast.tree.from.TableReferenceJoin;
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.DomainResult;
import org.hibernate.sql.results.graph.DomainResultCreationState; import org.hibernate.sql.results.graph.DomainResultCreationState;
@ -145,6 +148,7 @@ public class ToOneAttributeMapping
private final Set<String> targetKeyPropertyNames; private final Set<String> targetKeyPropertyNames;
private final Cardinality cardinality; private final Cardinality cardinality;
private final boolean hasJoinTable;
/* /*
Capture the other side's name of a possibly bidirectional association to allow resolving circular fetches. Capture the other side's name of a possibly bidirectional association to allow resolving circular fetches.
It may be null if the referenced property is a non-entity. It may be null if the referenced property is a non-entity.
@ -171,6 +175,7 @@ public class ToOneAttributeMapping
referencedPropertyName = original.referencedPropertyName; referencedPropertyName = original.referencedPropertyName;
targetKeyPropertyName = original.targetKeyPropertyName; targetKeyPropertyName = original.targetKeyPropertyName;
cardinality = original.cardinality; cardinality = original.cardinality;
hasJoinTable = original.hasJoinTable;
bidirectionalAttributePath = original.bidirectionalAttributePath; bidirectionalAttributePath = original.bidirectionalAttributePath;
declaringTableGroupProducer = original.declaringTableGroupProducer; declaringTableGroupProducer = original.declaringTableGroupProducer;
isKeyTableNullable = original.isKeyTableNullable; isKeyTableNullable = original.isKeyTableNullable;
@ -242,7 +247,10 @@ public class ToOneAttributeMapping
this.entityMappingType = entityMappingType; this.entityMappingType = entityMappingType;
this.navigableRole = navigableRole; this.navigableRole = navigableRole;
this.declaringTableGroupProducer = resolveDeclaringTableGroupProducer( declaringEntityPersister, navigableRole ); this.declaringTableGroupProducer = resolveDeclaringTableGroupProducer(
declaringEntityPersister,
navigableRole
);
if ( bootValue instanceof ManyToOne ) { if ( bootValue instanceof ManyToOne ) {
final ManyToOne manyToOne = (ManyToOne) bootValue; final ManyToOne manyToOne = (ManyToOne) bootValue;
this.notFoundAction = ( (ManyToOne) bootValue ).getNotFoundAction(); this.notFoundAction = ( (ManyToOne) bootValue ).getNotFoundAction();
@ -260,6 +268,7 @@ public class ToOneAttributeMapping
? name ? name
: bootValue.getPropertyName(); : bootValue.getPropertyName();
if ( cardinality == Cardinality.LOGICAL_ONE_TO_ONE ) { if ( cardinality == Cardinality.LOGICAL_ONE_TO_ONE ) {
boolean hasJoinTable = false;
// Handle join table cases // Handle join table cases
for ( Join join : entityBinding.getJoinClosure() ) { for ( Join join : entityBinding.getJoinClosure() ) {
if ( join.getPersistentClass().getEntityName().equals( entityBinding.getEntityName() ) if ( join.getPersistentClass().getEntityName().equals( entityBinding.getEntityName() )
@ -267,7 +276,10 @@ public class ToOneAttributeMapping
&& join.getTable() == manyToOne.getTable() && join.getTable() == manyToOne.getTable()
&& equal( join.getKey(), manyToOne ) ) { && equal( join.getKey(), manyToOne ) ) {
//noinspection deprecation //noinspection deprecation
bidirectionalAttributeName = SelectablePath.parse( join.getPropertyIterator().next().getName() ); bidirectionalAttributeName = SelectablePath.parse(
join.getPropertyIterator().next().getName()
);
hasJoinTable = true;
break; break;
} }
} }
@ -280,8 +292,10 @@ public class ToOneAttributeMapping
entityBinding.getPropertyClosure() entityBinding.getPropertyClosure()
); );
} }
this.hasJoinTable = hasJoinTable;
} }
else { else {
this.hasJoinTable = false;
bidirectionalAttributeName = findBidirectionalOneToManyAttributeName( bidirectionalAttributeName = findBidirectionalOneToManyAttributeName(
propertyPath, propertyPath,
declaringType, declaringType,
@ -294,7 +308,12 @@ public class ToOneAttributeMapping
else { else {
// Only set the bidirectional attribute name if the referenced property can actually be circular i.e. an entity type // Only set the bidirectional attribute name if the referenced property can actually be circular i.e. an entity type
final Property property = entityBinding.getProperty( referencedPropertyName ); final Property property = entityBinding.getProperty( referencedPropertyName );
this.bidirectionalAttributePath = property != null && property.getValue() instanceof EntityType this.hasJoinTable = cardinality == Cardinality.LOGICAL_ONE_TO_ONE
&& property != null
&& ( property.getValue() instanceof ManyToOne )
&& ( (ManyToOne) property.getValue() ).isLogicalOneToOne();
this.bidirectionalAttributePath = property != null && property.getValue()
.getType() instanceof EntityType
? SelectablePath.parse( referencedPropertyName ) ? SelectablePath.parse( referencedPropertyName )
: null; : null;
} }
@ -302,12 +321,20 @@ public class ToOneAttributeMapping
isKeyTableNullable = true; isKeyTableNullable = true;
} }
else { else {
final String targetTableName = MappingModelCreationHelper.getTableIdentifierExpression( manyToOne.getTable(), declaringEntityPersister.getFactory() ); final String targetTableName = MappingModelCreationHelper.getTableIdentifierExpression(
manyToOne.getTable(),
declaringEntityPersister.getFactory()
);
if ( CollectionPart.Nature.fromNameExact( navigableRole.getParent().getLocalName() ) != null ) { if ( CollectionPart.Nature.fromNameExact( navigableRole.getParent().getLocalName() ) != null ) {
// * the to-one's parent is directly a collection element or index // * the to-one's parent is directly a collection element or index
// * therefore, its parent-parent should be the collection itself // * therefore, its parent-parent should be the collection itself
final PluralAttributeMapping pluralAttribute = (PluralAttributeMapping) declaringEntityPersister.findByPath( final PluralAttributeMapping pluralAttribute = (PluralAttributeMapping) declaringEntityPersister.findByPath(
navigableRole.getParent().getParent().getFullPath().substring( declaringEntityPersister.getNavigableRole().getFullPath().length() + 1 ) ); navigableRole.getParent()
.getParent()
.getFullPath()
.substring( declaringEntityPersister.getNavigableRole()
.getFullPath()
.length() + 1 ) );
assert pluralAttribute != null; assert pluralAttribute != null;
final AbstractCollectionPersister persister = (AbstractCollectionPersister) pluralAttribute.getCollectionDescriptor(); final AbstractCollectionPersister persister = (AbstractCollectionPersister) pluralAttribute.getCollectionDescriptor();
@ -328,6 +355,7 @@ public class ToOneAttributeMapping
else { else {
assert bootValue instanceof OneToOne; assert bootValue instanceof OneToOne;
cardinality = Cardinality.ONE_TO_ONE; cardinality = Cardinality.ONE_TO_ONE;
hasJoinTable = false;
/* /*
The otherSidePropertyName value is used to determine bidirectionality based on the navigablePath string The otherSidePropertyName value is used to determine bidirectionality based on the navigablePath string
@ -381,7 +409,7 @@ public class ToOneAttributeMapping
} }
notFoundAction = null; notFoundAction = null;
isKeyTableNullable = isNullable(); isKeyTableNullable = isNullable();
isOptional = ! bootValue.isConstrained(); isOptional = !bootValue.isConstrained();
isInternalLoadNullable = isNullable(); isInternalLoadNullable = isNullable();
} }
@ -436,21 +464,28 @@ public class ToOneAttributeMapping
} }
this.targetKeyPropertyNames = targetKeyPropertyNames; this.targetKeyPropertyNames = targetKeyPropertyNames;
} }
else if ( bootValue.isReferenceToPrimaryKey() ) { else {
final PersistentClass entityBinding = bootValue.getBuildingContext().getMetadataCollector()
.getEntityBinding( entityMappingType.getEntityName() );
final Type propertyType = entityBinding.getRecursiveProperty( referencedPropertyName ).getType();
if ( bootValue.isReferenceToPrimaryKey() ) {
this.targetKeyPropertyName = referencedPropertyName; this.targetKeyPropertyName = referencedPropertyName;
final Set<String> targetKeyPropertyNames = new HashSet<>( 2 ); final Set<String> targetKeyPropertyNames = new HashSet<>( 3 );
addPrefixedPropertyNames( addPrefixedPropertyNames(
targetKeyPropertyNames, targetKeyPropertyNames,
targetKeyPropertyName, targetKeyPropertyName,
propertyType,
declaringEntityPersister.getFactory()
);
addPrefixedPropertyNames(
targetKeyPropertyNames,
null,
bootValue.getType(), bootValue.getType(),
declaringEntityPersister.getFactory() declaringEntityPersister.getFactory()
); );
this.targetKeyPropertyNames = targetKeyPropertyNames; this.targetKeyPropertyNames = targetKeyPropertyNames;
} }
else { else {
final PersistentClass entityBinding = bootValue.getBuildingContext().getMetadataCollector()
.getEntityBinding( entityMappingType.getEntityName() );
final Type propertyType = entityBinding.getRecursiveProperty( referencedPropertyName ).getType();
final CompositeType compositeType; final CompositeType compositeType;
if ( propertyType.isComponentType() && ( compositeType = (CompositeType) propertyType ).isEmbedded() if ( propertyType.isComponentType() && ( compositeType = (CompositeType) propertyType ).isEmbedded()
&& compositeType.getPropertyNames().length == 1 ) { && compositeType.getPropertyNames().length == 1 ) {
@ -476,7 +511,10 @@ public class ToOneAttributeMapping
final String mapsIdAttributeName; final String mapsIdAttributeName;
// If there is a "virtual property" for a non-PK join mapping, we try to see if the columns match the // If there is a "virtual property" for a non-PK join mapping, we try to see if the columns match the
// primary key columns and if so, we add the primary key property name as target key property // primary key columns and if so, we add the primary key property name as target key property
if ( ( mapsIdAttributeName = findMapsIdPropertyName( entityMappingType, referencedPropertyName ) ) != null ) { if ( ( mapsIdAttributeName = findMapsIdPropertyName(
entityMappingType,
referencedPropertyName
) ) != null ) {
addPrefixedPropertyPaths( addPrefixedPropertyPaths(
targetKeyPropertyNames, targetKeyPropertyNames,
mapsIdAttributeName, mapsIdAttributeName,
@ -500,6 +538,7 @@ public class ToOneAttributeMapping
} }
} }
} }
}
private static SelectablePath findBidirectionalOneToManyAttributeName( private static SelectablePath findBidirectionalOneToManyAttributeName(
String propertyPath, String propertyPath,
@ -629,6 +668,7 @@ public class ToOneAttributeMapping
this.targetKeyPropertyName = original.targetKeyPropertyName; this.targetKeyPropertyName = original.targetKeyPropertyName;
this.targetKeyPropertyNames = original.targetKeyPropertyNames; this.targetKeyPropertyNames = original.targetKeyPropertyNames;
this.cardinality = original.cardinality; this.cardinality = original.cardinality;
this.hasJoinTable = original.hasJoinTable;
this.bidirectionalAttributePath = original.bidirectionalAttributePath; this.bidirectionalAttributePath = original.bidirectionalAttributePath;
this.declaringTableGroupProducer = declaringTableGroupProducer; this.declaringTableGroupProducer = declaringTableGroupProducer;
this.isInternalLoadNullable = original.isInternalLoadNullable; this.isInternalLoadNullable = original.isInternalLoadNullable;
@ -729,7 +769,7 @@ public class ToOneAttributeMapping
final String newFkPrefix; final String newFkPrefix;
if ( prefix == null ) { if ( prefix == null ) {
newPrefix = propertyName; newPrefix = propertyName;
newPkPrefix = propertyName + "." + EntityIdentifierMapping.ROLE_LOCAL_NAME; newPkPrefix = EntityIdentifierMapping.ROLE_LOCAL_NAME;
newFkPrefix = ForeignKeyDescriptor.PART_NAME; newFkPrefix = ForeignKeyDescriptor.PART_NAME;
} }
else if ( propertyName == null ) { else if ( propertyName == null ) {
@ -891,9 +931,8 @@ public class ToOneAttributeMapping
FetchTiming fetchTiming, FetchTiming fetchTiming,
DomainResultCreationState creationState) { DomainResultCreationState creationState) {
final AssociationKey associationKey = foreignKeyDescriptor.getAssociationKey(); final AssociationKey associationKey = foreignKeyDescriptor.getAssociationKey();
final boolean associationKeyVisited = creationState.isAssociationKeyVisited( associationKey );
if ( creationState.isAssociationKeyVisited( associationKey ) if ( associationKeyVisited || bidirectionalAttributePath != null ) {
|| bidirectionalAttributePath != null && !creationState.isRegisteringVisitedAssociationKeys() ) {
NavigablePath parentNavigablePath = fetchablePath.getParent(); NavigablePath parentNavigablePath = fetchablePath.getParent();
assert parentNavigablePath.equals( fetchParent.getNavigablePath() ); assert parentNavigablePath.equals( fetchParent.getNavigablePath() );
// The parent navigable path is {fk} if we are creating the domain result for the foreign key for a circular fetch // The parent navigable path is {fk} if we are creating the domain result for the foreign key for a circular fetch
@ -952,6 +991,12 @@ public class ToOneAttributeMapping
); );
} }
if ( !associationKeyVisited && creationState.isRegisteringVisitedAssociationKeys() ) {
// If the current association key hasn't been visited yet and we are registering keys,
// then there can't be a circular fetch
return null;
}
/* /*
class Child { class Child {
@OneToOne @OneToOne
@ -2069,10 +2114,36 @@ public class ToOneAttributeMapping
private void initializeIfNeeded(TableGroup lhs, SqlAstJoinType sqlAstJoinType, TableGroup tableGroup) { private void initializeIfNeeded(TableGroup lhs, SqlAstJoinType sqlAstJoinType, TableGroup tableGroup) {
if ( sqlAstJoinType == SqlAstJoinType.INNER && ( isNullable || !lhs.canUseInnerJoins() ) ) { if ( sqlAstJoinType == SqlAstJoinType.INNER && ( isNullable || !lhs.canUseInnerJoins() ) ) {
if ( hasJoinTable ) {
// Set the join type of the table reference join to INNER to retain cardinality expectation
final TableReference lhsTableReference = lhs.resolveTableReference(
tableGroup.getNavigablePath(),
identifyingColumnsTableExpression
);
final List<TableReferenceJoin> tableReferenceJoins = lhs.getTableReferenceJoins();
for ( int i = 0; i < tableReferenceJoins.size(); i++ ) {
final TableReferenceJoin tableReferenceJoin = tableReferenceJoins.get( i );
if ( tableReferenceJoin.getJoinType() != SqlAstJoinType.INNER
&& tableReferenceJoin.getJoinedTableReference() == lhsTableReference ) {
tableReferenceJoins.set(
i,
new TableReferenceJoin(
true,
tableReferenceJoin.getJoinedTableReference(),
tableReferenceJoin.getPredicate()
)
);
return;
}
}
throw new AssertionFailure( "Couldn't find table reference join for join table: " + lhsTableReference );
}
else {
// Force initialization of the underlying table group join to retain cardinality // Force initialization of the underlying table group join to retain cardinality
tableGroup.getPrimaryTableReference(); tableGroup.getPrimaryTableReference();
} }
} }
}
private SqlAstJoinType getJoinTypeForFetch(NavigablePath navigablePath, TableGroup tableGroup) { private SqlAstJoinType getJoinTypeForFetch(NavigablePath navigablePath, TableGroup tableGroup) {
for ( TableGroupJoin tableGroupJoin : tableGroup.getTableGroupJoins() ) { for ( TableGroupJoin tableGroupJoin : tableGroup.getTableGroupJoins() ) {

View File

@ -55,7 +55,7 @@ public class TableReferenceJoin implements TableJoin, PredicateContainer {
@Override @Override
public String toString() { public String toString() {
return getJoinType().getText() + " join " + getJoinedTableReference().toString(); return getJoinType().getText() + "join " + getJoinedTableReference().toString();
} }
@Override @Override

View File

@ -31,6 +31,7 @@ import org.hibernate.internal.util.StringHelper;
import org.hibernate.internal.util.collections.ArrayHelper; import org.hibernate.internal.util.collections.ArrayHelper;
import org.hibernate.mapping.Component; import org.hibernate.mapping.Component;
import org.hibernate.mapping.Property; import org.hibernate.mapping.Property;
import org.hibernate.metamodel.mapping.EmbeddableMappingType;
import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart; import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart;
import org.hibernate.metamodel.mapping.SelectableMapping; import org.hibernate.metamodel.mapping.SelectableMapping;
import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess; import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess;
@ -39,6 +40,7 @@ import org.hibernate.property.access.spi.PropertyAccess;
import org.hibernate.query.sqm.SqmExpressible; import org.hibernate.query.sqm.SqmExpressible;
import org.hibernate.resource.beans.internal.FallbackBeanInstanceProducer; import org.hibernate.resource.beans.internal.FallbackBeanInstanceProducer;
import org.hibernate.resource.beans.spi.ManagedBeanRegistry; import org.hibernate.resource.beans.spi.ManagedBeanRegistry;
import org.hibernate.type.descriptor.ValueExtractor;
import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.spi.CompositeTypeImplementor; import org.hibernate.type.spi.CompositeTypeImplementor;
import org.hibernate.usertype.CompositeUserType; import org.hibernate.usertype.CompositeUserType;
@ -507,10 +509,7 @@ public class ComponentType extends AbstractType implements CompositeTypeImplemen
values[i] = propertyTypes[i].deepCopy( values[i], factory ); values[i] = propertyTypes[i].deepCopy( values[i], factory );
} }
final EmbeddableInstantiator instantiator = mappingModelPart.getEmbeddableTypeDescriptor() Object result = instantiator().instantiate( () -> values, factory );
.getRepresentationStrategy()
.getInstantiator();
Object result = instantiator.instantiate( () -> values, factory );
//not absolutely necessary, but helps for some //not absolutely necessary, but helps for some
//equals()/hashCode() implementations //equals()/hashCode() implementations
@ -530,10 +529,7 @@ public class ComponentType extends AbstractType implements CompositeTypeImplemen
Object owner, Object owner,
Map<Object, Object> copyCache) { Map<Object, Object> copyCache) {
if ( !isMutable() ) { if ( original == null && target == null ) {
return original;
}
if ( original == null ) {
return null; return null;
} }
if ( compositeUserType != null ) { if ( compositeUserType != null ) {
@ -542,15 +538,7 @@ public class ComponentType extends AbstractType implements CompositeTypeImplemen
//if ( original == target ) return target; //if ( original == target ) return target;
final Object[] originalValues = getPropertyValues( original ); final Object[] originalValues = getPropertyValues( original );
final Object[] resultValues; final Object[] resultValues = getPropertyValues( target );
if ( target == null ) {
resultValues = new Object[originalValues.length];
}
else {
resultValues = getPropertyValues( target );
}
final Object[] replacedValues = TypeHelper.replace( final Object[] replacedValues = TypeHelper.replace(
originalValues, originalValues,
resultValues, resultValues,
@ -560,11 +548,8 @@ public class ComponentType extends AbstractType implements CompositeTypeImplemen
copyCache copyCache
); );
if ( target == null ) { if ( target == null || !isMutable() ) {
final EmbeddableInstantiator instantiator = mappingModelPart.getEmbeddableTypeDescriptor() return instantiator().instantiate( () -> replacedValues, session.getSessionFactory() );
.getRepresentationStrategy()
.getInstantiator();
return instantiator.instantiate( () -> replacedValues, session.getSessionFactory() );
} }
else { else {
setPropertyValues( target, replacedValues ); setPropertyValues( target, replacedValues );
@ -581,10 +566,7 @@ public class ComponentType extends AbstractType implements CompositeTypeImplemen
Map<Object, Object> copyCache, Map<Object, Object> copyCache,
ForeignKeyDirection foreignKeyDirection) { ForeignKeyDirection foreignKeyDirection) {
if ( !isMutable() ) { if ( original == null && target == null ) {
return original;
}
if ( original == null ) {
return null; return null;
} }
if ( compositeUserType != null ) { if ( compositeUserType != null ) {
@ -594,15 +576,7 @@ public class ComponentType extends AbstractType implements CompositeTypeImplemen
final Object[] originalValues = getPropertyValues( original ); final Object[] originalValues = getPropertyValues( original );
final Object[] resultValues; final Object[] resultValues = getPropertyValues( target );
if ( target == null ) {
resultValues = new Object[originalValues.length];
}
else {
resultValues = getPropertyValues( target );
}
final Object[] replacedValues = TypeHelper.replace( final Object[] replacedValues = TypeHelper.replace(
originalValues, originalValues,
resultValues, resultValues,
@ -613,11 +587,8 @@ public class ComponentType extends AbstractType implements CompositeTypeImplemen
foreignKeyDirection foreignKeyDirection
); );
if ( target == null ) { if ( target == null || !isMutable() ) {
final EmbeddableInstantiator instantiator = mappingModelPart.getEmbeddableTypeDescriptor() return instantiator().instantiate( () -> replacedValues, session.getSessionFactory() );
.getRepresentationStrategy()
.getInstantiator();
return instantiator.instantiate( () -> replacedValues, session.getSessionFactory() );
} }
else { else {
setPropertyValues( target, replacedValues ); setPropertyValues( target, replacedValues );
@ -688,10 +659,7 @@ public class ComponentType extends AbstractType implements CompositeTypeImplemen
assembled[i] = propertyTypes[i].assemble( (Serializable) values[i], session, owner ); assembled[i] = propertyTypes[i].assemble( (Serializable) values[i], session, owner );
} }
final EmbeddableInstantiator instantiator = mappingModelPart.getEmbeddableTypeDescriptor() final Object instance = instantiator().instantiate( () -> assembled, session.getFactory() );
.getRepresentationStrategy()
.getInstantiator();
final Object instance = instantiator.instantiate( () -> assembled, session.getFactory() );
final PropertyAccess parentInjectionAccess = mappingModelPart.getParentInjectionAttributePropertyAccess(); final PropertyAccess parentInjectionAccess = mappingModelPart.getParentInjectionAttributePropertyAccess();
if ( parentInjectionAccess != null ) { if ( parentInjectionAccess != null ) {
@ -830,10 +798,15 @@ public class ComponentType extends AbstractType implements CompositeTypeImplemen
} }
private Object resolve(Object[] value, SharedSessionContractImplementor session) throws HibernateException { private Object resolve(Object[] value, SharedSessionContractImplementor session) throws HibernateException {
final EmbeddableInstantiator instantiator = mappingModelPart.getEmbeddableTypeDescriptor() return instantiator().instantiate( () -> value, session.getFactory() );
.getRepresentationStrategy() }
.getInstantiator();
return instantiator.instantiate( () -> value, session.getFactory() ); private EmbeddableMappingType embeddableTypeDescriptor() {
return mappingModelPart.getEmbeddableTypeDescriptor();
}
protected final EmbeddableInstantiator instantiator() {
return embeddableTypeDescriptor().getRepresentationStrategy().getInstantiator();
} }
@Override @Override

View File

@ -213,7 +213,7 @@ public class TypeHelper {
); );
if ( target[i] != null && compositeType instanceof ComponentType ) { if ( target[i] != null && compositeType instanceof ComponentType ) {
final ComponentType componentType = (ComponentType) compositeType; final ComponentType componentType = (ComponentType) compositeType;
if ( componentType.isCompositeUserType() ) { if ( !componentType.isMutable() || componentType.isCompositeUserType() ) {
final EmbeddableInstantiator instantiator = ( (ComponentType) compositeType ).getMappingModelPart() final EmbeddableInstantiator instantiator = ( (ComponentType) compositeType ).getMappingModelPart()
.getEmbeddableTypeDescriptor() .getEmbeddableTypeDescriptor()
.getRepresentationStrategy() .getRepresentationStrategy()

View File

@ -177,7 +177,7 @@ public class EntityWithBidirectionalAssociationsOneOfWhichIsAJoinTableTest {
.setParameter( "id", 1 ) .setParameter( "id", 1 )
.getSingleResult(); .getSingleResult();
statementInspector.assertExecutedCount( 2 ); statementInspector.assertExecutedCount( 2 );
statementInspector.assertNumberOfOccurrenceInQuery( 0, "join", 2 ); statementInspector.assertNumberOfOccurrenceInQuery( 0, "join", 1 );
statementInspector.assertNumberOfOccurrenceInQuery( 1, "join", 3 ); statementInspector.assertNumberOfOccurrenceInQuery( 1, "join", 3 );
Male son = parent.getSon(); Male son = parent.getSon();
assertThat( son, CoreMatchers.notNullValue() ); assertThat( son, CoreMatchers.notNullValue() );

View File

@ -4,14 +4,17 @@ import org.hibernate.testing.orm.junit.DomainModel;
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;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import jakarta.persistence.Embeddable; import jakarta.persistence.Embeddable;
import jakarta.persistence.Embedded; import jakarta.persistence.Embedded;
import jakarta.persistence.Entity; import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.Id; import jakarta.persistence.Id;
import jakarta.persistence.ManyToOne;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.*;
@DomainModel(annotatedClasses = { @DomainModel(annotatedClasses = {
MergeRecordPropertyTestCase.MyEntity.class MergeRecordPropertyTestCase.MyEntity.class
@ -20,6 +23,16 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
@JiraKey("HHH-16759") @JiraKey("HHH-16759")
public class MergeRecordPropertyTestCase { public class MergeRecordPropertyTestCase {
@AfterEach
protected void cleanupTest(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
session.createQuery( "update MyEntity e set e.record.assoc = null" ).executeUpdate();
session.createQuery( "delete from MyEntity" ).executeUpdate();
}
);
}
@Test @Test
public void mergeDetached(SessionFactoryScope scope) { public void mergeDetached(SessionFactoryScope scope) {
scope.inTransaction( scope.inTransaction(
@ -38,24 +51,53 @@ public class MergeRecordPropertyTestCase {
} }
@Test @Test
public void mergeTransient(SessionFactoryScope scope) { public void mergeDetachedNullComponent(SessionFactoryScope scope) {
scope.inTransaction( scope.inTransaction(
session -> session.merge( new MyEntity( 2L, new MyRecord( "test", "xyz" ) ) ) session -> session.persist( new MyEntity( 1L, new MyRecord( "test", "abc" ) ) )
);
scope.inTransaction(
session -> session.merge( new MyEntity( 1L ) )
); );
scope.inSession( scope.inSession(
session -> { session -> {
final MyEntity entity = session.find( MyEntity.class, 2L ); final MyEntity entity = session.find( MyEntity.class, 1L );
assertNull( entity.record );
}
);
}
@Test
public void mergeTransient(SessionFactoryScope scope) {
scope.inTransaction(
session -> session.merge( new MyEntity( 1L, new MyRecord( "test", "xyz" ) ) )
);
scope.inSession(
session -> {
final MyEntity entity = session.find( MyEntity.class, 1L );
assertEquals( "test", entity.record.name ); assertEquals( "test", entity.record.name );
assertEquals( "xyz", entity.record.description ); assertEquals( "xyz", entity.record.description );
} }
); );
} }
@Test
public void mergeTransientNullComponent(SessionFactoryScope scope) {
scope.inTransaction(
session -> session.merge( new MyEntity( 1L ) )
);
scope.inSession(
session -> {
final MyEntity entity = session.find( MyEntity.class, 1L );
assertNull( entity.record );
}
);
}
@Test @Test
public void mergePersistent(SessionFactoryScope scope) { public void mergePersistent(SessionFactoryScope scope) {
scope.inTransaction( scope.inTransaction(
session -> { session -> {
final MyEntity entity = new MyEntity( 3L, new MyRecord( "test", "efg" ) ); final MyEntity entity = new MyEntity( 1L, new MyRecord( "test", "efg" ) );
session.persist( entity ); session.persist( entity );
entity.setRecord( new MyRecord( "test", "h" ) ); entity.setRecord( new MyRecord( "test", "h" ) );
session.merge( entity ); session.merge( entity );
@ -63,13 +105,41 @@ public class MergeRecordPropertyTestCase {
); );
scope.inSession( scope.inSession(
session -> { session -> {
final MyEntity entity = session.find( MyEntity.class, 3L ); final MyEntity entity = session.find( MyEntity.class, 1L );
assertEquals( "test", entity.record.name ); assertEquals( "test", entity.record.name );
assertEquals( "h", entity.record.description ); assertEquals( "h", entity.record.description );
} }
); );
} }
@Test
public void mergePersistentDetached(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
final MyEntity entity = new MyEntity( 1L, new MyRecord( "test", "abc" ) );
session.persist( entity );
session.flush();
session.clear();
final MyEntity entity2 = new MyEntity( 2L, new MyRecord( "test", "efg", new MyEntity( 1L ) ) );
final MyEntity mergedEntity = session.merge( entity2 );
assertTrue( session.contains( mergedEntity.record.assoc() ) );
}
);
}
@Test
public void mergePersistentManaged(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
final MyEntity entity = new MyEntity( 1L, new MyRecord( "test", "abc" ) );
session.persist( entity );
final MyEntity entity2 = new MyEntity( 1L, new MyRecord( "test", "efg", new MyEntity( 1L ) ) );
final MyEntity mergedEntity = session.merge( entity2 );
assertTrue( session.contains( mergedEntity.record.assoc() ) );
}
);
}
@Entity(name = "MyEntity") @Entity(name = "MyEntity")
public static class MyEntity { public static class MyEntity {
@Id @Id
@ -80,6 +150,10 @@ public class MergeRecordPropertyTestCase {
public MyEntity() { public MyEntity() {
} }
public MyEntity(Long id) {
this.id = id;
}
public MyEntity(Long id, MyRecord record) { public MyEntity(Long id, MyRecord record) {
this.id = id; this.id = id;
this.record = record; this.record = record;
@ -103,6 +177,9 @@ public class MergeRecordPropertyTestCase {
} }
@Embeddable @Embeddable
public static record MyRecord(String name, String description) { public static record MyRecord(String name, String description, @ManyToOne(fetch = FetchType.LAZY) MyEntity assoc) {
public MyRecord(String name, String description) {
this( name, description, null );
}
} }
} }