【问题标题】:org.hibernate.LazyInitializationException: How to properly use Hibernate's lazy loading featureorg.hibernate.LazyInitializationException:如何正确使用 Hibernate 的延迟加载功能
【发布时间】:2011-08-15 18:46:10
【问题描述】:

我在使用 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;
    }
}

我通过以下映射文件在 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>

如您所见,在包映射元素中,lazy 设置为“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;
    }
}

问题只是:当我访问列表“mailAccounts”中的元素时,出现以下异常:

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

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

那么,如果我不能确定我的会话在方法 loadUserAccount(String username) 终止后仍然有效,那么拥有该功能的意义何在?我该如何解决?

感谢您的帮助!

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

更新:这是我的休眠配置.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>

【问题讨论】:

    标签: java hibernate


    【解决方案1】:

    延迟加载工作与否与事务边界无关。它只需要打开 Session。

    但是,会话何时打开取决于您实际设置 SessionFactory 的方式,而您没有告诉我们! SessionFactory.getCurrentSession() 的实际操作背后有配置!如果您让它使用默认版本的ThreadLocalSessionContext 并且不做任何事情来管理生命周期,那么它实际上确实默认为在您提交时关闭会话。 (因此,拓宽事务边界是延迟加载异常的“修复”这一常见概念。)

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

    编辑:如果这对您有用,您当然也可以在事务中初始化列表。我只是认为,当您需要为实体的每个水合级别使用某种“标志”参数的新方法签名时,这会导致非常笨拙的 API。 dao.getUser() dao.getUserWithMailAccounts() dao.getUserWIthMailAccountsAndHistoricalIds() 等等。

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

    这取决于您的要求和架构,实际对话的规模有多大。

    【讨论】:

    • 我认为手动打开和关闭会话可能是一种选择。但是,在整个应用程序生命周期内保持会话打开并仅在应用程序终止时关闭它是否明智?
    • 更新:factory.openSession() 替换 factory.getCurrentSession() 虽然永远不会关闭会话,但我仍然想知道这是否是一个好习惯。
    • 这将是一种不常见的方法,但对于桌面应用程序来说可能是可行的。会话不是线程安全的,因此您需要小心同步来自异步工作人员的访问。在不了解您的应用程序的情况下很难说!会话生命周期的范围可能较小,这是合理的。请记住,当 Session 出现异常时,您应该得到一个新异常。因此,即使您保留相同的参考,也需要进行一些管理。而且您可能也不希望 L1 缓存无限制地增长。
    • 我正在构建一个多线程 Web 应用程序。不知道 Session 不是线程安全的。这会让事情变得更加艰难。无论如何,非常感谢您的建议。我很感激!
    • "或者,您可以提供一个 ThreadLocalSessionContext 的子类,以您希望的边界管理会话生命周期..."请详细说明如何执行此操作.. 谢谢
    【解决方案2】:

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

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

    【讨论】:

    【解决方案3】:

    您需要将整个过程封装在一个事务中。

    因此,与其在 loadUserAccount 中启动和提交事务,不如在此之外进行。

    例如:

    public void processAccount()
    {
        getSession().beginTransaction();
        UserAccount userAccount = loadUserAccount("User");
        Vector accts = userAccount.getMailAccounts();  //This here is lazy-loaded -- DB requests will happen here
        getSession().getTransaction().commit();
    }
    

    通常,您希望将事务包装在整个工作单元中。我怀疑你对交易的理解有点太细了。

    【讨论】:

    • 这不是一个选项,因为我不知道何时实际访问该列表,因为这取决于用户交互。可能这实际上从未发生过。在这种情况下,交易永远不会关闭。
    • 这是什么类型的应用程序?网络应用?独立?
    猜你喜欢
    • 2013-12-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-25
    • 2018-11-22
    • 1970-01-01
    相关资源
    最近更新 更多