は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
によって投稿エンティティが表されていることがわかります。HibernateProxy
Post
post_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`