HHH-18489 Lazy, unowned one-to-one associations get loaded eagerly in queries - even with bytecode enhancement
This commit is contained in:
parent
b407aa7679
commit
2f2dbbe2e6
|
@ -34,8 +34,9 @@ public class LazyAttributeLoadingInterceptor extends AbstractLazyLoadInterceptor
|
|||
private final Object identifier;
|
||||
|
||||
//N.B. this Set needs to be treated as immutable
|
||||
private final Set<String> lazyFields;
|
||||
private Set<String> lazyFields;
|
||||
private Set<String> initializedLazyFields;
|
||||
private Set<String> mutableLazyFields;
|
||||
|
||||
public LazyAttributeLoadingInterceptor(
|
||||
String entityName,
|
||||
|
@ -193,4 +194,11 @@ public class LazyAttributeLoadingInterceptor extends AbstractLazyLoadInterceptor
|
|||
return initializedLazyFields == null ? Collections.emptySet() : initializedLazyFields;
|
||||
}
|
||||
|
||||
public void addLazyFieldByGraph(String fieldName) {
|
||||
if ( mutableLazyFields == null ) {
|
||||
mutableLazyFields = new HashSet<>( lazyFields );
|
||||
lazyFields = mutableLazyFields;
|
||||
}
|
||||
mutableLazyFields.add( fieldName );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -378,6 +378,7 @@ public abstract class AbstractEntityPersister
|
|||
private final String[] lazyPropertyNames;
|
||||
private final int[] lazyPropertyNumbers;
|
||||
private final Type[] lazyPropertyTypes;
|
||||
private final Set<String> nonLazyPropertyNames;
|
||||
|
||||
//information about all properties in class hierarchy
|
||||
private final String[] subclassPropertyNameClosure;
|
||||
|
@ -469,6 +470,7 @@ public abstract class AbstractEntityPersister
|
|||
private final boolean implementsLifecycle;
|
||||
|
||||
private List<UniqueKeyEntry> uniqueKeyEntries = null; //lazily initialized
|
||||
private HashMap<String,SingleIdArrayLoadPlan> nonLazyPropertyLoadPlansByName;
|
||||
|
||||
public AbstractEntityPersister(
|
||||
final PersistentClass persistentClass,
|
||||
|
@ -606,6 +608,7 @@ public abstract class AbstractEntityPersister
|
|||
propertyColumnUpdateable = new boolean[hydrateSpan][];
|
||||
propertyColumnInsertable = new boolean[hydrateSpan][];
|
||||
sharedColumnNames = new HashSet<>();
|
||||
nonLazyPropertyNames = new HashSet<>();
|
||||
|
||||
final HashSet<Property> thisClassProperties = new HashSet<>();
|
||||
final ArrayList<String> lazyNames = new ArrayList<>();
|
||||
|
@ -663,6 +666,9 @@ public abstract class AbstractEntityPersister
|
|||
lazyNumbers.add( i );
|
||||
lazyTypes.add( prop.getValue().getType() );
|
||||
}
|
||||
else {
|
||||
nonLazyPropertyNames.add( prop.getName() );
|
||||
}
|
||||
|
||||
propertyColumnUpdateable[i] = prop.getValue().getColumnUpdateability();
|
||||
propertyColumnInsertable[i] = prop.getValue().getColumnInsertability();
|
||||
|
@ -1222,6 +1228,10 @@ public abstract class AbstractEntityPersister
|
|||
partsToSelect.add( getAttributeMapping( getSubclassPropertyIndex( lazyAttributeDescriptor.getName() ) ) );
|
||||
}
|
||||
|
||||
return createLazyLoanPlan( partsToSelect );
|
||||
}
|
||||
|
||||
private SingleIdArrayLoadPlan createLazyLoanPlan(List<ModelPart> partsToSelect) {
|
||||
if ( partsToSelect.isEmpty() ) {
|
||||
// only one-to-one is lazily fetched
|
||||
return null;
|
||||
|
@ -1542,75 +1552,117 @@ public abstract class AbstractEntityPersister
|
|||
final EntityEntry entry,
|
||||
final String fieldName,
|
||||
final SharedSessionContractImplementor session) {
|
||||
|
||||
if ( !hasLazyProperties() ) {
|
||||
throw new AssertionFailure( "no lazy properties" );
|
||||
}
|
||||
|
||||
final PersistentAttributeInterceptor interceptor = asPersistentAttributeInterceptable( entity ).$$_hibernate_getInterceptor();
|
||||
assert interceptor != null : "Expecting bytecode interceptor to be non-null";
|
||||
|
||||
LOG.tracef( "Initializing lazy properties from datastore (triggered for `%s`)", fieldName );
|
||||
|
||||
final String fetchGroup = getEntityMetamodel().getBytecodeEnhancementMetadata()
|
||||
.getLazyAttributesMetadata()
|
||||
.getFetchGroupName( fieldName );
|
||||
final List<LazyAttributeDescriptor> fetchGroupAttributeDescriptors = getEntityMetamodel().getBytecodeEnhancementMetadata()
|
||||
.getLazyAttributesMetadata()
|
||||
.getFetchGroupAttributeDescriptors( fetchGroup );
|
||||
|
||||
final Set<String> initializedLazyAttributeNames = interceptor.getInitializedLazyAttributeNames();
|
||||
|
||||
final SingleIdArrayLoadPlan lazySelect = getSQLLazySelectLoadPlan( fetchGroup );
|
||||
|
||||
try {
|
||||
Object result = null;
|
||||
final Object[] values = lazySelect.load( id, session );
|
||||
int i = 0;
|
||||
for ( LazyAttributeDescriptor fetchGroupAttributeDescriptor : fetchGroupAttributeDescriptors ) {
|
||||
final boolean previousInitialized = initializedLazyAttributeNames.contains( fetchGroupAttributeDescriptor.getName() );
|
||||
|
||||
if ( previousInitialized ) {
|
||||
// todo : one thing we should consider here is potentially un-marking an attribute as dirty based on the selected value
|
||||
// we know the current value - getPropertyValue( entity, fetchGroupAttributeDescriptor.getAttributeIndex() );
|
||||
// we know the selected value (see selectedValue below)
|
||||
// we can use the attribute Type to tell us if they are the same
|
||||
//
|
||||
// assuming entity is a SelfDirtinessTracker we can also know if the attribute is
|
||||
// currently considered dirty, and if really not dirty we would do the un-marking
|
||||
//
|
||||
// of course that would mean a new method on SelfDirtinessTracker to allow un-marking
|
||||
|
||||
// its already been initialized (e.g. by a write) so we don't want to overwrite
|
||||
i++;
|
||||
continue;
|
||||
if ( nonLazyPropertyNames.contains( fieldName ) ) {
|
||||
// An eager property can be lazy because of an applied EntityGraph
|
||||
final List<ModelPart> partsToSelect = new ArrayList<>(1);
|
||||
int propertyIndex = getPropertyIndex( fieldName );
|
||||
partsToSelect.add( getAttributeMapping( propertyIndex ) );
|
||||
SingleIdArrayLoadPlan lazyLoanPlan;
|
||||
if ( nonLazyPropertyLoadPlansByName == null ) {
|
||||
nonLazyPropertyLoadPlansByName = new HashMap<>();
|
||||
lazyLoanPlan = createLazyLoanPlan( partsToSelect );
|
||||
;
|
||||
nonLazyPropertyLoadPlansByName.put( fieldName, lazyLoanPlan );
|
||||
}
|
||||
else {
|
||||
lazyLoanPlan = nonLazyPropertyLoadPlansByName.get( fieldName );
|
||||
if ( lazyLoanPlan == null ) {
|
||||
lazyLoanPlan = createLazyLoanPlan( partsToSelect );
|
||||
;
|
||||
nonLazyPropertyLoadPlansByName.put( fieldName, lazyLoanPlan );
|
||||
}
|
||||
|
||||
final Object selectedValue = values[i++];
|
||||
final boolean set = initializeLazyProperty(
|
||||
fieldName,
|
||||
}
|
||||
try {
|
||||
final Object[] values = lazyLoanPlan.load( id, session );
|
||||
final Object selectedValue = values[0];
|
||||
initializeLazyProperty(
|
||||
entity,
|
||||
entry,
|
||||
fetchGroupAttributeDescriptor.getLazyIndex(),
|
||||
selectedValue
|
||||
selectedValue,
|
||||
propertyIndex,
|
||||
getPropertyTypes()[propertyIndex]
|
||||
);
|
||||
if ( set ) {
|
||||
result = selectedValue;
|
||||
interceptor.attributeInitialized( fetchGroupAttributeDescriptor.getName() );
|
||||
}
|
||||
|
||||
return selectedValue;
|
||||
}
|
||||
catch (JDBCException ex) {
|
||||
throw session.getJdbcServices().getSqlExceptionHelper().convert(
|
||||
ex.getSQLException(),
|
||||
"could not initialize lazy properties: " + infoString( this, id, getFactory() ),
|
||||
lazyLoanPlan.getJdbcSelect().getSqlString()
|
||||
);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if ( !hasLazyProperties() ) {
|
||||
throw new AssertionFailure( "no lazy properties" );
|
||||
}
|
||||
|
||||
LOG.trace( "Done initializing lazy properties" );
|
||||
final PersistentAttributeInterceptor interceptor = asPersistentAttributeInterceptable( entity ).$$_hibernate_getInterceptor();
|
||||
assert interceptor != null : "Expecting bytecode interceptor to be non-null";
|
||||
|
||||
return result;
|
||||
}
|
||||
catch ( JDBCException ex ) {
|
||||
throw session.getJdbcServices().getSqlExceptionHelper().convert(
|
||||
ex.getSQLException(),
|
||||
"could not initialize lazy properties: " + infoString( this, id, getFactory() ),
|
||||
lazySelect.getJdbcSelect().getSqlString()
|
||||
);
|
||||
LOG.tracef( "Initializing lazy properties from datastore (triggered for `%s`)", fieldName );
|
||||
|
||||
final String fetchGroup = getEntityMetamodel().getBytecodeEnhancementMetadata()
|
||||
.getLazyAttributesMetadata()
|
||||
.getFetchGroupName( fieldName );
|
||||
final List<LazyAttributeDescriptor> fetchGroupAttributeDescriptors = getEntityMetamodel().getBytecodeEnhancementMetadata()
|
||||
.getLazyAttributesMetadata()
|
||||
.getFetchGroupAttributeDescriptors( fetchGroup );
|
||||
|
||||
final Set<String> initializedLazyAttributeNames = interceptor.getInitializedLazyAttributeNames();
|
||||
|
||||
final SingleIdArrayLoadPlan lazySelect = getSQLLazySelectLoadPlan( fetchGroup );
|
||||
|
||||
try {
|
||||
Object result = null;
|
||||
final Object[] values = lazySelect.load( id, session );
|
||||
int i = 0;
|
||||
for ( LazyAttributeDescriptor fetchGroupAttributeDescriptor : fetchGroupAttributeDescriptors ) {
|
||||
final boolean previousInitialized = initializedLazyAttributeNames.contains(
|
||||
fetchGroupAttributeDescriptor.getName() );
|
||||
|
||||
if ( previousInitialized ) {
|
||||
// todo : one thing we should consider here is potentially un-marking an attribute as dirty based on the selected value
|
||||
// we know the current value - getPropertyValue( entity, fetchGroupAttributeDescriptor.getAttributeIndex() );
|
||||
// we know the selected value (see selectedValue below)
|
||||
// we can use the attribute Type to tell us if they are the same
|
||||
//
|
||||
// assuming entity is a SelfDirtinessTracker we can also know if the attribute is
|
||||
// currently considered dirty, and if really not dirty we would do the un-marking
|
||||
//
|
||||
// of course that would mean a new method on SelfDirtinessTracker to allow un-marking
|
||||
|
||||
// its already been initialized (e.g. by a write) so we don't want to overwrite
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
final Object selectedValue = values[i++];
|
||||
final boolean set = initializeLazyProperty(
|
||||
fieldName,
|
||||
entity,
|
||||
entry,
|
||||
fetchGroupAttributeDescriptor,
|
||||
selectedValue
|
||||
);
|
||||
if ( set ) {
|
||||
result = selectedValue;
|
||||
interceptor.attributeInitialized( fetchGroupAttributeDescriptor.getName() );
|
||||
}
|
||||
}
|
||||
|
||||
LOG.trace( "Done initializing lazy properties" );
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
catch (JDBCException ex) {
|
||||
throw session.getJdbcServices().getSqlExceptionHelper().convert(
|
||||
ex.getSQLException(),
|
||||
"could not initialize lazy properties: " + infoString( this, id, getFactory() ),
|
||||
lazySelect.getJdbcSelect().getSqlString()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1669,6 +1721,43 @@ public abstract class AbstractEntityPersister
|
|||
return fieldName.equals( lazyPropertyNames[index] );
|
||||
}
|
||||
|
||||
|
||||
|
||||
protected boolean initializeLazyProperty(
|
||||
final String fieldName,
|
||||
final Object entity,
|
||||
final EntityEntry entry,
|
||||
LazyAttributeDescriptor fetchGroupAttributeDescriptor,
|
||||
final Object propValue) {
|
||||
final String name = fetchGroupAttributeDescriptor.getName();
|
||||
initializeLazyProperty(
|
||||
entity,
|
||||
entry,
|
||||
propValue,
|
||||
getPropertyIndex( name ),
|
||||
fetchGroupAttributeDescriptor.getType()
|
||||
);
|
||||
return fieldName.equals( name );
|
||||
}
|
||||
|
||||
private void initializeLazyProperty(Object entity, EntityEntry entry, Object propValue, int index, Type type) {
|
||||
setPropertyValue( entity, index, propValue );
|
||||
if ( entry.getLoadedState() != null ) {
|
||||
// object have been loaded with setReadOnly(true); HHH-2236
|
||||
entry.getLoadedState()[index] = type.deepCopy(
|
||||
propValue,
|
||||
factory
|
||||
);
|
||||
}
|
||||
// If the entity has deleted state, then update that as well
|
||||
if ( entry.getDeletedState() != null ) {
|
||||
entry.getDeletedState()[index] = type.deepCopy(
|
||||
propValue,
|
||||
factory
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public NavigableRole getNavigableRole() {
|
||||
return navigableRole;
|
||||
|
|
|
@ -9,12 +9,16 @@ package org.hibernate.sql.results.graph.entity.internal;
|
|||
import java.util.function.BiConsumer;
|
||||
|
||||
import org.hibernate.FetchNotFoundException;
|
||||
import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer;
|
||||
import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor;
|
||||
import org.hibernate.engine.internal.ManagedTypeHelper;
|
||||
import org.hibernate.engine.spi.EntityHolder;
|
||||
import org.hibernate.engine.spi.EntityKey;
|
||||
import org.hibernate.engine.spi.EntityUniqueKey;
|
||||
import org.hibernate.engine.spi.PersistenceContext;
|
||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||
import org.hibernate.graph.GraphSemantic;
|
||||
import org.hibernate.graph.spi.AppliedGraph;
|
||||
import org.hibernate.graph.spi.AttributeNodeImplementor;
|
||||
import org.hibernate.internal.log.LoggingHelper;
|
||||
import org.hibernate.metamodel.mapping.ModelPart;
|
||||
import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping;
|
||||
|
@ -37,6 +41,7 @@ import org.hibernate.type.Type;
|
|||
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
import static org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer.UNFETCHED_PROPERTY;
|
||||
import static org.hibernate.sql.results.graph.entity.internal.EntityInitializerImpl.determineConcreteEntityDescriptor;
|
||||
|
||||
/**
|
||||
|
@ -193,7 +198,17 @@ public class EntityDelayedFetchInitializer
|
|||
// For unique-key mappings, we always use bytecode-laziness if possible,
|
||||
// because we can't generate a proxy based on the unique key yet
|
||||
if ( referencedModelPart.isLazy() ) {
|
||||
instance = LazyPropertyInitializer.UNFETCHED_PROPERTY;
|
||||
instance = UNFETCHED_PROPERTY;
|
||||
}
|
||||
else if ( getParent().isEntityInitializer() && isLazyByGraph( rowProcessingState ) ) {
|
||||
// todo : manage the case when parent is an EmbeddableInitializer
|
||||
final Object resolvedInstance = getParent().asEntityInitializer()
|
||||
.getResolvedInstance( rowProcessingState );
|
||||
final LazyAttributeLoadingInterceptor persistentAttributeInterceptor = (LazyAttributeLoadingInterceptor) ManagedTypeHelper
|
||||
.asPersistentAttributeInterceptable( resolvedInstance ).$$_hibernate_getInterceptor();
|
||||
|
||||
persistentAttributeInterceptor.addLazyFieldByGraph( navigablePath.getLocalName() );
|
||||
instance = UNFETCHED_PROPERTY;
|
||||
}
|
||||
else {
|
||||
instance = concreteDescriptor.loadByUniqueKey(
|
||||
|
@ -224,7 +239,7 @@ public class EntityDelayedFetchInitializer
|
|||
// For primary key based mappings we only use bytecode-laziness if the attribute is optional,
|
||||
// because the non-optionality implies that it is safe to have a proxy
|
||||
else if ( referencedModelPart.isOptional() && referencedModelPart.isLazy() ) {
|
||||
instance = LazyPropertyInitializer.UNFETCHED_PROPERTY;
|
||||
instance = UNFETCHED_PROPERTY;
|
||||
}
|
||||
else {
|
||||
instance = session.internalLoad(
|
||||
|
@ -244,6 +259,19 @@ public class EntityDelayedFetchInitializer
|
|||
}
|
||||
}
|
||||
|
||||
private boolean isLazyByGraph(RowProcessingState rowProcessingState) {
|
||||
final AppliedGraph appliedGraph = rowProcessingState.getQueryOptions().getAppliedGraph();
|
||||
if ( appliedGraph != null && appliedGraph.getSemantic() == GraphSemantic.FETCH ) {
|
||||
final AttributeNodeImplementor<Object> attributeNode = appliedGraph.getGraph()
|
||||
.findAttributeNode( navigablePath.getLocalName() );
|
||||
if ( attributeNode != null && attributeNode.getAttributeDescriptor() == getInitializedPart().asAttributeMapping() ) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resolveInstance(Object instance, EntityDelayedFetchInitializerData data) {
|
||||
if ( instance == null ) {
|
||||
|
|
Loading…
Reference in New Issue