Transações JTA + Hibernate + Web logic 9

O que são e como funcionam

 

Transações JTA se tornaram praticamente unanimidade como escolha para gerenciamento de transações em JAVA. Nesse artigo, tento explicar o porquê, como usar e mostrar exemplos de uso com o Hibernate e o servidor de aplicações Web Logic versão 9.

 

JTA – O que é

JTA é um acrônimo para Java Transaction API e é uma especificação feita pela Sun Microsystems (criadora do Java) visando padronizar o uso de transações distribuídas feitas por aplicativos Java.

 

Basicamente, isso significa que ela pode ser usada para fazer commit, rollback ou criar transações, ao invés de usar transações diretamente pelo driver JDBC. A principal vantagem que o uso de JTA acarreta, contudo, é a possibilidade de executar transações globais, cujo conceito será explicado um pouco mais adiante. A página oficial com documentação sobre a especificação encontra-se em http://java.sun.com/products/jta/.

 

JTA define somente as interfaces que um aplicativo deve chamar para fazer uso de transações distribuídas, mas deve ser implementada por algum serviço que implemente essa API. Praticamente todo application server, atualmente, tem uma implementação dessas, que é normalmente chamada de Transaction Manager.

 

Como muita gente vê uso de  JTA em aplicações fora de um aplication Server, um grupo chamado Object Web criou uma implementação dessa API livre, que pode ser usada em qualquer aplicação. Essa implementação chama-se JOTM e vale a pena dar uma conferida: http://jotm.objectweb.org/. Nesse artigo, nos concentraremos na implementação do Web Logic, mas os conceitos valem para qualquer outra implementação, uma vez que a API é a mesma. 

 

Transações globais

Uma transação, como já deve ser sabido por parte do leitor, é uma unidade lógica de trabalho e deve seguir 4 princípios fundamentais:

 

  • Atomicidade – As operações executadas dentro de uma transação devem ser executadas por inteiro (todas as operações) ou nenhuma das operações devem surtir efeito. Ou tudo, ou nada.
  • Consistência – A transação não deve afetar o estado de consistência dos dados relacionados entre si no banco.
  • Isolamento – Transações rodando em paralelo devem ser isoladas uma da outra, de forma que se duas transações mexem num mesmo registro de uma mesma tabela, por exemplo, deve existir algum mecanismo que cuide do nível de isolamento nesse acesso concorrente.
  • Durabilidade – As Transações garantem que os dados gravados não são perdidos mesmo em caso de reinício ou falha no sistema.

Quando estamos usando transações simples (Não JTA), essas características são asseguradas pelo próprio sistema de gerenciamento de banco de dados. Então se usamos uma aplicação Java que acessa Oracle via JDBC e esta cria uma transação local, é o Oracle que precisará se preocupar com concorrência, paralelismo, integridade e precaução contra falhas. Esse tipo de transação é simples porque o banco tem domínio total dos dados sendo tratados.

 

Existem ocasiões, contudo, que tem se tornado cada vez mais freqüentes hoje em dia, em que é necessária a criação de uma transação no acesso a dois sistemas diferentes. Por exemplo, vamos supor que tenhamos um sistema responsável pelo estoque empresa e outro sistema responsável pelas operações financeiras realizadas pelos sistemas da empresa. Um terceiro sistema, de vendas, poderia ter de registrar as operações de entrada de caixa no sistema de operações financeiras e registrar as baixas de estoque no sistema de estoque para poder concretizar uma venda. 

 

O que aconteceria se um erro fosse causado na hora de efetuar a baixa de estoque? Obviamente, as operações financeiras não podem ser registradas, visto que o estoque não foi baixado e isso é trabalho para uma transação. Seria simples realizar essa operação se ambos os sistemas usassem a mesma base, mas e se supormos o caso de o sistema de operações financeiras estar rodando em cima de uma base Oracle, o de estoques em cima de MySQL e o de vendas em cima de SQL Server?

 

mevtjhwfig01.jpg 

