Hibernate e sincronismo

13 respostas
fviana

ola pessoal…

tenho uma webapp em struts.
uso hibernate para persistencia.

criei um filter que controla sessions e transactions com o banco de dados.

public class SessionFilter implements Filter {
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 
throws IOException, ServletException {
        Session session = null;
        Transaction tx = null;
        if (request instanceof HttpServletRequest){
            session = Persistencia.initSession();
            tx = session.beginTransaction();
            request.setAttribute("sessionDB", session);
        }

        chain.doFilter(request, response);

        if (request instanceof HttpServletRequest){
            if (request.getAttribute("transaction") != null && 
request.getAttribute("transaction").equals("true")){
                tx.commit();
            }else{
                tx.rollback();
            }
            request.removeAttribute("transaction");
            session.close();
            request.removeAttribute("sessionDB");
        }
    }
}

tudo funciona perfeitamente até um ponto.
se voces repararem, a cada request é criado uma session e uma transação com o banco de dados.
aconteceu por duas vezes um fato q eu nao entendi.

dois usuarios estavam trabalhando normalmente no sistema, e, em um instante, eles fizeram operações identicas, ou seja, ambos entraram no cadastro de cliente e cadastraram dois clientes diferentes mas quando foram salvar os dados no banco, foi salvo dois registros identicos, iguais porem com id diferente.

é como se a session estivesse fora de sincronismo, mas teoricamente so daria erro se fosse a mesma session, o que não é.

para cada request há uma nova session, que ao final do request é fechado.

será que alguem pode me ajudar?

13 Respostas

fviana

alguem pode me ajudar?

Mauricio_Linhares

Cara, sua solução tá meio bizarra, talvez o código que está lidando com isso é que está com erros (nos actions do Struts por exemplo).

Faça isso usando ThreadLocal: http://www.hibernate.org/207.html

_fs

Cara, se não me engano o struts reaproveita ações, talvez o erro resida aí.

E mais: o forum é algo comunitário, e o pessoal que ajuda os outros faz isso no tempo livre - o que, em muitos casos, é quase inexistente. Se ninguém respondeu ainda, tenha paciencia e espere até que alguem o faça :wink:

_fs

Maurício, as actions do struts são thread-safe?

Mauricio_Linhares

Nops, as Actions do Struts são apenas um servlet que não herda de HttpServlet. Nada de variáveis de instância.

fviana

ele pode ate reaproveitar action mas formbean não, porque senão daria erros… pelo menos na teoria…

na verdade estava usando ThreadLocal mas nao funcionou legal por causa de session, objetos lazy e transactions…

hibernate é muito bom mas ate hoje nao consegui rodar 100% usando struts.

o pessoal fala que roda, mandam consultar a documentação do struts e hibernate mas nunca resolvo o problema.

não há exemplos, dicas e/ou tutoriais de struts e hibernate juntos, pelo menos eu ate hoje nao achei.

tenho uma aplicação em swing e nunca tive problemas, roda 100% mas quando a coisa é web, em especial struts, o trosso da zebra.

alguem tem um exemplo de struts e hibernate rodando juntos? ou será que tem outra solução melhor?

Mauricio_Linhares

fviana:
na verdade estava usando ThreadLocal mas nao funcionou legal por causa de session, objetos lazy e transactions…

Eu usei ThreadLocal com o Struts por muito tempo e nunca tive problemas.

Hoje contunuo usando ThreadLocal com o Spring e também não tive problema nenhum.

fviana

me ajuda pelo amor de deus!!!

manda um exemplo simples!

meu sistema faz o seguinte:
inclusão, edição, remoção, visualização e pesquisa de clientes

abaixo está todo o codigo dele. digam-me o que está bom e o que esta ruim, como adicionar threadlocal nele?

/*
 * classe que trata toda a persistencia
 */
public abstract class Persistencia {
	private Object beforeObject;
	private Session session;
	
	public Object getBeforeObject() {
		return beforeObject;
	}
	
	public void setBeforeObject(Object object) {
		this.beforeObject = object;
	}
	
	public String getId() {
		return "id"+getTabela();
	}
	
	public abstract Class getDAOClass();
	
	public String getTabela(){
		String ret = this.getDAOClass().getName();
		int fim = ret.lastIndexOf(".")+1;
		ret = ret.substring(fim, ret.length());
		log(" - "+ret);
		return ret;
	}
	
	public Object search(String id) throws Exception{
		return search("id"+getTabela(), id);
	}
	
