36

セッション外で遅延読み込みオブジェクト/コレクションを使用することを知っています。initialize Hibernate.initialize(Object obj)() メソッドに引数として渡されるオブジェクトが初期化され、セッションのスコープ外で使用できるようにします。

しかし、これがどのように機能するのか理解できません。つまり、そうしている場合、熱心なフェッチが行われることになるので、なぜ構成で遅延を行い、実行時に熱心なフェッチになってしまうのかということです。

Hibernate.initialize()つまり、そのオブジェクトの使用とeagerly読み込みの違いを知りたいのです。

私はそれを間違えましたか、それとも何かを見逃しましたか?

4

6 に答える 6

39

違いは適用範囲です。

コレクションの関連付けを遅延させる理由は、本当に必要がない場合に、親オブジェクトが読み込まれるたびにコレクションが読み込まれるのを避けるためです。

通常はコレクションを遅延ロードしているが、特定の用途のために、セッションが閉じられる前にコレクションがロードされていることを確認する必要がある場合は、Hibernate.initialize(Object obj)指摘したように使用できます。

実際に常にコレクションをロードする必要がある場合は、熱心にロードする必要があります。ただし、ほとんどのソフトウェアでは、そうではありません。

于 2013-06-26T11:48:43.473 に答える
16

Hibernate.initialize(proxy)、二次キャッシュを使用している場合にのみ役立ちます。そうしないと、最初のクエリでプロキシを初期化するよりも効率の悪い 2 番目のクエリを発行することになります。

N+1 クエリの問題のリスク

したがって、次のテスト ケースを実行すると、次のようになります。

LOGGER.info("Clear the second-level cache");

entityManager.getEntityManagerFactory().getCache().evictAll();
 
LOGGER.info("Loading a PostComment");
 
PostComment comment = entityManager.find(
    PostComment.class,
    1L
);
 
assertEquals(
    "A must read!",
    comment.getReview()
);
 
Post post = comment.getPost();
 
LOGGER.info("Post entity class: {}", post.getClass().getName());
 
Hibernate.initialize(post);
 
assertEquals(
    "High-Performance Java Persistence",
    post.getTitle()
);

まず、第 2 レベルのキャッシュを明示的に有効にしてプロバイダーを構成しない限り、Hibernate は第 2 レベルのキャッシュを使用しないため、第 2 レベルのキャッシュをクリアします。

このテスト ケースを実行すると、Hibernate は次の SQL ステートメントを実行します。

-- Clear the second-level cache
 
-- Evicting entity cache: com.vladmihalcea.book.hpjp.hibernate.fetching.HibernateInitializeTest$Post
-- Evicting entity cache: com.vladmihalcea.book.hpjp.hibernate.fetching.HibernateInitializeTest$PostComment
 
-- Loading a PostComment
 
SELECT pc.id AS id1_1_0_,
       pc.post_id AS post_id3_1_0_,
       pc.review AS review2_1_0_
FROM   post_comment pc
WHERE  pc.id=1
 
-- Post entity class: com.vladmihalcea.book.hpjp.hibernate.fetching.HibernateInitializeTest$Post$HibernateProxy$5LVxadxF
 
SELECT p.id AS id1_0_0_,
       p.title AS title2_0_0_
FROM   post p
WHERE  p.id=1

2 番目のレベルのキャッシュが適切に削除され、エンティティを取得した後、post_comment データベース テーブル行の列から取得されたエンティティ識別子のみを含むインスタンスPostCommentによって投稿エンティティが表されていることがわかります。HibernateProxyPostpost_id

メソッドの呼び出しによりHibernate.initialize、セカンダリ SQL クエリが実行されてエンティティがフェッチされますが、Postこれはあまり効率的ではなく、N+1 クエリの問題につながる可能性があります。

JPQLでJOIN FETCHを使用する

前のケースではPostComment、JOIN FETCH JPQL ディレクティブを使用して、post 関連付けとともに をフェッチする必要があります。

LOGGER.info("Clear the second-level cache");
 
entityManager.getEntityManagerFactory().getCache().evictAll();
 
LOGGER.info("Loading a PostComment");
 
PostComment comment = entityManager.createQuery(
    "select pc " +
    "from PostComment pc " +
    "join fetch pc.post " +
    "where pc.id = :id", PostComment.class)
.setParameter("id", 1L)
.getSingleResult();
 
assertEquals(
    "A must read!",
    comment.getReview()
);
 
