2010-06-14 18 views
38

Para un desplazamiento respuesta hasta el final de esta ...Hibernate/primavera: no se puede inicializar con pereza - no hay ninguna sesión o sesión se cerró

El problema básico es el mismo que pidió tiempo múltiple. Tengo un programa simple con dos eventos POJO y usuario, donde un usuario puede tener múltiples eventos.

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

} 

El Usuario:

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

} 

Nota: Este es un proyecto de ejemplo. I realmente quiero usar recuperación lenta aquí.

Ahora tenemos que configurar la primavera y de hibernación y tienen un simple-db.xml básica para la carga:

 

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

Nota: He jugado un poco con el CustomScopeConfigurer y SimpleThreadScope, pero eso no cambia nada.

que tienen un simple DAO-impl (sólo pegar el UserDAO - la EventDao es más o menos la misma - a excepción de la función "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; 
} 

} 
 

estoy consiguiendo el org.hibernate.LazyInitializationException - no se puede inicializar con pereza una colección de papel: data.model.User.events, no hay ninguna sesión o sesión se levanta a la línea con user.getEvents() size();

Y por último pero no menos importante aquí. es la clase de prueba que uso:

 

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

} 
 

y aquí es la excepción:

 
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) 

cosas lo intentaron, pero no funcionó:

  • asignar un threadScope y el uso de BeanFactory (he usado "solicitud" o "hilo" - no hay diferencia notado):
 
    // scope stuff 
    Scope threadScope = new SimpleThreadScope(); 
    ConfigurableListableBeanFactory beanFactory = ac.getBeanFactory(); 
    beanFactory.registerScope("request", threadScope); 
    ac.refresh(); 
... 
  • La creación de una transacción por conseguir el objeto de sesión desde el deo:
 
... 
    Transaction tx = ((UserDaoImpl)udao).getSession().beginTransaction(); 
    tx.begin(); 
    users = udao.listUserWithEvent(); 
... 
  • conseguir una transacción dentro de la 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; 
} 

estoy realmente fuera de las ideas por ahora . Además, usar listUser o listEvent funciona bien.

Paso adelante:

Gracias a Thierry me dieron un paso más (creo). Creé la clase MyTransaction y realicé todo mi trabajo allí, obteniendo todo desde la primavera.Las nuevas miradas como estos:

 

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

Por desgracia ahora hay un nulo-puntero Excepción @:. User.getEvents() size(); (en el daoImpl).

Sé que no debe ser nulo (ni desde la salida en la consola ni desde el diseño de db).

Aquí está la salida de la consola para obtener más información (hice un cheque por user.getEvent() == null e impreso "evento es 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 

, usted puede obtener el proyecto de ejemplo de http://www.gargan.org/code/hibernate-test1.tgz (que es un proyecto de Eclipse/experto)

La solución (para aplicaciones de consola)

realidad, hay dos soluciones para este problema - en función de su entorno:

Para una aplicación de consola que necesita una plantilla de transacción que captura la lógica actutal db y se encarga de la transacción:

 

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

} 
 

Se puede usar esta llamando:

 

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

Para para que funcione, debe definir la clase de plantilla para la primavera (es decir, en su db.xml-básico):

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

Otro (posible solución)

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

La solución (para los servlets)

servlets son no es un gran problema. Cuando usted tiene un servlet que simplemente puede iniciar y unirse a una transacción en el comienzo de su función y desvincularla de nuevo al final:

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

Respuesta

26

creo que no se debe utilizar los métodos transaccionales sesión de Hibernate, sino dejar que la primavera hace ese.

Agregue esto a su conf primavera:

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

y luego me gustaría modificar su método de prueba para utilizar la plantilla de transacciones de primavera:

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

como nota al margen, las asociaciones son perezosos @OneToMany de forma predeterminada, por lo que no es necesario anotarlo perezoso.(@ * ToMany son perezosos por defecto, @ * Toone están ansiosos por defecto)

EDIT: he aquí ahora lo que está sucediendo desde el punto de vista de hibernación:

  • sesión abierta (con inicio de la transacción)
  • guardar un usuario y mantenerlo en la sesión (ver el caché de sesión como un hashMap entidad donde la clave es el identificador de la entidad)
  • salvar a un evento y mantenerlo en la sesión
  • salvar a otro evento y mantenerlo en el sesión
  • ...

    mismo con todas las operaciones de salvar ...

  • continuación, cargar todos los usuarios (la consulta "de los usuarios")

  • en ese punto de hibernación ver que tiene ya el objeto en su periodo de sesiones, así que descarte el que obtuvo de la solicitud y devuelva el de la sesión.
  • su usuario en la sesión no tiene su colección de eventos inicializada, por lo que obtiene nulo.
  • ...

