Como abrir e controlar uma Transação no DAO?

Boa tarde galera…

fiz um programa que um usuário passou 1 mês testando, somente ele, fazendo operações como updates e inserts, e o resultado foi o esperado, 100%.
No entanto, qndo passamos para mais usuários, começou haver problema de concorrência na transação.
Por exemplo, qndo 2 usuário vão fazer update ao mesmo tempo, 1 deles conclui e o outro da erro, ou ambos não conclui 100% dos updates solicitado.

Sou um pouco novato com Java web. Gostaria Que alguém pudesse me orientar

Abaixo segue um pedaço do DAO, na qual se refere a um método que faz um update.

[code]public boolean upIdDetalheUFRs(String IdDocContabil,
String fontel,
int tipoUpdate,
int idUsuario,
String nomeUsuario) {

    Connection conn = null;
    PreparedStatement ps = null;
    boolean isOk = false;

    conn = ConnectionFactory.getConnection();
    if (conn == null) {
        return isOk;
    }

    sql = UFRQuerry.upIdDetalheUFRs(IdDocContabil, fontel, tipoUpdate, idUsuario, nomeUsuario);

    try {
        ps = conn.prepareStatement(sql);
        ps.executeUpdate();
        isOk = true;
    } catch (SQLException ex) {
        Logger.getLogger(SaldoCCorrenteFonteDAO.class.getName()).log(Level.SEVERE, null, ex);
    } finally {
        ConnectionFactory.closeConnection(conn, ps);
    }

    return isOk;
}

}
[/code]

Na classe connectionFactory, teria alguma que mudar algo tb?

O que poderia fazer para controlar a concorrência do update? Já que se 2 ou mais fizerem ao mesmo tempo poderá causar problema.

Obs, seria possível corrigir esse problema sem utilizar hibernate?

Muito obrigado

veja isto: http://mballem.wordpress.com/2011/02/21/utilizando-swing-com-banco-de-dados/

t+ e boa sorte

[quote=fernandopaiva]veja isto: http://mballem.wordpress.com/2011/02/21/utilizando-swing-com-banco-de-dados/

t+ e boa sorte[/quote]

Boa tarde fernando …
Entao, preciso de algo que gerencie as transaçoes na hora que os updates sao realizados.

Cada usuario pode executar varios updates em um evento solicitado.
Quando + de 1 usuario realiza a operação de update ao mesmo tempo, ocorre “bloqueio/erro” na operação “stmt.executeUpdate();” nao concretizando - a totalmente.

Obrigado.

sete a connection como auto-commit igual false e feche e de commit manualmente

qq coisa estamos ai

para mais infos:
http://docs.oracle.com/javase/tutorial/jdbc/basics/transactions.html#transactions_data_integrity

abrassss

[quote=renanreismartins]sete a connection como auto-commit igual false e feche e de commit manualmente

qq coisa estamos ai

para mais infos:
http://docs.oracle.com/javase/tutorial/jdbc/basics/transactions.html#transactions_data_integrity

abrassss[/quote]

Sim, ja tentei fazer isso tb. Mas ainda nao deu certo.

Será q pode ser a conexao?..

Entendendo…
o usuario faz uma busca onde será listado uma tabela com dados, cada linha possui um checkbox. O usuario irá marcar essa checkbox, e escolher a Fonte (unico valor de um caixa de SELECT) que ele irá colocar para as checkbox selecionadas. Depois ele irá apertar o botao submit.

Assim, a informação irá para a classe ACTION. La, ele irá passar pelo um FOR, onde verificará as checkbox selecionadas.
Dentro do for, chamará o DAO para fazer o update. Sendo que, cada vez q passar pelo for, o DAO sempre será chamado. Logo, cada vez que o DAO for chamado, abrirá sempre uma nova conexao, ou seja, uma conexao por checkbox selecionado.

Da primeira vez, fiz para abrir e fechar a conexao quando o DAO fosse chamado. Agora, abri uma conexao antes do FOR, para manter uma conexao unica para todo o UPDATE, ou seja, abrindo uma unica conexao para todos os Checkbox.

