HHH-18131 Composite identifiers with associations stopped working with @IdClass

This commit is contained in:
Andrea Boriero 2024-09-25 17:18:35 +02:00 committed by Andrea Boriero
parent 3aae473eca
commit 80bbea4283
15 changed files with 105 additions and 24 deletions

View File

@ -164,7 +164,7 @@ public class DefaultMergeEventListener
final Object originalId; final Object originalId;
if ( entry == null ) { if ( entry == null ) {
final EntityPersister persister = source.getEntityPersister( event.getEntityName(), entity ); final EntityPersister persister = source.getEntityPersister( event.getEntityName(), entity );
originalId = persister.getIdentifier( entity, source ); originalId = persister.getIdentifier( entity, copiedAlready );
if ( originalId != null ) { if ( originalId != null ) {
final EntityKey entityKey; final EntityKey entityKey;
if ( persister.getIdentifierType() instanceof ComponentType ) { if ( persister.getIdentifierType() instanceof ComponentType ) {

View File

@ -366,4 +366,8 @@ public class MergeContext implements Map<Object,Object> {
// Entity was not found in current persistence context. Use Object#toString() method. // Entity was not found in current persistence context. Use Object#toString() method.
return "[" + entity + "]"; return "[" + entity + "]";
} }
public EventSource getEventSource() {
return session;
}
} }

View File

@ -15,6 +15,7 @@ import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer;
import org.hibernate.engine.spi.EntityHolder; import org.hibernate.engine.spi.EntityHolder;
import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.EntityKey;
import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.engine.spi.TypedValue; import org.hibernate.engine.spi.TypedValue;
import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.CoreMessageLogger;
@ -53,7 +54,7 @@ public final class EntityPrinter {
result.put( result.put(
entityPersister.getIdentifierPropertyName(), entityPersister.getIdentifierPropertyName(),
entityPersister.getIdentifierType().toLoggableString( entityPersister.getIdentifierType().toLoggableString(
entityPersister.getIdentifier( entity, null ), entityPersister.getIdentifier( entity, (SharedSessionContractImplementor) null ),
factory factory
) )
); );

View File

@ -13,6 +13,7 @@ import org.hibernate.Hibernate;
import org.hibernate.MappingException; import org.hibernate.MappingException;
import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.EntityEntry;
import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.jpa.internal.util.PersistenceUtilHelper; import org.hibernate.jpa.internal.util.PersistenceUtilHelper;
import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.proxy.LazyInitializer; import org.hibernate.proxy.LazyInitializer;
@ -106,7 +107,7 @@ public class PersistenceUnitUtilImpl implements PersistenceUnitUtil, Serializabl
catch (MappingException ex) { catch (MappingException ex) {
throw new IllegalArgumentException( entityClass.getName() + " is not an entity", ex ); throw new IllegalArgumentException( entityClass.getName() + " is not an entity", ex );
} }
return persister.getIdentifier( entity, null ); return persister.getIdentifier( entity, (SharedSessionContractImplementor) null );
} }
} }

View File

@ -10,6 +10,7 @@ import org.hibernate.TransientObjectException;
import org.hibernate.engine.internal.ForeignKeys; import org.hibernate.engine.internal.ForeignKeys;
import org.hibernate.engine.spi.IdentifierValue; import org.hibernate.engine.spi.IdentifierValue;
import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.event.spi.MergeContext;
import jakarta.persistence.EmbeddedId; import jakarta.persistence.EmbeddedId;
import jakarta.persistence.Id; import jakarta.persistence.Id;
@ -68,6 +69,15 @@ public interface EntityIdentifierMapping extends ValuedModelPart {
*/ */
Object getIdentifier(Object entity); Object getIdentifier(Object entity);
/**
* Extract the identifier from an instance of the entity
*
* It's supposed to be use during the merging process
*/
default Object getIdentifier(Object entity, MergeContext mergeContext){
return getIdentifier( entity );
}
/** /**
* Return the identifier of the persistent or transient object, or throw * Return the identifier of the persistent or transient object, or throw
* an exception if the instance is "unsaved" * an exception if the instance is "unsaved"

View File

@ -14,6 +14,7 @@ import org.hibernate.engine.spi.EntityHolder;
import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.EntityKey;
import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.PersistenceContext;
import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.event.spi.MergeContext;
import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.metamodel.mapping.AttributeMapping; import org.hibernate.metamodel.mapping.AttributeMapping;
import org.hibernate.metamodel.mapping.EmbeddableMappingType; import org.hibernate.metamodel.mapping.EmbeddableMappingType;
@ -196,6 +197,11 @@ public class InverseNonAggregatedIdentifierMapping extends EmbeddedAttributeMapp
@Override @Override
public Object getIdentifier(Object entity) { public Object getIdentifier(Object entity) {
return getIdentifier( entity, null );
}
@Override
public Object getIdentifier(Object entity, MergeContext mergeContext) {
if ( hasContainingClass() ) { if ( hasContainingClass() ) {
final Object id = identifierValueMapper.getRepresentationStrategy().getInstantiator().instantiate( final Object id = identifierValueMapper.getRepresentationStrategy().getInstantiator().instantiate(
null, null,
@ -218,16 +224,17 @@ public class InverseNonAggregatedIdentifierMapping extends EmbeddedAttributeMapp
//JPA 2 @MapsId + @IdClass points to the pk of the entity //JPA 2 @MapsId + @IdClass points to the pk of the entity
else if ( attributeMapping instanceof ToOneAttributeMapping else if ( attributeMapping instanceof ToOneAttributeMapping
&& !( identifierValueMapper.getAttributeMapping( i ) instanceof ToOneAttributeMapping ) ) { && !( identifierValueMapper.getAttributeMapping( i ) instanceof ToOneAttributeMapping ) ) {
final Object toOne = getIfMerged( o, mergeContext );
final ToOneAttributeMapping toOneAttributeMapping = (ToOneAttributeMapping) attributeMapping; final ToOneAttributeMapping toOneAttributeMapping = (ToOneAttributeMapping) attributeMapping;
final ModelPart targetPart = toOneAttributeMapping.getForeignKeyDescriptor().getPart( final ModelPart targetPart = toOneAttributeMapping.getForeignKeyDescriptor().getPart(
toOneAttributeMapping.getSideNature().inverse() toOneAttributeMapping.getSideNature().inverse()
); );
if ( targetPart.isEntityIdentifierMapping() ) { if ( targetPart.isEntityIdentifierMapping() ) {
propertyValues[i] = ( (EntityIdentifierMapping) targetPart ).getIdentifier( o ); propertyValues[i] = ( (EntityIdentifierMapping) targetPart )
.getIdentifier( toOne, mergeContext );
} }
else { else {
propertyValues[i] = o; propertyValues[i] = toOne;
assert false;
} }
} }
else { else {
@ -242,6 +249,16 @@ public class InverseNonAggregatedIdentifierMapping extends EmbeddedAttributeMapp
} }
} }
private static Object getIfMerged(Object o, MergeContext mergeContext) {
if ( mergeContext != null ) {
final Object merged = mergeContext.get( o );
if ( merged != null ) {
return merged;
}
}
return o;
}
@Override @Override
public void setIdentifier(Object entity, Object id, SharedSessionContractImplementor session) { public void setIdentifier(Object entity, Object id, SharedSessionContractImplementor session) {
final Object[] propertyValues = new Object[identifierValueMapper.getNumberOfAttributeMappings()]; final Object[] propertyValues = new Object[identifierValueMapper.getNumberOfAttributeMappings()];

View File

@ -15,6 +15,7 @@ import org.hibernate.engine.spi.EntityHolder;
import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.EntityKey;
import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.PersistenceContext;
import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.event.spi.MergeContext;
import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.mapping.Component; import org.hibernate.mapping.Component;
import org.hibernate.mapping.RootClass; import org.hibernate.mapping.RootClass;
@ -237,6 +238,11 @@ public class NonAggregatedIdentifierMappingImpl extends AbstractCompositeIdentif
@Override @Override
public Object getIdentifier(Object entity) { public Object getIdentifier(Object entity) {
return getIdentifier( entity, null );
}
@Override
public Object getIdentifier(Object entity, MergeContext mergeContext) {
if ( hasContainingClass() ) { if ( hasContainingClass() ) {
final LazyInitializer lazyInitializer = HibernateProxy.extractLazyInitializer( entity ); final LazyInitializer lazyInitializer = HibernateProxy.extractLazyInitializer( entity );
if ( lazyInitializer != null ) { if ( lazyInitializer != null ) {
@ -259,16 +265,16 @@ public class NonAggregatedIdentifierMappingImpl extends AbstractCompositeIdentif
//JPA 2 @MapsId + @IdClass points to the pk of the entity //JPA 2 @MapsId + @IdClass points to the pk of the entity
else if ( attributeMapping instanceof ToOneAttributeMapping else if ( attributeMapping instanceof ToOneAttributeMapping
&& !( identifierValueMapper.getAttributeMapping( i ) instanceof ToOneAttributeMapping ) ) { && !( identifierValueMapper.getAttributeMapping( i ) instanceof ToOneAttributeMapping ) ) {
final Object toOne = getIfMerged( o, mergeContext );
final ToOneAttributeMapping toOneAttributeMapping = (ToOneAttributeMapping) attributeMapping; final ToOneAttributeMapping toOneAttributeMapping = (ToOneAttributeMapping) attributeMapping;
final ModelPart targetPart = toOneAttributeMapping.getForeignKeyDescriptor().getPart( final ModelPart targetPart = toOneAttributeMapping.getForeignKeyDescriptor().getPart(
toOneAttributeMapping.getSideNature().inverse() toOneAttributeMapping.getSideNature().inverse()
); );
if ( targetPart.isEntityIdentifierMapping() ) { if ( targetPart.isEntityIdentifierMapping() ) {
propertyValues[i] = ( (EntityIdentifierMapping) targetPart ).getIdentifier( o ); propertyValues[i] = ( (EntityIdentifierMapping) targetPart ).getIdentifier( toOne, mergeContext );
} }
else { else {
propertyValues[i] = o; propertyValues[i] = toOne;
assert false;
} }
} }
else { else {
@ -285,6 +291,16 @@ public class NonAggregatedIdentifierMappingImpl extends AbstractCompositeIdentif
} }
} }
private static Object getIfMerged(Object o, MergeContext mergeContext) {
if ( mergeContext != null ) {
final Object merged = mergeContext.get( o );
if ( merged != null ) {
return merged;
}
}
return o;
}
@Override @Override
public void setIdentifier(Object entity, Object id, SharedSessionContractImplementor session) { public void setIdentifier(Object entity, Object id, SharedSessionContractImplementor session) {
final Object[] propertyValues = new Object[identifierValueMapper.getNumberOfAttributeMappings()]; final Object[] propertyValues = new Object[identifierValueMapper.getNumberOfAttributeMappings()];

View File

@ -100,6 +100,7 @@ import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.event.spi.EventSource; import org.hibernate.event.spi.EventSource;
import org.hibernate.event.spi.LoadEvent; import org.hibernate.event.spi.LoadEvent;
import org.hibernate.event.spi.MergeContext;
import org.hibernate.generator.BeforeExecutionGenerator; import org.hibernate.generator.BeforeExecutionGenerator;
import org.hibernate.generator.EventType; import org.hibernate.generator.EventType;
import org.hibernate.generator.Generator; import org.hibernate.generator.Generator;
@ -4724,6 +4725,11 @@ public abstract class AbstractEntityPersister
return identifierMapping.getIdentifier( entity ); return identifierMapping.getIdentifier( entity );
} }
@Override
public Object getIdentifier(Object entity, MergeContext mergeContext) {
return identifierMapping.getIdentifier( entity, mergeContext );
}
@Override @Override
public void setIdentifier(Object entity, Object id, SharedSessionContractImplementor session) { public void setIdentifier(Object entity, Object id, SharedSessionContractImplementor session) {
identifierMapping.setIdentifier( entity, id, session ); identifierMapping.setIdentifier( entity, id, session );

View File

@ -30,6 +30,7 @@ import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.event.spi.EventSource; import org.hibernate.event.spi.EventSource;
import org.hibernate.event.spi.MergeContext;
import org.hibernate.generator.BeforeExecutionGenerator; import org.hibernate.generator.BeforeExecutionGenerator;
import org.hibernate.generator.EventType; import org.hibernate.generator.EventType;
import org.hibernate.generator.Generator; import org.hibernate.generator.Generator;
@ -1130,8 +1131,18 @@ public interface EntityPersister extends EntityMappingType, EntityMutationTarget
*/ */
Object getIdentifier(Object entity, SharedSessionContractImplementor session); Object getIdentifier(Object entity, SharedSessionContractImplementor session);
/** /**
* Inject the identifier value into the given entity. * Get the identifier of an instance from the object's identifier property.
* Throw an exception if it has no identifier property.
*
* It's supposed to be use during the merging process
*/
default Object getIdentifier(Object entity, MergeContext mergeContext) {
return getIdentifier( entity, mergeContext.getEventSource() );
}
/**
* Inject the identifier value into the given entity.
*/ */
void setIdentifier(Object entity, Object id, SharedSessionContractImplementor session); void setIdentifier(Object entity, Object id, SharedSessionContractImplementor session);

View File

@ -9,6 +9,7 @@ package org.hibernate.query.derived;
import org.hibernate.Incubating; import org.hibernate.Incubating;
import org.hibernate.engine.spi.IdentifierValue; import org.hibernate.engine.spi.IdentifierValue;
import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.event.spi.MergeContext;
import org.hibernate.metamodel.mapping.BasicEntityIdentifierMapping; import org.hibernate.metamodel.mapping.BasicEntityIdentifierMapping;
import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.ManagedMappingType; import org.hibernate.metamodel.mapping.ManagedMappingType;
@ -51,6 +52,11 @@ public class AnonymousTupleBasicEntityIdentifierMapping
return delegate.getIdentifier( entity ); return delegate.getIdentifier( entity );
} }
@Override
public Object getIdentifier(Object entity, MergeContext mergeContext) {
return delegate.getIdentifier( entity, mergeContext );
}
@Override @Override
public void setIdentifier(Object entity, Object id, SharedSessionContractImplementor session) { public void setIdentifier(Object entity, Object id, SharedSessionContractImplementor session) {
delegate.setIdentifier( entity, id, session ); delegate.setIdentifier( entity, id, session );

View File

@ -12,6 +12,7 @@ import java.util.Set;
import org.hibernate.Incubating; import org.hibernate.Incubating;
import org.hibernate.engine.spi.IdentifierValue; import org.hibernate.engine.spi.IdentifierValue;
import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.event.spi.MergeContext;
import org.hibernate.metamodel.mapping.CompositeIdentifierMapping; import org.hibernate.metamodel.mapping.CompositeIdentifierMapping;
import org.hibernate.metamodel.mapping.EmbeddableMappingType; import org.hibernate.metamodel.mapping.EmbeddableMappingType;
import org.hibernate.metamodel.mapping.internal.SingleAttributeIdentifierMapping; import org.hibernate.metamodel.mapping.internal.SingleAttributeIdentifierMapping;
@ -70,6 +71,11 @@ public class AnonymousTupleEmbeddedEntityIdentifierMapping extends AnonymousTupl
return delegate.getIdentifier( entity ); return delegate.getIdentifier( entity );
} }
@Override
public Object getIdentifier(Object entity, MergeContext mergeContext) {
return delegate.getIdentifier( entity, mergeContext );
}
@Override @Override
public void setIdentifier(Object entity, Object id, SharedSessionContractImplementor session) { public void setIdentifier(Object entity, Object id, SharedSessionContractImplementor session) {
delegate.setIdentifier( entity, id, session ); delegate.setIdentifier( entity, id, session );

View File

@ -14,6 +14,7 @@ import org.hibernate.engine.FetchStyle;
import org.hibernate.engine.FetchTiming; import org.hibernate.engine.FetchTiming;
import org.hibernate.engine.spi.IdentifierValue; import org.hibernate.engine.spi.IdentifierValue;
import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.event.spi.MergeContext;
import org.hibernate.metamodel.mapping.EmbeddableMappingType; import org.hibernate.metamodel.mapping.EmbeddableMappingType;
import org.hibernate.metamodel.mapping.NonAggregatedIdentifierMapping; import org.hibernate.metamodel.mapping.NonAggregatedIdentifierMapping;
import org.hibernate.metamodel.mapping.internal.IdClassEmbeddable; import org.hibernate.metamodel.mapping.internal.IdClassEmbeddable;
@ -79,6 +80,12 @@ public class AnonymousTupleNonAggregatedEntityIdentifierMapping extends Anonymou
return delegate.getIdentifier( entity ); return delegate.getIdentifier( entity );
} }
@Override
public Object getIdentifier(Object entity, MergeContext mergeContext) {
return delegate.getIdentifier( entity, mergeContext );
}
@Override @Override
public void setIdentifier(Object entity, Object id, SharedSessionContractImplementor session) { public void setIdentifier(Object entity, Object id, SharedSessionContractImplementor session) {
delegate.setIdentifier( entity, id, session ); delegate.setIdentifier( entity, id, session );

View File

@ -149,7 +149,7 @@ public class AnyType extends AbstractType implements CompositeType, AssociationT
final EntityPersister concretePersister = guessEntityPersister( entity, factory ); final EntityPersister concretePersister = guessEntityPersister( entity, factory );
return concretePersister == null return concretePersister == null
? null ? null
: concretePersister.getIdentifier( entity, null ); : concretePersister.getIdentifier( entity, (SharedSessionContractImplementor) null );
} }
private EntityPersister guessEntityPersister(Object object, SessionFactoryImplementor factory) { private EntityPersister guessEntityPersister(Object object, SessionFactoryImplementor factory) {

View File

@ -284,7 +284,7 @@ public abstract class EntityType extends AbstractType implements AssociationType
final EntityPersister concretePersister = getAssociatedEntityPersister( factory ); final EntityPersister concretePersister = getAssociatedEntityPersister( factory );
return concretePersister == null return concretePersister == null
? null ? null
: concretePersister.getIdentifier( entity, null ); : concretePersister.getIdentifier( entity, (SharedSessionContractImplementor) null );
} }
@Override @Override
@ -353,7 +353,7 @@ public abstract class EntityType extends AbstractType implements AssociationType
else { else {
final Class<?> mappedClass = persister.getMappedClass(); final Class<?> mappedClass = persister.getMappedClass();
if ( mappedClass.isAssignableFrom( x.getClass() ) ) { if ( mappedClass.isAssignableFrom( x.getClass() ) ) {
id = persister.getIdentifier( x, null ); id = persister.getIdentifier( x, (SharedSessionContractImplementor) null );
} }
else { else {
id = x; id = x;
@ -387,7 +387,7 @@ public abstract class EntityType extends AbstractType implements AssociationType
} }
else { else {
if ( mappedClass.isAssignableFrom( x.getClass() ) ) { if ( mappedClass.isAssignableFrom( x.getClass() ) ) {
xid = persister.getIdentifier( x, null ); xid = persister.getIdentifier( x, (SharedSessionContractImplementor) null );
} }
else { else {
//JPA 2 case where @IdClass contains the id and not the associated entity //JPA 2 case where @IdClass contains the id and not the associated entity
@ -402,7 +402,7 @@ public abstract class EntityType extends AbstractType implements AssociationType
} }
else { else {
if ( mappedClass.isAssignableFrom( y.getClass() ) ) { if ( mappedClass.isAssignableFrom( y.getClass() ) ) {
yid = persister.getIdentifier( y, null ); yid = persister.getIdentifier( y, (SharedSessionContractImplementor) null );
} }
else { else {
//JPA 2 case where @IdClass contains the id and not the associated entity //JPA 2 case where @IdClass contains the id and not the associated entity
@ -564,7 +564,7 @@ public abstract class EntityType extends AbstractType implements AssociationType
id = lazyInitializer.getInternalIdentifier(); id = lazyInitializer.getInternalIdentifier();
} }
else { else {
id = persister.getIdentifier( value, null ); id = persister.getIdentifier( value, (SharedSessionContractImplementor) null );
} }
result.append( '#' ) result.append( '#' )

View File

@ -1,16 +1,12 @@
/* /*
* Hibernate, Relational Persistence for Idiomatic Java * SPDX-License-Identifier: LGPL-2.1-or-later
* * Copyright Red Hat Inc. and Hibernate Authors
* 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.engine.internal; package org.hibernate.engine.internal;
import java.util.List; import java.util.List;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.hibernate.cfg.AvailableSettings; import org.hibernate.cfg.AvailableSettings;
import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.CoreMessageLogger;
@ -183,7 +179,7 @@ public class StatisticalLoggingSessionEventListenerTest {
// Number of lines // Number of lines
assertThat( sessionMetricsLog.lines().count() ) assertThat( sessionMetricsLog.lines().count() )
.as( "The StatisticalLoggingSessionEventListener should write a line per metric (" .as( "The StatisticalLoggingSessionEventListener should write a line per metric ("
+ numberOfMetrics + " lines) plus a header and a footer (2 lines)" ) + numberOfMetrics + " lines) plus a header and a footer (2 lines)" )
.isEqualTo( numberOfMetrics + 2 ); .isEqualTo( numberOfMetrics + 2 );
// Total time // Total time
long sumDuration = metricList.stream().map( SessionMetric::getDuration ).mapToLong( Long::longValue ).sum(); long sumDuration = metricList.stream().map( SessionMetric::getDuration ).mapToLong( Long::longValue ).sum();