Java 使用JPA和Hibernate时如何解决LazyInitializationException

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/578433/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 16:27:35  来源:igfitidea点击:

How to solve the LazyInitializationException when using JPA and Hibernate

javahibernatejpaormlazy-initialization

提问by

I am working on a project for a customer who wants to use lazy initialization. They always get "lazy initialization exception" when mapping classes with the default lazy loading mode.

我正在为想要使用延迟初始化的客户开发一个项目。当使用默认的延迟加载模式映射类时,它们总是会得到“延迟初始化异常”。

@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;

Is there a standard pattern using JPA classes to avoid this error?

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

Snippets are welcome, thanks a lot for your time.

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

回答by cliff.meyers

OpenSessionInView is one pattern to deal with this problem. Some info here:

OpenSessionInView 是处理这个问题的一种模式。这里的一些信息:

http://www.hibernate.org/43.html

http://www.hibernate.org/43.html

You'll want to be cautious when implementing this pattern and understand the implications. Each time you navigate a lazy association in the view it will fire off another SQL query to load the data. If your use cases are such that the number and size of these SQL queries is small then this may not matter. Make sure that at a minimum you adjust your logging settings so you can see what kind of queries Hibernate is "magically" executing in the background for you to load the data.

在实施此模式时,您需要谨慎并了解其含义。每次在视图中导航惰性关联时,它都会触发另一个 SQL 查询以加载数据。如果您的用例是这些 SQL 查询的数量和大小都很小,那么这可能无关紧要。确保至少调整您的日志记录设置,以便您可以查看 Hibernate 在后台“神奇地”执行的查询类型,以便您加载数据。

Also consider the kind of application you are writing. If you're not dealing with remoting (no web services, no AJAX-based web client) then OSIV may work very nicely. However, if a remoting serializer starts to walk the entire object graph, it will likely trigger a ridiculous number of SQL queries and cripple your DB and app server.

还要考虑您正在编写的应用程序类型。如果您不处理远程处理(没有 Web 服务,没有基于 AJAX 的 Web 客户端),那么 OSIV 可能会很好地工作。然而,如果远程序列化程序开始遍历整个对象图,它可能会触发大量的 SQL 查询并削弱您的数据库和应用程序服务器。

回答by Daniel Alexiuc

LazyInitializationException means that you are calling the collection after the hibernate session has closed, or after the object has been detached from the session.

LazyInitializationException 意味着您在休眠会话关闭后或对象与会话分离后调用集合。

You need to either re-attach the object to hibernate session, change the place where you are calling the collection, or move the boundary of where the session gets closed to a higher layer.

您需要将对象重新附加到休眠会话,更改调用集合的位置,或者将会话关闭的边界移动到更高层。

回答by jb.

There are many ways to pre-fetch properties, so they are there after session is closed:

有多种预取属性的方法,因此它们在会话关闭后仍然存在:

  1. Call appropriate getter. After field is fetched into bean it is there after session is closed.
  2. You may initialize field in EJBQL query , look for JOIN FETCHkeyword.
  3. Enable AvailableSettings.ENABLE_LAZY_LOAD_NO_TRANS if you're on a Hibernate version that supports it.
  1. 调用适当的 getter。字段被提取到 bean 后,会话关闭后它就在那里。
  2. 您可以在 EJBQL 查询中初始化字段,查找JOIN FETCH关键字。
  3. 如果您使用的是支持它的 Hibernate 版本,请启用 AvailableSettings.ENABLE_LAZY_LOAD_NO_TRANS。

Several problems may occur when you try these solutions:

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

  1. The getters' invocation may be optimized away by the JIT compiler (sometimes this takes a while).
  2. The entities you are trying to JOIN FETCHmay be linked through multiple 'many' relationships involving List's. In this case the resulting query returns ambiguous results and Hibernate will refuse to fetch your data in a single query.
  3. There is already one interesting bug related to AvailableSettings.ENABLE_LAZY_LOAD_NO_TRANS. And there will be more because as the hibernate guys say: Note: this may happen outside of the transaction and is not safe. Use with caution.You're on your own mostly.
  1. getter 的调用可能会被 JIT 编译器优化掉(有时这需要一段时间)。
  2. 您尝试JOIN FETCH连接的实体可能通过涉及 List 的多个“许多”关系进行链接。在这种情况下,结果查询返回不明确的结果,Hibernate 将拒绝在单个查询中获取您的数据。
  3. 已经有一个与 AvailableSettings.ENABLE_LAZY_LOAD_NO_TRANS 相关的有趣错误。而且还会有更多,因为正如 hibernate 的人所说:注意:这可能发生在事务之外并且不安全。谨慎使用。你主要靠自己。

