For an answer scroll down to the end of this...
基本问题与多次询问相同 . 我有一个简单的程序,有两个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; }
}
注意:这是一个示例项目 . 我 really 想在这里使用Lazy抓取 .
现在我们需要配置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 - 无法懒惰地初始化一个角色集合:data.model.User.events,没有会话或会话在user.getEvents()的行中关闭.size();
最后但并非最不重要的是我使用的Test类:
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] 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)
事情尝试但没有奏效:
- 分配一个threadScope并使用beanfactory(我使用"request"或"thread" - 没有注意到差异):
// scope stuff
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工作正常 .
Step forward:
感谢蒂埃里,我更进了一步(我想) . 我创建了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();
}
不幸的是现在有一个空指针Exception @:user.getEvents() . size(); (在daoImpl中) .
我知道它不应该是null(既不是来自控制台的输出也不是来自db布局) .
这是控制台输出以获取更多信息(我检查了user.getEvent()== null并打印了“EVENT为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
您可以从http://www.gargan.org/code/hibernate-test1.tgz获取示例项目(这是一个eclipse / maven项目)
The solution (for console applications)
这个问题实际上有两个解决方案 - 取决于您的环境:
对于控制台应用程序,您需要一个事务模板,该模板捕获actutal db逻辑并负责事务:
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>
Another (possible) solution
谢谢你
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);
}
}
The solution (for servlets)
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);
}
7 回答
我来到这里寻找有关类似问题的提示 . 我尝试了Thierry提到的解决方案,但它没有用 . 之后,我尝试了这些线,它工作:
事实上,我所做的是一个必须利用Spring存在经理/服务的批处理 . 在加载上下文并进行一些调用之后,我创建了一个着名的问题“未能懒惰地初始化集合” . 那3行为我解决了 .
我认为你不应该使用hibernate会话事务方法,但让spring做到这一点 .
将此添加到您的spring conf:
然后我会修改你的测试方法以使用spring transaction模板:
作为旁注,默认情况下@OneToMany关联是惰性的,因此您不需要将其注释为惰性 . (@ * ToMany默认为LAZY,默认情况下@ * ToOne为EAGER)
编辑:现在从hibernate的角度来看现在发生了什么:
开放会话(有交易开始)
保存用户并将其保留在会话中(将会话缓存视为实体哈希映射,其中密钥是实体ID)
保存一个事件并将其保留在会话中
保存另一个事件并将其保留在会话中
...与所有保存操作相同...
然后加载所有用户(“来自用户”查询)
那时hibernate看到它已经在会话中已经有了对象,所以丢弃它从请求中得到的对象并从会话中返回一个 .
会话中的用户没有初始化其事件集合,因此您将获得null .
......
以下是一些增强代码的要点:
在模型中
,当不需要集合排序时,使用Set,而不是List的集合(私有Set事件,而不是私有List事件)
在模型中
,键入你的集合,否则hibernate将不会获取哪个实体(私有Set <Event>事件)
当您设置双向关系的一侧,并且您希望在同一事务中使用关系的mappedBy端时,请设置两侧 . 在下一个tx之前,Hibernate不会为你做这件事(当会话是来自db状态的全新视图时) .
因此,为了解决上述问题,要么在一个事务中进行保存,要么在另一个事务中进行加载:
或两者兼而有之双方:
(另外不要忘记解决上面的代码增强点,Set not List,集合打字)
对于Web应用程序,还可以在web.xml中声明一个特殊的Filter,它将执行session-per-request:
之后,您可以在请求期间随时延迟加载数据 .
问题是你的dao正在使用一个hibernate会话,但是user.getName的延迟加载(我假设它就是它抛出的地方)正在该会话之外发生 - 要么根本不在会话中,要么在另一个会话中 . 通常我们在进行DAO调用之前打开一个hibernate会话,并且在完成所有延迟加载之前不要关闭它 . Web请求通常包含在大型会话中,因此不会发生这些问题 .
通常我们在SessionWrapper中包含了dao和lazy调用 . 类似于以下内容:
显然,SessionFactory与你的dao注入了相同的SessionFactory .
在您的情况下,您应该将整个listUserWithEvent主体包装在此逻辑中 . 就像是:
您需要将SessionWrapper实例注入到您的daos中 .
有趣!
我在@Controller的@RequestMapping处理程序方法中遇到了同样的问题 . 简单的解决方案是在处理程序方法中添加@Transactional注释,以便在方法体执行的整个持续时间内保持会话打开
Easiest solution to implement:
在会话范围内[在使用@Transactional注释的API内],执行以下操作:
如果A有一个懒惰加载的List <B>,只需调用一个API,确保加载List
What's that API ?
尺寸(); List类的API .
So all that's needed is:
Logger.log(a.getBList.size());
记录大小的这个简单调用确保它在计算列表大小之前获得整个列表 . 现在你不会得到例外!
在JBoss中对我们有用的是从#1262613_获取的解决方案#2 .
web.xml中:
ConnectionFilter:
也许它也适用于Spring .