Java Hibernate.initialize()的工作原理
我知道要在会话之外使用惰性加载对象/集合,我们需要Java Hibernate.initialize()的工作原理,java,hibernate,proxy,lazy-initialization,Java,Hibernate,Proxy,Lazy Initialization,我知道要在会话之外使用惰性加载对象/集合,我们需要Hibernate.initialize(Object obj),以便作为参数传递给initialize()方法的对象被初始化,并且可以在会话范围之外使用 但我无法理解它是如何工作的。我的意思是,如果我们这样做了,那么我们最终会进行急切的抓取,那么为什么我们会在配置中偷懒,并最终在运行时进行急切的抓取呢 换句话说,我想知道使用Hibernate.initialize()和急切地加载该对象之间的区别 我是弄错了还是遗漏了什么?区别在于适用范围 使集
Hibernate.initialize(Object obj)
,以便作为参数传递给initialize()方法的对象被初始化,并且可以在会话范围之外使用
但我无法理解它是如何工作的。我的意思是,如果我们这样做了,那么我们最终会进行急切的抓取,那么为什么我们会在配置中偷懒,并最终在运行时进行急切的抓取呢
换句话说,我想知道使用Hibernate.initialize()
和急切地加载该对象之间的区别
我是弄错了还是遗漏了什么?区别在于适用范围 使集合关联变为惰性的原因是避免在不需要父对象的情况下,每次加载父对象时都让它加载集合 如果您在正常情况下延迟加载集合,但对于特定用途,您需要确保在会话关闭之前已加载集合,您可以使用
Hibernate.initialize(Object obj)
如果您实际上总是需要加载集合,那么您确实应该急切地加载它。然而,在大多数软件中,情况并非如此。考虑@Don Ruby answer
下一个区别是,
Hibernate.initialize
生成并执行额外的sql以获取数据。所以您可以在会话关闭后使用它。当您在实体中使用Eager
fetch时,在数据库中查找数据(在连接会话下)时,始终会提取集合,但在数据库中查找数据之后不会提取。实际上,如果您使用Eager,当您有大集合时,它确实会影响您的性能。因此,在这种情况下使用Hibernate.initialize
是一个好主意
看看这个:
考虑以下示例: 我有一个实体LoanApplication(在本例中,它是一个非常重的对象),其中包含各种字段(也可能很大)。考虑一下LoanApplication的次级贷款领域。
@OneToMany(fetch = FetchType.LAZY)
@JoinColumn(name = "application_fk")
@Index(name = "appl_fk_idx_subloansLoanApp")
private Set<SubLoan> subLoans;
这主要有助于提高性能,因为每次检索LoanApplication时,大型对象集(即“subLoan”)最初将为空,除非您确实需要它们。考虑您有一个表,该表可能与其他4个表有关系。在这种情况下,如果使用eager-then,那么在每次获取操作中都将获取所有四个相关表中的所有对应关系
但是,您可能需要这样的需求,即只需要从相关表中的一个表中获取数据,因此在这种情况下,您可以只获取所需的关系,而不是使用Hibernate初始化设备来装载整个四个相关表的数据。
< P> >代码> Hibernate。仅在使用二级缓存时有用。否则,您将发出第二个查询,该查询的效率低于仅使用初始查询初始化代理的效率 冒着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()
);
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不会使用二级缓存
运行此测试用例时,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\u注释数据库表行的post\id
列检索的post
实体标识符
现在,由于调用了Hibernate.initialize
方法,执行了一个辅助SQL查询来获取Post
实体,这不是很有效,可能会导致N+1查询问题
在JPQL中使用joinfetch
在前一种情况下,应该使用JOIN-FETCH-JPQL指令获取post命令
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
何时真正值得使用,您需要使用二级缓存:
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()
);
这一次,我们不再逐出二级缓存区域,而且,由于我们使用读写缓存并发策略,实体在持久化后立即被缓存,因此在运行上述测试用例时不需要执行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
也可用于集合。现在,因为二级缓存集合是通读的,这意味着它们在运行以下测试用例时第一次加载时存储在缓存中:
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()
);
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);
但是,如果已缓存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`
如果您有一个将在会话外部访问的集合,并且您只执行Hibernate.initialize,那么在访问成员时将出现一个很好的LazyInitializationException。