Hibernate.initialize()的工作原理

时间:2013-06-26 11:03:54

标签: java hibernate

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

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

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

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

5 个答案:

答案 0 :(得分:32)

不同之处在于适用范围。

使集合关联变得懒惰的原因是为了避免在每次加载父对象时加载集合,如果你真的不需要它的话。

如果您正在懒惰地加载一个集合,但是对于特定用途,您需要确保在会话关闭之前已加载该集合,您可以使用Hibernate.initialize(Object obj),如您所述。

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

答案 1 :(得分:5)

考虑以下示例:

我有一个实体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”。除非你真的想要它们,否则它们最初是空的。

答案 2 :(得分:3)

正如我在this article中所述,Hibernate.initialize(proxy)仅在使用二级缓存时才有用。否则,您将发出一个二级查询,该二级查询的效率要比仅使用初始查询初始化代理低。

引发N + 1个查询问题

因此,在执行以下测试用例时:

LOGGER.info(“清除二级缓存”);

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

我们可以看到二级缓存已被正确清除,并且在获取PostComment实体之后,post实体由HibernateProxy实例表示,该实例仅包含Post从post_comment数据库表行的post_id列中检索到的实体标识符。

  

现在,由于调用了Hibernate.initialize方法,将执行辅助SQL查询来获取Post实体,这不是很有效,并且可以导致N+1 query issues。 / p>

通过JPQL使用JOIN FETCH

在以前的情况下,应使用JOIN FETCH JPQL指令来获取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

将第二级缓存与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关联,如“缓存命中日志”消息所示。

  

因此,如果您使用的是二级缓存,则可以使用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);

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`

答案 3 :(得分:2)

考虑@Don Ruby回答

下一个区别是Hibernate.initialize生成并执行额外的sql以获取数据。因此,您可以在会话结束后使用它。当您在实体中使用Eager fetch时,它总是在数据库中查找数据(在连接会话下)时获取该集合,但不会在它之后获取。

答案 4 :(得分:0)

考虑你有一张表可能与其他4个表有关系。在这种情况下,如果您使用eager,那么将在每次获取操作中获取所有四个相关表中的所有对应关系。

但是考虑到您可能处于这样一种要求,即您只需要来自相关表中的一个表的数据,因此在这种情况下,您只能获取所需的关系而不是提取整个四个相关的表。使用Hibernate.initialize工具的数据。