hibernate/春季:未能懒惰地初始化 – 没有会议或会议被closures

对于一个答案向下滚动到这个结尾…

基本的问题和多次询问一样。 我有一个简单的程序,有两个POJO事件和用户 – 用户可以有多个事件。

@Entity @Table public class Event { private Long id; private String name; private User user; @Column @Id @GeneratedValue public Long getId() {return id;} public void setId(Long id) { this.id = id; } @Column public String getName() {return name;} public void setName(String name) {this.name = name;} @ManyToOne @JoinColumn(name="user_id") public User getUser() {return user;} public void setUser(User user) {this.user = user;} } 

用户:

 @Entity @Table public class User { private Long id; private String name; private List<Event> events; @Column @Id @GeneratedValue public Long getId() { return id; } public void setId(Long id) { this.id = id; } @Column public String getName() { return name; } public void setName(String name) { this.name = name; } @OneToMany(mappedBy="user", fetch=FetchType.LAZY) public List<Event> getEvents() { return events; } public void setEvents(List<Event> events) { this.events = events; } } 

注意:这是一个示例项目。 我真的想在这里使用Lazy fetching。

现在我们需要configurationspring和hibernate,并且有一个简单的basic-db.xml来加载:

<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"> <bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close" scope="thread"> <property name="driverClassName" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://192.168.1.34:3306/hibernateTest" /> <property name="username" value="root" /> <property name="password" value="" /> <aop:scoped-proxy/> </bean> <bean class="org.springframework.beans.factory.config.CustomScopeConfigurer"> <property name="scopes"> <map> <entry key="thread"> <bean class="org.springframework.context.support.SimpleThreadScope" /> </entry> </map> </property> </bean> <bean id="mySessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean" scope="thread"> <property name="dataSource" ref="myDataSource" /> <property name="annotatedClasses"> <list> <value>data.model.User</value> <value>data.model.Event</value> </list> </property> <property name="hibernateProperties"> <props> <prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop> <prop key="hibernate.show_sql">true</prop> <prop key="hibernate.hbm2ddl.auto">create</prop> </props> </property> <aop:scoped-proxy/> </bean> <bean id="myUserDAO" class="data.dao.impl.UserDaoImpl"> <property name="sessionFactory" ref="mySessionFactory" /> </bean> <bean id="myEventDAO" class="data.dao.impl.EventDaoImpl"> <property name="sessionFactory" ref="mySessionFactory" /> </bean> </beans>
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"> <bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close" scope="thread"> <property name="driverClassName" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://192.168.1.34:3306/hibernateTest" /> <property name="username" value="root" /> <property name="password" value="" /> <aop:scoped-proxy/> </bean> <bean class="org.springframework.beans.factory.config.CustomScopeConfigurer"> <property name="scopes"> <map> <entry key="thread"> <bean class="org.springframework.context.support.SimpleThreadScope" /> </entry> </map> </property> </bean> <bean id="mySessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean" scope="thread"> <property name="dataSource" ref="myDataSource" /> <property name="annotatedClasses"> <list> <value>data.model.User</value> <value>data.model.Event</value> </list> </property> <property name="hibernateProperties"> <props> <prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop> <prop key="hibernate.show_sql">true</prop> <prop key="hibernate.hbm2ddl.auto">create</prop> </props> </property> <aop:scoped-proxy/> </bean> <bean id="myUserDAO" class="data.dao.impl.UserDaoImpl"> <property name="sessionFactory" ref="mySessionFactory" /> </bean> <bean id="myEventDAO" class="data.dao.impl.EventDaoImpl"> <property name="sessionFactory" ref="mySessionFactory" /> </bean> </beans> 

注意:我玩过CustomScopeConfigurer和SimpleThreadScope,但没有改变任何东西。

