【问题标题】:How to solve the LazyInitializationException when using JPA and Hibernate使用 JPA 和 Hibernate 时如何解决 LazyInitializationException
【发布时间】:2010-10-09 08:53:36
【问题描述】:

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

@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 类的标准模式来避免此错误?

欢迎提供片段,非常感谢您抽出宝贵时间。

【问题讨论】:

    标签: java hibernate jpa orm lazy-initialization


    【解决方案1】:

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

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

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

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

    【讨论】:

    • JOIN FETCH 与 EAGER 加载有何不同?不是都渴望吗?
    • JOIN FETCH 已明确设置。 EAGER 获取是隐式的,不可覆盖。
    • 但两者本质上都是热切的(显式或隐式)。我是否正确理解您打算使用急切获取/加载作为延迟加载的解决方案?
    • 但是默认只有一个可以是 LAZY。如果您阅读链接的文章,您将更好地理解我在本文中作为结论所写的内容。
    • 我阅读了您关于 hibernate.enable_lazy_load_no_trans 的文章(以及许多其他文章;顺便说一句。我非常感谢您的工作),但仍然无法理解为什么它被认为是一种不好的做法。例如,您向用户显示订单列表。用户可以单击订单以查看详细信息。在我看来,在这种(非常典型的)情况下使用hibernate.enable_lazy_load_no_trans 是一种自然的方法。提前加载/获取所有订单的所有详细信息感觉非常错误......
    【解决方案2】:

    我正在开展一个项目,该项目旨在解决使用 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 Model Mapper

    【讨论】:

      【解决方案3】:

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

      你需要设置hibernate-property 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>
      

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

      【讨论】:

      • 您能否在答案中添加此属性的确切作用?该实施可能对数据一致性或性能产生严重影响。可能两者兼而有之。
      • 此选项在事务外加载惰性关联,这意味着数据可能与父实体的数据不一致。但这并不比使用 Spring 的 OpenEntityManagerInViewFilter 更糟糕,后者还会在事务之外加载内容。我正在使用基于字段的映射,并且没有遇到过 JIT 搞乱生成的代理的情况。
      • 请勿使用此功能。它已损坏,您将丢失数据。 hibernate.atlassian.net/browse/HHH-7971
      • 该错误已在 Hibernate 版本 4.3.5 中修复。从该 Hibernate 版本开始使用此功能是安全的。
      • 在撰写本文时,这里还有一个问题:hibernate.atlassian.net/browse/HHH-8782
      【解决方案4】:

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

      【讨论】:

        【解决方案5】:

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

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

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

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

        【讨论】:

        • 如果您期望服务器上的负载,请不要在视图中使用开放会话。这个答案可能会在那个方向使用警告。
        • 我认为该链接足以解释该模式的含义,但我添加了一些警告以防万一。 :)
        【解决方案6】:

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

        【讨论】:

        • 它有效,但现在我遇到了休眠映射的递归循环问题
        【解决方案7】:

        预取属性的方法有很多种,所以在会话关闭后它们就在那里:

        1. 调用适当的吸气剂。将字段提取到 bean 后,会话关闭后它就在那里。
        2. 您可以在EJBQL查询中初始化字段,寻找JOIN FETCH关键字。
        3. 如果您使用的是支持它的 Hibernate 版本,请启用 AvailableSettings.ENABLE_LAZY_LOAD_NO_TRANS。

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

        1. getter 的调用可能会被 JIT 编译器优化掉(有时这需要一段时间)。
        2. 您尝试JOIN FETCH 的实体可能通过涉及列表的多个“多”关系链接。在这种情况下,生成的查询会返回不明确的结果,Hibernate 将拒绝在单个查询中获取您的数据。
        3. 已经有一个interesting bug related to AvailableSettings.ENABLE_LAZY_LOAD_NO_TRANS。而且还会有更多,因为正如冬眠者所说:注意:这可能发生在事务之外并且不安全。谨慎使用。您主要是靠自己。

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

        或者停止使用 Hibernate...

        【讨论】:

        • 对于第 (3) 点值得注意的问题是链接错误已于 4.1.7 版关闭。在撰写本文时,这里还有一个相关的未解决问题:hibernate.atlassian.net/browse/HHH-8782
        • "或者停止使用 Hibernate..." ...阿门。
        【解决方案8】:

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

        【讨论】:

          【解决方案9】:

          LazyInitializationException 表示您在休眠会话关闭后或对象从会话中分离后调用集合。

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

          【讨论】:

          • 没错,我们得到了 WHAT 部分;问题如何!
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-12-17
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-09-28
          相关资源
          最近更新 更多