| 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
fullTextQuery.initializeObjectsWith(ObjectLookupMethod.SKIP, DatabaseRetrievalMethod.QUERY);
to
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:
- Regardless of the DB retrieval method being used, the results should be the same
- 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)
The call trace to FullTextQuery.getResultList goes more or less like this:
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:
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:
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:
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 |