quarta-feira, 11 de abril de 2007

Controlando as transações Parte II


Bom, agora que vc ja esta entretido com o controle de transações, nos vamos fazer este controle de forma implicita, isso irá tornar um pouco obscuro a sua percepção sobre o assunto. O controle de transações descrito neste artigo é uma obra de Davi Luan Carneiro, ele escreveu um artigo no GUJ descrevendo passo a passo como esse processo funciona, por isso eu vou descrever aqui apenas a nossa implementação para este tipo de controle de transação.


Este controle de transação se encaixa perfeitamente com o artigo anterior. Criamos novamente uma classe de negocio que será responsavel por fazer o controle de transações, essas classes devem permanecer na camada de "model", assim como descrevemos anteriormente.

|---+model
|
|---+busines
|---+persistence
|---dao
|---dto
|---factory
|---transaction

Creio que vc ja deva saber porque frizei estes packages, porem o package transaction será um pouco mais complexo, pois ira abrigar as classes desenvolvidas pelo Davi. A grande sacada dessa solução é o uso de Annotations, agora ao invés de ficar replicando codigo, nos iremos apenar anotar se tal metodo requer ou nao uma transação, veja o codigo abaixo.

public class LoginBO{
private DAOFactory factory = null;

public LoginBO(){
factory = DAOFactory.getDAOFactory(DAOFactory.HIBERNATE);
}

@HibernateTransaction
public Person makePersistent(Person person) throws RuntimeException {
return factory.getPersonDAO().makePersistent(person);
}
}

Ainda nao acabamos, pois para que esta anotação funcione nos vamos precisar instanciar a nossa classe de negocio através de uma forma um pouco peculiar, portanto vamos esconder isso usando o pattern Factory. Crie a seguinte factory junto de suas classes de negocios.

public class BOFactory {

public static LoginBO getLogin(){
try{
return (LoginBO)TransactionClass.
create(LoginBO.class, HibernateInterceptorAnnotation.class);
}catch (Exception ex){
return null;
}
}
}

Agora se vc quiser gravar um registro no banco de dados, vc deve proceder da seguinte maneira

LoginBO loginBO = BOFactory.getLogin();
Person person = new Person("Ronildo Braga Jr","26-01-1982","M");
loginBO.makePersistent(person);


Eu vou descrever aqui as demais classes que foram adicionadas ao nosso package transaction, porem aconselho vc a ler o artigo do Davi por inteiro.


package org.iprogramming.model.persistence.hibernateTransaction;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* 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
* @author Ronildo
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface HibernateTransaction {}




package org.iprogramming.model.persistence.hibernateTransaction;

import java.lang.reflect.Method;

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import org.apache.log4j.Logger;
import org.iprogramming.Util;


/**
* Cerne do componente para controle de transacao. Faz uso de CGLib,
* uma biblioteca que provê mecanismos de manipulação de bytecode, permitindo
* implementar interfaces dinamicamente, interceptar a chamada de métodos.
* Chamando determinado método, interceptamos a sua ação, e verificamos se este método
* é transacional. Se for, abrimos uma transação, e executamos o método.
* Se ele for executado com sucesso, damos commit na operação.
* Porém, se der exceção, fazemos um rollback
*
* Isso se parece com a solução de Filtros Http, porém com uma vantagem:
* o controle transacional não fica misturado com a API do Controller (C do padrão MVC),
* e é mais flexível, pois não se prende ao contexto web
*
* Observe também que esta solução é melhor que tratar as transações diretamente nas classes Java.
* Isso porque você vai evitar muito código repetitivo (try, catch, commit, rollback, etc),
* e não vai “poluir” inúmeras classes com este tipo de código.
* @author Ronildo
*/
public abstract class HibernateInterceptor implements MethodInterceptor {

private static final Logger logger = Util.startLogger(HibernateInterceptor.class);

/**
* O método intercept, proveniente da interface MethodInterceptor (CGLib), se encarrega
* de fazer o controle transacional. Veja que ele usa o HibernateHelper,
* e que o método transacional é explicitamente invocado, através de methodProxy.invokeSuper(),
* e o seu retorno é explicitamente devolvido
* @throws Throwable
*/
public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {

if (isTransactional(object, method)){
logger.debug("logica de negocio deve ser executada dentro de uma transacao");
HibernateHelper.beginTransaction();
}

Object result = null;
try {
result = methodProxy.invokeSuper(object, args);
HibernateHelper.commitTransaction();
logger.debug("logica de negocio executada com sucesso");
}catch (Throwable e){
logger.debug("erro ao executar logica de negocio");
HibernateHelper.rollbackTransaction();
throw e;
}

return result;
}


/**
* Definido como abstrato para para obter máxima flexibilidade, pois apenas
* estendendo esta classe e implementando este método, podemos saber se o
* método é transacional ou não de diversas maneiras, como arquivos XML,
* arquivos texto, e anotações! (Esta classe é um caso do pattern Template Method).
*/
public abstract boolean isTransactional(Object object, Method method) ;
}




