HHH-18271 Fix faulty maybe lazy set determination leading to issue with previous row optimization

This commit is contained in:
Christian Beikov 2024-08-06 16:14:15 +02:00 committed by Steve Ebersole
parent badf4f278f
commit a2c948909a
11 changed files with 294 additions and 74 deletions

View File

@ -221,9 +221,18 @@ public interface Initializer<Data extends InitializerData> {
boolean isEager();
/**
* Indicates whether this initializers has eager sub-initializers, that could initialize lazy state of existing objects.
* Indicates whether this initializer or one of its sub-parts could be made lazy.
*/
boolean hasEagerSubInitializers();
default boolean isLazyCapable() {
// Usually, every model part for which an initializer exists is lazy capable
// except for embeddable initializers with no sub-initializers
return true;
}
/**
* Indicates whether this initializer has sub-initializers which are lazy.
*/
boolean hasLazySubInitializers();
/**
* Indicates if this is a result or fetch initializer.

View File

@ -240,7 +240,7 @@ public abstract class AbstractCollectionInitializer<Data extends AbstractCollect
}
@Override
public boolean hasEagerSubInitializers() {
public boolean hasLazySubInitializers() {
return true;
}

View File

@ -44,7 +44,7 @@ public class DelayedCollectionInitializer extends AbstractNonJoinCollectionIniti
}
@Override
public boolean hasEagerSubInitializers() {
public boolean hasLazySubInitializers() {
return false;
}

View File

@ -6,11 +6,12 @@
*/
package org.hibernate.sql.results.graph.embeddable.internal;
import java.util.BitSet;
import java.util.Arrays;
import java.util.Collection;
import java.util.function.BiConsumer;
import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer;
import org.hibernate.engine.internal.ManagedTypeHelper;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.metamodel.mapping.AttributeMapping;
import org.hibernate.metamodel.mapping.EmbeddableMappingType;
@ -53,7 +54,7 @@ public class EmbeddableInitializerImpl extends AbstractInitializer<EmbeddableIni
private final NavigablePath navigablePath;
private final EmbeddableValuedModelPart embedded;
private final EmbeddableMappingType embeddableMappingType;
private final InitializerParent<InitializerData> parent;
private final @Nullable InitializerParent<InitializerData> parent;
private final boolean isResultInitializer;
private final boolean isPartOfKey;
private final boolean createEmptyCompositesEnabled;
@ -62,7 +63,10 @@ public class EmbeddableInitializerImpl extends AbstractInitializer<EmbeddableIni
protected final DomainResultAssembler<?>[][] assemblers;
protected final BasicResultAssembler<?> discriminatorAssembler;
protected final @Nullable Initializer<InitializerData>[][] subInitializers;
protected final BitSet subInitializersNeedingResolveIfParentInitialized;
protected final @Nullable Initializer<InitializerData>[][] subInitializersForResolveFromInitialized;
// protected final BitSet eagerSubInitializers;
protected final boolean lazyCapable;
protected final boolean hasLazySubInitializer;
public static class EmbeddableInitializerData extends InitializerData implements ValueAccess {
protected final InitializerData parentData;
@ -123,18 +127,42 @@ public class EmbeddableInitializerImpl extends AbstractInitializer<EmbeddableIni
(BasicResultAssembler<?>) discriminatorFetch.createAssembler( this, creationState ) :
null;
this.subInitializers = createInitializers( assemblers );
final BitSet subInitializersNeedingResolveIfParentInitialized = new BitSet(subInitializers.length * embeddableMappingType.getNumberOfFetchables());
final @Nullable Initializer<InitializerData>[][] eagerSubInitializers = new Initializer[subInitializers.length][];
Arrays.fill( eagerSubInitializers, Initializer.EMPTY_ARRAY );
// final BitSet eagerSubInitializers = new BitSet(subInitializers.length * embeddableMappingType.getNumberOfFetchables());
boolean lazyCapable = false;
boolean hasLazySubInitializers = false;
for ( int subclassId = 0; subclassId < subInitializers.length; subclassId++ ) {
final Initializer<InitializerData>[] subInitializer = subInitializers[subclassId];
for ( int i = 0; i < subInitializer.length; i++ ) {
final Initializer<InitializerData> initializer = subInitializer[i];
if ( initializer != null ) {
subInitializersNeedingResolveIfParentInitialized.set( subclassId * embeddableMappingType.getNumberOfFetchables() + i );
if ( initializer.isEager() ) {
if ( eagerSubInitializers[subclassId] == Initializer.EMPTY_ARRAY ) {
eagerSubInitializers[subclassId] = new Initializer[subInitializer.length];
}
eagerSubInitializers[subclassId][i] = initializer;
hasLazySubInitializers = hasLazySubInitializers || initializer.hasLazySubInitializers();
}
else {
hasLazySubInitializers = true;
}
lazyCapable = lazyCapable || initializer.isLazyCapable();
}
}
}
this.subInitializersNeedingResolveIfParentInitialized = subInitializersNeedingResolveIfParentInitialized;
this.subInitializersForResolveFromInitialized = isEnhancedForLazyLoading( embeddableMappingType )
? subInitializers
: eagerSubInitializers;
this.lazyCapable = lazyCapable;
this.hasLazySubInitializer = hasLazySubInitializers;
}
private static boolean isEnhancedForLazyLoading(EmbeddableMappingType embeddableMappingType) {
return ManagedTypeHelper.isPersistentAttributeInterceptableType(
embeddableMappingType.getRepresentationStrategy().getMappedJavaType().getJavaTypeClass()
);
}
protected DomainResultAssembler<?>[][] createAssemblers(
@ -224,9 +252,13 @@ public class EmbeddableInitializerImpl extends AbstractInitializer<EmbeddableIni
}
@Override
public boolean hasEagerSubInitializers() {
// Since embeddables are never lazy, we only need to check the components
return !subInitializersNeedingResolveIfParentInitialized.isEmpty();
public boolean isLazyCapable() {
return lazyCapable;
}
@Override
public boolean hasLazySubInitializers() {
return hasLazySubInitializer;
}
@Override
@ -328,17 +360,10 @@ public class EmbeddableInitializerImpl extends AbstractInitializer<EmbeddableIni
}
private void resolveInstanceSubInitializers(int subclassId, Object instance, RowProcessingState rowProcessingState) {
final int fetchables = embedded.getNumberOfFetchables();
final int startIndex = subclassId * fetchables;
final int fetchableIndexNeedingResolve = subInitializersNeedingResolveIfParentInitialized.nextSetBit( startIndex );
if ( fetchableIndexNeedingResolve < 0 || fetchableIndexNeedingResolve >= startIndex + fetchables ) {
return;
}
final Initializer<InitializerData>[] subInitializer = subInitializers[subclassId];
for ( int i = 0; i < subInitializer.length; i++ ) {
if ( subInitializersNeedingResolveIfParentInitialized.get( startIndex + i ) ) {
final Initializer<?> initializer = subInitializer[i];
assert initializer != null;
final Initializer<?>[] initializers = subInitializersForResolveFromInitialized[subclassId];
for ( int i = 0; i < initializers.length; i++ ) {
final Initializer<?> initializer = initializers[i];
if ( initializer != null ) {
final Object subInstance = embeddableMappingType.getValue( instance, i );
if ( subInstance == LazyPropertyInitializer.UNFETCHED_PROPERTY ) {
// Go through the normal initializer process

View File

@ -6,7 +6,6 @@
*/
package org.hibernate.sql.results.graph.embeddable.internal;
import java.util.BitSet;
import java.util.function.BiConsumer;
import java.util.function.Function;
@ -58,7 +57,9 @@ public class NonAggregatedIdentifierMappingInitializer extends AbstractInitializ
private final DomainResultAssembler<?>[] assemblers;
private final @Nullable Initializer<InitializerData>[] initializers;
private final BitSet subInitializersNeedingResolveIfParentInitialized;
private final @Nullable Initializer<InitializerData>[] subInitializersForResolveFromInitialized;
private final boolean lazyCapable;
private final boolean hasLazySubInitializer;
private final boolean hasIdClass;
public static class NonAggregatedIdentifierMappingInitializerData extends InitializerData implements ValueAccess {
@ -125,14 +126,23 @@ public class NonAggregatedIdentifierMappingInitializer extends AbstractInitializ
this.sessionFactory = creationState.getSqlAstCreationContext().getSessionFactory();
this.assemblers = createAssemblers( this, resultDescriptor, creationState, virtualIdEmbeddable, fetchConverter );
final Initializer<?>[] initializers = new Initializer[assemblers.length];
final BitSet subInitializersNeedingResolveIfParentInitialized = new BitSet(assemblers.length);
final Initializer<?>[] eagerSubInitializers = new Initializer[assemblers.length];
boolean empty = true;
boolean emptyEager = true;
boolean lazyCapable = false;
boolean hasLazySubInitializers = false;
for ( int i = 0; i < assemblers.length; i++ ) {
final Initializer<?> initializer = assemblers[i].getInitializer();
if ( initializer != null ) {
if ( initializer.isEager() ) {
subInitializersNeedingResolveIfParentInitialized.set( i );
eagerSubInitializers[i] = initializer;
hasLazySubInitializers = hasLazySubInitializers || initializer.hasLazySubInitializers();
emptyEager = false;
}
else {
hasLazySubInitializers = true;
}
lazyCapable = lazyCapable || initializer.isLazyCapable();
initializers[i] = initializer;
empty = false;
}
@ -141,7 +151,12 @@ public class NonAggregatedIdentifierMappingInitializer extends AbstractInitializ
this.initializers = (Initializer<InitializerData>[]) (
empty ? Initializer.EMPTY_ARRAY : initializers
);
this.subInitializersNeedingResolveIfParentInitialized = subInitializersNeedingResolveIfParentInitialized;
// No need to think about bytecode enhancement here, since ids can't contain lazy basic attributes
this.subInitializersForResolveFromInitialized = (Initializer<InitializerData>[]) (
emptyEager ? Initializer.EMPTY_ARRAY : initializers
);
this.lazyCapable = lazyCapable;
this.hasLazySubInitializer = hasLazySubInitializers;
}
protected static DomainResultAssembler<?>[] createAssemblers(
@ -260,13 +275,9 @@ public class NonAggregatedIdentifierMappingInitializer extends AbstractInitializ
}
private void resolveInstanceSubInitializers(Object instance, RowProcessingState rowProcessingState) {
if ( subInitializersNeedingResolveIfParentInitialized.nextSetBit( 0 ) < 0) {
return;
}
for ( int i = 0; i < initializers.length; i++ ) {
if ( subInitializersNeedingResolveIfParentInitialized.get( i ) ) {
final Initializer<InitializerData> initializer = initializers[i];
assert initializer != null;
for ( int i = 0; i < subInitializersForResolveFromInitialized.length; i++ ) {
final Initializer<InitializerData> initializer = subInitializersForResolveFromInitialized[i];
if ( initializer != null ) {
final Object subInstance = virtualIdEmbeddable.getValue( instance, i );
if ( subInstance == LazyPropertyInitializer.UNFETCHED_PROPERTY ) {
// Go through the normal initializer process
@ -370,9 +381,13 @@ public class NonAggregatedIdentifierMappingInitializer extends AbstractInitializ
}
@Override
public boolean hasEagerSubInitializers() {
// Since embeddables are never lazy, we only need to check the components
return !subInitializersNeedingResolveIfParentInitialized.isEmpty();
public boolean isLazyCapable() {
return lazyCapable;
}
@Override
public boolean hasLazySubInitializers() {
return hasLazySubInitializer;
}
/*

View File

@ -52,6 +52,7 @@ public class DiscriminatedEntityInitializer
private final boolean eager;
private final boolean resultInitializer;
private final boolean keyIsEager;
private final boolean hasLazySubInitializer;
public static class DiscriminatedEntityInitializerData extends InitializerData {
protected EntityPersister concreteDescriptor;
@ -80,9 +81,15 @@ public class DiscriminatedEntityInitializer
this.keyValueAssembler = keyFetch.createAssembler( this, creationState );
this.eager = eager;
this.resultInitializer = resultInitializer;
this.keyIsEager = keyValueAssembler != null
&& keyValueAssembler.getInitializer() != null
&& keyValueAssembler.getInitializer().isEager();
final Initializer<?> initializer = keyValueAssembler.getInitializer();
if ( initializer == null ) {
this.keyIsEager = false;
this.hasLazySubInitializer = false;
}
else {
this.keyIsEager = initializer.isEager();
this.hasLazySubInitializer = !initializer.isEager() || initializer.hasLazySubInitializers();
}
}
@Override
@ -316,8 +323,8 @@ public class DiscriminatedEntityInitializer
}
@Override
public boolean hasEagerSubInitializers() {
return keyIsEager;
public boolean hasLazySubInitializers() {
return hasLazySubInitializer;
}
@Override

View File

@ -55,6 +55,7 @@ public class EntityDelayedFetchInitializer
private final DomainResultAssembler<?> identifierAssembler;
private final @Nullable BasicResultAssembler<?> discriminatorAssembler;
private final boolean keyIsEager;
private final boolean hasLazySubInitializer;
public static class EntityDelayedFetchInitializerData extends InitializerData {
// per-row state
@ -86,9 +87,15 @@ public class EntityDelayedFetchInitializer
this.discriminatorAssembler = discriminatorResult == null
? null
: (BasicResultAssembler<?>) discriminatorResult.createResultAssembler( this, creationState );
this.keyIsEager = identifierAssembler != null
&& identifierAssembler.getInitializer() != null
&& identifierAssembler.getInitializer().isEager();
final Initializer<?> initializer;
if ( identifierAssembler == null || ( initializer = identifierAssembler.getInitializer() ) == null ) {
this.keyIsEager = false;
this.hasLazySubInitializer = false;
}
else {
this.keyIsEager = initializer.isEager();
this.hasLazySubInitializer = !initializer.isEager() || initializer.hasLazySubInitializers();
}
}
@Override
@ -291,8 +298,8 @@ public class EntityDelayedFetchInitializer
}
@Override
public boolean hasEagerSubInitializers() {
return keyIsEager;
public boolean hasLazySubInitializers() {
return hasLazySubInitializer;
}
@Override

View File

@ -132,9 +132,9 @@ public class EntityInitializerImpl extends AbstractInitializer<EntityInitializer
private final Initializer<?>[][] subInitializers;
private final Initializer<?>[][] subInitializersForResolveFromInitialized;
private final MutabilityPlan<Object>[][] updatableAttributeMutabilityPlans;
private final ImmutableBitSet[] lazySubInitializers;
private final ImmutableBitSet[] lazySets;
private final ImmutableBitSet[] maybeLazySets;
private final boolean hasEagerSubInitializers;
private final boolean hasLazySubInitializers;
public static class EntityInitializerData extends InitializerData {
@ -280,7 +280,7 @@ public class EntityInitializerImpl extends AbstractInitializer<EntityInitializer
final DomainResultAssembler<?>[][] assemblers = new DomainResultAssembler[subMappingTypes.size() + 1][];
final Initializer<?>[][] subInitializers = new Initializer<?>[subMappingTypes.size() + 1][];
final Initializer<?>[][] eagerSubInitializers = new Initializer<?>[subMappingTypes.size() + 1][];
final BitSet[] lazySubInitializers = new BitSet[subMappingTypes.size() + 1];
final BitSet[] lazySets = new BitSet[subMappingTypes.size() + 1];
final BitSet[] maybeLazySets = new BitSet[subMappingTypes.size() + 1];
final MutabilityPlan[][] updatableAttributeMutabilityPlans = new MutabilityPlan[subMappingTypes.size() + 1][];
assemblers[rootEntityDescriptor.getSubclassId()] = new DomainResultAssembler[rootEntityDescriptor.getNumberOfFetchables()];
@ -291,7 +291,7 @@ public class EntityInitializerImpl extends AbstractInitializer<EntityInitializer
updatableAttributeMutabilityPlans[subMappingType.getSubclassId()] = new MutabilityPlan[subMappingType.getNumberOfAttributeMappings()];
}
boolean hasEagerSubInitializers = false;
boolean hasLazySubInitializers = false;
final int size = entityDescriptor.getNumberOfFetchables();
for ( int i = 0; i < size; i++ ) {
final AttributeMapping attributeMapping = entityDescriptor.getFetchable( i ).asAttributeMapping();
@ -309,20 +309,22 @@ public class EntityInitializerImpl extends AbstractInitializer<EntityInitializer
if ( subInitializers[subclassId] == null ) {
subInitializers[subclassId] = new Initializer<?>[size];
eagerSubInitializers[subclassId] = new Initializer<?>[size];
lazySubInitializers[subclassId] = new BitSet(size);
maybeLazySets[subclassId] = new BitSet(size);
lazySets[subclassId] = new BitSet( size );
maybeLazySets[subclassId] = new BitSet( size );
}
subInitializers[subclassId][stateArrayPosition] = subInitializer;
if ( subInitializer.isEager() ) {
hasEagerSubInitializers = true;
eagerSubInitializers[subclassId][stateArrayPosition] = subInitializer;
if ( subInitializer.hasEagerSubInitializers() ) {
if ( subInitializer.hasLazySubInitializers() ) {
maybeLazySets[subclassId].set( stateArrayPosition );
hasLazySubInitializers = true;
}
}
else {
lazySubInitializers[subclassId].set( stateArrayPosition );
// Lazy initializer
lazySets[subclassId].set( stateArrayPosition );
maybeLazySets[subclassId].set( stateArrayPosition );
hasLazySubInitializers = true;
}
}
@ -338,19 +340,18 @@ public class EntityInitializerImpl extends AbstractInitializer<EntityInitializer
if ( subInitializers[subMappingType.getSubclassId()] == null ) {
subInitializers[subMappingType.getSubclassId()] = new Initializer<?>[size];
eagerSubInitializers[subMappingType.getSubclassId()] = new Initializer<?>[size];
lazySubInitializers[subMappingType.getSubclassId()] = new BitSet(size);
lazySets[subMappingType.getSubclassId()] = new BitSet(size);
maybeLazySets[subMappingType.getSubclassId()] = new BitSet(size);
}
subInitializers[subMappingType.getSubclassId()][stateArrayPosition] = subInitializer;
if ( subInitializer.isEager() ) {
hasEagerSubInitializers = true;
eagerSubInitializers[subMappingType.getSubclassId()][stateArrayPosition] = subInitializer;
if ( subInitializer.hasEagerSubInitializers() ) {
if ( subInitializer.hasLazySubInitializers() ) {
maybeLazySets[subMappingType.getSubclassId()].set( stateArrayPosition );
}
}
else {
lazySubInitializers[subMappingType.getSubclassId()].set( stateArrayPosition );
lazySets[subMappingType.getSubclassId()].set( stateArrayPosition );
maybeLazySets[subMappingType.getSubclassId()].set( stateArrayPosition );
}
}
@ -366,7 +367,7 @@ public class EntityInitializerImpl extends AbstractInitializer<EntityInitializer
}
}
subInitializers[i] = Initializer.EMPTY_ARRAY;
lazySubInitializers[i] = emptyBitSet;
lazySets[i] = emptyBitSet;
maybeLazySets[i] = emptyBitSet;
}
OUTER: for ( int i = 0; i < eagerSubInitializers.length; i++ ) {
@ -385,9 +386,11 @@ public class EntityInitializerImpl extends AbstractInitializer<EntityInitializer
this.subInitializersForResolveFromInitialized = rootEntityDescriptor.getBytecodeEnhancementMetadata().isEnhancedForLazyLoading()
? subInitializers
: eagerSubInitializers;
this.lazySubInitializers = Arrays.stream(lazySubInitializers).map( ImmutableBitSet::valueOf ).toArray(ImmutableBitSet[]::new);
this.maybeLazySets = Arrays.stream(maybeLazySets).map( ImmutableBitSet::valueOf ).toArray(ImmutableBitSet[]::new);
this.hasEagerSubInitializers = hasEagerSubInitializers;
this.lazySets = Arrays.stream( lazySets ).map( ImmutableBitSet::valueOf ).toArray( ImmutableBitSet[]::new );
this.maybeLazySets = Arrays.stream( maybeLazySets )
.map( ImmutableBitSet::valueOf )
.toArray( ImmutableBitSet[]::new );
this.hasLazySubInitializers = hasLazySubInitializers;
this.updatableAttributeMutabilityPlans = updatableAttributeMutabilityPlans;
this.notFoundAction = notFoundAction;
@ -559,7 +562,7 @@ public class EntityInitializerImpl extends AbstractInitializer<EntityInitializer
// or the entity entry has a lazy set available
|| ( maybeLazySet = ( entityEntry = data.entityHolder.getEntityEntry() ).getMaybeLazySet() ) != null
// which is contained in the lazy sub-initializers
&& lazySubInitializers[subclassId].contains( maybeLazySet ) ) {
&& lazySets[subclassId].contains( maybeLazySet ) ) {
return;
}
final RowProcessingState rowProcessingState = data.getRowProcessingState();
@ -1585,8 +1588,8 @@ public class EntityInitializerImpl extends AbstractInitializer<EntityInitializer
}
@Override
public boolean hasEagerSubInitializers() {
return hasEagerSubInitializers;
public boolean hasLazySubInitializers() {
return hasLazySubInitializers;
}
public boolean isPreviousRowReuse() {

View File

@ -53,6 +53,7 @@ public class EntitySelectFetchInitializer<Data extends EntitySelectFetchInitiali
protected final ToOneAttributeMapping toOneMapping;
protected final boolean affectedByFilter;
protected final boolean keyIsEager;
protected final boolean hasLazySubInitializer;
public static class EntitySelectFetchInitializerData extends InitializerData {
// per-row state
@ -88,9 +89,15 @@ public class EntitySelectFetchInitializer<Data extends EntitySelectFetchInitiali
this.keyAssembler = keyResult.createResultAssembler( this, creationState );
this.isEnhancedForLazyLoading = concreteDescriptor.getBytecodeEnhancementMetadata().isEnhancedForLazyLoading();
this.affectedByFilter = affectedByFilter;
this.keyIsEager = keyAssembler != null
&& keyAssembler.getInitializer() != null
&& keyAssembler.getInitializer().isEager();
final Initializer<?> initializer = keyAssembler.getInitializer();
if ( initializer == null ) {
this.keyIsEager = false;
this.hasLazySubInitializer = false;
}
else {
this.keyIsEager = initializer.isEager();
this.hasLazySubInitializer = !initializer.isEager() || initializer.hasLazySubInitializers();
}
}
@Override
@ -321,8 +328,8 @@ public class EntitySelectFetchInitializer<Data extends EntitySelectFetchInitiali
}
@Override
public boolean hasEagerSubInitializers() {
return keyIsEager;
public boolean hasLazySubInitializers() {
return hasLazySubInitializer;
}
@Override

View File

@ -12,6 +12,7 @@ import java.util.List;
import org.hibernate.Hibernate;
import org.hibernate.testing.orm.junit.EntityManagerFactoryScope;
import org.hibernate.testing.orm.junit.Jira;
import org.hibernate.testing.orm.junit.Jpa;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
@ -32,6 +33,7 @@ import jakarta.persistence.Table;
@Jpa(
annotatedClasses = { ReloadEntityTest.Book.class, ReloadEntityTest.Author.class, ReloadEntityTest.AuthorDetails.class }
)
@Jira("https://hibernate.atlassian.net/browse/HHH-18271")
public class ReloadEntityTest {
@BeforeEach

View File

@ -0,0 +1,145 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.orm.test.query;
import java.util.ArrayList;
import java.util.List;
import org.hibernate.Hibernate;
import org.hibernate.testing.orm.junit.EntityManagerFactoryScope;
import org.hibernate.testing.orm.junit.Jira;
import org.hibernate.testing.orm.junit.Jpa;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import jakarta.persistence.OneToOne;
import jakarta.persistence.Table;
@Jpa(
annotatedClasses = { ReloadWithPreviousRowEntityTest.Book.class, ReloadWithPreviousRowEntityTest.Author.class, ReloadWithPreviousRowEntityTest.AuthorDetails.class }
)
@Jira("https://hibernate.atlassian.net/browse/HHH-18271")
public class ReloadWithPreviousRowEntityTest {
@BeforeEach
public void prepareTestData(EntityManagerFactoryScope scope) {
scope.inTransaction( entityManager -> {
final Book book1 = new Book();
book1.name = "Book 1";
final Book book2 = new Book();
book2.name = "Book 2";
final Book book3 = new Book();
book3.name = "Book 3";
final Author author1 = new Author();
author1.name = "Author 1";
final Author author2 = new Author();
author2.name = "Author 2";
final AuthorDetails details1 = new AuthorDetails();
details1.name = "Author Details";
details1.author = author1;
author1.details = details1;
final AuthorDetails details2 = new AuthorDetails();
details2.name = "Author Details";
details2.author = author2;
author2.details = details2;
author1.books.add( book1 );
author1.books.add( book2 );
author1.books.add( book3 );
book1.author = author1;
book2.author = author1;
book3.author = author2;
details1.favoriteBook = book3;
entityManager.persist( author1 );
entityManager.persist( author2 );
entityManager.persist( book1 );
entityManager.persist( book2 );
entityManager.persist( book3 );
} );
}
@Test
public void testReloadWithPreviousRow(EntityManagerFactoryScope scope) {
scope.inEntityManager( em -> {
// Load authors into persistence context
Author author = em.createQuery( "from Author a join fetch a.details where a.name = 'Author 1'", Author.class ).getSingleResult();
em.createQuery( "from Author a join fetch a.details d left join fetch d.favoriteBook join fetch a.books where a.name = 'Author 1'", Author.class ).getResultList();
Assertions.assertTrue( Hibernate.isInitialized( author.details.favoriteBook ) );
Assertions.assertTrue( Hibernate.isInitialized( author.books ) );
} );
}
@Entity(name = "Author")
@Table(name = "Author")
public static class Author {
@Id
@GeneratedValue
public Long authorId;
@Column
public String name;
@OneToMany(fetch = FetchType.LAZY, mappedBy = "author")
public List<Book> books = new ArrayList<>();
@OneToOne(optional = false, fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
public AuthorDetails details;
}
@Entity(name = "AuthorDetails")
@Table(name = "AuthorDetails")
public static class AuthorDetails {
@Id
@GeneratedValue
public Long detailsId;
@Column
public String name;
@OneToOne(fetch = FetchType.LAZY, mappedBy = "details", optional = false)
public Author author;
@ManyToOne(fetch = FetchType.LAZY)
public Book favoriteBook;
public String getName() {
return name;
}
}
@Entity(name = "Book")
@Table(name = "Book")
public static class Book {
@Id
@GeneratedValue
public Long bookId;
@Column
public String name;
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "author_id", nullable = false)
public Author author;
}
}