	public Object search(String campo, String id) throws Exception{
		String tabela = getTabela();
		List list = execSQL("from "+tabela+" where "+campo+"='"+id+"'");
		if (list.size()>0)
			return list.get(0);
		else
			return null;
	}
	
	public List execSQL(String condicao) throws Exception{
		if (condicao == null || condicao.equals("")){
			Exception erro = new Exception(this.getClass().getName() + ".execSQL("+condicao+")");
			log(erro);
			throw erro;
		}
		
		Query qry = session.createQuery(condicao);
		List list = qry.list();
		return list;
	}
	
	public List select() throws Exception{
		return execSQL("from " + getTabela());
	}
	
	public List select(String condicao) throws Exception{
		return execSQL("from " + getTabela() + ((condicao==null || condicao.equals(""))? "" : " where " + condicao));
	}
	
	public void insert(Object obj) throws Exception{
		if (!isDAOClass(obj)){
			Exception erro = new Exception(this.getClass().getName() + ".insert("+obj.toString()+") - "+obj.getClass().getName());
			log(erro);
			throw erro;
		}
		
		session.save(obj);
		session.flush();
	}

	public void update(Object obj) throws Exception{
		if (!isDAOClass(obj)){
			Exception erro = new Exception(this.getClass().getName() + ".insert("+obj.toString()+") - "+obj.getClass().getName());
			log(erro);
			throw erro;
		}
		
		session.update(obj);
		session.flush();
	}

	public void delete(Object obj) throws Exception {
		if (!isDAOClass(obj)){
			Exception erro = new Exception(this.getClass().getName() + ".insert("+obj.toString()+") - "+obj.getClass().getName());
			log(erro);
			throw erro;
		}
		session.delete(obj);
		session.flush();
	}
	
	public abstract boolean isDAOClass(Object obj);
	
	public Transaction initTransaction() throws Exception{
		return session.beginTransaction();
	}
	
	public Session getSession(){
		return session;
	}
	
	public void setSession(Session session){
		this.session = session;
	}
	
	// staticos
	private static Log log = LogFactory.getLog(Persistencia.class);
	private static SessionFactory sessionFactory = null;
	
	public static synchronized Session initSession() throws Exception{
		if (!isOpenFactory())
			openFactory();
		return sessionFactory.openSession();
	}
	
	public static synchronized void closeFactory() throws Exception{
		if (sessionFactory != null)
			sessionFactory.close();
		sessionFactory = null;
	}
	
	public static synchronized boolean isOpenFactory() throws Exception {
		return (sessionFactory != null);
	}
	
	public static synchronized void openFactory() throws Exception{
		String file = Utils.getKey("hibernate.file");
		sessionFactory = new Configuration().configure(file).buildSessionFactory();
	}
	
	private static synchronized void log(Exception e){
		log.debug("\n ERRO\n"+e);
	}
	
	private static synchronized void logLn(String msg){
		log.debug("\n"+msg);
	}
	
	private static synchronized void log(String msg){
		log.debug(msg);
	}
}

/*
 * form generico
 */
public class Form extends ActionForm {
	private String formAction;
	private String flag;
	private Session session;
	
	public Session getSession() {
		return session;
	}
	
	public void setSession(Session session) {
		this.session = session;
	}
	
	public String getFlag() {
		return flag;
	}
	
	public void setFlag(String flag) {
		this.flag = flag;
	}
	
	public String getFormAction() {
		return formAction;
	}
	
	public void setFormAction(String formAction) {
		this.formAction = formAction;
	}
	/*
	 * limpa os campos do form
	 */
	public void reset() throws Exception{
		
	}
	
	public void reset(HttpServletRequest request) throws Exception{
		reset();
	}
	/*
	 * valida os campos do form
	 */
	public ActionErrors validate(HttpServletRequest request) throws Exception{
		return validate();
	}
	
	public ActionErrors validate() throws Exception{
		return null;
	}
	/*
	 * inicia as configurações padrões do form
	 */
	public void init(HttpServletRequest request) throws Exception{
		init();
	}
	
	public void init() throws Exception{
		
	}
}

/*
 * form para manipulação de registros do DB
 */
public class DBForm extends Form {
	public final static String ACTION_NEW = "new";
	public final static String ACTION_EDIT = "edit";
	public final static String ACTION_DELETE = "delete";
	public final static String ACTION_VIEW = "view";

	private Persistencia daoForm;
	private String postForward = "closereload";

