@OneToOne 与 Hibernate 关系的延迟加载

Mam*_*mut 3 java hibernate jpa kotlin

我有一个具有 @OneToOne 关联的实体。像这样:

@Entity
public class Person {
    @Id Long id;
    @OneToOne(fetch = FetchType.LAZY) Address address;
}
Run Code Online (Sandbox Code Playgroud)

当我加载此类实体时,Hibernate 会忽略 LAZY弗拉德解释说

延迟加载适用于 @OneToOne 关联的父端之外的情况。这是因为 Hibernate 没有其他方法可以知道是否为该变量分配 null 或 Proxy。

这里有类似的声明。我不明白。有 FK 列 PERSON.ADDRESS_ID,如果有任何值,Hibernate应该知道应该使用代理。我错过了什么吗?


更新:我的原始代码是用 Kotlin 编写的。我尝试在 Java 中创建相同的示例,令人惊讶的是,延迟加载在那里工作得很好。

cod*_*nic 5

尝试使用@OneToMany关系来理解它。

When you have that, you specify some collection i.e List, for example we have an entity

class A {
   @OneToMany
   List<B> bs;

   public List<B> getBs() {
      return bs;
   }
}
Run Code Online (Sandbox Code Playgroud)

So when hibernate loads the A, it is able to identify that you have List<B> and you may call getBs() just after the class is loaded so hibernate creates a wrapper list which doesn't have any B yet and it will wait until you perform any operation on the list ie. iterate, add etc.

As soon as you perform the operation, hibernate will issue the query and load the objects into the set, hence lazy loading works fine here.

That's why one-to-many by default is lazy

Now let's take example of @OneToOne

class A {
   @OneToOne
   B b;
   
   public B getB() {}
}
Run Code Online (Sandbox Code Playgroud)

When hibernate loads A, it will see that user may call the getB just after A is loaded, so it needs to initialise B as well.

Now, even if B supports proxy, hibernate have to initialise it with proxy or null and how that decision will be made, it will have to query the B to check if it exists or not but if it queries just to check, why just check only, why not initialise it fully, hence it does it eagerly, ignoring the Lazy attribute1

But this is not true for child side, if you specify the @One-To-One on child side

class B {
   @OneToOne(lazy)
   A a;
   
   public A getA() {}
}
Run Code Online (Sandbox Code Playgroud)

Because this is the entity for table which holds the foreign key to A entity table, hibernate will initialise it with the proxy of A because hibernate knows that this entity is child entity and has foreign key associated, so it can lazy load when required, if it's null, you would get null A.

Correction:

The above behaviour is obvious for the optionable relation (optional = true) as I have already explained and you may find other answers stating that, but it is not obvious when you use optional=false.

With non-optional relation, we would think that hibernate identify that there would be a child present for the parent so hibernate will initialise the proxy and it should depict the lazy loading behaviour.

但是,为了初始化代理,hibernate 将需要最少的信息(例如标识符),并且需要从子表中查询,因此它与可选关系相同,并且会急切地加载。

有一种解决方案仍然可以使其工作(至少我是这么认为),如果您使用以下命令与子实体共享父实体的主键@MapsId

class A {
    @Id
    private Integer id;
    
    @OneToOne(fetch = Lazy, mappedBy = "a", optional = false)
    private B b;
}

class B {
   @Id
   private Integer id;
   
   @OneToOne
   @MapsId
   private A a;
}
Run Code Online (Sandbox Code Playgroud)

这应该有效,因为现在您正在与子进程共享父主键,并且 hibernate 现在知道标识符,并且不需要从表中查询它,并且应该能够轻松初始化代理。

然而,它不起作用并且仍然急切地加载,这很奇怪,经过一番挖掘,我发现了Vlad 本人报告的这个问题。2

尽管我在相关问题中找到了解决方法,并且还询问了上述问题是否有效,但这就是为什么不在这里发布的原因。


1一些旧版本的 hibernate 也支持从父端延迟加载,但该行为在最近的版本中被删除,因为它们需要检查子项是否存在。

2我使用 hibernate 版本 5.4.8.Final 和 5.4.30.Final 检查了此行为