@ManyToOne 在启用 Hibernate 二级缓存时延迟加载失败
@ManyToOne failed to lazy load when Hibernate second cache is enabled
我在我的项目中创建了两个非常简单的实体:
@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;
// ...
}
我们有一个可选的 @ManyToOne
从 TestA 到 TestB 的关系。
当我尝试获取 TestA 实例时,如下所示:
entityManager.find(TestA.class, myId);
我得到两个 select:一个用于 TestA,还有一个用于 TestB,因为它是急切加载的,这是不应该发生的。
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=?
我尝试了所有这些组合,甚至为了测试将关系设置为非可选:
@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;
这并没有改变任何东西,TestB 仍然在急切地加载。
但是,当我在 persistence.xml
中禁用二级缓存时,像这样:
<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>
现在 TestB 是延迟加载的,我只在访问 TestA.getTestB() 时看到第二个 select 查询。
当我查看日志时,我可以看到,在启用二级缓存的情况下,Hibernate 解析 testB 以将其放入缓存中:
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
我在这里完全无能为力,因为我无法解释这种行为。我的代码中是否存在错误或错误做法?
找到了!
为了完全符合 JPA,代理应该在访问其任何字段时初始化,甚至标识符(Hibernate 不是这种情况,以避免查询循环)。
自 Hibernate 5.2.13, there is a new option to fulfill this rule, hibernate.jpa.compliance.proxy
起,默认设置为 false
以便与 Hibernate 以前的行为保持一致。
但是!由于 Wildfly 14,此选项在服务器上下文中默认设置为 true
。我的代码是运行在WF18实例上,所以是一样的。
解决方案是,如 this other question 中所述,覆盖 persistence.xml
中的 属性,将其重置为其默认值,false
:
<property name="hibernate.jpa.compliance.proxy" value="false" />
我在我的项目中创建了两个非常简单的实体:
@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;
// ...
}
我们有一个可选的 @ManyToOne
从 TestA 到 TestB 的关系。
当我尝试获取 TestA 实例时,如下所示:
entityManager.find(TestA.class, myId);
我得到两个 select:一个用于 TestA,还有一个用于 TestB,因为它是急切加载的,这是不应该发生的。
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=?
我尝试了所有这些组合,甚至为了测试将关系设置为非可选:
@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;
这并没有改变任何东西,TestB 仍然在急切地加载。
但是,当我在 persistence.xml
中禁用二级缓存时,像这样:
<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>
现在 TestB 是延迟加载的,我只在访问 TestA.getTestB() 时看到第二个 select 查询。
当我查看日志时,我可以看到,在启用二级缓存的情况下,Hibernate 解析 testB 以将其放入缓存中:
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
我在这里完全无能为力,因为我无法解释这种行为。我的代码中是否存在错误或错误做法?
找到了!
为了完全符合 JPA,代理应该在访问其任何字段时初始化,甚至标识符(Hibernate 不是这种情况,以避免查询循环)。
自 Hibernate 5.2.13, there is a new option to fulfill this rule, hibernate.jpa.compliance.proxy
起,默认设置为 false
以便与 Hibernate 以前的行为保持一致。
但是!由于 Wildfly 14,此选项在服务器上下文中默认设置为 true
。我的代码是运行在WF18实例上,所以是一样的。
解决方案是,如 this other question 中所述,覆盖 persistence.xml
中的 属性,将其重置为其默认值,false
:
<property name="hibernate.jpa.compliance.proxy" value="false" />