5

我的问题是 OneToOne 关联的休眠急切加载为每个空关系执行 +1 选择。

实体示例:

@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;
}

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

查询示例:

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

结果:如果所有蓝色侧实体都有一个红色侧,则一切正常,因此 hibernate 只对将检索到的实体执行一次对数据库的查询。

但是,如果蓝方实体没有关联的红方实体,则休眠尝试再次找到另一方。Hibernate sql 注释对每个空的 redSide 属性说 '/* load RedSide */ select ...'。

如何跳过第二个选择?

当延迟不是非常低时,就会出现实际问题。如果我尝试选择 100 万行,并且 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

第一行包含蓝色和红色面,但第二行仅包含蓝色面。所以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
4

1 回答 1

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  
}  

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

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

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

所以简历:如果您的 B->C 映射是强制性的(约束=true),Hibernate 将使用 C 代理,从而导致延迟初始化。但是如果你允许 B 没有 C,Hibernate 只需要在它加载 B 的那一刻检查 C 的存在。但是一个 SELECT 来检查存在是低效的,因为同一个 SELECT 可能不只是检查存在,而是加载整个对象。所以延迟加载消失了。

于 2014-03-16T20:53:42.563 回答