2010-06-14 6 views
38

Для ответа прокрутите вниз до конца этого ...Hibernate/Spring: не удалось инициализировать лениво - не сеанс или сеанс был закрыт

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

} 

Примечание: Это пример проекта. I действительно хотите использовать Lazy выборка здесь.

Теперь нам нужно настроить весну и спящим режимом и имеет простой базовый-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-осущ (только вставив 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() размер();

и последнее, но не в последнюю очередь здесь. является классом испытаний, который я использую:

 

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(); 
... 
  • Настройка транзакции путем получения объекта сеанса из дезодо:
 
... 
    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 и выполняю всю свою работу, получая все от весны.Новые основные выглядит следующим образом:

 

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() размер(); (в daoImpl).

Я знаю, что он не должен быть нулевым (ни с выхода в консоли, ни с макета 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 (это проект с затмением/Maven)

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

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

Для консольного приложения вам нужен шаблон транзакций, который улавливает логику 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); 
 

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

ответ

26

Я думаю, что вы не должны использовать Hibernate сессии транзакционные методы, но пусть весна сделать что.

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

<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 ленива по умолчанию @ * Тун является EAGER по умолчанию)

EDIT: здесь теперь, что происходит с спящем точки зрения:

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

  • затем загрузить все пользователи (далее «от пользователей» запросов)

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

Вот некоторые моменты для повышения код:

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

+1

Я пробовал ваше решение, и я избавился от проблемы Lazy - к сожалению, он не загружает никаких событий вообще: user.getEvents() Производит нулевой указатель (хотя я вижу в db и с первого итерация, связанная с тем, что у пользователя есть связанные события). Возможно ли, что hibernateTemplate.find doe snot разрешает зависимости? – Niko

+0

Я загрузил свой тестовый проект с вашим предложением уже на месте. возможно, вы можете узнать, что происходит. – Niko

3

Проблема в том, что ваше dao использует один спящий режим ses но ленивая загрузка 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.

+0

К сожалению, я действительно не понимаю, как это работает. Вы имеете в виду, что вы DAOImpl расширяете SessionWrapper? И кто (в моем примере) назвал бы runLogic и каково было бы Callable? Возможно, его cleaerer, когда вы расширяете пример и адаптируете его к образцу кода, который я предоставил. Кроме того, мой вопрос касается не-веб, потому что я хочу использовать ту же модель/daos, которую я использую в Интернете, также для автономных приложений (наши сканеры данных). – Niko

+0

Извините, что это сбивает с толку. Я добавил раздел с примером runLogic. Вам не нужно вообще менять свой dao. Он должен быть введен тем же SessionFactory, который вводится в SessionWrapper. Затем SessionWrapper вводится в daos. – Gray

+0

Вот что делает шаблон весенней транзакции. (это просто отсутствует. Typing: /) – Thierry

7

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

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

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

+0

Исправлено! Хотя мой XML-файл сказал , поэтому мне пришлось делать getBean ("wfSessionFactory"). Кроме того, он мог бы, вероятно, использовать вызов «unbindResource» там в конце (см. Ответы в конце вопроса) – rogerdpack

9

В случае веб-приложения, также можно объявить специальный фильтр в 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> 

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

+0

в моем приложении этот фильтр также был объявлен, но исключение - это throw. –

2

Интересно!

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

1

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

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

если а был список <B>, который лениво загружен, просто вызвать API, который позволяет убедиться, что список загружается

Что это за API?

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

Таким образом, все, что нужно, это:

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

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

+2

Это не сработало для меня. – abbas

0

Что работало для нас в JBoss, было решение № 2, взятое из this site at 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 { } 
} 

Может быть, это будет работать с весной тоже.

Смежные вопросы

 Смежные вопросы