Por que eu devo ler este artigo:

Em aplicações empresariais geralmente existem algumas operações que devem ser executadas com um extremo nível de garantia. Isso quer dizer que o sistema realiza atividades críticas, que devem ser concluídas efetivamente. Mas se por acaso, algum empecilho impedir a conclusão da atividade, esta deve ser abortada desde seu início, retornando a aplicação para um estado estável e deixando claro que a atividade não foi realizada.

Como exemplo, podem-se tomar as transações bancárias. Quando uma pessoa deseja fazer uma transferência de um valor de sua conta para outra conta. De maneira alguma o sistema pode executar esta operação e não concluí-la de maneira completa, seja qual for o motivo. Ou a transferência é concluída com sucesso, ou ela é inteiramente abortada. Falhas no cumprimento deste princípio em um sistema bancário, traria um caos financeiro por problemas que viessem ocorrer durante a operação de transferência.

Conclui-se então que existem realmente atividades críticas que sistemas empresariais devem ser capazes de gerenciar. E justamente para este gerenciamento que o conceito das transações é aplicado.

Este artigo tratará sobre a manipulação de transações em aplicações corporativas Java, utilizando EJB.

UM POUCO MAIS SOBRE O CONCEITO DE TRANSAÇÕES

Uma transação corresponde a um conjunto de operações que devem ser executadas como uma única operação (ou todas são executadas ou nenhuma). Não seguir este princípio em transações, gera possibilidade de falha de integridade de dados.

Na Listagem 1, pode-se observar um pseudocódigo com uma representação de uma transação, exemplificando uma transferência bancária bastante simples.

Listagem 1: Pseudocódigo de uma transferência bancária

Início da transação;
Processamento bancário: verifica saldo da pessoa A;
Processamento bancário: retira saldo da pessoa A;
Processamento bancário: injeta saldo na pessoa B;
Efetivar transação.

Observando o código acima, fica claro que se algum dos passos não for executado, e a operação for considerada como concluída, poderiam ocorrer erros como: saldo ser retirado da pessoa A mesmo que esta não o tenha, ou então o saldo da pessoa A é retirado e não é injetado na pessoa B perdendo-se o valor. Reforça-se o conceito de que a transação deve trabalhar como uma unidade, mesmo sendo composta por diversas operações.

No caso de aplicações, as transações devem ser finalizadas como efetivadas ou abortadas. No primeiro caso, os dados manipulados durante a transação são gravados permanentemente, no entendimento que o processo ocorreu dentro do normal. No segundo caso, algo ocorreu fora da normalidade e qualquer manipulação de dados durante a transação é descartada.

Este é o conceito básico de transações, independente de linguagem ou ambiente.

TRANSAÇÕES COM EJB CONTAINER

O EJB Container oferece ao programador ao menos duas maneiras de trabalhar com transações: Container-Managed Transactions (transações gerenciadas pelo container) e Bean-Managed Transactions (transações gerenciadas pelo bean). À diante serão tratadas estas duas maneiras.

TRANSAÇÕES GERENCIADAS PELO CONTAINER

Container-Managed Transactions, é o tratamento para as transações recomendado para a maioria dos casos. Corresponde em dar ao container a autonomia para tratar as transações. Estas transações gerenciadas pelo container podem ser aplicadas em qualquer tipo de bean: Entity, Session ou Message-Driven. O desenvolvimento da aplicação se torna um tanto mais simplificado pois o código do enterprise bean não fica explicitamente demarcado com tratamentos de transação. O programador inclusive não precisa e nem deve utilizar as instruções básicas de início e fim de transação (begin e commit), cabendo ao container decidir quando serão necessárias.

A Listagem 2 apresenta um código exemplo com transação de banco de dados, e em seguida a explicação.

Listagem 2: Transação gerenciada pelo Container

@Stateless
@TransactionManagement(TransactionManagementType.CONTAINER)
public class ServicosStateless implements ServicosStatelessLocal {
    
    @PersistenceContext
    EntityManager em;   

    @Override
    public void gravarItem(Item item){
        if (item.getIdItem() == null){
            em.persist(item);
            em.flush();
        }else{
            em.merge(item);
            em.flush();
        }
        System.out.println("Item Gravado: " + item.getIdItem() + item.getDescricao());
    }
}

Na Listagem 2, nota-se uma classe ServicosStateless anotada com @TransactionManagement(TransactionManagementType.CONTAINER)”. Esta anotação define que a classe possui todas suas transações gerenciadas pelo container. Como o exemplo trata de transação de banco de dados, foi necessário dizer ao container que deseja-se utilizar um contexto de persistência EntityManager, apenas declarando-o no início do código. O container fica encarregado de disponibilizar o recurso conforme for necessário, inclusive abrindo e encerrando as conexões. Foi criado um método gravarItem() que após uma verificação manda gravar (persist) ou mesclar (merge) o objeto no banco, sem demais instruções de transação. A instrução flush é utilizada para sincronizar o registro do banco de dados com o objeto em memória, que por vezes sofre alterações como a criação de uma chave primária no banco, que não tem haver com a transação mas sim com sincronização de objetos. E por último são apresentados dados do objeto.

