仅当您使用二级缓存时,它才有用。否则,您将发出第二个查询,其效率低于仅使用初始查询初始化代理的效率。Hibernate.initialize(proxy)
冒着 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()
);
首先,我们将清除二级缓存,因为除非您显式启用二级缓存并配置提供程序,否则Hibernate不会使用二级缓存。
运行此测试用例时,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
我们可以看到,第二级缓存已正确逐出,并且在获取实体后,post 实体由一个实例表示,该实例仅包含从post_comment数据库表行的列中检索到的实体标识符。PostComment
HibernateProxy
Post
post_id
现在,由于调用了该方法,因此执行辅助 SQL 查询来获取实体,这不是很有效,并且可能导致 N+1 查询问题。Hibernate.initialize
Post
将 JOIN FETCH 与 JPQL 结合使用
在前面的案例中,应使用 JOIN FETCH JPQL 指令获取其 post 关联来获取 。PostComment
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
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()
);
这一次,我们不再逐出二级缓存区域,并且由于我们使用的是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
因此,如果您使用的是二级缓存,则可以使用 来获取实现业务用例所需的额外关联。在这种情况下,即使您有 N+1 个缓存调用,每个调用也应该运行得非常快,因为二级缓存配置正确,数据是从内存中返回的。Hibernate.initiaize
Hibernate.initialize
和代理收集
也可以用于集合。现在,因为二级缓存集合是通读的,这意味着在运行以下测试用例时,它们在第一次加载时存储在缓存中:Hibernate.initialize
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);
休眠执行 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`