	public DBForm(Persistencia dao){
		daoForm = dao;
	}
	
	public String getPostForward() {
		return postForward;
	}
	
	public void setPostForward(String postForward) {
		this.postForward = postForward;
	}
	
	public Persistencia getDAO() throws Exception {
		return daoForm;
	}
	
	public void setSession(Session session) {
		super.setSession(session);
		if (daoForm != null)
			daoForm.setSession(session);
	}
	
	public void setDAO(Persistencia daoForm) {
		this.daoForm = daoForm;
	}
	/*
	 * seta as propriedades do objeto passado no parametro nas propriedades do formBean
	 */
	public void setObject(Object obj) throws Exception{
		
	}
	/*
	 * localiza ou cria um objeto setando nele as propriedades do formBean
	 */
	public Object getObject() throws Exception{
		return null;
	}
	/*
	 * localiza um objeto atraves da propriedade id do formBean
	 */
	public Object getObjectId() throws Exception{
		return daoForm.search(Utils.execMethod(this, "getid"+daoForm.getTabela()).toString()); // Utils.execMethod -> executa o metodo x do objeto y
	}
	
	public String onNew() throws Exception{
		Object obj = getObject();
		daoForm.insert(obj);
		return postForward;
	}
	
	public String onEdit() throws Exception{
		Object obj = getObject();
		daoForm.update(obj);
		return postForward;
	}
	
	public String onDelete() throws Exception{
		Object obj = getObjectId();
		daoForm.delete(obj);
		return postForward;
	}
	
	public String onNew(HttpServletRequest request) throws Exception{
		return onNew();
	}
	
	public String onEdit(HttpServletRequest request) throws Exception{
		return onEdit();
	}
	
	public String onDelete(HttpServletRequest request) throws Exception{
		return onDelete();
	}
	
	public boolean isNew() throws Exception{
		try{
			return getFormAction().equals(DBForm.ACTION_NEW);
		}catch(Exception e){
			return false;
		}
	}
	
	public boolean isEdit() throws Exception{
		try{
			return getFormAction().equals(DBForm.ACTION_EDIT);
		}catch(Exception e){
			return false;
		}
	}
	
	public boolean isDelete() throws Exception{
		try{
			return getFormAction().equals(DBForm.ACTION_DELETE);
		}catch(Exception e){
			return false;
		}
	}
	
	public boolean isView() throws Exception{
		try{
			return getFormAction().equals(DBForm.ACTION_VIEW);
		}catch(Exception e){
			return false;
		}
	}
}

/*
 * form de pesquisas generica
 */
public class PesquisaForm extends Form {
	private String sql;
	private String filtro;
	public String getFiltro() {
		return filtro;
	}
	public void setFiltro(String filtro) {
		this.filtro = filtro;
	}
	public String getSql() {
		return sql;
	}
	public void setSql(String sql) {
		this.sql = sql;
	}
}

/*
 * action generica que seta a session, valida transação e ocorrencia de erros
 */
public abstract class GenericAction extends Action {
	private Session session;
	
	public final ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
		ActionForward forward = null;
		Form formBean = (Form) form;
		try{
			session = (Session) request.getAttribute("sessionDB");
			formBean.setSession(session);
			forward = onExecute(mapping, formBean, request, response);
			request.setAttribute("transaction", "true");
			
		}catch(Exception e){
			request.setAttribute("transaction", "false");
			request.getSession().setAttribute("exception", e);
			forward = mapping.findForward("erro");
			
		}
		return forward;
	}
	
	public abstract ActionForward onExecute(ActionMapping mapping, Form form, HttpServletRequest request, HttpServletResponse response) throws Exception;
	
	public Session getSession() {
		return session;
	}

	public void setSession(Session session) {
		this.session = session;
	}
}

/*
 * action responsavel por carregar um registro especificado em um formBeam - abre o form para view, edit e inset
 */
public abstract class FormAction extends GenericAction {
	public final ActionForward onExecute(ActionMapping mapping, Form form, HttpServletRequest request, HttpServletResponse response) throws Exception {
		DBForm dbForm = (DBForm)form;
		String forward = "";
		Object obj = null;
		if (dbForm .isNew()){
			dbForm .init(request);
			dbForm .reset(request);
			obj = dbForm .getObject();
			forward = "form";
		}else
		if (dbForm .isEdit()){
			dbForm .init(request);
			obj = dbForm .getObjectId();
			dbForm .setObject(obj);
			forward = "form";
		}else
		if (dbForm .isView()){
			obj = dbForm .getObjectId();
			forward = "view";
		}
		request.getSession().setAttribute("registro"+dbForm.getDAO().getTabela(), obj); // seta o registro na session
		return mapping.findForward(forward);
	}
}

