Boa tarde a todos...
Estou com um problema um tanto chato, acho que muitos dos que já trabalharam com Hibernate e J2EE já devem ter se deparado com ele. Estou completando 7 dias hoje, em debugs incansáveis e pesquisas pra resolver esse problema. Apesar de ser um problema que envolve Hibernate, postei na seção de Web pois tem muito mais haver. Primeiramente, alguns trechos de código:
Struts.xml, trecho com Interceptor.<struts>
<package name="BaseActs" extends="struts-default">
(...)
<interceptors>
<interceptor name="SessaoHibernate" class="net.m21xx.integra.helpers.HibernateSessionInterceptor" />
<interceptor name="Seguranca" class="net.m21xx.integra.helpers.SecurityInterceptor" />
<interceptor-stack name="IntegraStack">
<interceptor-ref name="Seguranca" />
<interceptor-ref name="defaultStack" />
</interceptor-stack>
</interceptors>
<filter>
<display-name>HibernateSessionFilter</display-name>
<filter-name>HibernateSessionFilter</filter-name>
<filter-class>net.m21xx.integra.helpers.HibernateSessionFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>HibernateSessionFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<hibernate-configuration>
<session-factory>
<property name="hibernate.connection.driver_class">org.gjt.mm.mysql.Driver</property>
<property name="hibernate.connection.url">jdbc:mysql://localhost:3306/integra</property>
(...)
<property name="hibernate.connection.pool_size">2</property>
<property name="hibernate.connection.autocommit">false</property>
<property name="hibernate.connection.release_mode">on_close</property>
<property name="hibernate.default_schema">integra</property>
<property name="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</property>
<property name="hibernate.transaction.factory_class">org.hibernate.transaction.JTATransactionFactory</property>
<property name="hibernate.transaction.manager_lookup_class">org.hibernate.transaction.JBossTransactionManagerLookup</property>
<property name="hibernate.transaction.auto_close_session">false</property>
<property name="hibernate.transaction.flush_before_completion">false</property>
<property name="hibernate.cache.provider_class">org.hibernate.cache.NoCacheProvider</property>
<property name="hibernate.current_session_context_class">jta</property>
<property name="hibernate.show_sql">false</property>
<mapping resource="mapeamento.hbm.xml"/>
</session-factory>
</hibernate-configuration>
<class name="GruposVO" table="grupos">
<id name="id">
<generator class="native" />
</id>
<property name="nome" />
<property name="descricao" />
<bag name="usuarios" lazy="true" table="usuarios_grupos">
<key column="grupoId" />
<many-to-many column="usuarioId" class="UsuariosVO" />
</bag>
</class>
<class name="UsuariosVO" table="usuarios">
<id name="id">
<generator class="native" />
</id>
<property name="nome" column="usuario" />
<property name="senha" />
<property name="habilitado" />
<property name="dataCriacao" column="tsDataCriacao" />
<property name="ultimoLogin" column="tsUltimoLogin" />
<bag name="grupos" lazy="true" table="usuarios_grupos">
<key column="usuarioId" />
<many-to-many column="grupoId" class="GruposVO" />
</bag>
<bag name="sessoes" lazy="true" inverse="true">
<key column="usuarioId" />
<one-to-many class="SessoesVO" />
</bag>
</class>
public class ActHome extends BaseAction
{
public String execute() throws Exception
{
// Executa código comum ás páginas
super.execute();
// TODO preencher com algo - página inicial do sistema! (blog?)
setUsuario(base_usuario);
return SUCCESS;
}
/* Teste */
private UsuariosVO usuario;
public UsuariosVO getUsuario()
{
return usuario;
}
public void setUsuario(UsuariosVO pUsuario)
{
usuario = pUsuario;
}
}
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<div class="tc_content">
<ul>
<s:iterator value="usuario.grupos">
<li><s:property value="nome" /></li>
</s:iterator>
</ul>
</div>
Session session = HibernateUtil.getSessionFactory().openSession();
System.out.println(session.hashCode());
ArrayList<UsuariosVO> lItems = (ArrayList<UsuariosVO>) session.createQuery("from UsuariosVO").list();
UsuariosVO lUsuario = lItems.get(0);
System.out.println(lUsuario.getNome());
System.out.println(session.hashCode());
ArrayList<GruposVO> lItems2 = (ArrayList<GruposVO>) session.createQuery("from GruposVO").list();
GruposVO lGrupo = lItems2.get(0);
System.out.println(lGrupo.getNome());
System.out.println(session.hashCode());
Collection<GruposVO> lGrupos = lUsuario.getGrupos();
System.out.println(lGrupos.size());
Iterator<GruposVO> lIterator = lGrupos.iterator();
while (lIterator.hasNext())
{
GruposVO lGrupoIt = lIterator.next();
System.out.println(lGrupoIt.getNome());
}
System.out.println(session.hashCode());
Collection<UsuariosVO> lUsuarios = lGrupo.getUsuarios();
System.out.println(lUsuarios.size());
Iterator<UsuariosVO> lIterator2 = lUsuarios.iterator();
while (lIterator2.hasNext())
{
UsuariosVO lUsuarioIt = lIterator2.next();
System.out.println(lUsuarioIt.getNome());
}
System.out.println(session.hashCode());
session.close();
Ok, explicando agora. Estou codificando um sistema, que precisa de autenticação e autorização. Durante o desenvolvimento surgiu a necessidade de testar o acesso aos dados e se o mapeamento estava correto. Então, coloquei um atributo "usuario" alimientado com base_usuario (o código super...() carrega com um Objeto contendo detalhes do usuário, descritos no mapeamento). No JSP que faz uso dessa Action, coloquei um Iterator e ai o problema se instalou.
root causeorg.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: net.m21xx.integra.domain.models.UsuariosVO.grupos, no session or session was closed
De acordo com minhas pesquisas, configurações e o manual do Hibernate, toda sessão tem um tempo de vida, definido em configuração, que por padrão fecha a sessão após o fim da transação, dispensando a necessidade do programador fechar a sessão (e/ou a transação) após o acesso aos dados no seu EJB (ou Action, em caso de acesso direto). O erro que foi disparado foi causado por uma parte do Objeto persistente que não foi inicializada por estar em modo LAZY (faz-se o acesso on-demand, e não ao chamar o load()).
Acontece que este é um mapeamento *ToMany (many-to-many nesse caso), que, por questões de massa de dados e de performance, deve permanescer em LAZY (estas chamadas de usuários e grupos são feitas mais de uma vez a cada request).
Nas buscas por uma solução, eu encontrei algo que poderia me ajudar. Se funcionasse. Se chama "Open Session In View". É uma "técnica" que mantém a sessão Hibernate ativa antes mesmo da Action entrar em execução, passando pela renderização dos JSP necessários para montar a View (e assim dando a chance de o Hibernate fazer uso da sessão para preencher os dados LAZY do objeto).
Acontece que não funciona. Tentei também, usar MBeans do JBoss para prover o acesso ao Hibernate e ao Banco de dados via JNDI. Sem sucesso para o problema. Segue o MBean:<server>
<mbean code="org.hibernate.jmx.HibernateService" name="jboss.jca:service=HibernateFactory,name=HibernateFactory">
<!-- Required services -->
<depends>jboss.jca:service=LocalTxCM,name=IntegraDS</depends>
<!-- Bind the Hibernate service to JNDI -->
<attribute name="JndiName">java:/hibernate/SessionFactory</attribute>
<!-- Datasource settings -->
<attribute name="Datasource">java:IntegraDS</attribute>
<attribute name="Dialect">org.hibernate.dialect.MySQL5Dialect</attribute>
<!-- Transaction integration -->
<attribute name="TransactionStrategy">org.hibernate.transaction.JTATransactionFactory</attribute>
<attribute name="TransactionManagerLookupStrategy">org.hibernate.transaction.JBossTransactionManagerLookup</attribute>
<attribute name="FlushBeforeCompletionEnabled">false</attribute>
<attribute name="AutoCloseSessionEnabled">false</attribute>
<!-- Fetching options -->
<attribute name="MaximumFetchDepth">5</attribute>
<!-- Second-level caching -->
<attribute name="SecondLevelCacheEnabled">false</attribute>
<attribute name="CacheProviderClass">org.hibernate.cache.NoCacheProvider</attribute>
<attribute name="QueryCacheEnabled">false</attribute>
<!-- Logging -->
<attribute name="ShowSqlEnabled">true</attribute>
<!-- Mapping files -->
<attribute name="MapResources">mapeamento.hbm.xml</attribute>
</mbean>
</server>
O MBean me trouxe outro problema. Nem todas as configurações que fazemos no XML podem ser feitas no MBean, por não existirem atributos mapeados (ou explicitados no manual) para devido fim.
Como podem ver nas amostras de código acima, usei uma ServletFilter e um StrutsInterceptor para tentar sanar o problema. Porém, ao chamar o hashCode, observei no Log do JBoss que a sessão mudou (coloquei org.hibernate em modo TRACE e vi a mudança, forçando a destruição da sessão), portanto, os dados que eram para ser usados pela chamada LAZY já não estariam mais disponíveis. Podem notar também que as configurações estão usando JTA no contexto JBoss. Passei a usar JTA na esperança da UserTransaction me ajudar, mas a sessão (ou a transação, vai saber...) está morrendo antes de chegar na View!
Seguem alguns dos links que usei na minha pesquisa. Diga-se de passagem, também consultei o manual.
http://www.javalobby.org/java/forums/t20533.html
http://www.jroller.com/kbaum/entry/orm_lazy_initialization_with_dao
http://www.guj.com.br/posts/list/15/133184.java#719514
https://www.hibernate.org/43.html
Estes são apenas alguns, meu histórico é grande e não dura muito tempo (apago todo dia). Eu acredito que seja algo com o JTA/UserTransaction... usei todas as configurações que pude no Hibernate (deixei tudo no manual, até flush() to usando hehe). Alguma idéia?
