Skip to content

Commit a1d0b82

Browse files
dreab8sebersole
authored andcommitted
HHH-18489 Lazy, unowned one-to-one associations get loaded eagerly in queries - even with bytecode enhancement
1 parent 51fd19f commit a1d0b82

File tree

3 files changed

+187
-62
lines changed

3 files changed

+187
-62
lines changed

hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/LazyAttributeLoadingInterceptor.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,9 @@ public class LazyAttributeLoadingInterceptor extends AbstractLazyLoadInterceptor
3434
private final Object identifier;
3535

3636
//N.B. this Set needs to be treated as immutable
37-
private final Set<String> lazyFields;
37+
private Set<String> lazyFields;
3838
private Set<String> initializedLazyFields;
39+
private Set<String> mutableLazyFields;
3940

4041
public LazyAttributeLoadingInterceptor(
4142
String entityName,
@@ -193,4 +194,11 @@ public Set<String> getInitializedLazyAttributeNames() {
193194
return initializedLazyFields == null ? Collections.emptySet() : initializedLazyFields;
194195
}
195196

197+
public void addLazyFieldByGraph(String fieldName) {
198+
if ( mutableLazyFields == null ) {
199+
mutableLazyFields = new HashSet<>( lazyFields );
200+
lazyFields = mutableLazyFields;
201+
}
202+
mutableLazyFields.add( fieldName );
203+
}
196204
}

hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java

Lines changed: 147 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,7 @@ public abstract class AbstractEntityPersister
397397
private final int[] lazyPropertyNumbers;
398398
private final Type[] lazyPropertyTypes;
399399
private final String[][] lazyPropertyColumnAliases;
400+
private final Set<String> nonLazyPropertyNames;
400401

401402
//information about all properties in class hierarchy
402403
private final String[] subclassPropertyNameClosure;
@@ -495,6 +496,7 @@ public abstract class AbstractEntityPersister
495496
private final boolean implementsLifecycle;
496497

497498
private List<UniqueKeyEntry> uniqueKeyEntries = null; //lazily initialized
499+
private HashMap<String,SingleIdArrayLoadPlan> nonLazyPropertyLoadPlansByName;
498500