entendido, acontece que não é uma boa prática controlar transações dentro de um DAO, uma vez que vc pode ter regras de negocios que chamarão um dao N vezes (como é o seu caso) ou N daos.

Para esse tipo de problema gosto de considerar um framework para resolver o problema, como por exemplo usando anotação @Transaction do Spring.

Mas caso seu stack seja simples, considere implementar uma transação por request.

abrassss

Bom Renan.

Um usuario poderá selecionar de 1(X) a 600(Y) checkbox… ou até bem mais.

seria chamada (Y) DAOs.

No entanto, queria restringir apenas 1 conexao para fazer Y updates, no caso Y DAOs.

Para teste, estou monitorando as entradas das conexoes para 2 usuarios, estou dando um System.out para isso.
Quando Apenas 1 executa, fica bem sucedido. Quando 2 Executam, ha concorrencia de updates/transação.

Estou bem confuso, podem existem muitas possibilidades de erro, ou pode ser coisa bem simples. Mas como sou “NOVATO” com este problema em especifico, nao estou conseguindo enxergar alguma solução.

Vc falou sobre framework, mas seria complexo aplica-lo?..

e sobre a implementação de uma transação depois do requesta? seria uma unica conexao em uma chamada p/ varios daos?..

de uma olhada na classe connectionFactory Abaixo, será q esta ok ?

package br.com.conexaodb2.connection;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author user
 */




public class ConnectionFactory {

    public static Connection conn = null;

    public static Connection conectar() {
        try {
            Class.forName("com.ibm.db2.jcc.DB2Driver");
            //String url = "jdbc:db2://###";
            //return DriverManager.getConnection(url, "usuario", "senha");
           String url = "jdbc:db2://###";
            return DriverManager.getConnection(url, "usuario", "senha");
        } catch (SQLException ex) {
            Logger.getLogger(ConnectionFactory.class.getName()).log(Level.SEVERE, null, ex);
            return null;
        } catch (ClassNotFoundException ex) {
            Logger.getLogger(ConnectionFactory.class.getName()).log(Level.SEVERE, null, ex);
            return null;
        }
    }

    public static Connection getConnection() {
        try {
            if (conn == null || conn.isClosed()) {
                conn = conectar();
            }
        } catch (SQLException ex) {
            Logger.getLogger(ConnectionFactory.class.getName()).log(Level.SEVERE, null, ex);
        }
        return conn;
    }

    public static void closeConnection(Connection conn, PreparedStatement ps) {
        if (conn != null) {
            try {
                ps.close();
                conn.close();
            } catch (SQLException ex) {
                Logger.getLogger(ConnectionFactory.class.getName()).log(Level.SEVERE, null, ex);
            }
        }

    }

    public static void closeConnection(Connection conn, PreparedStatement ps, ResultSet rs) {
        if (conn != null) {
            try {
                rs.close();
                closeConnection(conn, ps);
            } catch (SQLException ex) {
                Logger.getLogger(ConnectionFactory.class.getName()).log(Level.SEVERE, null, ex);
            }
        }

    }
}

Obrigado…

uma transação por request é uma solução, onde para uma request vc usa apenas uma conexao e uma transacao independente da qtde de daos e etc.

Imagine o cenario:

o usuário faz a requisição para a action que usa seu dao N vezes, ao invés de abrir N conexões como está sendo feito no DAO, vc deve usar uma unica conexao para que tudo esteja no contexto de uma unica transação.

entao para melhorarmos podemos podemos criar essa conexao e abrir a transacao na action, mas esse interesse é ortogonal, quase tdas suas actions teriam esse codigo repetido, sem contar que nao é responsabilidade da action. Crie um filtro, abra a conexão e a transação e passe a conexão para a action através do request. Lá vc instancia seu DAO com essa conexão.

Entendeu?

abrassss

ps uma boa prática é: abriu, feche!

entao como seu filter é responsável por abrir sua conexão e transação, então ele é responsável por fechar.

abrasss

