Hibernate.initialize()的工作原理

Ana*_*and 21 java hibernate

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

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

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

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

Don*_*oby 32

不同之处在于应用范围.

使集合关联变得懒惰的原因是,如果您不真正需要它,则每次加载父对象时都要避免加载集合.

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

如果你实际上总是需要加载集合,你应该确实加载它.但在大多数软件中,情况并非如此.


Vla*_*cea 15

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()
);
Run Code Online (Sandbox Code Playgroud)

首先,我们将清除二级缓存,因为除非您明确启用二级缓存并配置提供程序,否则 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
Run Code Online (Sandbox Code Playgroud)

我们可以看到二级缓存被正确驱逐,并且在获取PostComment实体后,post 实体由一个HibernateProxy实例表示,该实例仅包含Postpost_idpost_comment 数据库表行的列中检索到的实体标识符。

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

在 JPQL 中使用 JOIN FETCH

在前一种情况下,PostComment应该使用 JOIN FETCH JPQL 指令与其后关联一起获取。

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()
);
Run Code Online (Sandbox Code Playgroud)

这一次,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
Run Code Online (Sandbox Code Playgroud)

使用二级缓存 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()
);
Run Code Online (Sandbox Code Playgroud)

这一次,我们不再逐出二级缓存区域,并且由于我们使用的是 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`
Run Code Online (Sandbox Code Playgroud)

无论是PostCommentpost关联是从第二级高速缓存取出作为由Cache所示击中日志消息。

因此,如果您使用的是二级缓存,则可以使用Hibernate.initiaize来获取实现业务用例所需的额外关联。在这种情况下,即使您有 N+1 个缓存调用,由于二级缓存配置正确并且数据从内存中返回,因此每个调用都应该运行得非常快。

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);
Run Code Online (Sandbox Code Playgroud)

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'}
]
Run Code Online (Sandbox Code Playgroud)

但是,如果PostComment集合已被缓存:

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

运行前面的测试用例时,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`
Run Code Online (Sandbox Code Playgroud)


Jat*_*hoo 5

请考虑以下示例:

我有一个实体LoanApplication(在这种情况下是一个非常重的对象),它内部有各种字段(也可能很大).例如,考虑LoanApplication中的SubLoan字段.

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

在此示例中,FetchType为LAZY.现在,如果在执行某些操作时在某些控制器的方法中遇到LoanApplication,则subLoans集最初将为null,除非您希望使用它.在这种情况下,您使用Hibernate.initialize如下:

Hibernate.initialize(loanApplication.getSubLoans());
Run Code Online (Sandbox Code Playgroud)

这有助于主要提高性能,因为每次检索LoanApplication时,除非您真的需要,否则大量对象即'subLoan'最初将为空.