/*
 * action que efetiva as operações com o banco - insert, edit e delete
public abstract class PostAction extends GenericAction {
	public final ActionForward onExecute(ActionMapping mapping, Form form, HttpServletRequest request, HttpServletResponse response) throws Exception {
		DBForm dbForm = (DBForm)form;
		String forward = "";
		if (dbForm.isDelete()){
			forward = dbForm.onDelete(request);
		}else{
			ActionErrors erro = dbForm.validate(request);
			if (erro != null && !erro.isEmpty()){
				saveErrors(request, erro);
				return mapping.findForward("form");
			}
			if (dbForm.isNew()){
				forward = dbForm.onNew(request);
			}else
			if (dbForm.isEdit()){
				forward = dbForm.onEdit(request);
			}
		}
		return mapping.findForward(forward);
	}
}

/*
 * action que executa pesquisas
 */
public abstract class PesquisaAction extends GenericAction {

	protected abstract Persistencia getDAO();

	public final ActionForward onExecute(ActionMapping mapping, Form f, HttpServletRequest request, HttpServletResponse response) throws Exception {
		PesquisaForm form = (PesquisaForm)f;
		Persistencia dao = getDAO();
		dao.setSession(form.getSession());

		String sql = "";
		if (form.getSql() != null && !form.getSql().equals(""))
			sql = form.getSql().toString();
		String sqlExec = sql.toString();
		if (sqlExec.equals(""))
			sqlExec = (form.getFiltro() != null && !form.getFiltro().equals("")) ? form.getFiltro() : "";
		else
			sqlExec += (form.getFiltro() != null && !form.getFiltro().equals("")) ? " and (" + form.getFiltro() + ")" : "";
		if (sqlExec.equals(""))
			request.getSession().setAttribute("resultado"+dao.getTabela(), dao.select());
		else
			request.getSession().setAttribute("resultado"+dao.getTabela(), dao.execSQL("from " + dao.getTabela() + " pesquisa where " + sqlExec));
		
		form.setFormAction(dao.getTabela());
		return mapping.findForward("pesquisa");
	}
}


// até aqui temos as classes genericas

/*
 * objeto de negocio - Setor
 */
public class Setor implements java.io.Serializable {
	private java.lang.Integer idSetor;
	private java.lang.String descricao;
	// ... geteres e seteres
}

public class SetorDAO extends Persistencia {
	public SetorDAO(){	
	}
	public SetorDAO(Session session){
		setSession(session);
	}
	public Class getDAOClass() {
		return Setor.class;
	}
	public boolean isDAOClass(Object obj){
		return (obj instanceof Setor);
	}
}

public class SetorForm extends DBForm {
	private String idSetor;
	private String descricao;

	public SetorForm() {
		super(new SetorDAO());
	}
	public String getDescricao() {
		return descricao;
	}
	public void setDescricao(String descricao) {
		this.descricao = descricao;
	}
	public String getIdSetor() {
		return idSetor;
	}
	public void setIdSetor(String idSetor) {
		this.idSetor = idSetor;
	}
	
	public Object getObject() throws AppException {
		Setor setor = (Setor) getObjectId();
		if (setor == null){
			setor = new Setor();
			setor.setIdSetor( Integer.valueOf(idSetor) );
		}
		setor.setDescricao( descricao );
		return setor;
	}
	
	public void setObject(Object obj) throws AppException {
		Setor setor = (Setor) obj;
		descricao = setor.getDescricao();
		
		getDAO().setBeforeObject(Utils.clone(setor)); // faz um backup do objeto caso precise reaver os dados
	}
	
	public void reset() throws Exception {
		descricao = "";
		idSetor = "0";
	}
}

//.. há controles de acesso mas são dispensaveis
public class PesquisaSetorAction extends PesquisaAction {
	protected Persistencia getDAO() {
		return new SetorDAO();
	}
}

//.. há controles de acesso mas são dispensaveis
public class PostSetorAction extends PostAction {

}

//.. há controles de acesso mas são dispensaveis
public class FormSetorAction extends FormAction {
}
Mauricio_Linhares