Como visto, o EJB possui comportamento padrão no gerenciamento da transação. Mas em alguns momentos, surge a necessidade de ajustar este gerenciamento padrão do container. Por exemplo, nem sempre é necessário que todos os métodos do bean sejam executados abrindo novas conexões, pois alguns nem as utilizam e outros devem aproveitar uma transação já aberta. E nisto entram os Atributos de Transação (Transaction Attributes).

Os Transaction Attributes simplesmente controlam o escopo das transações. Para ntender a importância dos Atributos de Transação, deve-se observar o diagrama na Figura 1. Nota-se a existência de um método A() em um bean e um método B() em outro bean. Uma transação é iniciada com o método A() e então é invocado o método B(). Este método B() pode requerer a criação de uma nova e exclusiva transação, ou então a necessidade do uso da mesma transação, ou dispensar o uso de transações. Neste caso, quem tem o poder de definir este comportamento em relação às transações para o método B() são os Transaction Attributes.

 Diagrama de exemplo para Transaction Attributes
Figura 1: Diagrama de exemplo para Transaction Attributes.

Os Transaction Attributes são utilizados como simples anotações em cada método ou na classe. Na listagem 3 segue o exemplo do código que utiliza um atributo de transação, anotado como @TransactionAttribute(TransactionAttributeType.REQUIRED).

Listagem 3: Atributos de Transações

@Override
    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    public void gravarItem(Item item){
        if (item.getIdItem() == null){
            em.persist(item);
            em.flush();
        }else{
            em.merge(item);
            em.flush();
        }
        System.out.println("Item Gravado: " + item.getIdItem() + item.getDescricao());
    }

Existem diferentes atributos de transação, sendo eles: REQUIRED, REQUIRESNEW, MANDATORY, NOTSUPPORTED SUPPORTS e NEVER. Adiante será explicado o básico de cada um dos atributos.

Anotando o atributo REQUIRED em um método, se o cliente estiver com uma transação em andamento e chamar pelo método, este será executado utilizando da transação já aberta, ou caso não houver transação em andamento, criará automaticamente um nova para o uso.

Com o atributo REQUIRESNEW, se no cliente uma transação já estiver aberta, esta será suspensa, e uma nova transação será criada para o uso do método. Após o fim da execução do método, a transação que foi suspensa é retomada. Mas caso o cliente não tenha uma transação em andamento, será criada uma para a execução do método. O diferencial em relação ao REQUIRED, é que será garantido que o método será executado sempre com uma nova transação, sem aproveitar alguma que já esteja em andamento.

O MANDATORY, exige que ao executar o método haja alguma transação em andamento pelo cliente, para que seja usada. Caso não haja transação em andamento, o Container disparará uma exceção TransactionRequiredException.

No caso do atributo NOTSUPPORTED, o método possui garantia que será executado sem transações abertas pelo cliente. Se alguma estiver em andamento, esta será suspensa até o término do método.

Já o atributo SUPPORTS faz com que o método seja executado com ou sem transação em andamento. Se alguma existir, tudo bem, e se não existir, tudo bem também. Mas deve ficar claro que nenhuma transação será criada caso não exista, e sim que o método não depende de transação alguma, e as transações não atrapalham a execução.

E o atributo NEVER, obriga a ausência de transações na chamada do método. Se alguma existir, esta não será suspensa, mas sim será disparada uma exceção RemoteException.

Tendo entendido os atributos, agora é importante entender o funcionamento do roolback (abortamento) das transações gerenciadas pelo container. Existem duas formas de abortar uma transação: ou por uma exceção de sistema que lançará um rollback automaticamente ou então invocando o método setRollbackOnly da interface EJBContext.

Quando exceções de aplicação são disparadas, o rollback não é automático, pois justamente a regra de negócio fica a cargo do programador. Desta forma, ao criarmos um método de transação bancária, por exemplo, pode-se fazer diversas verificações em busca de falhas, e caso sejam encontradas, então deve-se disparar um rollback manualmente. Isso garante a integridade da regra de negócio, sendo que se algo de errado for percebido, a transação será desfeita com todas as suas alterações de dados.

Para um estudo mais aprofundado recomenda-se entender o funcionamento da sincronização de variáveis de instância de Session Bean's.

TRANSAÇÕES GERENCIADAS PELO BEAN

Bean-Managed Transactions, corresponde em trazer a responsabilidade do gerenciamento da transação para o bean, ou seja, para o próprio programador decidir o início, o abortamento e o fim da transação. Qualquer erro de programação no momento de projetar este tratamento, pode resultar em falhas graves de integridade de dados. Não é recomendado para a maioria dos casos, já que o gerenciamento do próprio container é excelente e livre de falhas.

O pseudocódigo da Listagem 4, ilustra um pouco o controle que o programador detém em mãos, quando decide controlar as transações de uma forma bem personalizada.

