要找到答案,请向下滚动到本文末尾...
基本问题与多次询问相同。我有一个带有两个 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。
现在我们需要配置 spring 和 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>
Note: I played around with the CustomScopeConfigurer and SimpleThreadScope, but that didnt change anything.
I have a simple dao-impl (only pasting the userDao - the EventDao is pretty much the same - except with out the "listWith" function:
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;
}
}
I am getting the org.hibernate.LazyInitializationException - failed to lazily initialize a collection of role: data.model.User.events, no session or session was closed at the line with user.getEvents().size();
And last but not least here is the Test class I use:
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();
}
}
and here is the Exception:
1621 [main] ERROR org.hibernate.LazyInitializationException - failed to lazily initialize a collection of role: data.model.User.events, no session or session was closed org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: data.model.User.events, no session or session was closed at org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:380) at org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationExceptionIfNotConnected(AbstractPersistentCollection.java:372) at org.hibernate.collection.AbstractPersistentCollection.readSize(AbstractPersistentCollection.java:119) at org.hibernate.collection.PersistentBag.size(PersistentBag.java:248) at data.dao.impl.UserDaoImpl.listUserWithEvent(UserDaoImpl.java:38) at HibernateTest.main(HibernateTest.java:44) Exception in thread "main" org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: data.model.User.events, no session or session was closed at org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:380) at org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationExceptionIfNotConnected(AbstractPersistentCollection.java:372) at org.hibernate.collection.AbstractPersistentCollection.readSize(AbstractPersistentCollection.java:119) at org.hibernate.collection.PersistentBag.size(PersistentBag.java:248) at data.dao.impl.UserDaoImpl.listUserWithEvent(UserDaoImpl.java:38) at HibernateTest.main(HibernateTest.java:44)
Things tried but did not work:
- assign a threadScope and using beanfactory (I used "request" or "thread" - no difference noticed):
// scope stuff Scope threadScope = new SimpleThreadScope(); ConfigurableListableBeanFactory beanFactory = ac.getBeanFactory(); beanFactory.registerScope("request", threadScope); ac.refresh(); ...
- Setting up a transaction by getting the session object from the deo:
... Transaction tx = ((UserDaoImpl)udao).getSession().beginTransaction(); tx.begin(); users = udao.listUserWithEvent(); ...
- getting a transaction within the listUserWithEvent()
public List listUserWithEvent() { SessionFactory sf = hibernateTemplate.getSessionFactory(); Session s = sf.openSession(); Transaction tx = s.beginTransaction(); tx.begin(); List users = hibernateTemplate.find("from User"); for (User user : users) { System.out.println("LIST : " + user.getName() + ":"); user.getEvents().size(); } tx.commit(); return users; }
I am really out of ideas by now. Also, using the listUser or listEvent just work fine.
Step forward:
Thanks to Thierry I got one step further (I think). I created the MyTransaction class and do my whole work in there, getting everything from spring. The new main looks like this:
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();
}
Unfortunately now there is a null-pointer Exception @: user.getEvents().size(); (in the daoImpl).
I know that it should not be null (neither from the output in the console nor from the db layout).
Here is the console output for more information (I did a check for user.getEvent() == null and printed "EVENT is NULL"):
New user... Hibernate: insert into User (name) values (?) Hibernate: insert into User (name) values (?) Hibernate: insert into Event (name, user_id) values (?, ?) Hibernate: insert into Event (name, user_id) values (?, ?) Hibernate: insert into Event (name, user_id) values (?, ?) List users: Hibernate: select user0_.id as id0_, user0_.name as name0_ from User user0_ 1:User1 2:User2 List events: Hibernate: select event0_.id as id1_, event0_.name as name1_, event0_.user_id as user3_1_ from Event event0_ 1:Birthday1 for 1:User1 2:Birthday2 for 1:User1 3:Wedding for 2:User2 Hibernate: select user0_.id as id0_, user0_.name as name0_ from User user0_ Events for users 1:User1 -- EVENT is NULL 2:User2 -- EVENT is NULL
You can get the sample project from http://www.gargan.org/code/hibernate-test1.tgz (it's an eclipse/maven project)
The solution (for console applications)
There are actually two solutions for this problem - depending on your environment:
For a console application you need a transaction template which captures the actutal db logic and takes care of the transaction:
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;
}
}
You can use this by calling:
TransactionTemplate transactionTemplate = (TransactionTemplate) context.getBean("transactionTemplate");
UserGetTransaction mt = new UserGetTransaction(context);
transactionTemplate.execute(mt);
In order for this to work you need to define the template class for spring (ie. in your basic-db.xml):
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="transactionManager"/>
</bean>
另一种(可能的)解决方案
谢谢安迪
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);
}
}
解决方案(针对 servlet)
Servlet 并不是什么大问题。当你有一个 servlet 时,你可以简单地在函数的开头启动和绑定(bind)一个事务,然后在最后再次取消绑定(bind):
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 session 事务方法,而是让 spring 来做。
将此添加到您的 spring conf:
<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>
然后我会修改你的测试方法以使用 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 默认是 LAZY,@*ToOne 默认是 EAGER)
编辑:从 hibernate 的角度来看,这是现在发生的事情:
- 打开 session (事务开始)
- 保存用户并将其保留在 session 中(将 session 缓存视为实体 HashMap ,其中键是实体 ID)
- 保存事件并将其保留在 session 中
- 保存另一个事件并将其保留在 session 中
...与所有保存操作相同...
然后加载所有用户(“来自用户”查询)
- 此时 hibernate 发现它已经在其 session 中拥有该对象,因此丢弃从请求中获得的对象并从 session 中返回对象。
- 您在 session 中的用户未初始化其事件集合,因此您将获得 null。
- ...
以下是增强代码的几点:
- 在您的模型中,当不需要集合排序时,为您的集合使用 Set,而不是 List(私有(private) Set 事件,而不是私有(private) List 事件)
- 在您的模型中,输入您的集合,否则 hibernate 将不会获取哪个实体(私有(private) Set
事件) - 当您设置双向关系的一侧,并且希望在同一事务中使用关系的 mappedBy 一侧时,请设置两侧。 Hibernate 不会在下一次 tx 之前为您执行此操作(当 session 是来自 db 状态的新 View 时)。
所以要解决上述问题,要么在一个事务中保存,然后在另一个事务中加载:
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,collection typing)
关于java - hibernate/ Spring : failed to lazily initialize - no session or session was closed,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3041259/