Hibernate.initialize() 的工作原理

2022-09-01 04:26:02

我知道要在会话外部使用懒惰加载对象/集合,我们这样做是为了初始化作为参数传递给 initialize() 方法的对象,并且可以在会话范围之外使用。Hibernate.initialize(Object obj)

但是我无法理解这是如何运作的。我的意思是,如果我们这样做,那么我们最终会有急切的获取,所以为什么我们在配置中做懒惰,最终在运行时急切地获取。

换句话说,我想知道使用和加载该对象之间的区别。Hibernate.initialize()eagerly

是我弄错了还是错过了什么?


答案 1

区别在于适用范围。

使集合关联延迟的原因是为了避免在每次加载父对象时都加载集合(如果确实不需要它)。

如果正常延迟加载集合,但对于特定用途,需要确保在关闭会话之前已加载集合,则可以按照说明使用。Hibernate.initialize(Object obj)

如果您实际上总是需要加载集合,那么您确实应该热切地加载它。然而,在大多数软件中,情况并非如此。


答案 2

仅当您使用二级缓存时,它才有用。否则,您将发出第二个查询,其效率低于仅使用初始查询初始化代理的效率。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数据库表行的列中检索到的实体标识符。PostCommentHibernateProxyPostpost_id

现在,由于调用了该方法,因此执行辅助 SQL 查询来获取实体,这不是很有效,并且可能导致 N+1 查询问题。Hibernate.initializePost

将 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`

和关联都是从二级缓存中提取的,如缓存命中日志消息所示。PostCommentpost

因此,如果您使用的是二级缓存,则可以使用 来获取实现业务用例所需的额外关联。在这种情况下,即使您有 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`

推荐