Fetching an Entity with a lazily loaded Embeddable with more fields than the parent results in an ArrayIndexOutOfBoundsException

Description

Fetching an Entity containing an Embeddable fails if the Embeddable has more fields than the Entity and is not loaded eagerly. This exception happens before the Entity is loaded from the database.

java.lang.ArrayIndexOutOfBoundsException: Index 1 out of bounds for length 1 at org.hibernate.metamodel.mapping.internal.BasicAttributeMapping.generateFetch(BasicAttributeMapping.java:304) at org.hibernate.sql.results.graph.FetchParent.generateFetchableFetch(FetchParent.java:105) at org.hibernate.loader.ast.internal.LoaderSelectBuilder.lambda$createFetchableBiConsumer$5(LoaderSelectBuilder.java:833) at org.hibernate.loader.ast.internal.LoaderSelectBuilder.visitFetches(LoaderSelectBuilder.java:672) at org.hibernate.loader.ast.internal.LoaderSqlAstCreationState.visitFetches(LoaderSqlAstCreationState.java:123) at org.hibernate.sql.results.graph.AbstractFetchParent.afterInitialize(AbstractFetchParent.java:32) at org.hibernate.sql.results.graph.embeddable.internal.EmbeddableFetchImpl.<init>(EmbeddableFetchImpl.java:75) at org.hibernate.metamodel.mapping.internal.EmbeddedAttributeMapping.generateFetch(EmbeddedAttributeMapping.java:238) at org.hibernate.sql.results.graph.FetchParent.generateFetchableFetch(FetchParent.java:105) at org.hibernate.loader.ast.internal.LoaderSelectBuilder.lambda$createFetchableBiConsumer$5(LoaderSelectBuilder.java:833) at org.hibernate.loader.ast.internal.LoaderSelectBuilder.visitFetches(LoaderSelectBuilder.java:672) at org.hibernate.loader.ast.internal.LoaderSqlAstCreationState.visitFetches(LoaderSqlAstCreationState.java:123) at org.hibernate.sql.results.graph.AbstractFetchParent.afterInitialize(AbstractFetchParent.java:32) at org.hibernate.sql.results.graph.entity.AbstractEntityResultGraphNode.afterInitialize(AbstractEntityResultGraphNode.java:100) at org.hibernate.persister.entity.AbstractEntityPersister.createDomainResult(AbstractEntityPersister.java:1298) at org.hibernate.loader.ast.internal.LoaderSelectBuilder.generateSelect(LoaderSelectBuilder.java:451) at org.hibernate.loader.ast.internal.LoaderSelectBuilder.createSelect(LoaderSelectBuilder.java:178) at org.hibernate.loader.ast.internal.SingleIdEntityLoaderStandardImpl.createLoadPlan(SingleIdEntityLoaderStandardImpl.java:180) at org.hibernate.loader.ast.internal.SingleIdEntityLoaderStandardImpl.resolveLoadPlan(SingleIdEntityLoaderStandardImpl.java:153) at org.hibernate.loader.ast.internal.SingleIdEntityLoaderStandardImpl.load(SingleIdEntityLoaderStandardImpl.java:66) at org.hibernate.persister.entity.AbstractEntityPersister.doLoad(AbstractEntityPersister.java:4380) at org.hibernate.persister.entity.AbstractEntityPersister.load(AbstractEntityPersister.java:4370) at org.hibernate.event.internal.DefaultLoadEventListener.loadFromDatasource(DefaultLoadEventListener.java:590) at org.hibernate.event.internal.DefaultLoadEventListener.loadFromCacheOrDatasource(DefaultLoadEventListener.java:576) at org.hibernate.event.internal.DefaultLoadEventListener.load(DefaultLoadEventListener.java:545) at org.hibernate.event.internal.DefaultLoadEventListener.doLoad(DefaultLoadEventListener.java:538) at org.hibernate.event.internal.DefaultLoadEventListener.load(DefaultLoadEventListener.java:200) at org.hibernate.event.internal.DefaultLoadEventListener.loadWithRegularProxy(DefaultLoadEventListener.java:280) at org.hibernate.event.internal.DefaultLoadEventListener.proxyOrLoad(DefaultLoadEventListener.java:235) at org.hibernate.event.internal.DefaultLoadEventListener.doOnLoad(DefaultLoadEventListener.java:104) at org.hibernate.event.internal.DefaultLoadEventListener.onLoad(DefaultLoadEventListener.java:76) at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:118) at org.hibernate.internal.SessionImpl.fireLoadNoChecks(SessionImpl.java:1221) at org.hibernate.internal.SessionImpl.fireLoad(SessionImpl.java:1209) at org.hibernate.loader.access.IdentifierLoadAccessImpl.doLoad(IdentifierLoadAccessImpl.java:192) at org.hibernate.loader.access.IdentifierLoadAccessImpl.lambda$load$1(IdentifierLoadAccessImpl.java:158) at org.hibernate.loader.access.IdentifierLoadAccessImpl.perform(IdentifierLoadAccessImpl.java:105) at org.hibernate.loader.access.IdentifierLoadAccessImpl.load(IdentifierLoadAccessImpl.java:158) at org.hibernate.internal.SessionImpl.find(SessionImpl.java:2336) at org.hibernate.internal.SessionImpl.find(SessionImpl.java:2303) at org.hibernate.orm.test.query.sql.EmbeddableLazyFetchTest.lambda$testSelect$2(EmbeddableLazyFetchTest.java:60) at org.hibernate.testing.orm.transaction.TransactionUtil.wrapInTransaction(TransactionUtil.java:49) at org.hibernate.testing.orm.transaction.TransactionUtil.inTransaction(TransactionUtil.java:24) at org.hibernate.testing.orm.junit.SessionFactoryExtension$SessionFactoryScopeImpl.inTransaction(SessionFactoryExtension.java:375) at org.hibernate.testing.orm.junit.SessionFactoryExtension$SessionFactoryScopeImpl.inTransaction(SessionFactoryExtension.java:352) at org.hibernate.orm.test.query.sql.EmbeddableLazyFetchTest.testSelect(EmbeddableLazyFetchTest.java:56)

