http://www.guj.com.br Controle Transacional no Hibernate 3 com Anotações Davi Luan Carneiro Muito se discute na comunidade Java sobre como se deve fazer controle transacional, e neste artigo mostraremos uma forma simples e funcional de fazê-lo, explicando cada passo. Introdução O Hibernate é um framework de mapeamento objeto relacional desenvolvido em Java. Devido às suas grandes potencialidades e facilidade de uso, ele se tornou hoje padrão em termos de persistência com banco de dados, na plataforma Java. Dentre os seus vários serviços, o Hibernate provê um meio de se controlar transações, através de métodos de suas interfaces Session e Transaction. A questão, porém, não é simplesmente como fazer o controle transacional, mas sim como fazê-lo de maneira adequada. Existem algumas opções, todas com suas vantagens e desvantagens, tais como: Controlar as transações explicitamente dentro das classes Java; Utilizar Hibernate em conjunto com EJBs, e configurar o controle transacional com JTA; Utilizar o controle de transações do Spring Framework; Em ambiente web, criar filters (classes que implementam a interface Filter, da API de Servlets) que comecem uma transação antes do processamento da requisição, e a terminem logo após. • • • • Todas essas opções são válidas, e adequadas em vários casos específicos. Porém queremos, neste artigo, apresentar mais uma: como controlar as transações através de Annotations, um dos novos recursos do Java 5. Não usaremos nenhuma solução pronta, porém nós mesmos vamos implementar uma passo a passo. Para executar os exemplos deste artigo, é necessário que você tenha o Java 5 instalado, e que faça o download do Hibernate 3, em http://www.hibernate.org. Presumo que você tenha um conhecimento básico do framework. O que é uma transação? Transação é uma operação que engloba uma ou mais sub-operações, geralmente relacionadas a bancos de dados, e que possuem estas 4 propriedades fundamentais, conhecidas pelo acrônimo ACID: Atomicidade: assegura que a operação não será realizada pela metade. Ela só será confirmada (commit) se todas as sub-operações forem feitas com sucesso. Caso contrário, a operação toda será descartada, e os efeitos das sub-operações até então concluídas serão cancelados (rollback). Consistência: uma transação deve sempre zelar para que não produza dados inconsistentes. O próprio conceito de atomicidade deve garantir isso. Isolamento: a execução de uma transação não deve interferir na execução de outra. Sem essa propriedade, teríamos grandes chances de uma transação estar manipulando dados inconsistentes. Durabilidade: garante que as modificações realizadas na transação serão persistidas, não importando onde. (bancos relacionais, arquivos XML, etc.) @HibernateTransaction Utilizaremos esta anotação para marcar os métodos que queremos que sejam transacionais. Consideramos como métodos transacionais aqueles cujo processamento constitui uma única transação com o banco. Confira o código: package br.com.guj.hibernateTransaction; rollback().get(). } return (Session) sessionThreadLocal.METHOD) @Retention(RetentionPolicy.printStackTrace(). transactionThreadLocal. garantindo que cada thread possua uma única instância de Session e Transaction.get().lang.get(). bem como deverá ser usada na confecção dos DAOs (Data Access Object).hibernateTransaction. if (transaction != null && !transaction. transactionThreadLocal.wasRolledBack()) { transaction.openSession().RUNTIME) public @interface HibernateTransaction {} HibernateHelper Esta classe será útil para gerenciar os objetos Session e Transaction. package br.wasCommitted() && !transaction. } static void beginTransaction() { if (transactionThreadLocal.com. } closeSession(). } catch (RuntimeException e) { e. if (transaction != null && !transaction. } private static void closeSession() { Session session = (Session) sessionThreadLocal. public class HibernateHelper { private static final SessionFactory sessionFactory. @Target(ElementType.close().configure() . private static final ThreadLocal transactionThreadLocal = new ThreadLocal(). } } public static Session currentSession() { if (sessionThreadLocal.commit(). } } . static { try { sessionFactory = new Configuration().*.hibernate. throw e.get(). e protegendo esses objetos de qualquer problema de concorrência.set(null). if (session != null) { session.buildSessionFactory().Configuration. private static final ThreadLocal sessionThreadLocal = new ThreadLocal().get() == null) { Transaction transaction = currentSession(). transactionThreadLocal. } sessionThreadLocal. } } static void commitTransaction() { Transaction transaction = (Transaction) transactionThreadLocal.wasRolledBack()) { transaction.set(null).wasCommitted() && !transaction. Ela faz uso de ThreadLocal.set(null).get() == null) { Session session = sessionFactory. } static void rollbackTransaction() { Transaction transaction = (Transaction) transactionThreadLocal. import org.annotation.http://www.cfg.set(transaction).br import java.guj.*. import org. sessionThreadLocal.com.guj.beginTransaction(). } closeSession().set(session).hibernate. } catch (Exception e) { HibernateHelper. a solução dos Filters não oferece muita flexibilidade quanto ao tratamento a ser feito quando acontecer o roolback.invokeSuper(object. e é mais flexível.proxy.sf. permitindo implementar interfaces dinamicamente.com. através de methodProxy.*.com. sendo o cerne do nosso componente. porém com uma vantagem: o controle transacional não fica misturado com a API do Controller (C do padrão MVC).br HibernateInterceptor Esta é uma classe extremamente interessante. catch.hibernateTransaction. } public abstract boolean isTransactional(Object object. onde o método isTransactional foi definido como abstrato. method)) { HibernateHelper. Fazemos isso para obter máxima flexibilidade. pois não se prende ao contexto web. fazemos um rollback. e o seu retorno é explicitamente devolvido.lang. e anotações! (Esta classe é um caso do pattern Template Method). Method method) throws Exception { .reflect. em return result. indicando que o método é transacional. Object[] args.Annotation. Assim. Ela faz uso de CGLib. if (isTransactional(object. e verificamos se este método é transacional. Além disso. se encarrega de fazer o controle transacional. throw e.guj.annotation. Se ele for executado com sucesso. HibernateInterceptorAnnotation Está é a nossa implementação de HibernateInterceptor. import java. você já deve ter captado o funcionamento do nosso componente: chamando determinado método. public abstract class HibernateInterceptor implements MethodInterceptor { public Object intercept(Object object. interceptar a chamada de métodos.Method.guj. abrimos uma transação. Method method) throws Exception. import java.guj. Method method.rollbackTransaction(). MethodProxy methodProxy) throws Throwable { Object result = null.lang.hibernateTransaction.invokeSuper(). retorna true. Isso se parece com a solução de Filtros Http. e não vai “poluir” inúmeras classes com este tipo de código. commit. } try { result = methodProxy. Imagine uma aplicação web com as transações gerenciadas por filters. } return result. etc. e executamos o método. import net. public class HibernateInterceptorAnnotation extends HibernateInterceptor { public boolean isTransactional(Object object. } Observe que temos uma classe abstrata. Se for.commitTransaction(). podemos saber se o método é transacional ou não de diversas maneiras. import java.cglib. Porém. se der exceção. Se encontrar. etc).Method. como arquivos XML.lang. HibernateHelper. Se não encontrar. args). Isso porque você vai evitar muito código repetitivo (try. package br. Veja que ele usa o HibernateHelper. proveniente da interface MethodInterceptor (CGLib).com.beginTransaction(). Já o método intercept. interceptamos a sua ação. rollback. damos commit na operação.reflect. O método isTransactional procura no método a anotação @HibernateTransaction. retorna false. e que o método transacional é explicitamente invocado. package br. pois apenas estendendo esta classe e implementando este método. uma biblioteca que provê mecanismos de manipulação de bytecode. arquivos texto. e imagine que surja a necessidade de implementar uma view em Swing: você terá que reimplementar o controle transacional. Observe também que esta solução é melhor que tratar as transações diretamente nas classes Java.http://www. return object.cfg.hibernateTransaction. String nome. } } Esta classe possui o método create. Recomendo que você não permita que esta classe seja conhecida em vários lugares de sua aplicação. package br. Devemos criá-las a partir do objeto Enhancer.hibernateTransaction. Um débito e um crédito. você evita “poluir” suas classes desnecessariamente. mas poderíamos também passar HibernateInterceptorXML. Já pensou se a operação fosse interrompida pela metade? O dinheiro sairia da conta de um. Essa operação consistirá em duas sub-operações: subtrair a quantia da conta de um dos clientes. Object object = Enhancer. que recebe dois argumentos do tipo Class: o beanClass. que representa o Cliente do banco. ela deve ser criada por meio de TransactionClass.newInstance().class.guj. Double saldo.xml e hibernate.class). Dessa maneira.getAnnotation(HibernateTransaction.http://www. . em termos práticos: se quisermos que determinada classe possua um método transacional. Observe que temos aqui um típico exemplo da importância de transações. implementamos uma classe para nos ajudar na construção de objetos que possuam métodos transacionais. ou integrá-la com o seu mecanismo de IoC (como Spring Framework ou PicoContainer). HibernateInterceptorTextFile. import net. return annotation != null. pois eliminaria a necessidade de se criar os objetos a partir do Enhancer.com.class. public class private private private Cliente { Long id. package br. fornecido pela API.com. qualquer coisa que a sua imaginação permitir. não podemos criar suas classes através de um construtor. Por isso.create(beanClass. enfim. Procure “escondê-la” utilizando o pattern Factory. passaremos HibernateInterceptorAnnotation. a classe DAOFactory. e os arquivos Cliente. e o interceptorClass.example. senha. e adicionar o mesmo valor na conta do outro.Enhancer.guj. porém não iria para a conta do outro! Não nos preocuparemos em construir um sistema perfeito. Assim.xml. public class TransactionClass { public static Object create(Class beanClass. que se refere ao tipo de Interceptor usado.br Annotation annotation = method. Isto seria vantajoso. mas faremos o exemplo da maneira mais simples possível. Há vários modos de usar AOP com Java. iremos para um exemplo prático: uma transferência bancária.sf.class.com. tais como o AspectJ. JBoss AOP ou Spring AOP. Class interceptorClass) throws Exception { HibernateInterceptor interceptor = (HibernateInterceptor)interceptorClass. Assim.hbm. omitimos a interface ClienteDAO. } } TransactionClass Para podermos interceptar os nossos métodos usando CGLib.cglib. interceptor). Bem. Neste último argumento. Exemplo de uso Agora que já demonstramos como construir o nosso componente.guj. segue abaixo a nossa primeira classe. visto que a proposta central é demonstrar o uso da anotação que criamos.proxy. Estes poderão ser baixados no site do GUJ. que se refere à classe que desejamos criar. Aspectos O nosso exemplo também poderia ser implementado com AOP. Double valor) throws Exception { pagador. passaremos à classe Banco. import br.guj. .io.list().currentSession().saldo = saldo.atualizaCliente(favorecido).hibernateTransaction.currentSession(). } public List<Cliente> listaClientes() throws Exception { return HibernateHelper.HibernateHelper. destino.http://www. } public void transfere(Cliente destino.currentSession().guj. valor).getSaldo() < valor) { throw new Exception("Não possui saldo suficiente"). } public void atualizaCliente(Cliente cliente) throws Exception { HibernateHelper.senha = senha. package br.nome = nome.currentSession(). public class HibernateClienteDAO implements ClienteDAO { public void salvaCliente(Cliente cliente) throws Exception { HibernateHelper. Cliente favorecido. } //Por ser um select.Serializable.setSaldo(destino.save(cliente).class).createClienteDAO().io.transfere(favorecido.hibernateTransaction.com. Note que ela possui um método efetuarTransferencia anotado com HibernateTransaction.example. import java.atualizaCliente(pagador). Observe que ela deve usar o HibernateHelper. } public Cliente getCliente(Serializable id) throws Exception { return (Cliente)HibernateHelper. } } Agora. package br.guj.guj.valor).example. Portanto.hibernateTransaction.com.Serializable.com. import java. this. id).guj.HibernateTransaction. Double valor) throws Exception { if (valor <= 0) { throw new Exception("Valor inválido: menor que zero"). this. import br. ClienteDAO dao = DAOFactory. dao.com.com. public class Banco { @HibernateTransaction public void efetuarTransferencia(Cliente pagador.get(Cliente.update(cliente).List.setSaldo(this. } //sets e gets omitidos Logo abaixo temos a classe HibernateClienteDAO. não precisa de transação public Cliente getCliente(Serializable id) throws Exception { return DAOFactory. String senha.br public Cliente() {} public Cliente(String nome.createCriteria(Cliente.getSaldo() + valor).delete(cliente). Double saldo) { this. implementação de ClienteDAO. } if (this.currentSession(). dao. } public void removeCliente(Cliente cliente) throws Exception { HibernateHelper. } this.getCliente(id). temos é um método transacional. import java.util.getSaldo() .hibernateTransaction.class.createClienteDAO(). Double valor = new Double(teclado. } catch (Exception e) { System.com. Cliente pagador = banco.guj.println("Qual é o valor a ser transferido?").net Biblioteca utilizada neste artigo http://www.out.com. favorecido. //Chamada à operação transacional banco.getCliente(new Long(teclado.pdf Apresentação sobre Transações Davi Luan Carneiro (
[email protected]().br/cursos/java/j530/j530_12_Transactions.br } } Por fim.guj. Ela pede ao usuário o ID do pagador.println("Qual é o ID do favorecido?"). import java.com. eis a classe Main. e. System. HibernateInterceptorAnnotation.http://www.util. Programa em Java desde 2004. Scanner teclado = new Scanner(System.next()). . Espero que você tenha aprendido coisas novas! Referências hibernate.out.com) é técnico em Informática pelo Colégio Técnico da Unicamp. System.argonavis.out. para que ele possa ser reaproveitado em diversos projetos. package br. A minha sugestão é que você transforme esta solução (com eventuais extensões) em um componente de software. para o Hibernate.Scanner.out.efetuarTransferencia(pagador.next())). damos rollback.org Site oficial do Hibernate cglib.guj.out. Se qualquer exceção acontecer. Confira a stackTrace:").in).create(Banco.com.hibernateTransaction.println("Transferência efetuada com sucesso!"). Cliente favorecido = banco.example.hibernateTransaction. Conclusão Neste artigo. se tudo der certo.class. e graduando em Ciência da Computação pela Universidade Paulista. System.*.class). procurei demonstrar como fazer um controle transacional inteligente com anotações.sourceforge. e o valor a ser transferido. Esta solução não atenderá a todos os sistemas. } } Veja que. valor). public class Main { public static void main(String[] args) throws Exception { try { Banco banco = (Banco) TransactionClass. import br.println("Qual é o ID do pagador?"). System.getCliente(new Long(teclado. mas creio que pode ser útil para muitos deles.next())).println("Transação não foi efetuada. Veja que o Banco é construído usando o TransactionClass. a transação será finalizada e os dados do banco serão atualizados. do favorecido.