VRaptor 2.6.0 + Hibernate 3.5.4 (recebendo um LazyInitializationException)

Pessoal, já peço desculpas por entrar num assunto que já deve ter gerado muita discussão, mas gostaria de receber algumas orientações!

VRaptor versão 2.6.0
Hibernate versão 3.5.4
Apache Tomcat versão 6.0.28
JVM versão 1.6.0_21-ea-b05
Servidor Debian 2.6.26-2-686-bigmem

Tenho uma aplicação que esta em produção a 4 anos e praticamente 90% anotada com @OneToMany(FetchType.LAZY), exceto é claro algumas anotações @OneToOne (que são EAGER por padrão), porém existe apenas uma única classe chamada Projeto que esta com uma coleção anotada com FetchType.EAGER, isto por causa de um Transiente que esta sendo chamado dentro de uma regra “validade LÓGICA”, já que ao colocar como FetchType.LAZY eu recebo a mensagem de erro:

ERROR [LazyInitializationException] failed to lazily initialize a collection of role: br.com.faespsenar.sicp.model.Projeto.periodosDeRealizacao, no session or session was closed.

Justamente no momento da chamada da propriedade transiente:

at br.com.faespsenar.sicp.model.Projeto.getDataPrevistaTemp(Projeto.java:823)
at br.com.faespsenar.sicp.logic.ProjetoAprovadoLogic.validateAtualizaProjeto(ProjetoAprovadoLogic.java:425)

OBS: A finalidade do getDataPrevistaTemp é obter a PRIMEIRA data de realização de um Projeto.

Eu já tentei até segui as orientações do nosso amigo Camilo Lopes através do seu blog, com as dicas do pessoal do Hibernate sobre Open Session View.

Porém ao aplicar o filtro (HibernateSessionRequestFilter), eu percebi uma queda de performance em todo o sistema e também não resolveu o problema de LazyInitializationException ao chamar o transiente getDataPrevistaTemp().

NOTA: Eu particularmente não gosto dos Transientes, porém temos alguns relatórios através do JasperReport que necessitam deles.

Então vamos lá…

Segue abaixo a classe Projeto.java:

@Entity
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
@Proxy(lazy = true)
public class Projeto implements Comparable<Projeto>, java.lang.Cloneable {

    @Id
    @SequenceGenerator(name = "seq_projeto", sequenceName = "seq_projeto_id_seq", initialValue = 1, allocationSize = 1)
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "seq_projeto")
    private Long                             id;
	...
	...
	...
    @OneToMany(mappedBy = "projeto", cascade = {CascadeType.REMOVE}, fetch = FetchType.EAGER)
    @BatchSize(size=50)
    @OrderBy("localRealizacao")
    private Set<LocalDeRealizacao> locaisDeRealizacao;
	...
	...
	...
    @Transient
    private Date dataPrevistaTemp;

    /**
     * Retorna a data inicial do Periodo de Realização
     * @return dataPrevistaTemp
     */
    public Date getDataPrevistaTemp() {
        if (this.getPeriodosDeRealizacao() != null && !this.getPeriodosDeRealizacao().isEmpty())
            dataPrevistaTemp = this.getPeriodosDeRealizacao().iterator().next().getDataPrevista();
        else
            dataPrevistaTemp = null;

        return dataPrevistaTemp;
    }
    
    // Demais Getters e Setters omitidos...

}

Segue abaixo a lógica ProjetoAprovadoLogic.java:

@Component("projetoaprovado")
@InterceptedBy({ DaoInterceptor.class, FactoryInterceptor.class, LoginInterceptor.class, DownloadInterceptor.class, NoCacheInterceptor.class })
public class ProjetoAprovadoLogic {

    private DaoFactory   daoFactory;
    private Usuario      usuarioConectado;
    private ClientOutput clientOutput;
    ...
    ...
    ...
    @Out @Parameter(create=true)
    private Projeto    projeto;
    ...
    ...
    ...
    public ProjetoAprovadoLogic(DaoFactory daoFactory, Usuario usuarioConectado, ClientOutput clientOutput) {
        this.daoFactory = daoFactory;
        this.usuarioConectado = usuarioConectado;
        this.clientOutput = clientOutput;
    }
    ...
    ...
    ...
    public void validateAtualizaProjeto(ValidationErrors errors) {
        Projeto p = this.daoFactory.getProjetoDao().localizarUnico(this.projeto, "id");
        ...
        ...
        ...
        // Nesta chamada ocorre o erro!
        Date dtPrevista = p.getDataPrevistaTemp();
        ...
        ...
        ...
	}
    ...
    ...
    ...
}	

