使用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在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;
}
}
问题是:当我访问列表“mailAccounts”中的元素时,我得到以下异常:
org.hibernate.LazyInitializationException: 没有懒惰地初始化一个 角色集合: test.account.UserAccount.mailAccounts, 没有会话或会话被关闭
我假设这个异常的原因是会话已关闭(不知道为什么以及如何),因此Hibernate无法加载列表。
如您所见,我甚至从session.close()
方法中删除了loadUserAccount()
调用,但会话似乎仍然被关闭或被其他实例替换。
如果我设置lazy=false
,那么一切顺利,但这不是我想要的,因为我需要“由于性能问题而”按需加载数据的功能。
因此,如果我在方法loadUserAccount(String username)
终止后无法确定我的会话是否仍然有效,那么具有该功能的重点是什么?如何解决该问题?
感谢您的帮助!
Ps:我是Hibernate的初学者,请原谅我的无聊。
更新:这是我的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>
答案 0 :(得分:22)
延迟加载是否与事务边界无关。它只要求会话开放。
然而,当会话打开时,取决于你实际上如何设置SessionFactory,你没有告诉我们! SessionFactory.getCurrentSession()
实际做了什么后面有配置!如果您使用默认版本的ThreadLocalSessionContext
并且没有做任何事情来管理生命周期,那么它确实默认在您提交时关闭会话。 (因此,扩展事务边界的常见概念是延迟加载异常的“修复”。)
如果您使用sessionFactory.openSession()
和session.close()
管理自己的会话生命周期,您将能够在会话生命周期内,在事务边界之外延迟加载。或者,您可以提供ThreadLocalSessionContext
的子类,该子类使用您想要的边界管理会话生命周期。还有一些现成的替代方案,例如OpenSessionInView过滤器,可以在Web应用程序中使用它将会话生命周期绑定到Web请求生命周期。
编辑:当然,如果适合你,你也可以在事务中初始化列表。我只是认为当您需要为您的实体的每个水合程度的某种“标志”参数的新方法签名时,这会导致非常笨重的API。 dao.getUser()dao.getUserWithMailAccounts()dao.getUserWIthMailAccountsAndHistoricalIds()等。
编辑2:您可能会发现这对于会话保持打开的时间/会话范围与事务范围之间的关系的不同方法很有帮助。 (特别是每个请求的会话与分离对象的会话与每个会话的会话。)
这取决于您的要求和架构,实际上对话的大小。
答案 1 :(得分:5)
您获得异常的原因可能是您加载数据的事务已关闭(以及与之进行的会话),即您在会话外工作。在一个会话中使用实体时(或正确使用二级缓存时跨会话),延迟加载特别有用。
AFAIK你可以告诉Hibernate自动为延迟加载打开一个新会话,但我暂时没有使用它,因此我必须再查看它是如何工作的。
答案 2 :(得分:0)
您需要将整个流程包装在一个事务中。
因此,不要在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();
}
通常,您希望将事务包装在整个工作单元中。我怀疑你对交易的理解有点过于细微。