簡體   English   中英

org.hibernate.LazyInitializationException:如何正確使用 Hibernate 的延遲加載功能

[英]org.hibernate.LazyInitializationException: How to properly use Hibernate's lazy loading feature

使用 Hibernate 和lazy=true 模式從我的數據庫中加載對象列表時遇到了一些麻煩。 希望有人可以在這里幫助我。

我在這里有一個簡單的 class,名為 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;
    }
}

我通過以下映射文件將這個 class 映射到 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,沒有 session 或 Z21D6F40CFB5119082E442Z4E0E 已關閉

我認為此異常的原因是 session 已關閉(不知道為什么以及如何關閉),因此 Hibernate 無法加載列表。 如您所見,我什至從loadUserAccount()方法中刪除了session.close()調用,但 session 似乎仍然被關閉或被另一個實例取代。 如果我設置lazy=false ,那么一切都會順利進行,但這不是我想要的,因為由於性能問題,我需要“按需”加載數據的功能。

所以,如果我不能確定我的 session 在方法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>

延遲加載工作與否與事務邊界無關。 它只需要打開 Session。

但是,session 何時打開取決於您實際設置 SessionFactory 的方式,而您沒有告訴我們! SessionFactory.getCurrentSession()實際執行的操作背后有配置! 如果你讓它 go 使用默認版本的ThreadLocalSessionContext並且不做任何事情來管理生命周期,它實際上確實默認為在你提交時關閉 session。 (因此,拓寬事務邊界的普遍概念是延遲加載異常的“修復”。)

如果您使用sessionFactory.openSession()session.close()管理自己的 session 生命周期,您將能夠在 Z21D6F40CFB511982E4424E0E250A95 事務之外的生命周期邊界內延遲加載。 或者,您可以提供ThreadLocalSessionContext的子類,該子類以您想要的邊界管理 session 生命周期。 還有現成的替代方案,例如可用於 web 應用程序的 OpenSessionInView 過濾器,以將 session 生命周期綁定到 web 請求生命周期。

編輯:如果這對您有用,您當然也可以只初始化事務中的列表。 我只是認為,當您需要為實體的每個水合級別使用某種“標志”參數的新方法簽名時,這會導致非常笨拙的 API。 dao.getUser() dao.getUserWithMailAccounts() dao.getUserWIthMailAccountsAndHistoricalIds() 等等。

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. (特別是每個請求的會話與分離對象與會話每個會話的想法。)

這取決於您的要求和架構,實際上對話的規模有多大。

您收到異常的原因可能是您加載數據的事務已關閉(以及 session),即您在 session 之外工作。 延遲加載在使用 session 中的實體(或在正確使用二級緩存時跨會話)時特別有用。

AFAIK,您可以告訴 Hibernate 自動打開一個新的 session 以進行延遲加載,但我有一段時間沒有使用它,因此我不得不再次查看它是如何工作的。

您需要將整個過程包裝在事務中。

因此,與其在 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();
}

通常,您希望圍繞整個工作單元包裝您的事務。 我懷疑你對交易的理解有點太細了。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM