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

时间:2018-11-25 11:18:05

标签: hibernate jpa kotlin proxy

对于JPA实体中的if(coin_change >= .00) { coins += 1; pennies += 1; coin_change = coin_change - .01; continue; } else { break; } 关系,我只对实际的id引用感兴趣,而不是获取与该关系关联的整个模型。

例如,这些Kotlin JPA实体:

@ManyToOne

现在,当我从Kotlin代码访问@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) } } 时,实际上是从数据库中查询了完整的country.continent.id。 这太过分了,因为我只对Continent感兴趣。

我尝试添加Continent.id,例如:

@Access(AccessType.PROPERTY)

但是没有什么区别。整个@Entity class Continent( @Id @Access(AccessType.PROPERTY) var id: String, 仍从数据库中查询。

我尝试了Continent,就像其他帖子(例如Hibernate one-to-one: getId() without fetching entire object)中提到的那样,但是我已经注意到对此的反馈意见不一。

我将Kotlin @Access(AccessType.PROPERTY)与Hibernate 5.3.7.Final一起使用。

我想知道{em> 1) 1.3.0方法是否正确, 2)是否也可以与Kotlin一起使用?也许Kotlin生成Java代码的方式引起了问题?

更新

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

该项目包含一个简单的测试,用于检索@Access(AccessType.PROPERTY)并已启用SQL日志记录。从记录中可以看到查询了大陆

更新2

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

3 个答案:

答案 0 :(得分:1)

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

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

实际上,我在Hibernate ORM中创建了这两个测试用例,并且一切正常:

默认情况下,仅访问ID时,代理不会初始化。

这是测试:

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 );

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

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

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

    }
}

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

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

这就是我们得到的:

protected void assertProxyState(Continent continent) {
    assertEquals( "Europe", continent.getName() );
}

因此,一切正常。

问题来自Kotlin或Spring Data JPA。您需要进一步调查它,并查看为什么初始化代理。

很可能是由于向toString实体添加了compareContinent实现。

答案 1 :(得分:1)

我有类似的问题。原来所有@Entity注释的类都必须是“打开的”。否则,Hibernate将无法创建代理子类,因此将无法延迟加载您的实体。

答案 2 :(得分:1)

正如Adrien Dos Reis提到的那样,Hibernate要求所有带有@Entity注释的类都必须是开放的(即,不是final)。不必手动打开类和属性来进行所有繁琐的工作,只需将all-open插件添加到kotlin-maven-plugin即可,如下所示:

<plugin>
    <groupId>org.jetbrains.kotlin</groupId>
    <artifactId>kotlin-maven-plugin</artifactId>

    <configuration>
        <args>
            <arg>-Xjsr305=strict</arg>
        </args>

        <compilerPlugins>
            <plugin>all-open</plugin>
            <plugin>jpa</plugin>
        </compilerPlugins>

        <pluginOptions>
            <option>all-open:annotation=javax.persistence.Entity</option>
        </pluginOptions>
    </configuration>

    <dependencies>
        <dependency>
            <groupId>org.jetbrains.kotlin</groupId>
            <artifactId>kotlin-maven-allopen</artifactId>
            <version>${kotlin.version}</version>
        </dependency>

        <dependency>
            <groupId>org.jetbrains.kotlin</groupId>
            <artifactId>kotlin-maven-noarg</artifactId>
            <version>${kotlin.version}</version>
        </dependency>
    </dependencies>
</plugin>

请注意,选项all-open:annotation=javax.persistence.Entity已添加到全打开插件中。这将导致所有带@Entity注释的类在默认情况下处于打开状态。有关详细信息,请参阅https://kotlinlang.org/docs/reference/compiler-plugins.html#all-open-compiler-plugin