org.hibernate.LazyInitializationException:如何正确使用Hibernate的延迟加载功能

Tim*_*imo 9 java hibernate

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

我在这里有一个名为UserAccount的简单类,如下所示:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

我通过以下映射文件在Hibernate中映射此类:

<?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>
Run Code Online (Sandbox Code Playgroud)

如您所见,lazy在bag mapping元素中设置为"true".

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

加载也可以通过调用loadUserAccount(String username)(见下面的代码):

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

问题是:当我访问列表"mailAccounts"中的元素时,我得到以下异常:

org.hibernate.LazyInitializationException:无法懒惰地初始化角色集合:test.account.UserAccount.mailAccounts,没有会话或会话被关闭

我假设这个异常的原因是会话被关闭(不知道为什么和如何),因此Hibernate无法加载列表.正如您所看到的,我甚至session.close()loadUserAccount()方法中删除了调用,但会话似乎仍然被关闭或被另一个实例替换.如果我设置lazy=false,那么一切顺利,但这不是我想要的,因为我需要由于性能问题"按需"加载数据的功能.

因此,如果我无法确定方法loadUserAccount(String username)终止后我的会话是否仍然有效,那么具有该功能的重点是什么?如何解决这个问题?

谢谢你的帮助!

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

更新:这是我的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>
Run Code Online (Sandbox Code Playgroud)

Aff*_*ffe 22

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

但是,当会话打开时,取决于你实际上如何设置SessionFactory,你没有告诉我们!SessionFactory.getCurrentSession()实际上有背后的配置!如果你让它使用默认版本ThreadLocalSessionContext并且没有做任何事情来管理生命周期,它实际上确实默认在你提交时关闭会话.(因此,扩展事务边界的常见概念是延迟加载异常的"修复".)

如果您使用管理自己的会话生命周期,sessionFactory.openSession()并且session.close()您将能够在会话生命周期内,在事务边界之外延迟加载.或者,您可以提供一个子类ThreadLocalSessionContext来管理会话生命周期以及您想要的边界.还有一些现成的替代方案,例如OpenSessionInView过滤器,可以在Web应用程序中使用它将会话生命周期绑定到Web请求生命周期.

编辑:您当然也可以初始化事务中的列表,如果它适合您.我只是认为当您需要为您的实体的每个水合程度的某种"标志"参数的新方法签名时,这会导致非常笨重的API.dao.getUser()dao.getUserWithMailAccounts()dao.getUserWIthMailAccountsAndHistoricalIds()等.

编辑2:您可能会发现这对于会话保持打开的时间/会话范围与事务范围之间的关系的不同方法很有帮助.(特别是每个请求的会话 - 分离对象与每个会话的会话的想法.)

这取决于您的要求和架构,实际上对话有多大.


Tho*_*mas 5

您获得异常的原因可能是您加载数据的事务已关闭(以及与之相关的会话),即您在会话之外工作.在一个会话中使用实体时(或正确使用二级缓存时跨会话),延迟加载特别有用.

AFAIK你可以告诉Hibernate自动打开一个新的会话来进行延迟加载,但我暂时没有使用它,因此我必须再查看它是如何工作的.