diff --git a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/applicationfieldaccess/GetterAccessAssociationsTest.java b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/applicationfieldaccess/GetterAccessAssociationsTest.java new file mode 100644 index 0000000000000..b11e678d654c0 --- /dev/null +++ b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/applicationfieldaccess/GetterAccessAssociationsTest.java @@ -0,0 +1,464 @@ +package io.quarkus.hibernate.orm.applicationfieldaccess; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.util.ArrayList; +import java.util.List; + +import javax.inject.Inject; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EntityManager; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.JoinTable; +import javax.persistence.ManyToMany; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.OneToOne; +import javax.transaction.HeuristicMixedException; +import javax.transaction.HeuristicRollbackException; +import javax.transaction.NotSupportedException; +import javax.transaction.RollbackException; +import javax.transaction.SystemException; +import javax.transaction.UserTransaction; + +import org.hibernate.Hibernate; +import org.hibernate.LazyInitializationException; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; + +/** + * Checks that access to fields through getters by the application works correctly for all association types. + */ +public class GetterAccessAssociationsTest { + + private static final String CONTAINED_VALUE = "someValue"; + + @RegisterExtension + static QuarkusUnitTest runner = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addClass(ContainingEntity.class) + .addClass(AccessDelegate.class)) + .withConfigurationResource("application-fetch-max-depth-zero.properties"); + + @Inject + EntityManager em; + + @Inject + UserTransaction transaction; + + @Test + public void testGetterAccess() + throws SystemException, NotSupportedException, HeuristicRollbackException, HeuristicMixedException, + RollbackException { + // Ideally we'd write a @ParameterizedTest and pass the delegates as parameters, + // but we cannot do that due to JUnit using a different classloader than the test. + for (AccessDelegate delegate : AccessDelegate.values()) { + doTestGetterAccess(delegate); + } + } + + private void doTestGetterAccess(AccessDelegate delegate) + throws SystemException, NotSupportedException, HeuristicRollbackException, HeuristicMixedException, + RollbackException { + ContainingEntity entity = new ContainingEntity(); + ContainedEntity containedEntity = new ContainedEntity(); + containedEntity.setValue(CONTAINED_VALUE); + + transaction.begin(); + em.persist(entity); + em.persist(containedEntity); + transaction.commit(); + + transaction.begin(); + entity = em.getReference(ContainingEntity.class, entity.getId()); + containedEntity = em.getReference(ContainedEntity.class, containedEntity.getId()); + // Initially the assertion doesn't pass: the value was not set yet + AssertionError expected = null; + try { + delegate.assertValueAndLaziness(entity, containedEntity); + } catch (AssertionError e) { + expected = e; + } + if (expected == null) { + throw new IllegalStateException("This test is buggy: assertions should not pass at this point."); + } + transaction.rollback(); + + transaction.begin(); + entity = em.getReference(ContainingEntity.class, entity.getId()); + containedEntity = em.getReference(ContainedEntity.class, containedEntity.getId()); + // Since field access is replaced with accessor calls, + // we expect this change to be detected by dirty tracking and persisted. + delegate.setValue(entity, containedEntity); + transaction.commit(); + + // Test getReference() + transaction.begin(); + entity = em.getReference(ContainingEntity.class, entity.getId()); + containedEntity = em.getReference(ContainedEntity.class, containedEntity.getId()); + // We're working on an uninitialized proxy. + assertThat(entity).returns(false, Hibernate::isInitialized); + // The above should have persisted a value that passes the assertion. + delegate.assertValueAndLaziness(entity, containedEntity); + // Accessing the value should trigger initialization of the proxy. + assertThat(entity).returns(true, Hibernate::isInitialized); + transaction.commit(); + + // Test find() + transaction.begin(); + entity = em.find(ContainingEntity.class, entity.getId()); + containedEntity = em.find(ContainedEntity.class, containedEntity.getId()); + // We're working on an actual entity instance (not a proxy). + assertThat(entity).returns(true, Hibernate::isInitialized); + // The above should have persisted a value that passes the assertion. + delegate.assertValueAndLaziness(entity, containedEntity); + transaction.commit(); + + // Test find() + access outside of session + transaction.begin(); + entity = em.find(ContainingEntity.class, entity.getId()); + containedEntity = em.find(ContainedEntity.class, containedEntity.getId()); + // We're working on an actual entity instance (not a proxy). + assertThat(entity).returns(true, Hibernate::isInitialized); + transaction.commit(); + // Access to the property out of session should pass certain assertions + // (which are different depending on the type of association). + delegate.assertAccessOutOfSession(entity, containedEntity); + } + + @Entity + public static class ContainingEntity { + + @Id + @GeneratedValue + public long id; + + @OneToOne + private ContainedEntity oneToOne; + + @ManyToOne + private ContainedEntity manyToOne; + + @OneToMany + @JoinTable(name = "containing_oneToMany") + private List oneToMany = new ArrayList<>(); + + @ManyToMany + @JoinTable(name = "containing_manyToMany") + private List manyToMany = new ArrayList<>(); + + @OneToOne(mappedBy = "oneToOne") + private ContainedEntity oneToOneMappedBy; + + @OneToMany(mappedBy = "manyToOne") + private List oneToManyMappedBy = new ArrayList<>(); + + @ManyToMany(mappedBy = "manyToMany") + private List manyToManyMappedBy = new ArrayList<>(); + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public ContainedEntity getOneToOne() { + return oneToOne; + } + + public void setOneToOne(ContainedEntity oneToOne) { + this.oneToOne = oneToOne; + } + + public ContainedEntity getManyToOne() { + return manyToOne; + } + + public void setManyToOne(ContainedEntity manyToOne) { + this.manyToOne = manyToOne; + } + + public List getOneToMany() { + return oneToMany; + } + + public void setOneToMany(List oneToMany) { + this.oneToMany = oneToMany; + } + + public List getManyToMany() { + return manyToMany; + } + + public void setManyToMany(List manyToMany) { + this.manyToMany = manyToMany; + } + + public ContainedEntity getOneToOneMappedBy() { + return oneToOneMappedBy; + } + + public void setOneToOneMappedBy(ContainedEntity oneToOneMappedBy) { + this.oneToOneMappedBy = oneToOneMappedBy; + } + + public List getOneToManyMappedBy() { + return oneToManyMappedBy; + } + + public void setOneToManyMappedBy(List oneToManyMappedBy) { + this.oneToManyMappedBy = oneToManyMappedBy; + } + + public List getManyToManyMappedBy() { + return manyToManyMappedBy; + } + + public void setManyToManyMappedBy(List manyToManyMappedBy) { + this.manyToManyMappedBy = manyToManyMappedBy; + } + } + + @Entity + public static class ContainedEntity { + + @Id + @GeneratedValue + private long id; + + @Column(name = "value_") + private String value; + + @OneToOne + private ContainingEntity oneToOne; + + @ManyToOne + private ContainingEntity manyToOne; + + @ManyToMany + @JoinTable(name = "containing_manyToMany_mappedBy") + private List manyToMany = new ArrayList<>(); + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public ContainingEntity getOneToOne() { + return oneToOne; + } + + public void setOneToOne(ContainingEntity oneToOne) { + this.oneToOne = oneToOne; + } + + public ContainingEntity getManyToOne() { + return manyToOne; + } + + public void setManyToOne(ContainingEntity manyToOne) { + this.manyToOne = manyToOne; + } + + public List getManyToMany() { + return manyToMany; + } + + public void setManyToMany(List manyToMany) { + this.manyToMany = manyToMany; + } + } + + private enum AccessDelegate { + + ONE_TO_ONE { + @Override + public void setValue(ContainingEntity entity, ContainedEntity containedEntity) { + entity.setOneToOne(containedEntity); + } + + @Override + public void assertValueAndLaziness(ContainingEntity entity, ContainedEntity containedEntity) { + // No expectations regarding laziness on ToOne associations + assertThat(entity.getOneToOne()).isEqualTo(containedEntity); + consumeValue(entity.getOneToOne()); + } + + @Override + public void assertAccessOutOfSession(ContainingEntity entity, ContainedEntity containedEntity) { + // No expectations regarding laziness on ToOne associations + assertThat(entity.getOneToOne()).isEqualTo(containedEntity); + consumeValue(entity.getOneToOne()); + } + }, + MANY_TO_ONE { + @Override + public void setValue(ContainingEntity entity, ContainedEntity containedEntity) { + entity.setManyToOne(containedEntity); + } + + @Override + public void assertValueAndLaziness(ContainingEntity entity, ContainedEntity containedEntity) { + // No expectations regarding laziness on ToOne associations + assertThat(entity.getManyToOne()).isEqualTo(containedEntity); + consumeValue(entity.getManyToOne()); + } + + @Override + public void assertAccessOutOfSession(ContainingEntity entity, ContainedEntity containedEntity) { + // No expectations regarding laziness on ToOne associations + assertThat(entity.getManyToOne()).isEqualTo(containedEntity); + consumeValue(entity.getManyToOne()); + } + }, + ONE_TO_MANY { + @Override + public void setValue(ContainingEntity entity, ContainedEntity containedEntity) { + entity.getOneToMany().add(containedEntity); + } + + @Override + public void assertValueAndLaziness(ContainingEntity entity, ContainedEntity containedEntity) { + assertThat((Object) entity.getOneToMany()).returns(false, Hibernate::isInitialized); + assertThat(entity.getOneToMany()).containsExactly(containedEntity); + assertThat((Object) entity.getOneToMany()).returns(true, Hibernate::isInitialized); + } + + @Override + public void assertAccessOutOfSession(ContainingEntity entity, ContainedEntity containedEntity) { + // We expect to be able to call the getter outside of the session on an initialized entity. + // https://github.com/quarkusio/quarkus/discussions/27657 + assertThatCode(() -> entity.getOneToMany()).doesNotThrowAnyException(); + // But of course, the collection is not initialized and accessing the content won't work. + var collection = entity.getOneToMany(); + assertThat((Object) collection).returns(false, Hibernate::isInitialized); + assertThatThrownBy(() -> collection.size()).isInstanceOf(LazyInitializationException.class); + } + }, + MANY_TO_MANY { + @Override + public void setValue(ContainingEntity entity, ContainedEntity containedEntity) { + entity.getManyToMany().add(containedEntity); + } + + @Override + public void assertValueAndLaziness(ContainingEntity entity, ContainedEntity containedEntity) { + assertThat((Object) entity.getManyToMany()).returns(false, Hibernate::isInitialized); + assertThat(entity.getManyToMany()).containsExactly(containedEntity); + assertThat((Object) entity.getManyToMany()).returns(true, Hibernate::isInitialized); + } + + @Override + public void assertAccessOutOfSession(ContainingEntity entity, ContainedEntity containedEntity) { + // We expect to be able to call the getter outside of the session on an initialized entity. + // https://github.com/quarkusio/quarkus/discussions/27657 + assertThatCode(() -> entity.getManyToMany()).doesNotThrowAnyException(); + // But of course, the collection is not initialized and accessing the content won't work. + var collection = entity.getManyToMany(); + assertThat((Object) collection).returns(false, Hibernate::isInitialized); + assertThatThrownBy(() -> collection.size()).isInstanceOf(LazyInitializationException.class); + } + }, + ONE_TO_ONE_MAPPED_BY { + @Override + public void setValue(ContainingEntity entity, ContainedEntity containedEntity) { + entity.setOneToOneMappedBy(containedEntity); + containedEntity.setOneToOne(entity); + } + + @Override + public void assertValueAndLaziness(ContainingEntity entity, ContainedEntity containedEntity) { + // No expectations regarding laziness on ToOne associations + assertThat(entity.getOneToOneMappedBy()).isEqualTo(containedEntity); + consumeValue(entity.getOneToOneMappedBy()); + } + + @Override + public void assertAccessOutOfSession(ContainingEntity entity, ContainedEntity containedEntity) { + // No expectations regarding laziness on ToOne associations + assertThat(entity.getOneToOneMappedBy()).isEqualTo(containedEntity); + consumeValue(entity.getOneToOneMappedBy()); + } + }, + ONE_TO_MANY_MAPPED_BY { + @Override + public void setValue(ContainingEntity entity, ContainedEntity containedEntity) { + entity.getOneToManyMappedBy().add(containedEntity); + containedEntity.setManyToOne(entity); + } + + @Override + public void assertValueAndLaziness(ContainingEntity entity, ContainedEntity containedEntity) { + assertThat((Object) entity.getOneToManyMappedBy()).returns(false, Hibernate::isInitialized); + assertThat(entity.getOneToManyMappedBy()).containsExactly(containedEntity); + assertThat((Object) entity.getOneToManyMappedBy()).returns(true, Hibernate::isInitialized); + } + + @Override + public void assertAccessOutOfSession(ContainingEntity entity, ContainedEntity containedEntity) { + // We expect to be able to call the getter outside of the session on an initialized entity. + // https://github.com/quarkusio/quarkus/discussions/27657 + assertThatCode(() -> entity.getOneToManyMappedBy()).doesNotThrowAnyException(); + // But of course, the collection is not initialized and accessing the content won't work. + var collection = entity.getOneToManyMappedBy(); + assertThat((Object) collection).returns(false, Hibernate::isInitialized); + assertThatThrownBy(() -> collection.size()).isInstanceOf(LazyInitializationException.class); + } + }, + MANY_TO_MANY_MAPPED_BY { + @Override + public void setValue(ContainingEntity entity, ContainedEntity containedEntity) { + entity.getManyToManyMappedBy().add(containedEntity); + containedEntity.getManyToMany().add(entity); + } + + @Override + public void assertValueAndLaziness(ContainingEntity entity, ContainedEntity containedEntity) { + assertThat((Object) entity.getManyToManyMappedBy()).returns(false, Hibernate::isInitialized); + assertThat(entity.getManyToManyMappedBy()).containsExactly(containedEntity); + assertThat((Object) entity.getManyToManyMappedBy()).returns(true, Hibernate::isInitialized); + } + + @Override + public void assertAccessOutOfSession(ContainingEntity entity, ContainedEntity containedEntity) { + // We expect to be able to call the getter outside of the session on an initialized entity. + // https://github.com/quarkusio/quarkus/discussions/27657 + assertThatCode(() -> entity.getManyToManyMappedBy()).doesNotThrowAnyException(); + // But of course, the collection is not initialized and accessing the content won't work. + var collection = entity.getManyToManyMappedBy(); + assertThat((Object) collection).returns(false, Hibernate::isInitialized); + assertThatThrownBy(() -> collection.size()).isInstanceOf(LazyInitializationException.class); + } + }; + + protected void consumeValue(ContainedEntity entity) { + assertThat(entity.getValue()).isEqualTo(CONTAINED_VALUE); + } + + public abstract void setValue(ContainingEntity entity, ContainedEntity containedEntity); + + public abstract void assertValueAndLaziness(ContainingEntity entity, ContainedEntity containedEntity); + + public abstract void assertAccessOutOfSession(ContainingEntity entity, ContainedEntity containedEntity); + + } +} diff --git a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/publicfields/PublicFieldAccessAssociationsTest.java b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/applicationfieldaccess/PublicFieldAccessAssociationsTest.java similarity index 98% rename from extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/publicfields/PublicFieldAccessAssociationsTest.java rename to extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/applicationfieldaccess/PublicFieldAccessAssociationsTest.java index d1501dcf4903c..eb33fe93856b5 100644 --- a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/publicfields/PublicFieldAccessAssociationsTest.java +++ b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/applicationfieldaccess/PublicFieldAccessAssociationsTest.java @@ -1,4 +1,4 @@ -package io.quarkus.hibernate.orm.publicfields; +package io.quarkus.hibernate.orm.applicationfieldaccess; import static org.assertj.core.api.Assertions.assertThat; @@ -30,8 +30,8 @@ import io.quarkus.test.QuarkusUnitTest; /** - * Checks that public field access is correctly replaced with getter/setter calls, - * regardless of the field type. + * Checks that access to public fields by the application is correctly replaced with getter/setter calls + * and works correctly for all association types. */ public class PublicFieldAccessAssociationsTest { diff --git a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/publicfields/PublicFieldAccessFieldTypesTest.java b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/applicationfieldaccess/PublicFieldAccessFieldTypesTest.java similarity index 97% rename from extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/publicfields/PublicFieldAccessFieldTypesTest.java rename to extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/applicationfieldaccess/PublicFieldAccessFieldTypesTest.java index 80435a245ddce..657bcbefb5a04 100644 --- a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/publicfields/PublicFieldAccessFieldTypesTest.java +++ b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/applicationfieldaccess/PublicFieldAccessFieldTypesTest.java @@ -1,4 +1,4 @@ -package io.quarkus.hibernate.orm.publicfields; +package io.quarkus.hibernate.orm.applicationfieldaccess; import static org.assertj.core.api.Assertions.assertThat; @@ -23,8 +23,8 @@ import io.quarkus.test.QuarkusUnitTest; /** - * Checks that public field access is correctly replaced with getter/setter calls, - * regardless of the field type. + * Checks that access to public fields by the application is correctly replaced with getter/setter calls + * and works correctly for all field types. */ public class PublicFieldAccessFieldTypesTest { diff --git a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/publicfields/PublicFieldAccessFinalFieldTest.java b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/applicationfieldaccess/PublicFieldAccessFinalFieldTest.java similarity index 98% rename from extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/publicfields/PublicFieldAccessFinalFieldTest.java rename to extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/applicationfieldaccess/PublicFieldAccessFinalFieldTest.java index aaed67aa68cad..14b8157df5b00 100644 --- a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/publicfields/PublicFieldAccessFinalFieldTest.java +++ b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/applicationfieldaccess/PublicFieldAccessFinalFieldTest.java @@ -4,7 +4,7 @@ * License: GNU Lesser General Public License (LGPL), version 2.1 or later. * See the lgpl.txt file in the root directory or . */ -package io.quarkus.hibernate.orm.publicfields; +package io.quarkus.hibernate.orm.applicationfieldaccess; import static org.assertj.core.api.Assertions.assertThat; @@ -30,7 +30,7 @@ import io.quarkus.test.QuarkusUnitTest; /** - * Checks that public, final field access is correctly replaced with getter calls for reads, + * Checks that access to public, final fields by the application is correctly replaced with getter calls for reads, * but not replaced at all for writes (since writes to final fields can only occur from constructors). *

* See https://github.com/quarkusio/quarkus/issues/20186 diff --git a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/publicfields/PublicFieldAccessInheritanceTest.java b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/applicationfieldaccess/PublicFieldAccessInheritanceTest.java similarity index 95% rename from extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/publicfields/PublicFieldAccessInheritanceTest.java rename to extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/applicationfieldaccess/PublicFieldAccessInheritanceTest.java index a2c6ab66ec783..21eca3aedf230 100644 --- a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/publicfields/PublicFieldAccessInheritanceTest.java +++ b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/applicationfieldaccess/PublicFieldAccessInheritanceTest.java @@ -1,4 +1,4 @@ -package io.quarkus.hibernate.orm.publicfields; +package io.quarkus.hibernate.orm.applicationfieldaccess; import static org.assertj.core.api.Assertions.assertThat; @@ -22,8 +22,8 @@ import io.quarkus.test.QuarkusUnitTest; /** - * Checks that public field access is correctly replaced with getter/setter calls, - * regardless of where the field is declared in the class hierarchy. + * Checks that access to public fields by the application is correctly replaced with getter/setter calls + * and works correctly regardless of where the field is declared in the class hierarchy. */ public class PublicFieldAccessInheritanceTest { diff --git a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/publicfields/PublicFieldWithProxyAndLazyLoadingAndInheritanceTest.java b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/applicationfieldaccess/PublicFieldWithProxyAndLazyLoadingAndInheritanceTest.java similarity index 98% rename from extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/publicfields/PublicFieldWithProxyAndLazyLoadingAndInheritanceTest.java rename to extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/applicationfieldaccess/PublicFieldWithProxyAndLazyLoadingAndInheritanceTest.java index b0a8ae6d00c0b..09ce9e5542bc9 100644 --- a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/publicfields/PublicFieldWithProxyAndLazyLoadingAndInheritanceTest.java +++ b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/applicationfieldaccess/PublicFieldWithProxyAndLazyLoadingAndInheritanceTest.java @@ -4,7 +4,7 @@ * License: GNU Lesser General Public License (LGPL), version 2.1 or later. * See the lgpl.txt file in the root directory or . */ -package io.quarkus.hibernate.orm.publicfields; +package io.quarkus.hibernate.orm.applicationfieldaccess; import static io.quarkus.hibernate.orm.TransactionTestUtils.inTransaction; import static org.assertj.core.api.Assertions.assertThat; diff --git a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/batch/BatchFetchSizeTest.java b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/batch/BatchFetchSizeTest.java index cc27d1a5bbf1b..cb27c4980500e 100644 --- a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/batch/BatchFetchSizeTest.java +++ b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/batch/BatchFetchSizeTest.java @@ -45,20 +45,20 @@ public void testDefaultBatchFetchSize() throws Exception { transaction.begin(); List entities = session.createQuery("from MainEntity", MainEntity.class).list(); - assertThat(entities).allSatisfy(e -> assertThat(Hibernate.isPropertyInitialized(e, "others")) + assertThat(entities).allSatisfy(e -> assertThat(Hibernate.isInitialized(e.others)) .as("'others' initialized for " + e).isFalse()); MainEntity entity = entities.get(0); // Trigger initialization for the collection from one entity. entity.others.get(0); - assertThat(Hibernate.isPropertyInitialized(entity, "others")) + assertThat(Hibernate.isInitialized(entity.others)) .as("'others' initialized for " + entity).isTrue(); // 20 entities were already in the session when the initialization above occurred, // so it should have triggered batch initialization of several 'others' collections. assertThat(entities).hasSize(20); assertThat(entities) - .filteredOn(e -> Hibernate.isPropertyInitialized(e, "others")) + .filteredOn(e -> Hibernate.isInitialized(e.others)) .hasSize(16); // Default batch fetch size is 16 transaction.commit(); } diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/FastBootEntityManagerFactoryBuilder.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/FastBootEntityManagerFactoryBuilder.java index 0fc026dcfebf9..cad28dcac61a9 100644 --- a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/FastBootEntityManagerFactoryBuilder.java +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/FastBootEntityManagerFactoryBuilder.java @@ -121,7 +121,6 @@ private String getExceptionHeader() { } protected void populate(String persistenceUnitName, SessionFactoryOptionsBuilder options, StandardServiceRegistry ssr) { - // will use user override value or default to false if not supplied to follow // JPA spec. final boolean jtaTransactionAccessEnabled = runtimeSettings.getBoolean( @@ -158,6 +157,11 @@ protected void populate(String persistenceUnitName, SessionFactoryOptionsBuilder options.applyEntityNotFoundDelegate(new JpaEntityNotFoundDelegate()); + // This is necessary for Hibernate Reactive, see https://github.com/quarkusio/quarkus/issues/15814 + // This is also necessary for Hibernate ORM if we want to prevent calls to getters on initialized entities + // outside of sessions from throwing exceptions, see https://github.com/quarkusio/quarkus/discussions/27657 + options.enableCollectionInDefaultFetchGroup(true); + if (this.validatorFactory != null) { options.applyValidatorFactory(validatorFactory); }