Xavier Portebois
Xavier Portebois

Reputation: 3504

@ManyToOne failed to lazy load when Hibernate second cache is enabled

I have created two really simple entities in my project:

@Entity
@Access(FIELD)
public class TestA implements Serializable
{
    @Id
    private UUID id;
    @Version
    private Long hVersion;

    @ManyToOne(fetch = FetchType.LAZY, optional = true)
    private TestB testB;

    // ...
}


@Entity
@Access(FIELD)
public class TestB implements Serializable
{
    @Id
    private UUID id;
    @Version
    private Long hVersion;

    // ...
}

We've got an optional @ManyToOne relation from TestA to TestB.

When I'm trying to fetch a TestA instance, like this:

entityManager.find(TestA.class, myId);

I'm getting two select: one for TestA, but also one for TestB, as it's eagerly loaded, which shouldn't happen.

Hibernate: select testa0_.id as id1_20_0_, testa0_.h_version as h_versio2_20_0_, testa0_.test_b_id as test_b_i3_20_0_ from test_a testa0_ where testa0_.id=?
Hibernate: select testb0_.id as id1_21_0_, testb0_.h_version as h_versio2_21_0_ from test_b testb0_ where testb0_.id=?

I tried all this combinations, even setting the relation as non-optional for the sake of testing:

@ManyToOne(fetch = FetchType.LAZY, optional = true)
private TestB testB;

@ManyToOne(fetch = FetchType.LAZY, optional = false)
private TestB testB;

@ManyToOne(fetch = FetchType.LAZY, optional = false)
@LazyToOne(LazyToOneOption.PROXY)
private TestB testB;

@ManyToOne(fetch = FetchType.LAZY, optional = false)
@LazyToOne(LazyToOneOption.NO_PROXY)
private TestB testB;

That doesn't change anything, TestB is still eagerly loaded.

However, when I'm disabling the second level cache in persistence.xml, like this:

<properties>
    <property name="hibernate.dialect" value="org.hibernate.dialect.PostgreSQLDialect" />
    <property name="hibernate.show_sql" value="true" />
    <property name="hibernate.debug" value="false" />
    <property name="hibernate.cache.use_second_level_cache" value="false" />
    <property name="hibernate.cache.use_query_cache" value="false" />
</properties>

Now TestB is lazy loaded, and I see the second select query only when I'm accessing TestA.getTestB().

When I'm looking at the logs, I can see that, with second level cache enabled, Hibernate resolve testB to put it in cache:

DEBUG [org.hibernate.resource.transaction.backend.jta.internal.JtaTransactionCoordinatorImpl] (default task-4) Hibernate RegisteredSynchronization successfully registered with JTA platform
DEBUG [org.hibernate.SQL] (default task-4) select testa0_.id as id1_20_0_, testa0_.h_version as h_versio2_20_0_, testa0_.test_b_id as test_b_i3_20_0_ from test_a testa0_ where testa0_.id=?
DEBUG [org.hibernate.loader.plan.exec.process.internal.ResultSetProcessorImpl] (default task-4) Starting ResultSet row #0
DEBUG [org.hibernate.loader.plan.exec.process.internal.EntityReferenceInitializerImpl] (default task-4) On call to EntityIdentifierReaderImpl#resolve, EntityKey was already known; should only happen on root returns with an optional identifier specified
DEBUG [org.hibernate.engine.internal.TwoPhaseLoad] (default task-4) Resolving associations for [com.monde3.lpt.veriqualis.model.test.TestA#00000000-9999-1111-1111-000000000001]
DEBUG [org.hibernate.engine.internal.TwoPhaseLoad] (default task-4) Adding entity to second-level cache: [com.monde3.lpt.veriqualis.model.test.TestA#00000000-9999-1111-1111-000000000001]
DEBUG [org.hibernate.internal.SessionImpl] (default task-4) Initializing proxy: [com.monde3.lpt.veriqualis.model.test.TestB#00000000-9999-1111-1111-000000000002]
DEBUG [org.hibernate.SQL] (default task-4) select testb0_.id as id1_21_0_, testb0_.h_version as h_versio2_21_0_ from test_b testb0_ where testb0_.id=?
DEBUG [org.hibernate.loader.plan.exec.process.internal.ResultSetProcessorImpl] (default task-4) Starting ResultSet row #0
DEBUG [org.hibernate.loader.plan.exec.process.internal.EntityReferenceInitializerImpl] (default task-4) On call to EntityIdentifierReaderImpl#resolve, EntityKey was already known; should only happen on root returns with an optional identifier specified
DEBUG [org.hibernate.engine.internal.TwoPhaseLoad] (default task-4) Resolving associations for [com.monde3.lpt.veriqualis.model.test.TestB#00000000-9999-1111-1111-000000000002]
DEBUG [org.hibernate.engine.internal.TwoPhaseLoad] (default task-4) Adding entity to second-level cache: [com.monde3.lpt.veriqualis.model.test.TestB#00000000-9999-1111-1111-000000000002]
DEBUG [org.hibernate.engine.internal.TwoPhaseLoad] (default task-4) Done materializing entity [com.monde3.lpt.veriqualis.model.test.TestB#00000000-9999-1111-1111-000000000002]
DEBUG [org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl] (default task-4) Skipping aggressive release due to registered resources
DEBUG [org.hibernate.loader.entity.plan.AbstractLoadPlanBasedEntityLoader] (default task-4) Done entity load : com.monde3.lpt.veriqualis.model.test.TestB#00000000-9999-1111-1111-000000000002

I'm absolutely helpless here, as I cannot explain this behavior. Is there a mistake or a bad practice somewhere in my code?

Upvotes: 3

Views: 823

Answers (1)

Xavier Portebois
Xavier Portebois

Reputation: 3504

I found it!

To be fully JPA compliant, a proxy should be initialized when any of its fields is accessed, even the identifier (which was not the case with Hibernate, to avoid a query loop).

Since Hibernate 5.2.13, there is a new option to fulfill this rule, hibernate.jpa.compliance.proxy, which is set by default to false in order to keep consistency with Hibernate previous behaviors.

But! Since Wildfly 14, this option is set by default to true in the server context. My code is running on a WF18 instance, so it's the same.

The solution was, like stated in this other question, to override the property in persistence.xml, resetting it to its default value, false:

<property name="hibernate.jpa.compliance.proxy" value="false" />

Upvotes: 2

Related Questions