Desenvolvimento - SQL

Programação de Banco de Dados - parte 6

Este é o sexto artigo sobre Banco de Dados e vamos continuar abordar o componente ADO. Agora vamos abordar Transações e como implementar isso usando ADO.

por Renato Mattos



Este é o sexto artigo sobre Banco de Dados e vamos continuar abordar o componente ADO. No quinto artigo falamos como usar o ADO para conectar ao banco e como consulta, incluir, excluir e atualizar registros. Agora vamos abordar Transações e como implementar isso usando ADO.

Primeiro o que seria uma transação?

Uma transação é uma unidade atômica de processamento que inclui uma ou mais operações de leitura ou escrita no banco de dados. Algumas características de transações:

- Atomicidade: como principal característica sua execução tem que ser integral, ou nenhuma parte deve ser executada, ou seja, você utiliza transação quando determinada ação envolve atualização de mais de uma tabela do banco.E para garantir que todas as atualizações sejam feitas utiliza-se transação. Se uma ação pode ser separada em ações menores, então temos duas (ou mais) transações, ou seja, se uma ou mais ações podem falhar sem deixar o banco de dados em estado inconsistente, estas ações não devem ser parte da mesma transação.

- Consistência: A execução de uma transação deve levar ao banco de dados de um estado consistente a outro também consistente. Uma transação é consistente se não violar a integridade do banco de dados. Se a transação tiver êxito ou falhar, ela deve deixar o banco de dados em um estado consistente.Se uma transação falhar, ela precisa desfazer todas as alterações temporárias e deixar o banco de dados no estado em que ele estava antes que a transação iniciou.

- Isolamento: Uma transação não deve tornar suas atualizações visíveis a outras transações antes do seu fim. Uma transação que debita uma conta e credita outra deve ser totalmente transparente. Quando isso é feito, a transação precisa atualizar o banco de dados de uma só vez. Por exemplo, se um usuário solicitar o saldo de uma conta e a mesma está sofrendo uma transação o banco de dados só deve retornar o valor do saldo depois que completar a atualização, assim dessa forma durante a transação algumas linha são bloqueadas.

- Durabilidade: Após o termino de uma transação, suas atualizações não podem ser perdidas por causa de falhas futuras. Se todas as ações forem realizadas com sucesso isso não significa sucesso na transação, pois precisa gravar os dados de volta ao disco. Caso ocorra uma falha no disco a transação não é válida. Então antes que uma transação seja completada ela deve verificar se as alterações foram gravadas com sucesso antes de terminar.

Dessa foram ao utilizar transação você garante que será executado cada linha do código pertencente à transação. Caso ocorra alguma falha durante a execução da transação devem ser canceladas todas as alterações até a ocorrência da falha.Assim você mantém a integridade do banco de dados além da consistência dos dados da sua aplicação.

As transações podem ser divididas em dois tipos: de integridade dos dados e de consistência da ação. Integridade e consistência do banco podem dizer a mesma coisa, mas quando me refiro à integridade dos dados é em relação às regras impostas nos relacionamentos.

Exemplo:

Temos uma tabela VENDA que armazena os dados da venda como: valor, data, caixa e etc. Outra tabela PRODUTOS com os dados dos produtos cadastrados no estoque. E por ultimo uma tabela PRODUTOS VENDIDOS que relaciona a venda com o produto assim:

Tabela Venda
IdVenda= 0001
Valor= R$ 150,00
Caixa= Fulano

Tabela Produto
IdProduto= 200
Valor= R$ 5,00
Estoque=100

Tabela ProdutosVendidos
IdVenda= 0001
IdProduto= 200
Qtde=30

Então a integridade seria quebrada caso apaga-se o produto 200 da tabela produto e não propagar a exclusão para a tabela ProdutosVendidos, ou incluir um registro na tabela ProdutosVendidos onde o IdVenda não existe na tabela Venda e a mesma coisa para o IdProduto.E sobre a consistência da ação seria ocorrendo a venda 0001 gravar na tabela ProdutosVendidos os itens da venda e para cada item vendido atualizar o estoque.

