web-dev-qa-db-ja.com

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 Iクエリに2つの演算子があるとすると、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クエリを実行できますか?

14

ツリーに2つのレベルしかないことがわかっている場合、より深い1つのレベルに参加することを考えましたか。以下のようなもの?

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
12
Andrei I

Hibernate Relationsはさまざまなフェッチ戦略で動作します。

Hibernateはデータを取得するための4つの戦略を提供します:

[〜#〜]選択[〜#〜]

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

このメソッドでは、複数のSQLが起動されます。これは、親テーブルのすべてのレコードを取得するために発生します。残りは、各親レコードのレコードを取得するために発生します。これは基本的にN + 1の問題です。最初のクエリは、データベースからNレコードを取得します。この場合、N親レコードです。親ごとに、新しいクエリが子を取得します。したがって、N親の場合、Nクエリは子テーブルから情報を取得します。

[〜#〜]参加[〜#〜]

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

これは、必要に応じて発生するSELECTとは異なり、JOINフェッチですべてのデータベースの取得が事前に行われることを除いて、SELECTフェッチ戦略に似ています。これは、パフォーマンスの重要な考慮事項になる可能性があります。

[〜#〜] subselect [〜#〜]

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

2つのSQLが起動されます。 1つはすべての親を取得し、2つ目はWHERE句でSUBSELECTクエリを使用して、一致する親IDを持つすべての子を取得します。

[〜#〜]バッチ[〜#〜]

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

バッチサイズは、子が取得される親の数に対応します。一度に取得するレコード数を指定できますが、複数のクエリが実行されます。

1対多および多対多を許可-結合、選択、およびサブ選択

多対1および1対1で許可-結合および選択


Hibernateは(関連付けがフェッチされる場合)も区別します

1 .即時フェッチ-

親がロードされると、関連付け、コレクション、または属性がすぐにフェッチされます。 (lazy =“ false”)

2 .レイジーコレクションフェッチ-

コレクションは、アプリケーションがそのコレクションに対して操作を呼び出すときにフェッチされます。 (これはコレクションのデフォルトです。(lazy =“ true”)

3。 "Extra-lazy"コレクションのフェッチ-

コレクションの個々の要素は、必要に応じてデータベースからアクセスされます。 Hibernateは、絶対に必要な場合を除いて、コレクション全体をメモリにフェッチしないようにします(非常に大きなコレクションに適しています)(lazy =“ extra”)

4 .プロキシフェッチ-

関連付けられたオブジェクトで識別子ゲッター以外のメソッドが呼び出されると、単一値の関連付けがフェッチされます。 (レイジー=「プロキシ」)

5。 "プロキシなし"フェッチ-

インスタンス変数がアクセスされると、単一値の関連付けがフェッチされます。プロキシフェッチと比較して、このアプローチは遅延が少なくなります(lazy =“ no-proxy”)

6 .Lazy属性フェッチ-

属性または単一値の関連付けは、インスタンス変数がアクセスされたときにフェッチされます。 (lazy =“ true”)

1対多および多対多は、即時、レイジー、エクストラレイジーを許可します

多対1および1対1では、即時プロキシ、プロキシなしを許可

13
Dileep

関連付けをEAGERとマークしました。したがって、クエリで何をしても、Hibernateは関連するすべてのドメインと、ロードされたドメインのネットワークコードをロードします。そして、すべてのコレクションのロードが、既にロードされている空のコレクションまたはエンティティを返すまで、追加のドメインなどのドメインとネットワークコードをロードします。

これを回避するには、コレクションを遅延させます(デフォルトの場合)。次に、そのオペレーターとそのネットワークコードでドメインをロードすると、それだけがロードされます。

4
JB Nizet

クエリにCriteria APIを使用する場合、EAGERマッピングはHibernateによって自動的に考慮されます。

HQLを使用する場合、JOINにFETCHキーワードを手動で追加して、Hibernateが最初のクエリに関係を含め、後続のクエリを回避するようにする必要があります。

これはHibernate固有であり、他のORMでは動作が異なる場合があります。

少し異なる角度については この質問/回答 を参照してください。

2
sola

それはそれほど文書化されていませんが、 FetchMode を設定してみましたか? Criteria API:domainCriteria.setFetchMode("operators", JOIN)を使用するか、リレーション定義で@Fetch(JOIN)を使用することにより、これを行うことができます。

注釈(および見かけ上の注釈のみ)は、フェッチモードSUBSELECTを設定することもできます。これにより、Hibernateが少なくとも3つのクエリを実行するように制限されます。あなたのデータセットを知らないので、これらのテーブルでの大きなファットジョインはあまり健康的ではないように見えるので、これはあなたのための方法であるべきだと思います。自分で理解するのが最善だと思います...

1
skirsch

FetchType.EAGERnetworkCodesoperatorsの両方に対して、domainをクエリすると、hibernateはnetworkCodesoperatorsの両方をロードします。これがEAGERフェッチモードのアイデア全体です

したがって、クエリを次のように簡単に変更できます。

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

APIの詳細 ここ

乾杯!!

0
Sachin Thapa

私の最初の観察は、マッピングが熱心にロードされなければならないことをマッピングが示している場合、結合を含むHQLクエリを記述する必要がないことです。

ただし、結合を使用しない場合は、フェッチ戦略をサブ選択として使用するようにHibernateに指示できます。

Hibernateは、指定されたマッピングに基づいて、起動時にオブジェクトをロードするためのSQLクエリを生成し、それをキャッシュします。ただし、あなたの場合、自己と任意の深さを使用して1対多の入れ子関係があるため、HibernateがSQLを正しく熱心にフェッチする前に決定することができないようです。したがって、実行時に照会する親ドメインの深さに応じて、複数の結合照会を送信する必要があります。

私には、HQLと結果のSQL /()が1対1の対応関係になり、真実ではない可能性があると考えているようです。 HQLを使用してオブジェクトを照会し、ormはマッピングに基づいてそのオブジェクトとその関係(eager/lazy)をロードする方法を決定するか、実行時にそれらを指定することもできます(たとえば、マッピングの遅延関連付けはQuery apiによってオーバーライドできます)ただし、その逆はありません)。何をロードするか(私のマーキングeagerまたはlazy)、どのように熱心にロードするか(結合/サブ選択を使用)をormに伝えることができます。

[〜#〜]更新[〜#〜]

ドメインモデルで次のクエリを実行すると

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

0
Shailendra