使用JPA和Hibernate时如何解决LazyInitializationException问题

53

我正在为一位客户开发项目,他们希望使用延迟初始化。但是默认的懒加载模式会导致"懒加载异常"。

@JoinTable(name = "join_profilo_funzionalita", joinColumns = {@JoinColumn(name =    "profilo_id", referencedColumnName = "profilo_id")}, inverseJoinColumns = {@JoinColumn(name = "funzionalita_id", referencedColumnName = "funzionalita_id")})
//@ManyToMany(fetch=FetchType.EAGER) - no exceptions if uncommented
@ManyToMany 
private Collection<Funzionalita> funzionalitaIdCollection;

有没有使用JPA类避免此错误的标准模式?

欢迎提供代码片段,非常感谢您的时间。

9个回答

61

Hibernate 4.1.6终于解决了这个问题:https://hibernate.atlassian.net/browse/HHH-7457

您需要设置 hibernate.enable_lazy_load_no_trans=true 属性。

以下是在Spring中的设置方法:

<bean id="entityManagerFactory"
      class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="dataSource" ref="myDataSource"/>
    <property name="packagesToScan" value="com.mycompany.somepackage"/>
    <property name="jpaVendorAdapter" ref="hibernateVendorAdapter"/>
    <property name="jpaDialect" ref="jpaDialect"/>
    <property name="jpaProperties">
        <props>
            <prop key="hibernate.enable_lazy_load_no_trans">true</prop>
        </props>
    </property>
</bean>

现在你可以放心地浏览和操作你的领域模型,而不用担心在Hibernate会话(在“JPA术语”中称为持久性上下文)之外出现延迟初始化异常了。


3
您能否在回答中补充一下这个属性的具体功能?实现可能会对数据一致性或性能产生严重影响,也可能两者都会受到影响。 - iwein
2
这个选项在事务外加载延迟关联,这意味着数据可能与父实体的不一致。但这并不比使用Spring的OpenEntityManagerInViewFilter更糟糕,后者也会在事务外加载内容。我正在使用基于字段的映射,并且没有遇到JIT混乱生成的代理的情况。 - andreak
8
请勿使用此功能。它已损坏,您将丢失数据。https://hibernate.atlassian.net/browse/HHH-7971 - Vojtěch
3
这个 bug 在 Hibernate 版本 4.3.5 中得到了修复。自那个版本开始,这个功能是安全可用的。 - Christopher Parker
1
请查看此文章:https://vladmihalcea.com/the-best-way-to-handle-the-lazyinitializationexception/ - olivmir
显示剩余3条评论

18

有许多方法可以预取属性,以便在会话关闭后它们仍然存在:

  1. 调用适当的getter。在字段被获取到bean中后,会话关闭后仍然存在。
  2. 您可以在EJBQL查询中初始化字段,请查找JOIN FETCH关键字。
  3. 如果您使用支持它的Hibernate版本,则可以启用AvailableSettings.ENABLE_LAZY_LOAD_NO_TRANS。

尝试这些解决方案时可能会出现几个问题:

  1. getter的调用可能会被JIT编译器优化掉(有时需要一段时间)。
  2. 您要尝试JOIN FETCH的实体可能通过涉及List的多个“many”关系链接。在这种情况下,查询结果返回模糊的结果,Hibernate将拒绝在单个查询中获取数据。
  3. 已经有一个与AvailableSettings.ENABLE_LAZY_LOAD_NO_TRANS相关的有趣的错误。并且还会有更多,因为正如Hibernate的人所说:注意:这可能发生在事务之外,不安全。谨慎使用。大多数情况下您自己处理。

最好的方法是首先尝试JOIN FETCH。如果不起作用,请尝试getter方法。如果在运行时被JIT编译器搞乱了,则将结果分配给public static volatile Object

或者停止使用Hibernate...


对于第三点需要注意的问题,与之相关的 bug 已在版本 4.1.7 中关闭。撰写本文时,仍存在一个相关的未解决问题,请参见:https://hibernate.atlassian.net/browse/HHH-8782。 - Steve Chambers
4
"或者停止使用Hibernate..." ...阿门。 - Night Owl
你只能在一个急加载的方法上使用JOIN FETCH,以达到你所想要的目的,而所有其他剩余的方法都继续保持懒加载。 - Juliano Suman Curti

16

1
它能够工作,但现在我在Hibernate映射中遇到了递归循环问题。 - Oleg Abrazhaev

8
LazyInitializationException是指在hibernate会话关闭或对象已从会话中分离后调用集合。您需要重新将对象附加到hibernate会话、更改调用集合的位置,或将关闭会话的边界移动到更高层。

8
明白了,我们已经知道了“什么”;现在问题在于“怎么做”! - Niks