我有一个简单的dao-impl(只粘贴userDao – EventDao几乎是一样的 – 除了“listWith”函数:

public class UserDaoImpl implements UserDao{ private HibernateTemplate hibernateTemplate; public void setSessionFactory(SessionFactory sessionFactory) { this.hibernateTemplate = new HibernateTemplate(sessionFactory); } @SuppressWarnings("unchecked") @Override public List listUser() { return hibernateTemplate.find("from User"); } @Override public void saveUser(User user) { hibernateTemplate.saveOrUpdate(user); } @Override public List listUserWithEvent() { List users = hibernateTemplate.find("from User"); for (User user : users) { System.out.println("LIST : " + user.getName() + ":"); user.getEvents().size(); } return users; } }
public class UserDaoImpl implements UserDao{ private HibernateTemplate hibernateTemplate; public void setSessionFactory(SessionFactory sessionFactory) { this.hibernateTemplate = new HibernateTemplate(sessionFactory); } @SuppressWarnings("unchecked") @Override public List listUser() { return hibernateTemplate.find("from User"); } @Override public void saveUser(User user) { hibernateTemplate.saveOrUpdate(user); } @Override public List listUserWithEvent() { List users = hibernateTemplate.find("from User"); for (User user : users) { System.out.println("LIST : " + user.getName() + ":"); user.getEvents().size(); } return users; } } 

我得到了org.hibernate.LazyInitializationException – 无法懒惰地初始化一个angular色集合:data.model.User.events,没有会话或会话被closures在user.getEvents()。size() ;

最后但并非最不重要的是这里是我使用的testing类:

public class HibernateTest { public static void main(String[] args) { ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("basic-db.xml"); UserDao udao = (UserDao) ac.getBean("myUserDAO"); EventDao edao = (EventDao) ac.getBean("myEventDAO"); System.out.println("New user..."); User user = new User(); user.setName("test"); Event event1 = new Event(); event1.setName("Birthday1"); event1.setUser(user); Event event2 = new Event(); event2.setName("Birthday2"); event2.setUser(user); udao.saveUser(user); edao.saveEvent(event1); edao.saveEvent(event2); List users = udao.listUserWithEvent(); System.out.println("Events for users"); for (User u : users) { System.out.println(u.getId() + ":" + u.getName() + " --"); for (Event e : u.getEvents()) { System.out.println("\t" + e.getId() + ":" + e.getName()); } } ((ConfigurableApplicationContext)ac).close(); } }
public class HibernateTest { public static void main(String[] args) { ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("basic-db.xml"); UserDao udao = (UserDao) ac.getBean("myUserDAO"); EventDao edao = (EventDao) ac.getBean("myEventDAO"); System.out.println("New user..."); User user = new User(); user.setName("test"); Event event1 = new Event(); event1.setName("Birthday1"); event1.setUser(user); Event event2 = new Event(); event2.setName("Birthday2"); event2.setUser(user); udao.saveUser(user); edao.saveEvent(event1); edao.saveEvent(event2); List users = udao.listUserWithEvent(); System.out.println("Events for users"); for (User u : users) { System.out.println(u.getId() + ":" + u.getName() + " --"); for (Event e : u.getEvents()) { System.out.println("\t" + e.getId() + ":" + e.getName()); } } ((ConfigurableApplicationContext)ac).close(); } } 

这里是例外:

 1621 [main]错误org.hibernate.LazyInitializationException  - 无法延迟初始化angular色集合:data.model.User.events,没有会话或会话已closures
 org.hibernate.LazyInitializationException:无法懒惰地初始化一个angular色集合:data.model.User.events,没有会话或会话被closures
 在org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:380)
  org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationExceptionIfNotConnected(AbstractPersistentCollection.java:372)
 在org.hibernate.collection.AbstractPersistentCollection.readSize(AbstractPersistentCollection.java:119)
 在org.hibernate.collection.PersistentBag.size(PersistentBag.java:248)
 在data.dao.impl.UserDaoImpl.listUserWithEvent(UserDaoImpl.java:38)
 在HibernateTest.main(HibernateTest.java:44)
线程“main”中的exceptionorg.hibernate.LazyInitializationException:未能延迟初始化angular色集合:data.model.User.events,没有会话或会话已closures
 在org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:380)
  org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationExceptionIfNotConnected(AbstractPersistentCollection.java:372)
 在org.hibernate.collection.AbstractPersistentCollection.readSize(AbstractPersistentCollection.java:119)
 在org.hibernate.collection.PersistentBag.size(PersistentBag.java:248)
 在data.dao.impl.UserDaoImpl.listUserWithEvent(UserDaoImpl.java:38)
 在HibernateTest.main(HibernateTest.java:44)