499501
@Deprecated(since = "6.0")
500502
public AbstractEntityPersister(
@@ -645,6 +647,7 @@ public AbstractEntityPersister(
645647
propertyColumnUpdateable = new boolean[hydrateSpan][];
646648
propertyColumnInsertable = new boolean[hydrateSpan][];
647649
sharedColumnNames = new HashSet<>();
650+
nonLazyPropertyNames = new HashSet<>();
648651

649652
final HashSet<Property> thisClassProperties = new HashSet<>();
650653
final ArrayList<String> lazyNames = new ArrayList<>();
@@ -710,6 +713,9 @@ public AbstractEntityPersister(
710713
lazyTypes.add( prop.getValue().getType() );
711714
lazyColAliases.add( colAliases );
712715
}
716+
else {
717+
nonLazyPropertyNames.add( prop.getName() );
718+
}
713719

714720
propertyColumnUpdateable[i] = prop.getValue().getColumnUpdateability();
715721
propertyColumnInsertable[i] = prop.getValue().getColumnInsertability();
@@ -1330,6 +1336,10 @@ private SingleIdArrayLoadPlan createLazyLoadPlan(List<LazyAttributeDescriptor> f
13301336
partsToSelect.add( getAttributeMapping( getSubclassPropertyIndex( lazyAttributeDescriptor.getName() ) ) );
13311337
}
13321338

1339+
return createLazyLoanPlan( partsToSelect );
1340+
}
1341+
1342+
private SingleIdArrayLoadPlan createLazyLoanPlan(List<ModelPart> partsToSelect) {
13331343
if ( partsToSelect.isEmpty() ) {
13341344
// only one-to-one is lazily fetched
13351345
return null;
@@ -1655,75 +1665,117 @@ protected Object initializeLazyPropertiesFromDatastore(
16551665
final EntityEntry entry,
16561666
final String fieldName,
16571667
final SharedSessionContractImplementor session) {
1658-
1659-
if ( !hasLazyProperties() ) {
1660-
throw new AssertionFailure( "no lazy properties" );
1668+
if ( nonLazyPropertyNames.contains( fieldName ) ) {
1669+
// An eager property can be lazy because of an applied EntityGraph
1670+
final List<ModelPart> partsToSelect = new ArrayList<>(1);
1671+
int propertyIndex = getPropertyIndex( fieldName );
1672+
partsToSelect.add( getAttributeMapping( propertyIndex ) );
1673+
SingleIdArrayLoadPlan lazyLoanPlan;
1674+
if ( nonLazyPropertyLoadPlansByName == null ) {
1675+
nonLazyPropertyLoadPlansByName = new HashMap<>();
1676+
lazyLoanPlan = createLazyLoanPlan( partsToSelect );
1677+
;
1678+
nonLazyPropertyLoadPlansByName.put( fieldName, lazyLoanPlan );
1679+
}
1680+
else {
1681+
lazyLoanPlan = nonLazyPropertyLoadPlansByName.get( fieldName );
1682+
if ( lazyLoanPlan == null ) {
1683+
lazyLoanPlan = createLazyLoanPlan( partsToSelect );
1684+
;
1685+
nonLazyPropertyLoadPlansByName.put( fieldName, lazyLoanPlan );
1686+
}
1687+
}
1688+
try {
1689+
final Object[] values = lazyLoanPlan.load( id, session );
1690+
final Object selectedValue = values[0];
1691+
initializeLazyProperty(
1692+
entity,
1693+
entry,
1694+
selectedValue,
1695+
propertyIndex,
1696+
getPropertyTypes()[propertyIndex]
1697+
);
1698+
return selectedValue;
1699+
}
1700+
catch (JDBCException ex) {
1701+
throw session.getJdbcServices().getSqlExceptionHelper().convert(
1702+
ex.getSQLException(),
1703+
"could not initialize lazy properties: " + infoString( this, id, getFactory() ),
1704+
lazyLoanPlan.getJdbcSelect().getSqlString()
1705+
);
1706+
}
16611707
}
1708+
else {
1709+
if ( !hasLazyProperties() ) {
1710+
throw new AssertionFailure( "no lazy properties" );
1711+
}
16621712

1663-
final PersistentAttributeInterceptor interceptor = asPersistentAttributeInterceptable( entity ).$$_hibernate_getInterceptor();
1664-
assert interceptor != null : "Expecting bytecode interceptor to be non-null";
1713+
final PersistentAttributeInterceptor interceptor = asPersistentAttributeInterceptable( entity ).$$_hibernate_getInterceptor();
1714+
assert interceptor != null : "Expecting bytecode interceptor to be non-null";
16651715

1666-
LOG.tracef( "Initializing lazy properties from datastore (triggered for `%s`)", fieldName );
1716+
LOG.tracef( "Initializing lazy properties from datastore (triggered for `%s`)", fieldName );
16671717

1668-
final String fetchGroup = getEntityMetamodel().getBytecodeEnhancementMetadata()
1669-
.getLazyAttributesMetadata()
1670-
.getFetchGroupName( fieldName );
1671-
final List<LazyAttributeDescriptor> fetchGroupAttributeDescriptors = getEntityMetamodel().getBytecodeEnhancementMetadata()
1672-
.getLazyAttributesMetadata()
1673-
.getFetchGroupAttributeDescriptors( fetchGroup );
1718+
final String fetchGroup = getEntityMetamodel().getBytecodeEnhancementMetadata()
1719+
.getLazyAttributesMetadata()
1720+
.getFetchGroupName( fieldName );
1721+
final List<LazyAttributeDescriptor> fetchGroupAttributeDescriptors = getEntityMetamodel().getBytecodeEnhancementMetadata()
1722+
.getLazyAttributesMetadata()
1723+
.getFetchGroupAttributeDescriptors( fetchGroup );
16741724

1675-
final Set<String> initializedLazyAttributeNames = interceptor.getInitializedLazyAttributeNames();
1725+
final Set<String> initializedLazyAttributeNames = interceptor.getInitializedLazyAttributeNames();
16761726

1677-
final SingleIdArrayLoadPlan lazySelect = getSQLLazySelectLoadPlan( fetchGroup );
1727+
final SingleIdArrayLoadPlan lazySelect = getSQLLazySelectLoadPlan( fetchGroup );
16781728

1679-
try {
1680-
Object result = null;
1681-
final Object[] values = lazySelect.load( id, session );
1682-
int i = 0;
1683-
for ( LazyAttributeDescriptor fetchGroupAttributeDescriptor : fetchGroupAttributeDescriptors ) {
1684-
final boolean previousInitialized = initializedLazyAttributeNames.contains( fetchGroupAttributeDescriptor.getName() );
1685-
1686-
if ( previousInitialized ) {
1687-
// todo : one thing we should consider here is potentially un-marking an attribute as dirty based on the selected value
1688-
// we know the current value - getPropertyValue( entity, fetchGroupAttributeDescriptor.getAttributeIndex() );
1689-
// we know the selected value (see selectedValue below)
1690-
// we can use the attribute Type to tell us if they are the same
1691-
//
1692-
// assuming entity is a SelfDirtinessTracker we can also know if the attribute is
1693-
// currently considered dirty, and if really not dirty we would do the un-marking
1694-
//
1695-
// of course that would mean a new method on SelfDirtinessTracker to allow un-marking
1696-
1697-
// its already been initialized (e.g. by a write) so we don't want to overwrite
1698-
i++;
1699-
continue;
1700-
}
1729+
try {
1730+
Object result = null;
1731+
final Object[] values = lazySelect.load( id, session );
1732+
int i = 0;
1733+
for ( LazyAttributeDescriptor fetchGroupAttributeDescriptor : fetchGroupAttributeDescriptors ) {
1734+
final boolean previousInitialized = initializedLazyAttributeNames.contains(
1735+
fetchGroupAttributeDescriptor.getName() );
1736+
1737+
if ( previousInitialized ) {
1738+
// todo : one thing we should consider here is potentially un-marking an attribute as dirty based on the selected value
1739+
// we know the current value - getPropertyValue( entity, fetchGroupAttributeDescriptor.getAttributeIndex() );
1740+
// we know the selected value (see selectedValue below)
1741+
// we can use the attribute Type to tell us if they are the same
1742+
//
1743+
// assuming entity is a SelfDirtinessTracker we can also know if the attribute is
1744+
// currently considered dirty, and if really not dirty we would do the un-marking
1745+
//
1746+
// of course that would mean a new method on SelfDirtinessTracker to allow un-marking
1747+
1748+
// its already been initialized (e.g. by a write) so we don't want to overwrite
1749+
i++;
1750+
continue;
1751+
}
17011752

1702-
final Object selectedValue = values[i++];
1703-
final boolean set = initializeLazyProperty(
1704-
fieldName,
1705-
entity,
1706-
entry,
1707-
fetchGroupAttributeDescriptor.getLazyIndex(),
1708-
selectedValue
1709-
);
1710-
if ( set ) {
1711-
result = selectedValue;
1712-
interceptor.attributeInitialized( fetchGroupAttributeDescriptor.getName() );
1753+
final Object selectedValue = values[i++];
1754+
final boolean set = initializeLazyProperty(
1755+
fieldName,
1756+
entity,
1757+
entry,
1758+
fetchGroupAttributeDescriptor,
1759+
selectedValue
1760+
);
1761+
if ( set ) {
1762+
result = selectedValue;
1763+
interceptor.attributeInitialized( fetchGroupAttributeDescriptor.getName() );
1764+
}
17131765
}
17141766

1715-
}
1767+
LOG.trace( "Done initializing lazy properties" );
17161768

1717-
LOG.trace( "Done initializing lazy properties" );
1769+
return result;
17181770

1719-
return result;
1720-
}
1721-
catch ( JDBCException ex ) {
1722-
throw session.getJdbcServices().getSqlExceptionHelper().convert(
1723-
ex.getSQLException(),
1724-
"could not initialize lazy properties: " + infoString( this, id, getFactory() ),
1725-
lazySelect.getJdbcSelect().getSqlString()
1726-
);
1771+
}
1772+
catch (JDBCException ex) {
1773+
throw session.getJdbcServices().getSqlExceptionHelper().convert(
1774+
ex.getSQLException(),
1775+
"could not initialize lazy properties: " + infoString( this, id, getFactory() ),
1776+
lazySelect.getJdbcSelect().getSqlString()
1777+
);
1778+
}
17271779
}
17281780
}
17291781

@@ -1782,6 +1834,43 @@ protected boolean initializeLazyProperty(
17821834
return fieldName.equals( lazyPropertyNames[index] );
17831835
}
17841836

1837+
1838+
1839+
protected boolean initializeLazyProperty(
1840+
final String fieldName,
1841+
final Object entity,
1842+
final EntityEntry entry,
1843+
LazyAttributeDescriptor fetchGroupAttributeDescriptor,
1844+
final Object propValue) {
1845+
final String name = fetchGroupAttributeDescriptor.getName();
1846+
initializeLazyProperty(
1847+
entity,
1848+
entry,
1849+
propValue,
1850+
getPropertyIndex( name ),
1851+
fetchGroupAttributeDescriptor.getType()
1852+
);
1853+
return fieldName.equals( name );
1854+
}
1855+
1856+
private void initializeLazyProperty(Object entity, EntityEntry entry, Object propValue, int index, Type type) {
1857+
setPropertyValue( entity, index, propValue );
1858+
if ( entry.getLoadedState() != null ) {
1859+
// object have been loaded with setReadOnly(true); HHH-2236
1860+
entry.getLoadedState()[index] = type.deepCopy(
1861+
propValue,
1862+
factory
1863+
);
1864+
}
1865+
// If the entity has deleted state, then update that as well
1866+
if ( entry.getDeletedState() != null ) {
1867+
entry.getDeletedState()[index] = type.deepCopy(
1868+
propValue,
1869+
factory
1870+
);
1871+
}
1872+
}
1873+
17851874
@Override
17861875
public NavigableRole getNavigableRole() {
17871876
return navigableRole;
@@ -3227,7 +3316,7 @@ else if ( discriminatorValue == NOT_NULL_DISCRIMINATOR ) {
32273316
return new NullnessPredicate( sqlExpression, true );
32283317
}
32293318
else if ( hasNull ) {
3230-
junction.add( new NullnessPredicate( sqlExpression ) );
3319+
junction.add( new NullnessPredicate( sqlExpression ) );
32313320
}
32323321

32333322
junction.add( predicate );

hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityDelayedFetchInitializer.java

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,16 @@
99
import java.util.function.BiConsumer;
1010

1111
import org.hibernate.FetchNotFoundException;
12-
import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer;
12+
import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor;
13+
import org.hibernate.engine.internal.ManagedTypeHelper;
1314
import org.hibernate.engine.spi.EntityHolder;
1415
import org.hibernate.engine.spi.EntityKey;
1516
import org.hibernate.engine.spi.EntityUniqueKey;
1617
import org.hibernate.engine.spi.PersistenceContext;
1718
import org.hibernate.engine.spi.SharedSessionContractImplementor;
19+
import org.hibernate.graph.GraphSemantic;
20+
import org.hibernate.graph.spi.AppliedGraph;
21+
import org.hibernate.graph.spi.AttributeNodeImplementor;
1822
import org.hibernate.internal.log.LoggingHelper;
1923
import org.hibernate.metamodel.mapping.ModelPart;
2024
import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping;
@@ -37,6 +41,7 @@
3741

3842
import org.checkerframework.checker.nullness.qual.Nullable;
3943

44+
import static org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer.UNFETCHED_PROPERTY;
4045
import static org.hibernate.sql.results.graph.entity.internal.EntityInitializerImpl.determineConcreteEntityDescriptor;
4146

4247
/**
@@ -193,7 +198,17 @@ public void resolveInstance(EntityDelayedFetchInitializerData data) {
193198
// For unique-key mappings, we always use bytecode-laziness if possible,
194199
// because we can't generate a proxy based on the unique key yet
195200
if ( referencedModelPart.isLazy() ) {
196-
instance = LazyPropertyInitializer.UNFETCHED_PROPERTY;
201+
instance = UNFETCHED_PROPERTY;
202+
}
203+
else if ( getParent().isEntityInitializer() && isLazyByGraph( rowProcessingState ) ) {
204+
// todo : manage the case when parent is an EmbeddableInitializer
205+
final Object resolvedInstance = getParent().asEntityInitializer()
206+
.getResolvedInstance( rowProcessingState );
207+
final LazyAttributeLoadingInterceptor persistentAttributeInterceptor = (LazyAttributeLoadingInterceptor) ManagedTypeHelper
208+
.asPersistentAttributeInterceptable( resolvedInstance ).$$_hibernate_getInterceptor();
209+
210+
persistentAttributeInterceptor.addLazyFieldByGraph( navigablePath.getLocalName() );
211+
instance = UNFETCHED_PROPERTY;
197212
}
198213
else {
199214
instance = concreteDescriptor.loadByUniqueKey(
@@ -224,7 +239,7 @@ public void resolveInstance(EntityDelayedFetchInitializerData data) {
224239
// For primary key based mappings we only use bytecode-laziness if the attribute is optional,
225240
// because the non-optionality implies that it is safe to have a proxy
226241
else if ( referencedModelPart.isOptional() && referencedModelPart.isLazy() ) {
227-
instance = LazyPropertyInitializer.UNFETCHED_PROPERTY;
242+
instance = UNFETCHED_PROPERTY;
228243
}
229244
else {
230245
instance = session.internalLoad(
@@ -244,6 +259,19 @@ else if ( referencedModelPart.isOptional() && referencedModelPart.isLazy() ) {
244259
}
245260
}
246261

262+
private boolean isLazyByGraph(RowProcessingState rowProcessingState) {
263+
final AppliedGraph appliedGraph = rowProcessingState.getQueryOptions().getAppliedGraph();
264+
if ( appliedGraph != null && appliedGraph.getSemantic() == GraphSemantic.FETCH ) {
265+
final AttributeNodeImplementor<Object> attributeNode = appliedGraph.getGraph()
266+
.findAttributeNode( navigablePath.getLocalName() );
267+
if ( attributeNode != null && attributeNode.getAttributeDescriptor() == getInitializedPart().asAttributeMapping() ) {
268+
return false;
269+
}
270+
return true;
271+
}
272+
return false;
273+
}
274+
247275
@Override
248276
public void resolveInstance(Object instance, EntityDelayedFetchInitializerData data) {
249277
if ( instance == null ) {

0 commit comments

Comments
 (0)