【问题标题】:How to fix org.hibernate.LazyInitializationException - could not initialize proxy - no Session如何修复 org.hibernate.LazyInitializationException - 无法初始化代理 - 没有会话
【发布时间】:2014-03-01 16:56:51
【问题描述】:

我得到以下异常:

Exception in thread "main" org.hibernate.LazyInitializationException: could not initialize proxy - no Session
    at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:167)
    at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:215)
    at org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer.invoke(JavassistLazyInitializer.java:190)
    at sei.persistence.wf.entities.Element_$$_jvstc68_47.getNote(Element_$$_jvstc68_47.java)
    at JSON_to_XML.createBpmnRepresantation(JSON_to_XML.java:139)
    at JSON_to_XML.main(JSON_to_XML.java:84)

当我尝试从 main 调用以下行时:

Model subProcessModel = getModelByModelGroup(1112);
System.out.println(subProcessModel.getElement().getNote());

我首先像这样实现了getModelByModelGroup(int modelgroupid) 方法:

public static Model getModelByModelGroup(int modelGroupId, boolean openTransaction) {

    Session session = SessionFactoryHelper.getSessionFactory().getCurrentSession();     
    Transaction tx = null;

    if (openTransaction) {
        tx = session.getTransaction();
    }

    String responseMessage = "";

    try {
        if (openTransaction) {
            tx.begin();
        }
        Query query = session.createQuery("from Model where modelGroup.id = :modelGroupId");
        query.setParameter("modelGroupId", modelGroupId);

        List<Model> modelList = (List<Model>)query.list(); 
        Model model = null;

        for (Model m : modelList) {
            if (m.getModelType().getId() == 3) {
                model = m;
                break;
            }
        }

        if (model == null) {
            Object[] arrModels = modelList.toArray();
            if (arrModels.length == 0) {
                throw new Exception("Non esiste ");
            }

            model = (Model)arrModels[0];
        }

        if (openTransaction) {
            tx.commit();
        }

        return model;

   } catch(Exception ex) {
       if (openTransaction) {
           tx.rollback();
       }
       ex.printStackTrace();
       if (responseMessage.compareTo("") == 0) {
           responseMessage = "Error" + ex.getMessage();
       }
       return null;
    }
}

并得到了异常。然后一位朋友建议我总是测试会话并获取当前会话以避免此错误。所以我这样做了:

public static Model getModelByModelGroup(int modelGroupId) {
    Session session = null;
    boolean openSession = session == null;
    Transaction tx = null;
    if (openSession) {
        session = SessionFactoryHelper.getSessionFactory().getCurrentSession(); 
        tx = session.getTransaction();
    }
    String responseMessage = "";

    try {
        if (openSession) {
            tx.begin();
        }
        Query query = session.createQuery("from Model where modelGroup.id = :modelGroupId");
        query.setParameter("modelGroupId", modelGroupId);

        List<Model> modelList = (List<Model>)query.list(); 
        Model model = null;

        for (Model m : modelList) {
            if (m.getModelType().getId() == 3) {
                model = m;
                break;
            }
        }

        if (model == null) {
            Object[] arrModels = modelList.toArray();
            if (arrModels.length == 0) {
                throw new RuntimeException("Non esiste");
            }

            model = (Model)arrModels[0];

            if (openSession) {
                tx.commit();
            }
            return model;
        } catch(RuntimeException ex) {
            if (openSession) {
                tx.rollback();
            }
            ex.printStackTrace();
            if (responseMessage.compareTo("") == 0) {
                responseMessage = "Error" + ex.getMessage();
            }
            return null;        
        }
    }
}

但仍然出现同样的错误。 我已经为这个错误阅读了很多内容,并找到了一些可能的解决方案。其中之一是将lazyLoad设置为false,但我不允许这样做,这就是我被建议控制会话的原因

