org.hibernate.LazyInitializationException:如何正确使用Hibernate的延迟加载功能 [英] org.hibernate.LazyInitializationException: How to properly use Hibernate's lazy loading feature

查看:83
本文介绍了org.hibernate.LazyInitializationException:如何正确使用Hibernate的延迟加载功能的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

使用Hibernate和lazy = true模式从我的数据库加载对象列表时遇到了一些麻烦。
希望有人可以帮助我。



我在这里有一个名为UserAccount的简单类,它看起来像这样:

  public class UserAccount {
long id;
字符串用户名;
列表< MailAccount> mailAccounts = new Vector< MailAccount>();

public UserAccount(){
super();
}

public long getId(){
return id;
}

public void setId(long id){
this.id = id;
}

public String getUsername(){
return username;
}

public void setUsername(String username){
this.username = username;
}

公共列表< MailAccount> getMailAccounts(){
if(mailAccounts == null){
mailAccounts = new Vector< MailAccount>();
}
返回mailAccounts;
}

public void setMailAccounts(List< MailAccount> mailAccounts){
this.mailAccounts = mailAccounts;




$ b我在Hibernate中通过下面的映射映射这个类file:

 <?xml version =1.0?> 
<!DOCTYPE hibernate-mapping PUBLIC - // Hibernate / Hibernate映射DTD 3.0 // EN
http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd >
< hibernate-mapping>
< class name =test.account.UserAccounttable =USERACCOUNT>

< id name =idtype =longaccess =field>
< column name =USER_ACCOUNT_ID/>
< generator class =native/>
< / id>

< property name =username/>

< bag name =mailAccountstable =MAILACCOUNTSlazy =trueinverse =truecascade =all>
< key column =USER_ACCOUNT_ID>< / key>
< / bag>

< / class>
< / hibernate-mapping>

正如您所看到的,在包映射元素中,lazy被设置为true。



将数据保存到数据库中工作正常:

通过调用 loadUserAccount(String用户名)(见下面的代码):

  public class HibernateController implements DatabaseController {
private会话会话=空;
private final SessionFactory sessionFactory = buildSessionFactory();

public HibernateController(){
super();

$ b $ private SessionFactory buildSessionFactory(){
try {
return new Configuration()。configure()。buildSessionFactory();
} catch(Throwable ex){
System.err.println(Initial SessionFactory creation failed。+ ex);
抛出新的ExceptionInInitializerError(ex);


$ b $ public UserAccount loadUserAccount(String username)throws FailedDatabaseOperationException {
UserAccount account = null;
会话会话=空;
交易交易=空;
尝试{
session = getSession();
transaction = session.beginTransaction();
Query query = session.createQuery(FROM UserAccount WHERE username =:uname)。setParameter(uname,username));
account =(UserAccount)query.uniqueResult();
transaction.commit();
} catch(Exception e){
transaction.rollback();
抛出新的FailedDatabaseOperationException(e);
} finally {
if(session.isOpen()){
// session.close();
}
}

返回帐户;


private Session getSession(){
if(session == null){
session = getSessionFactory()。getCurrentSession();
}
返回会话;






$ b

问题在于:当我访问列表mailAccounts,我收到以下异常:


org.hibernate.LazyInitializationException:
未能延迟初始化
角色集合:
test.account.UserAccount.mailAccounts,
没有会话或会话已关闭


我认为这个异常的原因是会话关闭(不知道为什么以及如何),因此Hibernate无法加载列表。
您可以看到,我甚至从 loadUserAccount()中移除了 session.close()方法,但会话似乎仍然是关闭或由另一个实例取代。
如果我设置 lazy = false ,那么一切正常,但这不是我想要的,因为我需要根据性能按需加载数据的功能因此,如果我不能确定我的会话在方法 loadUserAccount(String username)终止,这个功能有什么意义,我该如何解决这个问题?



感谢您的帮助!

Ps:我是Hibernate初学者,所以请原谅我的noobishness。



更新:这里是我的hibernate config.cfg。 xml

 <?xml version =1.0encoding =UTF-8?> 
<!DOCTYPE hibernate-configuration PUBLIC
- // Hibernate / Hibernate配置DTD 3.0 // EN
http://www.hibernate.org/dtd/hibernate-configuration -3.0.dtd>
< hibernate-configuration>
< session-factory>
< property name =hibernate.connection.driver_class> com.mysql.jdbc.Driver< / property>
< property name =hibernate.connection.password> foo< / property>
< property name =hibernate.connection.url> jdbc:mysql:// localhost:3306 / mytable< / property>
< property name =hibernate.connection.username>用户< / property>
< property name =hibernate.dialect> org.hibernate.dialect.MySQLInnoDBDialect< / property>

<! - 自动创建表格 - >
<! - < property name =hbm2ddl.auto> create< / property> - >

<! - 启用Hibernate的自动会话上下文管理 - >
< property name =current_session_context_class>线程< / property>

<! - 映射 - >
< mapping resource =test / account / SmampiAccount.hbm.xml/>
< mapping resource =test / account / MailAccount.hbm.xml/>
< / session-factory>
< / hibernate-configuration>


解决方案

延迟加载是否与事务无关边界。它只需要会话是开放的。



但是,会话打开时取决于您如何设置SessionFactory,您没有告诉我们!实际上在 SessionFactory.getCurrentSession()后面进行配置!如果你使用默认版本的 ThreadLocalSessionContext 并且没有做任何事情来管理生命周期,它确实实际上默认在提交时关闭会话。 (因此,扩大事务边界的常见概念是延迟加载异常的'修复')。如果您使用 sessionFactory.openSession() session.close()在会话生命周期内,您可以在事务边界之外延迟加载。或者,你可以提供一个 ThreadLocalSessionContext 的子类,用你想要的边界来管理会话生命周期。还有一些可用的替代方法,例如OpenSessionInView过滤器,可用于Web应用程序将会话生命周期绑定到Web请求生命周期。



编辑:您当然也可以在事务内部初始化列表,如果这对您有用。我只是认为,如果您需要为您的实体的每个级别提供某种标志参数的新方法签名,会导致真正笨重的API。 dao.getUser()dao.getUserWithMailAccounts()dao.getUserWIthMailAccountsAndHistoricalIds()等。



编辑2:您可能会发现这有助于不同方法会话多久保持公开/会话范围和事务范围之间的关系。 (特别是session-per-request-with-detached-objects vs session-per-conversation)。



这取决于您的要求和体系结构,实际上对话有多大。


I got some trouble loading a list of objects from my database using Hibernate and lazy=true mode. Hope that someone can help me out here.

I have a simple class here called UserAccount which looks like this:

public class UserAccount {
    long id;
    String username;
    List<MailAccount> mailAccounts = new Vector<MailAccount>();

    public UserAccount(){
        super();
    }

    public long getId(){
        return id;
    }

    public void setId(long id){
        this.id = id;
    }

    public String getUsername(){
        return username;
    }

    public void setUsername(String username){
        this.username = username;
    }

    public List<MailAccount> getMailAccounts() {
        if (mailAccounts == null) {
            mailAccounts = new Vector<MailAccount>();
        }
        return mailAccounts;
    }

    public void setMailAccounts(List<MailAccount> mailAccounts) {
        this.mailAccounts = mailAccounts;
    }
}

I am mapping this class in Hibernate via the following mapping file:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
    <class name="test.account.UserAccount" table="USERACCOUNT">

        <id name="id" type="long" access="field">
            <column name="USER_ACCOUNT_ID" />
            <generator class="native" />
        </id>

        <property name="username" />

        <bag name="mailAccounts" table="MAILACCOUNTS" lazy="true" inverse="true" cascade="all">
            <key column="USER_ACCOUNT_ID"></key>
            <one-to-many class="test.account.MailAccount" />
        </bag>

    </class>
</hibernate-mapping>

As you can see, lazy is set to "true" in the bag mapping element.

Saving the data to the database works fine:

Loading also works by calling loadUserAccount(String username) (see code below):

public class HibernateController implements DatabaseController {
    private Session                 session         = null;
    private final SessionFactory    sessionFactory  = buildSessionFactory();

    public HibernateController() {
        super();
    }

    private SessionFactory buildSessionFactory() {
        try {
            return new Configuration().configure().buildSessionFactory();
        } catch (Throwable ex) {
            System.err.println("Initial SessionFactory creation failed." + ex);
            throw new ExceptionInInitializerError(ex);
        }
    }

    public UserAccount loadUserAccount(String username) throws FailedDatabaseOperationException {
        UserAccount account = null;
        Session session = null;
        Transaction transaction = null;
        try {
            session = getSession();
            transaction = session.beginTransaction();
            Query query = session.createQuery("FROM UserAccount WHERE username = :uname").setParameter("uname", username));
            account = (UserAccount) query.uniqueResult();
            transaction.commit();
        } catch (Exception e) {
            transaction.rollback();
            throw new FailedDatabaseOperationException(e);
        } finally {
            if (session.isOpen()) {
                // session.close();
            }
        }

        return account;
    }

    private Session getSession() {
        if (session == null){
            session = getSessionFactory().getCurrentSession();
        }
        return session;
    }
}

The problem is just: When I access elements within the list "mailAccounts", I get the following exception:

org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: test.account.UserAccount.mailAccounts, no session or session was closed

I assume the reason for this exception is that the session got closed (don't know why and how) and thus Hibernate cannot load the list. As you can see, I even removed the session.close() call from the loadUserAccount() method but the session still seems to be either get closed or replaced by another instance. If I set lazy=false, then everything works smoothly but this is not what I wanted because I need the feature of loading data "on demand" due to performance issues.

So, if I can't be sure that my session is still valid after the method loadUserAccount(String username) terminated, what's the point of having that feature and how do I work around that?

Thanks for your help!

Ps: I am a Hibernate beginner so please excuse my noobishness.

Update: Here is my hibernate config.cfg.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
    <session-factory>
        <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
        <property name="hibernate.connection.password">foo</property>
        <property name="hibernate.connection.url">jdbc:mysql://localhost:3306/mytable</property>
        <property name="hibernate.connection.username">user</property>
        <property name="hibernate.dialect">org.hibernate.dialect.MySQLInnoDBDialect</property>

        <!-- Auto create tables -->
<!--        <property name="hbm2ddl.auto">create</property>-->

        <!-- Enable Hibernate's automatic session context management -->
        <property name="current_session_context_class">thread</property>

        <!-- Mappings -->
        <mapping resource="test/account/SmampiAccount.hbm.xml"/>
        <mapping resource="test/account/MailAccount.hbm.xml"/>
    </session-factory>
</hibernate-configuration>

解决方案

Lazy Loading working or not has nothing to do with transaction boundaries. It only requires that the Session be open.

However, when the session is open depends on how you've actually set up the SessionFactory, which you did not tell us! There is config going on behind what SessionFactory.getCurrentSession() actually does! If you're letting it go with the default version of ThreadLocalSessionContext and not doing anything to manage the life cycle, it does indeed actually default to closing the session when you commit. (Hence the common conception that broadening transaction boundaries is the 'fix' for a lazy load exception.)

If you manage you own session life cycle with sessionFactory.openSession() and session.close() you will be able to lazy load fine within the session life cycle, outside transaction boundaries. Alternately you can provide a subclass of ThreadLocalSessionContext that manages the session life-cycle with the boundaries you desire. There are also readily available alternatives such as the OpenSessionInView filter that can be used in web applications to bind the session life-cycle to the web request life cycle.

edit: You can also of course just initialize the list inside the transaction if that works for you. I just think that leads to really clunky APIs when you need either a new method signature of some kind of 'flag' parameter for each level of hydration of your entity. dao.getUser() dao.getUserWithMailAccounts() dao.getUserWIthMailAccountsAndHistoricalIds() and so on.

edit 2: You may find this helpful for different approaches to how long the session stays open/the relationship between session scope and transaction scope. (particularly the idea of session-per-request-with-detached-objects vs session-per-conversation.)

It depends on your requirements and architecture just how big a conversation actually is.

这篇关于org.hibernate.LazyInitializationException:如何正确使用Hibernate的延迟加载功能的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