我知道要在会话之外使用延迟加载对象/集合,我们这样做Hibernate.initialize(Object obj)
是为了初始化作为参数传递给 initialize() 方法的对象,并且可以在会话范围之外使用。
但是我无法理解这是如何工作的。我的意思是,如果我们这样做,那么我们最终会急切地获取,所以为什么我们在配置中懒惰并最终在运行时急切地获取。
换句话说,我想知道使用Hibernate.initialize()
和eagerly
加载该对象之间的区别。
我弄错了还是错过了什么?
我知道要在会话之外使用延迟加载对象/集合,我们这样做Hibernate.initialize(Object obj)
是为了初始化作为参数传递给 initialize() 方法的对象,并且可以在会话范围之外使用。
但是我无法理解这是如何工作的。我的意思是,如果我们这样做,那么我们最终会急切地获取,所以为什么我们在配置中懒惰并最终在运行时急切地获取。
换句话说,我想知道使用Hibernate.initialize()
和eagerly
加载该对象之间的区别。
我弄错了还是错过了什么?
区别在于适用范围。
使集合关联变得惰性的原因是避免在您不需要时每次加载父对象时都加载集合。
如果您是正常延迟加载集合,但对于特定用途,您需要确保在会话关闭之前已加载集合,您可以Hibernate.initialize(Object obj)
按照您的说明使用。
如果您实际上总是需要加载集合,那么您确实应该急切地加载它。但在大多数软件中,情况并非如此。
仅当Hibernate.initialize(proxy)
您使用二级缓存时才有用。否则,您将发出第二个查询,其效率低于仅使用初始查询初始化代理。
因此,在执行以下测试用例时:
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
我们可以看到二级缓存被正确清除,并且在获取PostComment
实体之后,post 实体由一个HibernateProxy
实例表示,该实例仅包含从post_comment 数据库表行的列中Post
检索到的实体标识符。post_id
现在,由于对该
Hibernate.initialize
方法的调用,会执行辅助 SQL 查询来获取Post
实体,这不是很有效,可能会导致 N+1 查询问题。
在前一种情况下,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()
);
这一次,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`
PostComment
和关联都是post
从二级缓存中获取的,如缓存命中日志消息所示。
因此,如果您使用的是二级缓存,则可以使用
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`
考虑以下示例:
我有一个实体 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”)最初都是空的,除非您真的想要它们。
考虑@Don Ruby 答案
下一个区别是Hibernate.initialize
生成并执行额外的 sql 来获取数据。因此,您可以在会话关闭后使用它。当您Eager
在实体中使用 fetch 时,它总是在数据库中查找数据期间(在连接会话下)获取该集合,而不是在它之后。
实际上,如果您使用 EAGER,当您有大量收藏时,它确实会影响您的性能。因此,在这种情况下使用Hibernate.initialize
.
考虑您有一个可能与其他 4 个表有关系的表。在这种情况下,如果您使用 eager then,则所有四个相关表中的所有对应关系都将在每个 fetch 操作中获取。
但是考虑到您可能需要从相关表中的一个表中获取数据,因此在这种情况下,您可以仅获取所需的关系,而不是使用 Hibernate.initialize 工具加载整个四个相关表的数据.