Будьте в спящем режиме/Spring: отказавший для ленивой инициализации - никакая сессия или сессия не были закрыты

Поскольку ответ прокручивает вниз в конец этого...

Основная проблема совпадает с, спросили несколько время. У меня есть простая программа с двумя Событиями POJOs и Пользователем - где у пользователя может быть несколько событий.

@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 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 getEvents() { return events; }
 public void setEvents(List events) { this.events = events; }

}

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

Теперь мы должны настроить пружину и быть в спящем режиме и иметь простой basic-db.xml для загрузки:






 
  
  
  
  
  
 

 
  
   
    
     
    
   
  
 

 
  
  
   
    data.model.User
    data.model.Event
   
  
  
   
    org.hibernate.dialect.MySQLDialect
    true
    create
   
  
  

 

 
  
 

 
  
 



Примечание: Я играл вокруг с CustomScopeConfigurer и SimpleThreadScope, но это ничего не изменило.

У меня есть простой дао-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] 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 (я использовал "запрос" или "поток" - никакое замеченное различие):
  // 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 просто хорошо работает.

Шаг вперед:

Благодаря Thierry я получил один шаг вперед (я думаю). Я создал класс MyTransaction, и сделайте мою целую работу там, получив все с пружины. Новые основные взгляды как это:


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

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

Вот консольный вывод для получения дополнительной информации (я сделал проверку на user.getEvent () == пустой указатель и распечатал "СОБЫТИЕ, является ПУСТЫМ"):

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 (это - проект затмения/знатока),

Решение (для консольных приложений)

Существует на самом деле два решения для этой проблемы - в зависимости от Вашей среды:

Для консольного приложения Вам нужен шаблон транзакции, который получает actutal логику дб и заботится о транзакции:


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


    

Другое (возможное) решение

спасибо 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);
}

38
задан Niko 2 January 2016 в 11:31
поделиться

3 ответа

Я думаю, вам не следует использовать транзакционные методы сессий 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)

EDIT: вот что происходит с точки зрения hibernate:

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

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

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

Вот некоторые моменты для улучшения вашего кода:

  • в вашей модели, когда упорядочивание коллекции не требуется, используйте Set, а не List для ваших коллекций (частные события Set, а не частные события List)
  • в вашей модели, введите ваши коллекции, иначе hibernate не сможет определить, какую сущность извлекать (частные события Set)
  • когда вы устанавливаете одну сторону двунаправленного отношения, и вы хотите использовать 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, типизация коллекций)

.
26
ответ дан 27 November 2019 в 03:50
поделиться

Проблема в том, что ваш дао использует один сеанс гибернации но ленивая загрузка user.getName (я предполагаю, что это то место, где он выбрасывает) происходит вне этого сеанса - либо не в сеансе вообще, либо в другом сеансе. Обычно мы открываем сеанс гибернации до того, как делаем вызовы DAO, и не закрываем его, пока не закончим со всеми отложенными загрузками. Веб-запросы обычно заключаются в большой сеанс, поэтому этих проблем не возникает.

Обычно мы упаковываем наши 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.

3
ответ дан 27 November 2019 в 03:50
поделиться

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

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

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

7
ответ дан 27 November 2019 в 03:50
поделиться
Другие вопросы по тегам:

Похожие вопросы: