JTA: Java Transaction API

JTA: Java Transaction API
Alberto Souza
Alberto Souza

Compartilhe

A JTA é a espeficação do mundo JAVA EE responsável por definir a API necessária para controlarmos transações dentro das nossas aplicações. Como controle transacional é uma característica que está presente em quase todo projeto, acabamos usando essa especificação tanto em containers JAVA EE, como um Wildfly, quanto em Servlet Containers, como um Tomcat ou Undertow.

Não se assuste: apesar de possuir diversas opções e funcionamento, no dia a dia utilizamos funcionalidades mais que não são nada complicadas

Utilização da JTA dentro um servidor de aplicação

Quando optamos por rodar nosso código dentro de um servidor de aplicação, ganhamos de brinde as implementações de todas as especificações que compõe o JAVA EE, entre elas a JTA. Para tentar facilitar o entendimento, vamos supor que temos um projeto simples para fazer um cadastro de livros.

Uma funcionalidade bem simples é a adição de livros. Para não termos que falar de parâmetros nem nada, vou apenas deixar um método responsável por criar dois livros.

@Path("/gerenciado/produtos")
public class ProdutosResourceTransacaoGerenciada {
    @Path("/")
    @GET
    @Produces(value = { MediaType.TEXT_PLAIN }) 
    public String criaVariosProdutos() {
        manager.persist(new Produto("jaxrs-project"));
        manager.persist(new Produto("spring-project"));
        return manager.createQuery("select p from Produto p").getResultList().size() + "foram criados";
    }    
}

Agora, caso eu acesse o endereço configurado, recebo a seguinte exception:

org.jboss.resteasy.spi.UnhandledException: javax.persistence.TransactionRequiredException: WFLYJPA0060: 
Transaction is required to perform this operation (either use a transaction or 
extended persistence context)

O Wildfly está dizendo que uma transação é necessária para conseguirmos salvar nosso livro no banco de dados.

Container Managed Transactions

Para resolver este problema, basta que façamos a seguinte alteração:

@Transactional
public String criaVariosProdutos() {
    manager.persist(new Produto("jaxrs-project"));
    manager.persist(new Produto("spring-project"));
    return manager.createQuery("select p from Produto p").getResultList().size() + "foram criados";
}

Perceba que adicionamos a annotation Transactional em cima do nosso método. Apenas com essa alteração ensinamos ao servidor de aplicação que nosso método deve ser executado dentro de uma transação.

Esse nível de facilidade só existe por conta do CDI. Essa especificação é responsável por definir o comportamento de um container de injeção de dependências no mundo JAVA EE e representa a cola que faltava entre todas as especificações. Desde o JAVA EE 6, existe um trabalho para que todos os objetos sejam criados como beans do CDI. Dessa forma ficou mais fácil realizar integrações entre as especificações.

Por exemplo, para que seja possível o funcionamento da annotation Transactional, é criado um interceptor que controla o fluxo da transação. Está descrito no javadoc da annotation. Abaixo segue um trecho:

 This support is provided via an implementation of CDI interceptors that conduct the
 necessary suspending, resuming, etc. The Transactional interceptor interposes on business method
 invocations only and not on lifecycle events. Lifecycle methods are invoked in an unspecified
 transaction context.    

Caso você queira, ainda pode definir o detalhe de como a transação deve ser gerenciada. Para isso é necessário que seja passado o argumento informando o tipo de transação. Abaixo temos a lista de possibilidades.

  • REQUIRED Caso não exista uma transação, ela é iniciada. Caso alguam outra já esteja sendo executada, nada é feito. Este é o tipo mais comumente usado.
  • REQUIRES_NEW Uma nova transação sempre vai ser criada. Caso alguma já esteja em andamento, a atual é suspensa e uma nova transação é executada.
  • MANDATORY Informa que uma transação é obrigatória para determinado momento.
  • SUPPORTS Informa que o método pode rodar com ou sem uma transação ativa.
  • NOT_SUPPORTED Informa que a transação corrente deve ser suspensa para a execuçao de determinado método. Quando o método acaba, a transação volta a ficar ativa.
  • NEVER Informa que tal método nunca deve rodar em um contexto transacional.