【问题讨论】:

    标签: java hibernate session orm lazy-loading


    【解决方案1】:

    使用 @NamedEntityGraph。 Eagar fetch 会降低性能。详细解释请参考https://thorben-janssen.com/lazyinitializationexception/

    【讨论】:

      【解决方案2】:

      在 Spring 应用中只需添加

      @Transactional(readOnly = true)
      

      关于你的功能。

      提醒导入spring Transactional注解

      导入 org.springframework.transaction.annotation.Transactional;

      【讨论】:

        【解决方案3】:

        处理LazyInitializationException 的最佳方法是使用JOIN FETCH 指令:

        Query query = session.createQuery("""
            select m
            from Model m
            join fetch m.modelType
            where modelGroup.id = :modelGroupId
            """
        );
        

        无论如何,请不要按照某些答案的建议使用以下反模式:

        有时,DTO 投影是比获取实体更好的选择,这样您就不会得到任何LazyInitializationException

        【讨论】:

        • 我如何确定哪个呼叫有问题?我发现很难识别电话。有什么办法吗?出于测试目的,我使用了FetchType=EAGER,但这不是正确的解决方案,对吧?
        • 只使用日志记录。 EAGER 很糟糕,是的。
        • 然后,在离开@Transactional 服务之前,您应该使用 DTO 或初始化所有关联。
        • 我们应该提倡最好的企业实践,而不是速战速决。
        • 这是最好的解决办法
        【解决方案4】:

        这发生在我已经使用@Transactional(value=...) 并且正在使用多个事务管理器时。

        我的表单正在发回已经包含 @JsonIgnore 的数据,因此从表单发回的数据不完整。

        最初我使用反模式解决方案,但发现它非常慢。我通过将其设置为 false 来禁用它。

        spring.jpa.properties.hibernate.enable_lazy_load_no_trans=false
        

        修复是确保首先从数据库中检索具有延迟加载数据但未加载的任何对象。

        Optional<Object> objectDBOpt = objectRepository.findById(object.getId());
        
        if (objectDBOpt.isEmpty()) {
            // Throw error
        } else {
            Object objectFromDB = objectDBOpt.get();
        }
        

        简而言之,如果您已经尝试了所有其他答案,只要确保您回头检查您是否首先从数据库加载,如果您没有提供所有 @JsonIgnore 属性并且正在使用它们您的数据库查询。

        【讨论】:

          【解决方案5】:

          这意味着您在代码中使用 JPA 或休眠并在数据库上执行修改操作而不进行业务逻辑事务。 如此简单的解决方案就是标记你的代码@Transactional

          【讨论】:

            【解决方案6】:

            如果您使用 Spring 将类标记为 @Transactional,那么 Spring 将处理会话管理。

            @Transactional
            public class MyClass {
                ...
            }
            

            通过使用@Transactional,可以自动处理事务传播等许多重要方面。在这种情况下,如果调用另一个事务方法,该方法将可以选择加入正在进行的事务,从而避免“无会话”异常。

            警告如果您使用@Transactional,请注意由此产生的行为。有关常见的陷阱,请参阅this article。例如,即使您没有明确调用 save

            ,实体的更新也会保持

            【讨论】:

            • 我不能夸大这个答案的重要性。我强烈建议您先尝试此选项。
            • 另外请注意,您必须将@EnableTransactionManagement 添加到您的配置中才能启用事务。 “如果调用了另一个事务方法,该方法将可以选择加入正在进行的事务”这种行为对于实现事务的不同方式是不同的,即接口代理与类代理或 AspectJ 编织。参考documentation
            • 我们是否应该理解 Spring Transactional 注释因此被建议,不仅用于修改事务,还用于访问?
            • 我强烈建议将此注释仅用于测试。真正的代码应该将每个方法分别标记为类中的事务。除非类中的所有方法都需要打开与数据库的事务连接。
            • 将@Transactional(readOnly = true) 而不是@Transactional 放置不安全吗?
            【解决方案7】:

            在不同的用例中遇到相同的异常。

            用例:尝试使用DTO投影从数据库读取数据。

            解决方案:使用get方法代替load

            通用操作

            public class HibernateTemplate {
            public static Object loadObject(Class<?> cls, Serializable s) {
                Object o = null;
                Transaction tx = null;
                try {
                    Session session = HibernateUtil.getSessionFactory().openSession();
                    tx = session.beginTransaction();
                    o = session.load(cls, s); /*change load to get*/
                    tx.commit();
                    session.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
                return o;
            }
            

            }

            持久性类

            public class Customer {
            
            @Id
            @GeneratedValue(strategy = GenerationType.IDENTITY)
            @Column(name = "Id")
            private int customerId;
            
            @Column(name = "Name")
            private String customerName;
            
            @Column(name = "City")
            private String city;
            
            //constructors , setters and getters
            
            }
            

            CustomerDAO 界面

            public interface CustomerDAO 
                 {
               public CustomerTO getCustomerById(int cid);
                 }
            

            实体传输对象类

            public class CustomerTO {
            
            private int customerId;
            
            private String customerName;
            
            private String city;
            
            //constructors , setters and getters
            

            }

            工厂类

            public class DAOFactory {
            
            static CustomerDAO customerDAO;
            static {
                customerDAO = new HibernateCustomerDAO();
            }
            
            public static CustomerDAO getCustomerDAO() {
                return customerDAO;
            }
            

            }

            实体特定的 DAO

            public class HibernateCustomerDAO implements CustomerDAO {
            
            @Override
            public CustomerTO getCustomerById(int cid) {
                Customer cust = (Customer) HibernateTemplate.loadObject(Customer.class, cid);
                CustomerTO cto = new CustomerTO(cust.getCustomerId(), cust.getCustomerName(), cust.getCity());
                return cto;
            }
            

            }

            检索数据:测试类

            CustomerDAO cdao = DAOFactory.getCustomerDAO();
            CustomerTO c1 = cdao.getCustomerById(2);
            System.out.println("CustomerName -> " + c1.getCustomerName() + " ,CustomerCity -> " + c1.getCity());
            

            当前数据

            Hibernate System 生成的查询和输出

            休眠:从 CustomerLab31 customer0_ 中选择 customer0_.Id 作为 Id1_0_0_,customer0_.City 作为 City2_0_0_,customer0_.Name 作为 Name3_0_0_ where customer0_.Id=?

            客户名称 -> 科迪,客户城市 -> 洛杉矶

            【讨论】:

              【解决方案8】:

              这里的问题是您的会话管理配置设置为在您提交事务时关闭会话。检查你是否有类似的东西:

              <property name="current_session_context_class">thread</property>
              

              在您的配置中。

              为了克服这个问题,您可以更改会话工厂的配置或打开另一个会话,而只要求那些延迟加载的对象。但我在这里建议的是在 getModelByModelGroup 本身中初始化这个惰性集合并调用:

              Hibernate.initialize(subProcessModel.getElement());
              

              当您仍处于活动会话中时。

              最后一件事。一个友好的建议。你的方法中有这样的东西:

              for (Model m : modelList) {
                  if (m.getModelType().getId() == 3) {
                      model = m;
                      break;
                  }
              }
              

              请插入此代码,仅在上面几行的查询语句中过滤类型 id 等于 3 的模型。

              更多阅读:

              session factory configuration

              problem with closed session

              【讨论】:

              • 谢谢!我使用 openSession() 而不是 getCurrentSession() 作为您给我建议的链接之一解决了我的问题,但现在我害怕这样做是错误的
              • 不,可能没问题。但请阅读更多内容,以便能够完全控制您的会话和事务。了解基础知识非常重要,因为所有更高级别的技术(如 Spring、Hibernate 等)都基于相同的概念。
              【解决方案9】:

              在我的情况下,错误的session.clear() 导致了这个问题。

              【讨论】:

                【解决方案10】:

                如果你使用 spring data jpa , spring boot 你可以在 application.properties 中添加这一行

                spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true
                

                【讨论】:

                • 这被认为是一种反模式。 vladmihalcea.com/…
                • 如果它是一个反模式,你能建议一个替代方案吗?我也在使用 Spring Data,而我看过的其他答案对我没有帮助。
                • 感谢这对我有用。
                【解决方案11】:

                这意味着您尝试访问的对象未加载,因此请编写一个查询,对您尝试访问的对象进行 join fetch

                例如:

                如果您尝试从 ObjectA 获取 ObjectB,其中 ObjectB 是 ObjectA 中的外键。

                查询:

                SELECT objA FROM ObjectA obj JOIN FETCH obj.objectB objB
                

                【讨论】:

                  【解决方案12】:

                  对于以下注释的一对多关系,我遇到了同样的错误。

                  @OneToMany(mappedBy="department", cascade = CascadeType.ALL)
                  

                  添加 fetch=FetchType.EAGER 后更改如下,它对我有用。

                  @OneToMany(mappedBy="department", cascade = CascadeType.ALL, fetch=FetchType.EAGER)
                  

                  【讨论】:

                  • 是的,它可能会修复它,但现在您正在加载整个数据树。在大多数情况下,这会对性能产生负面影响
                  • 解决了我的问题,非常感谢
                  【解决方案13】:

                  如果您使用的是 JPQL,使用 JOIN FETCH 是最简单的方法: http://www.objectdb.com/java/jpa/query/jpql/from#LEFT_OUTER_INNER_JOIN_FETCH_

                  【讨论】:

                    【解决方案14】:

                    如果您使用的是Grail's 框架,则可以通过在域类中的特定字段上使用Lazy 关键字来轻松解决延迟初始化异常

                    例如:

                    class Book {
                        static belongsTo = [author: Author]
                        static mapping = {
                            author lazy: false
                        }
                    }
                    

                    查找更多信息here

                    【讨论】:

                      【解决方案15】:

                      这个异常是因为当你调用session.getEntityById()时,会话将被关闭。因此,您需要将实体重新附加到会话。或者简单的解决方案是将default-lazy="false" 配置为您的entity.hbm.xml,或者如果您使用注释,只需将@Proxy(lazy=false) 添加到您的实体类。

                      【讨论】:

                      • 如何将实体重新附加到会话?
                      【解决方案16】:

                      servlet-context.xml

                      中进行以下更改
                          <beans:property name="hibernateProperties">
                              <beans:props>
                      
                                  <beans:prop key="hibernate.enable_lazy_load_no_trans">true</beans:prop>
                      
                              </beans:props>
                          </beans:property>
                      

                      【讨论】:

                        【解决方案17】:

                        这里有几个很好的答案可以在广泛的范围内处理此错误。我遇到了 Spring Security 的一个特定情况,它有一个快速但可能不是最佳的修复。

                        在用户授权期间(在登录并通过身份验证后立即),我在扩展 SimpleUrlAuthenticationSuccessHandler 的自定义类中测试用户实体的特定权限。

                        我的用户实体实现了 UserDetails 并具有一组延迟加载的角色,这些角色引发了“org.hibernate.LazyInitializationException - 无法初始化代理 - 无会话”异常。将该 Set 从“fetch=FetchType.LAZY”更改为“fetch=FetchType.EAGER”为我解决了这个问题。

                        【讨论】:

                          【解决方案18】:

                          你可以尝试设置

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

                          在 hibernate.cfg.xml 或 persistence.xml 中

                          这个属性要记住的问题已经很好地解释了here

                          【讨论】:

                          • 你能解释一下它的意思吗?
                          • 我也很好奇这是做什么的。它确实解决了我遇到的问题,但我想了解原因。
                          • 经过数千次“只需在配置文件中添加lazy=false”和“只需使用 Hibernate.initialize() 初始化每个对象”,最终对我来说是一个具体可行的解决方案。这个选项应该成为hibernate的标准!
                          • 对于persistence.xml:&lt;property name="hibernate.enable_lazy_load_no_trans" value="true"/&gt;
                          • 请勿使用此属性,如果 Spring 管理您的交易,此属性将导致交易爆炸,SPRING 将关闭应用程序
                          【解决方案19】:

                          使用 session.get(*.class, id);但不加载函数

                          【讨论】:

                          • 你能解释一下吗?
                          【解决方案20】:

                          我遇到了同样的问题。我认为解决此问题的另一种方法是您可以更改查询以加入从模型中获取您的元素,如下所示:

                          Query query = session.createQuery("from Model m join fetch m.element where modelGroup.id = :modelGroupId")
                          

                          【讨论】:

                            【解决方案21】:

                            您也可以通过将lazy=false 添加到您的*.hbm.xml 文件中来解决它,或者您可以在从db 获取对象时在Hibernate.init(Object) 中初始化您的对象

                            【讨论】:

                            • 通常添加lazy=false 不是一个好主意。这就是为什么 lazy 默认为 true 的原因
                            • OP事先明确表示不允许他这样做。
                            猜你喜欢
                            • 2016-07-29
                            • 2011-12-01
                            • 2014-04-21
                            • 2019-05-10
                            • 2015-11-09
                            • 2021-08-10
                            • 1970-01-01
                            • 2014-01-14
                            • 2013-01-10
                            相关资源
                            最近更新 更多