Sendo que as regras de integridade o SGBD trata para nós sendo transparente. Claro algumas regras de integridade devem ser impostas ao banco sendo por trigger, procedures ou por regras oferecidas diretamente pela interface do banco, como por exemplo: manter a integridade do relacionamento propagando exclusão ou alteração no Access.

Não vou abordar as transações de integridade do banco, sendo que podem ser impostas na própria aplicação pelo que vai ser mostrado aqui, no entanto um projeto que deixa do lado da aplicação a integridade referencial do banco tem o banco de dados mal projetado.

Vamos então nos preocupar somente com as transações das "regras do negócio". Essas transações podem passar como despercebidas no banco de dados.Isso depende do projeto, caso seja definido que as regras do negócio sejam do lado do banco então as transações seriam através de script"s em SQL definidos por trigges e procedures no banco. Aonde a aplicação iria apenas executá-las e aguardar o seu retorno. O ADO nesse caso somente iria "participar" no comando da execução da trigger ou procedure.

Existe uma diferença entre manter a integridade do banco e manter as regras do negocio. As regras de integridade do banco devem estar definidas no próprio banco, enquanto as regras do negócio podem ser dividas entre banco ou aplicação, ou então definidas em uma terceira camada, como abordamos no artigo 2.

O que vou vamos ver nesse artigo são transações que definem a regra do negócio fora do banco de dados usando ADO. O que é muito simples, pois todas as características de uma transação o ADO juntamente com o SGBD tratam para nós. Para nós basta informar quando se inicia uma transação, o seu fim e no tratamento de erro cancelar a transação. Nossa atenção deve voltar para a analise do problema e verificar para cada ação no banco se envolve ou não uma transação para manter a regra do negócio.

Agora chega de teoria e vamos finalmente a prática. Os comandos de transação são métodos da classe Connection. As ações da transação têm que usar o mesmo objeto (variável da classe connection) que chamou o método de inicio da transação. Conforme abordado no quinto artigo declare a variável connection, exemplo:

"Objeto de Conexão com Banco
Public pVarConBanco As New ADODB.Connection

pVarConBanco.BeginTrans "Comando de inicio de transação
pVarConBanco.CommitTrans "Comando de término de transação
pVarConBanco.RollbackTrans "Comando que cancela a transação.

Os comandos do ADO para transação se resume nesses. Agora um exemplo de uma transação:

Private Function NovaConta(ByVal IdMovimento As Long) As Long
    "Retorna Id da Nova Conta(IdVenda)
    Dim lVarIdVenda As Long
    
    On Error GoTo T_Erro
    pVarConBanco.BeginTrans
    On Error GoTo T_Transacao
    With pVarRegBanco
        .CursorLocation = adUseServer
        .Open "Insert Into Venda Values(……)", pVarConBanco, adOpenStatic, adLockOptimistic
        
        On Error GoTo T_Consulta
        "Pega Id da Venda
        .Open "Select  @@Identity From Venda", pVarConBanco, adOpenStatic, adLockReadOnly
        lVarIdVenda = pVarRegBanco(0)
        .Close
        
        On Error GoTo T_Transacao
        "Relaciona Conta com Mesa
        .Open "Insert into ContaMesa Values(………)", pVarConBanco, adOpenStatic, adLockOptimistic
        
        On Error GoTo T_Alteracao
        "Altera Status Mesa
        .Open "Select Status From Mesa Where IdMesa = " & Me.IdMesa & "", pVarConBanco, adOpenStatic, 
adLockOptimistic
        !Status = EnStatus.Reservada
        .Update
        .Close
    End With
    pVarConBanco.CommitTrans   
       
    Exit Function
T_Erro:
    MSG_ERRO "NovaConta"
    Exit Function
T_Transacao:
    Set pVarRegBanco = Nothing
    pVarConBanco.RollbackTrans
    MSG_ERRO "NovaConta"
    Exit Function
T_Consulta:
    pVarRegBanco.Close
    Set pVarRegBanco = Nothing
    pVarConBanco.RollbackTrans
    MSG_ERRO "NovaConta"
T_Alteração:
    pVarRegBanco.CancelUpdate
    Set pVarRegBanco = Nothing
    pVarConBanco.RollbackTrans
    MSG_ERRO "NovaConta"
End Function

Temos um exemplo completo de uma transação que envolve:

