使用 hibernate.enable_lazy_load_no_trans 解决 Hibernate Lazy-Init 问题

Posted

技术标签:

【中文标题】使用 hibernate.enable_lazy_load_no_trans 解决 Hibernate Lazy-Init 问题【英文标题】:Solve Hibernate Lazy-Init issue with hibernate.enable_lazy_load_no_trans 【发布时间】:2014-10-11 08:07:26 【问题描述】:

我一直在遭受臭名昭著的休眠异常

org.hibernate.LazyInitializationException: could not initialize proxy - no Session

现在社区为之欢呼

<property name="hibernate.enable_lazy_load_no_trans" value="true"/>

说它解决了问题,但谨慎使用

谨慎使用是什么意思?这个属性实际上是做什么的?

请给我任何见解。提前致谢。

【问题讨论】:

@SteveChambers 404 - 死链接 【参考方案1】:

这种方法的问题是你可以产生 N+1 效应。

假设您有以下实体:

public class Person
    @OneToMany // default to lazy
    private List<Order> orderList;

如果您有一个返回 10K 人的报告,并且如果您在此报告中执行代码 person.getOrderList(),则 JPA/Hibernate 将执行 10K 查询。这是 N+1 效应,您将无法控制将要执行的所有查询。

现在想象一下 Order 如下所示:

public class Order
    @OneToMany // default to lazy
    private List<EmailSent> emailSentList;

现在想象一下,您对person.getOrderList() 进行了迭代,并且对于每个Order order,您将执行order.getEmailSentList()。你现在能看出问题了吗?

对于 LazyInitializationException 你可以有一些解决方案:

使用 OpenInSessionInView 方法。您将需要创建一个 WebFilter 来打开和关闭事务。问题在于 N+1 效应。 使用 hibernate.enable_lazy_load_no_trans 配置,这是一种休眠,如果需要,您将无法将您的项目移植到其他 JPA 提供程序。你也可以有 N+1 的效果。 使用名为 PersistenceContext Extended 的 EJB 功能。这样,您将保持多个事务的上下文打开。问题是:可能会发生 N+1 效应,使用大量服务器内存(实体将保持托管状态) 在查询中使用 FETCH。使用这种方法,您可以执行 JPQL/HQL,例如:select p from Person p join fetch p.orderList。使用此查询,您将从数据库中加载您的列表,并且不会产生 N+1 效果。问题是您需要为每个案例编写一个 JPQL。

如果仍有问题,请查看以下链接:

Part 1 Part 2

【讨论】:

N+1 不是关于几次会话,而是关于数据库的几次行程。 我不确定您是否理解我在上面所说的内容。在视图中打开会话,您将获得 N+1 效果,您将保持事务打开,并且对于每次获取未获取的实体,都会完成一次新的数据库之旅。我说 n+1 和 fetch 在 jpql 中,你的意思是什么? 是的,您现在发现了延迟初始化的主要方面。我建议深入探讨该主题;例如与 Eager-fetch 的区别、基数,尤其是您的“问题”无处不在,JOIN FETCH 将简单地消除 DBMS 驱动程序的开销以进行一些额外的“旅行”,但也会降低大型数据集的性能. JOIN 可能只适用于小型数据集。混合方法也可能产生可接受的结果。 我喜欢这样的对话,但双方都非常自信和矛盾。有人可以提供一个可靠的资源来说明懒惰和渴望之间的权衡吗?另外,让我们解决一个事实,即当有另一个我“需要设置”的属性时我什至可以选择@Lazy,否则它不起作用。 @uaiHebert ,所以你推荐在查询中使用 FETCH 作为最佳方法吗?【参考方案2】:

这与我们如何利用 Hibernate 通过 Session 概念执行可重复读取语义的方式背道而驰。 当第一次加载对象并且如果在会话的生命周期内再次引用该对象时,则返回相同的对象,而不管该对象在数据库中是否已更改。这是hibernate自动提供的可重复读语义。

使用此设置,您没有提供此保证的会话,因此如果您现在访问此数据,您将获得最新版本的数据。

这可能没问题。但是考虑一下这个对象在某个地方保存了很长时间并且数据发生了很大变化的场景,因此延迟获取的数据与会话活动时已经加载的数据有很大的不同。这是你需要关心的。

简单来说,如果您的程序是 不受以下因素影响:在 会话到将从会话中延迟获取的数据

但是,如果这(您的程序面临时间问题,它可能一次正常工作而另一次失败)是一个问题,那么在会话期间获取所有必要的数据。 p>

【讨论】:

【参考方案3】:

The best way to solve the LazyInitializationException 是在您的实体查询中使用 JOIN FETCH 指令。

EAGER loading 不利于性能。此外,还有一些反模式,例如:

Open Session in View hibernate.enable_lazy_load_no_trans

你永远不应该使用它,因为它们要么需要为 UI 渲染打开数据库连接(在视图中打开会话),要么对于在初始持久性上下文之外获取的每个惰性关联都需要一个数据库连接(@ 987654327@).

有时,您甚至不需要实体,a DTO projection is even better。

【讨论】:

请解释最后一句“甚至不需要实体,DTO 投影更好。”。什么是 DTO 投影?以及为什么它可以替换实体。 你所说的“反模式”是什么意思? 反模式意味着它伪装成一个解决方案,而实际上它是一个糟糕的问题解决方案。 我看了你的博客(你文章中的引用)也试过了,如果 PostComment 中的 Post 对象是空的,那么结果会是错误的:当 postComment 存在,但它没有 Post,它应该返回一个带有空/null Post 的 PostComment,但它实际上没有返回 PostComment。 如果使用spring boot怎么办...比如findById方法?【参考方案4】:

可能是因为有更好的解决方案,例如@Transactional,其中打开和关闭会话遵循非常常见的模式:“打开会话然后将所有内容包装在 try-catch-finally 中;catch 回滚并最终关闭会话。 "此注释通常位于 Web 应用程序和服务的请求级别。

或者,如果您需要更精细的控制,您可以使用 SessionFactory 手动打开会话。

正如其他人所提到的,延迟加载是您需要注意的事情。这不是灵丹妙药,但它会很有帮助。一般来说,如果你的应用被设计成有很多小请求,那就没问题了。

急切加载也可能非常糟糕。例如,当您的对象模型有很多多对多关系但您的请求使用的数据不超过一层时。

或者你可以暂时忘记整件事。使用延迟加载,直到它成为一个问题。如果是这样,无论如何,你会更好地使用 Mybatis。

【讨论】:

以上是关于使用 hibernate.enable_lazy_load_no_trans 解决 Hibernate Lazy-Init 问题的主要内容,如果未能解决你的问题,请参考以下文章

如何使用:从 __future__ 使用 django 导入部门

使用 MAX Date 显示最新日期

正确使用 __format__

python之 __getattr____getattr____getitem____setitem__ 使用

使用 AUTOLAYOUT 和 SIZECLASSES 对齐视图

使用多个文本区域居中多个标签[重复]