7

解决LazyInitializationException的最佳方法是在实体查询中使用JOIN FETCH指令。

FetchType.EAGER加载会降低性能。此外,还有反模式,如:

你永远不应该使用它们,因为它们要么需要数据库连接为UI渲染而打开(Open Session in View),要么需要为每个在初始持久化上下文之外获取的延迟关联获取数据库连接(hibernate.enable_lazy_load_no_trans)。

有时候,你甚至不需要实体,使用DTO投影会更好。只有在需要修改实体时才应获取实体。对于只读事务,DTO投影更好

JOIN FETCH和EAGER加载有什么不同?难道两者都是急加载吗? - Eugen Labun
JOIN FETCH已经被明确设置。EAGER获取是隐式的,无法覆盖。 - Vlad Mihalcea
但两者本质上都是急切的(明确或隐含)。我理解你的意思是建议使用急切的获取/加载作为懒加载的解决方案? - Eugen Labun
但是只有一个可以默认为LAZY。如果您阅读了链接的文章,您将更好地理解我在本文中得出的结论。 - Vlad Mihalcea
我读了你关于 hibernate.enable_lazy_load_no_trans 的文章(还有其他很多,顺便说一句,我非常欣赏你的工作),但仍然不明白为什么它被认为是一种不好的做法。例如,您向用户显示订单列表。用户可以单击订单以查看详细信息。对于这种(非常典型的)情况,使用 hibernate.enable_lazy_load_no_trans 似乎是一种自然的方法。提前加载/获取所有订单的所有详细信息感觉非常错误... - Eugen Labun
1
急切加载在查询级别上是可以的,但不适用于映射级别或SessionFactory级别。就是这么简单。 - Vlad Mihalcea

5
OpenSessionInView是一种解决此问题的模式。这里有一些信息:http://www.hibernate.org/43.html
实施此模式时要小心并了解其含义。每次在视图中导航到一个延迟关联,它都会触发另一个SQL查询来加载数据。如果您的用例满足SQL查询的数量和大小很小,那么这可能无关紧要。请确保至少调整日志设置,以便您可以查看Hibernate在后台“神奇”执行哪种查询来加载数据。
还要考虑您正在编写的应用程序的类型。如果您没有处理远程访问(没有Web服务,没有基于AJAX的Web客户端),那么OSIV可能非常好用。但是,如果远程序列化器开始遍历整个对象图,则可能会触发大量的SQL查询并瘫痪您的DB和应用服务器。

如果您的服务器预计会承载大量负载,请勿在视图中使用开放式会话。此答案可以在这个方向上发出警告。 - iwein
1
我认为链接足以解释模式的含义,但为了安全起见,我添加了一些警告。 :) - cliff.meyers

4

当您使用集合并希望使用延迟加载进行初始化时,请在会话关闭之前使用该集合。如果在此之后关闭会话,但仍想要使用该集合,则会出现lazyinitializeException异常,因为默认情况下是尝试使用lazy。


1
Oracle Java教程指出:“企业Bean支持事务,这些机制管理共享对象的并发访问。”因此,为了处理Lazy Fetch问题,我创建了一个无状态Java会话Bean,然后获取所有需要的子类,然后从该方法返回。这避免了懒惰获取异常。Oracle还将其称为“Session Façade”核心J2EE模式。这种模式似乎比其他一些做法更好。

0

我正在开发一个项目,旨在使用ModelMapper将实体映射到DTO时解决常见的JPA问题。该问题已经在项目中得到解决。项目链接:JPA Model Mapper

“为了提高性能,将实体声明为延迟加载非常重要,这样我们就不需要每次需要一些数据时都获取所有相关实体。但是,这种技术会导致一些问题。最常见的问题是LazyInitializationException,有时可能非常烦人。大多数情况下,我们只想要未加载实体的空对象,而不是访问时抛出异常的对象…”

来源:JPA Model Mapper

因此,在项目中,我们通过为所有未加载实体设置null来处理LazyInitializationException。以下示例显示其工作原理。

重新映射实体并为所有未加载实体设置null:

TypedQuery<SystemEntity> query =
        em.createQuery("select s from SystemEntity s where s.id = 1",  SystemEntity.class);

SystemEntity system = query.getSingleResult();
return new JpaModelMapper(em).mapEntity(system, SystemEntity.class);

将实体重新映射到DTO,对于所有未加载的实体设置为null:

TypedQuery<SystemEntity> query =
        em.createQuery("select s from SystemEntity s where s.id = 1",  SystemEntity.class);

SystemEntity system = query.getSingleResult();
return new JpaModelMapper(em).mapEntity(system, SystemDTO.class);

更多信息请参见JPA模型映射器


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接