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 }
我希望这个单一查询可以获取Set
和Set<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个查询最大值。 不知道你的数据集,我认为这应该是你的方式,因为这些表上的一个大胖子加入似乎不太健康。 最好自己弄清楚,我猜…
由于您已经为networkCodes
和operators
指定了FetchType.EAGER
,因此每当您查询domain
,hibernate都会加载networkCodes
和operators
。 这就是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); }