当@PostLoad注释存在时,为什么Hibernate会使用FetchMode.SUBSELECT

时间:2018-02-08 15:11:50

标签: java hibernate jpa orm

我必须将一个(非常非规范化的)DB Schema映射到hibernate,由于各种原因,我需要使用回调方法将行集合“解析”到业务对象中。

以下是我的映射的简化示例:

@Table(name = "CONTAINER")
class Container {
  @OneToMany(fetch = FetchType.EAGER)
  @JoinColumn(name = "CONTAINER_ID", referencedColumnName = "ID")
  @MapKey(name = "key")
  @Fetch(FetchMode.SUBSELECT)
  private Map<Longs, MappedAttribute> mappedAttributes;

  @Transient
  private BusinessAttribute businessAttribute;

  @PostLoad
  private void postLoad(){
    this.businessAttribute = new BusinessAttribute(mappedAttributes);
  }
}

@Table(name = "ATTRIBUTES")
class MappedAttribute {
  ...
}

class BusinessAttribute {
  public BusinessAttribute(List<MappedAttribute> args){}
}

映射本身是有效的,我最终得到了我想要的业务对象,但令我困惑的是Hibernate用来获取相关行的SQL查询(简化了你会得到它的要点):

-- First select the root objects
select * from CONTAINER where ID in (1,2,3,4); 
-- Then select all the attribute rows for each root object in individual queries
select * from ATTRIBUTES where CONTAINER_ID = 1;
select * from ATTRIBUTES where CONTAINER_ID = 2;
select * from ATTRIBUTES where CONTAINER_ID = 3;
select * from ATTRIBUTES where CONTAINER_ID = 4;

这种行为看起来很像我指定FetchMode.SELECT时会发生什么。不幸的是,这对我来说是不可接受的,因为我通常需要加载几千个容器,这会产生太多的SQL查询。为避免这种情况,我明确指定了FetchType.EAGERFetchMode.SUBSELECT,但它们似乎被忽略了。

请注意,我也没有触及postLoad()方法中任何对象的任何持久属性,因为我只设置了@Transient属性。

有趣的是,如果我只是注释掉@PostLoad注释,那么查询看起来就像我预期的那样(当然,我的业务对象没有创建):

--  First select the root objects
select * from CONTAINER where ID in (1,2,3,4);
-- The select all the attributes for all root objects at once
select * from ATTRIBUTES where CONTAINER_ID in (
    select ID from CONTAINER where ID in (1,2,3,4)
);

当回调存在时,Hibernate 5.2.12 EntityManager是否有任何理由决定忽略FetchMode.SUBSELECT

有没有什么方法可以同时获得回调和子选择行为?

1 个答案:

答案 0 :(得分:0)

此行为是Hibernate中的错误导致的,该错误报告为HHH-12279

问题的根源是在加载了实体的所有基本属性之后,但在完全加载任何关联之前调用@PostLoad带注释的方法。

上面的示例中发生的是mappedAttributes属性 not null,但在调用@PostLoad方法时,其内容尚未从DB加载。此时调用该集合上的任何getter方法会触发该集合元素的延迟加载。

由于有多个Container个实体,并且每个Container都有自己的子集合,因此每个子集合都会触发自己的SQL select查询。

我已经尝试了很多变通办法(Interceptor,EventListener,Integrator等等),但是发现没有办法(除了从Hibernate获取实体后手动调用postLoad())。没有工作的替代方案都表现出相同的行为(可能是因为它们都挂钩了相同的生命周期)。