Muito do que eu tinha, já era, porque eu não uso mais o Struts, mas a classe que mantinha as sessions nas threads e era usada pelos DAOs era essa aí ó:

/*
 * Created on 11/05/2005
 *
 * Código desenvolvido por Maurício Linhares
 * 
 */
package br.edu.cefetpb.coinfo.dao;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;

/**
 * @author Maurício
 *
 * Código desenvolvido por Maurício Linhares
 * 
 */
public class HibernateUtility {

    private static Log logger = LogFactory.getLog(HibernateUtility.class);
    
    private static final SessionFactory factory;
    private static final ThreadLocal threadSession = new ThreadLocal();
    private static final ThreadLocal threadTransaction = new ThreadLocal();
        
    static {
        factory = new Configuration().configure().buildSessionFactory();
    }
    
    public static Session getSession() {
        
        Session session = (Session) threadSession.get();
        
        if ( session == null || !session.isOpen() ) {
        
            session = factory.openSession();
            threadSession.set(session);
            logger.debug("Abriu a sessão");
            logger.debug(session);
        }
        
        return session;
        
    }
    
    public static void closeSession() {
        
        Session session = (Session) threadSession.get();
        
        if (session != null && session.isOpen() ) {
            threadSession.set(null);
            session.close();
            logger.debug("Fechou a sessão");
            logger.debug(session);            
        }
        
    }
    
    public static void beginTransaction () {
        
        Transaction transaction = (Transaction) threadTransaction.get();
        
        if (transaction == null) {
            transaction = getSession().beginTransaction();
            threadTransaction.set(transaction);
            logger.debug("Iniciou a transação:");
            logger.debug(transaction);            
        }
        
    }
    
    public static void commitTransaction() {
        
        Transaction transaction = (Transaction) threadTransaction.get();
        
        if (transaction != null && !transaction.wasCommitted() && !transaction.wasRolledBack() ) {
            
            transaction.commit();
            logger.debug("Deu commit na transação:");
            logger.debug(transaction);                
            
        }
        
    }
    
    
}

E o filtro era esse aí ó (veja que é o filtro que faz o commit, uma coisa horrível, mas era assim):

/*
 * Created on 12/05/2005
 *
 * Código desenvolvido por Maurício Linhares
 * 
 */
package br.edu.cefetpb.coinfo.filtro;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

import br.edu.cefetpb.coinfo.dao.HibernateUtility;

/**
 * @author Maurício
 *
 * Código desenvolvido por Maurício Linhares
 * 
 */
public class FiltroDoHibernate implements Filter {

    /* (non-Javadoc)
     * @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
     */
    public void init(FilterConfig arg0) throws ServletException {
        

    }

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

    /* (non-Javadoc)
     * @see javax.servlet.Filter#destroy()
     */
    public void destroy() {
        

    }

}
fviana

ok, mas veja bem.
a session e a transação será a mesma!?
ou seja, imagine que eu e você faça uma requisição ao mesmo tempo, será aberto uma sessão e transação compartilhada para nós dois, correto?

alem disso, o meu codigo está correto? está dentro do padrao MVC? e do Struts?

fviana

o meu codigo está correto? está dentro do padrao MVC? e do Struts?

usando threadlocal pode ocorrer erros nas transações. pense na seguinte situação:

user1 e user2

user1 acaba de entrar no sistema
user2 em seguida entra no sistema

user1 abre uma transação e faz um update a uma consulta, neste mesmo tempo o user2 faz um update (na mesma session e transaction), porém dá um erro no update e um rollback é chamado. quando o user1, que nada teria a ver com user2, for comitar no db, as atualizações dele não serão efetivadas por ter ocorrido um rollback em sua transaction.

estou partindo do pre suposto que as operações de user1 irão demorar um bom tempo e a de user2 são instantaneas, ou seja, a session e a transaçao serão a mesma.

será que é isso mesmo? se não for ajudem-me.

Mauricio_Linhares

Não, cada sessão e transação estão amarradas a Thread corrente, é impossível que duas requisições estejam na mesma thread, porque o próprio container cria uma nova thread pra cada requisição.

Aí quem tem que saber é você. Tá funcionando?

fviana

valeu cara…
agora entendi o threadlocal…
ele garante uma session para cada requisição, colocando a session na thread para a requisicao criada pelo container.

ok, vou modificar no sistema. qualquer coisa, eu posto novamente

Criado 24 de agosto de 2005
Ultima resposta 24 de ago. de 2005
Respostas 13
Participantes 3