Para melhorar o desempenho de um banco de dados relacional, é comum usar procedimentos armazenados, que nada mais são que funções definidas pelo usuário, contendo uma série de instruções SQL e armazenadas em um banco de dados. Neste artigo, mostraremos como acessar um Procedimento Armazenado por meio de instruções chamáveis com o driver JDBC Informix. CallableStatement de JDBC estende PreparedStatement e envia ao servidor uma solicitação para executar um Procedimento Armazenado para operações complexas, como retornar valores ou usar parâmetros de Procedimento Armazenado de entrada e saída.
Introdução
Em um aplicativo de banco de dados relacional, a principal vantagem de usar procedimentos armazenados em vez de instruções de SQL únicas é que o plano de consultas (ou plano de execução) é gerado quando o procedimento armazenado é criado, e o mesmo plano de consultas é reutilizado a cada execução do procedimento armazenado. Isso economiza uma quantidade considerável de recursos no servidor de banco de dados. Quando criado uma única vez, eles podem ser chamados por qualquer cliente de banco de dados, como aplicativos JDBC, quantas vezes necessário, sem precisar de um novo plano de execução.
A maneira de usar os procedimentos armazenados varia dependendo do servidor de banco de dados. Um sistema de gerenciamento de banco de dados (DBMS), como Informix e DB2, tem uma sintaxe SQL diferente para a execução de procedimentos armazenados. Isso cria um problema para desenvolvedores de aplicativos quando eles precisam escrever códigos para vários DBMSs. Uma declaração chamável é um método para executar procedimentos armazenados usando a mesma sintaxe SQL em todos os sistemas DBMS.
Por que usar procedimentos armazenados?
Imagine que temos um aplicativo JDBC que precisa repetir, de forma eficiente, uma sequência de tarefas várias vezes. Uma opção é usar um método Java, mas quantas vezes queremos fazer comunicação cliente/servidor para enviar e receber dados? O servidor de banco de dados prepara e gera um plano de consulta para cada instrução SQL enviada pelo aplicativo, o que consome tempo de CPU. Ao levar o desempenho em consideração, o uso de métodos Java simples com instruções SQL únicas pode ser uma má ideia.
Que tal usar um procedimento armazenado, que é uma tarefa realizada apenas uma vez? Crie uma única chamada SQL, que pode ser chamada a partir do aplicativo JDBC usando o objeto CallableStatement
, que age como responsável pela chamada para o procedimento armazenado no servidor. A maior parte da lógica de negócios irá residir no procedimento armazenado. Isso ajuda a simplificar o código do cliente e acelera a execução, pois as instruções SQL incluídas no procedimento armazenado foram preparadas e otimizadas quando o procedimento foi criado.
Chamando procedimentos armazenados em aplicativos JDBC
O driver JDBC Informix fornece os métodos Statement
, PreparedStatement
e CallableStatement
, que podem ser usados para executar procedimentos armazenados. O método a ser usado depende das características do procedimento armazenado. Por exemplo, se o procedimento retorna um único valor, você deve usar um objeto JDBC Statement
. A tabela a seguir contém algumas diretrizes para escolher o método a ser usado para cada tipo de procedimento armazenado.
Tabela 1. A Tabela mostra qual método JDBC usar com base no respectivo tipo de procedimento armazenado
Tipo de procedimento armazenado | Método JDBC |
---|---|
O procedimento armazenado não requer parâmetros IN ou OUT . |
Use um objeto Statement |
Procedimento armazenado com parâmetro IN . |
Use um objeto PreparedStatement |
Procedimento armazenado com parâmetros IN e OUT . |
Use um objeto CallableStatement |
Daremos alguns exemplos da execução de um procedimento armazenado usando métodos JDBC Informix nos seguintes casos:
- Uso de
Statement
sem parâmetros - Uso de
PreparedStatement
com parâmetros de entrada - Uso de
CallableStatement
com parâmetros de saída - Parâmetros nomeados em
CallableStatement
- Procedimentos armazenados sobrecarregados
Cada tópico mencionado acima terá os seguintes detalhes:
- Sintaxe usada para chamar o procedimento armazenado a partir do procedimento do driver JDBC Informix
- O esquema do procedimento armazenado no banco de dados
- Programa do driver JDBC de amostra com a saída
Uso do objeto Statement
sem parâmetros
A Listagem 1 mostra a sintaxe usada para executar um procedimento armazenado sem parâmetros IN
ou OUT
.
Lista 1. Sintaxe usada para executar um procedimento armazenado sem parâmetros IN
ou OUT
.
{call procedure-name}
Não é necessário usar o objeto CallableStatement
com esse tipo de procedimento armazenado. É possível usar uma instrução JDBC simples. O código na Listagem 2 mostra a definição do procedimento armazenado usando SQL Informix. O exemplo a seguir, GetCustName, retorna um único conjunto de resultados que contém uma coluna de dados, uma combinação do nome e sobrenome dos cinco primeiros contatos na tabela de clientes.
Lista 2. Definição do procedimento armazenado usando o objeto Statement
sem parâmetros
CREATE PROCEDURE GETCUSTNAME() RETURNING LVARCHAR AS NAME; DEFINE W_NAME LVARCHAR; FOREACH SELECT FIRST 5 FNAME || ' ' || LNAME INTO W_NAME FROM CUSTOMER RETURN W_NAME WITH RESUME; END FOREACH; END PROCEDURE;
OBSERVAÇÃO: Essa amostra de procedimento armazenado (e todas as outras deste artigo) usa as tabelas criadas no banco de dados de demonstração stores_demo, que é uma opção selecionável durante a instalação do Informix IDS.
A Listagem 3 mostra o código Java JDBC para execução do procedimento armazenado.
Lista 3. Código JDBC usando o objeto Statement
sem parâmetros
public static void executeStoredprocNoParams(Connection con) { try { Statement stmt = con.createStatement(); ResultSet rs = stmt.executeQuery("{call GETCUSTNAME}"); while (rs.next()) { System.out.println(rs.getString("Name")); } rs.close(); stmt.close(); } catch (Exception e) { e.printStackTrace(); } }
Como o procedimento armazenado retorna um conjunto de resultados, é necessário usar o método Statement.executeQuery()
e buscar nos resultados usando ResultSet.next()
. A Listagem 4 mostra a saída do seguinte programa.
Lista 4. Saída do programa da Listagem 3
$java sample_Stored procedure_1 Ludwig Pauli Carole Sadler Philip Currie Anthony Higgins Raymond Vector George Watson $
Uso de PreparedStatement
com parâmetros de entrada
Esse é o mais comum dos procedimentos armazenados. Um procedimento armazenado requer que parâmetros lhe passem dados para processamento interno. Devemos usar um PreparedStatement
para lidar com esse tipo de procedimento armazenado. A sintaxe é exibida na Listagem 5.
Lista 5. Sintaxe usada para execução de procedimentos armazenados usando instruções preparadas com parâmetros de entrada
{call procedure-name(?,?,...)}
Embora seja possível usar a sintaxe SQL do Informix para executar o procedimento armazenado (por exemplo, executar o procedimento procedure_name(?,?)
), recomendamos continuar com a sintaxe da sequência de escape SQL.
Os pontos de interrogação (?
) correspondem a cada um dos parâmetros de entrada exigidos pelo procedimento armazenado. Eles agem como marcador para os valores passados para o procedimento armazenado.
Para especificar um valor para o parâmetro, é possível usar um dos métodos setter na classe IfxPreparedStatement
(p.ex., pstmt.setInt()
). O método setter que é possível usar é determinado pelo tipo de dados do parâmetro IN
. Além do valor do parâmetro, o método setter obtém a posição do parâmetro na instrução SQL.
Lista 6. Exemplo mostrando um procedimento armazenado com uma assinatura
UPDMANU(INT MN_CODE, CHAR(10) MN_NAME, DATE MN_UPD);
Lista 7. Fragmento de código Java deve ser usado no caso do exemplo mostrado na Listagem 6
pstmt = con.prepareStatement("{call updmanu(?,?,?)}"); pstmt.setInt(1, manu_id); pstmt.setString(2, manu_code); pstmt.setDate(3, manu_date);
A Listagem 8 mostra o seguinte procedimento armazenado para demonstrar como usar os parâmetros IN
.
Lista 8. Exemplo de procedimento armazenado demonstrando como usar os parâmetros IN
.
CREATE PROCEDURE GETORDERS(CUS_ID INT) RETURNING INT AS ORDER_NUM, DATE AS ORDER_DATE, LVARCHAR AS SHINSTRUC; DEFINE W_ORDERN INT; DEFINE W_ORDERD DATE; DEFINE W_SHIP LVARCHAR; FOREACH SELECT ORDER_NUM,ORDER_DATE,SHIP_INSTRUCT INTO W_ORDERN,W_ORDERD,W_SHIP FROM ORDERS WHERE ORDERS.CUSTOMER_NUM=CUS_ID RETURN W_ORDERN,W_ORDERD,W_SHIP WITH RESUME; END FOREACH; END PROCEDURE;
Esse procedimento armazenado aceita um único parâmetro de entrada, chamado CUS_ID
, que é um valor de número inteiro, e retorna uma lista dos pedidos para esse customer_id. O código Java para chamar esse procedimento armazenado está na Listagem 9.
Lista 9. Código Java para execução de procedimento armazenado usando instruções preparadas com parâmetros de entrada
public static void executeSprocInParams(Connection con, int c_id) { try { PreparedStatement pstmt = con.prepareStatement("{call getorders(?)}"); pstmt.setInt(1, c_id); ResultSet rs = pstmt.executeQuery(); while (rs.next()) { System.out.println("Order Number\t: "+rs.getString("order_num")); System.out.println("Order Date\t: "+rs.getString("order_date")); System.out.println("Instructions\t: "+rs.getString("shinstruc")); System.out.println(); } rs.close(); pstmt.close(); } catch (Exception e) { e.printStackTrace(); } }
Executando o método executeSprocInParams()
, podemos ver a saída, como mostra a Listagem 10.
Lista 10. Saída do programa mostrado na Listagem 9
$java sample_Stored procedure_2 Order Number : 1001 Order Date : 2008-05-20 Instructions : express Order Number : 1003 Order Date : 2008-05-22 Instructions : express Order Number : 1011 Order Date : 2008-06-18 Instructions : express Order Number : 1013 Order Date : 2008-06-22 Instructions : express $
Uso de CallableStatement
com parâmetro de saída
Se o procedimento armazenado exigir o uso de parâmetros IN
ou OUT
, é necessário usar um JDBC CallableStatement
para lidar com os parâmetros. Apenas a classe IfxCallableStatement
(que é uma extensão de CallableStatement
de Java) pode lidar com parâmetros IN
e OUT
.
Em seguida, iremos demonstrar como chamar um procedimento armazenado que retorna um ou mais parâmetros OUT
. Esses são os parâmetros que o procedimento armazenado usa para retornar dados para o aplicativo de chamada como valores individuais, e não um conjunto de resultados, como vimos antes. A sintaxe SQL usada para procedimentos armazenados IN/OUT
é semelhante ao que mostramos antes na Lista 5.
Lista 11. Sintaxe usada para execução de procedimentos armazenados usando instruções chamáveis com parâmetros de saída
{call procedure-name(?,?,...)}
É necessário seguir a ordem correta para o parâmetro (IN
e OUT
). Os valores dos parâmetros OUT
devem ser registrados usando o método registerOutParameter()
da classe CallableStatement
. Cada parâmetro OUT
deve ser especificado na ordem correta. Assim como com o parâmetro IN
, o primeiro parâmetro desse método é o ordinal (ou posição) do parâmetro — cstmt.registerOutParameter(2, Types.INTEGER);, por exemplo.
O valor especificado para o parâmetro OUT
no método registerOutParameter
deve ser um dos tipos de dados JDBC Informix contidos em java.sql.Types, convertido internamente para um dos tipos de dados IDS nativos.
Para este exemplo, usaremos o seguinte procedimento armazenado, que usa a tabela “items” do banco de dados stores_demo, como mostra a Listagem 12.
Lista 12. Procedimento armazenado usado para demonstrar como usar os parâmetros OUT
CREATE PROCEDURE GETTOTAL(ORDER_ID INT, OUT TOTALPRICE MONEY); LET TOTALPRICE=(SELECT SUM(TOTAL_PRICE) FROM ITEMS WHERE ORDER_NUM=ORDER_ID); END PROCEDURE;
Esse procedimento armazenado retorna um único parâmetro OUT
(TotalPrice), que é um número inteiro, baseado no parâmetro IN
especificado (Order_ID), que também é um número inteiro. O valor retornado no parâmetro OUT
é a soma de todos os itens de um número de pedido específico contidos na tabela de itens.
Lista 13. Código Java para execução de procedimento armazenado usando instruções chamáveis sem parâmetros de saída
public static void executeStoredProcOUTParams(Connection con,int o_id) { try { CallableStatement cstmt = con.prepareCall("{call GetTotal(?, ?)}"); cstmt.setInt(1, o_id); cstmt.registerOutParameter(2, Types.INTEGER); cstmt.execute(); System.out.println("Total price for order" + o_id +"is $"+cstmt.getInt(2)); } catch (Exception e) { e.printStackTrace(); } }
Parâmetros nomeados em um CallableStatement
No exemplo anterior, usamos a posição para identificar cada um dos parâmetros no procedimento armazenado. É possível identificar os parâmetros por nome, o que torna o código do aplicativo mais limpo e mais fácil de ler.
O exemplo a seguir demonstra como usar parâmetros nomeados em um aplicativo Java. Observe que os nomes de parâmetro correspondem aos nomes de parâmetro na definição do procedimento armazenado.
Lista 14. Como usar parâmetros nomeados em um aplicativo Java
public static void executeStoredProcOUTParams(Connection con,int o_id) { try { CallableStatement cstmt = con.prepareCall("{call GetTotal(?, ?)}"); cstmt.setInt("Order_ID", o_id); cstmt.registerOutParameter("TotalPrice", Types.INTEGER); cstmt.execute(); System.out.println("Total price for order"+ o_id +"is $"+cstmt.getInt("TotalPrice")); } catch (Exception e) { e.printStackTrace(); } }
Os parâmetros devem ser indicados por índice ou por nome. Não é possível misturar os dois métodos. Essas duas amostras Java produzem a saída a seguir, mostrando o preço total de um pedido específico:
Lista 15. Saída das duas amostras de Java nas Listagens 13 e 14
$java sample_Stored procedure_3 Total price for order 1002 is $1200 $
OBSERVAÇÃO: Esses exemplos usam o método execute()
da classe CallableStatement
para executar o procedimento armazenado. Isso é usado porque o procedimento armazenado não retornou um conjunto de resultados. Se retornou, o método executeQuery()
deve ser usado, como no exemplo a seguir.
Lista 16. Procedimento armazenado usado para ilustrar quando usar o método executeQuery()
CREATE PROCEDURE GETTOTALBYMANU(CODE CHAR(3), OUT TOTAL MONEY) RETURNING CHAR(3) AS MANU_CODE, CHAR(10) AS MANU_NAME; DEFINE W_MANU_CODE CHAR(3); DEFINE W_MANU_NAME CHAR(10); LET TOTAL=(SELECT SUM(TOTAL_PRICE) FROM ITEMS WHERE MANU_CODE=CODE); SELECT MANU_CODE,MANU_NAME INTO W_MANU_CODE,W_MANU_NAME FROM MANUFACT WHERE MANU_CODE=CODE; RETURN W_MANU_CODE,W_MANU_NAME; END PROCEDURE;
O método na Listagem 17 usa executeQuery()
para chamar o procedimento armazenado GetTotalByManu
.
Lista 17. Código Java usado para ilustrar o uso do método executeQuery()
public static void executeStoredProcOUTParamsResulset(Connection con,String manu_id) { try { CallableStatement cstmt = con.prepareCall("{ call gettotalbymanu(?,?)}"); cstmt.setString(1, manu_id); cstmt.registerOutParameter(2, Types.CHAR); ResultSet rs = cstmt.executeQuery(); rs.next(); System.out.println("Total for manufacturer '"+rs.getString(2).trim()+ " ("+rs.getString(1)+") ' is $"+cstmt.getInt(2)); } catch (Exception e) { e.printStackTrace(); }
A saída do programa mostrado na Listagem 17 é mostrada na Listagem 18.
Lista 18. Saída do programa de exemplo Java da Listagem 17
$java sample_Stored procedure_4 Total for manufacturer 'Hero (HRO)' is $2882 $
OBSERVAÇÃO: Se você não sabe como o procedimento armazenado foi definido, pode usar rotinas de metadados JDBC para obter informações sobre o procedimento, como o nome e o tipo dos parâmetros que ele aceita.
O exemplo a seguir usa o método getProcedureColumns()
para obter o nome e o tipo do procedimento gettotalbymanu
.
Lista 19. Código Java
public static void executeStoredGetOutParams(Connection con,String procname) { try { DatabaseMetaData dbmd = con.getMetaData(); ResultSet rs = dbmd.getProcedureColumns("stores7","",procname.toUpperCase(),null); while (rs.next()) if (rs.getShort("COLUMN_TYPE")==DatabaseMetaData.procedureColumnOut) { System.out.println("OUT Parame: "+ rs.getString("COLUMN_NAME")); System.out.println("Type: "+rs.getString("DATA_TYPE") ); } } catch (Exception e) { e.printStackTrace(); } }
Outra alternativa possível é verificar se o procedimento armazenado tem parâmetros OUT
usando o método CallableStatement.hasOutParameter()
. Se tiver sido definido com parâmetros OUT
, ele retorna TRUE, como mostra a Listagem 20.
Lista 20. Código Java de amostra
CallableStatement cstmt = con.prepareCall("{ call gettotalbymanu(?,?)}"); if (((IfxCallableStatement) cstmt).hasOutParameter()) System.out.println("Stored procedure has OUT parameters "); // perform the logic
Chamando procedimentos armazenados com mais de uma assinatura
O servidor de banco de dados Informix suporta procedimentos armazenados sobrecarregados. É possível ter procedimentos armazenados com o mesmo nome mas com parâmetros (ou assinaturas) diferentes, para realizarem operações diferentes com base no parâmetro recebido. Um exemplo básico são os procedimentos mostrados na Lista 21 e Lista 22.
Lista 21. Definição de procedimento armazenado nº 1
CREATE PROCEDURE GETORDERS(CUS_ID INT) RETURNING INT AS ORDER_NUM, DATE AS ORDER_DATE, LVARCHAR AS SHINSTRUC; DEFINE W_ORDERN INT; DEFINE W_ORDERD DATE; DEFINE W_SHIP LVARCHAR; FOREACH SELECT ORDER_NUM,ORDER_DATE,SHIP_INSTRUCT INTO W_ORDERN,W_ORDERD,W_SHIP FROM ORDERS WHERE ORDERS.CUSTOMER_NUM=CUS_ID RETURN W_ORDERN,W_ORDERD,W_SHIP WITH RESUME; END FOREACH; END PROCEDURE;
Lista 22. Definição de procedimento armazenado nº 2
CREATE PROCEDURE GETORDERS(ORD_DATE DATE) RETURNING INT AS ORDER_NUM, DATE AS ORDER_DATE, LVARCHAR AS SHINSTRUC; DEFINE W_ORDERN INT; DEFINE W_ORDERD DATE; DEFINE W_SHIP LVARCHAR; FOREACH SELECT ORDER_NUM,ORDER_DATE,SHIP_INSTRUCT INTO W_ORDERN,W_ORDERD,W_SHIP FROM ORDERS WHERE ORDERS.ORDER_DATE=ORD_DATE RETURN W_ORDERN,W_ORDERD,W_SHIP WITH RESUME; END FOREACH; END PROCEDURE;
Ambos têm o mesmo nome (GETORDERS), mas o primeiro usa um parâmetro INT
para obter os pedidos de um cliente específico, e o segundo tem um parâmetro DATE
para retornar os pedidos para uma data específica, como mostra a Listagem 23.
Lista 23. Exemplo para mais de uma assinatura
CREATE PROCEDURE GETORDERS(CUS_ID INT) RETURNING INT AS ORDER_NUM, DATE AS ORDER_DATE, LVARCHAR AS SHINSTRUC; CREATE PROCEDURE GETORDERS(ORD_DATE DATE) RETURNING INT AS ORDER_NUM, DATE AS ORDER_DATE, LVARCHAR AS SHINSTRUC;
Para executar esses procedimentos armazenados em um aplicativo JDBC, é necessário fornecer o tipo de parâmetro na sintaxe SQL para que o mecanismo Informix saiba qual procedimento deve ser executado. Use o prefixo ::datatype
no marcador, como mostra a Listagem 24.
Lista 24. Exemplos de marcador
{call getorders(?::INT)} {call getorders(?::DATE)}
A Listagem 25 mostra como executar o procedimento GETORDERS(DATE)
.
Lista 25. Código Java para demonstrar como usar procedimentos armazenados com mais de uma assinatura
public static void executeSprocInParams_date(Connection con, String o_date) { try { PreparedStatement pstmt = con.prepareStatement("{call getorders(?::DATE)}"); pstmt.setString(1, o_date); ResultSet rs = pstmt.executeQuery(); while (rs.next()) { System.out.println("Order Number\t: "+rs.getString("order_num")); System.out.println("Order Date\t: "+rs.getString("order_date")); System.out.println("Instructions\t: "+rs.getString("shinstruc")); System.out.println(); } rs.close(); pstmt.close(); } catch (Exception e) { e.printStackTrace(); } }
Conclusão
Este artigo mostrou várias maneiras de acessar procedimentos armazenados simples e complexos a partir de um aplicativo JDBC. Os links para a documentação do servidor e do JDBC na seção Recursos darão assistência para que você explore os exemplos de código e desenvolva procedimentos armazenados eficientes e o aplicativo JDBC equivalente. Tudo o que você aprendeu neste artigo deve ajudá-lo a transferir uma lógica de negócios complexa para procedimentos armazenados e exportá-los para um aplicativo JDBC.
Recursos
Aprender
- “Informix JDBC Driver Guide” é um guia passo a passo para aprender as APIs do driver JDBC.
- Saiba mais sobre tipos de dados JDBC Informix e IDS.
- No Centro de Informações Informix 11.70, obtenha os recursos necessários para ampliar suas habilidades no Informix.
- Leia o “IBM Informix Developer’s Handbook” de IBM Redbooks para se atualizar rapidamente sobre o produto IBM Informix.
- Saiba mais sobre o Information Management na zona do Information Management no developerWorks. Encontre documentação técnica, artigos de instruções, treinamento, downloads, informações de produtos, e muito mais.
- Fique por dentro dos eventos e webcasts técnicos do developerWorks.
- Siga os tweets do developerWorks no Twitter.
Obter produtos e tecnologias
- Crie seu próximo projeto de desenvolvimento com o software de avaliação da IBM, disponível para download diretamente no developerWorks.
- Agora é possível usar o DB2 gratuitamente. Faça o download do O DB2 Express-C, uma versão gratuita do DB2 Express Edition para a comunidade que oferece os mesmos recursos de dados centrais que o DB2 Express Edition e fornece uma base sólida para desenvolver e implementar aplicativos.
Discutir
Confira a blogs do developerWorks e participe da Comunidade do developerWorks.
Sobre os autores
Babita Sonavane é desenvolvedora de software no IBM India Lab, em Bangalore. Babita tem mais de cinco anos de experiência em TI. Ela trabalha ativamente na manutenção e desenvolvimento de vários recursos nos drivers JDBC Informix e DB2.
Javier Sagrera é engenheiro de software no grupo Common Client Technologies (CCT). Ele passou a fazer parte da equipe Informix em 2000 e tem mais de 10 anos de experiência em desenvolvimento de aplicativos para servidores de bancos de dados e clientes Informix. Trabalhando atualmente no IBM UK Bedfont Lab, em Londres, ele tem conhecimento extenso sobre todas as tecnologias Microsoft® e é considerado especialista mundial em todas as ferramentas de desenvolvimento Informix.
***
Artigo original disponível em http://www.ibm.com/developerworks/br/data/library/techarticle/dm-1209storedprocedures/index.html