Situação especical caso a sua classe seja marcada como EJB

Caso a sua classe também seja marcada com a annotation Stateless ou Stateful, ela vai ser tratada pelo servidor de aplicação como um EJB. E aí, por default, todo método de um EJB que faz uso de um recurso transacional já é executado sob o contexto da JTA automaticamente. Não existe a necessidade de declarar a annotation Transactional.

Política de rollback

Uma parte também importante de entendermos é como funciona o mecanismo de rollback da JTA. Vamos imaginar um cenário onde precisamos aceitar um pedido de compra de um livro na loja. Um detalhe importante é que nossa API precisa avisar a quem está chamando o código que existe a possibilidade daquele pedido não ser processado naquele momento por falta de estoque. Uma possibilidade de código poderia ser a que segue:


@Transactional
public processaPedido(Pedido pedido){
    if(!estoque.atende(pedido)) {
        throw new SemEstoqueException(pedido);
    }
    pedidoDao.salva(pedido);
}

Do jeito que o código está escrito, não salvaríamos um pedido quando não tivesse estoque. Um outro approach pode ser o seguinte:


@Transactional
public processaPedido(Pedido pedido){
    pedidoDao.salva(pedido);

    if(!estoque.atende(pedido)) {
        throw new SemEstoqueException(pedido);
    }
}

Nós sempre queremos salvar o pedido, afinal de contas podemos fazer alguma coisa com essa informação. A pergunta que fica é: será que ele foi salvo mesmo? Afinal de contas lançamos uma exception aqui.

Para responder o questionamento é necessário saber se SemEstoqueException é uma exception checada. Caso ela seja filha direta de Exception, ou seja, checada, o interceptor do CDI que é responsável por controlar a transação vai entender que essa exception não deve gerar um rollback na aplicação. O JAVA EE entende que uma exception checada tem a ver com o negócio. Tanto que elas são chamadas de ApplicationException. Agora, caso ela seja uma exception não checada, filha de RuntimeException, o comportamento padrão do interceptor vai ser marcar a transação para rollback. Esse último tipo de exception, é entendida pelo JAVA EE como uma SystemException. Literalmente um problema não esperado(bug) e, por conta disso, a transação não deve ser efetivada.

Uma segunda opção relativa ao comportamento de rollback em função das exceptions é a utilização do parâmetro rollbackOn ou do dontRollbackOn. Ambos recebem um array de exception. Para não ter que ficar lidando com exceptions checadas, você pode assumir a criação apenas de filhas de RuntimeException. No momento que você entender que um rollback não precisa ser feito, basta usar o parâmetro dontRollbackOn.


@Transactional(dontRollbackOn={SemEstoqueException.class})

Não faz sentido para o exemplo usar o rollbackOn porque o comportamento da exception não checada já é de rollback por default. Você poderia usá-la para forçar o rollback numa exception checada.

Bean Manager Transactions

Em geral, o recomenado é utilizar o controle transacional provido pelo container e não ficar implementando este código de infraestrutura. Entretanto, as vezes necessitamos de um controle mais fino. Por exemplo controlando o tempo de execução máxima da transação. Para este tipo de cenário, podemos deixar claro que queremos o controle do fluxo.

@Path("/produtos")
@TransactionManagement(TransactionManagementType.BEAN)
public class ProdutosResourceTransacaoManual {
}

A annotation TransactionManagament deve ser usada quando você quer trocar o comportamento do controle transacional para manual. O default é o gerenciamento feito via container. Isso está explícito no código fonte da própria annotation.

@Target(TYPE) 
@Retention(RUNTIME)
public @interface TransactionManagement {
    TransactionManagementType value() 
        default TransactionManagementType.CONTAINER;
}

Agora podemos fazer o mesmo cadastro de livro anterior, só que admininstrando a transação manualmente.