Listagem 4: Pseudocódigo exemplo para transações gerenciadas pelo Bean

Início da transação;
Atualizar dados da tabela A;
Se (condição)
    Efetivar transação
Senão se (condição)
    Atualizar tabela B e C;
    Efetivar transação;
Senão
    Abortar transação;
    Iniciar transação;
    Atualizar tabela D;
    Efetivar transação.

Nota-se a flexibilidade das transações, e que o programador tem em mãos total controle de quando criar, abortar e efetivar uma ou mais transações em um Bean.

Durante a programação, no uso de transações gerenciadas pelo bean para session ou message-driven beans, deve-se optar pelo uso de transações JDBC ou JTA. Transações JDBC são controladas pelo gerenciador de transações do Database Management System (DBMS). E seu uso é indicado para ocasiões em que existe código legado dentro de um session bean, requerendo exclusivamente o uso do JDBC.

O uso de uma transação JDBC consiste basicamente em invocar o método commit e rollback da interface java.sql.Connection, não necessitando a inicialização manual da transação.

Transações JTA (Java Transaction API), tem como principal objetivo permitir o uso das transações no código sem que sejam dependentes da implementação do gerenciador de transações. O J2EE implementa o gerenciador de transações com o JTS (Java Transaction Service), mas seus métodos não são utilizados diretamente enquanto usa-se o JTA. Justamente, pelo fato do JTA abstrair estas chamadas de JTS de nível relativamente mais baixo. O JTA é preferível comparado ao JDBC, na questão de que uma transação JTA pode interagir com diferentes bancos de dados em uma única implementação, enquanto o JDBC requisita um suporte específico.

Para utilizar JTA, deve-se utilizar os métodos begin, commit e rollback da interface javax.transaction.UserTransaction. Na Listagem 5 segue um código de exemplo na utilização de JTA, e em seguida explicação.

Listagem 5: Exemplo do uso de transação JTA


Saiba mais: JDBC tutorial


@Stateless
@TransactionManagement(TransactionManagementType.BEAN)
public class ServicosStatelessBEAN implements ServicosStatelessLocal {

    @PersistenceContext
    EntityManager em;
    @Resource
    UserTransaction ut;

    @Override
    public void gravarItem(Item item) {
        
        try {
            this.ut.begin();
        } catch (Exception e) {
            throw new EJBException();
        }
        
        if (item.getIdItem() == null) {
            em.persist(item);
            em.flush();
        } else {
            em.merge(item);
            em.flush();
        }

        try {
            this.ut.commit();
        } catch (Exception e) {
            try {
                this.ut.rollback();
            } catch (Exception e1) {
                throw new EJBException(e1);
            }
        }  
        
        System.out.println("Item Gravado: " + item.getIdItem() + item.getDescricao());
    }
}

Nota-se que a classe está anotada como “@TransactionManagement(TransactionManagementType.BEAN)” trazendo a responsabilidade do controle das transações para o programador. A interface UserTransaction foi declarada e anotada como “@Resource”. O método gravarItem é iniciado com a execução de ut.begin() que inicia uma transação. Em seguida faz-se uma verificação e decide-se entre utilizar merge e persist. Por último, utiliza-se ut.commit()para confirmar a transação, e caso ocorra algum problema nesta confirmação o ut.rollback() é invocado que irá desfazer a transação. Observa-se que estes novos métodos estão envolvidos nos blocos try e catch.

CONCLUSÃO

Transações em um sistema corporativo são praticamente inevitáveis. Saber como interagir com estas é de suma importância, já que pode definir o rumo da integridade de dados. Apesar de ser possível utilizar o controle manual das transações demonstrando-se a flexibilidade do código, o gerenciamento pelo Container mostra-se extremamente viável, já que percebe suas responsabilidades de maneira correta e automática.

Leia nosso artigo sobre Transações com EJB na Revista Java Magazine 101


Saiu na DevMedia!

  • React Native: do Hello World ao CRUD:
    React é um framework JavaScript criado pelo Facebook para facilitar a construção de interfaces de usuário. React Native, da mesma empresa, nos permite criar aplicações mobile nativas utilizando JavaScript, na lógica, e React, para estruturar as views.

Saiba mais sobre Java ;)

  • Guias de Java:
    Encontre aqui os Guias de estudo que vão ajudar você a aprofundar seu conhecimento na linguagem Java. Desde o básico ao mais avançado. Escolha o seu!
  • Guia de Linguagem Java:
    Neste Guia de Referência você encontrará todo o conteúdo que precisa para começar a programar com a linguagem Java, a sua caixa de ferramentas base para criar aplicações com Java.
  • Guia de REST e Java:
    Devido a sua simplicidade, os Web Services RESTful têm se tornado cada vez mais populares. Neste guia você encontrará os conteúdos que precisa para dominar esse modelo que permite a construção de serviços menores a APIs completas.

5. BIBLIOGRAFIA