Query loader returns null embedded ID transient field

Description

Problem

Consider:

  • An entity whose embedded ID includes a transient field set on a non-transient field setter of the same class

  • A FullTextQuery (Hibernate Search) that returns projected FullTextQuery.THIS entities

If a single entity is returned, its ID transient field is correctly set to a non-null value. However, for two or more entities being returned, their ID transient fields are set to null.

Changing the database retrieval method from

1 fullTextQuery.initializeObjectsWith(ObjectLookupMethod.SKIP, DatabaseRetrievalMethod.QUERY);

to

1 fullTextQuery.initializeObjectsWith(ObjectLookupMethod.SKIP, DatabaseRetrievalMethod.FIND_BY_ID);

makes it work i.e. the ID transient field of each returned entity is correctly set to a non-null value, although at the cost of performance, as DatabaseRetrievalMethod.QUERY uses criteria query.

Ideally:

  1. Regardless of the DB retrieval method being used, the results should be the same

  2. The @PostLoad annotation could work for embedded IDs in order to avoid having transient fields set somewhere else (constructor or another field's setter method)

  • Findings*

The call trace to FullTextQuery.getResultList goes more or less like this:

1 2 3 4 5 6 FullTextQueryImpl.getResultList calls FullTextQueryImpl.list calls FullTextQueryImpl.doHibernateSearchList calls ProjectionLoader.load calls objectLoader.load AbstractLoader.load(List<EntityInfo> entityInfos) calls QueryLoader.executeLoad(executeLoad(List<EntityInfo> entityInfos))

At this point, the flow diverges for the different database retrieval methods:

For DatabaseRetrievalMethod.QUERY:

1 2 3 4 5 6 7 8 9 10 QueryLoader.executeLoad(executeLoad(List<EntityInfo> entityInfos)) calls CriteriaObjectInitializer calls CriteriaImpl.list CriteriaImpl.list calls SessionImpl.list SessionImpl.list calls CriteriaLoader.list CriteriaLoader.list calls CriteriaQueryTranslator.getQueryParameters CriteriaQueryTranslator.getQueryParameters calls Junction.getTypedValue (1) CriteriaLoader.list calls Loader.list Loader.list calls Loader.listIgnoreQueryCache Loader.listIgnoreQueryCache calls Loader.getResultList (2)

(1) returns non-transient fields only i.e. the transient field is lost
(2) based on (1), returns entities with non-transient fields only i.e. without the transient one

For DatabaseRetrievalMethod.FIND_BY_ID:

1 2 3 4 5 6 QueryLoader.executeLoad calls LookUpObjectInitializer calls ObjectLoaderHelper.load calls (3) ObjectLoaderHelper.executeLoad calls SessionImpl.load calls SessionImpl.doLoad (4)

(4) returns the entity with all fields (non-transient and transient) set.

Furthermore, before the part where the flow diverges, in the AbstractLoader class, there’s the following logic:

1 2 3 4 5 6 7 8 9 10 11 12 else if ( entityInfos.size() == 1 ) { final Object entity = executeLoad( entityInfos.get( 0 ) ); if ( entity == null ) { loadedObjects = Collections.EMPTY_LIST; } else { loadedObjects = Collections.singletonList( entity ); } } else { loadedObjects = executeLoad( entityInfos ); }

According to this logic, if a single entity is being loaded (else if block) then the flow goes to (3) i.e. the entity is returned with all fields (non-transient and transient) set. This seems to explain why, although not working for multiple entities, DatabaseRetrievalMethod.QUERY works when a single entity is being returned.

Discussion: https://discourse.hibernate.org/t/null-embedded-id-transient-field-for-multiple-returned-entities/1520/2

Environment

hibernate-core: 5.3.6.Final
hibernate-search-orm: 5.10.4.Final
hibernate-search-elasticsearch: 5.10.4.Final
DB: MySQL 5.7.22
JVM: Oracle 1.8.0_171-b11
OS: Ubuntu 16.04.4 LTS

Status

Assignee

Unassigned

Reporter

sant0s

Fix versions

None

Labels

backPortable

None

Suitable for new contributors

None

Requires Release Note

None

Pull Request

None

backportDecision

None

Components

Affects versions

5.3.6

Priority

Major
Configure