recursive practices manytoone lazyinitializationexception lazy initialize fetchtype false example best java hibernate spring lazy-loading

java - practices - Hibernate/Spring: no se inicializó con pereza: no se cerró ninguna sesión o sesión



lazy hibernate example (7)

Para una respuesta, desplácese hasta el final de este ...

El problema básico es el mismo que se le preguntó varias veces. 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 muestra. Realmente quiero usar Lazy Fetching aquí.

Ahora necesitamos configurar spring e hibernate y tener un basic-db.xml simple para cargar:

<?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: Jugué con CustomScopeConfigurer y SimpleThreadScope, pero eso no cambió nada.

Tengo un simple dao-impl (solo pegando el usuario, el EventDao es más o menos el mismo), excepto sin 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; } }

Obtengo la org.hibernate.LazyInitializationException: no se pudo inicializar de forma lenta una colección de roles: data.model.User.events, no se cerró ninguna sesión o sesión en la línea con user.getEvents (). Size () ;

Y por último, pero no menos importante, aquí está 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í está 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)

Las cosas intentaron pero no funcionaron:

  • asignar un threadScope y usar beanfactory (utilicé "solicitud" o "hilo" - no se notó ninguna diferencia):

// scope stuff Scope threadScope = new SimpleThreadScope(); ConfigurableListableBeanFactory beanFactory = ac.getBeanFactory(); beanFactory.registerScope("request", threadScope); ac.refresh(); ...

  • Configurando una transacción obteniendo el objeto de sesión del deo:

... Transaction tx = ((UserDaoImpl)udao).getSession().beginTransaction(); tx.begin(); users = udao.listUserWithEvent(); ...

  • obtener una transacción dentro de 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; }

A estas alturas ya no tengo ideas. Además, usar listUser o listEvent funciona bien.

Un paso adelante:

Gracias a Thierry, di un paso más (creo). Creé la clase MyTransaction y realicé todo mi trabajo allí, obteniendo todo desde la primavera. El nuevo principal se ve así:

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

Lamentablemente, ahora existe un Exception @: user.getEvents (). Size () de null-pointer; (en el daoImpl).

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

Aquí está la salida de la consola para más información (hice una comprobación para user.getEvent () == null e imprimí "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

Puede obtener el proyecto de muestra desde http://www.gargan.org/code/hibernate-test1.tgz (es un proyecto eclipse / maven)

La solución (para aplicaciones de consola)

En realidad, hay dos soluciones para este problema, dependiendo de su entorno:

Para una aplicación de consola necesita una plantilla de transacción que capture la lógica de db actutal y se encargue 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; } }

Puede usar esto llamando a:

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

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

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

Otra (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 servlets)

Los servlets no son un gran problema. Cuando tenga un servlet, puede simplemente iniciar y vincular una transacción al comienzo de su función y desvincularla nuevamente 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); }


¡Interesante!

Tuve el mismo problema en el método del controlador @RequestMapping de @ Controller. La solución simple fue agregar una anotación @Transactional al método del manejador para que la sesión permanezca abierta durante toda la duración de la ejecución del cuerpo del método


Creo que no deberías usar los métodos transaccionales de la sesión de hibernación, pero deja que la primavera lo haga.

Agregue esto a su conf de 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 modificaría su método de prueba para usar la plantilla de transacción 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 de @OneToMany son flojas por defecto, por lo que no es necesario anotarlas como flojas. (@ * ToMany son LAZY por defecto, @ * ToOne es EAGER por defecto)

EDITAR: aquí está lo que está sucediendo desde el punto de vista de Hibernate:

  • sesión abierta (con inicio de transacción)
  • guardar un usuario y mantenerlo en la sesión (ver el caché de la sesión como una entidad hashmap donde la clave es la identificación de la entidad)
  • guarda un evento y guárdalo en la sesión
  • guardar otro evento y mantenerlo en la sesión
  • ... lo mismo con todas las operaciones de salvar ...

  • luego cargue todos los usuarios (la consulta "de los usuarios")

  • en ese punto hibernate vea que ya tiene el objeto en su sesión, por lo tanto, 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.
  • ...

Aquí hay algunos puntos para mejorar su código:

  • en su modelo, cuando no se necesita el orden de la colección, use Establecer, no Lista para sus colecciones (eventos de Conjunto privados, eventos de Lista no privados)
  • en su modelo, escriba sus colecciones; de lo contrario, la hibernación no determinará qué entidad buscar (eventos privados de <Evento>)
  • cuando establece un lado de una relación bidireccional 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).

Entonces, para abordar el punto anterior, haga el guardado 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 establecer ambos lados:

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

(Además, no te olvides de abordar los puntos de mejora de código anteriores, Set not List, typeing de colección)


El problema es que tu dao está usando una sesión de hibernación, pero la carga lenta de user.getName (supongo que es donde tira) está sucediendo fuera de esa sesión, ya sea en una sesión o en otra. Normalmente abrimos una sesión de hibernación antes de hacer 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.

Por lo general, hemos completado nuestras llamadas dao y perezosa en una SessionWrapper. Algo 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, SessionFactory es la misma SessionFactory que se inyectó en su dao.

En su caso, 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.


En el caso de la aplicación web, también es posible declarar un filtro especial en web.xml, que hará una sesión por solicitud:

<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 actualizar sus datos en cualquier momento durante la solicitud.


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

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

De hecho, lo que estoy haciendo es un proceso por lotes que debe aprovechar los administradores / servicios de Spring existings. 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í.


Lo que funcionó para nosotros en JBoss fue la solución # 2 tomada de este sitio en 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 funcionaría también con Spring.


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

Dentro del alcance de la sesión [dentro de la API anotada con @Transactional], haga lo siguiente:

si A tenía una Lista <B> que está cargada de forma perezosa, simplemente llame a una API que se asegura de que la Lista esté cargada

¿Cuál es esa API?

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

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