Hibernate:LazyInitializationException:无法初始化代理

76

这是一个让我感到困惑的问题。我正在尝试实现一个基本的Hibernate DAO结构,但遇到了问题。

以下是关键代码:

int startingCount = sfdao.count();
sfdao.create( sf );
SecurityFiling sf2 = sfdao.read( sf.getId() );
sfdao.delete( sf );
int endingCount = sfdao.count();

assertTrue( startingCount == endingCount );
assertTrue( sf.getId().longValue() == sf2.getId().longValue() );
assertTrue( sf.getSfSubmissionType().equals( sf2.getSfSubmissionType() ) );
assertTrue( sf.getSfTransactionNumber().equals( sf2.getSfTransactionNumber() ) );

第三个assertTrue失败了,因为它试图将sf中的值与sf2中相应的值进行比较。以下是异常情况:

org.hibernate.LazyInitializationException: could not initialize proxy - no Session
    at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:86)
    at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:140)
    at org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer.invoke(JavassistLazyInitializer.java:190)
    at com.freightgate.domain.SecurityFiling_$$_javassist_7.getSfSubmissionType(SecurityFiling_$$_javassist_7.java)
    at com.freightgate.dao.SecurityFilingTest.test(SecurityFilingTest.java:73)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:40)
14个回答

69

问题在于你试图访问一个处于脱离状态的对象中的集合。在访问该集合之前,你需要将对象重新连接到当前会话。你可以通过以下方式实现:

session.update(object);

使用lazy=false不是一个好的解决方案,因为你会失去Hibernate的延迟初始化特性。当lazy=false时,集合将在请求对象时同时加载到内存中。这意味着,如果我们有一个包含1000个项目的集合,无论我们是否要访问它们,它们都将被加载到内存中。这并不好。

请阅读这篇文章,其中解释了问题、可能的解决方案以及为什么以这种方式实现。此外,要理解Sessions和Transactions,你必须阅读这篇其他文章


15

一般这意味着拥有的 Hibernate 会话已经关闭。您可以执行以下任一步骤来解决问题:

  1. 无论是哪个对象导致了该问题,都使用 HibernateTemplate.initialize(object name)
  2. 在您的 hbm 文件中使用 lazy=false

遇到了同样的问题,将lazy=false修复了。谢谢。 - autonomatt
1
现在在我的情况下,我对所有DAO级别使用lazy=false,但是由于它而导致应用程序性能变慢,尝试设置lazy=true,但现在会抛出lazyException。有什么建议可以解决这个问题吗? - Rachel
Pakore,你能指出为什么这不是解决方案以及如何理解它吗? - Victor
2
@Victor lazy=false 和 eager 是相同的。当我们选择使用 eager 加载关联时,每次加载实体时,所有“急切关联”都将被加载,即使我们不要求或使用它也是如此。 - Jonathas Pacífico

10

看看我的文章。我曾经遇到同样的问题-LazyInitializationException-这是我最终想出的答案:
http://community.jboss.org/wiki/LazyInitializationExceptionovercome
将 lazy 标志设置为 false 并不是答案 - 它会一次性加载所有内容,这并不一定好。例如:
1 条记录在表 A 中引用:
5 条记录在表 B 中引用:
25 条记录在表 C 中引用:
125 条记录在表 D 中引用:
...
等等。这只是其中一个示例,可能会出现的问题。
--Tim Sabin


1
你应该在这里解释解决方案,而不是链接到第三方网站。 - saimiris_devel
链接已损坏,导致此答案的价值为0。 - undefined

7

如果你正在使用带有JPA注释的Hibernate,则这将非常有用。在您的服务类中,应该有一个带有@PersistenceContext的实体管理器setter。将其更改为@PersistenceContext(type = PersistenceContextType.EXTENDED)。然后您可以在任何地方访问懒加载属性。


1
这不正确,除非您手动管理事务。Spring的扩展持久化上下文类型是为长时间对话模式设计的,而不是OP所询问的每个请求一次的模式。 - Dave

4

如果您正在使用Lazy loading,您的方法必须用注释

@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) 来标注无状态会话EJB。


