2013-08-27 30 views
13

我有以下查询和方法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作为

@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 
} 

我希望这个单一的查询,以获取Set<NetworkCode>Set<Domain>的关系,但它没有。再说说我Domain查询有两个运营商时,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查询,可以同时执行这两个操作?

回答

10

如果你知道你的树只有两个关卡,你有没有想过要加入更深的一个关卡。像下面的东西?

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 
3

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

为了避免这种情况,请使您的收藏懒惰(因为它们是默认情况下)。然后用它的运营商和它的网络代码加载一个域将会加载。

+0

这些集合需要'EAGER',以便它们在我的'Transaction'边界内初始化。我在视图图层中使用它们。我想知道是否有可能一次加载所有运营商/网络代码,而不是每个“域”的查询。 –

+1

不是不使用专有SQL(例如,请参阅http://docs.oracle.com/cd/B19306_01/server.102/b14200/queries003.htm),也可以不调整模型以引入例如树,并加载所有具有相同根节点的域。难道你不能简单地使一切都懒惰,并使用OpenSessionInView?使用tomany关联渴望几乎总是一个坏主意,尤其是如果递归的话:这使得它渴望**每个**用例,包括那些只需要加载一个特定实体的用例。 –

+0

问题不在于延迟加载。无论如何,所有这些数据都将被检索。我只是不想发送大量的SQL请求。我会仔细看看你链接的文件。给它几天。谢谢。 –

1

这是没有记录,很好,但你尝试设置FetchMode? 您可以通过使用Criteria API:domainCriteria.setFetchMode("operators", JOIN)或在关系定义处使用@Fetch(JOIN)来完成此操作。

注解(并且看起来只有注解)还允许设置一个获取模式SUBSELECT,它至少应该限制Hibernate执行最多3个查询。不知道你的数据集,我认为这应该是你的方式,因为对这些表的大加入看起来不太健康。最好弄清楚自己,我猜...

+0

我的HQL查询已经做了'FETCH JOIN'。我会尝试'SUBSELECT'。 –

+0

我不确定您使用的HQL符号是否与hibernate所理解的“获取模式”相同。 Afaik,HQL'JOIN FETCH'只是表示“哦,顺便说一句,完全初始化这种关系,而不仅仅是获取ID”--Hibernate并不解释它应该采用何种策略因为之前的'JOIN'只是应用'FETCH'的必要语法。但是,由于这里没有明确提到,我可能已经将我的经验与HQL和注解用法混淆了。 – skirsch

+0

呵呵,当你提出你的HQL:因为无论如何你已经使用'EAGER'注释了实现,你的查询是否会产生与简单的“SELECT DISTINCT domain FROM Domain domain WHERE domain.domainId =:domainId”不同的结果?为什么“DISTINCT”?因为'domainId'是PK,那么怎么会有不止一个结果呢? – skirsch

0

既然你已经两个networkCodesoperators指定FetchType.EAGER,只要你将查询domain,Hibernate会加载两个networkCodesoperators。这就是EAGER的整体思路获取模式

所以,你可以查询更改简单如下:

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

API细节here

干杯!

+1

EAGER fetch并不意味着hibernate会执行一个单一的sql查询:这意味着所有必须快速获取的对象将与根对象同时获取(即不一定在同一个查询中) – ben75

+0

@ ben75 - 是你是对的,不一定是相同的查询,但他们会同时加载。 –

+0

请注意,我的查询不是一个简单的'JOIN',它是'LEFT OUTER JOIN'。由于返回的实体不正确,我无法摆脱它。 –

0

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

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

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

对我来说,看起来你认为HQL和你的情况下产生的SQL /(s)可以有一对一的对应关系,这不是 是真的。使用HQL查询对象,orm决定如何基于映射来加载该对象及其关系(渴望/懒惰),或者您也可以在运行时指定它们(例如,映射中的惰性关联可以被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和操作集合是实例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<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); 
    } 

enter image description here

+0

HQL中的JOIN不仅用于“EAGER”加载,它用于“LEFT OUTER”。并不是每个域都有'运营商'或'网络码'。 'LEFT OUTER JOIN'对于正确的映射是必需的。 –

+0

看到我上面的更新。 – Shailendra

+0

问题不在于他们没有被急切加载。这就是IMO有太多的SQL查询生成了。我认为Hibernate可以少用它。 –

12

Hibernate的关系作品用不同的抓取策略..!

Hibernate提供用于检索数据4种策略:

选择

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

在这种方法也有多种发射的SQL。这第一个被触发 检索父表中的所有记录。剩下的是 因为检索每个家长记录的记录而被解雇。这基本上是 的N + 1问题。第一个查询从数据库检索N个记录, 这种情况下的N个父记录。对于每个父项,新的查询将检索 子项。因此,对于N个父项,N个查询从 子表中检索信息。

JOIN

@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) 

两个SQLS被解雇。一个检索所有Parent,第二个使用WHERE子句中的SUBSELECT查询检索具有匹配父ID的 的所有子项。

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

批大小映射到其子被检索家长的数量。 因此,我们可以指定一次要提取的记录数。但是 将执行多个查询。

一个一对多&许多一对多允许 - 加入,选择和再选择

多到一个&一到一个允许 - 加入和选择


Hibernate也之间的区别(是当关联是取指)

1. 立即取出 -

的关联,集合或属性被立即取出,当 父被加载。 (懒惰=“假”)

2. 延迟集合取 -

集合被取出,当应用程序在 该集合调用的操作。 (这是一个集的默认(懒惰=“真”)

3.“特懒”集合抓取 - 收集的

单个元素从数据库 进行访问根据需要,Hibernate试图不是整个集合都抓取到 内存,除非绝对需要(适用于非常大的集合) (懒惰=“额外”)

4。代理抓取 - 当除了 标识符吸气剂以外的方法,在所述相关联的对象调用

一个单值关联被取出。 (懒惰=“代理”)

5. “无代理” 取 -

一个单值关联被取出时的实例变量是 访问。相比代理抓取,这种方法没有那么懒惰(懒惰=“无代理”)

6. 懒惰属性获取 -

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

一个一对多&多到许多允许立即,Layzy,备用懒惰

多到一个&一个对一可以立刻进行代理,无代理

+0

这是一个很好的参考,但我不能在HQL中使用'@ Fetch'。 –

+0

您可以在JPA类中将额外延迟添加到获取策略中,以便除非您访问内部表关系,否则内部查询将不会执行。通过这个,你只会从主表中获取值,其余的将被忽略,并且也会用SINGLE查询。 – Dileep

2

你EAGER映射才会自动由Hibernate,如果你使用Criteria API为查询考虑。

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

这是Hibernate特有的,可能在其他ORM上的工作方式不同。

请参阅this question/answer稍微不同的角度。