BasicAttributeMapping.generateFetch seems to load the Entity’s property laziness with the Embeddable’s property index.

Since this bug is very similar to https://hibernate.atlassian.net/browse/HHH-15658, I have adapted its test case. In the test, the Embeddable is loaded lazily because it is omitted from the EntityGraph:

package org.hibernate.orm.test.query.sql; import java.util.Collections; import org.hibernate.graph.spi.RootGraphImplementor; import org.hibernate.testing.TestForIssue; 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.BeforeEach; import org.junit.jupiter.api.Test; import jakarta.persistence.Embeddable; import jakarta.persistence.Entity; import jakarta.persistence.Id; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; @DomainModel( annotatedClasses = EmbeddableLazyFetchTest.Person.class ) @SessionFactory @TestForIssue(jiraKey = "HHH-15778") public class EmbeddableLazyFetchTest { @BeforeEach public void setUp(SessionFactoryScope scope) { scope.inTransaction( session -> { Address address = new Address( "Milan", "Italy", "Italy", "20133" ); Person person = new Person( 1, address ); session.persist( person ); } ); } @AfterEach public void tearDown(SessionFactoryScope scope) { scope.inTransaction( session -> session.createMutationQuery( "delete from Person" ).executeUpdate() ); } @Test public void testSelect(SessionFactoryScope scope) { scope.inTransaction( session -> { RootGraphImplementor<Person> graph = session.createEntityGraph( Person.class ); Person person = session.find( Person.class, 1, Collections.singletonMap( "jakarta.persistence.fetchgraph", graph ) ); assertThat( person ).isNotNull(); } ); } @Entity(name = "Person") public static class Person { @Id private Integer id; private Address address; public Person() { } public Person(Integer id, Address address) { this.id = id; this.address = address; } } @Embeddable public static class Address { private String city; private String state; private String country; private String postcode; public Address() { } public Address(String city, String state, String country, String postcode) { this.city = city; this.state = state; this.country = country; this.postcode = postcode; } } }

Activity

Show:
Fixed

Details

Assignee

Reporter

Worked in

Components

Fix versions

Affects versions

Priority

Created November 29, 2022 at 10:10 AM
Updated February 7, 2023 at 11:20 AM
Resolved December 20, 2022 at 12:54 PM