繁体   English   中英

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

[英]Hibernate HQL join fetch not recursively fetching

我有以下查询和方法

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<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 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查询?

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. 懒人收集 -

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

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

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

4. 代理提取 -

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

5.“ 无代理 ”获取 -

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

6. 懒惰属性获取 -

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

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

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

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

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

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

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

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

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

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

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

它没有记录好,但你尝试设置FetchMode吗? 您可以使用Criteria API: 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之前做出正确的渴望获取。 因此,它需要发送多个联接查询,具体取决于您在运行时查询的父域的深度。

对我来说,看起来你认为HQL和你的案例中产生的SQL /(s)可能具有一对一的相应性,这是不正确的。 使用HQL,您可以查询对象,并且orm决定如何根据映射加载该对象及其关系(eager / lazy),或者您也可以在运行时指定它们(例如,查询中可以覆盖映射中的惰性关联)但反之亦然)。 你可以告诉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包装器),并且都将initialized属性设置为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);
    }

在此输入图像描述

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM