HHH-16759 When ComponentType is immutable, use instantiator instead of setting property values
This commit is contained in:
parent
130e05755a
commit
10baf4398a
|
@ -48,6 +48,7 @@ import org.hibernate.boot.model.internal.CreateKeySecondPass;
|
|||
import org.hibernate.boot.model.internal.FkSecondPass;
|
||||
import org.hibernate.boot.model.internal.IdGeneratorResolverSecondPass;
|
||||
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.SecondaryTableFromAnnotationSecondPass;
|
||||
import org.hibernate.boot.model.internal.SecondaryTableSecondPass;
|
||||
|
@ -1667,6 +1668,7 @@ public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector,
|
|||
private ArrayList<ImplicitColumnNamingSecondPass> implicitColumnNamingSecondPassList;
|
||||
|
||||
private ArrayList<SecondPass> generalSecondPassList;
|
||||
private ArrayList<OptionalDeterminationSecondPass> optionalDeterminationSecondPassList;
|
||||
|
||||
@Override
|
||||
public void addSecondPass(SecondPass secondPass) {
|
||||
|
@ -1705,6 +1707,9 @@ public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector,
|
|||
else if ( secondPass instanceof ImplicitColumnNamingSecondPass ) {
|
||||
addImplicitColumnNamingSecondPass( (ImplicitColumnNamingSecondPass) secondPass );
|
||||
}
|
||||
else if ( secondPass instanceof OptionalDeterminationSecondPass ) {
|
||||
addOptionalDeterminationSecondPass( (OptionalDeterminationSecondPass) secondPass );
|
||||
}
|
||||
else {
|
||||
// add to the general SecondPass list
|
||||
if ( generalSecondPassList == null ) {
|
||||
|
@ -1786,6 +1791,13 @@ public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector,
|
|||
implicitColumnNamingSecondPassList.add( secondPass );
|
||||
}
|
||||
|
||||
private void addOptionalDeterminationSecondPass(OptionalDeterminationSecondPass secondPass) {
|
||||
if ( optionalDeterminationSecondPassList == null ) {
|
||||
optionalDeterminationSecondPassList = new ArrayList<>();
|
||||
}
|
||||
optionalDeterminationSecondPassList.add( secondPass );
|
||||
}
|
||||
|
||||
|
||||
private boolean inSecondPass = false;
|
||||
|
||||
|
@ -1812,6 +1824,7 @@ public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector,
|
|||
|
||||
processSecondPasses( querySecondPassList );
|
||||
processSecondPasses( generalSecondPassList );
|
||||
processSecondPasses( optionalDeterminationSecondPassList );
|
||||
|
||||
processPropertyReferences();
|
||||
|
||||
|
|
|
@ -119,6 +119,7 @@ public class AnyBinder {
|
|||
}
|
||||
binder.setAccessType( inferredData.getDefaultAccess() );
|
||||
binder.setCascade( cascadeStrategy );
|
||||
binder.setBuildingContext( context );
|
||||
Property prop = binder.makeProperty();
|
||||
//composite FK columns are in the same table, so it's OK
|
||||
propertyHolder.addProperty( prop, columns, inferredData.getDeclaringClass() );
|
||||
|
|
|
@ -1276,6 +1276,7 @@ public abstract class CollectionBinder {
|
|||
binder.setProperty( property );
|
||||
binder.setInsertable( insertable );
|
||||
binder.setUpdatable( updatable );
|
||||
binder.setBuildingContext( buildingContext );
|
||||
Property prop = binder.makeProperty();
|
||||
//we don't care about the join stuffs because the column is on the association table.
|
||||
if ( !declaringClassSet ) {
|
||||
|
|
|
@ -192,7 +192,7 @@ public class EmbeddableBinder {
|
|||
entityBinder,
|
||||
isComponentEmbedded,
|
||||
isIdentifierMapper,
|
||||
false,
|
||||
context.getMetadataCollector().isInSecondPass(),
|
||||
customInstantiatorImpl,
|
||||
compositeUserTypeClass,
|
||||
annotatedColumns,
|
||||
|
|
|
@ -112,6 +112,7 @@ public class OneToOneSecondPass implements SecondPass {
|
|||
binder.setValue( value );
|
||||
binder.setCascade( cascadeStrategy );
|
||||
binder.setAccessType( inferredData.getDefaultAccess() );
|
||||
binder.setBuildingContext( buildingContext );
|
||||
|
||||
final LazyGroup lazyGroupAnnotation = property.getAnnotation( LazyGroup.class );
|
||||
if ( lazyGroupAnnotation != null ) {
|
||||
|
@ -186,6 +187,7 @@ public class OneToOneSecondPass implements SecondPass {
|
|||
manyToOne.setFetchMode( oneToOne.getFetchMode() );
|
||||
manyToOne.setLazy( oneToOne.isLazy() );
|
||||
manyToOne.setReferencedEntityName( oneToOne.getReferencedEntityName() );
|
||||
manyToOne.setReferencedPropertyName( mappedBy );
|
||||
manyToOne.setUnwrapProxy( oneToOne.isUnwrapProxy() );
|
||||
manyToOne.markAsLogicalOneToOne();
|
||||
property.setValue( manyToOne );
|
||||
|
|
|
@ -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 {
|
||||
}
|
|
@ -48,13 +48,16 @@ import org.hibernate.boot.model.IdentifierGeneratorDefinition;
|
|||
import org.hibernate.boot.spi.AccessType;
|
||||
import org.hibernate.boot.spi.MetadataBuildingContext;
|
||||
import org.hibernate.boot.spi.PropertyData;
|
||||
import org.hibernate.boot.spi.SecondPass;
|
||||
import org.hibernate.engine.OptimisticLockStyle;
|
||||
import org.hibernate.internal.CoreMessageLogger;
|
||||
import org.hibernate.mapping.Collection;
|
||||
import org.hibernate.mapping.Component;
|
||||
import org.hibernate.mapping.GeneratorCreator;
|
||||
import org.hibernate.mapping.Join;
|
||||
import org.hibernate.mapping.KeyValue;
|
||||
import org.hibernate.mapping.MappedSuperclass;
|
||||
import org.hibernate.mapping.PersistentClass;
|
||||
import org.hibernate.mapping.Property;
|
||||
import org.hibernate.mapping.RootClass;
|
||||
import org.hibernate.mapping.SimpleValue;
|
||||
|
@ -448,13 +451,32 @@ public class PropertyBinder {
|
|||
|
||||
private void handleOptional(Property property) {
|
||||
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) {
|
||||
final Value value = property.getValue();
|
||||
return value instanceof org.hibernate.mapping.OneToMany || value.isNullable();
|
||||
if ( !property.getValue().isNullable() ) {
|
||||
property.setOptional( false );
|
||||
}
|
||||
};
|
||||
// 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) {
|
||||
|
|
|
@ -143,4 +143,9 @@ public class ManyToOne extends ToOne {
|
|||
public boolean isLogicalOneToOne() {
|
||||
return isLogicalOneToOne;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNullable() {
|
||||
return getReferencedPropertyName() != null || super.isNullable();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,11 +9,13 @@ package org.hibernate.metamodel.mapping.internal;
|
|||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.hibernate.AssertionFailure;
|
||||
import org.hibernate.LockMode;
|
||||
import org.hibernate.annotations.NotFoundAction;
|
||||
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.TableGroupProducer;
|
||||
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.results.graph.DomainResult;
|
||||
import org.hibernate.sql.results.graph.DomainResultCreationState;
|
||||
|
@ -145,6 +148,7 @@ public class ToOneAttributeMapping
|
|||
private final Set<String> targetKeyPropertyNames;
|
||||
|
||||
private final Cardinality cardinality;
|
||||
private final boolean hasJoinTable;
|
||||
/*
|
||||
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.
|
||||
|
@ -171,6 +175,7 @@ public class ToOneAttributeMapping
|
|||
referencedPropertyName = original.referencedPropertyName;
|
||||
targetKeyPropertyName = original.targetKeyPropertyName;
|
||||
cardinality = original.cardinality;
|
||||
hasJoinTable = original.hasJoinTable;
|
||||
bidirectionalAttributePath = original.bidirectionalAttributePath;
|
||||
declaringTableGroupProducer = original.declaringTableGroupProducer;
|
||||
isKeyTableNullable = original.isKeyTableNullable;
|
||||
|
@ -242,7 +247,10 @@ public class ToOneAttributeMapping
|
|||
this.entityMappingType = entityMappingType;
|
||||
|
||||
this.navigableRole = navigableRole;
|
||||
this.declaringTableGroupProducer = resolveDeclaringTableGroupProducer( declaringEntityPersister, navigableRole );
|
||||
this.declaringTableGroupProducer = resolveDeclaringTableGroupProducer(
|
||||
declaringEntityPersister,
|
||||
navigableRole
|
||||
);
|
||||
if ( bootValue instanceof ManyToOne ) {
|
||||
final ManyToOne manyToOne = (ManyToOne) bootValue;
|
||||
this.notFoundAction = ( (ManyToOne) bootValue ).getNotFoundAction();
|
||||
|
@ -260,6 +268,7 @@ public class ToOneAttributeMapping
|
|||
? name
|
||||
: bootValue.getPropertyName();
|
||||
if ( cardinality == Cardinality.LOGICAL_ONE_TO_ONE ) {
|
||||
boolean hasJoinTable = false;
|
||||
// Handle join table cases
|
||||
for ( Join join : entityBinding.getJoinClosure() ) {
|
||||
if ( join.getPersistentClass().getEntityName().equals( entityBinding.getEntityName() )
|
||||
|
@ -267,7 +276,10 @@ public class ToOneAttributeMapping
|
|||
&& join.getTable() == manyToOne.getTable()
|
||||
&& equal( join.getKey(), manyToOne ) ) {
|
||||
//noinspection deprecation
|
||||
bidirectionalAttributeName = SelectablePath.parse( join.getPropertyIterator().next().getName() );
|
||||
bidirectionalAttributeName = SelectablePath.parse(
|
||||
join.getPropertyIterator().next().getName()
|
||||
);
|
||||
hasJoinTable = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -280,8 +292,10 @@ public class ToOneAttributeMapping
|
|||
entityBinding.getPropertyClosure()
|
||||
);
|
||||
}
|
||||
this.hasJoinTable = hasJoinTable;
|
||||
}
|
||||
else {
|
||||
this.hasJoinTable = false;
|
||||
bidirectionalAttributeName = findBidirectionalOneToManyAttributeName(
|
||||
propertyPath,
|
||||
declaringType,
|
||||
|
@ -294,7 +308,12 @@ public class ToOneAttributeMapping
|
|||
else {
|
||||
// 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 );
|
||||
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 )
|
||||
: null;
|
||||
}
|
||||
|
@ -302,12 +321,20 @@ public class ToOneAttributeMapping
|
|||
isKeyTableNullable = true;
|
||||
}
|
||||
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 ) {
|
||||
// * the to-one's parent is directly a collection element or index
|
||||
// * therefore, its parent-parent should be the collection itself
|
||||
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;
|
||||
|
||||
final AbstractCollectionPersister persister = (AbstractCollectionPersister) pluralAttribute.getCollectionDescriptor();
|
||||
|
@ -328,6 +355,7 @@ public class ToOneAttributeMapping
|
|||
else {
|
||||
assert bootValue instanceof OneToOne;
|
||||
cardinality = Cardinality.ONE_TO_ONE;
|
||||
hasJoinTable = false;
|
||||
|
||||
/*
|
||||
The otherSidePropertyName value is used to determine bidirectionality based on the navigablePath string
|
||||
|
@ -381,7 +409,7 @@ public class ToOneAttributeMapping
|
|||
}
|
||||
notFoundAction = null;
|
||||
isKeyTableNullable = isNullable();
|
||||
isOptional = ! bootValue.isConstrained();
|
||||
isOptional = !bootValue.isConstrained();
|
||||
isInternalLoadNullable = isNullable();
|
||||
}
|
||||
|
||||
|
@ -436,67 +464,78 @@ public class ToOneAttributeMapping
|
|||
}
|
||||
this.targetKeyPropertyNames = targetKeyPropertyNames;
|
||||
}
|
||||
else if ( bootValue.isReferenceToPrimaryKey() ) {
|
||||
this.targetKeyPropertyName = referencedPropertyName;
|
||||
final Set<String> targetKeyPropertyNames = new HashSet<>( 2 );
|
||||
addPrefixedPropertyNames(
|
||||
targetKeyPropertyNames,
|
||||
targetKeyPropertyName,
|
||||
bootValue.getType(),
|
||||
declaringEntityPersister.getFactory()
|
||||
);
|
||||
this.targetKeyPropertyNames = targetKeyPropertyNames;
|
||||
}
|
||||
else {
|
||||
final PersistentClass entityBinding = bootValue.getBuildingContext().getMetadataCollector()
|
||||
.getEntityBinding( entityMappingType.getEntityName() );
|
||||
final Type propertyType = entityBinding.getRecursiveProperty( referencedPropertyName ).getType();
|
||||
final CompositeType compositeType;
|
||||
if ( propertyType.isComponentType() && ( compositeType = (CompositeType) propertyType ).isEmbedded()
|
||||
&& compositeType.getPropertyNames().length == 1 ) {
|
||||
final Set<String> targetKeyPropertyNames = new HashSet<>( 2 );
|
||||
this.targetKeyPropertyName = compositeType.getPropertyNames()[0];
|
||||
addPrefixedPropertyPaths(
|
||||
if ( bootValue.isReferenceToPrimaryKey() ) {
|
||||
this.targetKeyPropertyName = referencedPropertyName;
|
||||
final Set<String> targetKeyPropertyNames = new HashSet<>( 3 );
|
||||
addPrefixedPropertyNames(
|
||||
targetKeyPropertyNames,
|
||||
targetKeyPropertyName,
|
||||
compositeType.getSubtypes()[0],
|
||||
propertyType,
|
||||
declaringEntityPersister.getFactory()
|
||||
);
|
||||
addPrefixedPropertyNames(
|
||||
targetKeyPropertyNames,
|
||||
EntityIdentifierMapping.ROLE_LOCAL_NAME,
|
||||
propertyType,
|
||||
null,
|
||||
bootValue.getType(),
|
||||
declaringEntityPersister.getFactory()
|
||||
);
|
||||
this.targetKeyPropertyNames = targetKeyPropertyNames;
|
||||
}
|
||||
else {
|
||||
final Set<String> targetKeyPropertyNames = new HashSet<>( 2 );
|
||||
this.targetKeyPropertyName = referencedPropertyName;
|
||||
final String mapsIdAttributeName;
|
||||
// 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
|
||||
if ( ( mapsIdAttributeName = findMapsIdPropertyName( entityMappingType, referencedPropertyName ) ) != null ) {
|
||||
final CompositeType compositeType;
|
||||
if ( propertyType.isComponentType() && ( compositeType = (CompositeType) propertyType ).isEmbedded()
|
||||
&& compositeType.getPropertyNames().length == 1 ) {
|
||||
final Set<String> targetKeyPropertyNames = new HashSet<>( 2 );
|
||||
this.targetKeyPropertyName = compositeType.getPropertyNames()[0];
|
||||
addPrefixedPropertyPaths(
|
||||
targetKeyPropertyNames,
|
||||
mapsIdAttributeName,
|
||||
entityMappingType.getEntityPersister().getIdentifierType(),
|
||||
targetKeyPropertyName,
|
||||
compositeType.getSubtypes()[0],
|
||||
declaringEntityPersister.getFactory()
|
||||
);
|
||||
addPrefixedPropertyNames(
|
||||
targetKeyPropertyNames,
|
||||
EntityIdentifierMapping.ROLE_LOCAL_NAME,
|
||||
propertyType,
|
||||
declaringEntityPersister.getFactory()
|
||||
);
|
||||
this.targetKeyPropertyNames = targetKeyPropertyNames;
|
||||
}
|
||||
else {
|
||||
final Set<String> targetKeyPropertyNames = new HashSet<>( 2 );
|
||||
this.targetKeyPropertyName = referencedPropertyName;
|
||||
final String mapsIdAttributeName;
|
||||
// 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
|
||||
if ( ( mapsIdAttributeName = findMapsIdPropertyName(
|
||||
entityMappingType,
|
||||
referencedPropertyName
|
||||
) ) != null ) {
|
||||
addPrefixedPropertyPaths(
|
||||
targetKeyPropertyNames,
|
||||
mapsIdAttributeName,
|
||||
entityMappingType.getEntityPersister().getIdentifierType(),
|
||||
declaringEntityPersister.getFactory()
|
||||
);
|
||||
}
|
||||
addPrefixedPropertyNames(
|
||||
targetKeyPropertyNames,
|
||||
targetKeyPropertyName,
|
||||
propertyType,
|
||||
declaringEntityPersister.getFactory()
|
||||
);
|
||||
addPrefixedPropertyNames(
|
||||
targetKeyPropertyNames,
|
||||
ForeignKeyDescriptor.PART_NAME,
|
||||
propertyType,
|
||||
declaringEntityPersister.getFactory()
|
||||
);
|
||||
this.targetKeyPropertyNames = targetKeyPropertyNames;
|
||||
}
|
||||
addPrefixedPropertyNames(
|
||||
targetKeyPropertyNames,
|
||||
targetKeyPropertyName,
|
||||
propertyType,
|
||||
declaringEntityPersister.getFactory()
|
||||
);
|
||||
addPrefixedPropertyNames(
|
||||
targetKeyPropertyNames,
|
||||
ForeignKeyDescriptor.PART_NAME,
|
||||
propertyType,
|
||||
declaringEntityPersister.getFactory()
|
||||
);
|
||||
this.targetKeyPropertyNames = targetKeyPropertyNames;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -629,6 +668,7 @@ public class ToOneAttributeMapping
|
|||
this.targetKeyPropertyName = original.targetKeyPropertyName;
|
||||
this.targetKeyPropertyNames = original.targetKeyPropertyNames;
|
||||
this.cardinality = original.cardinality;
|
||||
this.hasJoinTable = original.hasJoinTable;
|
||||
this.bidirectionalAttributePath = original.bidirectionalAttributePath;
|
||||
this.declaringTableGroupProducer = declaringTableGroupProducer;
|
||||
this.isInternalLoadNullable = original.isInternalLoadNullable;
|
||||
|
@ -729,7 +769,7 @@ public class ToOneAttributeMapping
|
|||
final String newFkPrefix;
|
||||
if ( prefix == null ) {
|
||||
newPrefix = propertyName;
|
||||
newPkPrefix = propertyName + "." + EntityIdentifierMapping.ROLE_LOCAL_NAME;
|
||||
newPkPrefix = EntityIdentifierMapping.ROLE_LOCAL_NAME;
|
||||
newFkPrefix = ForeignKeyDescriptor.PART_NAME;
|
||||
}
|
||||
else if ( propertyName == null ) {
|
||||
|
@ -891,9 +931,8 @@ public class ToOneAttributeMapping
|
|||
FetchTiming fetchTiming,
|
||||
DomainResultCreationState creationState) {
|
||||
final AssociationKey associationKey = foreignKeyDescriptor.getAssociationKey();
|
||||
|
||||
if ( creationState.isAssociationKeyVisited( associationKey )
|
||||
|| bidirectionalAttributePath != null && !creationState.isRegisteringVisitedAssociationKeys() ) {
|
||||
final boolean associationKeyVisited = creationState.isAssociationKeyVisited( associationKey );
|
||||
if ( associationKeyVisited || bidirectionalAttributePath != null ) {
|
||||
NavigablePath parentNavigablePath = fetchablePath.getParent();
|
||||
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
|
||||
|
@ -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 {
|
||||
@OneToOne
|
||||
|
@ -2069,8 +2114,34 @@ public class ToOneAttributeMapping
|
|||
|
||||
private void initializeIfNeeded(TableGroup lhs, SqlAstJoinType sqlAstJoinType, TableGroup tableGroup) {
|
||||
if ( sqlAstJoinType == SqlAstJoinType.INNER && ( isNullable || !lhs.canUseInnerJoins() ) ) {
|
||||
// Force initialization of the underlying table group join to retain cardinality
|
||||
tableGroup.getPrimaryTableReference();
|
||||
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
|
||||
tableGroup.getPrimaryTableReference();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -55,7 +55,7 @@ public class TableReferenceJoin implements TableJoin, PredicateContainer {
|
|||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getJoinType().getText() + " join " + getJoinedTableReference().toString();
|
||||
return getJoinType().getText() + "join " + getJoinedTableReference().toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -31,6 +31,7 @@ import org.hibernate.internal.util.StringHelper;
|
|||
import org.hibernate.internal.util.collections.ArrayHelper;
|
||||
import org.hibernate.mapping.Component;
|
||||
import org.hibernate.mapping.Property;
|
||||
import org.hibernate.metamodel.mapping.EmbeddableMappingType;
|
||||
import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart;
|
||||
import org.hibernate.metamodel.mapping.SelectableMapping;
|
||||
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.resource.beans.internal.FallbackBeanInstanceProducer;
|
||||
import org.hibernate.resource.beans.spi.ManagedBeanRegistry;
|
||||
import org.hibernate.type.descriptor.ValueExtractor;
|
||||
import org.hibernate.type.descriptor.jdbc.JdbcType;
|
||||
import org.hibernate.type.spi.CompositeTypeImplementor;
|
||||
import org.hibernate.usertype.CompositeUserType;
|
||||
|
@ -507,10 +509,7 @@ public class ComponentType extends AbstractType implements CompositeTypeImplemen
|
|||
values[i] = propertyTypes[i].deepCopy( values[i], factory );
|
||||
}
|
||||
|
||||
final EmbeddableInstantiator instantiator = mappingModelPart.getEmbeddableTypeDescriptor()
|
||||
.getRepresentationStrategy()
|
||||
.getInstantiator();
|
||||
Object result = instantiator.instantiate( () -> values, factory );
|
||||
Object result = instantiator().instantiate( () -> values, factory );
|
||||
|
||||
//not absolutely necessary, but helps for some
|
||||
//equals()/hashCode() implementations
|
||||
|
@ -530,10 +529,7 @@ public class ComponentType extends AbstractType implements CompositeTypeImplemen
|
|||
Object owner,
|
||||
Map<Object, Object> copyCache) {
|
||||
|
||||
if ( !isMutable() ) {
|
||||
return original;
|
||||
}
|
||||
if ( original == null ) {
|
||||
if ( original == null && target == null ) {
|
||||
return null;
|
||||
}
|
||||
if ( compositeUserType != null ) {
|
||||
|
@ -542,15 +538,7 @@ public class ComponentType extends AbstractType implements CompositeTypeImplemen
|
|||
//if ( original == target ) return target;
|
||||
|
||||
final Object[] originalValues = getPropertyValues( original );
|
||||
final Object[] resultValues;
|
||||
|
||||
if ( target == null ) {
|
||||
resultValues = new Object[originalValues.length];
|
||||
}
|
||||
else {
|
||||
resultValues = getPropertyValues( target );
|
||||
}
|
||||
|
||||
final Object[] resultValues = getPropertyValues( target );
|
||||
final Object[] replacedValues = TypeHelper.replace(
|
||||
originalValues,
|
||||
resultValues,
|
||||
|
@ -560,11 +548,8 @@ public class ComponentType extends AbstractType implements CompositeTypeImplemen
|
|||
copyCache
|
||||
);
|
||||
|
||||
if ( target == null ) {
|
||||
final EmbeddableInstantiator instantiator = mappingModelPart.getEmbeddableTypeDescriptor()
|
||||
.getRepresentationStrategy()
|
||||
.getInstantiator();
|
||||
return instantiator.instantiate( () -> replacedValues, session.getSessionFactory() );
|
||||
if ( target == null || !isMutable() ) {
|
||||
return instantiator().instantiate( () -> replacedValues, session.getSessionFactory() );
|
||||
}
|
||||
else {
|
||||
setPropertyValues( target, replacedValues );
|
||||
|
@ -581,10 +566,7 @@ public class ComponentType extends AbstractType implements CompositeTypeImplemen
|
|||
Map<Object, Object> copyCache,
|
||||
ForeignKeyDirection foreignKeyDirection) {
|
||||
|
||||
if ( !isMutable() ) {
|
||||
return original;
|
||||
}
|
||||
if ( original == null ) {
|
||||
if ( original == null && target == null ) {
|
||||
return null;
|
||||
}
|
||||
if ( compositeUserType != null ) {
|
||||
|
@ -594,15 +576,7 @@ public class ComponentType extends AbstractType implements CompositeTypeImplemen
|
|||
|
||||
|
||||
final Object[] originalValues = getPropertyValues( original );
|
||||
final Object[] resultValues;
|
||||
|
||||
if ( target == null ) {
|
||||
resultValues = new Object[originalValues.length];
|
||||
}
|
||||
else {
|
||||
resultValues = getPropertyValues( target );
|
||||
}
|
||||
|
||||
final Object[] resultValues = getPropertyValues( target );
|
||||
final Object[] replacedValues = TypeHelper.replace(
|
||||
originalValues,
|
||||
resultValues,
|
||||
|
@ -613,11 +587,8 @@ public class ComponentType extends AbstractType implements CompositeTypeImplemen
|
|||
foreignKeyDirection
|
||||
);
|
||||
|
||||
if ( target == null ) {
|
||||
final EmbeddableInstantiator instantiator = mappingModelPart.getEmbeddableTypeDescriptor()
|
||||
.getRepresentationStrategy()
|
||||
.getInstantiator();
|
||||
return instantiator.instantiate( () -> replacedValues, session.getSessionFactory() );
|
||||
if ( target == null || !isMutable() ) {
|
||||
return instantiator().instantiate( () -> replacedValues, session.getSessionFactory() );
|
||||
}
|
||||
else {
|
||||
setPropertyValues( target, replacedValues );
|
||||
|
@ -688,10 +659,7 @@ public class ComponentType extends AbstractType implements CompositeTypeImplemen
|
|||
assembled[i] = propertyTypes[i].assemble( (Serializable) values[i], session, owner );
|
||||
}
|
||||
|
||||
final EmbeddableInstantiator instantiator = mappingModelPart.getEmbeddableTypeDescriptor()
|
||||
.getRepresentationStrategy()
|
||||
.getInstantiator();
|
||||
final Object instance = instantiator.instantiate( () -> assembled, session.getFactory() );
|
||||
final Object instance = instantiator().instantiate( () -> assembled, session.getFactory() );
|
||||
|
||||
final PropertyAccess parentInjectionAccess = mappingModelPart.getParentInjectionAttributePropertyAccess();
|
||||
if ( parentInjectionAccess != null ) {
|
||||
|
@ -830,10 +798,15 @@ public class ComponentType extends AbstractType implements CompositeTypeImplemen
|
|||
}
|
||||
|
||||
private Object resolve(Object[] value, SharedSessionContractImplementor session) throws HibernateException {
|
||||
final EmbeddableInstantiator instantiator = mappingModelPart.getEmbeddableTypeDescriptor()
|
||||
.getRepresentationStrategy()
|
||||
.getInstantiator();
|
||||
return instantiator.instantiate( () -> value, session.getFactory() );
|
||||
return instantiator().instantiate( () -> value, session.getFactory() );
|
||||
}
|
||||
|
||||
private EmbeddableMappingType embeddableTypeDescriptor() {
|
||||
return mappingModelPart.getEmbeddableTypeDescriptor();
|
||||
}
|
||||
|
||||
protected final EmbeddableInstantiator instantiator() {
|
||||
return embeddableTypeDescriptor().getRepresentationStrategy().getInstantiator();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -213,7 +213,7 @@ public class TypeHelper {
|
|||
);
|
||||
if ( target[i] != null && compositeType instanceof ComponentType ) {
|
||||
final ComponentType componentType = (ComponentType) compositeType;
|
||||
if ( componentType.isCompositeUserType() ) {
|
||||
if ( !componentType.isMutable() || componentType.isCompositeUserType() ) {
|
||||
final EmbeddableInstantiator instantiator = ( (ComponentType) compositeType ).getMappingModelPart()
|
||||
.getEmbeddableTypeDescriptor()
|
||||
.getRepresentationStrategy()
|
||||
|
|
|
@ -177,7 +177,7 @@ public class EntityWithBidirectionalAssociationsOneOfWhichIsAJoinTableTest {
|
|||
.setParameter( "id", 1 )
|
||||
.getSingleResult();
|
||||
statementInspector.assertExecutedCount( 2 );
|
||||
statementInspector.assertNumberOfOccurrenceInQuery( 0, "join", 2 );
|
||||
statementInspector.assertNumberOfOccurrenceInQuery( 0, "join", 1 );
|
||||
statementInspector.assertNumberOfOccurrenceInQuery( 1, "join", 3 );
|
||||
Male son = parent.getSon();
|
||||
assertThat( son, CoreMatchers.notNullValue() );
|
||||
|
|
|
@ -4,14 +4,17 @@ import org.hibernate.testing.orm.junit.DomainModel;
|
|||
import org.hibernate.testing.orm.junit.JiraKey;
|
||||
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jakarta.persistence.Embeddable;
|
||||
import jakarta.persistence.Embedded;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.FetchType;
|
||||
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 = {
|
||||
MergeRecordPropertyTestCase.MyEntity.class
|
||||
|
@ -20,6 +23,16 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
|||
@JiraKey("HHH-16759")
|
||||
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
|
||||
public void mergeDetached(SessionFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
|
@ -38,24 +51,53 @@ public class MergeRecordPropertyTestCase {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void mergeTransient(SessionFactoryScope scope) {
|
||||
public void mergeDetachedNullComponent(SessionFactoryScope scope) {
|
||||
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(
|
||||
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( "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
|
||||
public void mergePersistent(SessionFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
session -> {
|
||||
final MyEntity entity = new MyEntity( 3L, new MyRecord( "test", "efg" ) );
|
||||
final MyEntity entity = new MyEntity( 1L, new MyRecord( "test", "efg" ) );
|
||||
session.persist( entity );
|
||||
entity.setRecord( new MyRecord( "test", "h" ) );
|
||||
session.merge( entity );
|
||||
|
@ -63,13 +105,41 @@ public class MergeRecordPropertyTestCase {
|
|||
);
|
||||
scope.inSession(
|
||||
session -> {
|
||||
final MyEntity entity = session.find( MyEntity.class, 3L );
|
||||
final MyEntity entity = session.find( MyEntity.class, 1L );
|
||||
assertEquals( "test", entity.record.name );
|
||||
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")
|
||||
public static class MyEntity {
|
||||
@Id
|
||||
|
@ -80,6 +150,10 @@ public class MergeRecordPropertyTestCase {
|
|||
public MyEntity() {
|
||||
}
|
||||
|
||||
public MyEntity(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public MyEntity(Long id, MyRecord record) {
|
||||
this.id = id;
|
||||
this.record = record;
|
||||
|
@ -103,6 +177,9 @@ public class MergeRecordPropertyTestCase {
|
|||
}
|
||||
|
||||
@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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue