[英]how can to resolve org.hibernate.LazyInitializationException?
[英]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.