使用FetchMode.JOIN的Hibernate OneToOne(optional = true)尝试重新选择空值

Ign*_*aca 5 java orm hibernate jpa

我的问题是,对于每个空关系,Hibernate急切加载OneToOne关联会执行+1 select.

实体示例:

@Entity 
class SideBlue {
    @Column(nullable = false)
    private Integer timestamp;

    @OneToOne(optional=true) 
    @JoinColumn(name="timestamp", referenceColumn="timestamp", insertable = false, updatable = false) 
    SideRed redSide; 
}
@Entity 
class SideRed {
    @Column(nullable = false)
    private Integer timestamp;
}
Run Code Online (Sandbox Code Playgroud)

(这是遗留数据库模式,因此不允许进行数据库修改)

查询示例:

CriteriaBuilder builder... CriteriaQuery query...
Root<SideBlue> root = query.from(SideBlue.class);
root.fetch(SideBlue_.sideRed, JoinType.LEFT);
entityManager().createQuery(query).getResultList();
Run Code Online (Sandbox Code Playgroud)

结果:如果所有蓝色边实体都有一个红色边,一切都正常,所以hibernate只对数据库执行一个查询,无论哪个实体都将被检索.

但是,如果蓝方实体没有关联的红色实体,那么hibernate会再次尝试找到另一方.Hibernate sql注释为每个null redSide属性说'/*load RedSide*/select ...'.

如何跳过第二次选择?

当延迟不是非常低时,出现实际问题.如果我尝试选择1百万行,并且1/3具有空的"红色边",则添加的总延迟是一个真正的问题.

编辑:

这是查询的调试日志

10:04:32.812 [main] DEBUG org.hibernate.loader.Loader - Result set row: 0
10:04:32.815 [main] DEBUG org.hibernate.loader.Loader - Result row: EntityKey[SideBlue#1269721], EntityKey[SideRed#3620564]
10:04:32.833 [main] DEBUG org.hibernate.loader.Loader - Result set row: 1
10:04:32.833 [main] DEBUG org.hibernate.loader.Loader - Result row: EntityKey[SideBlue#1269776], null
Run Code Online (Sandbox Code Playgroud)

第一行包含蓝色和红色边,但第二行仅包含蓝色边.所以hibernate必须知道相关的红色方面不存在.但是,在处理完所有结果行之后......

10:04:33.083 [main] DEBUG o.h.engine.internal.TwoPhaseLoad - Resolving associations for [BlueSide#1269721]
10:04:33.084 [main] DEBUG org.hibernate.loader.Loader - Loading entity: [RedSide#component[timestamp]{timestamp=1338937390}]
10:04:33.084 [main] DEBUG org.hibernate.SQL - /* load RedSide */ select ...
! Nothing really loaded because the previous SQL return empty result set, again !
10:04:33.211 [main] DEBUG org.hibernate.loader.Loader - Done entity load
Run Code Online (Sandbox Code Playgroud)

Dhe*_*rik 1

好吧,当您查询 SideBlue 时,您试图不加载 SideRed。我认为这是一个与 Hibernate 的“限制”相关的延迟加载问题(来自https://community.jboss.org/wiki/SomeExplanationsOnLazyLoadingone-to-one?_sscc=t):

class B {  
    private C cee;  

    public C getCee() {  
        return cee;  
    }  

    public void setCee(C cee) {  
        this.cee = cee;  
    }  
}  

class C {  
    // Not important really  
}  
Run Code Online (Sandbox Code Playgroud)

加载 B 后,您可以立即调用 getCee() 来获取 C。但是请注意,getCee() 是您的类的方法,Hibernate 无法控制它。Hibernate 不知道什么时候有人会调用 getCee()。这意味着 Hibernate 在从数据库加载 B 时必须将适当的值放入“cee”属性中。

如果为C启用了代理,Hibernate可以放置一个尚未加载的C代理对象,但当有人使用它时会加载它。这为一对一提供了延迟加载。

但现在想象一下您的 B 对象可能有也可能没有关联的 C (constrained="false")。当特定 B 没有 C 时 getCee() 应该返回什么?无效的。但请记住,Hibernate 必须在设置 B 时设置正确的“cee”值(因为它不知道何时有人会调用 getCee())。代理在这里没有帮助,因为代理本身已经在非空对象中。

因此,简历:如果您的 B->C 映射是强制性的(constrained=true),Hibernate 将使用 C 的代理,从而导致延迟初始化。但是如果你允许 B 没有 C,Hibernate 只需在加载 B 时检查 C 是否存在。但是检查存在的 SELECT 效率很低,因为相同的 SELECT 可能不仅检查存在,而且加载整个对象。所以延迟加载就消失了。