我知道在会话之外使用延迟加载对象/集合,我们这样Hibernate.initialize(Object obj)
做,初始化作为参数传递给initialize()方法的对象,并且可以在会话范围之外使用.
但是我无法理解它是如何工作的.我的意思是,如果我们这样做,那么我们最终会有急切的提取,所以为什么我们在配置中做了懒惰,最终在运行时进行了急切的提取.
换句话说,我想知道使用Hibernate.initialize()
和eagerly
加载该对象之间的区别.
我弄错了还是错过了什么?
Don*_*oby 32
不同之处在于应用范围.
使集合关联变得懒惰的原因是,如果您不真正需要它,则每次加载父对象时都要避免加载集合.
如果您正在懒惰加载集合,但是对于特定用途,您需要确保在会话关闭之前已加载集合,您可以Hibernate.initialize(Object obj)
按照您的说明使用.
如果你实际上总是需要加载集合,你应该确实加载它.但在大多数软件中,情况并非如此.
Vla*_*cea 15
在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()
);
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
实例表示,该实例仅包含Post
从post_id
post_comment 数据库表行的列中检索到的实体标识符。
现在,由于调用了该
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()
);
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)
无论是PostComment
与post
关联是从第二级高速缓存取出作为由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)
请考虑以下示例:
我有一个实体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'最初将为空.