Java Transaction API (JTA) na Plataforma Java EE 7

Conheça neste artigo o Java Transaction API, que está disponível na plataforma Java EE 7. Veja todas as características da Java Transaction API, as funcionalidades oferecidas e diversos exemplos práticos de como funciona o JTA.

Fique por dentro

A Java Transaction API ou simplesmente JTA, que são as iniciais do nome da API, está definida na especificação JSR 907 que também pode ser baixada no site oficial da Oracle ou no site da Java Community Process (veja seção Links).

A JTA especifica interfaces locais que são utilizadas entre um gerenciador de transação e as partes que estão envolvidas em um sistema de transação distribuído como: a aplicação, o gerenciador de recursos e o servidor de aplicação.

Já viu os novos cursos online de Java que a DevMedia lançou? Não deixem de conferir!

A API define uma interface de alto nível, além de anotações, e escopo para demarcar os limites da transação em uma aplicação transacional.

Uma transação é uma sequência de operações que são tratadas como um bloco único e indivisível e segue basicamente quatro princípios: o primeiro deles é a Atomicidade onde as operações que são executadas dentro de uma transação devem ser executadas por inteiro, ou seja, todas as operações devem ser executadas, ou então nenhuma das operações deve surtir efeito; o segundo princípio é a Consistência onde a transação não deve afetar o estado dos dados relacionados entre si; o terceiro princípio é o Isolamento onde as transações em paralelo devem ser isoladas uma da outra, de forma que, por exemplo, se duas transações alterarem um mesmo registro de uma tabela, deve existir algum mecanismo que cuide do nível de isolamento nesse acesso concorrente; por fim, temos o princípio da Durabilidade onde se garante que os dados gravados não são perdidos mesmo em caso de reinício ou falha no sistema.


Saiba mais: Guia do Programador Java


Para fazer o download do JTA podemos utilizar o Maven conforme o código da Listagem 1.

Listagem 1. Código Maven para baixar a JTA.

<dependency> <groupId>javax.transaction</groupId> <artifactId>jta</artifactId> <version>1.2</version> </dependency>

Também podemos baixar a Java Transaction API diretamente do site da Oracle.

Podemos verificar que quando utilizamos transações simples, sem utilizar JTA, esses quatro princípios são assegurados pelo próprio sistema de gerenciamento de banco de dados (SGBD). Dessa forma, basta utilizarmos um acesso via JDBC ao Banco de dados que este se responsabilizará por criar uma transação local e transfere toda a responsabilidade ao SGBD para que ele se preocupe com a concorrência, paralelismo, integridade e com as falhas que possam ocorrer. Assim sendo, temos uma transação simples, onde o próprio Banco de dados tem domínio dos dados. Porém, existem muitas situações em que necessitamos criar uma transação para acessar dois sistemas diferentes. Nesse tipo de situação precisamos que tudo seja realizado numa transação única, e portanto precisamos de uma transação global. JTA foi criada e é largamente utilizada para esse tipo de transação.

Para utilizarmos JTA precisamos que tanto o Banco de Dados, quanto o próprio driver JDBC suportem a especificação. Um exemplo onde ocorre esse suporte é no servidor de aplicação Weblogic e no banco de dados Oracle que também oferece suporte a JTA no driver JDBC. A opção "JDBC -> Datasources" no console do Weblogic permite que possamos fazer as configurações necessárias para utilizarmos JTA em nossa aplicação.

Veremos no restante do artigo como se dá o funcionamento da JTA na plataforma Java EE 7 e outros conceitos relacionados a essa importante API que é utilizada em todas as aplicações de médio ou grande porte.

Transações Gerenciadas pelo Usuário (User-Managed Transactions)

A interface UserTransaction permite que o aplicativo controle os limites das transações programaticamente. Esta interface é tipicamente usada em EJBs com transações gerenciadas por bean ou também chamada de bean-managed-transactions (BMT).

Podemos obter um UserTransaction usando injeção de dependência como mostra o exemplo da Listagem 2.

Listagem 2. Exemplo de injeção de dependência na interface UserTransaction utilizando a anotação @Inject.

@Inject UserTransaction userTransaction;