The best way to go is to try a JOIN FETCHfirst. If that doesn't work try the getter approach. If that gets messed up at runtime by the JIT compiler, assign the result to a public static volatile Object.

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

Or stop using Hibernate...

或者停止使用休眠...

回答by SANTOSH Kumar

When you are using collection and you want to initialize it with lazy loading then use that collection before session close. If session is close after that if you want to use then you get lazyinitializeException because lazy is try by default.

当您使用集合并希望使用延迟加载对其进行初始化时,请在会话关闭之前使用该集合。如果会话在此之后关闭,如果你想使用,那么你会得到lazyinitializeException,因为lazy 默认是try。

回答by andreak

Hibernate 4.1.6 finally solves this issue: https://hibernate.atlassian.net/browse/HHH-7457

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

You need to set the hibernate-property hibernate.enable_lazy_load_no_trans=true

您需要设置 hibernate-property hibernate.enable_lazy_load_no_trans=true

Here's how to do it in Spring:

以下是在 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>

Voila; Now you don't have to worry about LazyInitializationException while navigating your domain-model outside of a hibernate-session (persistence-context in "JPA-speak")

瞧;现在,在休眠会话之外导航域模型时,您不必担心 LazyInitializationException(“JPA-speak”中的持久上下文)

回答by Hani

Note that you shouldn't use hibernate.enable_lazy_load_no_transpre Hibernate 4.1.7, as it leaks connections. See https://hibernate.onjira.com/browse/HHH-7524

请注意,您不应使用hibernate.enable_lazy_load_no_transHibernate 4.1.7 之前的版本,因为它会泄漏连接。见https://hibernate.onjira.com/browse/HHH-7524

回答by K.Nicholas

The Oracle Java tutorials point out that "Enterprise beans support transactions, the mechanisms that manage the concurrent access of shared objects." So, in order to handle the Lazy Fetch issues I create a Stateless Java Session Bean and then get all of the sub classes I need before returning from the method. This avoids the lazy fetch exception. Oracle has also referred to this as a "Session Fa?ade" core J2EE pattern. This pattern seems better than some of the other practices mentioned.

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

回答by Vlad Mihalcea

The best way to solve the LazyInitializationExceptionis to use the JOIN FETCH directive in your entity queries.

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

FetchType.EAGER loadingis bad for performance. Also, there are anti-patterns such as:

FetchType.EAGER 加载对性能不利。此外,还有一些反模式,例如:

Which you should never use since they either require the database connection to be open for the UI rendering (Open Session in View), or a database connection is needed for every lazy association that is fetched outside of the initial Persistence Context (hibernate.enable_lazy_load_no_trans).

您永远不应该使用它,因为它们要么需要为 UI 呈现打开数据库连接(在视图中打开会话),要么需要为在初始持久性上下文 ( hibernate.enable_lazy_load_no_trans)之外获取的每个延迟关联都需要一个数据库连接。

Sometimes, you don't even need entities, and a DTO projection is even better. You should fetch entities only when you need to modify them. For read-only transactions, DTO projections are better.

有时,您甚至不需要实体,而 DTO 投影甚至更好。仅在需要修改实体时才应获取实体。对于只读事务,DTO 预测更好

回答by Vinícius M. Freitas

I'm working on a project that aims to solve common JPA problems when mapping entities to DTOs using ModelMapper. This issue has already been solved on the project. Project link: JPA Model Mapper

我正在从事一个项目,该项目旨在解决使用 ModelMapper 将实体映射到 DTO 时常见的 JPA 问题。这个问题已经在项目中解决了。项目链接:JPA 模型映射器

"Its crucial for performance to declare entities as lazy load so we don't need to fetch all related entities every time we need some data. But this technique leads to some issues. The most common one is the LazyInitializationException that can be pretty annoying sometimes. Most of the time we would just want a null object for a not loaded entity instead of an object that throws an exception if accessed..."

“将实体声明为延迟加载对性能至关重要,因此我们不需要每次需要一些数据时都获取所有相关实体。但这种技术会导致一些问题。最常见的一个是 LazyInitializationException,有时会很烦人. 大多数时候,我们只想要一个未加载实体的空对象,而不是一个在访问时抛出异常的对象......”

Source: JPA Model Mapper

资料来源:JPA 模型映射器

Therefore, in the project we deal with LazyInitializationException by setting null for all not loaded entities. The examples below show how it works.

因此,在项目中我们通过为所有未加载的实体设置 null 来处理 LazyInitializationException。下面的例子展示了它是如何工作的。

Remapping an entity setting null for all not loaded entities:

为所有未加载的实体重新映射实体设置 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);

Remapping an entity to a DTO setting null for all not loaded entities:

对于所有未加载的实体,将实体重新映射到 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);

For more informations please see JPA Model Mapper

有关更多信息,请参阅JPA 模型映射器