@Path("/produtos")
@TransactionManagement(TransactionManagementType.BEAN)
public class ProdutosResourceTransacaoManual {

    @PersistenceContext
    private EntityManager manager;
    @Inject
    private UserTransaction userTransaction;

    @Path("/transacaoManual")
    @GET
    @Produces(value = { MediaType.TEXT_PLAIN })
    public String criaVariosProdutosComTransacaoManual() throws Exception {
        userTransaction.begin();
            manager.persist(new Produto("jaxrs-project"));
            manager.persist(new Produto("spring-project"));     
        userTransaction.commit();
        return manager.createQuery("select p from Produto p").getResultList().size() + "foram criados";
    }    

Perceba que conseguirmos pedir injetado o objeto do tipo UserTransaction, que é responsável por fazer o controle transacional manual. Com esse objeto em mãos, apenas iniciamos e comitamos uma transação no ponto que queremos. Esse código de controle transacional solta diversas exceptions checadas, para facilitar o entendimento omitimos elas e adicionamos um throws para Exception.

Caso alguma coisas desse errado, poderíamos informar que precisamos de um rollback.


userTransaction.rollback();

Inclusive, agora se for necessário que essa operação execute em um limite de tempo estabelecido, podemos usar o método setTransactionTimeout que recebe um int com o número de segundos máximo.

Como foi dito no começo da seção, a melhor maneira é deixar este tipo de tratamento para o container. Só use se realmente você tiver com uma necessidade especial.

Banner da Escola de Programação: Matricula-se na escola de Programação. Junte-se a uma comunidade de mais de 500 mil estudantes. Na Alura você tem acesso a todos os cursos em uma única assinatura; tem novos lançamentos a cada semana; desafios práticos. Clique e saiba mais!

Spring com JTA

Quando o código executa fora de um servidor de aplicação, é necessário conseguirmos alguém que forneça pelo menos parte das facilidades. Nesse cenário entra o Spring, um framework super reconhecido no mundo Java e que atua tanto em parceria quanto como substituto de um servidor de aplicação.

Se você esitver rodando uma aplicação baseada no Spring e configurada com o Spring Boot, executar um código envolvido num contexto transacional é muito simples. Veja abaixo o exemplo:

    @Transactional
    public void save() {
        manager.persist(new Produto("jaxrs-project"));
        manager.persist(new Produto("spring-project"));
    }

Perceba que usamos a mesma annotation Transactional da especificação JTA. Isso só é possível porque as annotations de todas as especificações são semânticas. Elas não falam nada sobre detalhes de implementação, apenas declaram o que precisa acontecer naquele ponto do código. Dessa forma qualquer projeto pode tirar proveito das mesmas annotations.

Dentro de um servidor de aplicação

Caso o Spring tenha sido executado dentro um servidor de aplicação, ele automaticamente vai procurar pela implementação disponível da específicação através da JNDI. Você pode olhar um pouco sobre ele como faz isso acessendo o código fonte da classe JTATransactionManager.

Fora do servidor de aplicação

Por outro lado, se o Spring tiver sido executado isoladamente, por exemplo através de uma aplicação com Spring Boot e um servlet container embedado, ele não vai ter disponível uma implementação da JTA para se basear. Geralmente, mesmo nesse contexto, o projeto usa a JPA e para essa situação o Spring conta com sua própria implementação de contexto transacional baseada na JPA. Você pode acessar o código fonte seguindo este link.

A especifição JTA é amplamente usada em todos os nossos projetos. Seja através de uma implementação completa e integrada em um servidor de aplicação, seja através das suas annotations que podem ser lidas por qualquer outro projeto.

Alberto Souza
Alberto Souza

Alberto é desenvolvedor e coordenador dos treinamentos da Caelum São Paulo. Ele investe parte do seu tempo livre para criação de projetos pessoais, como o SetupMyProject.com, e colabora com projetos open source, como o VRaptor e o Stella.

Veja outros artigos sobre Programação