这种方法的问题是你可以产生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 你可以有一些解决方案:
select p from Person p join fetch p.orderList
。通过此查询,您将从数据库加载列表,并且不会产生 N+1 效果。问题是您需要为每种情况编写 JPQL。如果仍有任何问题,请检查以下链接:
这违背了我们利用 Hibernate 通过 Session 概念强制执行可重复读取语义的方式。 当首次加载对象时,如果在会话生命周期内再次引用该对象,则返回相同的对象,无论该对象在数据库中是否已更改。这就是hibernate自动提供的可重复读语义。
使用此设置,您将没有会话提供此保证,因此如果您现在访问此数据,您将获得最新版本的数据。
这可能没问题。但考虑一下这样的场景:该对象长时间保存在某个地方,并且数据发生了很大变化,因此延迟获取的数据与会话处于活动状态时已加载的数据有很大不同。这是你需要关心的。
简单来说,如果您的程序是 不受以下因素影响: 进入时已获取的数据有多陈旧 会话到将从会话中延迟获取的数据
但是,如果这是一个问题(你的程序面临计时问题,一次可能运行良好,另一次则失败)是一个问题,那么在会话中获取所有必要的数据。
解决 LazyInitializationException 的最佳方法是在实体查询中使用 JOIN FETCH 指令。
急切加载对性能不利。此外,还有一些反模式,例如:
您永远不应该使用它,因为它们要么需要打开数据库连接以进行 UI 渲染(在视图中打开会话),要么在初始持久性上下文之外获取的每个惰性关联都需要数据库连接(
hibernate.enable_lazy_load_no_trans
).
有时,您甚至不需要实体,DTO 投影甚至更好。
可能是因为有更好的解决方案,例如@Transactional,其中打开和关闭会话遵循一种非常常见的模式:“打开会话然后将所有内容包装在 try-catch-finally 中;catch 回滚并最终关闭会话。”此注释通常位于 Web 应用程序和服务的请求级别。
或者,如果您需要更精细的控制,您可以使用 SessionFactory 手动打开会话。
正如其他人提到的,延迟加载是您需要注意的事情。这不是灵丹妙药,但非常有帮助。一般来说,如果您的应用程序设计为有许多小请求,那么就可以了。
预加载也可能非常糟糕。例如,当您的对象模型具有大量多对多关系,但您的请求不使用超过一层深度的数据时。
或者你可以暂时忘记整件事。使用延迟加载,直到它成为问题为止。如果确实如此,那么无论如何,使用 Mybatis 会更好。