我有一个名为“manager”的具有 @ManyToOne 自我关系的 Person 实体。在大多数情况下,我不需要知道该人的经理,因此我用 Fetch.LAZY 标记了关系。然而,在我检索具有 Person 字段的实体的每种情况下,JPA(使用 Hibernate Provider)都会加载引用的 Person...以及该人的经理...以及该人的经理,等等,直到整个管理链已加载。所有这些查询都是在 TypedQuery.getResultList() 将控制权返回给我的代码之前执行的,因此这不是我无意中在会话中的某个位置引用管理器并触发真正的延迟加载的问题。看来 JPA 获取计划已确定由于某种原因整个管理链是必要的,并将它们全部加载。
我正在使用 JPA persistence-api 2.2 和 hibernate-core 5.4.20
这是 Person 类的缩写:
@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;
}
Run Code Online (Sandbox Code Playgroud)
具有“人员”字段的示例实体是“营销活动”实体:
@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;
...
}
Run Code Online (Sandbox Code Playgroud)
在营销活动获取的情况下,我知道将引用createdBy和owner字段,因此我急切地使用实体图来获取它们:
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();
Run Code Online (Sandbox Code Playgroud)
我可以看到这达到了预期的结果,生成的 SQL 连接到 mycreated_by和owner字段中:
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 ?
Run Code Online (Sandbox Code Playgroud)
现在问题就出现在这里。如果我为 org.hibernate.internal.TwoPhaseLoad 打开 DEBUG,我会看到createdBy 和owner 字段都解析为 id=CF21FF65-3A8C-40AB-BFAC-A7FD197C9512 的 Person 对象,并且根据调试,Person 对象它们都指向通过实体图覆盖急切地获取(如预期)。然而,当 Hibernate 开始处理该对象时,它会找到一个管理器引用 (id=01ACB688-D415-42C3-81E7-91EEBBF26535) 并立即发出一个不带上述说明文本的查询:
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]
Run Code Online (Sandbox Code Playgroud)
以类似的方式,在返回 TypedQuery.getResultList() 之前,也会获取 Person (id=01ACB688-D415-42C3-81E7-91EEBBF26535) 上面的三层管理。
我的问题是这样的:
当声明为 Fetch.LAZY 的实体未包含在应用 GraphSemantic.FETCH 的实体图中时,为什么会检索该实体?
笔记:
如果我放弃 EntityGraph,只是让事情根据 JPA 实体注释进行,我会得到几乎相同的结果,除了在 Campaign 查询之后立即对 Person (id=CF21FF65-3A8C-40AB-BFAC-A7FD197C9512) 进行额外查询。与上面的问题类似,两个 Campaign 对 Person 的引用都是惰性的,那么为什么要获取它们呢?
我是个笨蛋。答案就在日志中:
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.
Run Code Online (Sandbox Code Playgroud)
删除 @NotFound 注释解决了该问题。
| 归档时间: |
|
| 查看次数: |
2974 次 |
| 最近记录: |