Também podemos obter um UserTransaction utilizando uma pesquisa JNDI através do nome "java:comp/UserTransaction" conforme mostra o exemplo da Listagem 3.

Listagem 3. Exemplo de como obter um UserTransaction utilizando uma pesquisa JNDI.

Context context = new InitialContext(); UserTransaction userTransaction = (UserTransaction) context.lookup("java:comp/UserTransaction");

Segue na Listagem 4 um exemplo de como podemos demarcar uma transação após obtermos um UserTransaction.

Listagem 4. Demarcando uma transação utilizando uma instância de UserTransaction.

userTransaction.begin(); //Código dentro dos limites da transação... userTransaction.commit();

No código acima a chamada userTransaction.begin() que utiliza o método begin da classe UserTransaction é responsável por iniciar uma transação global e associar a transação com a thread chamada. A chamada userTransaction.commit() que utiliza o método commit da classe UserTransaction é responsável por completar a transação associada com a thread corrente. Todos os comandos entre begin e commit executam dentro de um escopo de transação.

Quando o método commit completa, a thread não é mais associada com uma transação. Porém, uma transação também pode ser revertida (rollback). Isto pode ser feito utilizando o método rollback conforme mostra o exemplo da Listagem 5.

Listagem 5. Exemplo de como podemos fazer um rollback da transação.

userTransaction.begin(); //Código dentro dos limites da transação... userTransaction.rollback();

Quando o método rollback completa, a thread não está mais associada com uma transação.

Outra operação de podemos realizar é alterar o valor de timeout associado com a transação iniciada pela thread corrente. O exemplo da Listagem 6 mostra um exemplo de como podemos fazer isso.

Listagem 6. Exemplo de como podemos alterar o timeout da transação.

userTransaction.setTransactionTimeout(3);

No código acima o timeout da transação é configurado para três segundos. Se o valor for zero, o serviço de transações restaura o valor padrão. Não é necessário o suporte às transações aninhadas.

Segue na Tabela 1 os métodos disponíveis na interface UserTransaction e o que faz cada um deles.

Método Tipo de Retorno Responsabilidade Exceções

begin()

void

Cria uma nova transação e associa essa transação com a thread atual.

NotSupportedException,

SystemException

commit()

void

Completa a transação associada com a thread atual.

RollbackException,

HeuristicMixedException,

HeuristicRollbackException,

java.lang.SecurityException,

java.lang.IllegalStateException,

SystemException

getStatus()

int

Obtém o status da transação associada com a thread atual.

SystemException

rollback()

void

Faz roll back da transação associada com a thread atual.

java.lang.IllegalStateException,

java.lang.SecurityException,

SystemException

setRollbackOnly()

void

Modifica a transação associada ao segmento atual de modo que o único resultado possível da transação é fazer um roll back.

java.lang.IllegalStateException,

SystemException

setTransactionTimeout(int seconds)

void

Modifica o valor do timeout associado com as transações que foram iniciadas pela thread atual com o método begin.

SystemException

Tabela 1. Métodos associados à interface UserTransaction.

A exceção NotSupportedException é lançada se a thread já está associada com uma transação e a implementação do Transaction Manager não suporta transações aninhadas. Uma exceção do tipo SystemException é lançada se o gerenciador de transação encontra uma condição de erro inesperada. Um RollbackException é lançado para indicar que a transação foi revertida (rolledback) ao invés de confirmada (commited). A exceção HeuristicMixedException é lançada para indicar que uma decisão heurística foi realizada e que algumas atualizações relevantes foram confirmadas (commited) enquanto outras sofreram rollback. A exceção HeuristicRollbackException é lançada para indicar que uma decisão heurística foi realizada e que algumas atualizações relevantes sofreram rollback. A exceção SecurityException é lançada para indicar que a thread não está permitida a confirmar (commit) a transação. A exceção IllegalStateException é lançada se a thread atual não está associada com uma transação.

Transações Gerenciadas pelo Container (Container-Managed Transactions)

A versão do JTA 1.2 introduz a anotação @javax.transaction.Transactional. Essa anotação possibilita que as aplicações possam demarcar os limites de uma transação declarativamente. Isto é feito utilizando beans CDI gerenciados, assim como as classes definidas como Managed beans, Servlets, JAX-RS e os endpoints JAX-WS.

