首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何防止延迟关系的加载

如何防止延迟关系的加载
EN

Stack Overflow用户
提问于 2020-12-02 01:38:29
回答 2查看 1.4K关注 0票数 5

我有一个个人实体,有一个@ManyToOne自我关系名为“经理”。在大多数情况下,我不需要知道这个人的经理,所以我已经标记了与Fetch.LAZY的关系。但是,在每次我检索带有Person字段的实体时,JPA (使用Hibernate提供程序)都会递归地加载引用的Person...and (person的manager...and ),等等,直到加载整个管理链为止。所有这些查询都是在TypedQuery.getResultList()将控制权返回给我的代码之前执行的,所以这不是我无意中引用会话中某个地方的管理器并触发真正的惰性负载的问题。似乎JPA获取计划已经确定了由于某种原因整个管理链是必要的,并将它们全部加载。

我使用的是JPA持久性-api 2.2和hibernate-core 5.4.20

以下是Person类的缩写:

代码语言:javascript
复制
@Entity
@IdClass(BaseTenantKey.class)
public class Person {

    @Id
    @Column(name = "id", nullable = false, updatable = false, columnDefinition = ENTITY_ID_DEF)
    private String id;

    @Id
    @Column(name = "tid", nullable = false, updatable = false, columnDefinition = TENANT_ID_DEF)
    private String tid = "default";

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumnsOrFormulas(value = {
            @JoinColumnOrFormula(formula = @JoinFormula(value = "tid", referencedColumnName = "tid")),
            @JoinColumnOrFormula(column = @JoinColumn(name = "managerid", referencedColumnName = "id"))
    })
    @NotFound(action = NotFoundAction.IGNORE)
    private Person manager;

    ...
}

@Embeddable
public class BaseTenantKey implements Serializable {

    @Column(name = "id", nullable = false, updatable = false, columnDefinition = ENTITY_ID_DEF)
    private String id;

    @Column(name = "tid", nullable = false, updatable = false, columnDefinition = TENANT_ID_DEF)
    private String tid;
}

带有Person字段的示例实体是活动实体:

代码语言:javascript
复制
@Entity
public class Campaign {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO, generator = "UUIDGenerator")
    @Column(name = "id", nullable = false, updatable = false, columnDefinition = ENTITY_ID_DEF)
    private String id;

    @Column(name = "tid", columnDefinition = TENANT_ID_DEF)
    private String tid = "default";

    @ManyToOne(fetch = FetchType.LAZY, optional = false)
    @JoinColumnsOrFormulas(value = {
            @JoinColumnOrFormula(formula = @JoinFormula(value = "tid", referencedColumnName = "tid")),
            @JoinColumnOrFormula(column = @JoinColumn(name = "created_by", referencedColumnName = "id",
                    nullable = false, updatable = false, columnDefinition = ENTITY_ID_DEF))
    })
    private Person createdBy;

    @ManyToOne(fetch = FetchType.LAZY, optional = false)
    @JoinColumnsOrFormulas(value = {
            @JoinColumnOrFormula(formula = @JoinFormula(value = "tid", referencedColumnName = "tid")),
            @JoinColumnOrFormula(column = @JoinColumn(name = "owner", referencedColumnName = "id", nullable = false,
                    columnDefinition = ENTITY_ID_DEF, foreignKey = @ForeignKey(name = "fk_campaign_person_owner")))
    })
    private Person owner;

    ...
}

在活动获取的情况下,我知道将引用createdByowner字段,因此我急切地使用实体图来获取它们:

代码语言:javascript
复制
    final EntityGraph<Campaign> graph = getEntityManager().createEntityGraph(Campaign.class);
    graph.addAttributeNodes(Campaign_.CREATED_BY, Campaign_.OWNER);
    
    ...

    final TypedQuery<E> typedQuery = getEntityManager().createQuery(filterQuery);
    typedQuery.setHint(GraphSemantic.FETCH.getJpaHintName(), graph);
    typedQuery.setHint(QueryHints.HINT_READONLY, readOnly);
    final List<E> result = typedQuery.getResultList();

