如何修复org.hibernate.LazyInitializationException-无法初始化代理-没有会话


188

我得到以下异常:

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,但是不允许这样做,因此建议我控制会话

Answers:


93

这是错误的,因为您在提交事务时将会话管理配置设置为关闭会话。检查是否有以下内容:

<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的那些模型。

更多阅读:

会话工厂配置

封闭会议的问题


1
谢谢!我使用openSession()而不是getCurrentSession()解决了我的问题,因为您给我的链接之一建议了这一点,但是现在我很
敬畏

2
不,可能很好。但是,请阅读更多内容,以完全控制您的会话和交易。了解基础知识非常重要,因为所有更高级别的技术(例如Spring,Hibernate等)都基于相同的概念运行。
goroncy

179

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

@Transactional
public class MyClass {
    ...
}

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

警告如果您确实使用@Transactional,请注意所产生的行为。有关常见陷阱,请参见本文。例如,即使您未显式调用实体,对实体的更新也会保留下来save


21
我不能高估这个答案的重要性。我强烈建议您首先尝试此选项。
sparkyspider16年

6
还请注意,您必须添加@EnableTransactionManagement到配置中才能启用事务。“ 如果调用另一个事务方法,该方法将可以选择加入正在进行的事务 ”,此行为对于实现事务的不同方式是不同的,即接口代理与类代理或AspectJ编织。请参阅文档
Erik Hofer

1
我们是否应该理解Transactional建议使用Spring 注释,不仅用于修改事务,而且仅用于访问事务?
斯蒂芬

8
我强烈建议仅在类上使用此批注进行测试。实际代码应将每个方法分别标记为类中的事务。除非类中的所有方法都需要打开与事务到数据库的连接。
m1ld

1
将@Transactional(readOnly = true)而不是仅@Transactional放起来不是安全的吗?
Hamedz

105

您可以尝试设置

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

在hibernate.cfg.xml或persistence.xml中

此属性要牢记的问题在这里得到了很好的解释


8
您还能解释一下它的含义吗?
Mohit Kanwar

2
我也很好奇这是做什么的。它确实解决了我遇到的问题,但我想了解原因。
哈桑

3
for persistence.xml:<property name="hibernate.enable_lazy_load_no_trans" value="true"/>
ACV


6
如果春天管理您的交易,此财产将导致交易爆炸,请不要使用此财产,而仅春天将关闭该应用程序
Youans

54

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

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

无论如何,请勿使用某些答案所建议的以下反模式:

有时,DTO投影比获取实体是更好的选择,这样一来,您将一无所获LazyInitializationException


如何确定哪个呼叫有问题?我发现很难识别来电。有什么办法吗?我出于测试目的使用FetchType=EAGER,但这不是正确的解决方案,对吧?
Shantaram Tupe

只需使用日志记录。EAGER不好,是的。
Vlad Mihalcea

然后,您应该使用DTO或初始化所有关联,然后再退出@Transactional服务。
Vlad Mihalcea

2
我们应该倡导最佳的企业实践,而不是快速的解决方法。
etlds

20

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

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

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

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

25
是的,它可能会解决它,但是现在您正在加载整个数据树。在大多数情况下,这会对性能产生负面影响
astro8891


9

由于发生异常,因此当您调用时session.getEntityById(),会话将关闭。因此,您需要将实体重新连接到会话。或Easy解决方案只是配置default-lazy="false" 给您,entity.hbm.xml或者如果您使用注释,则只需添加@Proxy(lazy=false)到您的实体类。


5

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

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

4

这意味着您要访问的对象未加载,因此编写一个查询,对要访问的对象进行联接提取

例如:

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

查询:

SELECT objA FROM ObjectA obj JOIN FETCH obj.objectB objB

3

这里有几个很好的答案可以广泛地处理此错误。我遇到了Spring Security的一个特殊情况,该问题可以快速解决,虽然可能不是最佳解决方案。

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

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



2

在不同的用例中面临相同的异常。

在此处输入图片说明

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

解决方案:使用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系统生成的查询和输出

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

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


1

如果您使用的是Grail'sFramework,则可以通过使用以下方法来解决延迟初始化异常Lazy在Domain Class中的特定字段上关键字。

例如:

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

在这里找到更多信息


1

以我为例,放错位置session.clear()会导致此问题。


1

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



-2

您也可以通过在* .hbm.xml文件中添加lazy = false来解决该问题,或者当从数据库中获取对象时,可以在Hibernate.init(Object)中初始化对象


10
通常添加lazy = false不是一个好主意。这就是默认情况下懒惰为真的原因
MoienGK 2014年

OP事先明确表示不允许他这样做。
GingerHead

-2

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>
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.