3
我们也遇到了这个错误。我们解决问题的方法是在Hibernate映射文件中添加了一个lazy=false
看起来我们有一个类A,它在一个Session中加载另一个类B。我们试图访问类B的数据,但是这个类B已经脱离了Session。
为了访问这个类B,我们必须在类A的Hibernate映射文件中指定lazy=false属性。例如:
     <many-to-one name="classA" 
                 class="classB"
                 lazy="false">
        <column name="classb_id"
                sql-type="bigint(10)" 
                not-null="true"/>
    </many-to-one>  

2
默认情况下,所有的一对多和多对多关联在第一次访问时都会被懒加载。在您的使用情况中,您可以通过将所有DAO操作包装到一个逻辑事务中来克服这个问题:
transactionTemplate.execute(new TransactionCallback<Void>() {
    @Override
    public Void doInTransaction(TransactionStatus transactionStatus) {

        int startingCount = sfdao.count();

        sfdao.create( sf );

        SecurityFiling sf2 = sfdao.read( sf.getId() );

        sfdao.delete( sf );

        int endingCount = sfdao.count();

        assertTrue( startingCount == endingCount );
        assertTrue( sf.getId().longValue() == sf2.getId().longValue() );
        assertTrue( sf.getSfSubmissionType().equals( sf2.getSfSubmissionType() ) );
        assertTrue( sf.getSfTransactionNumber().equals( sf2.getSfTransactionNumber() ) );

        return null;
    }
});

另一个选项是在加载实体时获取所有延迟加载的关联,这样可以:
SecurityFiling sf2 = sfdao.read( sf.getId() );

应该也获取“懒惰”的提交类型(submissionType)。
select sf
from SecurityFiling sf
left join fetch.sf.submissionType

这样,您可以热切地获取所有懒加载属性,并且在Session关闭后也可以访问它们。
您可以获取尽可能多的[one|many]-to-one关联和一个"[one|many]-to-many"列表关联(因为运行笛卡尔积)。
要初始化多个"[one|many]-to-many",您应该在加载根实体后立即使用Hibernate.initialize(collection)

2

看起来只有你的DAO在使用session。因此,每次调用DAO方法时都会开启一个新的session,然后关闭它。因此,程序的执行可以如下继续:

// open a session, get the number of entity and close the session
int startingCount = sfdao.count();

// open a session, create a new entity and close the session
sfdao.create( sf );

// open a session, read an entity and close the session
SecurityFiling sf2 = sfdao.read( sf.getId() );

// open a session, delete an entity and close the session
sfdao.delete( sf );

etc...

默认情况下,实体中的集合和关联是懒加载的:它们会在需要时从数据库中加载。因此: sf.getSfSubmissionType().equals(sf2.getSfSubmissionType()) 抛出异常,因为它请求从数据库重新加载,并且与加载实体相关联的会话已经关闭。
有两种方法可以解决这个问题:
  • 创建一个会话来封装我们所有的代码。这意味着更改DAO内容以避免打开第二个会话。
  • 在断言之前创建一个会话,然后更新(即重新连接)实体以使用该会话。
  • session.update(object);


2

好的,最终我弄清楚了我的错误所在。我曾错误地认为应该在每个DAO方法中包装一个事务。这是非常错误的!我已经吸取教训。我将所有DAO方法中的事务代码全部移除,并严格在应用程序/管理器层设置事务。这完全解决了我所有的问题。数据会在我需要时正确地进行延迟加载,在提交后进行封装和关闭。

生活真美好... :)


我不确定我完全理解,因为我没有在其他项目中看到过这种情况。但你是对的:在服务层的方法中添加@org.springframework.transaction.annotation.Transactional(readOnly=true)可以解决这个问题。(在该层,我们正在获取一个实体并将其传递给DAO的另一个调用。) - Arjan

2
如果您了解lazy=false的影响,并且仍然希望将其作为默认值(例如,用于原型制作),则可以使用以下任何一种方法:
  • 如果您正在使用XML配置:向您的<hibernate-mapping>元素添加default-lazy="false"
  • 如果您正在使用注释配置:向您的实体类添加@Proxy(lazy=false)

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