我可以看到,这有一个预期的结果,即生成的SQL联接在我的created_byowner字段中:

代码语言:javascript
复制
select
    campaign0_.id as id1_0_0_,
    person1_.id as id1_35_1_,
    person1_.tid as tid2_35_1_,
    person2_.id as id1_35_2_,
    person2_.tid as tid2_35_2_,
    campaign0_.created_at as created_2_0_0_,
    campaign0_.modified_at as modified3_0_0_,
    campaign0_.tid as tid4_0_0_,
    campaign0_.created_by as created30_0_0_,
    campaign0_.version as version5_0_0_,
    ...
    campaign0_.tid as formula58_0_,
    campaign0_.tid as formula59_0_,
    person1_.created_at as created_3_35_1_,
    person1_.modified_at as modified4_35_1_,
    ...
    person1_.tid as formula44_1_,
    person2_.created_at as created_3_35_2_,
    person2_.modified_at as modified4_35_2_,
    ...
    person2_.tid as formula44_2_ 
from
    campaign campaign0_ 
left outer join
    person person1_ 
        on campaign0_.created_by=person1_.id 
        and campaign0_.tid=person1_.tid 
left outer join
    person person2_ 
        on campaign0_.owner=person2_.id 
        and campaign0_.tid=person2_.tid limit ?

现在,问题就出现在这里。如果我为org.hibernate.internal.TwoPhaseLoad打开调试,我会看到createdBy和owner字段都解析给Person对象,id=CF21FF65-3A8C-40AB-BFAC-A7FD197C9512,并且根据调试,它们所指向的Person对象都被急切地用实体图覆盖来获取(正如预期的那样)。然而,当Hibernate开始处理该对象时,它会找到一个管理器引用(id=01ACB688-D415-42C3-81E7-91EEBBF26535),并立即发出一个没有上面提到的解释文本的查询:

