quarta-feira, 9 de dezembro de 2009

Invocando procedures com Java

Há uma tendência no mundo Java de exagerar no uso de frameworks. Não raramente, encontro sistemas que têm mais frameworks que telas, ou mais XML que Java. O Hibernate é dos piores infratores.

O problema de ter tanta configuração em XML é que ele sequer costuma diminuir a quantidade de código. Costuma haver, isso sim, uma troca. Troca-se Java por XML. Ou SQL por XML. Qual será a vantagem de trocar uma linguagem com tantos controles por algo que só é verificado em tempo de execução?

Pois, eu argumento que as consultas são melhor escritas em SQL e colocadas em procedures. E meu argumento principal é o de que isso torna muito mais fácil a vida do DBA. Estando as consultas em procedures e functions, ele pode facilmente encontrar e corrigir problemas de desempenho. Além disso, as consultas podem ser compartilhadas por diferentes sistemas; dados costumam viver mais que os sistemas que os utilizam (e raramente um banco de dados é usado por apenas uma aplicação).

Tendo tudo isso em mente, fiquei muito feliz quando Java finalmente adotou métodos com número variável de parâmetros (varargs) e autoboxing. Com a JDK 1.5, finalmente foi possível escrever uma interface realmente simples e direta para invocar procedures e functions. Para resolver um problema, é muito melhor criar uma boa abstração do que iludir-se com XML.

Considere as duas classes abstratas abaixo:

public abstract class Procedure {

public abstract void call(Object... args)
throws SQLException, IOException;
public abstract void close();

public String getString(int columnIndex) throws SQLException {
return getCallable().getString(columnIndex);
}

public String getString(String columnName) throws SQLException {
return getCallable().getString(columnName);
}

//demais gets omitidos
}

public abstract class Function {

public abstract Object call(Object... args) throws SQLException;
public abstract void close();

//gets omitidos
}

Eu não usei interfaces, porque os gets não dependem da implementação específica e, portanto, podem ser codificados na superclasse e aproveitadas na implementação para cada banco de dados.

O que eu almejei foi poder escrever algo como:

Procedure p=null;
try {
//BUSCAR_PESSOA_PROC(
// P_CODIGO IN NUMBER,
// P_CURSOR OUT SYS_REFCURSOR
//);
p=new OracleProcedure("java:comp/env/jdbc/data",
"PESSOAS_PKG.BUSCAR_PESSOA_PROC");
p.call(123, null);
ResultSet rs=p.getCursor(1);
while(rs.next()) {
//percorrer cursor
}
} catch(Exception e) {
e.printStackTrace();
} finally {
p.close();
}

O construtor de OracleProcedure recebe dois parâmetros:

  1. O nome de uma datasource;
  2. O nome de uma procedure (com ou sem package);

Para o método call(), passo os parâmetros exigidos pela procedure. Como o segundo parâmetro é de saída (OUT), uso null naquela posição. As instâncias de Procedure e Function têm todos os gets de java.sql.CallableStatement, de tal sorte que é possível pegar os parâmetros de saída sem dificuldades.

Na primeira vez que uma procedure é invocada, OracleProcedure (OracleFunction faz o mesmo) recupera os metadados (tipos dos parâmetros) e os guarda num Map, para não ter que repetir esse passo. Em seguida, o código monta a consulta (neste caso, "{call PESSOAS_PKG.BUSCAR_PESSOA_PROC(?,?)}") e também a guarda. Um CallableStatement é criado, os parâmetros são atribuídos (é fácil escolher o setXXX() adequado com base no tipo do parâmetro) e a consulta, finalmente, é executada.

Nas primeiras versões dessas classes, eu também guardava a referência à datasource, para evitar ter que fazer outra pesquisa JNDI. Desisti de fazer isso porque as implementações do JNDI já são rápidas o suficiente para tornar a economia de tempo desprezível. Além disso, era impossível alterar ou reiniciar a datasource sem reiniciar a aplicação.

Com isso, tenho uma maneira simples de invocar procedures e functions. As camadas em Java ficam bastante mais simples e livres de XML. O código de acesso a dados fica restrito ao banco e pode ser compartilhado com outras aplicações.

Um comentário:

Marcus Aurelius disse...

Tu tem essas classes implementadas prontas pra uso? De repente eu ponho elas em uso no que eu estou fazendo aqui.