如何修复org.hibernate.LazyInitializationException - could not initialize proxy - no Session错误

283

我遇到了以下异常:

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)

当我尝试从主函数调用以下代码时:

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,但我不允许这样做,所以建议我控制session。

25个回答

290
如果你使用Spring,请将类标记为@Transactional,然后Spring将处理会话管理。
@Transactional
public class MyClass {
    ...
}
通过使用@Transactional,很多重要的方面如事务传播将会被自动处理。在这种情况下,如果另一个带有事务的方法被调用,则该方法将具有加入正在进行的事务的选项,从而避免了"无会话"异常。 警告:如果您使用@Transactional,请注意其产生的行为。参见此文章中的常见陷阱。例如,对实体的更新即使您没有显式调用save也会被持久化。

39
我强调这个答案的重要性。我强烈建议首先尝试这个选项。 - sparkyspider
9
请注意,在配置中添加@EnableTransactionManagement以启用事务。当调用另一个带有事务注解的方法时,该方法将有加入进行中的事务的选项。不同的事务实现方式(例如接口代理、类代理或AspectJ编织)对此行为有所不同。请参阅文档 - Erik Hofer
2
我们应该了解Spring的Transactional注释,因此不仅建议用于修改事务,而且还建议用于访问现有的事务吗? - Stephane
10
我建议只在测试时将此注释放在类的顶部。真实代码应该将类中的每个方法单独标记为事务性,除非类中的所有方法都需要打开连接并使用数据库事务。 - m1ld
4
只使用 @Transactional(readOnly = true) 是否比仅使用 @Transactional 更安全? - Hamedz
显示剩余4条评论

111
警告:此解决方案对于在事务之外获取的每个延迟加载对象都会创建一个新的事务。请自行承担风险。
你可以尝试在 hibernate.cfg.xmlpersistence.xml 中设置以下内容。
<property name="hibernate.enable_lazy_load_no_trans">true</property>

这个属性需要记住的问题在这里很好地解释了。

10
可以,请提供需要翻译的内容。 - Mohit Kanwar
2
我也很好奇这是做什么的。它确实解决了我遇到的问题,但我想了解为什么。 - user377628
经过成千上万次的“只需在配置文件中将lazy=false设置”和“只需使用Hibernate.initialize()初始化每个对象”,我终于找到了一个具体可行的解决方案。这个选项应该成为Hibernate的标准! - saimiris_devel
3
用于 persistence.xml 的翻译: <property name="hibernate.enable_lazy_load_no_trans" value="true"/>在持久化配置文件 persistence.xml 中使用:<property name="hibernate.enable_lazy_load_no_trans" value="true"/> - ACV
26
如果Spring管理您的事务,请勿使用此属性,否则会导致事务爆炸,简单地说,Spring将关闭应用程序。 - Youans
显示剩余2条评论

108
这里存在的问题是您的会话管理配置设置为在提交事务时关闭会话。请检查是否有类似以下内容的配置:
什么导致此错误是您的会话管理配置设置为在提交事务时关闭会话。请检查是否有以下内容:
<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 configurationproblem with closed session。保留HTML标签。

1
谢谢!我使用openSession()解决了我的问题,而不是getCurrentSession(),因为你给我的链接中有一个建议这样做,但现在我担心这样做是否正确。 - Blerta Dhimitri
2
不,这可能没问题。但是阅读更多内容以便完全掌握会话和事务的控制是非常重要的。了解基础知识非常重要,因为所有高级技术(如Spring、Hibernate等)都是基于相同的概念运作的。 - goroncy

98

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

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

无论如何,不要使用一些答案中建议的以下反模式:

有时,DTO投影比获取实体更好的选择,这样就不会出现LazyInitializationException


2
我们应该倡导最佳企业实践,而不是临时抱佛脚。 - etlds
2
这是最好的修复。 - phamductri
嗨@VladMihalcea,JOIN FETCH等同于EAGER吗? - Bằng
使用JOIN FETCH查询时,孙级实体是否也包含在内?还是我们需要在查询中添加更多的JOIN? - Bằng
混淆FetchType.EAGERJOIN FETCH,以至于你得出使用FetchType.EAGER会更好的结论是可怕的。一旦你使用了FetchType.EAGER,你就永远无法懒加载该关联。另一方面,如果你将其设置为FetchType.LAZY,当你不需要它时,你有灵活性不去获取它,并在需要时使用JOIN FETCH获取它。 - Vlad Mihalcea
显示剩余7条评论

28

如果您使用Spring Data JPA和Spring Boot,可以在application.properties文件中添加以下行:

spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true

21
这被认为是一种反模式。https://vladmihalcea.com/the-hibernate-enable_lazy_load_no_trans-anti-pattern/ - Sudip Bhandari
1
如果这是反模式,你能否提供一个替代方案?我也在使用Spring Data,但其他答案对我没有帮助。 - MiguelMunoz
1
谢谢,这对我有用。 - Alex Escobar
它运行得很好,但我不知道错误是什么,为什么会发生。 - Sayed Hussainullah Sadat

26

对于下面的注释,我在一对多关系中遇到了相同的错误。

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

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

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

51
是的,它可能会修复它,但现在你正在加载整个数据树。这通常会产生负面的性能影响。 - astro8891
1
解决了我的问题,非常感谢。 - crispengari

10

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


你如何将实体重新附加到会话中? - Pere

7
这意味着您尝试访问的对象未加载,因此编写一个查询,使其中包含您要访问的对象的 join fetch
例如:
如果您正在尝试从 ObjectA 获取 ObjectB,而 ObjectB 是 ObjectA 中的外键,则查询如下:
SELECT objA FROM ObjectA obj JOIN FETCH obj.objectB objB

6

我遇到过同样的问题。我认为修复此问题的另一种方法是将查询更改为联接从Model提取Element,如下:

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

5

在不同的使用情况下遇到相同的异常。

enter image description here

用例:尝试使用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

}

客户 DAO 接口
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());

当前数据

enter image description here

Hibernate 系统查询和输出生成的结果

Hibernate: select customer0_.Id as Id1_0_0_, customer0_.City as City2_0_0_, customer0_.Name as Name3_0_0_ from CustomerLab31 customer0_ where customer0_.Id=?

客户名称 -> Cody ,客户城市 -> LA


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