Post post = comment.getPost();
 
LOGGER.info("Post entity class: {}", post.getClass().getName());
 
assertEquals(
    "High-Performance Java Persistence",
    post.getTitle()
);

今回は、Hibernate が単一の SQL ステートメントを実行するため、N+1 クエリの問題に遭遇するリスクはなくなりました。

-- Clear the second-level cache
 
-- Evicting entity cache: com.vladmihalcea.book.hpjp.hibernate.fetching.HibernateInitializeTest$Post
-- Evicting entity cache: com.vladmihalcea.book.hpjp.hibernate.fetching.HibernateInitializeTest$PostComment
 
-- Loading a PostComment
 
SELECT pc.id AS id1_1_0_,
       p.id AS id1_0_1_,
       pc.post_id AS post_id3_1_0_,
       pc.review AS review2_1_0_,
       p.title AS title2_0_1_
FROM   post_comment pc
INNER JOIN post p ON pc.post_id=p.id
WHERE  pc.id=1
 
-- Post entity class: com.vladmihalcea.book.hpjp.hibernate.fetching.HibernateInitializeTest$Post

2 次キャッシュの使用Hibernate.initialize

したがって、 をHibernate.initialize使用する価値がある場合を確認するには、2 次キャッシュを使用する必要があります。

LOGGER.info("Loading a PostComment");
 
PostComment comment = entityManager.find(
    PostComment.class,
    1L
);
 
assertEquals(
    "A must read!",
    comment.getReview()
);
 
Post post = comment.getPost();
 
LOGGER.info("Post entity class: {}", post.getClass().getName());
 
Hibernate.initialize(post);
 
assertEquals(
    "High-Performance Java Persistence",
    post.getTitle()
);

今回は、第 2 レベルのキャッシュ領域を削除しなくなりました。また、READ_WRITE キャッシュ同時実行戦略を使用しているため、エンティティは永続化された直後にキャッシュされるため、テストの実行時に SQL クエリを実行する必要はありません。上記のケース:

-- Loading a PostComment
 
-- Cache hit : 
region = `com.vladmihalcea.book.hpjp.hibernate.fetching.HibernateInitializeTest$PostComment`, 
key = `com.vladmihalcea.book.hpjp.hibernate.fetching.HibernateInitializeTest$PostComment#1`
 
-- Proxy class: com.vladmihalcea.book.hpjp.hibernate.fetching.HibernateInitializeTest$Post$HibernateProxy$rnxGtvMK
 
-- Cache hit : 
region = `com.vladmihalcea.book.hpjp.hibernate.fetching.HibernateInitializeTest$Post`, 
key = `com.vladmihalcea.book.hpjp.hibernate.fetching.HibernateInitializeTest$Post#1`

PostCommentと関連付けの両方がpost、キャッシュ ヒット ログ メッセージに示されているように、第 2 レベルのキャッシュから取得されます。

そのため、二次キャッシュを使用している場合は、 を使用してHibernate.initiaize、ビジネス ユース ケースを満たすために必要な追加の関連付けをフェッチしても問題ありません。この場合、N+1 キャッシュ呼び出しがあっても、2 番目のレベルのキャッシュが適切に構成され、データがメモリから返されるため、各呼び出しは非常に高速に実行されます。

Hibernate.initializeおよびプロキシ コレクション

コレクションにHibernate.initializeも使えます。現在、第 2 レベルのキャッシュ コレクションはリードスルーであるため、次のテスト ケースを実行すると、最初に読み込まれたときにキャッシュに格納されます。

LOGGER.info("Loading a Post");
 
Post post = entityManager.find(
    Post.class,
    1L
);
 
List<PostComment> comments = post.getComments();
 
LOGGER.info("Collection class: {}", comments.getClass().getName());
 
Hibernate.initialize(comments);
 
LOGGER.info("Post comments: {}", comments);

Hibernate は SQL クエリを実行してPostCommentコレクションをロードします。

-- Loading a Post
 
-- Cache hit : 
region = `com.vladmihalcea.book.hpjp.hibernate.fetching.HibernateInitializeTest$Post`, 
key = `com.vladmihalcea.book.hpjp.hibernate.fetching.HibernateInitializeTest$Post#1`
 
-- Collection class: org.hibernate.collection.internal.PersistentBag
 