代码语言:javascript
复制
o.h.engine.internal.TwoPhaseLoad    : Resolving attributes for [com.company.domain.Campaign#79670EF1-872B-49D7-ACAC-67DA188249CA]
o.h.engine.internal.TwoPhaseLoad    : Processing attribute `tid` : value = default
o.h.engine.internal.TwoPhaseLoad    : Attribute (`tid`)  - enhanced for lazy-loading? - false
o.h.engine.internal.TwoPhaseLoad    : Processing attribute `createdBy` : value = BaseTenantKey(id=CF21FF65-3A8C-40AB-BFAC-A7FD197C9512, tid=default)
o.h.engine.internal.TwoPhaseLoad    : Attribute (`createdBy`)  - enhanced for lazy-loading? - false
o.h.engine.internal.TwoPhaseLoad    : Overriding eager fetching using fetch graph. EntityName: com.company.domain.Campaign, associationName: createdBy, eager fetching: true
o.h.engine.internal.TwoPhaseLoad    : Processing attribute `version` : value = 197
o.h.engine.internal.TwoPhaseLoad    : Attribute (`version`)  - enhanced for lazy-loading? - false
o.h.engine.internal.TwoPhaseLoad    : Processing attribute `owner` : value = BaseTenantKey(id=CF21FF65-3A8C-40AB-BFAC-A7FD197C9512, tid=default)
o.h.engine.internal.TwoPhaseLoad    : Attribute (`owner`)  - enhanced for lazy-loading? - false
o.h.engine.internal.TwoPhaseLoad    : Overriding eager fetching using fetch graph. EntityName: com.company.domain.Campaign, associationName: owner, eager fetching: true
o.h.engine.internal.TwoPhaseLoad    : Done materializing entity [com.company.domain.Campaign#79670EF1-872B-49D7-ACAC-67DA188249CA]
o.h.engine.internal.TwoPhaseLoad    : Resolving attributes for [com.company.domain.Person#component[id,tid]{id=CF21FF65-3A8C-40AB-BFAC-A7FD197C9512, tid=default}]
o.h.engine.internal.TwoPhaseLoad    : Processing attribute `manager` : value = BaseTenantKey(id=01ACB688-D415-42C3-81E7-91EEBBF26535, tid=default)
o.h.engine.internal.TwoPhaseLoad    : Attribute (`manager`)  - enhanced for lazy-loading? - false
org.hibernate.SQL                   : 
select
    person0_.id as id1_35_0_,
    person0_.tid as tid2_35_0_,
    ...
    person0_.tid as formula44_0_ 
from
    person person0_ 
where
    person0_.id=? 
    and person0_.tid=?
o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [01ACB688-D415-42C3-81E7-91EEBBF26535]
o.h.type.descriptor.sql.BasicBinder : binding parameter [2] as [VARCHAR] - [default]

以类似的方式,在返回TypedQuery.getResultList()之前,还将获取上面的三层管理(id=01ACB688-D415-42C3-81E7-91EEBBF26535)。

我的问题是:

为什么当一个被声明为Fetch.LAZY的实体没有包含在GraphSemantic.FETCH应用的实体图中时,它会被检索?

注:

如果我放弃EntityGraph,让事情按照JPA实体注释进行,那么除了在活动查询之后立即对Person (id=CF21FF65-3A8C-40AB-A7FD197C9512)进行额外的查询外,我得到的结果几乎是相同的。和上面的问题相似,两次竞选活动中提到的人都是懒惰的,那么为什么要把他们取出来呢?

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2020-12-21 21:38:10

我是个笨蛋。答案就在原木上:

代码语言:javascript
复制
WARN 2170 --- org.hibernate.cfg.AnnotationBinder       : HHH000491: The [manager] association in the [demo.Person] entity uses both @NotFound(action = NotFoundAction.IGNORE) and FetchType.LAZY. The NotFoundAction.IGNORE @ManyToOne and @OneToOne associations are always fetched eagerly.

删除@NotFound注释解决了问题。

票数 1
EN

Stack Overflow用户

发布于 2020-12-03 10:03:00

这可能是id类支持或@JoinFormula使用的问题。尝试使用可嵌入的ids来代替:

代码语言:javascript
复制
@Entity
public class Person {

    @EmbeddedId
    private BaseTenantKey id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumns({
            @JoinColumn(value = "tid", insertable = false, updatable = false, referencedColumnName = "tid"),
            @JoinColumn(name = "managerid", insertable = false, updatable = false, referencedColumnName = "id")
    })
    @NotFound(action = NotFoundAction.IGNORE)
    private Person manager;

    ...
}

@Embeddable
public class BaseTenantKey implements Serializable {

    @Column(name = "id", nullable = false, updatable = false, columnDefinition = ENTITY_ID_DEF)
    private String id;

    @Column(name = "tid", nullable = false, updatable = false, columnDefinition = TENANT_ID_DEF)
    private String tid;
}

@Entity
public class Campaign {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO, generator = "UUIDGenerator")
    @Column(name = "id", nullable = false, updatable = false, columnDefinition = ENTITY_ID_DEF)
    private String id;

    @Column(name = "tid", columnDefinition = TENANT_ID_DEF)
    private String tid = "default";

    @ManyToOne(fetch = FetchType.LAZY, optional = false)
    @JoinColumns({
            @JoinColumn(value = "tid", insertable = false, updatable = false, referencedColumnName = "tid"),
            @JoinColumn(name = "created_by", referencedColumnName = "id",
                    nullable = false, updatable = false, columnDefinition = ENTITY_ID_DEF)
    })
    private Person createdBy;

    @ManyToOne(fetch = FetchType.LAZY, optional = false)
    @JoinColumns({
            @JoinColumn(value = "tid", insertable = false, updatable = false, referencedColumnName = "tid")),
            @JoinColumn(name = "owner", referencedColumnName = "id", nullable = false,
                    columnDefinition = ENTITY_ID_DEF, foreignKey = @ForeignKey(name = "fk_campaign_person_owner"))
    })
    private Person owner;

    ...
}
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/65101022

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档