Renan,
Fiz como vc descreveu acima:

uma boa prática é: abriu, feche!

Porem, o erro ainda persiste. Parei para analisar o SQLSTATE, me parece que é o problema esta na hora do commit do UPDATE.

sqlcode: -911
sqlstate: 40001
rollback da transação devido a um conflito.

Lembrando que, ja estou usando o set COMMIT

[b]
"SQL0911N Foi feito o rollback da transação
atual devido a um conflito ou
estouro de tempo ocioso.

Código de razão ?<código-razão>?.
Explicação: A unidade de trabalho atual foi
envolvida em uma disputa não resolvida pelo
uso de um objeto e precisou ser feito o rollback.

Os códigos de razão são os seguintes:

2 rollback da transação devido a um
conflito.

68 rollback da transação a estouro de
tempo ocioso de bloqueio.

72 a transação retrocedeu devido a um erro
referente a um Gerenciador do DB2

Data Links envolvido na transação.
Nota: As alterações associadas à unidade de
trabalho deverão ser fornecidas
novamente.

Será feito o rollback da aplicação para o
COMMIT anterior.

Resposta do Usuário: Para auxiliar a evitar um
conflito ou estouro de tempo ocioso de bloqueio,
emita, se possível, operações COMMIT
freqüentes para uma aplicação de execução
longa, ou para uma aplicação com probabilidade
de encontrar um conflito.
Usuários do sistema federado: o bloqueio pode
ocorrer no servidor federado ou na fonte de
dados. Não existe mecanismo para detectar
bloqueios que estendam-se sobre fontes de dados
e, potencialmente, sobre o sistema federado. É
possível identificar a fonte de dados que está
causando a falha na solicitação (consulte o guia
de determinação de problemas para determinar a
fonte de dados que está causando a falha no
processo da instrução SQL).
Os conflitos são freqüentemente normais ou
esperados enquanto se processam certas
combinações de instruções do SQL. É
recomendado que você projete aplicações para
evitar conflitos ao máximo possível.

sqlcode: -911
sqlstate: 40001"[/b]

poste sua nova implementação

abs

ConnectionFactory:

[code]package br.com.conexaodb2.connection;