- Cache hit, but item is unreadable/invalid : 
region = `com.vladmihalcea.book.hpjp.hibernate.fetching.HibernateInitializeTest$Post.comments`, 
key = `com.vladmihalcea.book.hpjp.hibernate.fetching.HibernateInitializeTest$Post.comments#1`
 
SELECT pc.post_id AS post_id3_1_0_,
       pc.id AS id1_1_0_,
       pc.id AS id1_1_1_,
       pc.post_id AS post_id3_1_1_,
       pc.review AS review2_1_1_
FROM   post_comment pc
WHERE  pc.post_id=1
 
-- Post comments: [
    PostComment{id=1, review='A must read!'}, 
    PostComment{id=2, review='Awesome!'}, 
    PostComment{id=3, review='5 stars'}
]

ただし、PostCommentコレクションが既にキャッシュされている場合:

doInJPA(entityManager -> {
    Post post = entityManager.find(Post.class, 1L);
 
    assertEquals(3, post.getComments().size());
});

前のテスト ケースを実行すると、Hibernate はすべてのデータをキャッシュからのみ取得できます。

-- Loading a Post
 
-- Cache hit : 
region = `com.vladmihalcea.book.hpjp.hibernate.fetching.HibernateInitializeTest$Post`, 
key = `com.vladmihalcea.book.hpjp.hibernate.fetching.HibernateInitializeTest$Post#1`
 
-- Collection class: org.hibernate.collection.internal.PersistentBag
 
-- Cache hit : 
region = `com.vladmihalcea.book.hpjp.hibernate.fetching.HibernateInitializeTest$Post.comments`, 
key = `com.vladmihalcea.book.hpjp.hibernate.fetching.HibernateInitializeTest$Post.comments#1`
 
-- Cache hit : 
region = `com.vladmihalcea.book.hpjp.hibernate.fetching.HibernateInitializeTest$PostComment`, 
key = `com.vladmihalcea.book.hpjp.hibernate.fetching.HibernateInitializeTest$PostComment#1`
 
-- Cache hit : 
region = `com.vladmihalcea.book.hpjp.hibernate.fetching.HibernateInitializeTest$PostComment`, 
key = `com.vladmihalcea.book.hpjp.hibernate.fetching.HibernateInitializeTest$PostComment#2`
 
-- Cache hit : 
region = `com.vladmihalcea.book.hpjp.hibernate.fetching.HibernateInitializeTest$PostComment`, 
key = `com.vladmihalcea.book.hpjp.hibernate.fetching.HibernateInitializeTest$PostComment#3`
于 2018-12-08T17:38:43.120 に答える
6

次の例を検討してください。

内部にさまざまなフィールドを持つエンティティ LoanApplication (この場合は非常に重いオブジェクト) があります (これも大きい場合があります)。たとえば、 LoanApplication の SubLoan フィールドを考えてみましょう。

@OneToMany(fetch = FetchType.LAZY)
@JoinColumn(name = "application_fk")
@Index(name = "appl_fk_idx_subloansLoanApp")
private Set<SubLoan> subLoans;

この例では、FetchType は LAZY です。いくつかの操作を実行しているときにコントローラーのメソッドで LoanApplication に遭遇した場合、使用する必要がない限り、subLoans セットは最初は null になります。その場合、次のように Hibernate.initialize を使用します。

Hibernate.initialize(loanApplication.getSubLoans());

これは主にパフォーマンスの向上に役立ちます。これは、LoanApplication を取得するたびに、オブジェクトの大きなセット、つまり「subLoan」が本当に必要でない限り、最初は空になるためです。

于 2016-01-15T11:04:57.907 に答える
1

実際、EAGER を使用すると、大きなコレクションがある場合にパフォーマンスに大きな影響を与えます。したがって、このような状況では を使用することをお勧めしHibernate.initializeます。

これを見てください: Hibernate Lazy Fetch vs Eager Fetch Type

于 2015-08-25T00:36:25.583 に答える
0

他の 4 つのテーブルと関係がある可能性のあるテーブルがあるとします。この場合、eager then を使用すると、すべての fetch 操作で 4 つの関連テーブルすべてから対応するすべての関係が取得されます。

ただし、関連するテーブルのうち 1 つのテーブルからのデータのみが必要な場合があると考えてください。この場合、Hibernate.initialize 機能を使用して 4 つの関連するテーブルのデータ全体をロードする代わりに、必要なリレーションシップのみを取得できます。 .

于 2017-12-19T08:47:24.513 に答える