Figura 01. Sistema de vendas realiza uma transação entre vários bancos diferentes

 

Esse é o caso em que é necessário o uso de uma transação global e é para isso que foi criada a tecnologia JTA. Para que seja possível usar JTA numa transação como a mostrada acima, é necessário que 3 artefatos tenham suporte à especificação:

  • O banco de dados
  • O Driver JDBC
  • O aplicativo Java, que deve fazer uso de alguma implementação do JTA.

JTA no weblogic

Para demonstrar o uso de JTA  no weblogic, vamos usar Oracle como base de dados. O Oracle tem suporte para JTA tanto no banco quanto no driver JDBC. A implementação do JTA no Java é o Transaction Manager do Weblogic. 

 

Vejamos então como ficaria a configuração do weblogic para usarmos JTA em nossa aplicação. Para realizarmos qualquer operação num banco de dado dentro de uma transação JTA, devemos usar uma conexão com o banco criada e gerenciada pelo próprio Web Logic. Para isso, devemos criar um datasource, usando o console.

 

Conforme mostra a figura 2, escolha a opção JDBC -> Datasources de dentro do console. Criar um datasource pelo console é muito fácil, pois o console possui um assistente para essa tarefa, então os passos não serão descritos aqui. O mais importante é certificar-se que o datasource está configurado corretamente.

 

O primeiro passo é escolher o datasource, conforme mostra a figura 3 e selecionar a pane Connection Pool.

 

mevtjhwfig02.jpg 

Figura 02. Escolhendo a opção JDBC -> Data Sources

 

mevtjhwfig03.jpg 

Figura 03. Escolhendo um Data Source já criado

 

Na pane Connection Pool, é necessário alterar o driver class name de oracle.jdbc.OracleDriver (padrão, sem uso de JTA) para oracle.jdbc.xa.client.OracleXADataSource. Para isso funcionar, é necessário certificar-se de estar usando uma versão atual do driver Jdbc que já suporte JTA. A que vem com o Weblogic 9 já tem esse suporte. A figura 4 mostra a configuração.

 

Também é necessário dizer para o weblogic que esse datasource usará a interface XA do driver JDBC. Confirme se isso está correto no pane transaction, conforme ilustra a figura 5.

 

mevtjhwfig04.jpg 

Figura 04. Pane Connection Pool

 

mevtjhwfig05.jpg 

Figura 05. Pane Transaction

 

XA diz respeito ao protocolo usado pela JTA, chamado Two Phase Commit. O funcionamento do protocolo é bem complexo, mas em resumo ele é o meio pelo qual as transações globais se tornam possíveis. Ele é quem sincroniza as transações ocorrendo em cada banco de forma que cada transação local possa ser enxergada como uma operação da transação global.

 

Além de configurar o datasource pelo console, é possível alterar os arquivos de configuração XML na mão, que ficam na pasta config/jdbc, dentro da pasta do domínio.

 

Para fazer o nosso exemplo funcionar, seria necessário criar mais 2 datasources, uma usando o driver do MySQL e outro o do SQL Server. As mesmas observações feitas para o oracle se aplicam para estes: é necessário usar o class name correto no driver Jdbc, certificar-se que o banco e o driver tem suporte para JTA e configurar corretamente o weblogic para fazer uso disso.

 

Usando JTA no Java

Como assumimos que nossa aplicação usa Hibernate e é o Hibernate quem faz a interface com o banco, precisamos configurar o Hibernate para que esse use o Data Source que criamos no Web Logic para obter uma conexão com o banco. Basta então configurar a session factory no hibernate.properties, conforme consta na listagem 1.

 

<!-- Transaction Management Configuration -->

hibernate.connection.datasource = java:/comp/env/jdbc/test

hibernate.transaction.factory_class = \

    org.hibernate.transaction.JTATransactionFactory

hibernate.transaction.manager_lookup_class = \

    org.hibernate.transaction.WeblogicTransactionManagerLookup

hibernate.dialect = org.hibernate.dialect.OracleDialect

