答案请往下滑至文章末尾...
基本问题已经被多次提出。我有一个简单的程序,包含两个POJOs:Event和User - 其中用户可以拥有多个事件。
@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>
注意:我已经尝试使用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; } }
我遇到了org.hibernate.LazyInitializationException异常 - 在user.getEvents().size()这一行出现了“无法延迟初始化角色集合:data.model.User.events,没有会话或会话已关闭”。
最后,这是我使用的测试类:
公共类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("新用户..."); User user = new User(); user.setName("测试");
Event event1 = new Event(); event1.setName("生日1"); event1.setUser(user);
Event event2 = new Event(); event2.setName("生日2"); event2.setUser(user);
udao.saveUser(user); edao.saveEvent(event1); edao.saveEvent(event2);
List users = udao.listUserWithEvent(); System.out.println("用户的事件"); 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 - 无法懒加载角色集合:data.model.User.events,没有会话或会话已关闭 org.hibernate.LazyInitializationException: 无法懒加载角色集合:data.model.User.events,没有会话或会话已关闭 在 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" org.hibernate.LazyInitializationException: 无法懒加载角色集合:data.model.User.events,没有会话或会话已关闭 在 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)尝试但未成功的方法:
- 分配一个线程范围并使用beanfactory(我使用了“request”或“thread”-没有注意到任何区别):
// 范围相关 Scope 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();
List users = hibernateTemplate.find("from User"); for (User user : users) { System.out.println("LIST : " + user.getName() + ":"); user.getEvents().size(); } tx.commit(); return users; }
我现在真的没有更多的想法了。此外,使用listUser或listEvent也可以正常工作。
向前迈进:
多亏了Thierry,我觉得我又向前迈进了一步。我创建了MyTransaction类,并在其中完成了所有工作,从spring获取了所有内容。新的main方法如下:
public static void main(String[] args) {
ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("basic-db.xml");
// 获取dao
UserDao udao = (UserDao) ac.getBean("myUserDAO");
EventDao edao = (EventDao) ac.getBean("myEventDAO");
// 获取事务模板
TransactionTemplate transactionTemplate = (TransactionTemplate) ac.getBean("transactionTemplate");
MyTransaction mt = new MyTransaction(udao, edao);
transactionTemplate.execute(mt);
((ConfigurableApplicationContext)ac).close();
}
不幸的是,现在在daoImpl中出现了空指针异常:user.getEvents().size();。
我知道它不应该为空(既不是从控制台输出,也不是从数据库布局)。以下是更多信息的控制台输出(我检查了user.getEvent() == null并打印了“EVENT is NULL”):
新用户... Hibernate: 插入到User (name)值(?) Hibernate: 插入到User (name)值(?) Hibernate: 插入到Event (name, user_id)值(?, ?) Hibernate: 插入到Event (name, user_id)值(?, ?) Hibernate: 插入到Event (name, user_id)值(?, ?) 用户列表: Hibernate: 选择user0_.id作为id0_,user0_.name作为name0_从用户user0_ 1:User1 2:User2 事件列表: Hibernate: 选择event0_.id作为id1_,event0_.name作为name1_,event0_.user_id作为user3_1_从事件event0_ 1:1的生日1:User1 2:1的生日2:User1 3:2的婚礼:User2 Hibernate: 选择user0_.id作为id0_,user0_.name作为name0_从用户user0_ 用户的事件 1:User1-- 事件为空 2:User2-- 事件为空您可以从http://www.gargan.org/code/hibernate-test1.tgz获取示例项目(这是一个eclipse/maven项目)
解决方案(适用于控制台应用程序)
实际上,对于这个问题有两种解决方案 - 取决于您的环境:
对于控制台应用程序,您需要一个事务模板来捕获实际的数据库逻辑并处理事务:
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);
为了使其工作,您需要在Spring中定义模板类(例如在basic-db.xml中):
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="transactionManager"/>
</bean>
另一种(可能的)解决方案
谢谢 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);
}
}
解决方案(适用于Servlet)
Servlet并不是什么大问题。当你有一个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);
}