Hibernate, ThreadLocal e timeout

pesoal, estou com uma duvida cruel…

tenho uma aplicação webapp para gerenciamento de arrecadações.
neste, é feito o logon no sistema que espera o usuario lançar os dados.
geralmente o usuario loga e fica o dia interio, de 8h as 18h.

uso struts e hibernate.

as HttpSession estão numa boa, porem as Session do hibernate dão timeout.

usei ThreadLocal como recomendações do pesoal do proprio Hibernate
http://www.hibernate.org/42.html

uso a mesma session do hibernate para todos os usuarios, o que deixa o sistema muito mais rápido, porem a session está dando timeout e quando acesso objetos lazy, dá uma exceção.

alguem sabe o que fazer???

Você usa a mesma session para todos os usuários???

Mas por quê?

O custo para se criar sessions é bastante leve, compensa mais você criar uma session para cada thead do sistema, ou melhor, para cada cliente ou usuário se assim preferir!

Utilizando uma session para todo mundo, como vc faz para controlar as tranzações, já que está todo mundo tranzando… oops (tranzacionando…) no mesmo lugar???

Se vc está usando a implementação de TheadLocal sugeriada pelo pessoal do Hibernate, provavelmente vc está usando uma conexão para cada thead do sistema!

Abraços!
Thiago

Não faça isso, você está correndo o risco de estar usando dados inválidos. Use uma session por transação. Nunca vá além disso.

está é a classe

public class Persistencia {
	
	public static List select(String condicao) throws Exception{
		if (condicao == null || condicao.equals(""))
			throw new NullPointerException("SQL está nula!");
		
		Session session = Persistencia.currentSession();
		Query qry = session.createQuery(condicao);
		List list = qry.list();
		return list;
	}
	
	public static void insert(Object obj) throws Exception{
		if (obj == null)
			throw new NullPointerException();
		
		Session session = Persistencia.currentSession();
		Transaction tx = session.beginTransaction();
		try{
			session.save(obj);
			session.flush();
			tx.commit();
		}catch(Exception e){
			tx.rollback();
			throw e;
		}
	}

	public static void update(Object obj) throws Exception{
		if (obj == null)
			throw new NullPointerException();
		
		Session session = Persistencia.currentSession();
		Transaction tx = session.beginTransaction();
		try{
			session.update(obj);
			session.flush();
			tx.commit();
		}catch(Exception e){
			tx.rollback();
			throw e;
		}
	}

	public static void delete(Object obj) throws Exception {
		if (obj == null)
			throw new NullPointerException();
		
		Session session = Persistencia.currentSession();
		Transaction tx = session.beginTransaction();
		try{
			session.delete(obj);
			session.flush();
			tx.commit();
		}catch(Exception e){
			tx.rollback();
			throw e;
		}
	}
	
	private static Log log = LogFactory.getLog(Persistencia.class);
	private static SessionFactory sessionFactory = null;
	private static final ThreadLocal session = new ThreadLocal();
	
	private static Session currentSession() throws HibernateException{
		Session s = (Session) session.get();
		if (s == null){
			s = sessionFactory.openSession();
			session.set(s);
		}
		return s;
	}
	
	public static void closeSession() throws HibernateException{
		Session s = (Session) session.get();
		session.set(null);
		if (s != null)
			s.close();
	}
	
	public static void disconnectSession() throws HibernateException{
		Session s = (Session) session.get();
		if (s != null)
			s.disconnect();
	}
	
	public static void closeFactory() throws HibernateException{
		if (sessionFactory != null)
			sessionFactory.close();
		sessionFactory = null;
	}
	
	public static void openFactory() throws HibernateException{
		try{
			sessionFactory = new Configuration().configure("hibernate.cfg.xml").buildSessionFactory();
		}catch(Exception e){
			log.error("Erro ao iniciar SessionFactory!", e);
			System.out.println("Erro - openFactory\n"+e);
		}
	}
}

ao iniciar o webapp é iniciado tambem a factory do hibernate.

	public static void openFactory() throws HibernateException{
		try{
			sessionFactory = new Configuration().configure("hibernate.cfg.xml").buildSessionFactory();
		}catch(Exception e){
			log.error("Erro ao iniciar SessionFactory!", e);
			System.out.println("Erro - openFactory\n"+e);
		}
	}

quando é feito o primeiro acesso ao banco de dados uma session é aberta:

	private static Session currentSession() throws HibernateException{
		Session s = (Session) session.get();
		if (s == null){
			s = sessionFactory.openSession();
			session.set(s);
		}
		return s;
	}

sempre que faço acessos a dados só uso os metodos select, insert, update e delete.
possuo tambem classes DAO, que fazem as regras de negocio, mas ao final sempre passa por aí.

o que ta acontecendo é que, nunca fecho e nem deconecto a session do hibernate, simplesmente vou usando os metodos. chega uma hora que dá erro de timeout no hibernate.

entendeu???
será que a forma que estou fazendo está correta?

Não está, você tem que fechar as session ao final de cada transação. >>transação<< e não comando.

voce fala assim:

 		Transaction tx = session.beginTransaction();
 		try{
 			session.save(obj);
 			session.flush();
 			tx.commit();
 		}catch(Exception e){
 			tx.rollback();
 			throw e;
 		}
 		Persistencia.closeSession(); // fechar aqui???

O normal é você fazer todas operações de um use case na mesma transação.

minha webapp é em struts.

tenho uma action chamada PostAction que contem os controles básicos de acesso a dados.

existe tambem uma ActionForm chamada DBForm, com metodos que chamam o DAO correspondente a cada formbean.

por herança, define-se qual o objeto (tabela do banco) cada formBean corresponde.

