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

时间:2013-08-27 19:13:10

标签: java hibernate jpa 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&gt;关系,但它没有。假设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查询?

7 个答案:

答案 0 :(得分:13)

Hibernate Relations可以使用不同的Fetch Strategies .. !!

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

选择

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

在此方法中,有多个SQL被触发。第一个被解雇了   用于检索父表中的所有记录。剩下的就是   因为检索每个父记录的记录而被解雇。这基本上是   N + 1问题。第一个查询从中检索数据库中的N条记录   这种情况N父记录。对于每个Parent,新查询将检索   儿童。因此对于N Parent,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)
  

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

<强> BATCH

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

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

     

一对多&amp;多对多允许 - 加入,选择和子选择

     

多对一&amp;一对一允许 - 加入并选择


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

1. 立即提取 -

  

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

2. 延迟收藏 -

  

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

3.“极端懒惰”集合提取 -

  

从数据库访问集合的各个元素   如所须。 Hibernate试图不将整个集合提取到   内存,除非绝对需要(适合非常大的收藏)   (懒惰=“额外的”)

4. 代理抓取 -

  

当一个方法以外的方法获取单值关联   在关联对象上调用标识符getter。   (懒惰=“代理”)

5.“无代理”抓取 -

  

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

6. 延迟属性提取 -

  

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

     

一对多&amp;多对多允许立即,懒惰,非常懒惰

     

多对一&amp;一对一允许立即代理,无代理

答案 1 :(得分:11)

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

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

答案 2 :(得分:4)

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

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

答案 3 :(得分:2)

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

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

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

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

答案 4 :(得分:1)

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

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

答案 5 :(得分:0)

由于您已为FetchType.EAGERnetworkCodes指定了operators,因此每当您查询domain时,hibernate都会加载networkCodes和{{1} }}。这就是operators获取模式

的全部概念

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

EAGER

API详细信息here

干杯!!

答案 6 :(得分:0)

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

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

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

对我而言,您认为HQL和您的案例中产生的SQL /(s)可能具有一对一的相应性,而不是 真正。使用HQL,您可以查询对象,并且orm决定如何加载该对象及其关系(eager / lazy)基于 映射或您也可以在运行时指定它们(例如,映射中的惰性关联可以被Query api覆盖,但反之亦然)。 你可以告诉orm要加载什么(我的标记渴望或懒惰)以及如何加载(使用join / sub select)。

<强>更新

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

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