Hibernate / Spring: не удалось лениво инициализировать - ни один сеанс или сеанс не был закрыт - PullRequest
38 голосов
/ 15 июня 2010

Для ответа прокрутите вниз до конца этого ...

Основная проблема та же, что и при многократном запросе.У меня есть простая программа с двумя 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; }

}

Примечание. Это пример проекта.Я действительно хочу использовать здесь Ленивое извлечение.

Теперь нам нужно настроить 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»)"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;
 }

}

Я получаю исключение 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 просто отлично работает.

Шаг вперед:

Благодаря Тьерри я сделал еще один шаг (я думаю).Я создал класс 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 ();(в даоимпл).

Я знаю, что он не должен быть нулевым (ни из вывода в консоли, ни из разметки БД).

Вот вывод консоли для получения дополнительной информации (я проверил user.getEvent () == null и напечатал «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

Вы можете получитьпример проекта из 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);

Чтобы это работало, вам нужно определить класс шаблона для весны (т. Е. В вашем 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);
      }
    }

Решение (для сервлетов)

Сервлеты не являются большой проблемой.Когда у вас есть сервлет, вы можете просто запустить и связать транзакцию в начале вашей функции и снова отсоединить ее в конце:

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 ]

26 голосов
/ 15 июня 2010

Я думаю, вам не следует использовать транзакционные методы сеанса гибернации, но пусть Spring сделает это.

Добавьте это к своему весеннему конфу:

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

и тогда я бы изменил ваш метод тестирования, чтобы использовать шаблон транзакции весны:

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)

РЕДАКТИРОВАТЬ: вот теперь, что происходит с точки зрения гибернации:

  • открытый сеанс (с началом транзакции)
  • сохранить пользователя и сохранить его в сеансе (см. Кэш сеанса как хэш-карту сущности, где ключом является идентификатор сущности)
  • сохранить событие и сохранить его в сеансе
  • сохранить другое событие и сохранить его в сеансе
  • ... то же самое со всеми операциями сохранения ...

  • , затем загрузить всех пользователей (запрос "from Users")

  • в этот момент hibernate видит, что у него уже есть объект в своем сеансе, поэтому отбросьте тот, который он получил из запроса, и верните тот из сеанса.
  • ваш пользователь в сеансе не инициализировал свою коллекцию событий, поэтому вы получаете null.
  • ...

Вот несколько моментов, которые улучшат ваш код:

  • в вашей модели, когда упорядочение коллекций не требуется, используйте Set, а не List для своих коллекций (частные события Set, а не частные события List)
  • в вашей модели введите свои коллекции, в противном случае в hibernate не будет, какой объект выбрать (закрытый набор событий)
  • когда вы задаете одну сторону двунаправленного отношения и хотите использовать сторону mappedBy отношения в одной транзакции, установите обе стороны. Hibernate не сделает этого за вас до следующей передачи (когда сеанс представляет собой новый вид из состояния БД).

Таким образом, для решения вышеприведенного пункта, либо сохраните в одной транзакции, а загрузите в другой:

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, набор набора)

9 голосов
/ 18 ноября 2011

В случае веб-приложения также можно объявить специальный фильтр в web.xml, который будет выполнять сеанс для запроса:

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

После этого вы можете лениво загружать ваши данные в любое время во время запроса.

7 голосов
/ 05 августа 2010

Я попал сюда в поисках подсказки о подобной проблеме.Я попробовал решение, упомянутое Тьерри, и оно не сработало.После этого я попробовал эти строки, и это сработало:

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

На самом деле я выполняю пакетный процесс, который должен использовать менеджеров / сервисы Spring.После загрузки контекста и выполнения некоторых вызовов я основал известную проблему «не удалось лениво инициализировать коллекцию».Эти 3 строки решили это за меня.

3 голосов
/ 15 июня 2010

Проблема заключается в том, что ваш дао использует один сеанс гибернации, но ленивая загрузка user.getName (я предполагаю, что именно там он генерирует) происходит вне этого сеанса - либо вообще не в сеансе, либо в другом сеансе. , Обычно мы открываем сеанс гибернации до , мы делаем вызовы 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 - это тот же 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.

2 голосов
/ 21 мая 2012

Интересно!

У меня была такая же проблема в методе @RequestMapping @ Controller. Простым решением было добавить аннотацию @Transactional в метод-обработчик, чтобы сеанс оставался открытым в течение всей продолжительности выполнения тела метода

1 голос
/ 15 ноября 2012

Самое простое решение для реализации:

В рамках сеанса [внутри API с аннотацией @Transactional] выполните следующее:

если у A был список , который загружается лениво, просто вызовите API, который обеспечивает загрузку списка

Что это за API?

размер (); API класса List.

Итак, все, что нужно, это:

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

Этот простой вызов регистрации размера гарантирует, что он получит весь список перед вычислением размера списка. Теперь вы не получите исключение!

0 голосов
/ 20 апреля 2015

В JBoss нам помогло решение №2, взятое с этого сайта в Java Code Geeks .

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>

ConnectionFilter:

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.

...