As anotações podem ser especificadas tanto em nível de método como em nível de classe, no entanto, as anotações em nível de método sobrescrevem aquelas que estão em nível de classe. Segue na Listagem 7 um exemplo.

Listagem 7. Exemplo de como podemos utilizar a anotação @Transactional.

@Transactional public class MeuBean { //Mais código aqui... }

Neste código, todos os métodos do bean são executados em uma transação.

Segue na Listagem 8 outro exemplo um pouco diferente utilizando a anotação @Transactional em nível de método:

Listagem 8. Exemplo de como podemos utilizar a anotação @Transactional em nível de método.

public class MeuBean { public void meuMetodo1() { //Mais código aqui... } @Transactional public void meuMetodo2() { //Mais código aqui... } }

Neste código acima temos uma situação um pouco diferente, onde apenas o método meuMetodo2 é executado em um contexto de transação.

Este suporte é fornecido através de uma implementação de interceptores CDI que realizam o cancelamento da transação quando necessário, a recuperação, entre outras funcionalidades.

O interceptor "Transactional" intervém nas invocações de métodos de negócios e nos eventos do ciclo de vida. Os métodos do ciclo de vida são invocados em um contexto de transação não especificado a menos que o método esteja explicitamente anotado com @Transactional.

Os interceptores “Transactional” devem ter a prioridade "Interceptor.Priority.PLATFORM_BEFORE+200".

O elemento TxType da anotação “Transactional” fornece tipos de transação equivalente a semântica utilizada nos atributos de transação do EJB.

Segue na Tabela 2 uma tabela com os tipos de transação:

TxType Fora de um contexto de Transação Dentro de um contexto de Transação

REQUIRED (default)

O interceptor deve iniciar uma nova transação JTA, o método de execução do Managed Bean deve então continuar dentro desde contexto de transação e a transação deve ser completada pelo interceptor.

O método de execução do Managed Bean deve continuar dentro deste contexto de transação.

REQUIRES_NEW

O interceptor deve iniciar uma nova transação JTA, o método de execução do Managed Bean deve então continuar dentro deste contexto de transação, e a transação deve ser completada pelo interceptor.

O contexto de transação corrente deve ser suspenso, uma nova transação JTA será iniciada, o método de execução do Managed Bean deve então continuar dentro deste contexto de transação, a transação deve ser completada, e a transação anterior que foi suspensa deve ser retomada.

MANDATORY

Uma TransactionalException com uma TransactionRequiredException aninhada deve ser lançada.

O método de execução do Managed Bean continuará sob o contexto.

SUPPORTS

O método de execução do Managed Bean deve continuar fora de um contexto de transação.

O método de execução do Managed Bean deve continuar dentro deste contexto de transação.

NOT_SUPPORTED

O método de execução do Managed Bean deve continuar fora de um contexto de transação.

O contexto de transação corrente deve ser suspenso, o método de execução do Managed Bean deve continuar fora de um contexto de transação, e a transação anterior suspensa deve ser retomada pelo interceptor que suspendeu essa transação após o método de execução ser completado.

NEVER

O método de execução do Managed Bean deve continuar fora de um contexto de transação.

Uma TransactionalException com uma ValidTransactionException aninhada deve ser lançada.

Tabela 2. Tipos de Transações suportadas pela JTA.

Dessa forma, podemos alterar o tipo da transação especificando o valor de TxType, conforme mostra o exemplo da Listagem 9.

Listagem 9. Exemplo de como especificar o tipo da transação como REQUIRES_NEW.

@Transactional(TxType.REQUIRES_NEW) public class MeuBean { //Mais código aqui... }

Por padrão, as exceções checadas não resultam em rollback da transação, porém instâncias de RuntimeException e suas subclasses resultam em rollback da transação.

Um elemento que pode ser bastante útil é o “rollbackOn”. O elemento "rollbackOn" pode ser configurado para indicar exceções que devem marcar a transação para fazer rollback. Segue na Listagem 10 um exemplo de como podemos utilizá-la.

Listagem 10. Especificando o elemento rollbackOn.

@Transactional(rollbackOn={Exception.class})

O código acima sobrescreverá o comportamento para as exceções checadas. Portanto, ao especificar o código acima teremos como resultado um rollback da transação para todas as exceções checadas.

Outro elemento que também podemos utilizar é o dontRollbackOn. O elemento dontRollbackOn pode ser configurado para indicar quais exceções que não devem causar rollback na transação. Segue um exemplo na Listagem 11 de como podemos utilizar o elemento dontRollbackOn:.

Listagem 11. Especificando o elemento dontRollbackOn.

@Transactional(dontRollbackOn={IllegalStateException.class})

Este código previne que aconteça rollback nas transações quando uma exceção do tipo IllegalStateException ou qualquer uma das suas subclasses for lançada.

Quando ambos os elementos são especificados (rollbackOn e dontRollbackOn) o dontRollbackOn possuirá precedência.

Anotação @TransactionScoped

A JTA 1.2 define um novo escopo CDI que é o TransactionScoped que está contido no pacote javax.transaction. Este escopo define uma instância de um bean em que o ciclo de vida é delimitado para a transação JTA ativa no momento. Se múltiplas instâncias de um bean são injetados dentro de uma transação, o proxy CDI subjacente refere-se a mesma instância, assegurando que apenas uma instância do bean é injetado.

O escopo da transação está ativo quando o retorno de uma chamada a UserTransaction.getStatus ou TransactionManager.getStatus está em um dos seguintes estados: Status.STATUS_ACTIVE, Status.STATUS_MARKED_ROLLBACK, Status.STATUS_PREPARED, Status.STATUS_UNKNOWN, Status.STATUS_PREPARING, Status.STATUS_COMMITTING ou Status.STATUS_ROLLING_BACK.

O objeto com esta anotação será associado com a transação JTA ativa atualmente quando o objeto é utilizado.

A complexidade de uma transação é determinada por quantos recursos estão envolvidos na aplicação. Quando temos apenas um recurso envolvido, é dito que temos o "Single-Phase Commit", porém quando temos mais de um recurso envolvido temos um "Two-Phase Commit", que é considerado ser mais difícil de ser configurado.

O Java Transaction API é uma API importante quando precisamos manter a consistência da aplicação quando temos diversos recursos que são executados num bloco único, ou seja, quando temos um "Two-Phase Commit". Algo bastante comum de ser encontrado é quando uma aplicação precisa utilizar JMS e uma Base de Dados, nesse caso como estão envolvidos dois recursos diferentes que precisam sofrer alterações a JTA torna-se bastante útil para manter a consistência dessa transação.

Por fim, uma aplicação pode demarcar uma transação como sendo gerenciada por bean (bean-managed-transaction) ou por container (container-managed transaction). Transações gerenciadas por bean são programaticamente demarcadas dentro da implementação de um bean. Neste caso, a transação é completamente controlada pela aplicação. Transações gerenciadas por container são controladas pelo container. Neste caso, o container junta-se a uma transação existente ou inicia uma nova transação para a aplicação e por fim finaliza a transação recém-criada quando o método do bean completa. As transações gerenciadas por container não necessitam do fornecimento de código para o gerenciamento da transação.

Outro assunto bastante importante e que devemos ter uma boa compreensão é quanto a performance da JTA. Está disponível nas referências bibliográficas um artigo oficial da Oracle que fornece importantes conceitos relacionados a este assunto. Este artigo mostra como a performance pode melhorar alterando simples configurações no “Two-Phase Commit Logging” e no “JTA Data Source”. O artigo também mostra como podemos monitorar os recursos JTA para verificar se estão ocorrendo erros que podem causar problemas de performance como, por exemplo, erros de timeout, rollback ou exceções que podem degradar a performance da aplicação. Em artigos futuros trataremos com um pouco mais de profundidade deste tema que é recorrente entre os desenvolvedores de aplicações.


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.

Referências:

Java Transaction API (JTA) Downloads

Arun, Java EE 7 Essentials. O’Reilly, 2013.

Oracle Official Website. Java Transaction API: Additional Performance Areas.

Comunidade JTA.

Artigos relacionados