Introduce `VirtualIdEmbeddable` and `IdClassEmbeddable` + instantiators

Prep work for EmbeddableInstantiator - initializer

Still need to
  - integrate EmbeddableInstantiator work
  - integrate embedded forms.  `VirtualIdEmbeddable` does not really need it as it can use the id-mapping itself as the embedded form.  But `IdClassEmbedded` should really be integrated
  - integrate `VirtualKeyEmbeddable` and `VirtualKeyEmbedded` for use as inverse composite fks
  - share `#finishInit` handling for `EmbeddableMappingType`, `VirtualIdEmbeddable` and `IdClassEmbeddable`
  - ability to use the containing composite owner as the parent of a composite (legacy behavior is to always use the "first" entity
This commit is contained in:
Steve Ebersole 2021-11-30 07:14:52 -06:00
parent 185bcd43aa
commit 01c6b144ce
1 changed files with 105 additions and 59 deletions

View File

@ -9,6 +9,7 @@ package org.hibernate.sql.results.graph.embeddable;
import java.util.List;
import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.metamodel.mapping.AttributeMapping;
import org.hibernate.metamodel.mapping.CompositeIdentifierMapping;
@ -45,13 +46,18 @@ public abstract class AbstractEmbeddableInitializer extends AbstractFetchParentA
private final NavigablePath navigablePath;
private final EmbeddableValuedModelPart embedded;
private final EmbeddableRepresentationStrategy representationStrategy;
private FetchParentAccess fetchParentAccess;
private final FetchParentAccess fetchParentAccess;
private final boolean createEmptyCompositesEnabled;
private final SessionFactoryImplementor sessionFactory;
private final List<DomainResultAssembler<?>> assemblers;
// per-row state
// NOTE : technically `resolvedValues` need not be instance state,
// but keeping it here allows for not creating arrays for each
// and every row
private final Object[] resolvedValues;
private final boolean createEmptyCompositesEnabled;
private Boolean allValuesNull;
private Object compositeInstance;
@ -97,6 +103,7 @@ public abstract class AbstractEmbeddableInitializer extends AbstractFetchParentA
&& !EntityIdentifierMapping.ROLE_LOCAL_NAME.equals( navigablePath.getLocalName() )
&& embeddableTypeDescriptor.isCreateEmptyCompositesEnabled();
sessionFactory = creationState.getSqlAstCreationContext().getSessionFactory();
}
@Override
@ -120,30 +127,7 @@ public abstract class AbstractEmbeddableInitializer extends AbstractFetchParentA
@Override
public void resolveKey(RowProcessingState rowProcessingState) {
if ( compositeInstance != null ) {
return;
}
final PropertyAccess parentInjectionPropertyAccess = embedded.getParentInjectionAttributePropertyAccess();
final FetchParentAccess fetchParentAccess = getFetchParentAccess();
if ( parentInjectionPropertyAccess != null && fetchParentAccess != null ) {
fetchParentAccess.findFirstEntityDescriptorAccess().registerResolutionListener(
// todo (6.0) : this is the legacy behavior
// - the first entity is injected as the parent, even if the composite
// is defined on another composite
owner -> {
if ( compositeInstance == null ) {
return;
}
parentInjectionPropertyAccess.getSetter().set(
compositeInstance,
owner,
rowProcessingState.getSession().getFactory()
);
}
);
}
// nothing to do
}
@Override
@ -169,7 +153,7 @@ public abstract class AbstractEmbeddableInitializer extends AbstractFetchParentA
if ( compositeInstance == null ) {
compositeInstance = representationStrategy
.getInstantiator()
.instantiate( null, rowProcessingState.getSession().getFactory() );
.instantiate( null, sessionFactory );
}
EmbeddableLoadingLogger.INSTANCE.debugf(
@ -179,33 +163,10 @@ public abstract class AbstractEmbeddableInitializer extends AbstractFetchParentA
}
@Override
public void initializeInstance(RowProcessingState rowProcessingState) {
final PropertyAccess parentInjectionPropertyAccess = embedded.getParentInjectionAttributePropertyAccess();
final Initializer initializer = rowProcessingState.resolveInitializer( navigablePath.getParent() );
public void initializeInstance(RowProcessingState processingState) {
final Initializer initializer = processingState.resolveInitializer( navigablePath.getParent() );
if ( parentInjectionPropertyAccess != null ) {
final Object owner;
if ( initializer instanceof CollectionInitializer ) {
owner = ( (CollectionInitializer) initializer ).getCollectionInstance().getOwner();
}
else if ( initializer instanceof EntityInitializer ) {
owner = ( (EntityInitializer) initializer ).getEntityInstance();
parentInjectionPropertyAccess.getSetter().set(
compositeInstance,
owner,
rowProcessingState.getSession().getFactory()
);
}
else {
throw new NotYetImplementedFor6Exception( getClass() );
}
parentInjectionPropertyAccess.getSetter().set(
compositeInstance,
owner,
rowProcessingState.getSession().getFactory()
);
}
handleParentInjection( processingState );
EmbeddableLoadingLogger.INSTANCE.debugf(
"Initializing composite instance [%s]",
@ -216,8 +177,8 @@ public abstract class AbstractEmbeddableInitializer extends AbstractFetchParentA
for ( int i = 0; i < assemblers.size(); i++ ) {
final DomainResultAssembler<?> assembler = assemblers.get( i );
final Object contributorValue = assembler.assemble(
rowProcessingState,
rowProcessingState.getJdbcValuesSourceProcessingState().getProcessingOptions()
processingState,
processingState.getJdbcValuesSourceProcessingState().getProcessingOptions()
);
resolvedValues[i] = contributorValue;
@ -234,14 +195,14 @@ public abstract class AbstractEmbeddableInitializer extends AbstractFetchParentA
if ( compositeInstance instanceof HibernateProxy ) {
if ( initializer != this ) {
( (AbstractEntityInitializer) initializer ).registerResolutionListener(
entity -> setPropertyValuesOnTarget( entity, rowProcessingState.getSession() )
entity -> setPropertyValuesOnTarget( entity, processingState.getSession() )
);
}
else {
Object target = representationStrategy
.getInstantiator()
.instantiate( null, rowProcessingState.getSession().getFactory() );
setPropertyValuesOnTarget( target, rowProcessingState.getSession() );
.instantiate( null, sessionFactory );
setPropertyValuesOnTarget( target, processingState.getSession() );
( (HibernateProxy) compositeInstance ).getHibernateLazyInitializer().setImplementation( target );
}
}
@ -254,10 +215,95 @@ public abstract class AbstractEmbeddableInitializer extends AbstractFetchParentA
// but that might cause unexpected outcomes for Hibernate 5 users that use createEmptyCompositesEnabled when updating.
// You can see the need for this by running EmptyCompositeEquivalentToNullTest
else if ( !areAllValuesNull ) {
setPropertyValuesOnTarget( compositeInstance, rowProcessingState.getSession() );
setPropertyValuesOnTarget( compositeInstance, processingState.getSession() );
}
}
}
private void handleParentInjection(RowProcessingState processingState) {
final PropertyAccess parentInjectionAccess = embedded.getParentInjectionAttributePropertyAccess();
if ( parentInjectionAccess == null ) {
// embeddable defined no parent injection
return;
}
// todo (6.0) : should we initialize the composite instance if we get here and it is null (not NULL_MARKER)?
// we want to avoid injection for `NULL_MARKER`
final Object compositeInstance = getCompositeInstance();
if ( compositeInstance == null ) {
EmbeddableLoadingLogger.INSTANCE.debugf(
"Skipping parent injection for null embeddable [%s]",
navigablePath
);
return;
}
final Object parent = determineParentInstance( processingState );
if ( parent == null ) {
EmbeddableLoadingLogger.INSTANCE.debugf(
"Unable to determine parent for injection into embeddable [%s]",
navigablePath
);
return;
}
EmbeddableLoadingLogger.INSTANCE.debugf(
"Injecting parent into embeddable [%s] : `%s` -> `%s`",
navigablePath,
parent,
compositeInstance
);
parentInjectionAccess.getSetter().set(
compositeInstance,
parent,
sessionFactory
);
}
private Object determineParentInstance(RowProcessingState processingState) {
// use `fetchParentAccess` if it is available - it is more efficient
// and the correct way to do it.
// NOTE: indicates that we are initializing a DomainResult as opposed to a Fetch
// todo (6.0) - this^^ does not work atm when the embeddable is the key or
// element of a collection because it passes in null as the fetch-parent-access.
// it should really pass the collection-initializer as the fetch-parent,
// or at least the fetch-parent of the collection could get passed.
if ( fetchParentAccess != null ) {
// the embeddable being initialized is a fetch, so use the fetchParentAccess
// to get the parent reference
//
// at the moment, this uses the legacy behavior of injecting the "first
// containing entity" as the parent. however,
// todo (6.x) - allow injection of containing composite as parent if
// it is the direct parent
final FetchParentAccess firstEntityDescriptorAccess = fetchParentAccess.findFirstEntityDescriptorAccess();
return firstEntityDescriptorAccess.getInitializedInstance();
}
// Otherwise, fallback to determining the parent-initializer by path
// todo (6.0) - this is the part that should be "subsumed" based on the
// comment above
final NavigablePath parentPath = navigablePath.getParent();
if ( parentPath == null ) {
return null;
}
final Initializer parentInitializer = processingState.resolveInitializer( parentPath );
if ( parentInitializer instanceof CollectionInitializer ) {
return ( (CollectionInitializer) parentInitializer ).getCollectionInstance().getOwner();
}
if ( parentInitializer instanceof EntityInitializer ) {
return ( (EntityInitializer) parentInitializer ).getEntityInstance();
}
throw new NotYetImplementedFor6Exception( getClass() );
}
private void setPropertyValuesOnTarget(Object compositeInstance, SharedSessionContractImplementor session) {
final EmbeddableMappingType embeddableTypeDescriptor;