LOAD event is triggered before EAGER associations are loaded

Description

Consider the following simplified example:

When I call

Then hibernate executes the following SQL (simplified a little bit, for confidentiality)

This is the kind of 1+N SQL queries that I would expect to see if I had set FetchMode.SELECT, and does not seem consistent with FetchMode.SUBSELECT

If I comment out the @PostLoad annotation on Container.postLoad(), then hibernate runs the following SQL queries:

Which is what I had expected it to do.

The @PostLoad callback is indeed called AFTER all the attributes of the entities are loaded, but BEFORE any of the associations are loaded (even before eagerly fetches ones).

In my @PostLoad callback, I access those eagerly fetched associations (which are collections), and that access triggers an early loading of the collection/associations. Since it is loaded within the context of a single entity, it uses a standard select.

Once all of the container entities have executed their @PostLoad callback, hibernate tries to load the associations, but since the associations have already been populated for all of the container entities, there is nothing to do anymore and the bulk SUBSELECT is skipped.

According to the JPA 2.1 Specification / JSR-338, this behavior is incorrect:

3.5.3 Semantics of the Life Cycle Callback Methods for Entities
[...]
The PostLoad method for an entity is invoked after the entity has been loaded into the current persistence context from the database or after the refresh operation has been applied to it.

and

3.2.9 Load State
An entity is considered to be loaded if all attributes with FetchType.EAGER —whether explictly specified or by default— (including relationship and other collection-valued attributes) have been loaded from the database or assigned by the application. Attributes with FetchType.LAZY may or may not have been loaded.

Environment

JVM: Oracle Java SE 8
Database: Oracle XE 11g, with ojdbc8 12.2.0.1 drivers

Activity

Show:
Nicolas Piguet
February 9, 2018, 12:24 PM

I've attached a test case that reproduces the problem. Unfortunately, I don't know how to do assertions on hibernate logs, or on the total number of queries that were run, so JUnit always considers it as passing.

When run it displays, the faulty behavior by printing the following logs (1 select to fetch all the containers, then N extra selects to fetch the children of each container independently):

However, if you comment out Container.postLoad, it results in the expected behavior (1 select to fetch all the containers + 1 select to fetch all of the children of all containers in one query):

Nicolas Piguet
February 9, 2018, 12:43 PM

Note that I've tried hooking into the event in different ways, using Interceptor and PostLoadEventListener and even InitializeCollectionEventListener but none of them is called at the appropriate time, and each attempt has resulted either in complete failure, or in the faulty case mentionned above.

Nicolas Piguet
February 12, 2018, 10:23 AM

After further research, it looks like the Hibernate behavior in this case is indeed wrong according to the JSR-338 specification, which states:

3.5.3 Semantics of the Life Cycle Callback Methods for Entities
[...]
The PostLoad method for an entity is invoked after the entity has been loaded into the current persistence
context from the database or after the refresh operation has been applied to it.

and

3.2.9 Load State
An entity is considered to be loaded if all attributes with FetchType.EAGER
—whether explictly specified or by default— (including relationship and other
collection-valued attributes) have been loaded from the database or assigned by the application.
Attributes with FetchType.LAZY may or may not have been loaded.

Nicolas Piguet
February 12, 2018, 12:07 PM

Added a V2 test case that properly uses JUnit and SessionEventListener to pass or fail the test.

Nicolas Piguet
February 12, 2018, 12:39 PM

Updated the bug description to match my clearer understanding of the problem.

Assignee

Unassigned

Reporter

Nicolas Piguet

Fix versions

None

Labels

None

backPortable

None

Suitable for new contributors

None

Requires Release Note

None

Pull Request

None

backportDecision

None

Components

Affects versions

Priority

Major
Configure