Listagem 01. Configuração da session factory no hibernate.properties

 

Os mesmos valores podem ser usados quando configuramos via hibernate.cfg.xml. A primeira propriedade é o nome JNDI do datasource, configurado no Web Logic (pane connection pool do console). O Hibernate usa esse nome para pedir para o Web Logic uma conexão JDBC. Também são especificados a factory a ser usada (normalmente a do próprio Hibernate) e a classe de lookup do transaction manager.

 

Essa classe deve implementar a interface TransactionManagerLookup, que provê meios de recuperar uma implementação específica da interface UserTransaction. No caso, essa classe vai devolver o nome JNDI da implementação do UserTransaction feita pelo Web Logic. Essa implementação é o transaction manager.

 

Uma vez que isso já esteja configurado, com o Hibernate o uso de transações globais fica quase transparente para o programador. Com a session factory configurada, basta usar um objeto SessionFactory (criado a partir de um objeto Configuration) para criar uma sessão hibernate, usando o método getCurrentSession().

 

Esse método vai retornar uma Session, que pode ser usada para realizar qualquer operação em banco com o Hibernate. O modo de funcionamento é bem simples, o método getCurrentSession() verifica se existe alguma Session associada com a transação JTA atual. Caso não exista, uma será iniciada e anexada ao contexto JTA. Desse modo, todas as operações estarão vinculadas com a transação JTA criada e será feito flush() automático quando a transação acabar.

 

Para criar a transação JTA e efetuar commit e rollback, podemos obter uma referência para o transaction manager do Web Logic via JNDI. Vejamos o código da listagem 2:

 

Context ctx = null;Hashtable env = new Hashtable();

env.put(Context.INITIAL_CONTEXT_FACTORY,         "weblogic.jndi.WLInitialContextFactory");

 

// Parâmetros para o Web Logic

// Substitua o servidor, nome de usuário e senha

// para o seu ambiente

 

env.put(Context.PROVIDER_URL, "t3://localhost:7001");

env.put(Context.SECURITY_PRINCIPAL, "Fred");env.put(Context.SECURITY_CREDENTIALS, "secret");

ctx = new InitialContext(env);

UserTransaction tx = (UserTransaction)  ctx.lookup("javax.transaction.UserTransaction");

Listagem 02. Obtendo uma referência para o transaction manager no Web Logic

 

Esse código obteve um objeto tx do tipo UserTransaction através do lookup JNDI do nome javax.transaction.UserTransaction. Esse nome JNDI já vem configurado com o Web Logic para retornar o transaction manager, não há necessidade de configurar.

 

Uma vez obtido o objeto tx, podemos usar os métodos begin(), commit() e rollback() para iniciar, fazer commit ou fazer rollback em uma transação global, respectivamente.

 

Como o transaction manager retornado pelo Web Logic em nosso lookup é o mesmo que é retornado para o Hibernate, então sempre que obtivermos uma Session do Hibernate pela chamada do método getCurrentSession(), o transaction manager saberá informar se existe ou não uma transação criada e retorná-la ou criá-la se necessário.

 

Observações finais

 

Para que as transações globais funcionem com vários bancos, é necessário que o mesmo transaction manager seja usado nas operações envolvendo cada banco. Isso implica que é necessário garantir que os vários datasources envolvendo bancos diferentes estejam perfeitamente configurados de acordo com um único transaction manager.

 

Apesar de todas as vantagens, existe uma característica que existe em transações locais, mas que não pode ser usada em transações globais, que são as chamadas nested transactions. Basicamente, isso significa que não é possível executar uma transação dentro da outra, ou seja, não é possível iniciar uma transação global se já existe uma ativa para o mesmo contexto transacional. É necessário primeiro efetuar commit ou rollback, finalizando a transação atual, para só então iniciar uma nova transação.

 

Atenção deve ser dada também para a queda de performance devido ao uso de transações globais. Criar uma transação global implica em gastar recursos para sincronizar as várias transações locais e pode reduzir a performance de sua aplicação consideravelmente.