Dúvida com thread-safe

19 respostas
ruyoutor

Fala galera!

Bem o problema é o seguinte. Quano eu inicio a minha aplicação eu tenho um evente ServletContextListener que atribui ao contexto o objeto que eu vou usar para fazer a persistência com o banco de dados esse objeto vai ser usado por toda a aplicação.
A minha dúvida é a seguinte. Se eu fizer isso:

synchronized(getServletContext){
   BaseDao bd = (BaseDao)getServletContext().getAttribute("conn");
   Cliente cliente = new Cliente();
   cliente.setId(1);
   cliente.setNome("Ruy");
   bd.persistObject(cliente);
}

e outro servlet(No caso seria a thread de outro servlet) tentar capturar uma referência do atributo do contexto ao mesmo tempo esse atributo vai estar bloqueado?

Não sei se me fiz entender, mas qualquer dúvida é só postar ai que eu tento de novo.

19 Respostas

Leozin

acho que não estaria bloqueado

estaria bloqueado se o mesmo servlet abrisse esse código, aí sim

otherwise não rola

Ps.: por que você não debuga e diz o resultado pra gente?! :slight_smile:

ruyoutor

É meio complexo fazer esses teste e ter total certeza do éxito pelo fato de precisar que mais de uma pessoa ou browser fassa solicitações a mesma thread ao mesmo tempo.
Mas eu não fiz isso não, eu estou syncronizando o objeto que está dentro do ServletContext.
Por exemplo:

BaseDao bd = getServletContext().getAttribute("conn");
Cliente cliente = new Cliente();
cliente.setID(1);
cliente.setNome("ruy");
synchronized(bd){
    bd.persistObject(cliente);
}

Acho que assim vai funcionar.
Conforme for eu posto aqui o resultado.

recoma

Qual é a razão desta solução?? Crie a BaseDao como um singleton, assim ela fica compartilhada por toda a aplicação…

Dá uma olhada como se faz um DAO…

http://java.sun.com/blueprints/corej2eepatterns/Patterns/DataAccessObject.html

BaseDao bd = BaseDao.getInstance(); Cliente cliente = new Cliente(); cliente.setId(1); cliente.setNome("Ruy"); bd.persistObject(cliente);

nbluis

Vixi…
Não fale esse nome.
Sempre tem alguém que acorda do fundo do abismo para xingar mais um pouco o Singleton.
:smiley:

Mauricio_Linhares

Assim, sem querer ser chato mas já sendo, como é que voê chegou a conclusão que esse seu código de acesso a banco de dados precisa ser thread safe?

Você está usando um banco de dados relacional?

Se estiver, o seu código vai ser sempre thread-safe se você estiver usando conexões diferentes pra cada uma das requisições do cliente (o que você com certeza deve estar fazendo, não é?).

ruyoutor

Putz… esse negocio tá ficando bom.

Deixa eu explicar melhor para vocês poderem me ajudar.

Eu tenho uma classe chamada BaseDao segue a implementação:

public class BaseDao {

    EntityManagerFactory factory;
    EntityManager manager;

    public BaseDao() throws Exception {
        factory = Persistence.createEntityManagerFactory("TerminalWebPU");
        manager = factory.createEntityManager();
    }

    public List<? extends Object> search(String qryName, Object... arg) throws DatabaseException {
        Query query = manager.createNamedQuery(qryName);
        for (int i = 0; i < arg.length; i += 2) {
            String name = (String) arg[i];
            if (arg[i + 1] instanceof String) {
                query.setParameter(name, (String) arg[i + 1]);
            } else {
                if (arg[i + 1] instanceof Integer) {
                    query.setParameter(name, arg[i + 1]);
                } else {
                    if (arg[i + 1] instanceof Date) {
                        query.setParameter(name, arg[i + 1]);
                    } else {
                        if (arg[i + 1] instanceof Boolean) {
                            query.setParameter(name, arg[i + 1]);
                        }
                    }
                }
            }
        }
        return query.getResultList();
    }

    public Boolean persistObject(Object obj) {
        EntityTransaction transaction = manager.getTransaction();
        try {
            transaction.begin();
            manager.persist(obj);
            transaction.commit();
            return true;
        } catch (Exception e) {
            transaction.rollback();
            e.printStackTrace();
            return false;
        }
    }

    public Boolean removeObject(Object obj) {
        EntityTransaction transaction = manager.getTransaction();
        try {
            transaction.begin();
            manager.remove(obj);
            transaction.commit();
            return true;
        } catch (Exception e) {
            transaction.rollback();
            e.printStackTrace();
            return false;
        }
    }

    public Object searchId(Class clas, Object pk) {
        Object result = manager.find(clas, pk);
        return result;
    }
}

Eu tenho um ContextListener implementado segue código:

