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。