Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1605,7 +1605,6 @@ private Object initLazyProperties(

final var interceptor = asPersistentAttributeInterceptable( entity ).$$_hibernate_getInterceptor();
assert interceptor != null : "Expecting bytecode interceptor to be non-null";
final Set<String> initializedLazyAttributeNames = interceptor.getInitializedLazyAttributeNames();

final var lazyAttributesMetadata = getBytecodeEnhancementMetadata().getLazyAttributesMetadata();
final String fetchGroup = lazyAttributesMetadata.getFetchGroupName( fieldName );
Expand All @@ -1615,29 +1614,32 @@ private Object initLazyProperties(
try {
Object finalResult = null;
final Object[] results = lazySelectLoadPlan.load( id, session );
final Set<String> initializedLazyAttributeNames = interceptor.getInitializedLazyAttributeNames();
int i = 0;
for ( var fetchGroupAttributeDescriptor : fetchGroupAttributeDescriptors ) {
final String attributeName = fetchGroupAttributeDescriptor.getName();
final boolean previousInitialized = initializedLazyAttributeNames.contains( attributeName );
if ( previousInitialized ) {
// it's already been initialized (e.g. by a write) so we don't want to overwrite
i++;
// TODO: we should consider 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
if ( fieldName.equals( attributeName ) ) {
finalResult = results[i];
}
else {
final Object result = results[i++];
if ( initializeLazyProperty( fieldName, entity, entry, fetchGroupAttributeDescriptor, result ) ) {
finalResult = result;
interceptor.attributeInitialized( attributeName );
}
if ( !initializedLazyAttributeNames.contains( attributeName ) ) {
initializeLazyProperty(
entity,
entry,
results[i],
getPropertyIndex( attributeName ),
fetchGroupAttributeDescriptor.getType()
);
}
// if the attribute has already been initialized (e.g. by a write) we don't want to overwrite
i++;
// TODO: we should consider 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
}
CORE_LOGGER.doneInitializingLazyProperties();
return finalResult;
Expand Down Expand Up @@ -1755,7 +1757,11 @@ private Object copiedLazyPropertyValue(int index, Object propValue) {
return lazyPropertyTypes[index].deepCopy( propValue, factory );
}

// Used by Hibernate Reactive
/**
* Used by Hibernate Reactive
* @deprecated
*/
@Deprecated(since = "7.2", forRemoval = true)
protected boolean initializeLazyProperty(
final String fieldName,
final Object entity,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
/*
* SPDX-License-Identifier: Apache-2.0
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.orm.test.bytecode.enhancement.lazy;

import jakarta.persistence.Basic;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.OneToOne;
import org.hibernate.Hibernate;
import org.hibernate.annotations.LazyGroup;
import org.hibernate.testing.bytecode.enhancement.EnhancementOptions;
import org.hibernate.testing.bytecode.enhancement.extension.BytecodeEnhanced;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;

@DomainModel(annotatedClasses = {
LazyOneToOneWithCastTest.EntityA.class,
LazyOneToOneWithCastTest.EntityB.class,
})
@SessionFactory
@BytecodeEnhanced
@EnhancementOptions(lazyLoading = true)
public class LazyOneToOneWithCastTest {

@Test
void oneNullOneNotNull(SessionFactoryScope scope) {
scope.inTransaction( session -> {
final EntityA entityA = new EntityA();
entityA.setId( 1 );

final EntityB entityB = new EntityB();
entityB.setId( 2 );
entityB.setToOneWithCast( entityA );
entityA.setToOneWithCast( entityB );
entityB.setLazyString( "lazy" );

session.persist( entityB );
session.persist( entityA );

} );

scope.inTransaction( session -> {
final EntityB entityB = session.find( EntityB.class, 2 );

EntityA toOne = entityB.getToOne();
assertThat( toOne ).isNull();
assertThat( Hibernate.isPropertyInitialized( entityB, "toOne" ) ).isTrue();
assertThat( Hibernate.isPropertyInitialized( entityB, "toOneWithCast" ) ).isFalse();
assertThat( Hibernate.isPropertyInitialized( entityB, "lazyString" ) ).isFalse();

// update lazy-basic property in same lazy-group
entityB.setLazyString( "lazy_updated" );
assertThat( Hibernate.isPropertyInitialized( entityB, "lazyString" ) ).isTrue();
assertThat( Hibernate.isPropertyInitialized( entityB, "toOneWithCast" ) ).isFalse();

// now access the lazy to-one with cast
final EntityA toOneWithCast = entityB.getToOneWithCast();
assertThat( Hibernate.isPropertyInitialized( entityB, "toOneWithCast" ) ).isTrue();
assertThat( Hibernate.isPropertyInitialized( entityB, "lazyString" ) ).isTrue();
assertThat( toOneWithCast ).isNotNull().extracting( EntityA::getId ).isEqualTo( 1 );
assertThat( entityB.getLazyString() ).isEqualTo( "lazy_updated" );
} );
}

@AfterEach
void tearDown(SessionFactoryScope scope) {
scope.getSessionFactory().getSchemaManager().truncateMappedObjects();
}

@Entity(name = "EntityA")
static class EntityA {
@Id
private Integer id;

@OneToOne
@JoinColumn(name = "entity_b_id")
private EntityB toOne;

@OneToOne(targetEntity = EntityB.class)
@JoinColumn(name = "entity_b_id_cast")
private EntityB toOneWithCast;

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public EntityB getToOne() {
return toOne;
}

public void setToOne(EntityB containedIndexedEmbedded) {
this.toOne = containedIndexedEmbedded;
}

public Object getToOneWithCast() {
return toOneWithCast;
}

public void setToOneWithCast(EntityB containedIndexedEmbeddedWithCast) {
this.toOneWithCast = containedIndexedEmbeddedWithCast;
}
}

@Entity(name = "EntityB")
static class EntityB {
@Id
private Integer id;

@OneToOne(mappedBy = "toOne", fetch = FetchType.LAZY)
@LazyGroup("toOneGroup")
private EntityA toOne;

@OneToOne(mappedBy = "toOneWithCast", targetEntity = EntityA.class, fetch = FetchType.LAZY)
@LazyGroup("toOneWithCastGroup")
private EntityA toOneWithCast;

@Basic(fetch = FetchType.LAZY)
@LazyGroup("toOneWithCastGroup")
private String lazyString;

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public EntityA getToOne() {
return toOne;
}

public void setToOne(EntityA toOne) {
this.toOne = toOne;
}

public EntityA getToOneWithCast() {
return toOneWithCast;
}

public void setToOneWithCast(EntityA toOneWithCast) {
this.toOneWithCast = toOneWithCast;
}

public String getLazyString() {
return lazyString;
}

public void setLazyString(String lazyString) {
this.lazyString = lazyString;
}
}
}