一尘不染

Hibernate/Spring:无法延迟初始化-没有会话或会话已关闭

hibernate

要获得答案,请向下滚动到此内容的结尾…

基本问题与多次询问相同。我有一个带有两个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抓取。

现在我们需要配置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]错误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)
 线程“主”中的异常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)

事情尝试了但没有用:

  • 分配一个threadScope并使用beanfactory(我使用了“ request”或“ thread”-没有区别):

    //作用域的东西
    范围threadScope = new SimpleThreadScope();
    ConfigurableListableBeanFactory beanFactory = ac.getBeanFactory();
    beanFactory.registerScope(“ request”,threadScope);
    ac.refresh();

  • 通过从Deo获取会话对象来设置事务:


    事务tx =((UserDaoImpl)udao).getSession()。beginTransaction();
    tx.begin();
    用户= udao.listUserWithEvent();

  • 在listUserWithEvent()中获取交易

    公共列表listUserWithEvent(){
    SessionFactory sf = hibernateTemplate.getSessionFactory();
    会话s = sf.openSession();
    交易tx = s.beginTransaction();
    tx.begin();

    列出用户= hibernateTemplate.find(“来自用户”);
    对于(用户用户:用户){
    System.out.println(“ LIST:” + user.getName()+“:”);
    user.getEvents()。size();
    }
    tx.commit();
    回头客;
    }

到目前为止,我真的没有主意。另外,使用listUser或listEvent也可以正常工作。

向前一步:

多亏蒂埃里(Thierry),我才迈出了一步。我创建了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();
 }

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

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

这是控制台的更多信息(我检查过user.getEvent()== null并显示“ EVENT is NULL”):

新用户...
hibernate:插入用户(名称)值(?)
hibernate:插入用户(名称)值(?)
hibernate:插入事件(名称,用户ID)值(?,?)
hibernate:插入事件(名称,用户ID)值(?,?)
hibernate:插入事件(名称,用户ID)值(?,?)
列出用户:
hibernate:从用户user0_中选择user0_.id作为id0_,选择user0_.name作为name0_
1:用户1
2:用户2
列出事件:
hibernate:从事件event0_中选择event0_.id作为id1_,event0_.name作为name1_,event0_.user_id作为user3_1_
1:User1的1:Birthday1
2:1的User1的生日2
3:为2:User2结婚
hibernate:从用户user0_中选择user0_.id作为id0_,选择user0_.name作为name0_
用户活动
1:用户1-
EVENT为NULL
2:用户2-
EVENT为NULL

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

解决方案(适用于控制台应用程序)

对于此问题,实际上有两种解决方案-取决于您的环境:

对于控制台应用程序,您需要一个事务模板来捕获实际的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>

另一个(可能的)解决方案

谢谢安迪

    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);
}

阅读 189

收藏
2020-06-20

共1个答案

一尘不染

我认为您不应该使用hibernate会话事务方法,而让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的角度来看,这就是现在正在发生的事情:

  • 开放会话(从事务开始)
  • 保存用户并将其保留在会话中(请参阅会话缓存作为实体哈希图,其中键是实体ID)
  • 保存事件并将其保留在会话中
  • 保存另一个事件并将其保留在会话中
  • …与所有保存操作相同…

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

  • 那时,hibernate看到它在其会话中已经有该对象,因此丢弃从请求中获取的对象,然后从会话中返回该对象。

  • 您会话中的用户未初始化其事件集合,因此您将获得null。

以下是增强代码的一些要点:

  • 在模型中,当不需要集合排序时,对集合使用Set而不是List(私有Set事件,而不是私有List事件)
  • 在模型中,键入您的集合,否则hibernate将不会获取哪个实体(私有Set 事件)
  • 当您设置双向关系的一侧,并且希望在同一事务中使用该关系的mappedBy一侧时,请设置双方。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));
...

(也别忘了解决上面的代码增强点,“不设置列表,集合键入”)

2020-06-20