使用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>
如您所见,在bag映射元素中,lazy设置为“ true”。
将数据保存到数据库可以正常工作:
通过调用也可以加载loadUserAccount(String username)(请参见下面的代码):
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,那么一切都会顺利进行,但这不是我想要的,因为由于性能问题,我需要“按需”加载数据的功能。
session.close()
loadUserAccount()
lazy=false
因此,如果我不确定在方法loadUserAccount(String username)终止后我的会话仍然有效,那么具有该功能的意义何在?该如何解决?
谢谢你的帮助!
附:我是一个hibernate初学者,所以请原谅我的肥胖。
更新: 这是我的hibernateconfig.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>
延迟加载是否工作与事务边界无关。它仅要求打开会话。
但是,何时打开会话取决于您实际上是如何设置SessionFactory的,您没有告诉我们!SessionFactory.getCurrentSession()实际执行的操作背后有配置!如果您允许使用默认版本的ThreadLocalSessionContext,而不执行任何管理生命周期的操作,则实际上确实默认是在提交时关闭会话。(因此,通常的观念是,扩展事务边界是延迟加载异常的“解决方案”。)
SessionFactory.getCurrentSession()
ThreadLocalSessionContext
如果你管理你自己的会话的生命周期与sessionFactory.openSession()和session.close()你将能够在会话的生命周期内延迟加载罚款,外事务边界。或者,您可以提供一个子类,ThreadLocalSessionContext该子类使用所需的边界来管理会话生命周期。还有一些容易获得的替代方法,例如OpenSessionInView过滤器,可以在Web应用程序中使用它来将会话生命周期绑定到Web请求生命周期。
sessionFactory.openSession()
编辑:当然,您也可以只初始化事务内的列表(如果适合您)。我只是认为,当您需要为实体的每个水合作用级别使用某种“标志”参数的新方法签名时,就会导致API变得笨拙。dao.getUser()dao.getUserWithMailAccounts()dao.getUserWIthMailAccountsAndHistoricalIds()等。
编辑2:您可能会发现这对于会话保持打开状态/会话范围与事务范围之间的关系的不同方法很有帮助。(特别是与分离对象的每个请求会话与每个会话的会话的想法。)
对话的实际规模取决于您的要求和体系结构。