Java Hibernate HQL join fetch 不递归获取
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/18473644/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Hibernate HQL join fetch not recursively fetching
提问by Sotirios Delimanolis
I have the following query and method
我有以下查询和方法
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();
}
With Domain
as
与Domain
作为
@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<NetworkCode> networkCodes = new HashSet<>();
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(joinColumns = {
@JoinColumn(name = "parent", referencedColumnName = "domain_id")
}, inverseJoinColumns = {
@JoinColumn(name = "child", referencedColumnName = "domain_id")
})
private Set<Domain> operators = new HashSet<>();
// more
}
I would expect this single query to fetch the Set<NetworkCode>
and Set<Domain
> relations, but it doesn't. Say that the Domain
I query has two operators, Hibernate would perform 1 + 2 * 2 = 5 queries
我希望这个单一的查询能够获取Set<NetworkCode>
和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=?
I'm guessing this is because I'm joining the operators Domain
elements but they have to join themselves.
我猜这是因为我加入了运营商Domain
元素,但他们必须加入自己。
Is there an HQL query I can execute that would do both?
是否有我可以执行的 HQL 查询可以同时执行?
采纳答案by Andrei I
If you know that you have only two levels in your tree, have you thought of joining deeper one level. Something like below?
如果您知道您的树中只有两个级别,那么您是否考虑过加入更深的一层。像下面这样的?
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
回答by JB Nizet
You marked your associations EAGER. So, whatever you do in your query, Hibernate will load all the associated domains and network codes of the loaded domains. And it will load the domains and network codes of the additional domains, etc. etc. until all collection loads return empty collections or entities that have already been loaded.
你标记了你的协会 EAGER。因此,无论您在查询中做什么,Hibernate 都会加载所有关联的域和所加载域的网络代码。并且它将加载附加域的域和网络代码等,直到所有集合加载返回空集合或已经加载的实体。
To avoid that, make your collections lazy (as they are by default). Then loading a domain with its operators and its network codes will load just that.
为避免这种情况,请使您的集合延迟(默认情况下)。然后加载一个域及其运营商和它的网络代码将加载它。
回答by skirsch
It's not documented that good, but did you try setting the FetchMode
?
You can do so by either using the Criteria API: domainCriteria.setFetchMode("operators", JOIN)
or use @Fetch(JOIN)
at the relation definition.
没有记录那么好,但是您是否尝试设置FetchMode
? 您可以使用 Criteria API:domainCriteria.setFetchMode("operators", JOIN)
或@Fetch(JOIN)
在关系定义中使用。
The annotation (and only the annotation as it seems) also allows to set a fetch mode SUBSELECT
, which should at least restrain Hibernate to execute 3 queries max. Not knowing your dataset, I assume this should be the way to go for you, as a big fat join over those tables does not seem too healthy. Best to figure it out for yourself, I guess...
注释(只有看起来的注释)还允许设置 fetch mode SUBSELECT
,这至少应该限制 Hibernate 最多执行 3 个查询。不知道您的数据集,我认为这应该是适合您的方式,因为在这些表上进行大型连接似乎不太健康。最好自己弄清楚,我想......
回答by Sachin Thapa
Since you have already specified FetchType.EAGER
for both networkCodes
and operators
, whenever you will query domain
, hibernate will load both networkCodes
and operators
. That's the whole idea of EAGER
fetching mode
因为您已经FetchType.EAGER
为networkCodes
and指定了operators
,所以无论何时查询domain
,hibernate 都会同时加载networkCodes
and operators
。这就是EAGER
获取模式的全部思想
So you could change your query simple to following:
因此,您可以将查询简单更改为以下内容:
private static final String FIND
= "SELECT DISTINCT domain"
+ " FROM Domain domain"
+ " WHERE domain.domainId = :domainId";
API details here
API 详细信息在这里
Cheers !!
干杯!!
回答by Shailendra
My first observation is that you do not need to write an HQL query containing joins if your mappings say that they must be eagerly loaded.
我的第一个观察是,如果您的映射表明它们必须被急切加载,则您不需要编写包含连接的 HQL 查询。
You can however tell the Hibernate to use fetching strategy as sub select if you don't want to use joins.
但是,如果您不想使用连接,您可以告诉 Hibernate 使用获取策略作为子选择。
Hibernate generates the SQL query for loading the objects during startup based on the specified mappings and caches it. However in your case, you have one to many nested relation with self and arbitrary depth, so looks like it won't be possible for hibernate to decide before hand the sql to correctly eager fetch. So it would need to send multiple joins queries depending upon the depth of the parent Domain you are querying at runtime.
Hibernate 根据指定的映射在启动期间生成用于加载对象的 SQL 查询并缓存它。但是,在您的情况下,您有一对多嵌套关系 self 和 任意 depth,因此看起来 hibernate 不可能事先决定 sql 正确地预先获取。因此,它需要根据您在运行时查询的父域的深度发送多个连接查询。
To me it looks like you are thinking that HQL and the resulting SQL/('s) in your case can have one to one correpondence which is not true. With HQL you query for objects and the orm decides how to load that object and its relations (eager/lazy) based on the mappings or you can specify them at runtime too ( for e.g, a lazy association in mapping can be overridden by Query api but not vice versa). You can tell the orm what to load ( my marking eager or lazy ) and how to load eagerly ( either using join / sub select).
在我看来,您似乎认为 HQL 和由此产生的 SQL/('s) 可以具有一对一的对应关系,但这是不正确的。使用 HQL 查询对象,orm 根据映射决定如何加载该对象及其关系(急切/懒惰),或者您也可以在运行时指定它们(例如,映射中的惰性关联可以被 Query api 覆盖但反之则不然)。您可以告诉 orm 加载什么(我的标记为eager或lazy)以及如何加载(使用join/sub select)。
UPDATE
更新
When I run the following query on your domain model
当我在您的域模型上运行以下查询时
SELECT DISTINCT domain FROM Domain domain LEFT OUTER JOIN FETCH domain.operators LEFT OUTER JOIN FETCH domain.networkCodes WHERE domain.domainId = :domainId";
I can see that the networkCode and operator collections are of instance PersistentSet ( this is Hibernate wrapper) and both have initialized property set to be true. Also in the underlying session context I can see the domains with the domain and the operators listed. So what is making you think that they are not eagerly loaded ?
我可以看到 networkCode 和 operator 集合是 PersistentSet 实例(这是 Hibernate 包装器),并且都将初始化属性设置为 true。同样在底层会话上下文中,我可以看到包含域和列出的运算符的域。那么是什么让你认为他们没有急切地加载?
This is how my domain looks like
这是我的域的样子
@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<NetworkCode> networkCodes = new HashSet<NetworkCode>();
@ManyToMany(mappedBy="parent",fetch = FetchType.EAGER, cascade=CascadeType.ALL)
private Set<Domain> operators = new HashSet<Domain>();
// 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<Domain> getOperators() {
return operators;
}
public Long getDomainId() {
return domainId;
}
public void setDomainId(Long domainId) {
this.domainId = domainId;
}
public void setOperators(Set<Domain> 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);
}
回答by Dileep
The Hibernate Relations Works with different Fetch Strategies..!!
Hibernate 关系适用于不同的 Fetch 策略..!!
Hibernate provides 4 strategies for retrieving data:
Hibernate 提供了 4 种检索数据的策略:
SELECT
选择
@OneToMany(mappedBy="tableName", cascade=CascadeType.ALL)
@Column(name="id")
@Fetch(FetchMode.SELECT)
In this Method there are Multiple SQLs fired. This first one is fired for retrieving all the records in the Parent table. The remaining are fired for retrieving records for each Parent Record. This is basically the N+1 problem. The first query retrieves N records from database, in this case N Parent records. For each Parent a new query retrieves Child. Therefore for N Parent, N queries retrieve information from Child table.
在此方法中,触发了多个 SQL。第一个被触发以检索父表中的所有记录。其余的用于检索每个父记录的记录。这基本上是N+1问题。第一个查询从数据库中检索 N 条记录,在本例中为 N 条父记录。对于每个父级,一个新的查询检索子级。因此,对于 N Parent,N 个查询从 Child 表中检索信息。
JOIN
加入
@OneToMany(mappedBy="tableName", cascade=CascadeType.ALL)
@Column(name="id")
@Fetch(FetchMode.JOIN)
This is similar to the SELECT fetch strategy except that fact that all database retrieval take place upfront in JOIN fetch unlike in SELECT where it happens on a need basis. This can become an important performance consideration.
这类似于 SELECT fetch 策略,不同之处在于所有数据库检索都在 JOIN fetch 中预先发生,这与 SELECT 不同,它是根据需要发生的。这可能成为一个重要的性能考虑因素。
SUBSELECT
子选择
@OneToMany(mappedBy="tableName", cascade=CascadeType.ALL)
@Column(name="id")
@Fetch(FetchMode.SUBSELECT)
Two SQLs are fired. One to retrieve all Parent and the second uses a SUBSELECT query in the WHERE clause to retrieve all child that has matching parent ids.
两个 SQL 被触发。第一个检索所有父级,第二个在 WHERE 子句中使用 SUBSELECT 查询检索具有匹配父级 ID 的所有子级。
BATCH
批
@OneToMany(mappedBy="tableName", cascade=CascadeType.ALL)
@Column(name="id")
@@BatchSize(size=2)
The batch size maps to the number of Parent whose child are retrieved. So we can specify the number of records to be fetched at a time.But Multiple queries will be executed.!!
one-to-many & many-to-many allows - join, Select and SubSelect
many-to-one & one-to-one allows - Join and Select
批量大小映射到检索其子代的父代的数量。所以我们可以指定一次要获取的记录数。但是会执行多个查询。!!
一对多和多对多允许 - 加入、选择和子选择
多对一和一对一允许 - 加入和选择
Hibernate also distinguishes between (when is the associations are fetched)
Hibernate 还区分(何时获取关联)
1.Immediate fetching-
1.立即获取-
an association, collection or attribute is fetched immediately, when the Parent is loaded. (lazy=“false”)
加载 Parent 时,会立即获取关联、集合或属性。(懒惰=“假”)
2.Lazy collection fetching-
2.懒惰的集合获取-
a collection is fetched when the application invokes an operation upon that collection. (This is the default for collections.(lazy=“true”)
当应用程序对该集合调用操作时,将获取该集合。(这是集合的默认设置。(lazy="true")
3."Extra-lazy" collection fetching -
3、“超懒”收藏——
individual elements of the collection are accessed from the database as needed. Hibernate tries not to fetch the whole collection into memory unless absolutely needed (suitable for very large collections) (lazy=“extra”)
根据需要从数据库访问集合的各个元素。Hibernate 尽量不将整个集合提取到内存中,除非绝对需要(适用于非常大的集合)(lazy=“extra”)
4.Proxy fetching-
4.代理获取-
a single-valued association is fetched when a method other than the identifier getter is invoked upon the associated object. (lazy=“proxy”)
当对关联对象调用标识符 getter 以外的方法时,将获取单值关联。(懒惰=“代理”)
5."No-proxy" fetching -
5.“无代理”获取 -
a single-valued association is fetched when the instance variable is accessed. Compared to proxy fetching, this approach is less lazy.(lazy=“no-proxy”)
访问实例变量时获取单值关联。与代理获取相比,这种方法不那么懒惰。(lazy="no-proxy")
6.Lazyattribute fetching -
6.懒惰的属性获取 -
an attribute or single valued association is fetched when the instance variable is accessed. (lazy=“true”)
one-to-many & many-to-many allows Immediate, Layzy, Extra Lazy
many-to-one & one-to-one allows Immediate Proxy, No Proxy
访问实例变量时获取属性或单值关联。(懒惰=“真”)
一对多和多对多允许 Immediate、Layzy、Extra Lazy
多对一和一对一允许立即代理,无代理
回答by sola
Your EAGER mapping will only be considered automatically by Hibernate if you use the Criteria API for the query.
如果您使用 Criteria API 进行查询,则 Hibernate 只会自动考虑您的 EAGER 映射。
If you use HQL, you will need to manually add the FETCH keyword to your JOINs to force Hibernate to include the relations in the first query and avoid subsequent queries.
如果您使用 HQL,您将需要手动将 FETCH 关键字添加到您的 JOIN 以强制 Hibernate 在第一个查询中包含关系并避免后续查询。
This is Hibernate-specific and may work differently on other ORMs.
这是特定于 Hibernate 的,在其他 ORM 上的工作方式可能有所不同。
See this question/answerfor a slightly different angle.
从稍微不同的角度看这个问题/答案。