import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
*

  • @author
    */
    public class ConnectionFactory {

// private

private static Connection conectar() {
    try {
        Class.forName("com.ibm.db2.jcc.DB2Driver");
       String url = "jdbc:db2://####";
       return DriverManager.getConnection(url, "##", "###");
    } catch (SQLException ex) {
        Logger.getLogger(ConnectionFactory.class.getName()).log(Level.SEVERE, null, ex);
        return null;
    } catch (ClassNotFoundException ex) {
        Logger.getLogger(ConnectionFactory.class.getName()).log(Level.SEVERE, null, ex);
        return null;
    }
}

public static Connection getConnection() {
    Connection conn = null;
    try {
        if (conn == null || conn.isClosed()) {
            conn = conectar();
        }
    } catch (SQLException ex) {
        Logger.getLogger(ConnectionFactory.class.getName()).log(Level.SEVERE, null, ex);
    }
    return conn;
}

public static void closeConnection(Connection conn, PreparedStatement ps) {
    if (conn != null) {
        try {
            ps.close();
            conn.close();
        } catch (SQLException ex) {
            Logger.getLogger(ConnectionFactory.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

}

public static void closeConnection(Connection conn, PreparedStatement ps, ResultSet rs) {
    if (conn != null) {
        try {
            rs.close();
            closeConnection(conn, ps);
        } catch (SQLException ex) {
            Logger.getLogger(ConnectionFactory.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

}

public static void closeConnection2(Connection conn, CallableStatement cs) {
    if (conn != null) {
        try {
            cs.close();
            conn.close();
        } catch (SQLException ex) {
            Logger.getLogger(ConnectionFactory.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

}

}[/code]

Action:

[code] if (tipoConsulta.equals(“1”)) {
String fonte = request.getParameter(“fonte”).trim();
String checkteste[] = request.getParameterValues(“checkteste”);
String nomeUsuario = request.getParameter(“nomeUsuario”);
String idUsuario = request.getParameter(“idUsuario”);
int contador = 0;

                Integer IidUsuario = Integer.parseInt(idUsuario);
                String IdDocContabil;
                String[] x = checkteste;

           
                System.out.println("UPDATE----------------------------------------------");
                for (int i = 0; i < x.length; i++) {
                    IdDocContabil = x[i];

                    contador = contador + 1;
                    if (salvar(IdDocContabil, fonte, 1, IidUsuario, nomeUsuario)) {

                        request.setAttribute("alertUpFr", "Atualizado com Sucesso!!!");

                    } else {

                        request.setAttribute("alertUpFr", "Erro ao Atualizar Fonte!!!");

                    }

                }

                String dtFinal = (String) request.getSession().getAttribute("dtFinal");
                String dtInicial = (String) request.getSession().getAttribute("dtInicial");
                String idDetalhe = (String) request.getSession().getAttribute("idDetalhe");
                String idTipoDoc = (String) request.getSession().getAttribute("idTipoDoc");


                String executivo = request.getParameter("executivo").trim();
                String fmas = request.getParameter("FMAS").trim();
                String saude = request.getParameter("saude").trim();
                String educacao = request.getParameter("educacao").trim();
                String fundeb = request.getParameter("FUNDEB").trim();



                request.getSession().removeAttribute("lstUFR");
                request.getSession().removeAttribute("lstFonte");

                request.getSession().removeAttribute("vlNumProc");
                request.getSession().removeAttribute("vlNumOrd");
                request.getSession().setAttribute("vlNumProc", "");
                request.getSession().setAttribute("vlNumOrd", "");


                request.getSession().setAttribute("lstUFR", new updateFonteRecursoDAO().getUFRs(idDetalhe,
                        dtInicial,
                        dtFinal,
                        idTipoDoc,
                        executivo,
                        fmas,
                        saude,
                        educacao,
                        fundeb));

                request.getSession().setAttribute("lstFonte", new updateFonteRecursoDAO().getIdDetalhe(idDetalhe,
                                                                                                       dtInicial,
                                                                                                       dtFinal));

                //System.out.println("quantidade de updates : ");
                //System.out.println(contador);

                request.setAttribute("msg", "Fonte Atualizada");
                request.getRequestDispatcher("mostraResultado.jsp").forward(request, response);


                return;

            }  [/code]

DAO:

  ...
   // ------------------------------------------------------------------------- UPDATE - FONTES
    public boolean upIdDetalheUFRs(String IdDocContabil,
            String fontel,
            int tipoUpdate,
            int idUsuario,
            String nomeUsuario) {

        Connection conn = null;
        CallableStatement cs = null;
        boolean isOk = false;

        conn = ConnectionFactory.getConnection();
        if (conn == null) {
            return isOk;
        }

        sql = UFRQuerry.upIdDetalheUFRs(IdDocContabil, fontel, tipoUpdate, idUsuario, nomeUsuario);

        try {
            conn.setAutoCommit( false );
            cs = conn.prepareCall (sql);
            cs.execute();
            isOk = true;
        } catch (SQLException ex) {
            Logger.getLogger(updateFonteRecursoDAO.class.getName()).log(Level.SEVERE, null, ex);
        } finally {
           
            ConnectionFactory.closeConnection(conn, cs);
        }

        return isOk;
    }
  ...

Bom dia galera.

Logo acima postei as 3 classes que uso para realizar o update.

Alguém poderia opinar sobre meu problema? Preciso muito concluir isso.

Obrigado.

n eh legal colocar daos na session, a connection factory ta meio estranha tb…

mas enfim, n achei onde vc comita a transacao…

abrasss

lembre qm abre o recurso é responsavel por fecha-lo

sua connection factory fecha statements e etc. nao eh legal isso.

ter getConnection e conectar é meio redundante

tratar erros do dao retornando boolean, a nao ser q tenha um bom motivo pra isso, nao é muito elegante tb, procure usar exceções

sao apenas algumas dicas

abrassss