Hibernate.initialize()的工作原理

ibps3vxo  于 2022-11-30  发布在  其他
关注(0)|答案(7)|浏览(102)

我知道要在会话外使用延迟加载对象/集合,我们执行Hibernate.initialize(Object obj),以便初始化作为参数传递给initialize()方法的对象,并可以在会话范围外使用。
但我不明白这是怎么回事,我的意思是,如果我们这样做,那么我们最终会有渴望的获取,所以为什么我们在配置中做了懒惰,最终在运行时的渴望的获取。
换句话说,我想知道使用Hibernate.initialize()eagerly加载该对象之间的区别。
我是不是搞错了或者错过了什么?

1szpjjfi

1szpjjfi1#

区别在于适用范围。
将集合关联设置为惰性的原因是为了避免在每次加载父对象时都让它加载集合(如果您实际上并不需要它)。
如果您通常是惰性加载集合,但对于特定用途,您需要确保在会话关闭之前已经加载了集合,则可以使用前面提到的Hibernate.initialize(Object obj)
如果你实际上总是需要加载这个集合,你应该急切地加载它,但在大多数软件中,情况并非如此。

w8f9ii69

w8f9ii692#

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()
);

首先,我们将清除二级缓存,因为除非显式启用二级缓存并配置提供程序,否则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_id列检索到的Post实体标识符。
现在,由于对Hibernate.initialize方法的调用,执行了一个辅助SQL查询来获取Post实体,这不是很有效,可能会导致N+1查询问题。

搭配JPQL使用JOIN FETCH

在前一种情况下,应该使用JOIN FETCH JPQL指令提取PostComment及其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配合使用

因此,要查看何时真正值得使用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`
ffscu2ro

ffscu2ro3#

请考虑以下示例:
我有一个实体LoanApplication(在本例中是一个非常重的对象),其中包含各种字段(也可能很大)。

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

在这个例子中,FetchType是LAZY。现在,如果在一些控制器的方法中执行一些操作时,你遇到LoanApplication,那么subLoans集最初将是null,除非你想使用它。在这种情况下,你使用Hibernate。初始化如下:

Hibernate.initialize(loanApplication.getSubLoans());

这主要有助于提高性能,因为每次检索LoanApplication时,大量对象(即'subLoan')最初都是空的,除非您确实需要它们。

lpwwtiir

lpwwtiir4#

考虑@Don Ruby的回答
下一个区别是Hibernate.initialize生成并执行额外的sql来获取数据。所以你可以在会话关闭后使用它。当你在实体中使用Eager fetch时,它总是在数据库中查找数据(在连接会话下)的过程中获取集合,而不是在它之后。

iklwldmw

iklwldmw5#

实际上,如果你使用EAGER,当你有大的集合时,它确实会影响你的性能。所以,在这种情况下使用Hibernate.initialize是个好主意。
看看这个:Hibernate Lazy Fetch vs Eager Fetch Type

unftdfkk

unftdfkk6#

假设您有一个表,它可能与其他4个表有关系。在这种情况下,如果您使用eager then,则在每次获取操作中,将获取所有4个相关表的所有对应关系。
但是,考虑到您可能只需要相关表中的一个表的数据,因此在这种情况下,您可以只获取所需的关系,而不是使用Hibernate.initialize工具装载整个四个相关表的数据。

t5fffqht

t5fffqht7#

下面是我所做的:
@自动连线会话工厂(org. hib.会话工厂)

Session currentSession = sessionFactory.getCurrentSession();
Session newSession;
if(null == currentSession) {
    newSession = sessionFactory.openSession();
    user= newSession.get(User.class, userId);
    Hibernate.initialize(user.getAddress());
    newSession.close();
} else {
    user = currentSession.get(User.class, userId);
    Hibernate.initialize(user.getAddress());
}

假设会话已经打开,则强制初始化(也称为强制加入)集合/代理(1-m/1-1)。如果会话不存在,则打开一个新会话,执行操作并再次关闭它。
如果你发现这里面有什么瑕疵就告诉我

相关问题