Hibernate / Spring: nie udało się leniwie zainicjować-Żadna sesja ani sesja nie została zamknięta

Aby uzyskać odpowiedź, przewiń w dół do końca tego...

Podstawowy problem jest taki sam jak zadawany wielokrotnie. Mam prosty program z dwoma POJOs zdarzenia i Użytkownika-gdzie użytkownik może mieć wiele zdarzeń.

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

}

Użytkownik:

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

}

Uwaga: jest to przykładowy projekt. I naprawdę chcę użyć leniwego pobierania tutaj.

Teraz musimy skonfigurować spring i hibernate i mieć prosty basic-db.xml dla Ładowanie:


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

Uwaga: grałem z CustomScopeConfigurer i SimpleThreadScope, ale to nic nie zmieniło.

Mam prosty dao-impl (tylko wklejanie userDao - EventDao jest prawie takie samo - z wyjątkiem funkcji "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;
 }

}

Dostaję org.hibernacja.LazyInitializationException-nie udało się leniwie zainicjować kolekcji danych role:.model.Użytkownik.wydarzenia, Żadna sesja ani sesja nie została zamknięta na linii z użytkownik.getEvents ().rozmiar();

I ostatnia, ale nie mniej ważna, oto Klasa testowa, której używam:


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

}

I tu jest wyjątek:

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)

Rzeczy próbowały, ale nie działały:

    W związku z tym, że nie jest to możliwe, nie jest to konieczne, ponieważ nie jest to konieczne.]}
  // scope stuff
  Scope threadScope = new SimpleThreadScope();
  ConfigurableListableBeanFactory beanFactory = ac.getBeanFactory();
  beanFactory.registerScope("request", threadScope);
  ac.refresh();
...
    W związku z tym, że nie jest to możliwe, nie jest to możliwe.]}
...
  Transaction tx = ((UserDaoImpl)udao).getSession().beginTransaction();
  tx.begin();
  users = udao.listUserWithEvent();
...
  • uzyskanie transakcji w ramach 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;
 }
Nie mam już pomysłów. Ponadto, korzystanie z listUser lub listEvent po prostu działa dobrze.

Krok do przodu:

Dzięki Thierry ' emu dostałem się o krok dalej (chyba). Stworzyłem klasę MyTransaction i wykonuję tam całą swoją pracę, dostając wszystko od wiosny. Nowy Główny wygląda tak:


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

Niestety teraz istnieje wyjątek null-pointer@: user.getEvents ().size (); (w daoImpl).

I wiedz, że nie powinno być null (ani z wyjścia w konsoli, ani z układu db).

Oto wyjście konsoli, aby uzyskać więcej informacji (sprawdziłem użytkownika.getEvent () = = null i drukowane "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

Możesz pobrać przykładowy projekt z http://www.gargan.org/code/hibernate-test1.tgz [[72]} (it ' s an eclipse/maven project)

Rozwiązanie (dla aplikacji konsolowych)

Istnieją właściwie dwa rozwiązania dla ten problem - w zależności od środowiska:

Dla aplikacji konsolowej potrzebujesz szablonu transakcji, który przechwytuje logikę actutal db i zajmie się transakcją:


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

}

Możesz użyć tego wywołując:

 TransactionTemplate transactionTemplate = (TransactionTemplate) context.getBean("transactionTemplate");
 UserGetTransaction mt = new UserGetTransaction(context);
 transactionTemplate.execute(mt);

Aby to zadziałało, musisz zdefiniować klasę szablonu dla springa (tj. w Twoim podstawowym-db.xml):

<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
    <property name="transactionManager" ref="transactionManager"/>
</bean>

Inne (możliwe) rozwiązanie

Dzięki 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);
      }
    }

Rozwiązanie (dla servlets)

Serwlety nie są aż tak dużym problemem. Gdy masz servlet, możesz po prostu rozpocząć i powiązać transakcję na początku swojej funkcji i odłączyć ją ponownie na końcu:

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);
}
Author: Niko, 2010-06-14

7 answers

Myślę, że nie powinieneś używać metod transakcyjnych sesji hibernate, ale niech spring to zrobi.

Dodaj to do swojej wiosennej konf:

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

I wtedy zmodyfikowałbym Twoją metodę testową, aby użyć szablonu transakcji wiosennej:

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

Na marginesie, Asocjacje @OneToMany są domyślnie leniwe, więc nie musisz ich opisywać leniwie. (@*ToMany są domyślnie leniwi, @ * ToOne są domyślnie chętni)

