VRaptor 3.3.1 + Spring 3.06 (Session was already closed) [RESOLVIDO]

Olá Pessoal,

Tenho uma aplicação rodando com VRaptor 3.3.1, onde as Transações são gerenciadas pelo Spring.

Entro no formulario para cadastrar um produto novo, então o form envia os dados para o metodo adiciona() da controller, e depois de adicionar ele redireciona para o metodo lista().
(Aplicação da apostila FJ-28 da Caelum)

No momento em que ele vai redirecionar par ao metodo lista() da controller, ele da o WARN abaixo:

20:46:01,863  WARN [CommonAnnotationBeanPostProcessor] Invocation of destroy method failed on bean with name 'criadorDeSession': org.hibernate.SessionException: Session was already closed

Alguem saberia me dizer como evitar isto ? ou se esse WARN deve ser ignorado ???

Abaixo, arquivos do projeto:

applicationContext.xml

<?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:tx="http://www.springframework.org/schema/tx"
	xsi:schemaLocation="
			http://www.springframework.org/schema/beans
			http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
			http://www.springframework.org/schema/tx
			http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">
	
	<tx:annotation-driven />
	
	<bean id="transactionManager"
		class="org.springframework.orm.hibernate3.HibernateTransactionManager">
		<property name="sessionFactory" ref="sessionFactory" />
	</bean>
	
	<bean id="sessionFactory"
		class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
		<property name="configLocation">
			<value>classpath:/hibernate.cfg.xml</value>
		</property>
	</bean>
	
</beans>

CriadorDeSession.java

@Component
public class CriadorDeSession implements ComponentFactory<Session> {

    private final SessionFactory factory;
    private final Proxifier proxifier;
    private Session sessionDoSpring;
    private Session session;

    public CriadorDeSession(SessionFactory factory, Proxifier proxifier) {
        this.factory = factory;
        this.proxifier = proxifier;
    }
    @PostConstruct
    public void abre() {
        this.session = proxifier.proxify(Session.class, new MethodInvocation<Session>() {
            public Object intercept(Session proxy, Method method, Object[] args, SuperMethod superMethod) {
                sessionDoSpring = SessionFactoryUtils.doGetSession(factory, true);
                return new Mirror().on(sessionDoSpring).invoke().method(method).withArgs(args);
            }
        });
    }
    public Session getInstance() {
        return this.session;
    }
    @PreDestroy
    public void fecha() {
        //this.session.close(); - desta forma não aparece o WARN no console, mas as sessoes vao ficando abertas até estourar o pool de conexoes.
        if(this.sessionDoSpring != null) {
            this.sessionDoSpring.close();
        }
    }
}

ProdutosController.java

@Resource
public class ProdutosController {

    private ProdutoDAO produtoDAO;
    private Result result;
    private Validator validator;

    @Autowired
    public void setProdutoDAO(ProdutoDAO produtoDAO) {
        this.produtoDAO = produtoDAO;
    }

    @Autowired
    public void setResult(Result result) {
        this.result = result;
    }

    @Autowired
    public void setValidator(Validator validator) {
        this.validator = validator;
    }

    
    @Post
    @Path("/produtos")
    @Transactional
    public void adiciona(Produto produto) {
        // Hibernate Validator
        validator.validate(produto);
        validator.onErrorUsePageOf(this).formulario();
        produtoDAO.salva(produto);
        produtoDAO.flush(); // Coloquei o flush() da Session aqui p/ acompanhar se o WARN era antes ou depois do insert.
        result.redirectTo(this).lista(); // No modo debug, o WARN ocorre exatamente nesta linha. O Insert já foi executado.
    }

    @Put
    @Path("/produtos/{produto.id}")
    @Transactional
    public void altera(Produto produto) {
        // Hibernate Validator
        validator.validate(produto);
        validator.onErrorUsePageOf(this).edita(produto.getId());
        produtoDAO.altera(produto);
        result.redirectTo(this).lista();
    }

    @Delete
    @Path("/produtos/{id}")
    @Transactional
    public void remove(Long id) {
        Produto produto = produtoDAO.carrega(id);
        produtoDAO.remove(produto);
        result.redirectTo(ProdutosController.class).lista();
    }

    @Get
    @Path("/produtos")
    public List<Produto> lista() {
        return produtoDAO.listaTudo();
    }

    @Get
    @Path("/produtos/novo")
    public void formulario() {
        // Direciona para WEB-INF\jsp\produtos\formulario.jsp
    }
}

tenta isso:

if(this.sessionDoSpring != null && sessionDoSpring.isOpen()) {  
    sessionDoSpring.close();
}

Fala Lucas,

Eu até já verificava se a session do Spring era != null antes de tentar fechá-la. Ai adicionei mais a verificação do isOpen() que você sugeriu, mas infelizmente o WARN ainda permanece.

Mas, nessa altura do campeonato, cuja as sessões estão se comportando direitinho… me pergunto… não seria só ignorar esse WARN ???

Ou isso vai acabar refletindo em algo no gerenciamento das minhas transações ???

Att,

bom, fechar algo que já está fechado não deve dar mto problema

o ideal mesmo é vc registrar o open session in view filter do spring, e nunca fechar a session (o spring faz isso pra vc)

Vlw Lucas,

vou estudar sobre… e fazer as devidas implementações.

:wink:

eu tive um problema parecido a mto tempo. No meu caso acontecia porque os mesmos objetos que eu recuperava da base de dados ( entities que mapeavam as tabelas ) estavam sendo enviados para a camada de view. Exemplo:

Colocar um objeto na request ou session, sendo que este objeto é um objeto persistente e possui atributos com tipo de carregamento lazy.

Eu resolvi o problema não deixando mais que os objetos persistidos saissem da camada de persistencia. Quando existia a necessidade de um objeto trafegar entre outras camadas, eu criava um clone desse objeto sem nenhuma dependência de carga do hibernate. Carregava os atributos que eu iria precisar e pronto.

Como o Lucas falou, existe também um filter do spring que resolve este problema chamado org.springframework.orm.hibernate3.support.OpenSessionInViewFilter … ele também pode resolver o problema.

[]'s

Valeu pelas dicas pessoal,

registrei no meu web.xml o HibernateCustomProvider, dei fim no applicationContext.xml, persistence.xml, etc… deixei os métodos anotados com @Transaction aonde eu precisava de transações e vualáaaa… tudo OK :wink:

<context-param>
    	<param-name>br.com.caelum.vraptor.provider</param-name>
    	<param-value>br.com.caelum.vraptor.util.hibernate.HibernateCustomProvider</param-value>
</context-param>

Grande Abraco a todos.