Mentawai Web Framework

Filtro de Transação

Gerenciamento de transações é uma parte importante de qualquer projeto web com muitos acessos ao banco de dados. Uma solução simples e poderoza, e que geralmente é suficente para a maioria dos casos é tornar a sua ação atômica. E isso pode ser feito facilmente com o TransactionFilter.

A classe org.mentawai.filter.TransactionFilter inicia uma transação logo antes da ação ser executada. Se a ação retornar SUCCESS a transação é efetuada (comitada). Caso contrário, a transação é cancelada (rolledback). Note que você pode também definir um conjunto de resultados para os quais a transação deverá ser comitada. Se você não definir nenhum resultado, o valor padrão será SUCCESS. Note também que se a ação lançar alguma exceção, a transação será cancelada.

O TransactionFilter procura por uma transação (org.mentawai.transaction.Transaction na entrada (input) da ação. Provavelmente você vai querer utilizar Inversão de Controle para colocar uma transação no input da sua action, como veremos mais adiante.

O código da org.mentawai.filter.TransactionFilter é apresentado abaixo:

package org.mentawai.filter;

import java.util.*;

import org.mentawai.core.*;
import org.mentawai.transaction.*;

public class TransactionFilter implements Filter {
    
    private final static String TRANSACTION_KEY = "transaction";
    
    private String transaction_key = TRANSACTION_KEY;
    private List results = new LinkedList();
    
    public TransactionFilter() {
        results.add(Action.SUCCESS);		
    }
    
    public TransactionFilter(String transaction_key) {
        this();
        this.transaction_key = transaction_key;
    }
    
    public TransactionFilter(String [] results) {
        for(int i=0;i<results.length;i++) {
            this.results.add(results[i]);
        }
    }
    
    public TransactionFilter(String transaction_key, String [] results) {
        this(results);
        this.transaction_key = transaction_key;
    }
    
    public String filter(InvocationChain chain) throws Exception {
        Action action = chain.getAction();
        Input input = action.getInput();
        Transaction transaction = (Transaction) input.getValue(transaction_key);
        if (transaction == null) {
            throw new FilterException("Cannot find transaction: " + transaction_key);
        }
        
        try {
            transaction.begin();
            String result = chain.invoke();
            if (results.contains(result)) {
                transaction.commit();
            } else {
                transaction.rollback();
            }
            return result;
        } catch(Exception e) {
            transaction.rollback();
            throw e;
        }
    }
    
    public List getResultsForCommit() { 
        return results;
    }
    
    public void destroy() {	}

}                    
					

A Interface Transaction

O Mentawai possui uma abstração de transação na interface org.mentawai.transaction.Transaction. Através dessa interface, você pode criar qualquer tipo de transação.

package org.mentawai.transaction;

public interface Transaction {
    public void begin() throws Exception;
    public void commit() throws Exception;
    public void rollback() throws Exception;
    public boolean isActive();
    public boolean wasCommited();
    public boolean wasRolledBack();
}
		

O Mentawai já vem com duas implementações da interface Transaction: org.mentawai.transaction.JdbcTransaction e org.mentawai.transaction.HibernateTransaction.Uma transação Jdbc precisa de uma java.sql.Connection para funcionar e, como é de se esperar, uma transação Hibernate precisa de uma org.hibernate.Session.

Como injetar dinâmicamente uma Connection ou uma Session dentro de suas transações? A resposta é Injeção de Dependência através da classe org.mentawai.filter.DIFilter.


Definindo uma Transação

Para definir corretamente uma transação para a sua action, você irá usar um pouco da mágica de Inversão de Controle. Segue abaixo um exemplo de uma ação usando JdbcTransaction:

public class ApplicationManager extends org.mentawai.core.ApplicationManager {
    
	private Component transaction;
	private ConnectionHandler connHandler;
	
	public void init(Context application) {
	
		// ConnectionPool
		this.connHandler = new C3P0ConnectionHandler("com.mysql.jdbc.Driver", 
						 "jdbc:mysql://mybooks.lohis.com.br/mybooks?autoReconnect=true", 
						 "mybooks", 
						 "mybooks");

		// your transaction will be a JdbcTransaction... here we are defining an IOC component...
		ioc("transaction", JdbcTransaction.class);
	}
    
	public void loadActions() {
	
		// Java-Style
    
		// turn Mentawai into a powerful IoC container...
		addGlobalFilter(new IoCFilter());
		
		// turn on the ConnectionPool for Mentawai...
		addGlobalFilter(new ConnectionFilter(connHandler));
		
		// whoever needs a connection will receive a connection...
		addGlobalFilter(new DIFilter("conn", "connection", Connection.class));
		
		// BookManager.createAndAddToUser
		ActionConfig ac = new ActionConfig("/BookManager", BookManagerAction.class, "createAndAddToUser");
		addActionConfig(ac);
		
		ac.addConsequence(SUCCESS, new Forward("/welcome.jsp"));
		ac.addConsequence(ERROR, new Forward("/bookmanager/createbook.jsp"));
		ac.addConsequence(BookManagerAction.ERROR2, new Forward("/welcome.jsp"));
		
		// make this action atomic !!!!
		// by default this filter will look for a transaction with the name "transaction" in the action input
		// refer above that this is exactly like we defined our IoC component...
		ac.addFilter(new TransactionFilter());
		
		
		// Ruby-Style
		
		// turn Mentawai into a powerful IoC container...
		filter(new IoCFilter());
		
		// turn on the ConnectionPool for Mentawai...
		filter(new ConnectionFilter(connHandler));
		
		// whoever needs a connection will receive a connection...
		filter(new DIFilter("conn", "connection", Connection.class));
		
		// BookManager.createAndAddToUser
		action("/BookManager", BookManagerAction.class, "createAndAddToUser")
			.on(SUCCESS, fwd("/welcome.jsp"))
			.on(ERROR, fwd("/bookmanager/createbook.jsp"))
			.on(BookManagerAction.ERROR2, fwd("/welcome.jsp"))
		
			// make this action atomic !!!!
			// by default this filter will look for a transaction with the name "transaction" in the action input
			// refer above that this is exactly like we defined our IoC component...
			.filter(new TransactionFilter());
		
	}
}            
		

Note que estamos criando uma uma nova transação para cada requisição com IoC. Isto é nescessário por que cada requisição ira ter sua própria instância de Connection colocada na entrada (input) da ação pelo ConnectionFilter. Com a a transação e a conexão na entrada (input) da ação, nós podemos usar o DIFilter para injetar a conexão na transação. Então nossa transação estará pronta para ser usada pelo TransactionFilter.