Hibernate HQL连接提取不是递归提取

我有以下查询和方法

private static final String FIND = "SELECT DISTINCT domain FROM Domain domain LEFT OUTER JOIN FETCH domain.operators LEFT OUTER JOIN FETCH domain.networkCodes WHERE domain.domainId = :domainId"; @Override public Domain find(Long domainId) { Query query = getCurrentSession().createQuery(FIND); query.setLong("domainId", domainId); return (Domain) query.uniqueResult(); } 

使用Domain as

 @Entity @Table public class Domain { @Id @GenericGenerator(name = "generator", strategy = "increment") @GeneratedValue(generator = "generator") @Column(name = "domain_id") private Long domainId; @Column(nullable = false, unique = true) @NotNull private String name; @Column(nullable = false) @NotNull @Enumerated(EnumType.STRING) private DomainType type; @OneToMany(cascade = { CascadeType.PERSIST, CascadeType.MERGE }, fetch = FetchType.EAGER) @JoinTable(joinColumns = { @JoinColumn(name = "domain_id") }, inverseJoinColumns = { @JoinColumn(name = "code") }) @NotEmpty @Valid // needed to recur because we specify network codes when creating the domain private Set networkCodes = new HashSet(); @ManyToMany(fetch = FetchType.EAGER) @JoinTable(joinColumns = { @JoinColumn(name = "parent", referencedColumnName = "domain_id") }, inverseJoinColumns = { @JoinColumn(name = "child", referencedColumnName = "domain_id") }) private Set operators = new HashSet(); // more } 

我希望这个单一查询可以获取SetSet<Domain >关系,但事实并非如此。 假设Domain I查询有两个运算符,Hibernate将执行1 + 2 * 2 = 5个查询

 Hibernate: select distinct domain0_.domain_id as domain1_1_0_, domain2_.domain_id as domain1_1_1_, networkcod4_.code as code2_2_, domain0_.name as name1_0_, domain0_.type as type1_0_, domain2_.name as name1_1_, domain2_.type as type1_1_, operators1_.parent as parent1_0__, operators1_.child as child4_0__, networkcod3_.domain_id as domain1_1_1__, networkcod3_.code as code5_1__ from domain domain0_ left outer join domain_operators operators1_ on domain0_.domain_id=operators1_.parent left outer join domain domain2_ on operators1_.child=domain2_.domain_id inner join domain_network_codes networkcod3_ on domain0_.domain_id=networkcod3_.domain_id inner join network_code networkcod4_ on networkcod3_.code=networkcod4_.code where domain0_.domain_id=? Hibernate: select operators0_.parent as parent1_1_, operators0_.child as child4_1_, domain1_.domain_id as domain1_1_0_, domain1_.name as name1_0_, domain1_.type as type1_0_ from domain_operators operators0_ inner join domain domain1_ on operators0_.child=domain1_.domain_id where operators0_.parent=? Hibernate: select networkcod0_.domain_id as domain1_1_1_, networkcod0_.code as code5_1_, networkcod1_.code as code2_0_ from domain_network_codes networkcod0_ inner join network_code networkcod1_ on networkcod0_.code=networkcod1_.code where networkcod0_.domain_id=? Hibernate: select operators0_.parent as parent1_1_, operators0_.child as child4_1_, domain1_.domain_id as domain1_1_0_, domain1_.name as name1_0_, domain1_.type as type1_0_ from domain_operators operators0_ inner join domain domain1_ on operators0_.child=domain1_.domain_id where operators0_.parent=? Hibernate: select networkcod0_.domain_id as domain1_1_1_, networkcod0_.code as code5_1_, networkcod1_.code as code2_0_ from domain_network_codes networkcod0_ inner join network_code networkcod1_ on networkcod0_.code=networkcod1_.code where networkcod0_.domain_id=? 

我猜这是因为我加入了运营商的Domain元素,但他们必须自己加入。

是否有可以执行的HQL查询?

如果您知道树中只有两个级别,那么您是否考虑过加入更深层次的级别。 像下面的东西?

 SELECT DISTINCT domain FROM Domain domain LEFT OUTER JOIN FETCH domain.operators operators1 LEFT OUTER JOIN FETCH domain.networkCodes LEFT OUTER JOIN FETCH operators1.operators operators2 LEFT OUTER JOIN FETCH operators1.networkCodes WHERE domain.domainId = :domainId 

Hibernate关系适用于不同的获取策略.. !!

Hibernate提供了4种检索数据的策略:

选择

 @OneToMany(mappedBy="tableName", cascade=CascadeType.ALL) @Column(name="id") @Fetch(FetchMode.SELECT) 

在此方法中,有多个SQL被触发。 触发第一个用于检索Parent表中的所有记录。 其余的被触发以检索每个父记录的记录。 这基本上是N + 1问题。 第一个查询从数据库中检索N个记录,在本例中为N个父记录。 对于每个Parent,新查询将检索Child。 因此,对于N Parent,N查询从Child表中检索信息。

加入

 @OneToMany(mappedBy="tableName", cascade=CascadeType.ALL) @Column(name="id") @Fetch(FetchMode.JOIN) 

这类似于SELECT获取策略,除了事实上所有数据库检索都在JOIN提取中预先发生,而不像SELECT那样需要它。 这可以成为重要的性能考虑因素。

子查询

  @OneToMany(mappedBy="tableName", cascade=CascadeType.ALL) @Column(name="id") @Fetch(FetchMode.SUBSELECT) 

触发了两个SQL。 一个用于检索所有Parent,另一个在WHERE子句中使用SUBSELECT查询来检索具有匹配父ID的所有子项。

批量

 @OneToMany(mappedBy="tableName", cascade=CascadeType.ALL) @Column(name="id") @@BatchSize(size=2) 

批量大小映射到其子项被检索的父级数。 所以我们可以指定一次获取的记录数。但是将执行多个查询。!!

一对多和多对多允许 – 加入,选择和子选择

多对一和一对一允许 – 加入和选择


Hibernate也区分(何时获取关联)

1. 立即取物

加载Parent时立即获取关联,集合或属性。 (懒惰=“假”)

2. 懒人collections

当应用程序调用该集合上的操作时,将获取集合。 (这是集合的默认值。(lazy =“true”)

3.“ 非常懒 ”的集合取物 –

根据需要从数据库访问集合的各个元素。 除非绝对需要(适用于非常大的集合),否则Hibernate会尝试不将整个集合提取到内存中(lazy =“extra”)

4. 代理提取

当在关联对象上调用除标识符getter之外的方法时,将获取单值关联。 (懒惰=“代理”)

5.“ 无代理 ”取得 –

访问实例变量时,将获取单值关联。 与代理提取相比,这种方法不那么懒惰。(lazy =“no-proxy”)

6. 懒惰属性获取 –

访问实例变量时,将获取属性或单值关联。 (懒惰=“真”)

一对多和多对多允许立即,懒惰,超级懒惰

多对一和一对一允许立即代理,无代理

你标记了你的协会EAGER。 因此,无论您在查询中执行什么操作,Hibernate都会加载所加载域的所有关联域和网络代码。 它将加载其他域的域和网络代码等,直到所有集合加载返回已经加载的空集合或实体。

为避免这种情况,请使您的集合变得懒惰(默认情况下)。 然后加载一个域及其运算符及其网络代码将加载该域。

如果您使用Criteria API进行查询,那么您的EAGER映射将仅由Hibernate自动考虑。

如果使用HQL,则需要手动将FETCH关键字添加到JOIN中,以强制Hibernate在第一个查询中包含关系,并避免后续查询。

这是特定于Hibernate的,可能在其他ORM上有所不同。

看到这个问题/答案的角度略有不同。

它没有记录好,但你尝试设置FetchMode吗? 您可以使用Criteria API domainCriteria.setFetchMode("operators", JOIN)domainCriteria.setFetchMode("operators", JOIN)或在关系定义中使用@Fetch(JOIN)

注释(并且只显示注释)还允许设置获取模式SUBSELECT ,这至少应该限制Hibernate执行3个查询最大值。 不知道你的数据集,我认为这应该是你的方式,因为这些表上的一个大胖子加入似乎不太健康。 最好自己弄清楚,我猜…

由于您已经为networkCodesoperators指定了FetchType.EAGER ,因此每当您查询domain ,hibernate都会加载networkCodesoperators 。 这就是EAGER抓取模式的全部概念

因此,您可以将查询简单地更改为以下内容:

 private static final String FIND = "SELECT DISTINCT domain FROM Domain domain WHERE domain.domainId = :domainId"; 

API详细信息

干杯!!

我的第一个观察是,如果你的映射表明它们必须被急切地加载,你不需要编写包含连接的HQL查询。

但是,如果您不想使用连接,则可以告诉Hibernate将提取策略用作子选择。

Hibernate根据指定的映射生成用于在启动期间加载对象的SQL查询并对其进行缓存。 但是在你的情况下,你有一对多的自我和任意深度的嵌套关系,所以看起来hibernate不可能在sql之前决定正确的eager fetch。 因此,它需要发送多个联接查询,具体取决于您在运行时查询的父域的深度。

对我来说,看起来你认为你的案例中的HQL和结果SQL /(s)可能具有一对一的相关性,这是不正确的。 使用HQL,您可以查询对象,并且orm决定如何根据映射加载该对象及其关系(eager / lazy),或者您也可以在运行时指定它们(例如,可以通过Query api覆盖映射中的惰性关联)但反之亦然)。 你可以告诉orm要加载什么(我的标记渴望或懒惰)以及如何加载(使用join / sub select)。

UPDATE

当我在您的域模型上运行以下查询时

 SELECT DISTINCT domain FROM Domain domain LEFT OUTER JOIN FETCH domain.operators LEFT OUTER JOIN FETCH domain.networkCodes WHERE domain.domainId = :domainId"; 

我可以看到networkCode和operator集合都是实例PersistentSet(这是Hibernate包装器),并且都将初始化属性设置为true。 同样在底层会话上下文中,我可以看到域名和运营商列出的域名。 那么是什么让你觉得他们没有急切的装?

这就是我的域名的样子

 @Entity @Table public class Domain { @Id @GenericGenerator(name = "generator", strategy = "increment") @GeneratedValue(generator = "generator") @Column(name = "domain_id") private Long domainId; @Column(nullable = false, unique = true) private String name; @Column(nullable = false) @Enumerated(EnumType.STRING) private DomainType type; @OneToMany(mappedBy = "domain",cascade = { CascadeType.PERSIST, CascadeType.MERGE }, fetch = FetchType.EAGER) private Set networkCodes = new HashSet(); @ManyToMany(mappedBy="parent",fetch = FetchType.EAGER, cascade=CascadeType.ALL) private Set operators = new HashSet(); // more @ManyToOne private Domain parent; public String getName() { return name; } public void setName(String name) { this.name = name; } public DomainType getType() { return type; } public void setType(DomainType type) { this.type = type; } public Set getOperators() { return operators; } public Long getDomainId() { return domainId; } public void setDomainId(Long domainId) { this.domainId = domainId; } public void setOperators(Set operators) { this.operators = operators; } public void addDomain(Domain domain){ getOperators().add(domain); domain.setParent(this); } public Domain getParent() { return parent; } public void setParent(Domain parent) { this.parent = parent; } public void addNetworkCode(NetworkCode netWorkCode){ getNetworkCodes().add(netWorkCode); netWorkCode.setDomain(this); } 

在此处输入图像描述