no metodo execute da action (PostAction), eu verifico qual tipo de acesso (inserção, edição, remoção ou visualização) foi requerido pelo usuário, chama o metodo correspondente no form que acessa o banco de dados e efetiva a requisição (DAO -> Persistencia.insert()…).

isto tá funcionando muito bem.

não estou sabendo como trabalhar com transações dentro deste modelo.

podem me ajudar???

Olá!

bom… eu naum manjo muito desta parte…

dê uma olhada neste link aqui…
http://www.hibernate.org/hib_docs/reference/en/html/quickstart.html

e veja este exemplo de código da doc do hibernate…

Session session = HibernateUtil.currentSession();

Transaction tx= session.beginTransaction();

Cat princess = new Cat();
princess.setName("Princess");
princess.setSex('F');
princess.setWeight(7.4f);

session.save(princess);
tx.commit();

HibernateUtil.closeSession();

Vc pode inicar sua transação com a chamada ao Transaction tx= session.beginTransaction(); e vc irá finalizá-la com o comando tx.commit();
. Provavelmente ai também deve ter o comando tx.rollback, ou algo do tipo.

Vc pode inicar a transação no início da sua action, e no final da action commite a transação, e se acontecer alguma exceção durante a execução da action, vc executa o rollback.

outra idéia interessante é vd usa filtros para inicar a transação e para inalizá-la. Assim, antes que inicie um action, a transação será iniciada, e ao terminar a execuão da action, caso o action tenha executado com sucesso, então dê commit, caso contrário, rollback!

Abraços!
Thiago

ok, valeu!!!

fiz os testes aqui e acontece o seguinte:
antes de iniciar a action eu abro uma transação e ao final fecho a transação porem quando a pagina jsp é montada dá uma exception:

 2005-07-15 09:14:14,117 ERROR org.hibernate.LazyInitializationException  -> could not initialize proxy - the owning Session was closed
 org.hibernate.LazyInitializationException: could not initialize proxy - the owning Session was closed
	at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:53)
	at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:80)
	at org.hibernate.proxy.CGLIBLazyInitializer.intercept(CGLIBLazyInitializer.java:134)
	at br.org.apaecontagem.model.Operador$$EnhancerByCGLIB$$1417c6ad.toString(<generated>)
	at java.lang.String.valueOf(String.java:2131)
	at java.io.PrintStream.print(PrintStream.java:462)
	at java.io.PrintStream.println(PrintStream.java:599)
	at Teste.main(Teste.java:39)
org.hibernate.LazyInitializationException: could not initialize proxy - the owning Session was closed
	at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:53)
	at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:80)
	at org.hibernate.proxy.CGLIBLazyInitializer.intercept(CGLIBLazyInitializer.java:134)
	at br.org.apaecontagem.model.Operador$$EnhancerByCGLIB$$1417c6ad.toString(<generated>)
	at java.lang.String.valueOf(String.java:2131)
	at java.io.PrintStream.print(PrintStream.java:462)
	at java.io.PrintStream.println(PrintStream.java:599)
	at Teste.main(Teste.java:39)

isto é causado pelos relacionamentos lazy.

public class A{
  private String a;
...
}
public class B{
  private A a;
  private String b;
...
}

... // na action
Transaction tx = session.beginTransaction();
Query qry = session.createQuery("from B");
request.getSession().setAttribute("lista", qry.list());
tx.commit();
...


... // no jsp
<logic:iterate name="lista" id="item">
  <bean:write name="item" property="a"/><br>  --> aqui é levantado exceção porque é lazy, quando ele faz acesso à session do Hibernate ela está fechada.
  <bean:write name="item" property="b"/><br> --> aqui imprime porque já foi carregado este valor no objeto
</logic:iterate>
...

humm… cruel hein…

eu naum manjo isso…

mas sugiro entaum q vc experimente não dar um commit na transação para ver se o erro persiste!

Se não persistir, entaum talvez vc solucione isso usando filttros…
http://www.guj.com.br/java.tutorial.artigo.11.1.guj

Abraços!
Thiago

se a session for fechada apos o processamento do JSP não ocorre erros.

usando filter, consigo pegar este evento?
porque ai eu posso fechar a session.

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 
        throws IOException, ServletException {
        HibernateUtils.openSession();
        chain.doFilter(request, response);
        HibernateUtils.closeSession();
    }

quando ‘chain.doFilter(request, response);’ é chamado o Servlet e o JSP são executados???

naum sei… nunca fiz isso… hehe…

faz o seguinte… testa, e fala pra gente se funcou!

Tem alguns tópicos rodando esta semana que comentou sobre vc conseguir acessar os dados que estão persistidos no hibernate dentro de sua jsp…

[quote=fviana]se a session for fechada apos o processamento do JSP não ocorre erros.

usando filter, consigo pegar este evento?
porque ai eu posso fechar a session.

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 
        throws IOException, ServletException {
        HibernateUtils.openSession();
        chain.doFilter(request, response);
        HibernateUtils.closeSession();
    }

quando ‘chain.doFilter(request, response);’ é chamado o Servlet e o JSP são executados???[/quote]

Não faça assim, você não precisa criar uma sessão a cada request, você só precisa fechar a sessão (se ela foi criada) depois que tudo for executado.

Lá no desenrolar da execução da request, se for necessário, alguém vai criar uma sessão e vai botar ela na tread local.

entendi!!!

mas quando a session é fechada neste ponto

     public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 
         throws IOException, ServletException {
         chain.doFilter(request, response);
         HibernateUtils.closeSession();  <---
     }

quer dizer que o JSP já foi montado?

sim, está correto…
chain.doFilter é o antes e o depois do request (action e jsp).

      public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 
          throws IOException, ServletException {
          ...antes
          chain.doFilter(request, response);
          ...depois
      }

Exato.