Segue abaixo o meu interceptador DaoInterceptor.java:

public class DaoInterceptor implements Interceptor {

    @Out(key="br.com.faespsenar.sicp.dao.DaoFactory")
    private DaoFactory daoFactory;

    public void intercept(LogicFlow flow) throws LogicException, ViewException {

        daoFactory = DaoFactory.getNewDaoFactory();

        // só necessário para as lógicas "antigas" que usam @In
        flow.getLogicRequest().getRequestContext().setAttribute("daoFactory", daoFactory);

        LogicRequest logicRequest = flow.getLogicRequest();

        try {
            if (daoFactory == null || !daoFactory.isOpen())
                logicRequest.getRequest().getRequestDispatcher("login.expirou.logic").forward(logicRequest.getRequest(), logicRequest.getResponse());
            else
                flow.execute();
        } catch (ServletException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (daoFactory != null) {
                if (daoFactory.hasTransaction()) {
                    daoFactory.rollback();
                }
                if (daoFactory.isOpen()) {
                    daoFactory.close();
                }
            }
            daoFactory = null;
        }
    }
}

Esta aplicação funciona muito bem e esta bem performática, porém existem partes do projeto que não há a necessidade de saber TODAS as datas de realização do Projeto, mas isso ocorre por causa do FetchType.EAGER e é neste ponto que eu gostaria de obter uma maior performance com o uso do FetchType.LAZY.

Alguma orientação ou sugestão ?

Obrigado a todos…

no dao interceptor, em volta do flow.execute() vc pode abrir e fechar a Session do hibernate:

daoFactory.openSession();
flow.execute();
daoFactory.closeSession();

e dentro do daoFactory não abrir mais sessões em lugar nenhum…

vc pode tb abrir e commitar ou dar um rollback numa transação

Opa Lucas!
Então eu tenho praticamente o necessário para um “Open Session in View”.
Vou verificar todo o código para saber se estou fazendo aberturas e fechamento de Sessão do Hibernate.

Muito obrigado pela dica e vou verificar o resultado desta mudança e volto a comentar aqui.

Valeu…

----- EDITADO -----

Pois é Lucas, justamente o que mais temia…

Não funcionou!

No DaoInterceptor tenho a seguinte chamada:

public class DaoInterceptor implements Interceptor {  
  
    @Out(key="br.com.faespsenar.sicp.dao.DaoFactory")  
    private DaoFactory daoFactory;  
  
    public void intercept(LogicFlow flow) throws LogicException, ViewException {  
  
        daoFactory = DaoFactory.getNewDaoFactory();  
        ...
        ...
        ...
    }
...
...
...
}

A implementação feita no getNewDaoFactory() já abre a sessão e verifiquei no código que ela é chamada somente no DaoInterceptor, sendo assim eu acabo tendo a abertura da sessão antes de renderizar qualquer “lógica” e seu fechamento logo após…

Em TODAS as minhas lógicas só faço a chamada do getDaoFactory() que verifica se já existe um DAO na ThreadLocal e a utiliza, porém se eu modificar getNewDaoFactory ao ponto de usar o DAO que esta na ThreadLocal, acabo tendo uma lista enorme de erro. A impressão é de que não poso utilizar ThreadLocal dentro de Um Interceptor.

public class DaoFactory extends DaoFactoryUtil {

    private static ThreadLocal<DaoFactory> factories = new ThreadLocal<DaoFactory>();

    public static DaoFactory getNewDaoFactory() {
        // Atenção: Não colocar qualquer código que feche a seção.
        DaoFactory daoFactory = new DaoFactory(HibernateUtil.getSession()); 
        factories.set(daoFactory);
        return daoFactory;
    }

    public static DaoFactory getDaoFactory() {
        if (factories.get() == null) {
            return getNewDaoFactory();
        }
        DaoFactory daoFactory = (DaoFactory) factories.get(); 
        return daoFactory;
    }

    private DaoFactory(Session session) {
        super(session);
    }

public class HibernateUtil {

    private static SessionFactory sessionFactory;
    ...
    ...
    ...
    public static Session getSession() {
        return sessionFactory.openSession();
    }
    ...
    ...
    ...
}

Se houver qualquer outra dica eu irei tentar implementar, caso contrário vou desistir e deixar como esta, pois a dor de cabeça será muito maior ao tentar modificar um código que foi escrito para os primórdios do vRaptor.

Desde já agradeço toda atenção e ajuda.