public class InitialiazeListener implements ServletContextListener {
           static BaseDao db = null;
    public void contextInitialized(ServletContextEvent event) {
        try {
            db = new BaseDao();
            event.getServletContext().setAttribute("conn", db);
        } catch (Exception ex) {
            Logger.getLogger(InitialiazeListener.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public void contextDestroyed(ServletContextEvent event) {
        event.getServletContext().removeAttribute("conn");
    }

}

Esse listener carrega a minha conexão logo no inicio da aplicação assim que eu estartar o tomcat. Então toda vez que eu preciso persistir ou consultar algo no banco eu crio uma referencia para esse objeto e utilizo os metodos persistObject e os outros de consulta. Pelo pouco que entendo o objeto ServletContext é uma para toda a aplicação e isso faz com que eu tenha uma conexão apenas para toda aplicação independente de quantas solicitações eu tenha, então eu acho que se eu vou ter varias threads de varios servlets acessando o mesmo objeto seria interesante eu sincronizar o mesmo quando eu estiver usando.

Por favor se eu estiver errado me digam como é o certo, mas antes de me xingarem pelo que falei eu quero que saibam que só tenho dois meses de java, então tenham um pouco de paciência comigo. srsrsr…

Esqueci de falar que estou usando o framework JPA se é que isso interessa.

Obrigado.

D

Posso estar falando besteira, mas o que quer é um Pool de Conexões?

saoj

Não existe ter apenas uma conexão com o banco de dados. Isso simplesmente é tentar dirigir um carro com uma roda só. :slight_smile:

A não ser que sua empresa só tenha licença para abrir UMA conexão com o banco de dados, o que não faz qualquer sentido tb, vc tem que usar obrigatoriamente um pool de conexão.

Esqueca synchronized!

synchronized = coisa avançada = dor de cabeça. Evite usá-lo sempre que possível…

O JPA, assim como o Hibernate já deve ter um pool de conexões por trás…

recoma

Tutorial: Build a Web Application (JSF) Using JPA

http://www.oracle.com/technology/products/ias/toplink/jpa/tutorials/jsf-jpa-tutorial.html

ruyoutor

Amigo eu posso estar enganado mas eu já fiz alguns teste com esse framework e constatei que ele demora muito para criar uma conexão com o banco, então achei que criando uma conexão logo quando inicio o tomcat eu ganharia performace ao envez de ficar criando várias toda vez que precisar. Eu poderia fazer igual o mauricio achou que eu estava fazendo criar a conexão como uma variavel local então eu teria uma conexão para cada solicitação, mas não acho uma boa ideia ter inumeras conexões com o banco. E quanto a ser avançado, eu não posso deixar de fazer algo porque é avançado eu posso deixar se não for adequado, mas também pelo que conheço sobre os servlets e como o tomcat gerencia-os acho que não tem como eu fugir do sincronismo.

Mauricio_Linhares

Bem, o que você está fazendo não é avançado, é errado.

Você deve ter um persistence manager aberto pra cada requisição que tiver que ser tratada, você não deve abrir um único pesistence manager pra toda a aplicação e ficar sincronizando ele.

Já pensou no caso de ele lançar uma exceção?

A partir do momento que o seu persistence manager lançar uma exceção ele fica inútilizado, então a sua aplicação simplesmente pararia de funcionar pra todo mundo porque aconteceu um erro com apenas uma requisição.

Nesse primeiro momento, como você ainda não tem muito conhecimento (nem muita preocupação) com demarcação de transações e coisas do gênero (na maior parte das aplicações você nem se preocupa com isso) cada um dos métodos do seu DAO deveriam abrir um persistence manager e iniciar uma transação só pra eles, não esquecendo de fechar o persistence manager ao fim de cada método.

Outra coisa, o que você chama de “demora pra abrir uma única conexão” é na verdade a demora pra criar um persistence manager factory que é um criador de conexões e aonde as configurações do JPA ficam guardadas, no contrutor do seu dao você sempre faz isso:

factory = Persistence.createEntityManagerFactory("TerminalWebPU");

Quando na verdade essa criação deveria estar em um bloco estático fora do construtor do seu DAO. O seu dao provavelmente ficaria assim:

public class BaseDao {

    static EntityManagerFactory factory = Persistence.createEntityManagerFactory("TerminalWebPU");
    EntityManager manager;

    public BaseDao() throws Exception {
        manager = factory.createEntityManager();
    }

    public List<? extends Object> search(String qryName, Object... arg) throws DatabaseException {
        Query query = manager.createNamedQuery(qryName);
        for (int i = 0; i < arg.length; i += 2) {
            String name = (String) arg[i];
            if (arg[i + 1] instanceof String) {
                query.setParameter(name, (String) arg[i + 1]);
            } else {
                if (arg[i + 1] instanceof Integer) {
                    query.setParameter(name, arg[i + 1]);
                } else {
                    if (arg[i + 1] instanceof Date) {
                        query.setParameter(name, arg[i + 1]);
                    } else {
                        if (arg[i + 1] instanceof Boolean) {
                            query.setParameter(name, arg[i + 1]);
                        }
                    }
                }
            }
        }
        return query.getResultList();
    }

    public Boolean persistObject(Object obj) {
        EntityTransaction transaction = manager.getTransaction();
        try {
            transaction.begin();
            manager.persist(obj);
            transaction.commit();
            return true;
        } catch (Exception e) {
            transaction.rollback();
            e.printStackTrace();
            return false;
        }
    }

    public Boolean removeObject(Object obj) {
        EntityTransaction transaction = manager.getTransaction();
        try {
            transaction.begin();
            manager.remove(obj);
            transaction.commit();
            return true;
        } catch (Exception e) {
            transaction.rollback();
            e.printStackTrace();
            return false;
        }
    }

    public Object searchId(Class clas, Object pk) {
        Object result = manager.find(clas, pk);
        return result;
    }
}

E como o Sérgio já bem disse, não use synchronized sem saber exatamente o que é que você está fazendo e quais vão ser os resultados dessa ação. Se você está com penas dois meses de Java, aproveite um pouco mais o seu tempo pra entender da linguagem e menos de receitas de bolo.

É claro que é chato ficar brincnado com console, swing e essas coisas, mas simplesmente pular pra aplicações web e ainda mais usando JPA sem entender como as coisas funcionam é perigoso, além de ser ruim pra você, que pode pegar maus costumes por não ter sido “apresentado” a maneiras melhores de se resolver o problema.

ruyoutor

Você diz em cada método eu deveria ter isso:

factory = Persistence.createEntityManagerFactory("TerminalWebPU");   
        manager = factory.createEntityManager();

E no casa eu deveria instanciar um novo objeto BaseDao para cada solicitação para que ele fique thread-safe? Seria isso?

Mauricio_Linhares

Em cada método você deveria ter isso:

manager = factory.createEntityManager();

E apenas um dao bastaria pra todos, se você não criasse o entitymanager no contrutor dele.

ruyoutor

Hum… Bom e toda vez que eu quiser utilizar eu crio um novo objeto?

Desculpe eu fazer tantas perguntas mas infelizmente eu não tenho muito tempo e o meu emprego depende disso eu me comprometi com isso e não posso parar agora.

Obrigado

saoj

ruyoutor:
Hum… Bom e toda vez que eu quiser utilizar eu crio um novo objeto?

Desculpe eu fazer tantas perguntas mas infelizmente eu não tenho muito tempo e o meu emprego depende disso eu me comprometi com isso e não posso parar agora.

Obrigado

A sua situação grita por um framework que abstraia a complexidade que vc não precisa e não sabe implementar. Veja se não é melhor vc usar Struts, Mentawai, VRaptor, Seam ou qualquer outra coisa que vá facilitar a sua vida e conservar o seu emprego.

D

ruyoutor:
Hum… Bom e toda vez que eu quiser utilizar eu crio um novo objeto?

Desculpe eu fazer tantas perguntas mas infelizmente eu não tenho muito tempo e o meu emprego depende disso eu me comprometi com isso e não posso parar agora.

Obrigado

O que o Mauricio quis dizer é pra utilizar um DAO genérico e depois implementá-lo com Daos específicos. Para facilitar mais, seria bom o uso de Spring, ai sim, ficaria bem mais fácil.
Agora, se além disso, precisar de um framework para desenvolvimento Web, o saoj num quer fazer propaganda, mas eu faço, utilize Mentawai. Nossa, excelente documentação e muitas resoluções pra problemas chatos.

Abraços

ruyoutor

Valeu galera esse post foi de muita ajuda.

G

Bem na verdade você deve criar um pool de conexões no banco de dados no servidor de aplicação. Após isto crie o seu EntityManagerFactory anotado com @PersistenceUnit(“nome”). Caso o seu servidor de aplicação tenha JTA crie tambem um user transcation assim:
@Resource
private UserTransaction utx;

Após isto crie uma função para retornar EntityManager. E sempre que for persistir algo faça o seguinte:

EntityManager em = getEntityManager();

try {

utx.begin();

em.joinTransaction();

em.persist(curso);

utx.commit();

} catch (Exception ex) {

try {

utx.rollback();

} catch (Exception e) {

}

} finally {

em.close();

}

caso você queira fazer um catch de uma exceção do bd coloque o codigo em.flush(); antes de utx.commit().

Isto funciona muito bem com o glass fish.

Tudo que estiver entre utx.begin() e utx.commit() são tread safe.

G

Ha o manage bean que implementa estes metodos deve ter escopo de sessão.
Assim você aproveita o pool de conexões.
A importância do pool de conexões para aplicações web pode ter analogia a passar a vazão de um rio por um cano fino. Quanto mais canos finos você tiver para dar vazão ao rio menor vai ser a pressão que você terá que agüentar.

Criado 27 de dezembro de 2007
Ultima resposta 16 de jan. de 2008
Respostas 19
Participantes 8