【问题标题】:Hibernate/Spring: failed to lazily initialize - no session or session was closedHibernate/Spring:延迟初始化失败 - 没有会话或会话被关闭
【发布时间】:2011-03-03 17:53:49
【问题描述】:

要找到答案,请向下滚动到本文末尾...

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

注意:我使用了 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 行关闭();

最后但并非最不重要的是我使用的测试类:

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("请求", 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("列表:" + user.getName() + ":"); user.getEvents().size(); } tx.commit(); 返回用户; }

我现在真的没有想法。此外,使用 listUser 或 listEvent 也可以正常工作。

前进:

感谢蒂埃里,我更进一步(我想)。我创建了 MyTransaction 类并在那里完成了我的全部工作,从春天得到了一切。新的 main 看起来像这样:

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 中)。

我知道它不应该为空(无论是来自控制台的输出还是来自 db 布局)。

这里是更多信息的控制台输出(我检查了 user.getEvent() == null 并打印了“EVENT is NULL”):

新用户... 休眠:插入用户(名称)值(?) 休眠:插入用户(名称)值(?) Hibernate:插入事件(名称,user_id)值(?,?) Hibernate:插入事件(名称,user_id)值(?,?) Hibernate:插入事件(名称,user_id)值(?,?) 列出用户: Hibernate:从用户 user0_ 中选择 user0_.id 作为 id0_,user0_.name 作为 name0_ 1:用户1 2:用户2 列出事件: 休眠:从事件 event0_ 中选择 event0_.id 作为 id1_,event0_.name 作为 name1_,event0_.user_id 作为 user3_1_ 1:生日 1 为 1:用户 1 2:生日 2 为 1:用户 1 3:婚礼 2:用户 2 Hibernate:从用户 user0_ 中选择 user0_.id 作为 id0_,user0_.name 作为 name0_ 用户事件 1:用户1—— 事件为空 2:用户2—— 事件为空

您可以从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>

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

谢谢安迪

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

【问题讨论】:

    标签: java hibernate spring lazy-loading


    【解决方案1】:

    我认为你不应该使用休眠会话事务方法,而是让 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)

    编辑:从休眠的角度来看,这是现在发生的事情:

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

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

    • 此时 hibernate 发现它已经在其会话中拥有该对象,因此丢弃从请求中获得的对象并从会话中返回对象。
    • 您在会话中的用户没有初始化其事件集合,因此您得到 null。
    • ...

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

    • 在您的模型中,当不需要集合排序时,为您的集合使用 Set,而不是 List(私有 Set 事件,而不是私有 List 事件)
    • 在您的模型中,输入您的集合,否则休眠将不会获取哪个实体(私有 Set 事件)
    • 当您设置双向关系的一侧,并且希望在同一事务中使用关系的 mappedBy 侧时,请设置两侧。 Hibernate 不会在下一次 tx 之前为您执行此操作(当会话是来自 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,collection typing)

    【讨论】:

    • 我尝试了你的解决方案,我摆脱了 Lazy 问题 - 不幸的是,它根本不加载任何事件: user.getEvents() 产生一个空指针(尽管我可以在db 并且从用户确实具有关联事件的第一次迭代开始)。 hibernateTemplate.find 是否有可能无法解决依赖关系?
    • 我已经根据您的建议上传了我的测试项目。也许你可以找出发生了什么。
    【解决方案2】:

    如果是 Web 应用程序,也可以在 web.xml 中声明一个特殊的过滤器,它将执行 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>
    

    之后,您可以在请求期间随时延迟加载数据。

    【讨论】:

    • 在我的应用程序中也声明了这个过滤器,但抛出异常。
    【解决方案3】:

    我来这里是为了寻找有关类似问题的提示。我尝试了蒂埃里提到的解决方案,但没有奏效。之后我尝试了这些行并且它起作用了:

    SessionFactory sessionFactory = (SessionFactory) context.getBean("sessionFactory");
    Session session = SessionFactoryUtils.getSession(sessionFactory, true);
    TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(session));
    

    事实上,我正在做的是一个必须利用 Spring 现有管理器/服务的批处理过程。在加载上下文并进行一些调用后,我发现了著名的问题“未能延迟初始化集合”。这 3 行为我解决了它。

    【讨论】:

    • 修复了!虽然我的 xml 文件说 所以我不得不做 getBean("wfSessionFactory") 代替。它也可能在最后使用“unbindResource”调用(请参阅问题末尾的答案)
    【解决方案4】:

    问题是您的 dao 正在使用一个休眠会话,但 user.getName 的延迟加载(我假设这是它抛出的地方)发生在该会话之外 - 根本不在会话中或在另一个会话中.通常,我们在进行 DAO 调用之前打开一个休眠会话,并且在完成所有延迟加载之前不要关闭它。 Web 请求通常包含在一个大会话中,因此不会发生这些问题。

    通常我们将 dao 和惰性调用包装在 SessionWrapper 中。类似于以下内容:

    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 中。

    【讨论】:

    • 不幸的是,我并没有真正做到这一点。你的意思是你DAOImpl扩展了SessionWrapper?谁(在我的例子中)会调用runLogic,什么是Callable?当您扩展示例并将其调整为我提供的示例代码时,它可能会更清晰。另外,我的问题是针对非网络的,因为我想将我在网络中使用的相同模型/daos 也用于独立应用程序(我们的数据爬虫)。
    • 抱歉让您感到困惑。我添加了一个带有 runLogic 示例的部分。你根本不需要改变你的道。它应该与注入到 SessionWrapper 中的相同 SessionFactory 一起注入。然后 SessionWrapper 被注入到 daos 中。
    • 这就是spring事务模板的作用。 (只是缺少打字:/)
    • spring事务模板也开启了数据库事务吧?我们不想用我们的会话包装器来做这件事。
    • 打开数据库 tx 是什么意思?它打开到数据库的连接,就像从 sessionFactory 获取会话一样。启用事务只是将连接上的 autoCommit 标志从 on 设置为 off。
    【解决方案5】:

    有趣!

    我在@Controller 的@RequestMapping 处理程序方法中遇到了同样的问题。 简单的解决方案是在处理程序方法中添加一个@Transactional 注释,以便在方法体执行的整个过程中会话保持打开状态

    【讨论】:

      【解决方案6】:

      最容易实施的解决方案:

      在会话范围内[在使用 @Transactional 注释的 API 内],执行以下操作:

      如果 A 有一个延迟加载的 List,只需调用一个 API 以确保 List 已加载

      那个 API 是什么?

      尺寸(); List 类的 API。

      所以所有需要的是:

      Logger.log(a.getBList.size());

      这个记录大小的简单调用确保它在计算列表大小之前获取整个列表。现在你不会得到异常了!

      【讨论】:

        【解决方案7】:

        在 JBoss 中对我们有用的是来自 this site at Java Code Geeks 的解决方案 #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>
        

        连接过滤器:

        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。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2011-03-31
          • 1970-01-01
          • 2014-11-02
          • 1970-01-01
          • 1970-01-01
          • 2012-11-13
          • 2011-12-11
          • 2017-08-13
          相关资源
          最近更新 更多