- Adicionar uma venda na tabela VENDAS.
- Pegar o código autonumerico do registro que acabamos de adicionar.
- Inserir esse código da venda na tabela CONTAMESA
- Alterar o status da mesa colocando ela como ocupada.

Logo para criar uma venda deve ter todas as alterações acima, se uma falhar vai quebrar a regra do negócio.

Comentando o código:

» pVarConBanco.BeginTrans "Inicio da transação.

Para cada alteração da transação vamos efetuar pela classe Recordset abordada no artigo cinco.

» Inclusão do registro na tabela Vendas, repare que o objeto de conexão dessa linha é o mesmo que iniciou a transação. Usando Insert pelo variável Recordset não é necessário fechar o cursor

pVarRegBanco.Open "Insert Into Venda Values(……)", pVarConBanco, adOpenStatic, adLockOptimistic

» Esta consulta com o campo especial @@Identity vai retornar o código autonumeric do registro que foi adicionado durante esta transação.Esse consulta só funciona se usar transação.

pVarRegBanco.Open "Select  @@Identity From Venda", pVarConBanco, adOpenStatic, adLockReadOnly
lVarIdVenda = pVarRegBanco(0) "Atribui a variável local o valor retornado pela consulta
pVarRegBanco.Close "Fecha o cursor

» Aqui fazemos outra inclusão, sendo que agora na tabela ContaMesa, usando o valor da variável lvarIdVenda.

PVarRegBanco.Open "Insert into ContaMesa Values(" & lvarIdVenda & ")", pVarConBanco, adOpenStatic, 
adLockOptimistic

» Fazemos agora uma alteração no Status da Mesa na tabela Mesa.

A consulta trazendo o registro desejado.

PVarRegBanco.Open "Select Status From Mesa Where IdMesa = " & Me.IdMesa & "", pVarConBanco, 
adOpenStatic, adLockOptimistic
PVarRegBanco!Status = EnStatus.Reservada"Atribuição do novo valor ao campo.
PVarRegBanco .Update"Confirmar a alteração.
PVarRegBanco.Close "Fecha o cursor.

» Por fim confirmamos a alteração.Somente neste ponto que todas as alterações serão confirmadas.

pVarConBanco.CommitTrans

Tratamento dos Erros

» Ao fazer uma transação deve ter uma atenção maior ao tratamento de erro. Repare que antes de chamar o BeginTrans tenho um tratamento de erro, onde não faz nada somente chama um procedimento padrão que exibe uma mensagem de erro para o usuário. Esse tratamento é necessário caso a conexão com o banco tenha sido perdida.

On Error GoTo T_Erro
pVarConBanco.BeginTrans

» Após o BeginTrans temos outro tratamento de erro (T_Transação) , onde colocamos o comando pVarConBanco.RollbackTrans. Não pode colocar esse comando no tratamento T_Erro, pois vai ocorrer um erro runtime, pois não pode chamar RollBack sem ter uma transação iniciada, logo a aplicação vai abortar pois deu erro dentro do tratamento de erro.

» Antes da consulta do código autonumeric alteramos novamente a referencia do erro (T_Consulta). Para pode chamar o comando .Close do Recordset.

» Depois retornamos o tratamento de erro para T_Transação, pois o próxima ação é um Insert usando a classe recordset. Logo no tratamento de erro dessa ação não pode estar o comando Close senão gera erro runtime.

» Depois temos outro tratamento diferenciado antes da atualização do status mesa. Isso para poder cancelar a atualização pelo comando CancelUpdate. Repare que neste tratamento temos também o comando Rollback, pois já foi iniciada a transação e ocorrendo qualquer tipo de erro devemos cancelar.

O que dá mais trabalho em uma transação é fazer o tratamento de erro como vocês podem ver, pois a própria transação em si se resume em três comando do ADO. Um comando interessante que vimos foi a consulta usando o campo especial @@Identity, onde retorna o código gerado na inclusão. Sendo que só funciona usando transação.

Assim fico por aqui, fechando o assunto transação. Espero que este artigo ajude mais um pouco a vocês. No próximo artigo vou falar como executar triggers e procedures pelo ADO. Até a próxima.

Renato Mattos

Renato Mattos - Diretor de Desenvolvimento Foco Sistemas.