package org.iprogramming.model.persistence.hibernateTransaction;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

/**
* Está é a nossa implementação de HibernateInterceptor. O método isTransactional
* procura no método a anotação @HibernateTransaction. Se encontrar, retorna true,
* indicando que o método é transacional. Se não encontrar, retorna false.
* @author Ronildo
*/
public class HibernateInterceptorAnnotation extends HibernateInterceptor {

public boolean isTransactional(Object object, Method method) {
Annotation annotation = method.getAnnotation(HibernateTransaction.class);
return annotation == null ? false : true;
}
}





package org.iprogramming.model.persistence.hibernateTransaction;

import net.sf.cglib.proxy.Enhancer;

/**
* Para podermos interceptar os nossos métodos usando CGLib, não podemos criar
* suas classes através de um construtor. Devemos criá-las a partir do objeto Enhancer,
* fornecido pela API. Assim, implementamos uma classe para nos ajudar na construção
* de objetos que possuam métodos transacionais.
*
* Esta classe possui o método create, que recebe dois argumentos do tipo Class: o beanClass,
* que se refere à classe que desejamos criar; e o interceptorClass, que se refere ao tipo de Interceptor usado.
* Neste último argumento, passaremos HibernateInterceptorAnnotation.class, mas poderíamos
* também passar HibernateInterceptorXML.class, HibernateInterceptorTextFile.class, enfim,
* qualquer coisa que a sua imaginação permitir.
*
* Esta classe nao deve ser conhecida em vários lugares da aplicação. Procure escondê-la utilizando o
* pattern Factory, ou integrá-la com o seu mecanismo de IoC (como Spring Framework ou PicoContainer).
* Assim, você evita “poluir” suas classes desnecessariamente.
* Bem, em termos práticos: se quisermos que determinada classe possua um método transacional,
* ela deve ser criada por meio de TransactionClass
* @author Ronildo
*
*/
public class TransactionClass {

public static Object create(Class beanClass, Class interceptorClass) throws InstantiationException, IllegalAccessException{
HibernateInterceptor interceptor = (HibernateInterceptor)interceptorClass.newInstance();
Object object = Enhancer.create(beanClass, interceptor);
return object;
}
}


Portanto finalizamos aqui, no proximo artigo irei demonstar soluções usando o SpringFramework e finalmente depois uma solução com EJB.

6 comentários:

Anônimo disse...

Muito bom este blog. Continue com ele, está ajudando bastante.

Um abraço.

Henrique André disse...

poderia aumentar a fonte da listagem do codigo fonte?

Ronildo Junior disse...

Eu vou dedicar mais tempo a edição do blog, portanto nos proximos artigos eu devo aumentar a fonte da listagem do codigo e corrigir o fonte dos artigos anteriores. Porem vc pode encontrar todo esse codigo em
http://itrust.cvs.sourceforge.net/itrust/

ed disse...

Gostei do blog...
vc utiliza as mesmas tecnologias que eu muito legal, temos quase o mesmo gosto

Luiz Augusto Pinto disse...

Tomei contato hoje com o seu blog e com o itrust. Parabéns.... Vá em frente...

Davi Luan disse...

Olá, valeu pelos créditos quanto ao artigo =D