EDIT: oto co dzieje się z punktu hibernate widok:

  • Otwarta sesja (z rozpoczęciem transakcji)
  • W przeciwieństwie do innych języków, w których nie ma dostępu do danych, nie ma dostępu do danych, które są przechowywane w pamięci podręcznej.]}
  • Zapisz Zdarzenie i zachowaj je w sesji
  • Zapisz inne zdarzenie i zachowaj je w sesji
  • ... to samo dotyczy wszystkich operacji ratowania ...

  • Następnie załaduj wszystkich użytkowników (zapytanie" od użytkowników")

  • w tym momencie hibernate zobaczyć, że ma już obiekt w jego sesji, więc odrzuć ten, który otrzymał z żądania i zwróć ten z sesji.
  • twój użytkownik w sesji nie ma zainicjalizowanej kolekcji zdarzeń, więc otrzymasz null.
  • ...

Oto kilka punktów do ulepszenia kodu:

  • w modelu, gdy zamawianie kolekcji nie jest potrzebne, użyj Set, not List dla kolekcji (prywatne zdarzenia Set, Nie prywatne zdarzenia List)
  • w swoim modelu wpisz swoje kolekcje, w przeciwnym razie hibernate nie będzie pobierał encji (private Set events)
  • gdy ustawisz jedną stronę relacji dwukierunkowej i chcesz użyć strony mappedBy relacji w tej samej transakcji, Ustaw obie strony. Hibernate nie zrobi tego za Ciebie przed nastÄ ™ pnym tx (gdy sesja jest Ĺ " wieĹźym widokiem ze stanu db).

Więc aby rozwiązać powyższy punkt, albo zapisz w jednej transakcji, a załaduj w innej:

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

Lub ustawić oba strony:

...
event1.setUser(user);
...
event2.setUser(user);
...
user.setEvents(Arrays.asList(event1,event2));
...

(nie zapomnij również zająć się powyższymi punktami ulepszania kodu, Set not List, typowanie kolekcji)

 26
Author: Thierry,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-31 17:11:31

W przypadku aplikacji webowej można również zadeklarować specjalny filtr w web.xml, który zrobi 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>

Następnie możesz załadować swoje dane w dowolnym momencie podczas żądania.

 9
Author: weekens,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-11-18 13:15:47

Dotarłem tu szukając podpowiedzi dotyczącej podobnego problemu. Wypróbowałem rozwiązanie wspomniane przez Thierry ' ego i nie zadziałało. Potem wypróbowałem te linie i zadziałało:

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

Rzeczywiście to, co robię, to proces wsadowy, który musi wykorzystać menedżerów/usługi Spring existings. Po wczytaniu kontekstu i wykonaniu kilku inwokacji założyłem słynny numer "nie udało się leniwie zainicjować kolekcji". Te 3 linie rozwiązały to dla mnie.

 7
Author: Fran Jiménez,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-08-05 16:42:17

Problem polega na tym, że Twój dao używa jednej sesji hibernacji, ale leniwego obciążenia użytkownika.getName (zakładam, że tam rzuca) dzieje się poza tą sesją - albo nie w ogóle, albo w innej sesji. Zazwyczaj otwieramy sesję hibernate przed wykonujemy połączenia DAO i nie zamykamy jej, dopóki nie skończymy z leniwymi ładunkami. Żądania internetowe są zwykle zawinięte w dużą sesję, więc problemy te nie występują.

Zazwyczaj owijamy nasze dao i leniwe rozmowy w sesji. Coś takiego:

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

Oczywiście Fabryka SessionFactory ta sama fabryka SessionFactory, która została wstrzyknięta do twojego dao.


W Twoim przypadku powinieneś zawinąć całą listUserWithEvent body w tę logikę. Coś w stylu:

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

Będziesz musiał wstrzyknąć instancję SessionWrapper do daos.

 3
Author: Gray,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-06-15 12:34:23

Ciekawe!

Miałem ten sam problem w metodzie obsługi @Controller ' s @ RequestMapping. Proste rozwiązanie polegało na dodaniu adnotacji @ Transactional do metody obsługi, tak aby sesja była otwarta przez cały czas wykonywania struktury metody

 2
Author: mhimu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-05-21 12:59:00

Najprostsze rozwiązanie do wdrożenia:

W ramach sesji [wewnątrz API z adnotacją @ Transactional] wykonaj następujące czynności:

Jeśli a ma listę, która jest leniwie załadowana, po prostu wywołaj API, które upewnia się, że lista jest załadowana

Co to za API ?

Size (); API klasy List.

Więc wystarczy:

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

To proste wywołanie logowania rozmiaru sprawia, że pobiera całą listę przed obliczeniem jej rozmiaru. Teraz nie dostaniesz wyjątku !

 1
Author: ydntn,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-11-15 12:47:51

W Jbossie zadziałało rozwiązanie #2 zaczerpnięte z tej strony w Java Code Geeks .

Www.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 { }
}
[[2]}może to zadziała również z wiosną.
 0
Author: EpicPandaForce,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-04-20 20:51:35