HHH-14950 - Support mapping of embeddables with no setters w/ custom instantiator

HHH-14964 - EmbeddableInitializer are called multiple times
This commit is contained in:
Steve Ebersole 2021-12-10 14:39:51 -06:00
parent 1e4f1fef44
commit 64af4885b9
3 changed files with 36 additions and 3 deletions

View File

@ -12,6 +12,7 @@ import java.util.function.Supplier;
import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.metamodel.internal.StandardEmbeddableInstantiator;
import org.hibernate.metamodel.mapping.AttributeMapping;
import org.hibernate.metamodel.mapping.CompositeIdentifierMapping;
import org.hibernate.metamodel.mapping.EmbeddableMappingType;
@ -62,6 +63,8 @@ public abstract class AbstractEmbeddableInitializer extends AbstractFetchParentA
private final List<DomainResultAssembler<?>> assemblers;
private final boolean usesStandardInstatiation;
// per-row state
private final Object[] rowState;
private Boolean stateAllNull;
@ -86,7 +89,8 @@ public abstract class AbstractEmbeddableInitializer extends AbstractFetchParentA
representationEmbeddable = embeddableTypeDescriptor;
}
representationStrategy = representationEmbeddable.getRepresentationStrategy();
this.representationStrategy = representationEmbeddable.getRepresentationStrategy();
this.usesStandardInstatiation = representationStrategy.getInstantiator() instanceof StandardEmbeddableInstantiator;
final int numOfAttrs = embeddableTypeDescriptor.getNumberOfAttributeMappings();
this.rowState = new Object[ numOfAttrs ];
@ -151,6 +155,37 @@ public abstract class AbstractEmbeddableInitializer extends AbstractFetchParentA
public void initializeInstance(RowProcessingState processingState) {
EmbeddableLoadingLogger.INSTANCE.debugf( "Initializing composite instance [%s]", navigablePath );
if ( compositeInstance == NULL_MARKER ) {
// we already know it is null
return;
}
// IMPORTANT: This method might be called multiple times for the same role for a single row.
// EmbeddableAssembler calls it as part of its `#assemble` and the RowReader calls it
// as part of its normal Initializer handling
//
// Unfortunately, as currently structured, we need this double call mainly to handle
// the case composite keys, especially those with key-m-1 refs.
//
// When we are processing a non-key embeddable, all initialization happens in
// the first call, and we can safely ignore the second call.
//
// When we are processing a composite key, we really need to react to both calls.
//
// Unfortunately, atm, because we reuse `EmbeddedAttributeMapping` in a few of these
// foreign-key scenarios, we cannot easily tell when one models a key or not.
//
// While this is "important" to be able to avoid extra handling, it is really only
// critical in the case we have custom constructor injection. Luckily, custom instantiation
// is only allowed for non-key usage atm, so we leverage that distinction here
if ( ! usesStandardInstatiation ) {
// we have a custom instantiator
if ( compositeInstance != null ) {
return;
}
}
stateInjected = false;
extractRowState( processingState );

View File

@ -67,7 +67,6 @@ public class InstantiationTests {
}
@Test
@FailureExpected( jiraKey = "HHH-14964", reason = "EmbeddableInitializer is called twice, causing problems" )
public void basicTest(SessionFactoryScope scope) {
scope.inTransaction( (session) -> {
final Person mick = new Person( 1, new NameImpl( "Mick", "Jagger" ) );

View File

@ -70,7 +70,6 @@ public class InstantiationTests {
}
@Test
@FailureExpected( jiraKey = "HHH-14964", reason = "EmbeddableInitializer is called twice, causing problems" )
public void basicTest(SessionFactoryScope scope) {
scope.inTransaction( (session) -> {
final Person mick = new Person( 1, Name.make( "Mick", "Jagger" ) );