English 中文(简体)
懒加载异常:无法初始化代理。
原标题:
  • 时间:2008-12-06 01:32:28
  •  标签:

这让我感到困惑。我正在尝试实现基本的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)
问题回答

问题是你试图访问一个处于“已分离”状态的对象中的集合。在访问当前 session 中的集合之前,你需要重新将对象连接到会话中。你可以通过...重新连接对象来实现。

session.update(object);

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

请阅读这篇文章,其中解释了问题,可能的解决方案以及为什么要以这种方式实现。此外,要了解会话和事务,您必须阅读另一篇文章

这通常意味着拥有 Hibernate 会话已经关闭。您可以执行以下步骤之一来修复它:

  1. whichever object creating this problem, use HibernateTemplate.initialize(object name)
  2. Use lazy=false in your hbm files.

See my article. I had the same problem - LazyInitializationException - and here s the answer I finally came up with:
http://community.jboss.org/wiki/LazyInitializationExceptionovercome
Setting lazy=false is not the answer - it can load everything all at once, and that s not necessarily good. Example:
1 record table A references:
5 records table B references:
25 records table C references:
125 records table D
...
etc. This is but one example of what can go wrong.
--Tim Sabin

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

如果您正在使用惰性加载,您的方法必须带有注释

对于无状态会话 EJB,@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)

我们也遇到了这个错误。我们解决该问题的方法是在Hibernate映射文件中添加lazy=false

看起來我們有一個在Session裡的Class A,它載入了另一個Class B。我們正在嘗試存取Class B的資料,但這個Class 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>  

好的,最终明白了自己哪里疏忽了。我曾错误地认为应该在每个DAO方法中包装一个事务。这是非常错误的!我吸取了教训。我已经从所有DAO方法中提取了所有事务代码,并在应用程序/管理器层严格设置了事务。这完全解决了我所有的问题。数据会在我需要时正确地惰性加载,一旦我提交,就会被包装起来并关闭下来。

生命很好... :)

如果您了解lazy=false的影响并且仍然希望将其设置为默认值(例如,用于原型制作),则可以使用以下任何一种方法:

  • if you are using XML configuration: add default-lazy="false" to your <hibernate-mapping> element
  • if you are using annotation configuration: add @Proxy(lazy=false) to your entity class(es)

It seems only your DAO are using session. Thus a new session is open then close for each call to a DAO method. Thus the execution of the program can be resumed as:

// 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...

By default, collection and association in an entity are lazy: they are loaded from the database on demand. Thus:

sf.getSfSubmissionType().equals( sf2.getSfSubmissionType() )

is throwing an exception because it request a new loading from the database, and the session associated with the loading of the entity has already been closed.

There is two approaches to resolve this problem:

  • create a session to enclosed all our code. Thus it would mean changing your DAO content to avoid opening a second session

  • create a session then update (i.e. reconnect) your entity to this session before the assertions.

    session.update(object);

By default, all one-to-many and many-to-many associations are fetched lazily upon being accessed for the first time.

In your use case, you could overcome this issue by wrapping all DAO operations into one logical transaction:

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;
    }
});

Another option is to fetch all LAZY associations upon loading your entity, so that:

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

should fetch the LAZY submissionType too:

select sf
from SecurityFiling sf
left join fetch.sf.submissionType

This way, you eagerly fetch all lazy properties and you can access them after the Session gets closed too.

You can fetch as many [one|many]-to-one associations and one "[one|many]-to-many" List associations (because of running a Cartesian Product).

To initialize multiple "[one|many]-to-many", you should use Hibernate.initialize(collection), right after loading your root entity.

If you are managing the Hibernate session manually, you may want to look into sessionFactory.getCurrentSession() and associated docs here:

http://www.hibernate.org/hib_docs/v3/reference/en/html/architecture-current-session.html

I think Piko means in his response that there is the hbm file. I have a file called Tax.java. The mapping information are saved in the hbm (=hibernate mapping) file. In the class tag there is a property called lazy. Set that property to true. The following hbm example shows a way to set the lazy property to false.

` id ...

If you are using Annotations instead look in the hibernate documenation. http://docs.jboss.org/hibernate/stable/annotations/reference/en/html_single/

I hope that helped.

use Hibernate.initialize for lazy field

If you using Spring and JPA annotation, the simpliest way to avoid problem with session in lazy initialize is replaysing:

@PersistenceContext   

to

@PersistenceContext(type = PersistenceContextType.EXTENDED)




相关问题
热门标签