当只需要实体 ID 时,如何避免初始化 Hibernate 代理

Mar*_*ijk 5 proxy hibernate jpa kotlin

对于@ManyToOneJPA 实体中的关系,我只对实际 id 引用感兴趣,而不是获取与该关系关联的整个模型。

以这些 Kotlin JPA 实体为例:

@Entity
class Continent(
        @Id
        var id: String,
        var code: String,
        var name: String
) : Comparable<Continent> {

    companion object {
        private val COMPARATOR = compareBy<Continent> { it.id }
    }

    override fun compareTo(other: Continent): Int {
        return COMPARATOR.compare(this, other)
    }
}

@Entity
class Country(
        @Id
        var id: String,
        var alpha2Code: String,
        var alpha3Code: String,
        var name: String,
        @ManyToOne(fetch = FetchType.LAZY)
        @JoinColumn(name = "continent_id")
        var continent: Continent

) : Comparable<Country> {

    companion object {
        private val COMPARATOR = compareBy<Country> { it.id }
    }

    override fun compareTo(other: Country): Int {
        return COMPARATOR.compare(this, other)
    }
}
Run Code Online (Sandbox Code Playgroud)

现在,当我从 Kotlin 代码访问时,实际上是从数据库查询country.continent.id完整内容。Continent这太过分了,因为我只对Continent.id.

我尝试添加@Access(AccessType.PROPERTY)如下:

@Entity
class Continent(
        @Id
        @Access(AccessType.PROPERTY)
        var id: String,
Run Code Online (Sandbox Code Playgroud)

但这没有什么区别。整体Continent还是从数据库中查询。

我尝试了@Access(AccessType.PROPERTY)其他帖子中提到的方法(例如Hibernate 一对一: getId() 而不获取整个对象),但我已经注意到对此的混合反馈。

我将 Hibernate5.3.7.Final与 Kotlin 一起使用1.3.0

我想知道1)@Access(AccessType.PROPERTY)方法是否正确,2)这是否也适用于 Kotlin?也许 Kotlin 生成 Java 代码的方式导致了问题?

更新

我创建了一个简单的测试项目,证明该大陆正在被查询。 https://github.com/marceloverdijk/hibernate-proxy-id

该项目包含一个简单的测试检索country.continent.id并启用了 SQL 日志记录。从日志记录中可以看出该大陆正在被查询。

更新2

我为此创建了https://youtrack.jetbrains.net/issue/KT-28525 。

Vla*_*cea 4

此行为由 JPA 规范定义,该规范要求在访问任何属性(甚至标识符)时获取关联。

传统上,Hibernate 在访问其标识符时不会初始化实体代理,但这种行为与 JPA 规范不一致,因此需要显式禁用此 JPA 合规性策略。

事实上,我在 Hibernate ORM 中创建了这两个测试用例,一切都按预期工作:

默认情况下,仅访问id时,Proxy不会初始化。

这是测试:

Continent continent = doInJPA( this::entityManagerFactory, entityManager -> {
    Country country = entityManager.find( Country.class, 1L );

    country.getContinent().getId();

    return country.getContinent();
} );

assertEquals( 1L, (long) continent.getId());

assertProxyState( continent );
Run Code Online (Sandbox Code Playgroud)

默认情况下,这是预期的行为:

protected void assertProxyState(Continent continent) {
    try {
        continent.getName();

        fail( "Should throw LazyInitializationException!" );
    }
    catch (LazyInitializationException expected) {

    }
}
Run Code Online (Sandbox Code Playgroud)

但是,如果我们切换到 JPA 兼容性 moe:

<property name="hibernate.jpa.compliance.proxy" value="false"/>
Run Code Online (Sandbox Code Playgroud)

这就是我们得到的:

protected void assertProxyState(Continent continent) {
    assertEquals( "Europe", continent.getName() );
}
Run Code Online (Sandbox Code Playgroud)

因此,一切都按预期进行。

问题来自 Kotlin 或 Spring Data JPA。您需要进一步调查它并了解代理为何被初始化。

最有可能的是因为添加到实体中的toString或实现。compareContinent