14

我有以下查询和方法

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>and Set<Domain> 关系,但事实并非如此。假设DomainI 查询有两个运算符,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 查询可以两者兼得?

4

7 回答 7

14

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

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
于 2013-10-09T09:47:55.727 回答
13

Hibernate 关系适用于不同的 Fetch 策略..!!

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

选择

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

在此方法中,触发了多个 SQL。第一个被触发以检索父表中的所有记录。其余的被触发以检索每个父记录的记录。这基本上是 N+1 问题。第一个查询从数据库中检索 N 条记录,在本例中为 N 条父记录。对于每个父项,一个新查询检索子项。因此对于 N 个 Parent,N 个查询从 Child 表中检索信息。

加入

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

这类似于 SELECT fetch 策略,除了所有数据库检索都在 JOIN fetch 中预先发生,而 SELECT 中它是根据需要发生的。这可能成为重要的性能考虑因素。

子选择

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

两个 SQL 被触发。一个检索所有父项,第二个在 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.属性获取——

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

一对多和多对多允许立即,懒惰,额外懒惰

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

于 2013-10-15T09:55:48.327 回答
4

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

为避免这种情况,请使您的集合变得懒惰(默认情况下)。然后用它的运营商加载一个域,它的网络代码将只加载那个。

于 2013-10-08T20:36:05.913 回答
2

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

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

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

请参阅此问题/答案以获得稍微不同的角度。

于 2013-10-16T07:32:35.313 回答
1

它没有记录得那么好,但是您是否尝试设置FetchMode?您可以使用 Criteria API:domainCriteria.setFetchMode("operators", JOIN)@Fetch(JOIN)在关系定义处使用。

注释(并且似乎只有注释)还允许设置 fetch mode SUBSELECT,这至少应该限制 Hibernate 最多执行 3 个查询。不知道您的数据集,我认为这应该是适合您的方法,因为对这些表进行大连接似乎不太健康。最好自己解决,我猜...

于 2013-10-09T10:35:00.370 回答
0

由于您已经为 and 指定了FetchType.EAGER两者,因此无论何时查询,hibernate 都会同时加载and 。这就是获取模式的全部想法networkCodesoperatorsdomainnetworkCodesoperatorsEAGER

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

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

API详细信息在这里

干杯!!

于 2013-10-11T15:39:44.720 回答
0

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

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

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

在我看来,您似乎认为 HQL 和您的情况下生成的 SQL/('s) 可以具有一对一的对应关系,这是不正确的。使用 HQL,您可以查询对象,然后 orm 根据映射决定如何加载该对象及其关系(急切/惰性),或者您也可以在运行时指定它们(例如,映射中的惰性关联可以被 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);
    }

在此处输入图像描述

于 2013-10-13T08:47:42.260 回答