事情尝试,但没有工作:

  • 分配一个threadScope和使用beanfactory(我使用“请求”或“线程” – 没有差别注意到):
   //范围的东西
  范围threadScope = new SimpleThreadScope();
   ConfigurableListableBeanFactory beanFactory = ac.getBeanFactory();
   beanFactory.registerScope(“request”,threadScope);
   ac.refresh();
 ...
  • 通过从deo获取会话对象来设置事务:
 ...
   Transaction tx =((UserDaoImpl)udao).getSession()。beginTransaction();
   tx.begin();
   users = udao.listUserWithEvent();
 ...
  • 在listUserWithEvent()中获得一个事务
  public List listUserWithEvent(){
   SessionFactory sf = hibernateTemplate.getSessionFactory();
   Session s = sf.openSession();
   Transaction tx = s.beginTransaction();
   tx.begin();

  列出users = hibernateTemplate.find(“from User”);
   for(User user:users){
    System.out.println(“LIST:”+ user.getName()+“:”);
    。user.getEvents()大小();
   }
   tx.commit();
  返回用户;
  }

我现在真的没有想法了。 此外,使用listUser或listEvent只是工作正常。

向前一步:

感谢Thierry,我进一步了(我想)。 我创build了MyTransaction类,并在那里做了我的整个工作,从spring获得一切。 新的主要是这样的:

public static void main(String[] args) { ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("basic-db.xml"); // getting dao UserDao udao = (UserDao) ac.getBean("myUserDAO"); EventDao edao = (EventDao) ac.getBean("myEventDAO"); // gettting transaction template TransactionTemplate transactionTemplate = (TransactionTemplate) ac.getBean("transactionTemplate"); MyTransaction mt = new MyTransaction(udao, edao); transactionTemplate.execute(mt); ((ConfigurableApplicationContext)ac).close(); }
public static void main(String[] args) { ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("basic-db.xml"); // getting dao UserDao udao = (UserDao) ac.getBean("myUserDAO"); EventDao edao = (EventDao) ac.getBean("myEventDAO"); // gettting transaction template TransactionTemplate transactionTemplate = (TransactionTemplate) ac.getBean("transactionTemplate"); MyTransaction mt = new MyTransaction(udao, edao); transactionTemplate.execute(mt); ((ConfigurableApplicationContext)ac).close(); } 

不幸的是现在有一个空指针Exception @:user.getEvents()。size(); (在daoImpl)。

我知道它不应该为null(无论从控制台的输出,也不是从数据库布局)。

这里是控制台输出的更多信息(我做了一个检查user.getEvent()== null并打印“EVENT为NULL”):

新用户...
hibernate:插入到用户(名称)值(?)
hibernate:插入到用户(名称)值(?)
 Hibernate:插入Event(name,user_id)值(?,?)
 Hibernate:插入Event(name,user_id)值(?,?)
 Hibernate:插入Event(name,user_id)值(?,?)
列出用户:
hibernate:从用户user0_中selectuser0_.id作为id0_,user0_.name作为name0_
 1:用户1
 2:用户2
列出事件:
hibernate:selectevent0_.id作为id1_,event0_.name作为name1_,event0_.user_id作为user3_1_从事件event0_
 1:生日1为1:用户1
 2:生日2为1:用户1
 3:婚礼2:用户2
hibernate:从用户user0_中selectuser0_.id作为id0_,user0_.name作为name0_
用户事件
 1:用户1  - 
 EVENT为NULL
 2:用户2  - 
 EVENT为NULL

你可以从http://www.gargan.org/code/hibernate-test1.tgz (这是一个eclipse / maven项目)

解决scheme(用于控制台应用程序

实际上有两个解决scheme – 这取决于你的环境:

对于控制台应用程序,您需要一个事务模板来捕获实际的数据库逻辑并处理事务:

public class UserGetTransaction implements TransactionCallback{ public List users; protected ApplicationContext context; public UserGetTransaction (ApplicationContext context) { this.context = context; } @Override public Boolean doInTransaction(TransactionStatus arg0) { UserDao udao = (UserDao) ac.getBean("myUserDAO"); users = udao.listUserWithEvent(); return null; } }
public class UserGetTransaction implements TransactionCallback{ public List users; protected ApplicationContext context; public UserGetTransaction (ApplicationContext context) { this.context = context; } @Override public Boolean doInTransaction(TransactionStatus arg0) { UserDao udao = (UserDao) ac.getBean("myUserDAO"); users = udao.listUserWithEvent(); return null; } } 

你可以通过调用这个来使用它:

TransactionTemplate transactionTemplate = (TransactionTemplate) context.getBean("transactionTemplate"); UserGetTransaction mt = new UserGetTransaction(context); transactionTemplate.execute(mt);
TransactionTemplate transactionTemplate = (TransactionTemplate) context.getBean("transactionTemplate"); UserGetTransaction mt = new UserGetTransaction(context); transactionTemplate.execute(mt); 

为了这个工作,你需要为spring定义模板类(即在你的basic-db.xml中):

 <bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate"> <property name="transactionManager" ref="transactionManager"/> </bean> 

另一个(可能)解决scheme

谢谢andi

  PlatformTransactionManager transactionManager = (PlatformTransactionManager) applicationContext.getBean("transactionManager"); DefaultTransactionAttribute transactionAttribute = new DefaultTransactionAttribute(TransactionDefinition.PROPAGATION_REQUIRED); transactionAttribute.setIsolationLevel(TransactionDefinition.ISOLATION_SERIALIZABLE); TransactionStatus status = transactionManager.getTransaction(transactionAttribute); boolean success = false; try { new UserDataAccessCode().execute(); success = true; } finally { if (success) { transactionManager.commit(status); } else { transactionManager.rollback(status); } } 

解决scheme(用于servlets)

Servlets并不是什么大问题。 当你有一个servlet时,你可以简单地在函数的开始处启动并绑定一个事务,最后再解除绑定:

 public void doGet(...) { SessionFactory sessionFactory = (SessionFactory) context.getBean("sessionFactory"); Session session = SessionFactoryUtils.getSession(sessionFactory, true); TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(session)); // Your code.... TransactionSynchronizationManager.unbindResource(sessionFactory); } 

我想你不应该使用hibernate会话事务方法,而是让spring做到这一点。

添加到你的springconf:

 <bean id="txManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager"> <property name="sessionFactory" ref="mySessionFactory" /> </bean> <bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate"> <property name="transactionManager" ref="txManager"/> </bean> 

然后我会修改你的testing方法来使用spring事务模板:

 public static void main(String[] args) { // init here (getting dao and transaction template) transactionTemplate.execute(new TransactionCallback() { @Override public Object doInTransaction(TransactionStatus status) { // do your hibernate stuff in here : call save, list method, etc } } } 

作为一个侧面说明,默认情况下@OneToMany关联是懒惰的,所以你不需要懒惰地注释它。 (@ * ToMany默认为懒惰,@ * ToOne默认为EAGER)

编辑:这里现在是从hibernate的angular度来看发生了什么:

  • 打开会话(事务启动)
  • 保存一个用户并保存在会话中(请参阅会话caching作为实体hashmap,其中的密钥是实体ID)
  • 保存一个事件并保存在会话中
  • 保存另一个事件并保存在会话中
  • …与所有保存操作相同…

  • 然后加载所有用户(“从用户”查询)

  • 那么hibernate就会看到它的session中已经有了这个对象,所以丢弃它从请求中获得的那个,并且从session中返回一个。
  • 你的会话中的用户没有初始化其事件集合,所以你得到null。

这里有一些要加强你的代码的要点:

  • 在你的模型中,当不需要集合sorting时,使用Set,而不是List来集合(私人设置事件,而不是私人列表事件)
  • 在你的模型中,键入你的集合,否则hibernate将不会获取哪个实体(private Set <Event> events)
  • 当你设置一个双向关系的一面,并且你希望在同一个事务中使用关系的mappedBy面时,设置双方。 在下一个tx之前,Hibernate将不会为你做这件事(当会话是从db状态的全新视图时)。

所以要解决上述问题,要么保存在一个事务中,而要在另一个事务中加载:

 public static void main(String[] args) { // init here (getting dao and transaction template) transactionTemplate.execute(new TransactionCallback() { @Override public Object doInTransaction(TransactionStatus status) { // save here } } transactionTemplate.execute(new TransactionCallback() { @Override public Object doInTransaction(TransactionStatus status) { // list here } } } 

或设置双方:

 ... event1.setUser(user); ... event2.setUser(user); ... user.setEvents(Arrays.asList(event1,event2)); ... 

(也别忘了解决上面的代码增强点,Set not List,集合types)

在Web应用程序的情况下,也可以在web.xml中声明一个特殊的Filter,它将执行session-per-request:

 <filter> <filter-name>openSessionInViewFilter</filter-name> <filter-class>org.springframework.orm.hibernate3.support.OpenSessionInViewFilter</filter-class> </filter> <filter-mapping> <filter-name>openSessionInViewFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> 

之后,您可以在请求期间随时延迟加载数据。

我到这里寻找关于类似问题的暗示。 我尝试了Thierry提到的解决scheme,它没有工作。 之后,我尝试了这些线,它的工作原理:

 SessionFactory sessionFactory = (SessionFactory) context.getBean("sessionFactory"); Session session = SessionFactoryUtils.getSession(sessionFactory, true); TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(session)); 

事实上我正在做的是一个批处理过程,它必须利用Spring存在的经理/服务。 在加载上下文并进行一些调用之后,我创build了一个着名的问题“未能懒惰地初始化一个集合”。 这三条线为我解决了。

问题在于你的dao使用的是一个hibernate会话,但user.getName的延迟加载(我认为这是抛出的地方)是在会话之外发生的 – 或者根本不在会话中,或者在另一个会话中。 通常情况下,我们打开一个hibernate会话之前,我们进行DAO调用,不要closures它,直到我们完成所有惰性负载。 Web请求通常被包装在一个大的会话中,所以这些问题不会发生。

通常我们已经在SessionWrapper中封装了我们的dao和懒惰的调用。 像下面这样:

 public class SessionWrapper { private SessionFactory sessionFactory; public void setSessionFactory(SessionFactory sessionFactory) { this.hibernateTemplate = new HibernateTemplate(sessionFactory); } public <T> T runLogic(Callable<T> logic) throws Exception { Session session = null; // if the session factory is already registered, don't do it again if (TransactionSynchronizationManager.getResource(sessionFactory) == null) { session = SessionFactoryUtils.getSession(sessionFactory, true); TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(session)); } try { return logic.call(); } finally { // if we didn't create the session don't unregister/release it if (session != null) { TransactionSynchronizationManager.unbindResource(sessionFactory); SessionFactoryUtils.releaseSession(session, sessionFactory); } } } } 

SessionFactory显然是注入到你的dao中的相同的SessionFactory。


在你的情况下,你应该包装整个listUserWithEvent正文在这个逻辑。 就像是:

 public List listUserWithEvent() { return sessionWrapper.runLogic(new Callable<List>() { public List call() { List users = hibernateTemplate.find("from User"); for (User user : users) { System.out.println("LIST : " + user.getName() + ":"); user.getEvents().size(); } } }); } 

您需要将SessionWrapper实例注入到您的daos中。

有趣!

我在@ Controller的@RequestMapping处理方法中遇到了同样的问题。 简单的解决scheme是将一个@Transactional注释添加到处理程序方法中,这样会话在整个方法体执行期间保持打开状态

最简单的解决scheme来实现:

在会话范围内[用@Transactional注解的API内部],请执行以下操作:

如果A有一个延迟加载的List <B>,只需调用一个确保List被加载的API即可

那是什么API?

尺寸(); List类的API。

所以所需要的是:

Logger.log(a.getBList.size());

logging大小的这个简单的调用确保它在计算列表的大小之前得到整个列表。 现在你不会得到exception!

在JBoss中,我们的工作是从Java Code Geeks的这个站点获得的解决scheme#2。

web.xml中:

  <filter> <filter-name>ConnectionFilter</filter-name> <filter-class>web.ConnectionFilter</filter-class> </filter> <filter-mapping> <filter-name>ConnectionFilter</filter-name> <url-pattern>/faces/*</url-pattern> </filter-mapping> 

ConnectionFilter:

 import java.io.IOException; import javax.annotation.Resource; import javax.servlet.*; import javax.transaction.UserTransaction; public class ConnectionFilter implements Filter { @Override public void destroy() { } @Resource private UserTransaction utx; @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { try { utx.begin(); chain.doFilter(request, response); utx.commit(); } catch (Exception e) { } } @Override public void init(FilterConfig arg0) throws ServletException { } } 

也许它也适用于Spring。