Skip to content

Commit 2f2dbbe

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

File tree

3 files changed

+186
-61
lines changed

3 files changed

+186
-61
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: 146 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,7 @@ public abstract class AbstractEntityPersister
378378
private final String[] lazyPropertyNames;
379379
private final int[] lazyPropertyNumbers;
380380
private final Type[] lazyPropertyTypes;
381+
private final Set<String> nonLazyPropertyNames;
381382

382383
//information about all properties in class hierarchy
383384
private final String[] subclassPropertyNameClosure;
@@ -469,6 +470,7 @@ public abstract class AbstractEntityPersister
469470
private final boolean implementsLifecycle;
470471

471472
private List<UniqueKeyEntry> uniqueKeyEntries = null; //lazily initialized
473+
private HashMap<String,SingleIdArrayLoadPlan> nonLazyPropertyLoadPlansByName;
472474

473475
public AbstractEntityPersister(
474476
final PersistentClass persistentClass,
@@ -606,6 +608,7 @@ public AbstractEntityPersister(
606608
propertyColumnUpdateable = new boolean[hydrateSpan][];
607609
propertyColumnInsertable = new boolean[hydrateSpan][];
608610
sharedColumnNames = new HashSet<>();
611+
nonLazyPropertyNames = new HashSet<>();
609612

610613
final HashSet<Property> thisClassProperties = new HashSet<>();
611614
final ArrayList<String> lazyNames = new ArrayList<>();
@@ -663,6 +666,9 @@ public AbstractEntityPersister(
663666
lazyNumbers.add( i );
664667
lazyTypes.add( prop.getValue().getType() );
665668
}
669+
else {
670+
nonLazyPropertyNames.add( prop.getName() );
671+
}
666672

667673
propertyColumnUpdateable[i] = prop.getValue().getColumnUpdateability();
668674
propertyColumnInsertable[i] = prop.getValue().getColumnInsertability();
@@ -1222,6 +1228,10 @@ private SingleIdArrayLoadPlan createLazyLoadPlan(List<LazyAttributeDescriptor> f
12221228
partsToSelect.add( getAttributeMapping( getSubclassPropertyIndex( lazyAttributeDescriptor.getName() ) ) );
12231229
}
12241230

1231+
return createLazyLoanPlan( partsToSelect );
1232+
}
1233+
1234+
private SingleIdArrayLoadPlan createLazyLoanPlan(List<ModelPart> partsToSelect) {
12251235
if ( partsToSelect.isEmpty() ) {
12261236
// only one-to-one is lazily fetched
12271237
return null;
@@ -1542,75 +1552,117 @@ protected Object initializeLazyPropertiesFromDatastore(
15421552
final EntityEntry entry,
15431553
final String fieldName,
15441554
final SharedSessionContractImplementor session) {
1545-
1546-
if ( !hasLazyProperties() ) {
1547-
throw new AssertionFailure( "no lazy properties" );
1555+
if ( nonLazyPropertyNames.contains( fieldName ) ) {
1556+
// An eager property can be lazy because of an applied EntityGraph
1557+
final List<ModelPart> partsToSelect = new ArrayList<>(1);
1558+
int propertyIndex = getPropertyIndex( fieldName );
1559+
partsToSelect.add( getAttributeMapping( propertyIndex ) );
1560+
SingleIdArrayLoadPlan lazyLoanPlan;
1561+
if ( nonLazyPropertyLoadPlansByName == null ) {
1562+
nonLazyPropertyLoadPlansByName = new HashMap<>();
1563+
lazyLoanPlan = createLazyLoanPlan( partsToSelect );
1564+
;
1565+
nonLazyPropertyLoadPlansByName.put( fieldName, lazyLoanPlan );
1566+
}
1567+
else {
1568+
lazyLoanPlan = nonLazyPropertyLoadPlansByName.get( fieldName );
1569+
if ( lazyLoanPlan == null ) {
1570+
lazyLoanPlan = createLazyLoanPlan( partsToSelect );
1571+
;
1572+
nonLazyPropertyLoadPlansByName.put( fieldName, lazyLoanPlan );
1573+
}
1574+
}
1575+
try {
1576+
final Object[] values = lazyLoanPlan.load( id, session );
1577+
final Object selectedValue = values[0];
1578+
initializeLazyProperty(
1579+
entity,
1580+
entry,
1581+
selectedValue,
1582+
propertyIndex,
1583+
getPropertyTypes()[propertyIndex]
1584+
);
1585+
return selectedValue;
1586+
}
1587+
catch (JDBCException ex) {
1588+
throw session.getJdbcServices().getSqlExceptionHelper().convert(
1589+
ex.getSQLException(),
1590+
"could not initialize lazy properties: " + infoString( this, id, getFactory() ),
1591+
lazyLoanPlan.getJdbcSelect().getSqlString()
1592+
);
1593+
}
15481594
}
1595+
else {
1596+
if ( !hasLazyProperties() ) {
1597+
throw new AssertionFailure( "no lazy properties" );
1598+
}
15491599

1550-
final PersistentAttributeInterceptor interceptor = asPersistentAttributeInterceptable( entity ).$$_hibernate_getInterceptor();
1551-
assert interceptor != null : "Expecting bytecode interceptor to be non-null";
1600+
final PersistentAttributeInterceptor interceptor = asPersistentAttributeInterceptable( entity ).$$_hibernate_getInterceptor();
1601+
assert interceptor != null : "Expecting bytecode interceptor to be non-null";
15521602

1553-
LOG.tracef( "Initializing lazy properties from datastore (triggered for `%s`)", fieldName );
1603+
LOG.tracef( "Initializing lazy properties from datastore (triggered for `%s`)", fieldName );
15541604

1555-
final String fetchGroup = getEntityMetamodel().getBytecodeEnhancementMetadata()
1556-
.getLazyAttributesMetadata()
1557-
.getFetchGroupName( fieldName );
1558-
final List<LazyAttributeDescriptor> fetchGroupAttributeDescriptors = getEntityMetamodel().getBytecodeEnhancementMetadata()
1559-
.getLazyAttributesMetadata()
1560-
.getFetchGroupAttributeDescriptors( fetchGroup );
1605+
final String fetchGroup = getEntityMetamodel().getBytecodeEnhancementMetadata()
1606+
.getLazyAttributesMetadata()
1607+
.getFetchGroupName( fieldName );
1608+
final List<LazyAttributeDescriptor> fetchGroupAttributeDescriptors = getEntityMetamodel().getBytecodeEnhancementMetadata()
1609+
.getLazyAttributesMetadata()
1610+
.getFetchGroupAttributeDescriptors( fetchGroup );
15611611

1562-
final Set<String> initializedLazyAttributeNames = interceptor.getInitializedLazyAttributeNames();
1612+
final Set<String> initializedLazyAttributeNames = interceptor.getInitializedLazyAttributeNames();
15631613

1564-
final SingleIdArrayLoadPlan lazySelect = getSQLLazySelectLoadPlan( fetchGroup );
1614+
final SingleIdArrayLoadPlan lazySelect = getSQLLazySelectLoadPlan( fetchGroup );
15651615

1566-
try {
1567-
Object result = null;
1568-
final Object[] values = lazySelect.load( id, session );
1569-
int i = 0;
1570-
for ( LazyAttributeDescriptor fetchGroupAttributeDescriptor : fetchGroupAttributeDescriptors ) {
1571-
final boolean previousInitialized = initializedLazyAttributeNames.contains( fetchGroupAttributeDescriptor.getName() );
1572-
1573-
if ( previousInitialized ) {
1574-
// todo : one thing we should consider here is potentially un-marking an attribute as dirty based on the selected value
1575-
// we know the current value - getPropertyValue( entity, fetchGroupAttributeDescriptor.getAttributeIndex() );
1576-
// we know the selected value (see selectedValue below)
1577-
// we can use the attribute Type to tell us if they are the same
1578-
//
1579-
// assuming entity is a SelfDirtinessTracker we can also know if the attribute is
1580-
// currently considered dirty, and if really not dirty we would do the un-marking
1581-
//
1582-
// of course that would mean a new method on SelfDirtinessTracker to allow un-marking
1583-
1584-
// its already been initialized (e.g. by a write) so we don't want to overwrite
1585-
i++;
1586-
continue;
1587-
}
1616+
try {
1617+
Object result = null;
1618+
final Object[] values = lazySelect.load( id, session );
1619+
int i = 0;
1620+
for ( LazyAttributeDescriptor fetchGroupAttributeDescriptor : fetchGroupAttributeDescriptors ) {
1621+
final boolean previousInitialized = initializedLazyAttributeNames.contains(
1622+
fetchGroupAttributeDescriptor.getName() );
1623+
1624+
if ( previousInitialized ) {
1625+
// todo : one thing we should consider here is potentially un-marking an attribute as dirty based on the selected value
1626+
// we know the current value - getPropertyValue( entity, fetchGroupAttributeDescriptor.getAttributeIndex() );
1627+
// we know the selected value (see selectedValue below)
1628+
// we can use the attribute Type to tell us if they are the same
1629+
//
1630+
// assuming entity is a SelfDirtinessTracker we can also know if the attribute is
1631+
// currently considered dirty, and if really not dirty we would do the un-marking
1632+
//
1633+
// of course that would mean a new method on SelfDirtinessTracker to allow un-marking
1634+
1635+
// its already been initialized (e.g. by a write) so we don't want to overwrite
1636+
i++;
1637+
continue;
1638+
}
15881639

1589-
final Object selectedValue = values[i++];
1590-
final boolean set = initializeLazyProperty(
1591-
fieldName,
1592-
entity,
1593-
entry,
1594-
fetchGroupAttributeDescriptor.getLazyIndex(),
1595-
selectedValue
1596-
);
1597-
if ( set ) {
1598-
result = selectedValue;
1599-
interceptor.attributeInitialized( fetchGroupAttributeDescriptor.getName() );
1640+
final Object selectedValue = values[i++];
1641+
final boolean set = initializeLazyProperty(
1642+
fieldName,
1643+
entity,
1644+
entry,
1645+
fetchGroupAttributeDescriptor,
1646+
selectedValue
1647+
);
1648+
if ( set ) {
1649+
result = selectedValue;
1650+
interceptor.attributeInitialized( fetchGroupAttributeDescriptor.getName() );
1651+
}
16001652
}
16011653

1602-
}
1654+
LOG.trace( "Done initializing lazy properties" );
16031655

1604-
LOG.trace( "Done initializing lazy properties" );
1656+
return result;
16051657

1606-
return result;
1607-
}
1608-
catch ( JDBCException ex ) {
1609-
throw session.getJdbcServices().getSqlExceptionHelper().convert(
1610-
ex.getSQLException(),
1611-
"could not initialize lazy properties: " + infoString( this, id, getFactory() ),
1612-
lazySelect.getJdbcSelect().getSqlString()
1613-
);
1658+
}
1659+
catch (JDBCException ex) {
1660+
throw session.getJdbcServices().getSqlExceptionHelper().convert(
1661+
ex.getSQLException(),
1662+
"could not initialize lazy properties: " + infoString( this, id, getFactory() ),
1663+
lazySelect.getJdbcSelect().getSqlString()
1664+
);
1665+
}
16141666
}
16151667
}
16161668

@@ -1669,6 +1721,43 @@ protected boolean initializeLazyProperty(
16691721
return fieldName.equals( lazyPropertyNames[index] );
16701722
}
16711723

1724+
1725+
1726+
protected boolean initializeLazyProperty(
1727+
final String fieldName,
1728+
final Object entity,
1729+
final EntityEntry entry,
1730+
LazyAttributeDescriptor fetchGroupAttributeDescriptor,
1731+
final Object propValue) {
1732+
final String name = fetchGroupAttributeDescriptor.getName();
1733+
initializeLazyProperty(
1734+
entity,
1735+
entry,
1736+
propValue,
1737+
getPropertyIndex( name ),
1738+
fetchGroupAttributeDescriptor.getType()
1739+
);
1740+
return fieldName.equals( name );
1741+
}
1742+
1743+
private void initializeLazyProperty(Object entity, EntityEntry entry, Object propValue, int index, Type type) {
1744+
setPropertyValue( entity, index, propValue );
1745+
if ( entry.getLoadedState() != null ) {
1746+
// object have been loaded with setReadOnly(true); HHH-2236
1747+
entry.getLoadedState()[index] = type.deepCopy(
1748+
propValue,
1749+
factory
1750+
);
1751+
}
1752+
// If the entity has deleted state, then update that as well
1753+
if ( entry.getDeletedState() != null ) {
1754+
entry.getDeletedState()[index] = type.deepCopy(
1755+
propValue,
1756+
factory
1757+
);
1758+
}
1759+
}
1760+
16721761
@Override
16731762
public NavigableRole getNavigableRole() {
16741763
return navigableRole;

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)