Éstos son algunos puntos a mejorar su código:

  • en su modelo, cuando no se necesita ordenar colección, utilice Set, no lista para sus colecciones (Set eventos privados Lista de eventos, no privados)
  • en su modelo, tipo de sus colecciones, de otro modo de hibernación no se qué entidad a buscar (privado Conjunto <Evento> eventos)
  • cuando se establece un lado de una bidire relación cional, y desea utilizar el lado mappedBy de la relación en la misma transacción, establezca ambos lados. Hibernate no lo hará por usted antes del próximo tx (cuando la sesión es una nueva vista desde el estado db).

Así que para abordar el punto anterior, o bien hacer la guardar en una transacción, y la carga en otra:

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

o conjunto ambos lados:

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

(También hacer no se olvide de abordar los puntos de mejora del código anterior, Establecer no la lista, la recopilación de tipos)

+1

Intenté su solución, y me deshice del problema Lazy - desafortunadamente, no carga ningún evento en absoluto: user.getEvents() Produce un puntero nulo (aunque puedo ver en el db y desde el primero iteración de que el usuario tiene eventos asociados). ¿Es posible que hibernateTemplate.find doe snot resuelva las dependencias? – Niko

+0

Cargué mi proyecto de prueba con su sugerencia ya en su lugar. tal vez puedas descubrir qué está pasando. – Niko

3

El problema es que su dao está utilizando una sesión de hibernación, pero la l azy carga de user.getName (supongo que es donde se lanza) está sucediendo fuera de esa sesión, ya sea en una sesión o en otra sesión. Normalmente abrimos una sesión de hibernación antes de hacemos llamadas DAO y no la cerramos hasta que hayamos terminado con todas las cargas perezosas. Las solicitudes web generalmente se envuelven en una gran sesión para que estos problemas no sucedan.

Normalmente hemos envuelto nuestras llamadas dao y perezosa en una SessionWrapper. Algo así como lo siguiente:

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

Obviamente la SessionFactory la misma SessionFactory que se inyecta en el DAO.


En su caso, usted debe envolver todo el cuerpo listUserWithEvent en esta lógica. Algo como:

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

Tendrá que inyectar la instancia de SessionWrapper en su daos.

+0

Lamentablemente, realmente no consigo que esto funcione. ¿Quiere decir que DAOImpl extiende el SessionWrapper? ¿Y quién (en mi ejemplo) llamaría runLogic y cuál sería el invocable? Tal vez sea más claro cuando extienda el ejemplo y lo adapte al código de muestra que proporcioné. Además, mi pregunta es específica para no web, porque quiero usar el mismo modelo/daos que uso en la web también para aplicaciones independientes (nuestros rastreadores de datos). – Niko

+0

Lo siento, fue confuso. He agregado una sección con un ejemplo de runLogic. No deberías necesitar cambiar tu dao en absoluto. Se debe inyectar con la misma SessionFactory inyectada en SessionWrapper. El SessionWrapper luego se inyecta en el daos. – Gray

+0

Eso es lo que hace la plantilla de transacción de primavera. (solo falta Typing: /) – Thierry

7

Llegué aquí buscando una pista sobre un problema similar. Probé la solución mencionada por Thierry y no funcionó. Después de eso traté estas líneas y funcionó:

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

hecho lo que estoy haciendo es un proceso por lotes que se deben aprovechar los gerentes existings primavera/servicios. Después de cargar el contexto y hacer algunas invocaciones, fundé el famoso número "Falló en inicializar perezadamente una colección". Esas 3 líneas lo resolvieron para mí.

+0

¡Solucionado! Aunque mi archivo xml decía así que tuve que hacer getBean ("wfSessionFactory") en su lugar. También probablemente podría usar una llamada "unbindResource" al final (ver las respuestas al final de la pregunta) – rogerdpack

9

En caso de aplicación web, también es posible declarar un filtro especial en web.xml, que va a hacer la sesión-por-petición:

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

Después de eso puede LazyLoad sus datos en cualquier momento durante el solicitud.

+0

en mi aplicación este filtro también fue declarado, pero la excepción es throw. –

2

¡Interesante!

Tuve el mismo problema en el @ controlador @RequestMapping método de controlador. La solución más sencilla era añadir una anotación de @Transactional al método de controlador de modo que la sesión se mantiene abierta durante toda la duración de la ejecución de un cuerpo de método

1

solución más fácil de implementar:

En el marco de la sesión [dentro de la API anotado con @Transactional], haga lo siguiente:

si a tenía una lista <B> que se carga con pereza, sólo tiene que llamar a una API que hace que la lista se carga

¿Qué es esa API?

tamaño(); API de la clase List.

Así que todo lo que se necesita es:

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

Esta simple llamada de registrar el tamaño asegura que obtenga toda la lista antes de calcular el tamaño de la lista. ¡Ahora no obtendrás la excepción!

+2

Esto no funcionó para mí. – abbas

0

Lo que funcionó para nosotros en JBoss fue la solución n. ° 2 tomada desde 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 { } 
} 

tal vez sería trabajar con la primavera también.

Cuestiones relacionadas