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:
parent
185bcd43aa
commit
01c6b144ce
|
@ -9,6 +9,7 @@ package org.hibernate.sql.results.graph.embeddable;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.hibernate.NotYetImplementedFor6Exception;
|
import org.hibernate.NotYetImplementedFor6Exception;
|
||||||
|
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||||
import org.hibernate.metamodel.mapping.AttributeMapping;
|
import org.hibernate.metamodel.mapping.AttributeMapping;
|
||||||
import org.hibernate.metamodel.mapping.CompositeIdentifierMapping;
|
import org.hibernate.metamodel.mapping.CompositeIdentifierMapping;
|
||||||
|
@ -45,13 +46,18 @@ public abstract class AbstractEmbeddableInitializer extends AbstractFetchParentA
|
||||||
private final NavigablePath navigablePath;
|
private final NavigablePath navigablePath;
|
||||||
private final EmbeddableValuedModelPart embedded;
|
private final EmbeddableValuedModelPart embedded;
|
||||||
private final EmbeddableRepresentationStrategy representationStrategy;
|
private final EmbeddableRepresentationStrategy representationStrategy;
|
||||||
private FetchParentAccess fetchParentAccess;
|
private final FetchParentAccess fetchParentAccess;
|
||||||
|
private final boolean createEmptyCompositesEnabled;
|
||||||
|
private final SessionFactoryImplementor sessionFactory;
|
||||||
|
|
||||||
private final List<DomainResultAssembler<?>> assemblers;
|
private final List<DomainResultAssembler<?>> assemblers;
|
||||||
|
|
||||||
// per-row state
|
// 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 Object[] resolvedValues;
|
||||||
private final boolean createEmptyCompositesEnabled;
|
private Boolean allValuesNull;
|
||||||
private Object compositeInstance;
|
private Object compositeInstance;
|
||||||
|
|
||||||
|
|
||||||
|
@ -97,6 +103,7 @@ public abstract class AbstractEmbeddableInitializer extends AbstractFetchParentA
|
||||||
&& !EntityIdentifierMapping.ROLE_LOCAL_NAME.equals( navigablePath.getLocalName() )
|
&& !EntityIdentifierMapping.ROLE_LOCAL_NAME.equals( navigablePath.getLocalName() )
|
||||||
&& embeddableTypeDescriptor.isCreateEmptyCompositesEnabled();
|
&& embeddableTypeDescriptor.isCreateEmptyCompositesEnabled();
|
||||||
|
|
||||||
|
sessionFactory = creationState.getSqlAstCreationContext().getSessionFactory();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -120,30 +127,7 @@ public abstract class AbstractEmbeddableInitializer extends AbstractFetchParentA
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void resolveKey(RowProcessingState rowProcessingState) {
|
public void resolveKey(RowProcessingState rowProcessingState) {
|
||||||
if ( compositeInstance != null ) {
|
// nothing to do
|
||||||
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()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -169,7 +153,7 @@ public abstract class AbstractEmbeddableInitializer extends AbstractFetchParentA
|
||||||
if ( compositeInstance == null ) {
|
if ( compositeInstance == null ) {
|
||||||
compositeInstance = representationStrategy
|
compositeInstance = representationStrategy
|
||||||
.getInstantiator()
|
.getInstantiator()
|
||||||
.instantiate( null, rowProcessingState.getSession().getFactory() );
|
.instantiate( null, sessionFactory );
|
||||||
}
|
}
|
||||||
|
|
||||||
EmbeddableLoadingLogger.INSTANCE.debugf(
|
EmbeddableLoadingLogger.INSTANCE.debugf(
|
||||||
|
@ -179,33 +163,10 @@ public abstract class AbstractEmbeddableInitializer extends AbstractFetchParentA
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void initializeInstance(RowProcessingState rowProcessingState) {
|
public void initializeInstance(RowProcessingState processingState) {
|
||||||
final PropertyAccess parentInjectionPropertyAccess = embedded.getParentInjectionAttributePropertyAccess();
|
final Initializer initializer = processingState.resolveInitializer( navigablePath.getParent() );
|
||||||
final Initializer initializer = rowProcessingState.resolveInitializer( navigablePath.getParent() );
|
|
||||||
|
|
||||||
if ( parentInjectionPropertyAccess != null ) {
|
handleParentInjection( processingState );
|
||||||
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()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
EmbeddableLoadingLogger.INSTANCE.debugf(
|
EmbeddableLoadingLogger.INSTANCE.debugf(
|
||||||
"Initializing composite instance [%s]",
|
"Initializing composite instance [%s]",
|
||||||
|
@ -216,8 +177,8 @@ public abstract class AbstractEmbeddableInitializer extends AbstractFetchParentA
|
||||||
for ( int i = 0; i < assemblers.size(); i++ ) {
|
for ( int i = 0; i < assemblers.size(); i++ ) {
|
||||||
final DomainResultAssembler<?> assembler = assemblers.get( i );
|
final DomainResultAssembler<?> assembler = assemblers.get( i );
|
||||||
final Object contributorValue = assembler.assemble(
|
final Object contributorValue = assembler.assemble(
|
||||||
rowProcessingState,
|
processingState,
|
||||||
rowProcessingState.getJdbcValuesSourceProcessingState().getProcessingOptions()
|
processingState.getJdbcValuesSourceProcessingState().getProcessingOptions()
|
||||||
);
|
);
|
||||||
|
|
||||||
resolvedValues[i] = contributorValue;
|
resolvedValues[i] = contributorValue;
|
||||||
|
@ -234,14 +195,14 @@ public abstract class AbstractEmbeddableInitializer extends AbstractFetchParentA
|
||||||
if ( compositeInstance instanceof HibernateProxy ) {
|
if ( compositeInstance instanceof HibernateProxy ) {
|
||||||
if ( initializer != this ) {
|
if ( initializer != this ) {
|
||||||
( (AbstractEntityInitializer) initializer ).registerResolutionListener(
|
( (AbstractEntityInitializer) initializer ).registerResolutionListener(
|
||||||
entity -> setPropertyValuesOnTarget( entity, rowProcessingState.getSession() )
|
entity -> setPropertyValuesOnTarget( entity, processingState.getSession() )
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
Object target = representationStrategy
|
Object target = representationStrategy
|
||||||
.getInstantiator()
|
.getInstantiator()
|
||||||
.instantiate( null, rowProcessingState.getSession().getFactory() );
|
.instantiate( null, sessionFactory );
|
||||||
setPropertyValuesOnTarget( target, rowProcessingState.getSession() );
|
setPropertyValuesOnTarget( target, processingState.getSession() );
|
||||||
( (HibernateProxy) compositeInstance ).getHibernateLazyInitializer().setImplementation( target );
|
( (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.
|
// 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
|
// You can see the need for this by running EmptyCompositeEquivalentToNullTest
|
||||||
else if ( !areAllValuesNull ) {
|
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) {
|
private void setPropertyValuesOnTarget(Object compositeInstance, SharedSessionContractImplementor session) {
|
||||||
final EmbeddableMappingType embeddableTypeDescriptor;
|
final EmbeddableMappingType embeddableTypeDescriptor;
|
||||||
|
|
Loading…
Reference in New Issue