quarta-feira, 11 de abril de 2007

Controlando as transações Parte I


Este artigo é primeiro artigo sobre controle de transações do hibernate. O controle de transações pode ser algo muito facil ou muito dificil dependendo da abordagem que você queira adotar, por este motivo demonstrarei não somente a minha opção mas também as diversas opções que você pode adotar, após estes artigos espero que vc seja capaz escolher a melhor solução para você.


A primeira opção é relativamente simples, nos vamos criar uma classe de negocio que será responsavel por fazer o controle de transações, e o programador responsavel por escrever essas classes deve saber executar de forma explicita as rotinas de begin transaction, commit e rolback do hibernate. Essas classes devem permanecer na camada de "model", por isso descrevemos abaixo aonde vc deve adicionar tais classes

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

Note que agora eu frizei dois packages(busines e transaction), apesar de nossas classes se encontrarem no package busines, nos iremos usar as classes no package transaction tambem, pois nela se encontra as rotinas do hibernate, os quais a propria documentacao do hibernate disponibiliza. Lembre-se que nossos packages é divido por funcionalidades, e por isso as classes de busines não estao contidas no package de persistence.


Abaixo segue uma solução que vc pode usar para controlar as suas transacoes do hibernate, quando eu disse que vc devia executar de forma explicita as rotinas de begin transaction, commit e rolback do hibernate, eu quis dizer exatamente isso: HibernateHelper.beginTransaction();. A classe HibernateHelper será descrita posteriormente.

public class LoginBO{
private DAOFactory factory = null;

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

public Person makePersistent(Person person) throws RuntimeException {

try {
HibernateHelper.beginTransaction();
person = factory.getPersonDAO().makePersistent(person);
HibernateHelper.commitTransaction();
}catch (RuntimeException e){
HibernateHelper.rollbackTransaction();
throw e;
}
return person;
}
}

As funcoes ou metodos da nossa classe de negocio poderão lançar uma RumtimeException, pois a ideia aqui é simples: A nossa classe deve fazer o controle de transacoes e tratar qualquer erro da camada de persistencia, e para isso nos executamos rolback para tratar a exceção, porem nos lançamos novamente a exceção para avisar a camada acima que ocorreu algum erro, mas que nao precisa se preocupar em resolver, pois gerenciar o banco de dados é nossa responsabilidade.


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

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


Abaixo exibimos a classe HibernateHelper, como dito anteriormente, a intenção aqui não é ficar replicando os materiais da internet. Basta dizer que esta classe deve: Gerenciar os objetos Session e Transaction, bem como deverá ser usada na confecção dos DAOs (Data Access Object). Ela faz uso de ThreadLocal, garantindo que cada thread possua uma única instância de Session e Transaction, e protegendo esses objetos de qualquer problema de concorrência

public class HibernateHelper {

private static final SessionFactory sessionFactory;
private static final ThreadLocal sessionThread = new ThreadLocal();
private static final ThreadLocal transactionThread = new ThreadLocal();

static {
try {
sessionFactory = new Configuration().configure().buildSessionFactory();
} catch (Throwable ex) {
//Make sure you log the exception, as it might be swallowed
System.err.println("Initial SessionFactory creation failed." + ex);
throw new ExceptionInInitializerError(ex);
}
}

public static Session currentSession() {
if (sessionThread.get() == null) {
Session session = sessionFactory.openSession();
sessionThread.set(session);
}
return (Session) sessionThread.get();
}

private static void closeSession() {
Session session = (Session) sessionThread.get();
if (session != null)
session.close();

sessionThread.set(null);
}

public static void beginTransaction() {
if (transactionThread.get() == null){
Transaction transaction = currentSession().beginTransaction();
transactionThread.set(transaction);
}
}

public static void commitTransaction() {
Transaction transaction = (Transaction) transactionThread.get();
if (transaction != null && !transaction.wasCommitted() && !transaction.wasRolledBack()) {
transaction.commit();
transactionThread.set(null);
}

closeSession();
}

public static void rollbackTransaction() {
Transaction transaction = (Transaction) transactionThread.get();
if (transaction != null && !transaction.wasCommitted() && !transaction.wasRolledBack()) {
transaction.rollback();
transactionThread.set(null);
}

closeSession();
}
}


Portanto finalizamos aqui, no proximo artigo iremos fazer o controle de transação de forma implicita, ou seja, nao iremos ficar replicando muito codigo. A solução acima é bem pratica e funcional, porem é um pouco tedioso ficar toda hora executando begin transaction, comit e etc



Um comentário:

Luís Alexandre disse...

Fera,

Muito bom o artigo, porém eu tenho um problema com essa solução usando a ThreadLocal ... está está travando o múltiplo acesso ao sistema, ou seja, quando já temos um usuário logado ocorre um travamento tanto para acessar outro usuário ou para usar a ferramenta Enterprise. No caso, eu utilizo o SQL Server 2000, se já passou por este problema, me diz como solucionou para ver se é melhor que a minha solução.

Meu email: alexandre@pempec.com.br

Abraços